Skip to content

Commit a240c69

Browse files
authored
Merge pull request #258 from carlaKC/65-confirmationparam
loopout: allow per-swap confirmation targets for server HTLC
2 parents e15549e + 13449fb commit a240c69

13 files changed

+253
-133
lines changed

client_test.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var (
2727
Amount: btcutil.Amount(50000),
2828
DestAddr: testAddr,
2929
MaxMinerFee: 50000,
30+
HtlcConfirmations: defaultConfirmations,
3031
SweepConfTarget: 2,
3132
MaxSwapFee: 1050,
3233
MaxPrepayAmount: 100,
@@ -36,16 +37,22 @@ var (
3637

3738
swapInvoiceDesc = "swap"
3839
prepayInvoiceDesc = "prepay"
40+
41+
defaultConfirmations = int32(loopdb.DefaultLoopOutHtlcConfirmations)
3942
)
4043

41-
// TestSuccess tests the loop out happy flow.
44+
// TestSuccess tests the loop out happy flow, using a custom htlc confirmation
45+
// target.
4246
func TestSuccess(t *testing.T) {
4347
defer test.Guard(t)()
4448

4549
ctx := createClientTestContext(t, nil)
4650

51+
req := *testRequest
52+
req.HtlcConfirmations = 2
53+
4754
// Initiate loop out.
48-
info, err := ctx.swapClient.LoopOut(context.Background(), testRequest)
55+
info, err := ctx.swapClient.LoopOut(context.Background(), &req)
4956
if err != nil {
5057
t.Fatal(err)
5158
}
@@ -57,7 +64,7 @@ func TestSuccess(t *testing.T) {
5764
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
5865

5966
// Expect client to register for conf.
60-
confIntent := ctx.AssertRegisterConf(false)
67+
confIntent := ctx.AssertRegisterConf(false, req.HtlcConfirmations)
6168

6269
testSuccess(ctx, testRequest.Amount, info.SwapHash,
6370
signalPrepaymentResult, signalSwapPaymentResult, false,
@@ -83,7 +90,7 @@ func TestFailOffchain(t *testing.T) {
8390
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
8491
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
8592

86-
ctx.AssertRegisterConf(false)
93+
ctx.AssertRegisterConf(false, defaultConfirmations)
8794

8895
signalSwapPaymentResult(
8996
errors.New(lndclient.PaymentResultUnknownPaymentHash),
@@ -141,18 +148,25 @@ func TestFailWrongAmount(t *testing.T) {
141148
func TestResume(t *testing.T) {
142149
defer test.Guard(t)()
143150

151+
defaultConfs := loopdb.DefaultLoopOutHtlcConfirmations
152+
144153
t.Run("not expired", func(t *testing.T) {
145-
testResume(t, false, false, true)
154+
testResume(t, defaultConfs, false, false, true)
155+
})
156+
t.Run("not expired, custom confirmations", func(t *testing.T) {
157+
testResume(t, 3, false, false, true)
146158
})
147159
t.Run("expired not revealed", func(t *testing.T) {
148-
testResume(t, true, false, false)
160+
testResume(t, defaultConfs, true, false, false)
149161
})
150162
t.Run("expired revealed", func(t *testing.T) {
151-
testResume(t, true, true, true)
163+
testResume(t, defaultConfs, true, true, true)
152164
})
153165
}
154166

155-
func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
167+
func testResume(t *testing.T, confs uint32, expired, preimageRevealed,
168+
expectSuccess bool) {
169+
156170
defer test.Guard(t)()
157171

158172
preimage := testPreimage
@@ -191,11 +205,13 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
191205
update.HtlcTxHash = &chainhash.Hash{1, 2, 6}
192206
}
193207

208+
// Create a pending swap with our custom number of confirmations.
194209
pendingSwap := &loopdb.LoopOut{
195210
Contract: &loopdb.LoopOutContract{
196211
DestAddr: dest,
197212
SwapInvoice: swapPayReq,
198213
SweepConfTarget: 2,
214+
HtlcConfirmations: confs,
199215
MaxSwapRoutingFee: 70000,
200216
PrepayInvoice: prePayReq,
201217
SwapContract: loopdb.SwapContract{
@@ -231,8 +247,8 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
231247
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
232248
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
233249

234-
// Expect client to register for conf.
235-
confIntent := ctx.AssertRegisterConf(preimageRevealed)
250+
// Expect client to register for our expected number of confirmations.
251+
confIntent := ctx.AssertRegisterConf(preimageRevealed, int32(confs))
236252

237253
signalSwapPaymentResult(nil)
238254
signalPrepaymentResult(nil)

cmd/loop/loopout.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/btcsuite/btcutil"
1111
"github.com/lightninglabs/loop"
1212
"github.com/lightninglabs/loop/labels"
13+
"github.com/lightninglabs/loop/loopdb"
1314
"github.com/lightninglabs/loop/looprpc"
1415
"github.com/urfave/cli"
1516
)
@@ -42,6 +43,13 @@ var loopOutCommand = cli.Command{
4243
Name: "amt",
4344
Usage: "the amount in satoshis to loop out",
4445
},
46+
cli.Uint64Flag{
47+
Name: "htlc_confs",
48+
Usage: "the number of of confirmations, in blocks " +
49+
"that we require for the htlc extended by " +
50+
"the server before we reveal the preimage.",
51+
Value: uint64(loopdb.DefaultLoopOutHtlcConfirmations),
52+
},
4553
cli.Uint64Flag{
4654
Name: "conf_target",
4755
Usage: "the number of blocks from the swap " +
@@ -135,6 +143,11 @@ func loopOut(ctx *cli.Context) error {
135143
}
136144

137145
sweepConfTarget := int32(ctx.Uint64("conf_target"))
146+
htlcConfs := int32(ctx.Uint64("htlc_confs"))
147+
if htlcConfs == 0 {
148+
return fmt.Errorf("at least 1 confirmation required for htlcs")
149+
}
150+
138151
quoteReq := &looprpc.QuoteRequest{
139152
Amt: int64(amt),
140153
ConfTarget: sweepConfTarget,
@@ -179,6 +192,7 @@ func loopOut(ctx *cli.Context) error {
179192
MaxSwapRoutingFee: int64(limits.maxSwapRoutingFee),
180193
OutgoingChanSet: outgoingChanSet,
181194
SweepConfTarget: sweepConfTarget,
195+
HtlcConfirmations: htlcConfs,
182196
SwapPublicationDeadline: uint64(swapDeadline.Unix()),
183197
Label: label,
184198
})

interface.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ type OutRequest struct {
6464
// client sweep tx.
6565
SweepConfTarget int32
6666

67+
// HtlcConfirmations specifies the number of confirmations we require
68+
// for on chain loop out htlcs.
69+
HtlcConfirmations int32
70+
6771
// OutgoingChanSet optionally specifies the short channel ids of the
6872
// channels that may be used to loop out.
6973
OutgoingChanSet loopdb.ChannelSet

loopd/swapclient_server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
8686
MaxSwapRoutingFee: btcutil.Amount(in.MaxSwapRoutingFee),
8787
MaxSwapFee: btcutil.Amount(in.MaxSwapFee),
8888
SweepConfTarget: sweepConfTarget,
89+
HtlcConfirmations: in.HtlcConfirmations,
8990
SwapPublicationDeadline: time.Unix(
9091
int64(in.SwapPublicationDeadline), 0,
9192
),

loopdb/loopout.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ type LoopOutContract struct {
3636
// client sweep tx.
3737
SweepConfTarget int32
3838

39+
// HtlcConfirmations is the number of confirmations we require the on
40+
// chain htlc to have before proceeding with the swap.
41+
HtlcConfirmations uint32
42+
3943
// OutgoingChanSet is the set of short ids of channels that may be used.
4044
// If empty, any channel may be used.
4145
OutgoingChanSet ChannelSet

loopdb/store.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,23 @@ var (
7777
// value: concatenation of uint64 channel ids
7878
outgoingChanSetKey = []byte("outgoing-chan-set")
7979

80+
// confirmationsKey is the key that stores the number of confirmations
81+
// that were requested for a loop out swap.
82+
//
83+
// path: loopOutBucket -> swapBucket[hash] -> confirmationsKey
84+
//
85+
// value: uint32 confirmation value
86+
confirmationsKey = []byte("confirmations")
87+
8088
byteOrder = binary.BigEndian
8189

8290
keyLength = 33
8391
)
8492

93+
// DefaultLoopOutHtlcConfirmations is the default number of confirmations we
94+
// set for a loop out htlc.
95+
const DefaultLoopOutHtlcConfirmations uint32 = 1
96+
8597
// fileExists returns true if the file exists, and false otherwise.
8698
func fileExists(path string) bool {
8799
if _, err := os.Stat(path); err != nil {
@@ -242,6 +254,23 @@ func (s *boltSwapStore) FetchLoopOutSwaps() ([]*LoopOut, error) {
242254
}
243255
}
244256

257+
// Set our default number of confirmations for the swap.
258+
contract.HtlcConfirmations = DefaultLoopOutHtlcConfirmations
259+
260+
// If we have the number of confirmations stored for
261+
// this swap, we overwrite our default with the stored
262+
// value.
263+
confBytes := swapBucket.Get(confirmationsKey)
264+
if confBytes != nil {
265+
r := bytes.NewReader(confBytes)
266+
err := binary.Read(
267+
r, byteOrder, &contract.HtlcConfirmations,
268+
)
269+
if err != nil {
270+
return err
271+
}
272+
}
273+
245274
updates, err := deserializeUpdates(swapBucket)
246275
if err != nil {
247276
return err
@@ -471,6 +500,18 @@ func (s *boltSwapStore) CreateLoopOut(hash lntypes.Hash,
471500
return err
472501
}
473502

503+
// Write our confirmation target under its own key.
504+
var buf bytes.Buffer
505+
err = binary.Write(&buf, byteOrder, swap.HtlcConfirmations)
506+
if err != nil {
507+
return err
508+
}
509+
510+
err = swapBucket.Put(confirmationsKey, buf.Bytes())
511+
if err != nil {
512+
return err
513+
}
514+
474515
// Finally, we'll create an empty updates bucket for this swap
475516
// to track any future updates to the swap itself.
476517
_, err = swapBucket.CreateBucket(updatesBucketKey)

loopdb/store_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ func TestLoopOutStore(t *testing.T) {
7070
SwapInvoice: "swapinvoice",
7171
MaxSwapRoutingFee: 30,
7272
SweepConfTarget: 2,
73+
HtlcConfirmations: 2,
7374
SwapPublicationDeadline: time.Unix(0, initiationTime.UnixNano()),
7475
}
7576

loopout.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
138138
return nil, err
139139
}
140140

141+
// If a htlc confirmation target was not provided, we use the default
142+
// number of confirmations. We overwrite this value rather than failing
143+
// it because the field is a new addition to the rpc, and we don't want
144+
// to break older clients that are not aware of this new field.
145+
confs := uint32(request.HtlcConfirmations)
146+
if confs == 0 {
147+
confs = loopdb.DefaultLoopOutHtlcConfirmations
148+
}
149+
141150
// Instantiate a struct that contains all required data to start the
142151
// swap.
143152
initiationTime := time.Now()
@@ -147,6 +156,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
147156
DestAddr: request.DestAddr,
148157
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
149158
SweepConfTarget: request.SweepConfTarget,
159+
HtlcConfirmations: confs,
150160
PrepayInvoice: swapResp.prepayInvoice,
151161
MaxPrepayRoutingFee: request.MaxPrepayRoutingFee,
152162
SwapPublicationDeadline: request.SwapPublicationDeadline,
@@ -606,8 +616,8 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
606616
// Wait for confirmation of the on-chain htlc by watching for a tx
607617
// producing the swap script output.
608618
s.log.Infof(
609-
"Register conf ntfn for swap script on chain (hh=%v)",
610-
s.InitiationHeight,
619+
"Register %v conf ntfn for swap script on chain (hh=%v)",
620+
s.HtlcConfirmations, s.InitiationHeight,
611621
)
612622

613623
// If we've revealed the preimage in a previous run, we expect to have
@@ -624,8 +634,8 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
624634
defer cancel()
625635
htlcConfChan, htlcErrChan, err :=
626636
s.lnd.ChainNotifier.RegisterConfirmationsNtfn(
627-
ctx, s.htlcTxHash, s.htlc.PkScript, 1,
628-
s.InitiationHeight,
637+
ctx, s.htlcTxHash, s.htlc.PkScript,
638+
int32(s.HtlcConfirmations), s.InitiationHeight,
629639
)
630640
if err != nil {
631641
return nil, err

loopout_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func TestLoopOutPaymentParameters(t *testing.T) {
116116

117117
// Swap is expected to register for confirmation of the htlc. Assert
118118
// this to prevent a blocked channel in the mock.
119-
ctx.AssertRegisterConf(false)
119+
ctx.AssertRegisterConf(false, defaultConfirmations)
120120

121121
// Cancel the swap. There is nothing else we need to assert. The payment
122122
// parameters don't play a role in the remainder of the swap process.
@@ -191,7 +191,7 @@ func TestLateHtlcPublish(t *testing.T) {
191191
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
192192

193193
// Expect client to register for conf
194-
ctx.AssertRegisterConf(false)
194+
ctx.AssertRegisterConf(false, defaultConfirmations)
195195

196196
// // Wait too long before publishing htlc.
197197
blockEpochChan <- int32(swap.CltvExpiry - 10)
@@ -290,7 +290,7 @@ func TestCustomSweepConfTarget(t *testing.T) {
290290
signalPrepaymentResult(nil)
291291

292292
// Notify the confirmation notification for the HTLC.
293-
ctx.AssertRegisterConf(false)
293+
ctx.AssertRegisterConf(false, defaultConfirmations)
294294

295295
blockEpochChan <- ctx.Lnd.Height + 1
296296

@@ -494,7 +494,7 @@ func TestPreimagePush(t *testing.T) {
494494
signalPrepaymentResult(nil)
495495

496496
// Notify the confirmation notification for the HTLC.
497-
ctx.AssertRegisterConf(false)
497+
ctx.AssertRegisterConf(false, defaultConfirmations)
498498

499499
blockEpochChan <- ctx.Lnd.Height + 1
500500

0 commit comments

Comments
 (0)