staticaddr: exclude close-to-expiry depositis from SelectDeposits

This commit is contained in:
Slyghtning 2025-10-08 10:54:22 +02:00
parent 9f57c8ee01
commit 152385885f
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
2 changed files with 79 additions and 6 deletions

View file

@ -862,8 +862,25 @@ func (m *Manager) GetAllSwaps(ctx context.Context) ([]*StaticAddressLoopIn,
// 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, deposits []*deposit.Deposit,
csvExpiry uint32, blockHeight uint32) ([]*deposit.Deposit, error) {
func SelectDeposits(targetAmount btcutil.Amount,
unfilteredDeposits []*deposit.Deposit, csvExpiry uint32,
blockHeight uint32) ([]*deposit.Deposit, error) {
// Filter out deposits that are too close to expiry to be swapped.
var deposits []*deposit.Deposit
for _, d := range unfilteredDeposits {
if !IsSwappable(
uint32(d.ConfirmationHeight), blockHeight, csvExpiry,
) {
log.Debugf("Skipping deposit %s as it expires before "+
"the htlc", d.OutPoint.String())
continue
}
deposits = append(deposits, d)
}
// Sort the deposits by amount in descending order, then by
// blocks-until-expiry in ascending order.
@ -901,6 +918,25 @@ func SelectDeposits(targetAmount btcutil.Amount, deposits []*deposit.Deposit,
selectedAmount, targetAmount)
}
// 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
}
return true
}
// DeduceSwapAmount calculates the swap amount based on the selected amount and
// the total deposit amount. It checks if the selected amount leaves a dust
// change output or exceeds the total deposits value. Note that if the selected

View file

@ -29,16 +29,16 @@ type testCase struct {
func TestSelectDeposits(t *testing.T) {
d1, d2, d3, d4 := &deposit.Deposit{
Value: 1_000_000,
ConfirmationHeight: 1000,
ConfirmationHeight: 5_000,
}, &deposit.Deposit{
Value: 2_000_000,
ConfirmationHeight: 2000,
ConfirmationHeight: 5_001,
}, &deposit.Deposit{
Value: 3_000_000,
ConfirmationHeight: 3000,
ConfirmationHeight: 5_002,
}, &deposit.Deposit{
Value: 3_000_000,
ConfirmationHeight: 3001,
ConfirmationHeight: 5_003,
}
d1.Hash = chainhash.Hash{1}
d1.Index = 0
@ -121,6 +121,43 @@ func TestSelectDeposits(t *testing.T) {
expected: []*deposit.Deposit{d3},
expectedErr: "",
},
{
name: "prefilter filters deposits close to expiry",
deposits: func() []*deposit.Deposit {
// dClose expires before
// htlcExpiry+DepositHtlcDelta and must be
// filtered out. dOK expires exactly at the
// threshold and must be eligible.
dClose := &deposit.Deposit{
Value: 3_000_000,
ConfirmationHeight: 3000,
}
dClose.Hash = chainhash.Hash{5}
dClose.Index = 0
dOK := &deposit.Deposit{
Value: 2_000_000,
ConfirmationHeight: 3050,
}
dOK.Hash = chainhash.Hash{6}
dOK.Index = 0
return []*deposit.Deposit{dClose, dOK}
}(),
targetValue: 1_000_000,
csvExpiry: 1000,
blockHeight: 3000,
expected: func() []*deposit.Deposit {
// Only dOK should be considered.
// dClose is filtered.
dOK := &deposit.Deposit{
Value: 2_000_000,
ConfirmationHeight: 3050,
}
dOK.Hash = chainhash.Hash{6}
dOK.Index = 0
return []*deposit.Deposit{dOK}
}(),
expectedErr: "",
},
}
for _, tc := range testCases {