mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
staticaddr/loopin: treat unconfirmed deposits as swappable
Static address deposits with no confirmation height have not started their CSV timeout yet, so keep them eligible for loop-in selection instead of treating them as already near expiry. Prefer confirmed deposits before unconfirmed ones during automatic selection, and share the remaining-lifetime calculation used by the selector.
This commit is contained in:
parent
557ba99513
commit
0223caa370
2 changed files with 75 additions and 23 deletions
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
|
|
@ -845,11 +846,11 @@ func (m *Manager) activeDepositsForLoopIn(loopIn *StaticAddressLoopIn) (
|
|||
)
|
||||
}
|
||||
|
||||
// SelectDeposits sorts the deposits by amount in descending order, then by
|
||||
// blocks-until-expiry in ascending order. It then selects the deposits that
|
||||
// are needed to cover the amount requested without leaving a dust change. It
|
||||
// returns an error if the sum of deposits minus dust is less than the requested
|
||||
// amount.
|
||||
// SelectDeposits sorts deposits by confirmation status first, then by amount in
|
||||
// descending order, then by blocks-until-expiry in ascending order. It then
|
||||
// selects the deposits that are needed to cover the amount requested without
|
||||
// leaving a dust change. It returns an error if the sum of deposits minus dust
|
||||
// is less than the requested amount.
|
||||
func SelectDeposits(targetAmount btcutil.Amount,
|
||||
unfilteredDeposits []*deposit.Deposit, csvExpiry uint32,
|
||||
blockHeight uint32) ([]*deposit.Deposit, error) {
|
||||
|
|
@ -871,14 +872,25 @@ func SelectDeposits(targetAmount btcutil.Amount,
|
|||
deposits = append(deposits, d)
|
||||
}
|
||||
|
||||
// Sort the deposits by amount in descending order, then by
|
||||
// blocks-until-expiry in ascending order.
|
||||
// Sort confirmed deposits ahead of unconfirmed ones so auto-selection
|
||||
// prefers deposits the server can accept immediately. Within each group
|
||||
// we prefer larger deposits, then earlier expiries.
|
||||
sort.Slice(deposits, func(i, j int) bool {
|
||||
iConfirmationHeight := uint32(deposits[i].GetConfirmationHeight())
|
||||
jConfirmationHeight := uint32(deposits[j].GetConfirmationHeight())
|
||||
iConfirmed := iConfirmationHeight > 0
|
||||
jConfirmed := jConfirmationHeight > 0
|
||||
if iConfirmed != jConfirmed {
|
||||
return iConfirmed
|
||||
}
|
||||
|
||||
if deposits[i].Value == deposits[j].Value {
|
||||
iExp := uint32(deposits[i].GetConfirmationHeight()) +
|
||||
csvExpiry - blockHeight
|
||||
jExp := uint32(deposits[j].GetConfirmationHeight()) +
|
||||
csvExpiry - blockHeight
|
||||
iExp := blocksUntilDepositExpiry(
|
||||
iConfirmationHeight, blockHeight, csvExpiry,
|
||||
)
|
||||
jExp := blocksUntilDepositExpiry(
|
||||
jConfirmationHeight, blockHeight, csvExpiry,
|
||||
)
|
||||
|
||||
return iExp < jExp
|
||||
}
|
||||
|
|
@ -910,20 +922,33 @@ func SelectDeposits(targetAmount btcutil.Amount,
|
|||
// IsSwappable checks if a deposit is swappable. It returns true if the deposit
|
||||
// is not expired and the htlc is not too close to expiry.
|
||||
func IsSwappable(confirmationHeight, blockHeight, csvExpiry uint32) bool {
|
||||
// The deposit expiry height is the confirmation height plus the csv
|
||||
// expiry.
|
||||
depositExpiryHeight := confirmationHeight + csvExpiry
|
||||
|
||||
// The htlc expiry height is the current height plus the htlc
|
||||
// cltv delta.
|
||||
htlcExpiryHeight := blockHeight + DefaultLoopInOnChainCltvDelta
|
||||
|
||||
// Ensure that the deposit doesn't expire before the htlc.
|
||||
if depositExpiryHeight < htlcExpiryHeight+DepositHtlcDelta {
|
||||
return false
|
||||
if confirmationHeight == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
// The deposit expiry height is the confirmation height plus the csv
|
||||
// expiry.
|
||||
return blocksUntilDepositExpiry(
|
||||
confirmationHeight, blockHeight, csvExpiry,
|
||||
) >= DefaultLoopInOnChainCltvDelta+DepositHtlcDelta
|
||||
}
|
||||
|
||||
// blocksUntilDepositExpiry returns the remaining number of blocks until a
|
||||
// deposit expires. Unconfirmed deposits return MaxUint32 because their CSV has
|
||||
// not started yet.
|
||||
func blocksUntilDepositExpiry(confirmationHeight, blockHeight,
|
||||
csvExpiry uint32) uint32 {
|
||||
|
||||
if confirmationHeight == 0 {
|
||||
return math.MaxUint32
|
||||
}
|
||||
|
||||
depositExpiryHeight := confirmationHeight + csvExpiry
|
||||
if depositExpiryHeight <= blockHeight {
|
||||
return 0
|
||||
}
|
||||
|
||||
return depositExpiryHeight - blockHeight
|
||||
}
|
||||
|
||||
// DeduceSwapAmount calculates the swap amount based on the selected amount and
|
||||
|
|
|
|||
|
|
@ -81,6 +81,27 @@ func TestSelectDeposits(t *testing.T) {
|
|||
expected: []*deposit.Deposit{d3},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "prefer confirmed deposit over larger unconfirmed one",
|
||||
deposits: []*deposit.Deposit{
|
||||
{
|
||||
Value: 2_000_000,
|
||||
ConfirmationHeight: 0,
|
||||
},
|
||||
{
|
||||
Value: 1_500_000,
|
||||
ConfirmationHeight: 5_004,
|
||||
},
|
||||
},
|
||||
targetValue: 1_000_000,
|
||||
expected: []*deposit.Deposit{
|
||||
{
|
||||
Value: 1_500_000,
|
||||
ConfirmationHeight: 5_004,
|
||||
},
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "single deposit insufficient by 1",
|
||||
deposits: []*deposit.Deposit{d1},
|
||||
|
|
@ -357,6 +378,12 @@ func TestGetAllSwapsPreservesStoreDeposits(t *testing.T) {
|
|||
require.Equal(t, []*deposit.Deposit{currentDeposit}, swaps[0].Deposits)
|
||||
}
|
||||
|
||||
// TestIsSwappableUnconfirmed checks that an unconfirmed deposit is considered
|
||||
// swappable because its CSV timeout has not started yet.
|
||||
func TestIsSwappableUnconfirmed(t *testing.T) {
|
||||
require.True(t, IsSwappable(0, 5000, 1000))
|
||||
}
|
||||
|
||||
// mockDepositManager implements DepositManager for tests.
|
||||
type mockDepositManager struct {
|
||||
// activeDeposits is the set returned by GetActiveDepositsInState.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue