Skip to content

Commit c7e7731

Browse files
committed
channeldb: update optional migration code for multiple versions
1 parent c19d891 commit c7e7731

File tree

4 files changed

+147
-68
lines changed

4 files changed

+147
-68
lines changed

channeldb/db.go

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ var (
308308
// to determine its state.
309309
optionalVersions = []optionalVersion{
310310
{
311-
name: "prune revocation log",
311+
name: "prune_revocation_log",
312312
migration: func(db kvdb.Backend,
313313
cfg MigrationConfig) error {
314314

@@ -1751,50 +1751,68 @@ func (d *DB) applyOptionalVersions(cfg OptionalMiragtionConfig) error {
17511751
Versions: make(map[uint64]string),
17521752
}
17531753
} else {
1754-
return err
1754+
return fmt.Errorf("unable to fetch optional "+
1755+
"meta: %w", err)
17551756
}
17561757
}
17571758

1758-
log.Infof("Checking for optional update: prune_revocation_log=%v, "+
1759-
"db_version=%s", cfg.PruneRevocationLog, om)
1760-
1761-
// Exit early if the optional migration is not specified.
1762-
if !cfg.PruneRevocationLog {
1763-
return nil
1764-
}
1765-
1766-
// Exit early if the optional migration has already been applied.
1767-
if _, ok := om.Versions[0]; ok {
1768-
return nil
1769-
}
1770-
1771-
// Get the optional version.
1772-
version := optionalVersions[0]
1773-
log.Infof("Performing database optional migration: %s", version.name)
1774-
1759+
// migrationCfg is the parent configuration which implements the config
1760+
// interfaces of all the single optional migrations.
17751761
migrationCfg := &MigrationConfigImpl{
17761762
migration30.MigrateRevLogConfigImpl{
17771763
NoAmountData: d.noRevLogAmtData,
17781764
},
17791765
}
17801766

1781-
// Migrate the data.
1782-
if err := version.migration(d, migrationCfg); err != nil {
1783-
log.Errorf("Unable to apply optional migration: %s, error: %v",
1784-
version.name, err)
1785-
return err
1786-
}
1767+
log.Infof("Applying %d optional migrations", len(optionalVersions))
17871768

1788-
// Update the optional meta. Notice that unlike the mandatory db
1789-
// migrations where we perform the migration and updating meta in a
1790-
// single db transaction, we use different transactions here. Even when
1791-
// the following update is failed, we should be fine here as we would
1792-
// re-run the optional migration again, which is a noop, during next
1793-
// startup.
1794-
om.Versions[0] = version.name
1795-
if err := d.putOptionalMeta(om); err != nil {
1796-
log.Errorf("Unable to update optional meta: %v", err)
1797-
return err
1769+
// Apply the optional migrations if requested.
1770+
for number, version := range optionalVersions {
1771+
log.Infof("Checking for optional update: name=%v", version.name)
1772+
1773+
// Exit early if the optional migration is not specified.
1774+
if !cfg.MigrationFlags[number] {
1775+
log.Debugf("Skipping optional migration: name=%s as "+
1776+
"it is not specified in the config",
1777+
version.name)
1778+
1779+
continue
1780+
}
1781+
1782+
// Exit early if the optional migration has already been
1783+
// applied.
1784+
if _, ok := om.Versions[uint64(number)]; ok {
1785+
log.Debugf("Skipping optional migration: name=%s as "+
1786+
"it has already been applied", version.name)
1787+
1788+
continue
1789+
}
1790+
1791+
log.Infof("Performing database optional migration: %s",
1792+
version.name)
1793+
1794+
// Call the migration function for the specific optional
1795+
// migration.
1796+
if err := version.migration(d, migrationCfg); err != nil {
1797+
log.Errorf("Unable to apply optional migration: %s, "+
1798+
"error: %v", version.name, err)
1799+
return err
1800+
}
1801+
1802+
// Update the optional meta. Notice that unlike the mandatory db
1803+
// migrations where we perform the migration and updating meta
1804+
// in a single db transaction, we use different transactions
1805+
// here. Even when the following update is failed, we should be
1806+
// fine here as we would re-run the optional migration again,
1807+
// which is a noop, during next startup.
1808+
om.Versions[uint64(number)] = version.name
1809+
if err := d.putOptionalMeta(om); err != nil {
1810+
log.Errorf("Unable to update optional meta: %v", err)
1811+
return err
1812+
}
1813+
1814+
log.Infof("Successfully applied optional migration: %s",
1815+
version.name)
17981816
}
17991817

18001818
return nil

channeldb/meta.go

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bytes"
55
"errors"
66
"fmt"
7+
"slices"
8+
"strings"
79

810
"github.com/lightningnetwork/lnd/kvdb"
911
"github.com/lightningnetwork/lnd/tlv"
@@ -30,6 +32,10 @@ var (
3032
// ErrMarkerNotPresent is the error that is returned if the queried
3133
// marker is not present in the given database.
3234
ErrMarkerNotPresent = errors.New("marker not present")
35+
36+
// ErrInvalidOptionalVersion is the error that is returned if the
37+
// optional version persisted in the database is invalid.
38+
ErrInvalidOptionalVersion = errors.New("invalid optional version")
3339
)
3440

3541
// Meta structure holds the database meta information.
@@ -104,15 +110,28 @@ type OptionalMeta struct {
104110
Versions map[uint64]string
105111
}
106112

113+
// String returns a string representation of the optional meta.
107114
func (om *OptionalMeta) String() string {
108-
s := ""
109-
for index, name := range om.Versions {
110-
s += fmt.Sprintf("%d: %s", index, name)
115+
if len(om.Versions) == 0 {
116+
return "empty"
117+
}
118+
119+
// Create a slice of indices to sort
120+
indices := make([]uint64, 0, len(om.Versions))
121+
for index := range om.Versions {
122+
indices = append(indices, index)
111123
}
112-
if s == "" {
113-
s = "empty"
124+
125+
// Sort the indices in ascending order.
126+
slices.Sort(indices)
127+
128+
// Create the string parts in sorted order.
129+
parts := make([]string, len(indices))
130+
for i, index := range indices {
131+
parts[i] = fmt.Sprintf("%d: %s", index, om.Versions[index])
114132
}
115-
return s
133+
134+
return strings.Join(parts, ", ")
116135
}
117136

118137
// fetchOptionalMeta reads the optional meta from the database.
@@ -146,7 +165,20 @@ func (d *DB) fetchOptionalMeta() (*OptionalMeta, error) {
146165
if err != nil {
147166
return err
148167
}
149-
om.Versions[version] = optionalVersions[i].name
168+
169+
// This check would not allow to downgrade LND software
170+
// to a version with an optional migration when an
171+
// optional migration not known to the current version
172+
// has already been applied.
173+
if version >= uint64(len(optionalVersions)) {
174+
return fmt.Errorf("optional version read "+
175+
"from db is %d, but only optional "+
176+
"migrations up to %d are known: %w",
177+
version, len(optionalVersions)-1,
178+
ErrInvalidOptionalVersion)
179+
}
180+
181+
om.Versions[version] = optionalVersions[version].name
150182
}
151183

152184
return nil
@@ -174,8 +206,12 @@ func (d *DB) putOptionalMeta(om *OptionalMeta) error {
174206
return err
175207
}
176208

177-
// Write the version indexes.
209+
// Write the version indexes of the single migrations.
178210
for v := range om.Versions {
211+
if v >= uint64(len(optionalVersions)) {
212+
return ErrInvalidOptionalVersion
213+
}
214+
179215
err := tlv.WriteVarInt(&b, v, &[8]byte{})
180216
if err != nil {
181217
return err

channeldb/meta_test.go

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -506,43 +506,56 @@ func TestOptionalMeta(t *testing.T) {
506506
om1, err := db.fetchOptionalMeta()
507507
require.NoError(t, err, "error getting optional meta")
508508
require.Equal(t, om, om1, "unexpected empty versions")
509-
require.Equal(t, "0: prune revocation log", om.String())
509+
require.Equal(t, "0: prune_revocation_log", om.String())
510510
}
511511

512512
// TestApplyOptionalVersions checks that the optional migration is applied as
513513
// expected based on the config.
514+
//
515+
// NOTE: Cannot be run in parallel because we alter the optionalVersions
516+
// global variable which could be used by other tests.
514517
func TestApplyOptionalVersions(t *testing.T) {
515-
t.Parallel()
516-
517518
db, err := MakeTestDB(t)
518519
require.NoError(t, err)
519520

520-
// Overwrite the migration function so we can count how many times the
521-
// migration has happened.
522-
migrateCount := 0
523-
optionalVersions[0].migration = func(_ kvdb.Backend,
524-
_ MigrationConfig) error {
521+
// migrateCount is the number of migrations that have been run. It
522+
// counts the number of times a migration function is called.
523+
var migrateCount int
525524

526-
migrateCount++
527-
return nil
525+
// Modify all migrations to track their execution.
526+
for i := range optionalVersions {
527+
optionalVersions[i].migration = func(_ kvdb.Backend,
528+
_ MigrationConfig) error {
529+
530+
migrateCount++
531+
532+
return nil
533+
}
528534
}
529535

530-
// Test that when the flag is false, no migration happens.
531-
cfg := OptionalMiragtionConfig{}
536+
// All migrations are disabled by default.
537+
cfg := NewOptionalMiragtionConfig()
538+
539+
// Run the optional migrations.
532540
err = db.applyOptionalVersions(cfg)
533541
require.NoError(t, err, "failed to apply optional migration")
534542
require.Equal(t, 0, migrateCount, "expected no migration")
535543

536544
// Check the optional meta is not updated.
537545
om, err := db.fetchOptionalMeta()
538546
require.NoError(t, err, "error getting optional meta")
539-
require.Empty(t, om.Versions, "expected empty versions")
540547

541-
// Test that when specified, the optional migration is applied.
542-
cfg.PruneRevocationLog = true
548+
// Enable all optional migrations.
549+
for i := range cfg.MigrationFlags {
550+
cfg.MigrationFlags[i] = true
551+
}
552+
543553
err = db.applyOptionalVersions(cfg)
544554
require.NoError(t, err, "failed to apply optional migration")
545-
require.Equal(t, 1, migrateCount, "expected migration")
555+
require.Equal(
556+
t, len(optionalVersions), migrateCount,
557+
"expected all migrations to be run",
558+
)
546559

547560
// Fetch the updated optional meta.
548561
om, err = db.fetchOptionalMeta()
@@ -556,12 +569,15 @@ func TestApplyOptionalVersions(t *testing.T) {
556569
}
557570
require.Equal(t, omExpected, om, "unexpected empty versions")
558571

559-
// Test that though specified, the optional migration is not run since
560-
// it's already been applied.
561-
cfg.PruneRevocationLog = true
572+
// We make sure running the migrations again does not call the
573+
// migrations again because the meta data should signal that they have
574+
// already been run.
562575
err = db.applyOptionalVersions(cfg)
563576
require.NoError(t, err, "failed to apply optional migration")
564-
require.Equal(t, 1, migrateCount, "expected no migration")
577+
require.Equal(
578+
t, len(optionalVersions), migrateCount,
579+
"expected all migrations to be run",
580+
)
565581
}
566582

567583
// TestFetchMeta tests that the FetchMeta returns the latest DB version for a

channeldb/options.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,18 @@ const (
2525
// OptionalMiragtionConfig defines the flags used to signal whether a
2626
// particular migration needs to be applied.
2727
type OptionalMiragtionConfig struct {
28-
// PruneRevocationLog specifies that the revocation log migration needs
29-
// to be applied.
30-
PruneRevocationLog bool
28+
// MigrationFlags is an array of booleans indicating which optional
29+
// migrations should be run. The index in the array corresponds to the
30+
// migration number in optionalVersions.
31+
MigrationFlags []bool
32+
}
33+
34+
// NewOptionalMiragtionConfig creates a new OptionalMiragtionConfig with the
35+
// default migration flags.
36+
func NewOptionalMiragtionConfig() OptionalMiragtionConfig {
37+
return OptionalMiragtionConfig{
38+
MigrationFlags: make([]bool, len(optionalVersions)),
39+
}
3140
}
3241

3342
// Options holds parameters for tuning and customizing a channeldb.DB.
@@ -62,7 +71,7 @@ type Options struct {
6271
// DefaultOptions returns an Options populated with default values.
6372
func DefaultOptions() Options {
6473
return Options{
65-
OptionalMiragtionConfig: OptionalMiragtionConfig{},
74+
OptionalMiragtionConfig: NewOptionalMiragtionConfig(),
6675
NoMigration: false,
6776
clock: clock.NewDefaultClock(),
6877
}
@@ -124,6 +133,6 @@ func OptionStoreFinalHtlcResolutions(
124133
// revocation logs needs to be applied or not.
125134
func OptionPruneRevocationLog(prune bool) OptionModifier {
126135
return func(o *Options) {
127-
o.OptionalMiragtionConfig.PruneRevocationLog = prune
136+
o.OptionalMiragtionConfig.MigrationFlags[0] = prune
128137
}
129138
}

0 commit comments

Comments
 (0)