diff --git a/channeldb/db.go b/channeldb/db.go index fa93bc19a..194743334 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -19,9 +19,9 @@ const ( dbFilePermission = 0600 ) -// 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. +// 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 *bolt.Tx) error type version struct { @@ -30,19 +30,20 @@ type version struct { } var ( - // DBVersions is storing all versions of database. If current version of - // database don't match with latest version this list will be used for - // retrieving all migration function that are need to apply to the + // dbVersions is storing all versions of database. If current version + // of database don't match with latest version this list will be used + // for retrieving all migration function that are need to apply to the // current db. - DBVersions = []version{ + dbVersions = []version{ { + // The base DB version requires no migration. number: 1, - migration: nil, // The base DB version requires no migration + migration: nil, }, } - // Big endian is the preferred byte order, due to cursor scans over integer - // keys iterating in order. + // Big endian is the preferred byte order, due to cursor scans over + // integer keys iterating in order. byteOrder = binary.BigEndian ) @@ -61,7 +62,6 @@ type DB struct { // Open opens an existing channeldb created under the passed namespace with // sensitive data encrypted by the passed EncryptorDecryptor implementation. -// TODO(roasbeef): versioning? func Open(dbPath string, netParams *chaincfg.Params) (*DB, error) { path := filepath.Join(dbPath, dbName) @@ -76,11 +76,19 @@ func Open(dbPath string, netParams *chaincfg.Params) (*DB, error) { return nil, err } - return &DB{ + chanDB := &DB{ store: bdb, netParams: netParams, dbPath: dbPath, - }, nil + } + + // Synchronize the version of database and apply migrations if needed. + if err := chanDB.syncVersions(dbVersions); err != nil { + bdb.Close() + return nil, err + } + + return chanDB, nil } // Wipe completely deletes all saved state within all used buckets within the @@ -299,42 +307,44 @@ func (d *DB) FetchAllChannels() ([]*OpenChannel, error) { return channels, err } -// SyncVersions function is used for safe db version synchronization. It applies +// syncVersions function is used for safe db version synchronization. It applies // migration functions to the current database and recovers the previous // state of db if at least one error/panic appeared during migration. -func (d *DB) SyncVersions(versions []version) error { +func (d *DB) syncVersions(versions []version) error { meta, err := d.FetchMeta(nil) if err != nil { return err } + // If the current database version matches the latest version number, + // then we don't need to perform any migrations. latestVersion := getLatestDBVersion(versions) - - if meta.dbVersionNumber < latestVersion { - migrations := getMigrationsToApply(versions, meta.dbVersionNumber) - - return d.store.Update(func(tx *bolt.Tx) error { - for _, migration := range migrations { - if migration == nil { - continue - } - - if err := migration(tx); err != nil { - return err - } - } - - meta.dbVersionNumber = latestVersion - if err := d.PutMeta(meta, tx); err != nil { - return err - } - - return nil - }) - + if meta.DbVersionNumber == latestVersion { + return nil } - return nil + // Otherwise, we fetch the migrations which need to applied, and + // execute them serially within a single database transaction to ensure + // the migration is atomic. + migrations := getMigrationsToApply(versions, meta.DbVersionNumber) + return d.store.Update(func(tx *bolt.Tx) error { + for _, migration := range migrations { + if migration == nil { + continue + } + + if err := migration(tx); err != nil { + return err + } + } + + meta.DbVersionNumber = latestVersion + if err := d.PutMeta(meta, tx); err != nil { + return err + } + + return nil + }) } func getLatestDBVersion(versions []version) uint32 { diff --git a/channeldb/meta.go b/channeldb/meta.go index 369e21b4f..b8239701e 100644 --- a/channeldb/meta.go +++ b/channeldb/meta.go @@ -9,14 +9,15 @@ var ( // the database. metaBucket = []byte("metadata") - // dbVersionKey is a boltdb key and it's used for storing/retrieveing + // dbVersionKey is a boltdb key and it's used for storing/retrieving // current database version. dbVersionKey = []byte("dbp") ) // Meta structure holds the database meta information. type Meta struct { - dbVersionNumber uint32 + // DbVersionNumber is the current schema version of the database. + DbVersionNumber uint32 } // FetchMeta fetches the meta data from boltdb and returns filled meta @@ -74,7 +75,7 @@ func (d *DB) PutMeta(meta *Meta, tx *bolt.Tx) error { func putDbVersion(metaBucket *bolt.Bucket, meta *Meta) error { scratch := make([]byte, 4) - byteOrder.PutUint32(scratch, meta.dbVersionNumber) + byteOrder.PutUint32(scratch, meta.DbVersionNumber) if err := metaBucket.Put(dbVersionKey, scratch); err != nil { return err } @@ -83,8 +84,8 @@ func putDbVersion(metaBucket *bolt.Bucket, meta *Meta) error { func fetchDbVersion(metaBucket *bolt.Bucket, meta *Meta) { if data := metaBucket.Get(dbVersionKey); data != nil { - meta.dbVersionNumber = byteOrder.Uint32(data) + meta.DbVersionNumber = byteOrder.Uint32(data) } else { - meta.dbVersionNumber = getLatestDBVersion(DBVersions) + meta.DbVersionNumber = getLatestDBVersion(dbVersions) } } diff --git a/channeldb/meta_test.go b/channeldb/meta_test.go index ccf0bae53..99a290385 100644 --- a/channeldb/meta_test.go +++ b/channeldb/meta_test.go @@ -2,9 +2,10 @@ package channeldb import ( "bytes" + "testing" + "github.com/boltdb/bolt" "github.com/go-errors/errors" - "testing" ) // TestVersionFetchPut checks the propernces of fetch/put methods @@ -22,12 +23,12 @@ func TestVersionFetchPut(t *testing.T) { t.Fatal(err) } - if meta.dbVersionNumber != getLatestDBVersion(DBVersions) { + if meta.DbVersionNumber != getLatestDBVersion(dbVersions) { t.Fatal("initialization of meta information wasn't performed") } - var newVersion uint32 = getLatestDBVersion(DBVersions) + 1 - meta.dbVersionNumber = newVersion + var newVersion uint32 = getLatestDBVersion(dbVersions) + 1 + meta.DbVersionNumber = newVersion if err := db.PutMeta(meta, nil); err != nil { t.Fatalf("update of meta failed %v", err) @@ -38,7 +39,7 @@ func TestVersionFetchPut(t *testing.T) { t.Fatal(err) } - if meta.dbVersionNumber != newVersion { + if meta.DbVersionNumber != newVersion { t.Fatal("update of meta information wasn't performed") } } @@ -87,17 +88,17 @@ func TestOrderOfMigrations(t *testing.T) { // TestGlobalVersionList checks that there is no mistake in global version list // in terms of version ordering. func TestGlobalVersionList(t *testing.T) { - if DBVersions == nil { + if dbVersions == nil { t.Fatal("can't find versions list") } - if len(DBVersions) == 0 { + if len(dbVersions) == 0 { t.Fatal("db versions list is empty") } - prev := DBVersions[0].number - for i := 1; i < len(DBVersions); i++ { - version := DBVersions[i].number + prev := dbVersions[0].number + for i := 1; i < len(dbVersions); i++ { + version := dbVersions[i].number if version == prev { t.Fatal("duplicates db versions") @@ -127,7 +128,7 @@ func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB), // Create test meta info with zero database version and put it on disk. // Than creating the version list pretending that new version was added. - meta := &Meta{dbVersionNumber: 0} + meta := &Meta{DbVersionNumber: 0} cdb.PutMeta(meta, nil) versions := []version{ @@ -158,7 +159,7 @@ func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB), }() // Sync with the latest version - applying migration function. - err = cdb.SyncVersions(versions) + err = cdb.syncVersions(versions) } func TestMigrationWithPanic(t *testing.T) { @@ -200,7 +201,7 @@ func TestMigrationWithPanic(t *testing.T) { t.Fatal(err) } - if meta.dbVersionNumber != 0 { + if meta.DbVersionNumber != 0 { t.Fatal("migration paniced but version is changed") } @@ -268,7 +269,7 @@ func TestMigrationWithFatal(t *testing.T) { t.Fatal(err) } - if meta.dbVersionNumber != 0 { + if meta.DbVersionNumber != 0 { t.Fatal("migration failed but version is changed") } @@ -335,7 +336,7 @@ func TestMigrationWithoutErrors(t *testing.T) { t.Fatal(err) } - if meta.dbVersionNumber != 1 { + if meta.DbVersionNumber != 1 { t.Fatal("version number isn't changed after " + "succesfully aplied migration") } diff --git a/lnd.go b/lnd.go index d9f5f3f6a..51f6e8a97 100644 --- a/lnd.go +++ b/lnd.go @@ -67,12 +67,6 @@ func lndMain() error { } defer chanDB.Close() - // Synchronize the version of database and apply migration if needed. - if err := chanDB.SyncVersions(channeldb.DBVersions); err != nil { - fmt.Println("unable to sync versions: ", err) - return err - } - // Next load btcd's TLS cert for the RPC connection. If a raw cert was // specified in the config, then we'll se that directly. Otherwise, we // attempt to read the cert from the path specified in the config.