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  }