123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686 |
- 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))
- }
|