staticaddr: selected swap amount migration

The selected_amount column of all previous
swaps is filled with the total value of
deposits that partook in these swaps.
This commit is contained in:
Slyghtning 2025-06-11 12:02:25 +02:00
parent 23f6c24c0c
commit 2ee772b251
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
8 changed files with 265 additions and 7 deletions

View file

@ -635,6 +635,16 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
return err
}
// Run the selected amount migration.
err = loopin.MigrateSelectedSwapAmount(
d.mainCtx, swapDb, depositStore, staticAddressLoopInStore,
)
if err != nil {
errorf("Selected amount migration failed: %v", err)
return err
}
staticLoopInManager = loopin.NewManager(&loopin.Config{
Server: staticAddressClient,
QuoteGetter: swapClient.Server,

View file

@ -65,6 +65,7 @@ type Querier interface {
InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error
IsStored(ctx context.Context, swapHash []byte) (bool, error)
MapDepositToSwap(ctx context.Context, arg MapDepositToSwapParams) error
OverrideSelectedSwapAmount(ctx context.Context, arg OverrideSelectedSwapAmountParams) error
OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error
SwapHashForDepositID(ctx context.Context, depositID []byte) ([]byte, error)
UpdateBatch(ctx context.Context, arg UpdateBatchParams) error

View file

@ -96,6 +96,12 @@ SELECT EXISTS (
WHERE swap_hash = $1
);
-- name: OverrideSelectedSwapAmount :exec
UPDATE static_address_swaps
SET
selected_amount = $2
WHERE swap_hash = $1;
-- name: MapDepositToSwap :exec
UPDATE
deposits

View file

@ -457,6 +457,23 @@ func (q *Queries) MapDepositToSwap(ctx context.Context, arg MapDepositToSwapPara
return err
}
const overrideSelectedSwapAmount = `-- name: OverrideSelectedSwapAmount :exec
UPDATE static_address_swaps
SET
selected_amount = $2
WHERE swap_hash = $1
`
type OverrideSelectedSwapAmountParams struct {
SwapHash []byte
SelectedAmount int64
}
func (q *Queries) OverrideSelectedSwapAmount(ctx context.Context, arg OverrideSelectedSwapAmountParams) error {
_, err := q.db.ExecContext(ctx, overrideSelectedSwapAmount, arg.SwapHash, arg.SelectedAmount)
return err
}
const swapHashForDepositID = `-- name: SwapHashForDepositID :one
SELECT
swap_hash

View file

@ -119,13 +119,13 @@ func TestDepositSwapHashMigration(t *testing.T) {
// If so, we reject the loop-in to prevent potential issues with
// parsing.
for _, outpoint := range loopIn.DepositOutpoints {
if strings.Contains(outpoint, outpointSeparator) {
if strings.Contains(outpoint, OutpointSeparator) {
return ErrInvalidOutpoint
}
}
joinedOutpoints := strings.Join(
loopIn.DepositOutpoints, outpointSeparator,
loopIn.DepositOutpoints, OutpointSeparator,
)
staticAddressLoopInParams := sqlc.InsertStaticAddressLoopInParams{
SwapHash: loopIn.SwapHash[:],

View file

@ -0,0 +1,84 @@
package loopin
import (
"context"
"fmt"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightningnetwork/lnd/lntypes"
)
const (
// selectedAmountMigrationID is the identifier for the selected swap
// amount migration.
selectedAmountMigrationID = "selected_amount"
)
// MigrateSelectedSwapAmount will update the selected swap amount of past swaps
// with the sum of the values of the deposits they swapped.
func MigrateSelectedSwapAmount(ctx context.Context, db loopdb.SwapStore,
depositStore *deposit.SqlStore, swapStore *SqlStore) error {
migrationDone, err := db.HasMigration(ctx, selectedAmountMigrationID)
if err != nil {
return fmt.Errorf("unable to check migration status: %w", err)
}
if migrationDone {
log.Infof("Selected swap amount migration already done, " +
"skipping")
return nil
}
log.Infof("Starting swap amount migration")
startTs := time.Now()
defer func() {
log.Infof("Finished swap amount migration in %v",
time.Since(startTs))
}()
// First we'll fetch all loop out swaps from the database.
swaps, err := swapStore.GetStaticAddressLoopInSwapsByStates(
ctx, FinalStates,
)
if err != nil {
return err
}
// Now we'll calculate the cost for each swap and finally update the
// costs in the database.
// TODO(hieblmi): normalize swap hash and deposit ids.
updateAmounts := make(map[lntypes.Hash]btcutil.Amount)
for _, swap := range swaps {
for _, outpoint := range swap.DepositOutpoints {
deposit, err := depositStore.DepositForOutpoint(
ctx, outpoint,
)
if err != nil {
return fmt.Errorf("unable to fetch deposit "+
"for outpoint %s: %w", outpoint, err)
}
if deposit == nil {
return fmt.Errorf("deposit for outpoint %s "+
"not found", outpoint)
}
// Set the selected amount to the value of the deposit.
updateAmounts[swap.SwapHash] += deposit.Value
}
}
log.Infof("Updating selected swap amounts for %d loop in swaps",
len(updateAmounts))
err = swapStore.BatchUpdateSelectedSwapAmounts(ctx, updateAmounts)
if err != nil {
return err
}
// Finally mark the migration as done.
return db.SetMigration(ctx, selectedAmountMigrationID)
}

View file

@ -0,0 +1,107 @@
package loopin
import (
"context"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
// TestMigrateSelectedSwapAmount tests the selected amount migration.
func TestMigrateSelectedSwapAmount(t *testing.T) {
// Set up test context objects.
ctxb := context.Background()
testDb := loopdb.NewTestDB(t)
testClock := clock.NewTestClock(time.Now())
defer testDb.Close()
db := loopdb.NewStoreMock(t)
depositStore := deposit.NewSqlStore(testDb.BaseDB)
swapStore := NewSqlStore(
loopdb.NewTypedStore[Querier](testDb), testClock,
&chaincfg.MainNetParams,
)
newID := func() deposit.ID {
did, err := deposit.GetRandomDepositID()
require.NoError(t, err)
return did
}
d1, d2 := &deposit.Deposit{
ID: newID(),
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{0x1a, 0x2b, 0x3c, 0x4d},
Index: 0,
},
Value: btcutil.Amount(100_000),
TimeOutSweepPkScript: []byte{
0x00, 0x14, 0x1a, 0x2b, 0x3c, 0x41,
},
},
&deposit.Deposit{
ID: newID(),
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{0x2a, 0x2b, 0x3c, 0x4e},
Index: 1,
},
Value: btcutil.Amount(200_000),
TimeOutSweepPkScript: []byte{
0x00, 0x14, 0x1a, 0x2b, 0x3c, 0x4d,
},
}
err := depositStore.CreateDeposit(ctxb, d1)
require.NoError(t, err)
err = depositStore.CreateDeposit(ctxb, d2)
require.NoError(t, err)
outpoints := []string{
d1.OutPoint.String(),
d2.OutPoint.String(),
}
_, clientPubKey := test.CreateKey(1)
_, serverPubKey := test.CreateKey(2)
p2wkhAddr := "bcrt1qq68r6ff4k4pjx39efs44gcyccf7unqnu5qtjjz"
addr, err := btcutil.DecodeAddress(p2wkhAddr, nil)
require.NoError(t, err)
swap := StaticAddressLoopIn{
SwapHash: lntypes.Hash{0x1, 0x2, 0x3, 0x4},
DepositOutpoints: outpoints,
ClientPubkey: clientPubKey,
ServerPubkey: serverPubKey,
HtlcTimeoutSweepAddress: addr,
Deposits: []*deposit.Deposit{d1, d2},
}
swap.SetState(Succeeded)
err = swapStore.CreateLoopIn(ctxb, &swap)
require.NoError(t, err)
storedSwaps, err := swapStore.GetStaticAddressLoopInSwapsByStates(
ctxb, FinalStates,
)
require.NoError(t, err)
require.EqualValues(t, 0, storedSwaps[0].SelectedAmount)
err = MigrateSelectedSwapAmount(ctxb, db, depositStore, swapStore)
require.NoError(t, err)
storedSwaps, err = swapStore.GetStaticAddressLoopInSwapsByStates(
ctxb, FinalStates,
)
require.NoError(t, err)
require.EqualValues(t, d1.Value+d2.Value, storedSwaps[0].SelectedAmount)
}

View file

@ -22,7 +22,7 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
const outpointSeparator = ";"
const OutpointSeparator = ";"
var (
// ErrInvalidOutpoint is returned when an outpoint contains the outpoint
@ -88,6 +88,11 @@ type Querier interface {
// DepositsForSwapHash retrieves all deposits for a given swap hash.
DepositsForSwapHash(ctx context.Context,
swapHash []byte) ([]sqlc.DepositsForSwapHashRow, error)
// OverrideSelectedSwapAmount updates the selected swap amount for
// a given swap hash.
OverrideSelectedSwapAmount(ctx context.Context,
params sqlc.OverrideSelectedSwapAmountParams) error
}
// BaseDB is the interface that contains all the queries generated by sqlc for
@ -226,11 +231,16 @@ func (s *SqlStore) CreateLoopIn(ctx context.Context,
return errors.New("loop-in must have at least one deposit")
}
amountRequested := int64(loopIn.TotalDepositAmount())
if loopIn.SelectedAmount > 0 {
amountRequested = int64(loopIn.SelectedAmount)
}
swapArgs := sqlc.InsertSwapParams{
SwapHash: loopIn.SwapHash[:],
Preimage: loopIn.SwapPreimage[:],
InitiationTime: loopIn.InitiationTime,
AmountRequested: int64(loopIn.TotalDepositAmount()),
AmountRequested: amountRequested,
CltvExpiry: loopIn.HtlcCltvExpiry,
MaxSwapFee: int64(loopIn.MaxSwapFee),
InitiationHeight: int32(loopIn.InitiationHeight),
@ -250,13 +260,13 @@ func (s *SqlStore) CreateLoopIn(ctx context.Context,
// If so, we reject the loop-in to prevent potential issues with
// parsing.
for _, outpoint := range loopIn.DepositOutpoints {
if strings.Contains(outpoint, outpointSeparator) {
if strings.Contains(outpoint, OutpointSeparator) {
return ErrInvalidOutpoint
}
}
joinedOutpoints := strings.Join(
loopIn.DepositOutpoints, outpointSeparator,
loopIn.DepositOutpoints, OutpointSeparator,
)
staticAddressLoopInParams := sqlc.InsertStaticAddressLoopInParams{
SwapHash: loopIn.SwapHash[:],
@ -266,6 +276,7 @@ func (s *SqlStore) CreateLoopIn(ctx context.Context,
HtlcTimeoutSweepAddress: loopIn.HtlcTimeoutSweepAddress.String(),
HtlcTxFeeRateSatKw: int64(loopIn.HtlcTxFeeRate),
DepositOutpoints: joinedOutpoints,
SelectedAmount: int64(loopIn.SelectedAmount),
PaymentTimeoutSeconds: int32(loopIn.PaymentTimeoutSeconds),
}
@ -350,6 +361,27 @@ func (s *SqlStore) UpdateLoopIn(ctx context.Context,
)
}
func (s *SqlStore) BatchUpdateSelectedSwapAmounts(ctx context.Context,
updateAmounts map[lntypes.Hash]btcutil.Amount) error {
return s.baseDB.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
func(q Querier) error {
for swapHash, amount := range updateAmounts {
err := q.OverrideSelectedSwapAmount(
ctx, sqlc.OverrideSelectedSwapAmountParams{
SwapHash: swapHash[:],
SelectedAmount: int64(amount),
},
)
if err != nil {
return err
}
}
return nil
})
}
// IsStored returns true if a swap with the given hash is stored in the
// database, false otherwise.
func (s *SqlStore) IsStored(ctx context.Context, swapHash lntypes.Hash) (bool,
@ -478,7 +510,7 @@ func toStaticAddressLoopIn(_ context.Context, network *chaincfg.Params,
}
depositOutpoints := strings.Split(
swap.DepositOutpoints, outpointSeparator,
swap.DepositOutpoints, OutpointSeparator,
)
timeoutAddressString := swap.HtlcTimeoutSweepAddress
@ -548,6 +580,7 @@ func toStaticAddressLoopIn(_ context.Context, network *chaincfg.Params,
LastHop: swap.LastHop,
QuotedSwapFee: btcutil.Amount(swap.QuotedSwapFeeSatoshis),
DepositOutpoints: depositOutpoints,
SelectedAmount: btcutil.Amount(swap.SelectedAmount),
HtlcTxFeeRate: chainfee.SatPerKWeight(
swap.HtlcTxFeeRateSatKw,
),