Skip to content

Commit 43323ff

Browse files
committed
loopout: extend htlc expiry based on conf target
1 parent e72d998 commit 43323ff

File tree

7 files changed

+74
-58
lines changed

7 files changed

+74
-58
lines changed

client.go

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package loop
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"strings"
78
"sync"
89
"sync/atomic"
@@ -33,20 +34,10 @@ var (
3334
// more than the server maximum.
3435
ErrSwapAmountTooHigh = errors.New("swap amount too high")
3536

36-
// ErrExpiryTooSoon is returned when the server proposes an expiry that
37-
// is too soon for us.
38-
ErrExpiryTooSoon = errors.New("swap expiry too soon")
39-
4037
// ErrExpiryTooFar is returned when the server proposes an expiry that
4138
// is too soon for us.
4239
ErrExpiryTooFar = errors.New("swap expiry too far")
4340

44-
// ErrSweepConfTargetTooFar is returned when the client proposes a
45-
// confirmation target to sweep the on-chain HTLC of a Loop Out that is
46-
// beyond the expiration height proposed by the server.
47-
ErrSweepConfTargetTooFar = errors.New("sweep confirmation target is " +
48-
"beyond swap expiration height")
49-
5041
// serverRPCTimeout is the maximum time a gRPC request to the server
5142
// should be allowed to take.
5243
serverRPCTimeout = 30 * time.Second
@@ -363,8 +354,21 @@ func (s *Client) LoopOut(globalCtx context.Context,
363354
return nil, err
364355
}
365356

366-
// Create a new swap object for this swap.
357+
// Calculate htlc expiry height.
358+
terms, err := s.Server.GetLoopOutTerms(globalCtx)
359+
if err != nil {
360+
return nil, err
361+
}
362+
367363
initiationHeight := s.executor.height()
364+
request.Expiry, err = s.getExpiry(
365+
initiationHeight, terms, request.SweepConfTarget,
366+
)
367+
if err != nil {
368+
return nil, err
369+
}
370+
371+
// Create a new swap object for this swap.
368372
swapCfg := newSwapConfig(s.lndServices, s.Store, s.Server)
369373
initResult, err := newLoopOutSwap(
370374
globalCtx, swapCfg, initiationHeight, request,
@@ -386,6 +390,24 @@ func (s *Client) LoopOut(globalCtx context.Context,
386390
}, nil
387391
}
388392

393+
// getExpiry returns an absolute expiry height based on the sweep confirmation
394+
// target, constrained by the server terms.
395+
func (s *Client) getExpiry(height int32, terms *LoopOutTerms,
396+
confTarget int32) (int32, error) {
397+
398+
switch {
399+
case confTarget < terms.MinCltvDelta:
400+
return height + terms.MinCltvDelta, nil
401+
402+
case confTarget > terms.MaxCltvDelta:
403+
return 0, fmt.Errorf("confirmation target %v exceeds maximum "+
404+
"server cltv delta of %v", confTarget,
405+
terms.MaxCltvDelta)
406+
}
407+
408+
return height + confTarget, nil
409+
}
410+
389411
// LoopOutQuote takes a LoopOut amount and returns a break down of estimated
390412
// costs for the client. Both the swap server and the on-chain fee estimator
391413
// are queried to get to build the quote response.
@@ -405,8 +427,14 @@ func (s *Client) LoopOutQuote(ctx context.Context,
405427
return nil, ErrSwapAmountTooHigh
406428
}
407429

430+
height := s.executor.height()
431+
expiry, err := s.getExpiry(height, terms, request.SweepConfTarget)
432+
if err != nil {
433+
return nil, err
434+
}
435+
408436
quote, err := s.Server.GetLoopOutQuote(
409-
ctx, request.Amount, request.SwapPublicationDeadline,
437+
ctx, request.Amount, expiry, request.SwapPublicationDeadline,
410438
)
411439
if err != nil {
412440
return nil, err
@@ -440,7 +468,6 @@ func (s *Client) LoopOutQuote(ctx context.Context,
440468
MinerFee: minerFee,
441469
PrepayAmount: quote.PrepayAmount,
442470
SwapPaymentDest: quote.SwapPaymentDest,
443-
CltvDelta: quote.CltvDelta,
444471
}, nil
445472
}
446473

interface.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ type OutRequest struct {
7171
// SwapPublicationDeadline can be set by the client to allow the server
7272
// delaying publication of the swap HTLC to save on chain fees.
7373
SwapPublicationDeadline time.Time
74+
75+
// Expiry is the absolute expiry height of the on-chain htlc.
76+
Expiry int32
7477
}
7578

7679
// Out contains the full details of a loop out request. This includes things
@@ -146,10 +149,6 @@ type LoopOutQuote struct {
146149
// sweep the htlc.
147150
MinerFee btcutil.Amount
148151

149-
// Time lock delta relative to current block height that swap server
150-
// will accept on the swap initiation call.
151-
CltvDelta int32
152-
153152
// SwapPaymentDest is the node pubkey where to swap payment needs to be
154153
// sent to.
155154
SwapPaymentDest [33]byte

loopd/swapclient_server.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,6 @@ func (s *swapClientServer) LoopOutQuote(ctx context.Context,
373373
PrepayAmtSat: int64(quote.PrepayAmount),
374374
SwapFeeSat: int64(quote.SwapFee),
375375
SwapPaymentDest: quote.SwapPaymentDest[:],
376-
CltvDelta: quote.CltvDelta,
377376
}, nil
378377
}
379378

loopout.go

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,14 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
106106

107107
// Post the swap parameters to the swap server. The response contains
108108
// the server revocation key and the swap and prepay invoices.
109-
log.Infof("Initiating swap request at height %v", currentHeight)
109+
log.Infof("Initiating swap request at height %v: amt=%v, expiry=%v",
110+
currentHeight, request.Amount, request.Expiry)
110111

111112
// The swap deadline will be given to the server for it to use as the
112113
// latest swap publication time.
113114
swapResp, err := cfg.server.NewLoopOutSwap(
114-
globalCtx, swapHash, request.Amount, receiverKey,
115-
request.SwapPublicationDeadline,
115+
globalCtx, swapHash, request.Amount, request.Expiry,
116+
receiverKey, request.SwapPublicationDeadline,
116117
)
117118
if err != nil {
118119
return nil, fmt.Errorf("cannot initiate swap: %v", err)
@@ -150,7 +151,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
150151
SenderKey: swapResp.senderKey,
151152
Preimage: swapPreimage,
152153
AmountRequested: request.Amount,
153-
CltvExpiry: swapResp.expiry,
154+
CltvExpiry: request.Expiry,
154155
MaxMinerFee: request.MaxMinerFee,
155156
MaxSwapFee: request.MaxSwapFee,
156157
},
@@ -994,18 +995,5 @@ func validateLoopOutContract(lnd *lndclient.LndServices,
994995
return ErrPrepayAmountTooHigh
995996
}
996997

997-
if response.expiry-height < MinLoopOutPreimageRevealDelta {
998-
log.Warnf("Proposed expiry %v (delta %v) too soon",
999-
response.expiry, response.expiry-height)
1000-
1001-
return ErrExpiryTooSoon
1002-
}
1003-
1004-
// Ensure the client has provided a sweep confirmation target that does
1005-
// not exceed the height at which we revert back to using the default.
1006-
if height+request.SweepConfTarget >= response.expiry-DefaultSweepConfTargetDelta {
1007-
return ErrSweepConfTargetTooFar
1008-
}
1009-
1010998
return nil
1011999
}

