lightning-terminal/db/migsets/post_migration_callbacks_dev.go
2026-05-14 11:39:12 +02:00

195 lines
5.4 KiB
Go

//go:build dev
package migsets
import (
"context"
"database/sql"
"encoding/binary"
"fmt"
"path/filepath"
"time"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/db/sqlcmig6"
"github.com/lightninglabs/lightning-terminal/firewalldb"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/sqldb/v2"
)
// MakePostStepCallbacksMig6 turns the post migration checks into a map of post
// step callbacks that can be used with the migrate package. The keys of the map
// are the migration versions, and the values are the callbacks that will be
// executed after the migration with the corresponding version is applied.
func MakePostStepCallbacksMig6(ctx context.Context,
basicClient lnrpc.LightningClient, db *sqldb.BaseDB,
macPath string, clock clock.Clock,
migVersion uint) migrate.ProgrammaticMigrEntry {
mig6queries := sqlcmig6.NewForType(db, db.BackendType)
mig6executor := sqldb.NewTransactionExecutor(
db, func(tx *sql.Tx) *sqlcmig6.Queries {
return mig6queries.WithTx(tx)
},
)
pMigr := func(_ *migrate.Migration, _ database.Driver) error {
// We ignore the actual driver that's being returned here, since
// we use migrate.NewWithInstance() to create the migration
// instance from our already instantiated database backend that
// is also passed into this function.
return mig6executor.ExecTx(
ctx, sqldb.WriteTxOpt(),
func(q6 *sqlcmig6.Queries) error {
log.Infof("Running post migration callback "+
"for migration version %d", migVersion)
return kvdbToSqlMigrationCallback(
ctx, basicClient, macPath, db, clock,
q6,
)
}, sqldb.NoOpReset,
)
}
return migrate.ProgrammaticMigrEntry{
// We want the migration to rerun on next startup if it errors,
// and not set the user's db to a dirty state.
ResetVersionOnError: true,
ProgrammaticMigr: pMigr,
}
}
func kvdbToSqlMigrationCallback(ctx context.Context,
basicClient lnrpc.LightningClient, macPath string, _ *sqldb.BaseDB,
clock clock.Clock, q *sqlcmig6.Queries) error {
start := time.Now()
log.Infof("Starting KVDB to SQL migration for all stores")
accountStore, err := accounts.NewBoltStore(
filepath.Dir(macPath), accounts.DBFilename, clock,
)
if err != nil {
return err
}
defer func() {
err := accountStore.Close()
if err != nil {
log.Errorf("Error closing bbolt account store during "+
"migration: %v", err)
}
}()
err = accounts.MigrateAccountStoreToSQL(ctx, accountStore.DB, q)
if err != nil {
return fmt.Errorf("error migrating account store to "+
"SQL: %w", err)
}
sessionStore, err := session.NewDB(
filepath.Dir(macPath), session.DBFilename,
clock, accountStore,
)
if err != nil {
return err
}
defer func() {
err := sessionStore.Close()
if err != nil {
log.Errorf("Error closing bbolt session store during "+
"migration: %v", err)
}
}()
err = session.MigrateSessionStoreToSQL(ctx, sessionStore.DB, q)
if err != nil {
return fmt.Errorf("error migrating session store to "+
"SQL: %w", err)
}
firewallStore, err := firewalldb.NewBoltDB(
filepath.Dir(macPath), firewalldb.DBFilename,
sessionStore, accountStore, clock,
)
if err != nil {
return err
}
defer func() {
err := firewallStore.Close()
if err != nil {
log.Errorf("Error closing bbolt rules store during "+
"migration: %v", err)
}
}()
// We'll fetch the macaroonIDList from `lnd` next. Note that since lnd's
// RPC servers may not have been fully started yet if the execution of
// accounts and session migration were really quick, we poll the request
// up to 10 times with a 0.5 second delay between the attempts. This
// should be a sufficient amount of time for the RPC servers to start.
const (
maxListMacaroonIDAttempts = 10
listMacaroonIDRetryDelay = 500 * time.Millisecond
)
var macaroonIDList *lnrpc.ListMacaroonIDsResponse
for i := 1; i <= maxListMacaroonIDAttempts; i++ {
macaroonIDList, err = basicClient.ListMacaroonIDs(
ctx, &lnrpc.ListMacaroonIDsRequest{},
)
if err == nil {
break
}
if i == maxListMacaroonIDAttempts {
return fmt.Errorf("error listing macaroon IDs when "+
"migrating stores to SQL after %d attempts: %w",
maxListMacaroonIDAttempts, err)
}
log.Warnf("Failed to list macaroon IDs when migrating "+
"stores to SQL (attempt %d/%d), retrying in %v: %v",
i, maxListMacaroonIDAttempts, listMacaroonIDRetryDelay,
err)
select {
case <-ctx.Done():
return fmt.Errorf("context canceled while retrying "+
"to list macaroon IDs: %w", ctx.Err())
case <-time.After(listMacaroonIDRetryDelay):
}
}
log.Infof("Successfully listed macaroon IDs during store migration.")
var macRootKeyIDs [][]byte
if macaroonIDList != nil {
for _, rootKeyID := range macaroonIDList.RootKeyIds {
rootKeyBytes := make([]byte, 8)
binary.BigEndian.PutUint64(rootKeyBytes[:], rootKeyID)
macRootKeyIDs = append(macRootKeyIDs, rootKeyBytes)
}
}
err = firewalldb.MigrateFirewallDBToSQL(
ctx, firewallStore.DB, q, macRootKeyIDs,
)
if err != nil {
return fmt.Errorf("error migrating firewalldb store "+
"to SQL: %w", err)
}
log.Infof("Succesfully migrated all KVDB stores to SQL in: %v",
time.Since(start))
return nil
}