mirror of
https://github.com/lightninglabs/pool.git
synced 2026-08-13 12:33:04 +02:00
181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
package clientdb
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
|
"github.com/lightninglabs/pool/clientdb/migrations"
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
// migration is a function which takes a prior outdated version of the database
|
|
// instance 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
|
|
|
|
var (
|
|
// metadataBucketKey stores all the metadata concerning the state of the
|
|
// database.
|
|
metadataBucketKey = []byte("metadata")
|
|
|
|
// dbVersionKey is the key used for storing/retrieving the current
|
|
// database version.
|
|
dbVersionKey = []byte("version")
|
|
|
|
// lockIDKey is the database key used for storing/retrieving the global
|
|
// lock ID to use when leasing outputs from the backing lnd node's
|
|
// wallet. This is mostly required so that calls to LeaseOutput are
|
|
// idempotent when attempting to lease an output we already have a lease
|
|
// for.
|
|
lockIDKey = []byte("lock-id")
|
|
|
|
// ErrDBReversion is returned when detecting an attempt to revert to a
|
|
// prior database version.
|
|
ErrDBReversion = errors.New("cannot revert to prior version")
|
|
|
|
// 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 = []migration{
|
|
migrations.AddInitialOrderTimestamps,
|
|
}
|
|
|
|
latestDBVersion = uint32(len(dbVersions))
|
|
)
|
|
|
|
// getDBVersion retrieves the current database version.
|
|
func getDBVersion(bucket *bbolt.Bucket) (uint32, error) {
|
|
versionBytes := bucket.Get(dbVersionKey)
|
|
if versionBytes == nil {
|
|
return 0, errors.New("database version not found")
|
|
}
|
|
return byteOrder.Uint32(versionBytes), nil
|
|
}
|
|
|
|
// setDBVersion updates the current database version.
|
|
func setDBVersion(bucket *bbolt.Bucket, version uint32) error {
|
|
var b [4]byte
|
|
byteOrder.PutUint32(b[:], version)
|
|
return bucket.Put(dbVersionKey, b[:])
|
|
}
|
|
|
|
// getBucket retrieves the bucket with the given key.
|
|
func getBucket(tx *bbolt.Tx, key []byte) (*bbolt.Bucket, error) {
|
|
bucket := tx.Bucket(key)
|
|
if bucket == nil {
|
|
return nil, fmt.Errorf("bucket \"%v\" does not exist",
|
|
string(key))
|
|
}
|
|
return bucket, nil
|
|
}
|
|
|
|
// getNestedBucket retrieves the nested bucket with the given key found within
|
|
// the given bucket. If the bucket does not exist and `create` is true, then the
|
|
// bucket is created.
|
|
func getNestedBucket(bucket *bbolt.Bucket, key []byte,
|
|
create bool) (*bbolt.Bucket, error) {
|
|
|
|
nestedBucket := bucket.Bucket(key)
|
|
if nestedBucket == nil && create {
|
|
return bucket.CreateBucketIfNotExists(key)
|
|
}
|
|
if nestedBucket == nil {
|
|
return nil, fmt.Errorf("nested bucket \"%v\" does not exist",
|
|
string(key))
|
|
}
|
|
return nestedBucket, nil
|
|
}
|
|
|
|
// 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 syncVersions(db *bbolt.DB) error {
|
|
var currentVersion uint32
|
|
err := db.View(func(tx *bbolt.Tx) error {
|
|
metadata, err := getBucket(tx, metadataBucketKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
currentVersion, err = getDBVersion(metadata)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Checking for schema update: latest_version=%v, "+
|
|
"db_version=%v", latestDBVersion, currentVersion)
|
|
|
|
switch {
|
|
// If the database reports a higher version that we are aware of, the
|
|
// user is probably trying to revert to a prior version of lnd. We fail
|
|
// here to prevent reversions and unintended corruption.
|
|
case currentVersion > latestDBVersion:
|
|
log.Errorf("Refusing to revert from db_version=%d to "+
|
|
"lower version=%d", currentVersion,
|
|
latestDBVersion)
|
|
|
|
return ErrDBReversion
|
|
|
|
// If the current database version matches the latest version number,
|
|
// then we don't need to perform any migrations.
|
|
case currentVersion == latestDBVersion:
|
|
return nil
|
|
}
|
|
|
|
log.Infof("Performing database schema migration")
|
|
|
|
// Otherwise we execute the migrations serially within a single database
|
|
// transaction to ensure the migration is atomic.
|
|
return db.Update(func(tx *bbolt.Tx) error {
|
|
for v := currentVersion; v < latestDBVersion; v++ {
|
|
log.Infof("Applying migration #%v", v+1)
|
|
|
|
migration := dbVersions[v]
|
|
if err := migration(tx); err != nil {
|
|
log.Infof("Unable to apply migration #%v", v+1)
|
|
return err
|
|
}
|
|
}
|
|
|
|
metadata, err := getBucket(tx, metadataBucketKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return setDBVersion(metadata, latestDBVersion)
|
|
})
|
|
}
|
|
|
|
// storeRandomLockID generates a random lock ID backed by the system's CSPRNG
|
|
// and stores it under the metadata bucket.
|
|
func storeRandomLockID(metadata *bbolt.Bucket) error {
|
|
var lockID wtxmgr.LockID
|
|
if _, err := rand.Read(lockID[:]); err != nil {
|
|
return err
|
|
}
|
|
return metadata.Put(lockIDKey, lockID[:])
|
|
}
|
|
|
|
// LockID retrieves the database's global lock ID used to lease outputs from the
|
|
// backing lnd node's wallet.
|
|
func (db *DB) LockID() (wtxmgr.LockID, error) {
|
|
var lockID wtxmgr.LockID
|
|
err := db.View(func(tx *bbolt.Tx) error {
|
|
metadata, err := getBucket(tx, metadataBucketKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lockIDBytes := metadata.Get(lockIDKey)
|
|
if lockIDBytes == nil {
|
|
return errors.New("lock ID not found")
|
|
}
|
|
|
|
copy(lockID[:], lockIDBytes)
|
|
return nil
|
|
})
|
|
return lockID, err
|
|
}
|