loopout_test.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ func TestLateHtlcPublish(t *testing.T) {
151151

152152
cfg := newSwapConfig(&lnd.LndServices, store, server)
153153

154+
testRequest.Expiry = height + testLoopOutMinOnChainCltvDelta
155+
154156
initResult, err := newLoopOutSwap(
155157
context.Background(), cfg, height, testRequest,
156158
)
@@ -227,7 +229,7 @@ func TestCustomSweepConfTarget(t *testing.T) {
227229
// the default.
228230
testReq := *testRequest
229231

230-
testReq.SweepConfTarget = testLoopOutOnChainCltvDelta -
232+
testReq.SweepConfTarget = testLoopOutMinOnChainCltvDelta -
231233
DefaultSweepConfTargetDelta - 1
232234

233235
// Set up custom fee estimates such that the lower confirmation target
@@ -376,8 +378,8 @@ func TestCustomSweepConfTarget(t *testing.T) {
376378

377379
// We'll then notify the height at which we begin using the default
378380
// confirmation target.
379-
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutOnChainCltvDelta -
380-
DefaultSweepConfTargetDelta
381+
defaultConfTargetHeight := ctx.Lnd.Height +
382+
testLoopOutMinOnChainCltvDelta - DefaultSweepConfTargetDelta
381383
blockEpochChan <- int32(defaultConfTargetHeight)
382384
expiryChan <- time.Now()
383385

@@ -427,8 +429,9 @@ func TestPreimagePush(t *testing.T) {
427429
// Start with a high confirmation delta which will have a very high fee
428430
// attached to it.
429431
testReq := *testRequest
430-
testReq.SweepConfTarget = testLoopOutOnChainCltvDelta -
432+
testReq.SweepConfTarget = testLoopOutMinOnChainCltvDelta -
431433
DefaultSweepConfTargetDelta - 1
434+
testReq.Expiry = ctx.Lnd.Height + testLoopOutMinOnChainCltvDelta
432435

433436
// We set our mock fee estimate for our target sweep confs to be our
434437
// max miner fee *2, so that our fee will definitely be above what we
@@ -520,7 +523,7 @@ func TestPreimagePush(t *testing.T) {
520523
// Now, we notify the height at which the client will start using the
521524
// default confirmation target. This has the effect of lowering our fees
522525
// so that the client still start sweeping.
523-
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutOnChainCltvDelta -
526+
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutMinOnChainCltvDelta -
524527
DefaultSweepConfTargetDelta
525528
blockEpochChan <- defaultConfTargetHeight
526529

server_mock_test.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ import (
1818
var (
1919
testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
2020

21-
testLoopOutOnChainCltvDelta = int32(30)
22-
testChargeOnChainCltvDelta = int32(100)
23-
testSwapFee = btcutil.Amount(210)
24-
testFixedPrepayAmount = btcutil.Amount(100)
25-
testMinSwapAmount = btcutil.Amount(10000)
26-
testMaxSwapAmount = btcutil.Amount(1000000)
21+
testLoopOutMinOnChainCltvDelta = int32(30)
22+
testLoopOutMaxOnChainCltvDelta = int32(40)
23+
testChargeOnChainCltvDelta = int32(100)
24+
testSwapFee = btcutil.Amount(210)
25+
testFixedPrepayAmount = btcutil.Amount(100)
26+
testMinSwapAmount = btcutil.Amount(10000)
27+
testMaxSwapAmount = btcutil.Amount(1000000)
2728
)
2829

2930
// serverMock is used in client unit tests to simulate swap server behaviour.
@@ -58,7 +59,7 @@ func newServerMock() *serverMock {
5859
}
5960

6061
func (s *serverMock) NewLoopOutSwap(ctx context.Context,
61-
swapHash lntypes.Hash, amount btcutil.Amount,
62+
swapHash lntypes.Hash, amount btcutil.Amount, expiry int32,
6263
receiverKey [33]byte, _ time.Time) (
6364
*newLoopOutResponse, error) {
6465

@@ -87,7 +88,6 @@ func (s *serverMock) NewLoopOutSwap(ctx context.Context,
8788
senderKey: senderKeyArray,
8889
swapInvoice: swapPayReqString,
8990
prepayInvoice: prePayReqString,
90-
expiry: s.height + testLoopOutOnChainCltvDelta,
9191
}, nil
9292
}
9393

@@ -97,18 +97,19 @@ func (s *serverMock) GetLoopOutTerms(ctx context.Context) (
9797
return &LoopOutTerms{
9898
MinSwapAmount: testMinSwapAmount,
9999
MaxSwapAmount: testMaxSwapAmount,
100+
MinCltvDelta: testLoopOutMinOnChainCltvDelta,
101+
MaxCltvDelta: testLoopOutMaxOnChainCltvDelta,
100102
}, nil
101103
}
102104

103105
func (s *serverMock) GetLoopOutQuote(ctx context.Context, amt btcutil.Amount,
104-
_ time.Time) (*LoopOutQuote, error) {
106+
expiry int32, _ time.Time) (*LoopOutQuote, error) {
105107

106108
dest := [33]byte{1, 2, 3}
107109

108110
return &LoopOutQuote{
109111
SwapFee: testSwapFee,
110112
SwapPaymentDest: dest,
111-
CltvDelta: testLoopOutOnChainCltvDelta,
112113
PrepayAmount: testFixedPrepayAmount,
113114
}, nil
114115
}

swap_server_client.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525

2626
// protocolVersion defines the version of the protocol that is currently
2727
// supported by the loop client.
28-
const protocolVersion = looprpc.ProtocolVersion_PREIMAGE_PUSH_LOOP_OUT
28+
const protocolVersion = looprpc.ProtocolVersion_USER_EXPIRY_LOOP_OUT
2929

3030
var (
3131
// errServerSubscriptionComplete is returned when our subscription to
@@ -46,7 +46,7 @@ type swapServerClient interface {
4646
GetLoopOutTerms(ctx context.Context) (
4747
*LoopOutTerms, error)
4848

49-
GetLoopOutQuote(ctx context.Context, amt btcutil.Amount,
49+
GetLoopOutQuote(ctx context.Context, amt btcutil.Amount, expiry int32,
5050
swapPublicationDeadline time.Time) (
5151
*LoopOutQuote, error)
5252

@@ -57,7 +57,7 @@ type swapServerClient interface {
5757
*LoopInQuote, error)
5858

5959
NewLoopOutSwap(ctx context.Context,
60-
swapHash lntypes.Hash, amount btcutil.Amount,
60+
swapHash lntypes.Hash, amount btcutil.Amount, expiry int32,
6161
receiverKey [33]byte,
6262
swapPublicationDeadline time.Time) (
6363
*newLoopOutResponse, error)
@@ -146,7 +146,7 @@ func (s *grpcSwapServerClient) GetLoopOutTerms(ctx context.Context) (
146146
}
147147

148148
func (s *grpcSwapServerClient) GetLoopOutQuote(ctx context.Context,
149-
amt btcutil.Amount, swapPublicationDeadline time.Time) (
149+
amt btcutil.Amount, expiry int32, swapPublicationDeadline time.Time) (
150150
*LoopOutQuote, error) {
151151

152152
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
@@ -156,6 +156,7 @@ func (s *grpcSwapServerClient) GetLoopOutQuote(ctx context.Context,
156156
Amt: uint64(amt),
157157
SwapPublicationDeadline: swapPublicationDeadline.Unix(),
158158
ProtocolVersion: protocolVersion,
159+
Expiry: expiry,
159160
},
160161
)
161162
if err != nil {
@@ -175,7 +176,6 @@ func (s *grpcSwapServerClient) GetLoopOutQuote(ctx context.Context,
175176
return &LoopOutQuote{
176177
PrepayAmount: btcutil.Amount(quoteResp.PrepayAmt),
177178
SwapFee: btcutil.Amount(quoteResp.SwapFee),
178-
CltvDelta: quoteResp.CltvDelta,
179179
SwapPaymentDest: destArray,
180180
}, nil
181181
}
@@ -222,7 +222,7 @@ func (s *grpcSwapServerClient) GetLoopInQuote(ctx context.Context,
222222
}
223223

224224
func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
225-
swapHash lntypes.Hash, amount btcutil.Amount,
225+
swapHash lntypes.Hash, amount btcutil.Amount, expiry int32,
226226
receiverKey [33]byte, swapPublicationDeadline time.Time) (
227227
*newLoopOutResponse, error) {
228228

@@ -235,6 +235,7 @@ func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
235235
ReceiverKey: receiverKey[:],
236236
SwapPublicationDeadline: swapPublicationDeadline.Unix(),
237237
ProtocolVersion: protocolVersion,
238+
Expiry: expiry,
238239
},
239240
)
240241
if err != nil {
@@ -254,7 +255,6 @@ func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
254255
swapInvoice: swapResp.SwapInvoice,
255256
prepayInvoice: swapResp.PrepayInvoice,
256257
senderKey: senderKey,
257-
expiry: swapResp.Expiry,
258258
serverMessage: swapResp.ServerMessage,
259259
}, nil
260260
}
@@ -528,7 +528,6 @@ type newLoopOutResponse struct {
528528
swapInvoice string
529529
prepayInvoice string
530530
senderKey [33]byte
531-
expiry int32
532531
serverMessage string
533532
}
534533

0 commit comments

Comments
 (0)