mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
70e3f8f511
Define a MigrationConfig interface that should be used to pass the config of an optional migration function. An implementation of this interface is added for migration30 (the only current optional migration).
686 lines
17 KiB
Go
686 lines
17 KiB
Go
package channeldb
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcwallet/walletdb"
|
|
"github.com/go-errors/errors"
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// applyMigration is a helper test function that encapsulates the general steps
|
|
// which are needed to properly check the result of applying migration function.
|
|
func applyMigration(t *testing.T, beforeMigration, afterMigration func(d *DB),
|
|
migrationFunc migration, shouldFail bool, dryRun bool) {
|
|
|
|
cdb, err := MakeTestDB(t)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cdb.dryRun = dryRun
|
|
|
|
// Create a test node that will be our source node.
|
|
testNode, err := createTestVertex(cdb)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
graph := cdb.ChannelGraph()
|
|
if err := graph.SetSourceNode(testNode); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// beforeMigration usually used for populating the database
|
|
// with test data.
|
|
beforeMigration(cdb)
|
|
|
|
// 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}
|
|
if err := cdb.PutMeta(meta); err != nil {
|
|
t.Fatalf("unable to store meta data: %v", err)
|
|
}
|
|
|
|
versions := []mandatoryVersion{
|
|
{
|
|
number: 0,
|
|
migration: nil,
|
|
},
|
|
{
|
|
number: 1,
|
|
migration: migrationFunc,
|
|
},
|
|
}
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if dryRun && r != ErrDryRunMigrationOK {
|
|
t.Fatalf("expected dry run migration OK")
|
|
}
|
|
err = errors.New(r)
|
|
}
|
|
|
|
if err == nil && shouldFail {
|
|
t.Fatal("error wasn't received on migration stage")
|
|
} else if err != nil && !shouldFail {
|
|
t.Fatalf("error was received on migration stage: %v", err)
|
|
}
|
|
|
|
// afterMigration usually used for checking the database state and
|
|
// throwing the error if something went wrong.
|
|
afterMigration(cdb)
|
|
}()
|
|
|
|
// Sync with the latest version - applying migration function.
|
|
err = cdb.syncVersions(versions)
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|
|
|
|
// TestVersionFetchPut checks the propernces of fetch/put methods
|
|
// and also initialization of meta data in case if don't have any in
|
|
// database.
|
|
func TestVersionFetchPut(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, err := MakeTestDB(t)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
meta, err := db.FetchMeta()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if meta.DbVersionNumber != getLatestDBVersion(dbVersions) {
|
|
t.Fatal("initialization of meta information wasn't performed")
|
|
}
|
|
|
|
newVersion := getLatestDBVersion(dbVersions) + 1
|
|
meta.DbVersionNumber = newVersion
|
|
|
|
if err := db.PutMeta(meta); err != nil {
|
|
t.Fatalf("update of meta failed %v", err)
|
|
}
|
|
|
|
meta, err = db.FetchMeta()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if meta.DbVersionNumber != newVersion {
|
|
t.Fatal("update of meta information wasn't performed")
|
|
}
|
|
}
|
|
|
|
// TestOrderOfMigrations checks that migrations are applied in proper order.
|
|
func TestOrderOfMigrations(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
appliedMigration := -1
|
|
versions := []mandatoryVersion{
|
|
{0, nil},
|
|
{1, nil},
|
|
{2, func(tx kvdb.RwTx) error {
|
|
appliedMigration = 2
|
|
return nil
|
|
}},
|
|
{3, func(tx kvdb.RwTx) error {
|
|
appliedMigration = 3
|
|
return nil
|
|
}},
|
|
}
|
|
|
|
// Retrieve the migration that should be applied to db, as far as
|
|
// current version is 1, we skip zero and first versions.
|
|
migrations, _ := getMigrationsToApply(versions, 1)
|
|
|
|
if len(migrations) != 2 {
|
|
t.Fatal("incorrect number of migrations to apply")
|
|
}
|
|
|
|
// Apply first migration.
|
|
migrations[0](nil)
|
|
|
|
// Check that first migration corresponds to the second version.
|
|
if appliedMigration != 2 {
|
|
t.Fatal("incorrect order of applying migrations")
|
|
}
|
|
|
|
// Apply second migration.
|
|
migrations[1](nil)
|
|
|
|
// Check that second migration corresponds to the third version.
|
|
if appliedMigration != 3 {
|
|
t.Fatal("incorrect order of applying migrations")
|
|
}
|
|
}
|
|
|
|
// TestGlobalVersionList checks that there is no mistake in global version list
|
|
// in terms of version ordering.
|
|
func TestGlobalVersionList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if dbVersions == nil {
|
|
t.Fatal("can't find versions list")
|
|
}
|
|
|
|
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
|
|
|
|
if version == prev {
|
|
t.Fatal("duplicates db versions")
|
|
}
|
|
if version < prev {
|
|
t.Fatal("order of db versions is wrong")
|
|
}
|
|
|
|
prev = version
|
|
}
|
|
}
|
|
|
|
// TestMigrationWithPanic asserts that if migration logic panics, we will return
|
|
// to the original state unaltered.
|
|
func TestMigrationWithPanic(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bucketPrefix := []byte("somebucket")
|
|
keyPrefix := []byte("someprefix")
|
|
beforeMigration := []byte("beforemigration")
|
|
afterMigration := []byte("aftermigration")
|
|
|
|
beforeMigrationFunc := func(d *DB) {
|
|
// Insert data in database and in order then make sure that the
|
|
// key isn't changes in case of panic or fail.
|
|
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return bucket.Put(keyPrefix, beforeMigration)
|
|
}, func() {})
|
|
if err != nil {
|
|
t.Fatalf("unable to insert: %v", err)
|
|
}
|
|
}
|
|
|
|
// Create migration function which changes the initially created data and
|
|
// throw the panic, in this case we pretending that something goes.
|
|
migrationWithPanic := func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bucket.Put(keyPrefix, afterMigration)
|
|
panic("panic!")
|
|
}
|
|
|
|
// Check that version of database and data wasn't changed.
|
|
afterMigrationFunc := func(d *DB) {
|
|
meta, err := d.FetchMeta()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if meta.DbVersionNumber != 0 {
|
|
t.Fatal("migration panicked but version is changed")
|
|
}
|
|
|
|
err = kvdb.Update(d, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value := bucket.Get(keyPrefix)
|
|
if !bytes.Equal(value, beforeMigration) {
|
|
return errors.New("migration failed but data is " +
|
|
"changed")
|
|
}
|
|
|
|
return nil
|
|
}, func() {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
applyMigration(t,
|
|
beforeMigrationFunc,
|
|
afterMigrationFunc,
|
|
migrationWithPanic,
|
|
true,
|
|
false)
|
|
}
|
|
|
|
// TestMigrationWithFatal asserts that migrations which fail do not modify the
|
|
// database.
|
|
func TestMigrationWithFatal(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bucketPrefix := []byte("somebucket")
|
|
keyPrefix := []byte("someprefix")
|
|
beforeMigration := []byte("beforemigration")
|
|
afterMigration := []byte("aftermigration")
|
|
|
|
beforeMigrationFunc := func(d *DB) {
|
|
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return bucket.Put(keyPrefix, beforeMigration)
|
|
}, func() {})
|
|
if err != nil {
|
|
t.Fatalf("unable to insert pre migration key: %v", err)
|
|
}
|
|
}
|
|
|
|
// Create migration function which changes the initially created data and
|
|
// return the error, in this case we pretending that something goes
|
|
// wrong.
|
|
migrationWithFatal := func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bucket.Put(keyPrefix, afterMigration)
|
|
return errors.New("some error")
|
|
}
|
|
|
|
// Check that version of database and initial data wasn't changed.
|
|
afterMigrationFunc := func(d *DB) {
|
|
meta, err := d.FetchMeta()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if meta.DbVersionNumber != 0 {
|
|
t.Fatal("migration failed but version is changed")
|
|
}
|
|
|
|
err = kvdb.Update(d, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value := bucket.Get(keyPrefix)
|
|
if !bytes.Equal(value, beforeMigration) {
|
|
return errors.New("migration failed but data is " +
|
|
"changed")
|
|
}
|
|
|
|
return nil
|
|
}, func() {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
applyMigration(t,
|
|
beforeMigrationFunc,
|
|
afterMigrationFunc,
|
|
migrationWithFatal,
|
|
true,
|
|
false)
|
|
}
|
|
|
|
// TestMigrationWithoutErrors asserts that a successful migration has its
|
|
// changes applied to the database.
|
|
func TestMigrationWithoutErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
bucketPrefix := []byte("somebucket")
|
|
keyPrefix := []byte("someprefix")
|
|
beforeMigration := []byte("beforemigration")
|
|
afterMigration := []byte("aftermigration")
|
|
|
|
// Populate database with initial data.
|
|
beforeMigrationFunc := func(d *DB) {
|
|
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return bucket.Put(keyPrefix, beforeMigration)
|
|
}, func() {})
|
|
if err != nil {
|
|
t.Fatalf("unable to update db pre migration: %v", err)
|
|
}
|
|
}
|
|
|
|
// Create migration function which changes the initially created data.
|
|
migrationWithoutErrors := func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bucket.Put(keyPrefix, afterMigration)
|
|
return nil
|
|
}
|
|
|
|
// Check that version of database and data was properly changed.
|
|
afterMigrationFunc := func(d *DB) {
|
|
meta, err := d.FetchMeta()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if meta.DbVersionNumber != 1 {
|
|
t.Fatal("version number isn't changed after " +
|
|
"successfully applied migration")
|
|
}
|
|
|
|
err = kvdb.Update(d, func(tx kvdb.RwTx) error {
|
|
bucket, err := tx.CreateTopLevelBucket(bucketPrefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
value := bucket.Get(keyPrefix)
|
|
if !bytes.Equal(value, afterMigration) {
|
|
return errors.New("migration wasn't applied " +
|
|
"properly")
|
|
}
|
|
|
|
return nil
|
|
}, func() {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
applyMigration(t,
|
|
beforeMigrationFunc,
|
|
afterMigrationFunc,
|
|
migrationWithoutErrors,
|
|
false,
|
|
false)
|
|
}
|
|
|
|
// TestMigrationReversion tests after performing a migration to a higher
|
|
// database version, opening the database with a lower latest db version returns
|
|
// ErrDBReversion.
|
|
func TestMigrationReversion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tempDirName := t.TempDir()
|
|
|
|
backend, cleanup, err := kvdb.GetTestBackend(tempDirName, "cdb")
|
|
require.NoError(t, err, "unable to get test db backend")
|
|
|
|
cdb, err := CreateWithBackend(backend)
|
|
if err != nil {
|
|
cleanup()
|
|
t.Fatalf("unable to open channeldb: %v", err)
|
|
}
|
|
|
|
// Update the database metadata to point to one more than the highest
|
|
// known version.
|
|
err = kvdb.Update(cdb, func(tx kvdb.RwTx) error {
|
|
newMeta := &Meta{
|
|
DbVersionNumber: getLatestDBVersion(dbVersions) + 1,
|
|
}
|
|
|
|
return putMeta(newMeta, tx)
|
|
}, func() {})
|
|
|
|
// Close the database. Even if we succeeded, our next step is to reopen.
|
|
cdb.Close()
|
|
cleanup()
|
|
|
|
require.NoError(t, err, "unable to increase db version")
|
|
|
|
backend, cleanup, err = kvdb.GetTestBackend(tempDirName, "cdb")
|
|
require.NoError(t, err, "unable to get test db backend")
|
|
t.Cleanup(cleanup)
|
|
|
|
_, err = CreateWithBackend(backend)
|
|
if err != ErrDBReversion {
|
|
t.Fatalf("unexpected error when opening channeldb, "+
|
|
"want: %v, got: %v", ErrDBReversion, err)
|
|
}
|
|
}
|
|
|
|
// TestMigrationDryRun ensures that opening the database in dry run migration
|
|
// mode will fail and not commit the migration.
|
|
func TestMigrationDryRun(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Nothing to do, will inspect version number.
|
|
beforeMigrationFunc := func(d *DB) {}
|
|
|
|
// Check that version of database version is not modified.
|
|
afterMigrationFunc := func(d *DB) {
|
|
err := kvdb.View(d, func(tx kvdb.RTx) error {
|
|
meta, err := d.FetchMeta()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if meta.DbVersionNumber != 0 {
|
|
t.Fatal("dry run migration was not aborted")
|
|
}
|
|
|
|
return nil
|
|
}, func() {})
|
|
if err != nil {
|
|
t.Fatalf("unable to apply after func: %v", err)
|
|
}
|
|
}
|
|
|
|
applyMigration(t,
|
|
beforeMigrationFunc,
|
|
afterMigrationFunc,
|
|
func(kvdb.RwTx) error { return nil },
|
|
true,
|
|
true)
|
|
}
|
|
|
|
// TestOptionalMeta checks the basic read and write for the optional meta.
|
|
func TestOptionalMeta(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, err := MakeTestDB(t)
|
|
require.NoError(t, err)
|
|
|
|
// Test read an empty optional meta.
|
|
om, err := db.fetchOptionalMeta()
|
|
require.NoError(t, err, "error getting optional meta")
|
|
require.Empty(t, om.Versions, "expected empty versions")
|
|
|
|
// Test write an optional meta.
|
|
om = &OptionalMeta{
|
|
Versions: map[uint64]string{
|
|
0: optionalVersions[0].name,
|
|
},
|
|
}
|
|
err = db.putOptionalMeta(om)
|
|
require.NoError(t, err, "error putting optional meta")
|
|
|
|
om1, err := db.fetchOptionalMeta()
|
|
require.NoError(t, err, "error getting optional meta")
|
|
require.Equal(t, om, om1, "unexpected empty versions")
|
|
require.Equal(t, "0: prune revocation log", om.String())
|
|
}
|
|
|
|
// TestApplyOptionalVersions checks that the optional migration is applied as
|
|
// expected based on the config.
|
|
func TestApplyOptionalVersions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, err := MakeTestDB(t)
|
|
require.NoError(t, err)
|
|
|
|
// Overwrite the migration function so we can count how many times the
|
|
// migration has happened.
|
|
migrateCount := 0
|
|
optionalVersions[0].migration = func(_ kvdb.Backend,
|
|
_ MigrationConfig) error {
|
|
|
|
migrateCount++
|
|
return nil
|
|
}
|
|
|
|
// Test that when the flag is false, no migration happens.
|
|
cfg := OptionalMiragtionConfig{}
|
|
err = db.applyOptionalVersions(cfg)
|
|
require.NoError(t, err, "failed to apply optional migration")
|
|
require.Equal(t, 0, migrateCount, "expected no migration")
|
|
|
|
// Check the optional meta is not updated.
|
|
om, err := db.fetchOptionalMeta()
|
|
require.NoError(t, err, "error getting optional meta")
|
|
require.Empty(t, om.Versions, "expected empty versions")
|
|
|
|
// Test that when specified, the optional migration is applied.
|
|
cfg.PruneRevocationLog = true
|
|
err = db.applyOptionalVersions(cfg)
|
|
require.NoError(t, err, "failed to apply optional migration")
|
|
require.Equal(t, 1, migrateCount, "expected migration")
|
|
|
|
// Fetch the updated optional meta.
|
|
om, err = db.fetchOptionalMeta()
|
|
require.NoError(t, err, "error getting optional meta")
|
|
|
|
// Verify that the optional meta is updated as expected.
|
|
omExpected := &OptionalMeta{
|
|
Versions: map[uint64]string{
|
|
0: optionalVersions[0].name,
|
|
},
|
|
}
|
|
require.Equal(t, omExpected, om, "unexpected empty versions")
|
|
|
|
// Test that though specified, the optional migration is not run since
|
|
// it's already been applied.
|
|
cfg.PruneRevocationLog = true
|
|
err = db.applyOptionalVersions(cfg)
|
|
require.NoError(t, err, "failed to apply optional migration")
|
|
require.Equal(t, 1, migrateCount, "expected no migration")
|
|
}
|
|
|
|
// TestFetchMeta tests that the FetchMeta returns the latest DB version for a
|
|
// freshly created DB instance.
|
|
func TestFetchMeta(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, err := MakeTestDB(t)
|
|
require.NoError(t, err)
|
|
|
|
meta := &Meta{}
|
|
err = db.View(func(tx walletdb.ReadTx) error {
|
|
return FetchMeta(meta, tx)
|
|
}, func() {
|
|
meta = &Meta{}
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, LatestDBVersion(), meta.DbVersionNumber)
|
|
}
|
|
|
|
// TestMarkerAndTombstone tests that markers like a tombstone can be added to a
|
|
// DB.
|
|
func TestMarkerAndTombstone(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, err := MakeTestDB(t)
|
|
require.NoError(t, err)
|
|
|
|
// Test that a generic marker is not present in a fresh DB.
|
|
var marker []byte
|
|
err = db.View(func(tx walletdb.ReadTx) error {
|
|
var err error
|
|
marker, err = CheckMarkerPresent(tx, []byte("foo"))
|
|
return err
|
|
}, func() {
|
|
marker = nil
|
|
})
|
|
require.ErrorIs(t, err, ErrMarkerNotPresent)
|
|
require.Nil(t, marker)
|
|
|
|
// Only adding the marker bucket should not be enough to be counted as
|
|
// a marker, we explicitly also want the value to be set.
|
|
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
_, err := tx.CreateTopLevelBucket([]byte("foo"))
|
|
return err
|
|
}, func() {})
|
|
require.NoError(t, err)
|
|
|
|
err = db.View(func(tx walletdb.ReadTx) error {
|
|
var err error
|
|
marker, err = CheckMarkerPresent(tx, []byte("foo"))
|
|
return err
|
|
}, func() {
|
|
marker = nil
|
|
})
|
|
require.ErrorIs(t, err, ErrMarkerNotPresent)
|
|
require.Nil(t, marker)
|
|
|
|
// Test that a tombstone marker is not present in a fresh DB.
|
|
err = db.View(EnsureNoTombstone, func() {})
|
|
require.NoError(t, err)
|
|
|
|
// Add a generic marker now and assert that it can be read.
|
|
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
return AddMarker(tx, []byte("foo"), []byte("bar"))
|
|
}, func() {})
|
|
require.NoError(t, err)
|
|
|
|
err = db.View(func(tx walletdb.ReadTx) error {
|
|
var err error
|
|
marker, err = CheckMarkerPresent(tx, []byte("foo"))
|
|
return err
|
|
}, func() {
|
|
marker = nil
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte("bar"), marker)
|
|
|
|
// A tombstone should still not be present.
|
|
err = db.View(EnsureNoTombstone, func() {})
|
|
require.NoError(t, err)
|
|
|
|
// Finally, add a tombstone.
|
|
tombstoneText := []byte("RIP test DB")
|
|
err = db.Update(func(tx walletdb.ReadWriteTx) error {
|
|
return AddMarker(tx, TombstoneKey, tombstoneText)
|
|
}, func() {})
|
|
require.NoError(t, err)
|
|
|
|
// We can read it as a normal marker.
|
|
err = db.View(func(tx walletdb.ReadTx) error {
|
|
var err error
|
|
marker, err = CheckMarkerPresent(tx, TombstoneKey)
|
|
return err
|
|
}, func() {
|
|
marker = nil
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, tombstoneText, marker)
|
|
|
|
// But also as a tombstone, and now we should get an error that the DB
|
|
// cannot be used anymore.
|
|
err = db.View(EnsureNoTombstone, func() {})
|
|
require.ErrorContains(t, err, string(tombstoneText))
|
|
|
|
// Now that the DB has a tombstone, we should no longer be able to open
|
|
// it once we close it.
|
|
_, err = CreateWithBackend(db.Backend)
|
|
require.ErrorContains(t, err, string(tombstoneText))
|
|
}
|