Skip to content

Commit 1877b7f

Browse files
committed
multi: store loop out htlc confirmations on disk
To allow users to specify differing confirmation targets, we store the swap conf target per-swap. This makes us restart safe, so we do not forget confirmation values for swaps that are in flight when we restart.
1 parent e15549e commit 1877b7f

File tree

7 files changed

+70
-12
lines changed

7 files changed

+70
-12
lines changed

client_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ var (
3636

3737
swapInvoiceDesc = "swap"
3838
prepayInvoiceDesc = "prepay"
39+
40+
defaultConfirmations = int32(loopdb.DefaultLoopOutHtlcConfirmations)
3941
)
4042

4143
// TestSuccess tests the loop out happy flow.
@@ -57,7 +59,7 @@ func TestSuccess(t *testing.T) {
5759
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
5860

5961
// Expect client to register for conf.
60-
confIntent := ctx.AssertRegisterConf(false)
62+
confIntent := ctx.AssertRegisterConf(false, defaultConfirmations)
6163

6264
testSuccess(ctx, testRequest.Amount, info.SwapHash,
6365
signalPrepaymentResult, signalSwapPaymentResult, false,
@@ -83,7 +85,7 @@ func TestFailOffchain(t *testing.T) {
8385
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
8486
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
8587

86-
ctx.AssertRegisterConf(false)
88+
ctx.AssertRegisterConf(false, defaultConfirmations)
8789

8890
signalSwapPaymentResult(
8991
errors.New(lndclient.PaymentResultUnknownPaymentHash),
@@ -196,6 +198,7 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
196198
DestAddr: dest,
197199
SwapInvoice: swapPayReq,
198200
SweepConfTarget: 2,
201+
HtlcConfirmations: loopdb.DefaultLoopOutHtlcConfirmations,
199202
MaxSwapRoutingFee: 70000,
200203
PrepayInvoice: prePayReq,
201204
SwapContract: loopdb.SwapContract{
@@ -232,7 +235,9 @@ func testResume(t *testing.T, expired, preimageRevealed, expectSuccess bool) {
232235
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
233236

234237
// Expect client to register for conf.
235-
confIntent := ctx.AssertRegisterConf(preimageRevealed)
238+
confIntent := ctx.AssertRegisterConf(
239+
preimageRevealed, defaultConfirmations,
240+
)
236241

237242
signalSwapPaymentResult(nil)
238243
signalPrepaymentResult(nil)

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: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
147147
DestAddr: request.DestAddr,
148148
MaxSwapRoutingFee: request.MaxSwapRoutingFee,
149149
SweepConfTarget: request.SweepConfTarget,
150+
HtlcConfirmations: loopdb.DefaultLoopOutHtlcConfirmations,
150151
PrepayInvoice: swapResp.prepayInvoice,
151152
MaxPrepayRoutingFee: request.MaxPrepayRoutingFee,
152153
SwapPublicationDeadline: request.SwapPublicationDeadline,
@@ -606,8 +607,8 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
606607
// Wait for confirmation of the on-chain htlc by watching for a tx
607608
// producing the swap script output.
608609
s.log.Infof(
609-
"Register conf ntfn for swap script on chain (hh=%v)",
610-
s.InitiationHeight,
610+
"Register %v conf ntfn for swap script on chain (hh=%v)",
611+
s.HtlcConfirmations, s.InitiationHeight,
611612
)
612613

613614
// If we've revealed the preimage in a previous run, we expect to have
@@ -624,8 +625,8 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
624625
defer cancel()
625626
htlcConfChan, htlcErrChan, err :=
626627
s.lnd.ChainNotifier.RegisterConfirmationsNtfn(
627-
ctx, s.htlcTxHash, s.htlc.PkScript, 1,
628-
s.InitiationHeight,
628+
ctx, s.htlcTxHash, s.htlc.PkScript,
629+
int32(s.HtlcConfirmations), s.InitiationHeight,
629630
)
630631
if err != nil {
631632
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

test/context.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/lightningnetwork/lnd/lnrpc"
1313
"github.com/lightningnetwork/lnd/lntypes"
1414
"github.com/lightningnetwork/lnd/zpay32"
15+
"github.com/stretchr/testify/require"
1516
)
1617

1718
// Context contains shared test context functions.
@@ -113,7 +114,7 @@ func (ctx *Context) AssertTrackPayment() TrackPaymentMessage {
113114
}
114115

115116
// AssertRegisterConf asserts that a register for conf has been received.
116-
func (ctx *Context) AssertRegisterConf(expectTxHash bool) *ConfRegistration {
117+
func (ctx *Context) AssertRegisterConf(expectTxHash bool, confs int32) *ConfRegistration {
117118
ctx.T.Helper()
118119

119120
// Expect client to register for conf
@@ -127,6 +128,11 @@ func (ctx *Context) AssertRegisterConf(expectTxHash bool) *ConfRegistration {
127128
case !expectTxHash && confIntent.TxID != nil:
128129
ctx.T.Fatalf("expected script only registration")
129130
}
131+
132+
// Require that we registered for the number of confirmations
133+
// the test expects.
134+
require.Equal(ctx.T, confs, confIntent.NumConfs)
135+
130136
case <-time.After(Timeout):
131137
ctx.T.Fatalf("htlc confirmed not subscribed to")
132138
}

0 commit comments

Comments
 (0)