github.com/decred/dcrlnd@v0.7.6/watchtower/wtdb/version.go (about) 1 package wtdb 2 3 import ( 4 "github.com/decred/dcrlnd/channeldb" 5 "github.com/decred/dcrlnd/kvdb" 6 ) 7 8 // migration is a function which takes a prior outdated version of the database 9 // instances and mutates the key/bucket structure to arrive at a more 10 // up-to-date version of the database. 11 type migration func(tx kvdb.RwTx) error 12 13 // version pairs a version number with the migration that would need to be 14 // applied from the prior version to upgrade. 15 type version struct { 16 migration migration 17 } 18 19 // towerDBVersions stores all versions and migrations of the tower database. 20 // This list will be used when opening the database to determine if any 21 // migrations must be applied. 22 var towerDBVersions = []version{} 23 24 // clientDBVersions stores all versions and migrations of the client database. 25 // This list will be used when opening the database to determine if any 26 // migrations must be applied. 27 var clientDBVersions = []version{} 28 29 // getLatestDBVersion returns the last known database version. 30 func getLatestDBVersion(versions []version) uint32 { 31 return uint32(len(versions)) 32 } 33 34 // getMigrations returns a slice of all updates with a greater number that 35 // curVersion that need to be applied to sync up with the latest version. 36 func getMigrations(versions []version, curVersion uint32) []version { 37 var updates []version 38 for i, v := range versions { 39 if uint32(i)+1 > curVersion { 40 updates = append(updates, v) 41 } 42 } 43 44 return updates 45 } 46 47 // getDBVersion retrieves the current database version from the metadata bucket 48 // using the dbVersionKey. 49 func getDBVersion(tx kvdb.RTx) (uint32, error) { 50 metadata := tx.ReadBucket(metadataBkt) 51 if metadata == nil { 52 return 0, ErrUninitializedDB 53 } 54 55 versionBytes := metadata.Get(dbVersionKey) 56 if len(versionBytes) != 4 { 57 return 0, ErrNoDBVersion 58 } 59 60 return byteOrder.Uint32(versionBytes), nil 61 } 62 63 // initDBVersion initializes the top-level metadata bucket and writes the passed 64 // version number as the current version. 65 func initDBVersion(tx kvdb.RwTx, version uint32) error { 66 _, err := tx.CreateTopLevelBucket(metadataBkt) 67 if err != nil { 68 return err 69 } 70 71 return putDBVersion(tx, version) 72 } 73 74 // putDBVersion stores the passed database version in the metadata bucket under 75 // the dbVersionKey. 76 func putDBVersion(tx kvdb.RwTx, version uint32) error { 77 metadata := tx.ReadWriteBucket(metadataBkt) 78 if metadata == nil { 79 return ErrUninitializedDB 80 } 81 82 versionBytes := make([]byte, 4) 83 byteOrder.PutUint32(versionBytes, version) 84 return metadata.Put(dbVersionKey, versionBytes) 85 } 86 87 // versionedDB is a private interface implemented by both the tower and client 88 // databases, permitting all versioning operations to be performed generically 89 // on either. 90 type versionedDB interface { 91 // bdb returns the underlying bbolt database. 92 bdb() kvdb.Backend 93 94 // Version returns the current version stored in the database. 95 Version() (uint32, error) 96 } 97 98 // initOrSyncVersions ensures that the database version is properly set before 99 // opening the database up for regular use. When the database is being 100 // initialized for the first time, the caller should set init to true, which 101 // will simply write the latest version to the database. Otherwise, passing init 102 // as false will cause the database to apply any needed migrations to ensure its 103 // version matches the latest version in the provided versions list. 104 func initOrSyncVersions(db versionedDB, init bool, versions []version) error { 105 // If the database has not yet been created, we'll initialize the 106 // database version with the latest known version. 107 if init { 108 return kvdb.Update(db.bdb(), func(tx kvdb.RwTx) error { 109 return initDBVersion(tx, getLatestDBVersion(versions)) 110 }, func() {}) 111 } 112 113 // Otherwise, ensure that any migrations are applied to ensure the data 114 // is in the format expected by the latest version. 115 return syncVersions(db, versions) 116 } 117 118 // syncVersions ensures the database version is consistent with the highest 119 // known database version, applying any migrations that have not been made. If 120 // the highest known version number is lower than the database's version, this 121 // method will fail to prevent accidental reversions. 122 func syncVersions(db versionedDB, versions []version) error { 123 curVersion, err := db.Version() 124 if err != nil { 125 return err 126 } 127 128 latestVersion := getLatestDBVersion(versions) 129 switch { 130 131 // Current version is higher than any known version, fail to prevent 132 // reversion. 133 case curVersion > latestVersion: 134 return channeldb.ErrDBReversion 135 136 // Current version matches highest known version, nothing to do. 137 case curVersion == latestVersion: 138 return nil 139 } 140 141 // Otherwise, apply any migrations in order to bring the database 142 // version up to the highest known version. 143 updates := getMigrations(versions, curVersion) 144 return kvdb.Update(db.bdb(), func(tx kvdb.RwTx) error { 145 for i, update := range updates { 146 if update.migration == nil { 147 continue 148 } 149 150 version := curVersion + uint32(i) + 1 151 log.Infof("Applying migration #%d", version) 152 153 err := update.migration(tx) 154 if err != nil { 155 log.Errorf("Unable to apply migration #%d: %v", 156 version, err) 157 return err 158 } 159 } 160 161 return putDBVersion(tx, latestVersion) 162 }, func() {}) 163 }