Skip to content

Commit 56816b7

Browse files
committed
staticaddr: validate per-deposit quote expiry
1 parent 77e738e commit 56816b7

4 files changed

Lines changed: 42 additions & 47 deletions

File tree

loopd/swapclient_server.go

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,11 +1185,16 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
11851185
)
11861186
}
11871187

1188-
err = validateStaticQuoteDepositsSwappable(
1189-
depositList.FilteredDeposits, staticAddrExpiry,
1190-
currentHeight,
1188+
selectedDeposits, err := s.depositManager.DepositsForOutpoints(
1189+
ctx, req.DepositOutpoints, false,
11911190
)
11921191
if err != nil {
1192+
return nil, fmt.Errorf("unable to retrieve selected "+
1193+
"deposits: %w", err)
1194+
}
1195+
if err := loopin.ValidateDepositsSwappable(
1196+
selectedDeposits, currentHeight,
1197+
); err != nil {
11931198
return nil, err
11941199
}
11951200

@@ -2834,29 +2839,6 @@ func depositBlocksUntilExpiry(confirmationHeight int64, expiry uint32,
28342839
return confirmationHeight + int64(expiry) - bestBlockHeight
28352840
}
28362841

2837-
// validateStaticQuoteDepositsSwappable rejects manual quote deposits that are
2838-
// too close to expiry for the server's static-address loop-in HTLC timeout.
2839-
func validateStaticQuoteDepositsSwappable(deposits []*looprpc.Deposit,
2840-
csvExpiry uint32, blockHeight uint32) error {
2841-
2842-
for _, deposit := range deposits {
2843-
if deposit.ConfirmationHeight <= 0 {
2844-
continue
2845-
}
2846-
2847-
confirmationHeight := uint32(deposit.ConfirmationHeight)
2848-
swappable := loopin.IsSwappable(
2849-
confirmationHeight, blockHeight, csvExpiry,
2850-
)
2851-
if !swappable {
2852-
return fmt.Errorf("deposit %s expires before htlc",
2853-
deposit.Outpoint)
2854-
}
2855-
}
2856-
2857-
return nil
2858-
}
2859-
28602842
// StaticOpenChannel initiates an open channel request using static address
28612843
// deposits.
28622844
func (s *swapClientServer) StaticOpenChannel(ctx context.Context,

loopd/swapclient_server_staticaddr_test.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ func TestValidateStaticAddressSendCoinsRequest(t *testing.T) {
382382
func TestNewStaticAddressFundsGeneratedAddress(t *testing.T) {
383383
t.Parallel()
384384

385-
addrMgr, lnd := newTestStaticAddressContext(t)
385+
addrMgr, lnd := newTestStaticAddressContext(t, 10)
386386
rawClient := &sendCoinsRPCClient{
387387
response: &lnrpc.SendCoinsResponse{Txid: "funding-txid"},
388388
}
@@ -422,7 +422,7 @@ func TestStaticAddressForDeposit(t *testing.T) {
422422
t.Parallel()
423423

424424
ctx := context.Background()
425-
addrMgr, lnd := newTestStaticAddressContext(t)
425+
addrMgr, lnd := newTestStaticAddressContext(t, 10)
426426
server := &swapClientServer{
427427
staticAddressManager: addrMgr,
428428
lnd: &lnd.LndServices,
@@ -503,7 +503,7 @@ func TestListStaticAddressDepositsReturnsVisibleDeposits(t *testing.T) {
503503
func TestStaticAddressWithdrawalIncludesDepositAddress(t *testing.T) {
504504
t.Parallel()
505505

506-
addrMgr, _ := newTestStaticAddressContext(t)
506+
addrMgr, _ := newTestStaticAddressContext(t, 10)
507507
addresses, err := addrMgr.GetAllAddresses(context.Background())
508508
require.NoError(t, err)
509509
require.Len(t, addresses, 1)
@@ -606,7 +606,7 @@ func TestPopulateBlocksUntilExpiryUsesOwningAddress(t *testing.T) {
606606
func TestStaticAddressLoopInResponseIncludesDepositAddress(t *testing.T) {
607607
t.Parallel()
608608

609-
addrMgr, lnd := newTestStaticAddressContext(t)
609+
addrMgr, lnd := newTestStaticAddressContext(t, 10)
610610
addresses, err := addrMgr.GetAllAddresses(t.Context())
611611
require.NoError(t, err)
612612
require.Len(t, addresses, 1)
@@ -765,13 +765,17 @@ func TestGetLoopInQuoteRejectsExpiringSelectedDeposit(t *testing.T) {
765765
expiring.SetState(deposit.Deposited)
766766

767767
addrMgr, lnd := newTestStaticAddressContext(t, 10)
768+
addresses, err := addrMgr.GetAllAddresses(t.Context())
769+
require.NoError(t, err)
770+
require.Len(t, addresses, 1)
771+
expiring.AddressParams = addresses[0]
768772
server := &swapClientServer{
769773
depositManager: newTestDepositManager(expiring),
770774
staticAddressManager: addrMgr,
771775
lnd: &lnd.LndServices,
772776
}
773777

774-
_, err := server.GetLoopInQuote(t.Context(), &looprpc.QuoteRequest{
778+
_, err = server.GetLoopInQuote(t.Context(), &looprpc.QuoteRequest{
775779
DepositOutpoints: []string{expiring.OutPoint.String()},
776780
})
777781
require.ErrorContains(t, err, "expires before htlc")
@@ -801,6 +805,10 @@ func TestGetLoopInQuoteAllowsFreshSelectedDeposit(t *testing.T) {
801805

802806
quoter := &staticAddrTestLoopInQuoter{}
803807
addrMgr, lnd := newTestStaticAddressContext(t, staticAddrExpiry)
808+
addresses, err := addrMgr.GetAllAddresses(t.Context())
809+
require.NoError(t, err)
810+
require.Len(t, addresses, 1)
811+
fresh.AddressParams = addresses[0]
804812
server := &swapClientServer{
805813
depositManager: newTestDepositManager(fresh),
806814
staticAddressManager: addrMgr,

staticaddr/loopin/manager.go

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -686,16 +686,8 @@ func (m *Manager) initiateLoopIn(ctx context.Context,
686686
// too close to the HTLC timeout. Automatic selection already
687687
// filters those deposits, so manual outpoint selection must
688688
// enforce the same rule before quoting and initiating a swap.
689-
params, err := m.cfg.AddressManager.
690-
GetStaticAddressParameters(ctx)
691-
if err != nil {
692-
return nil, fmt.Errorf("unable to retrieve static "+
693-
"address parameters: %w", err)
694-
}
695-
696689
err = ValidateDepositsSwappable(
697-
selectedDeposits, params.Expiry,
698-
m.currentHeight.Load(),
690+
selectedDeposits, m.currentHeight.Load(),
699691
)
700692
if err != nil {
701693
return nil, err
@@ -986,21 +978,27 @@ func IsSwappable(confirmationHeight, blockHeight, csvExpiry uint32) bool {
986978

987979
// ValidateDepositsSwappable verifies that selected deposits still have enough
988980
// timeout runway to back a static-address loop-in HTLC.
989-
func ValidateDepositsSwappable(deposits []*deposit.Deposit, csvExpiry uint32,
981+
func ValidateDepositsSwappable(deposits []*deposit.Deposit,
990982
blockHeight uint32) error {
991983

992-
for _, deposit := range deposits {
993-
confirmationHeight := deposit.GetConfirmationHeight()
984+
for _, d := range deposits {
985+
if d.AddressParams == nil {
986+
return fmt.Errorf("missing static address parameters for "+
987+
"deposit %s", d.OutPoint.String())
988+
}
989+
990+
confirmationHeight := d.GetConfirmationHeight()
994991
if confirmationHeight <= 0 {
995992
continue
996993
}
997994

998995
swappable := IsSwappable(
999-
uint32(confirmationHeight), blockHeight, csvExpiry,
996+
uint32(confirmationHeight), blockHeight,
997+
d.AddressParams.Expiry,
1000998
)
1001999
if !swappable {
10021000
return fmt.Errorf("deposit %s expires before htlc",
1003-
deposit.OutPoint)
1001+
d.OutPoint)
10041002
}
10051003
}
10061004

staticaddr/loopin/manager_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ func TestInitiateLoopInAllowsReservedAutoloopLabel(t *testing.T) {
271271

272272
const confirmationHeight = 0
273273
selectedDeposit := makeDeposit(1, 0, 9_000, confirmationHeight)
274+
selectedDeposit.AddressParams = &script.Parameters{Expiry: 10_000}
274275
selectedOutpoint := selectedDeposit.OutPoint.String()
275276
quoteErr := errors.New("quote failed")
276277
quoteGetter := &mockQuoteGetter{
@@ -317,14 +318,17 @@ func TestInitiateLoopInRejectsExpiringSelectedDeposit(t *testing.T) {
317318
selectedDeposit := makeDeposit(
318319
2, 0, 9_000, confirmationHeight,
319320
)
321+
selectedDeposit.AddressParams = &script.Parameters{Expiry: csvExpiry}
320322
selectedOutpoint := selectedDeposit.OutPoint.String()
321323
quoteGetter := &mockQuoteGetter{
322324
err: errors.New("quote should not be reached"),
323325
}
324326

325327
manager, err := NewManager(&Config{
326328
AddressManager: &mockAddressManager{
327-
params: &script.Parameters{Expiry: csvExpiry},
329+
// A global expiry would make this deposit look fresh. The
330+
// deposit's owning address must take precedence.
331+
params: &script.Parameters{Expiry: csvExpiry * 2},
328332
},
329333
DepositManager: &mockDepositManager{
330334
byOutpoint: map[string]*deposit.Deposit{
@@ -361,13 +365,16 @@ func TestInitiateLoopInAllowsFreshSelectedDeposit(t *testing.T) {
361365
selectedDeposit := makeDeposit(
362366
3, 0, 9_000, confirmationHeight,
363367
)
368+
selectedDeposit.AddressParams = &script.Parameters{Expiry: csvExpiry}
364369
selectedOutpoint := selectedDeposit.OutPoint.String()
365370
quoteErr := errors.New("quote reached")
366371
quoteGetter := &mockQuoteGetter{err: quoteErr}
367372

368373
manager, err := NewManager(&Config{
369374
AddressManager: &mockAddressManager{
370-
params: &script.Parameters{Expiry: csvExpiry},
375+
// A global expiry would reject this deposit. The deposit's
376+
// owning address must take precedence.
377+
params: &script.Parameters{Expiry: 10},
371378
},
372379
DepositManager: &mockDepositManager{
373380
byOutpoint: map[string]*deposit.Deposit{

0 commit comments

Comments
 (0)