mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-13 12:32:48 +02:00
channeldb: recover missing db version
Use strict metadata reads during migration selection so a metadata bucket with a missing metadata/dbp key is not interpreted as the latest DB version. Recover this state from mandatory DB version 33, the last mandatory version before the v0.20.x releases that could initialize a DB without writing the DB version key. This runs migration 35 without replaying migrations 0 through 33 against a DB that was already created by a modern schema/code path. After the selected migrations complete, syncVersions writes the latest DB version as usual.
This commit is contained in:
parent
3aff61aebe
commit
68264c70d9
3 changed files with 176 additions and 3 deletions
|
|
@ -43,6 +43,14 @@ import (
|
|||
|
||||
const (
|
||||
dbName = "channel.db"
|
||||
|
||||
// missingDBVersionRecoveryVersion is the latest mandatory DB
|
||||
// version before the init ordering regression that could create a
|
||||
// DB without writing the DB version key. Affected DBs are therefore
|
||||
// already at least this version, so recovery starts here to run the
|
||||
// v0.21 waiting proof migration without replaying older migrations
|
||||
// against a modern DB.
|
||||
missingDBVersionRecoveryVersion = 33
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -1869,16 +1877,43 @@ func (c *ChannelStateDB) DeleteChannelOpeningState(outPoint []byte) error {
|
|||
// applies migration functions to the current database and recovers the
|
||||
// previous state of db if at least one error/panic appeared during migration.
|
||||
func (d *DB) syncVersions(versions []mandatoryVersion) error {
|
||||
latestVersion := getLatestDBVersion(versions)
|
||||
|
||||
meta, err := d.FetchMeta()
|
||||
if err != nil {
|
||||
if err == ErrMetaNotFound {
|
||||
switch {
|
||||
case errors.Is(err, ErrMetaNotFound):
|
||||
meta = &Meta{}
|
||||
} else {
|
||||
|
||||
case errors.Is(err, ErrDBVersionNotFound):
|
||||
recoveryVersion := uint32(
|
||||
missingDBVersionRecoveryVersion,
|
||||
)
|
||||
|
||||
// Missing DB version recovery is only valid for DBs
|
||||
// created after the init ordering regression. Older DBs
|
||||
// wrote the DB version before init returned, so a
|
||||
// missing version key on a sub-33 DB is not a valid
|
||||
// state to infer from.
|
||||
if latestVersion < recoveryVersion {
|
||||
return fmt.Errorf("unable to recover missing "+
|
||||
"DB version key: latest_version=%v "+
|
||||
"recovery_version=%v", latestVersion,
|
||||
recoveryVersion)
|
||||
}
|
||||
|
||||
log.Warnf("DB version key missing, recovering from "+
|
||||
"db_version=%v", recoveryVersion)
|
||||
|
||||
meta = &Meta{
|
||||
DbVersionNumber: recoveryVersion,
|
||||
}
|
||||
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
latestVersion := getLatestDBVersion(versions)
|
||||
log.Infof("Checking for schema update: latest_version=%v, "+
|
||||
"db_version=%v", latestVersion, meta.DbVersionNumber)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ package channeldb
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -687,6 +689,135 @@ func TestInitChannelDBCreatesMissingTopLevelBuckets(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestMissingDBVersionRunsWaitingProofMigration asserts that a DB initialized
|
||||
// without a version key is recovered from the last v0.20 mandatory version so
|
||||
// migration 35 can migrate legacy waiting proof records.
|
||||
func TestMissingDBVersionRunsWaitingProofMigration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
backend, cleanup, err := kvdb.GetTestBackend(t.TempDir(), "cdb")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
const scid = 101
|
||||
ann := &lnwire.AnnounceSignatures1{
|
||||
ChannelID: lnwire.ChannelID{1, 2, 3},
|
||||
ShortChannelID: lnwire.NewShortChanIDFromInt(scid),
|
||||
NodeSignature: wireSig,
|
||||
BitcoinSignature: wireSig,
|
||||
ExtraOpaqueData: []byte{4, 5, 6},
|
||||
}
|
||||
|
||||
legacyKey, legacyValue := encodeLegacyWaitingProof(t, true, ann)
|
||||
|
||||
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||
_, err := tx.CreateTopLevelBucket(metaBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucket, err := tx.CreateTopLevelBucket(waitingProofsBucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(legacyKey[:], legacyValue)
|
||||
}, func() {})
|
||||
require.NoError(t, err)
|
||||
|
||||
db, err := CreateWithBackend(backend)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, db.Close())
|
||||
})
|
||||
|
||||
err = db.View(func(tx kvdb.RTx) error {
|
||||
metaBucket := tx.ReadBucket(metaBucket)
|
||||
require.NotNil(t, metaBucket)
|
||||
|
||||
versionBytes := metaBucket.Get(dbVersionKey)
|
||||
require.Len(t, versionBytes, 4)
|
||||
require.Equal(
|
||||
t, LatestDBVersion(), byteOrder.Uint32(versionBytes),
|
||||
)
|
||||
|
||||
bucket := tx.ReadBucket(waitingProofsBucketKey)
|
||||
require.NotNil(t, bucket)
|
||||
require.Nil(t, bucket.Get(legacyKey[:]))
|
||||
|
||||
proof := NewWaitingProof(true, ann)
|
||||
typedKey := proof.Key()
|
||||
require.NotNil(t, bucket.Get(typedKey[:]))
|
||||
|
||||
return nil
|
||||
}, func() {})
|
||||
require.NoError(t, err)
|
||||
|
||||
store, err := NewWaitingProofStore(db)
|
||||
require.NoError(t, err)
|
||||
|
||||
proof := NewWaitingProof(true, ann)
|
||||
migratedProof, err := store.Get(proof.Key())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, proof.Key(), migratedProof.Key())
|
||||
}
|
||||
|
||||
// TestMissingDBVersionRecoveryRequiresBaseline asserts that a missing version
|
||||
// key cannot be recovered if the target version list does not include the
|
||||
// recovery baseline.
|
||||
func TestMissingDBVersionRecoveryRequiresBaseline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
backend, cleanup, err := kvdb.GetTestBackend(t.TempDir(), "cdb")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
err = kvdb.Update(backend, func(tx kvdb.RwTx) error {
|
||||
_, err := tx.CreateTopLevelBucket(metaBucket)
|
||||
|
||||
return err
|
||||
}, func() {})
|
||||
require.NoError(t, err)
|
||||
|
||||
db := &DB{
|
||||
Backend: backend,
|
||||
}
|
||||
|
||||
versions := []mandatoryVersion{
|
||||
{
|
||||
number: 0,
|
||||
migration: nil,
|
||||
},
|
||||
{
|
||||
number: 1,
|
||||
migration: nil,
|
||||
},
|
||||
}
|
||||
|
||||
err = db.syncVersions(versions)
|
||||
require.ErrorContains(t, err, "unable to recover missing DB version")
|
||||
}
|
||||
|
||||
// encodeLegacyWaitingProof encodes a waiting proof using the pre-migration
|
||||
// format.
|
||||
func encodeLegacyWaitingProof(t *testing.T, isRemote bool,
|
||||
ann *lnwire.AnnounceSignatures1) ([9]byte, []byte) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
var key [9]byte
|
||||
binary.BigEndian.PutUint64(key[:8], ann.ShortChannelID.ToUint64())
|
||||
if isRemote {
|
||||
key[8] = 1
|
||||
}
|
||||
|
||||
var value bytes.Buffer
|
||||
require.NoError(t, binary.Write(&value, byteOrder, isRemote))
|
||||
require.NoError(t, ann.Encode(&value, 0))
|
||||
|
||||
return key, value.Bytes()
|
||||
}
|
||||
|
||||
// TestMarkerAndTombstone tests that markers like a tombstone can be added to a
|
||||
// DB.
|
||||
func TestMarkerAndTombstone(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@
|
|||
combination. This also affects callers replaying an affected historical
|
||||
route returned by `ListPayments` or `TrackPayment`.
|
||||
|
||||
* [Fixed a channeldb migration
|
||||
bug](https://github.com/lightningnetwork/lnd/pull/10985) where databases
|
||||
initialized without a persisted `metadata/dbp` version key could skip later
|
||||
mandatory migrations. This recovers such databases from the last known
|
||||
v0.20-era mandatory version so the v0.21 waiting proof migration runs
|
||||
without replaying older migrations against an already-initialized database.
|
||||
|
||||
# New Features
|
||||
|
||||
## Functional Enhancements
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue