loop/staticaddr/loopin/sql_store_test.go
Boris Nagaev 7cf0a87c2b
liquidity: count static loop-ins
Teach the liquidity manager to include persisted static loop-ins
in budget accounting, in-flight limits, and peer traffic backoff.
This adds the static fee model used for conservative accounting
and passes storage errors through the relevant planner helpers.

The daemon wiring now exposes static loop-ins to liquidity so the
manager can see the same ongoing swaps that the static-address
subsystem persists, while easy autoloop keeps working with the new
fallible traffic lookup path.
2026-05-22 02:20:36 -05:00

288 lines
8.7 KiB
Go

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"
)
// TestGetStaticAddressLoopInSwapsByStates tests that we can retrieve
// StaticAddressLoopIn swaps by their states and that the deposits
// associated with the swaps are correctly populated.
func TestGetStaticAddressLoopInSwapsByStates(t *testing.T) {
// Set up test context objects.
ctxb := context.Background()
testDb := loopdb.NewTestDB(t)
testClock := clock.NewTestClock(time.Now())
defer testDb.Close()
depositStore := deposit.NewSqlStore(testDb.BaseDB)
swapStore := NewSqlStore(
loopdb.NewTypedStore[Querier](testDb), testClock,
&chaincfg.RegressionNetParams,
)
newID := func() deposit.ID {
did, err := deposit.GetRandomDepositID()
require.NoError(t, err)
return did
}
loopingDepositID := newID()
loopedInDepositID := newID()
d1, d2 := &deposit.Deposit{
ID: loopingDepositID,
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: loopedInDepositID,
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)
// Add two updates per deposit, expect the last to be retrieved.
d1.SetState(deposit.Deposited)
d2.SetState(deposit.Deposited)
err = depositStore.UpdateDeposit(ctxb, d1)
require.NoError(t, err)
err = depositStore.UpdateDeposit(ctxb, d2)
require.NoError(t, err)
d1.SetState(deposit.LoopingIn)
d2.SetState(deposit.LoopedIn)
err = depositStore.UpdateDeposit(ctxb, d1)
require.NoError(t, err)
err = depositStore.UpdateDeposit(ctxb, d2)
require.NoError(t, err)
_, clientPubKey := test.CreateKey(1)
_, serverPubKey := test.CreateKey(2)
addr, err := btcutil.DecodeAddress(P2wkhAddr, nil)
require.NoError(t, err)
// Create pending swap.
swapHashPending := lntypes.Hash{0x1, 0x2, 0x3, 0x4}
swapPending := StaticAddressLoopIn{
SwapHash: swapHashPending,
SwapPreimage: lntypes.Preimage{0x1, 0x2, 0x3, 0x4},
DepositOutpoints: []string{d1.OutPoint.String()},
Deposits: []*deposit.Deposit{d1},
ClientPubkey: clientPubKey,
ServerPubkey: serverPubKey,
HtlcTimeoutSweepAddress: addr,
}
swapPending.SetState(SignHtlcTx)
err = swapStore.CreateLoopIn(ctxb, &swapPending)
require.NoError(t, err)
// Create succeeded swap.
swapHashSucceeded := lntypes.Hash{0x2, 0x2, 0x3, 0x5}
swapSucceeded := StaticAddressLoopIn{
SwapHash: swapHashSucceeded,
SwapPreimage: lntypes.Preimage{0x2, 0x2, 0x3, 0x5},
DepositOutpoints: []string{d2.OutPoint.String()},
Deposits: []*deposit.Deposit{d2},
ClientPubkey: clientPubKey,
ServerPubkey: serverPubKey,
HtlcTimeoutSweepAddress: addr,
}
swapSucceeded.SetState(Succeeded)
err = swapStore.CreateLoopIn(ctxb, &swapSucceeded)
require.NoError(t, err)
pendingSwaps, err := swapStore.GetStaticAddressLoopInSwapsByStates(ctxb, PendingStates)
require.NoError(t, err)
require.Len(t, pendingSwaps, 1)
require.Equal(t, swapHashPending, pendingSwaps[0].SwapHash)
require.Equal(t, []string{d1.OutPoint.String()}, pendingSwaps[0].DepositOutpoints)
require.Equal(t, SignHtlcTx, pendingSwaps[0].GetState())
pendingDeposits := pendingSwaps[0].Deposits
require.Len(t, pendingDeposits, 1)
require.Equal(t, d1.ID, pendingDeposits[0].ID)
require.Equal(t, d1.OutPoint, pendingDeposits[0].OutPoint)
require.Equal(t, d1.Value, pendingDeposits[0].Value)
require.Equal(t, deposit.LoopingIn, pendingDeposits[0].GetState())
finalizedSwaps, err := swapStore.GetStaticAddressLoopInSwapsByStates(ctxb, FinalStates)
require.NoError(t, err)
require.Len(t, finalizedSwaps, 1)
require.Equal(t, swapHashSucceeded, finalizedSwaps[0].SwapHash)
require.Equal(t, []string{d2.OutPoint.String()}, finalizedSwaps[0].DepositOutpoints)
require.Equal(t, Succeeded, finalizedSwaps[0].GetState())
finalizedDeposits := finalizedSwaps[0].Deposits
require.Len(t, finalizedDeposits, 1)
require.Equal(t, d2.ID, finalizedDeposits[0].ID)
require.Equal(t, d2.OutPoint, finalizedDeposits[0].OutPoint)
require.Equal(t, d2.Value, finalizedDeposits[0].Value)
require.Equal(t, deposit.LoopedIn, finalizedDeposits[0].GetState())
}
// TestCreateLoopIn tests that CreateLoopIn correctly creates a new
// StaticAddressLoopIn swap and associates it with the provided deposits.
func TestCreateLoopIn(t *testing.T) {
// Set up test context objects.
ctx := t.Context()
testDb := loopdb.NewTestDB(t)
testClock := clock.NewTestClock(time.Now())
defer testDb.Close()
depositStore := deposit.NewSqlStore(testDb.BaseDB)
swapStore := NewSqlStore(
loopdb.NewTypedStore[Querier](testDb), testClock,
&chaincfg.RegressionNetParams,
)
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(ctx, d1)
require.NoError(t, err)
err = depositStore.CreateDeposit(ctx, d2)
require.NoError(t, err)
d1.SetState(deposit.LoopingIn)
d2.SetState(deposit.LoopingIn)
err = depositStore.UpdateDeposit(ctx, d1)
require.NoError(t, err)
err = depositStore.UpdateDeposit(ctx, d2)
require.NoError(t, err)
_, clientPubKey := test.CreateKey(1)
_, serverPubKey := test.CreateKey(2)
addr, err := btcutil.DecodeAddress(P2wkhAddr, nil)
require.NoError(t, err)
// Create pending swap.
swapHashPending := lntypes.Hash{0x1, 0x2, 0x3, 0x4}
swapPending := StaticAddressLoopIn{
SwapHash: swapHashPending,
SwapPreimage: lntypes.Preimage{0x1, 0x2, 0x3, 0x4},
DepositOutpoints: []string{d1.OutPoint.String(),
d2.OutPoint.String()},
Deposits: []*deposit.Deposit{d1, d2},
ClientPubkey: clientPubKey,
ServerPubkey: serverPubKey,
HtlcTimeoutSweepAddress: addr,
}
swapPending.SetState(SignHtlcTx)
err = swapStore.CreateLoopIn(ctx, &swapPending)
require.NoError(t, err)
depositIDs, err := swapStore.DepositIDsForSwapHash(
ctx, swapHashPending,
)
require.NoError(t, err)
require.Len(t, depositIDs, 2)
require.Contains(t, depositIDs, d1.ID)
require.Contains(t, depositIDs, d2.ID)
swapHashes, err := swapStore.SwapHashesForDepositIDs(
ctx, []deposit.ID{depositIDs[0], depositIDs[1]},
)
require.NoError(t, err)
require.Len(t, swapHashes, 1)
require.Len(t, swapHashes[swapHashPending], 2)
require.Contains(t, swapHashes[swapHashPending], depositIDs[0])
require.Contains(t, swapHashes[swapHashPending], depositIDs[1])
swap, err := swapStore.GetLoopInByHash(ctx, swapHashPending)
require.NoError(t, err)
require.Equal(t, swapHashPending, swap.SwapHash)
require.Equal(t, []string{d1.OutPoint.String(), d2.OutPoint.String()},
swap.DepositOutpoints)
require.Equal(t, SignHtlcTx, swap.GetState())
require.Len(t, swap.Deposits, 2)
require.Equal(t, d1.ID, swap.Deposits[0].ID)
require.Equal(t, d1.OutPoint, swap.Deposits[0].OutPoint)
require.Equal(t, d1.Value, swap.Deposits[0].Value)
require.Equal(t, deposit.LoopingIn, swap.Deposits[0].GetState())
require.Equal(t, d2.ID, swap.Deposits[1].ID)
require.Equal(t, d2.OutPoint, swap.Deposits[1].OutPoint)
require.Equal(t, d2.Value, swap.Deposits[1].Value)
require.Equal(t, deposit.LoopingIn, swap.Deposits[1].GetState())
updateTime := testClock.Now().Add(time.Minute)
testClock.SetTime(updateTime)
swapPending.SetState(Succeeded)
err = swapStore.UpdateLoopIn(ctx, &swapPending)
require.NoError(t, err)
swap, err = swapStore.GetLoopInByHash(ctx, swapHashPending)
require.NoError(t, err)
require.Equal(t, Succeeded, swap.GetState())
require.WithinDuration(
t, updateTime.UTC(), swap.LastUpdateTime.UTC(),
time.Microsecond,
)
}