From 59615b3cb2cf01db74944e087ebf04b11509494a Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 8 Feb 2017 12:56:37 -0800 Subject: [PATCH] channedb: add db migration for databases before delivery script bug fix --- channeldb/db.go | 23 ++++++++-- channeldb/meta_test.go | 2 +- channeldb/migrations.go | 98 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 channeldb/migrations.go diff --git a/channeldb/db.go b/channeldb/db.go index 056ebdbd..95b95f67 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -39,6 +39,10 @@ var ( number: 0, migration: nil, }, + { + number: 1, + migration: deliveryScriptBugMigration, + }, } // Big endian is the preferred byte order, due to cursor scans over @@ -351,21 +355,30 @@ func (d *DB) syncVersions(versions []version) error { // If the current database version matches the latest version number, // then we don't need to perform any migrations. latestVersion := getLatestDBVersion(versions) + log.Infof("Checking for schema update: latest_version=%v, "+ + "db_version=%v", latestVersion, meta.DbVersionNumber) if meta.DbVersionNumber == latestVersion { return nil } + log.Infof("Performing database schema migration") + // Otherwise, we fetch the migrations which need to applied, and // execute them serially within a single database transaction to ensure // the migration is atomic. - migrations := getMigrationsToApply(versions, meta.DbVersionNumber) + migrations, migrationVersions := getMigrationsToApply(versions, + meta.DbVersionNumber) return d.Update(func(tx *bolt.Tx) error { - for _, migration := range migrations { + for i, migration := range migrations { if migration == nil { continue } + log.Infof("Applying migration #%v", migrationVersions[i]) + if err := migration(tx); err != nil { + log.Infof("Unable to apply migration #%v", + migrationVersions[i]) return err } } @@ -390,14 +403,16 @@ func getLatestDBVersion(versions []version) uint32 { // getMigrationsToApply retrieves the migration function that should be // applied to the database. -func getMigrationsToApply(versions []version, version uint32) []migration { +func getMigrationsToApply(versions []version, version uint32) ([]migration, []uint32) { migrations := make([]migration, 0, len(versions)) + migrationVersions := make([]uint32, 0, len(versions)) for _, v := range versions { if v.number > version { migrations = append(migrations, v.migration) + migrationVersions = append(migrationVersions, v.number) } } - return migrations + return migrations, migrationVersions } diff --git a/channeldb/meta_test.go b/channeldb/meta_test.go index cea1b9ba..347cd44c 100644 --- a/channeldb/meta_test.go +++ b/channeldb/meta_test.go @@ -62,7 +62,7 @@ func TestOrderOfMigrations(t *testing.T) { // 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) + migrations, _ := getMigrationsToApply(versions, 1) if len(migrations) != 2 { t.Fatal("incorrect number of migrations to apply") diff --git a/channeldb/migrations.go b/channeldb/migrations.go new file mode 100644 index 00000000..eddffea6 --- /dev/null +++ b/channeldb/migrations.go @@ -0,0 +1,98 @@ +package channeldb + +import ( + "bytes" + + "github.com/boltdb/bolt" + "github.com/roasbeef/btcd/wire" +) + +// deliveryScriptBugMigration is a database migration that patches an incorrect +// version of the database due to a typo in the key when fetching, putting, +// deleting the delivery scripts for a channel. As of database version 1, the +// querying logic expects delivery scripts to be in the proper place within the +// node's channel schema. This migration fixes the issue in the older version +// of the database so channels can properly be read from the database. +func deliveryScriptBugMigration(tx *bolt.Tx) error { + // Get the bucket dedicated to storing the metadata for open channels. + openChanBucket := tx.Bucket(openChannelBucket) + if openChanBucket == nil { + return nil + } + + // Next, fetch the bucket dedicated to storing metadata related to all + // nodes. All keys within this bucket are the serialized public keys of + // all our direct counterparties. + nodeMetaBucket := tx.Bucket(nodeInfoBucket) + if nodeMetaBucket == nil { + return nil + } + + log.Infof("Migration database from legacy channel delivery script " + + "schema") + + // Finally for each node public key in the bucket, fetch all the + // channels related to this particular node. + return nodeMetaBucket.ForEach(func(k, v []byte) error { + // Within the meta node meta bucket, each key is the node's + // serialized public key. Knowing this key allows us to fetch + // the node's open channel bucket. + nodeChanBucket := openChanBucket.Bucket(k) + if nodeChanBucket == nil { + return nil + } + + // Once we have the open channel bucket for this particular + // node, we then access another sub-bucket which is an index + // that stores the channel points (chanID's) for all active + // channels with the node. + nodeChanIDBucket := nodeChanBucket.Bucket(chanIDBucket[:]) + if nodeChanIDBucket == nil { + return nil + } + err := nodeChanIDBucket.ForEach(func(k, v []byte) error { + if k == nil { + return nil + } + + // The old delivery script key within a node's channel + // bucket for each channel was just the prefix key + // itself. So we'll check if this key stores any data, + // if not, then we don't need to migrate this channel. + oldDeliverykey := deliveryScriptsKey + deliveryScripts := nodeChanBucket.Get(oldDeliverykey) + if deliveryScripts == nil { + return nil + } + + // Decode the stored outpoint so we can log our + // progress. + outBytes := bytes.NewReader(k) + chanID := &wire.OutPoint{} + if err := readOutpoint(outBytes, chanID); err != nil { + return err + } + + log.Debugf("Migration delivery scripts of "+ + "ChannelPoint(%v)", chanID) + + // Next we manually construct the _proper_ key which + // uses the key prefix in conjunction with the chanID + // to create the final key. + deliveryKey := make([]byte, len(deliveryScriptsKey)+len(k)) + copy(deliveryKey[:3], deliveryScriptsKey) + copy(deliveryKey[3:], k) + + // To complete the migration for this channel, we now + // store the delivery scripts in their proper place. + return nodeChanBucket.Put(deliveryKey, deliveryScripts) + }) + if err != nil { + return err + } + + // Before we conclude, we'll also delete value of the incorrect + // delivery storage. + return nodeChanBucket.Delete(deliveryScriptsKey) + }) +}