Skip to content

Commit fcff783

Browse files
authored
Merge pull request #135 from joostjager/loopin-timeout-sig
loopin: fix handling of incorrect amount in external htlc tx
2 parents cecb26c + 9aaef3f commit fcff783

File tree

6 files changed

+75
-9
lines changed

6 files changed

+75
-9
lines changed

client_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ func testSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
265265
// Publish tick.
266266
ctx.expiryChan <- testTime
267267

268+
// Expect a signing request.
269+
<-ctx.Lnd.SignOutputRawChannel
270+
268271
if !preimageRevealed {
269272
ctx.assertStatus(loopdb.StatePreimageRevealed)
270273
ctx.assertStorePreimageReveal()

loopin.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
419419
// the swap invoice is either settled or canceled. If the htlc times out, the
420420
// timeout tx will be published.
421421
func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
422-
htlc *wire.OutPoint, htlcValue btcutil.Amount) error {
422+
htlcOutpoint *wire.OutPoint, htlcValue btcutil.Amount) error {
423423

424424
// Register the htlc spend notification.
425425
rpcCtx, cancel := context.WithCancel(ctx)
@@ -445,7 +445,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
445445
// checkTimeout publishes the timeout tx if the contract has expired.
446446
checkTimeout := func() error {
447447
if s.height >= s.LoopInContract.CltvExpiry {
448-
return s.publishTimeoutTx(ctx, htlc)
448+
return s.publishTimeoutTx(ctx, htlcOutpoint, htlcValue)
449449
}
450450

451451
return nil
@@ -572,7 +572,7 @@ func (s *loopInSwap) processHtlcSpend(ctx context.Context,
572572
// publishTimeoutTx publishes a timeout tx after the on-chain htlc has expired.
573573
// The swap failed and we are reclaiming our funds.
574574
func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
575-
htlc *wire.OutPoint) error {
575+
htlcOutpoint *wire.OutPoint, htlcValue btcutil.Amount) error {
576576

577577
if s.timeoutAddr == nil {
578578
var err error
@@ -596,8 +596,8 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
596596
}
597597

598598
timeoutTx, err := s.sweeper.CreateSweepTx(
599-
ctx, s.height, s.htlc, *htlc, s.SenderKey, witnessFunc,
600-
s.LoopInContract.AmountRequested, fee, s.timeoutAddr,
599+
ctx, s.height, s.htlc, *htlcOutpoint, s.SenderKey, witnessFunc,
600+
htlcValue, fee, s.timeoutAddr,
601601
)
602602
if err != nil {
603603
return err

loopin_test.go

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,25 @@ func TestLoopInSuccess(t *testing.T) {
113113
}
114114
}
115115

116-
// TestLoopInTimeout tests the scenario where the server doesn't sweep the htlc
116+
// TestLoopInTimeout tests scenarios where the server doesn't sweep the htlc
117117
// and the client is forced to reclaim the funds using the timeout tx.
118118
func TestLoopInTimeout(t *testing.T) {
119+
testAmt := int64(testLoopInRequest.Amount)
120+
t.Run("internal htlc", func(t *testing.T) {
121+
testLoopInTimeout(t, 0)
122+
})
123+
t.Run("external htlc", func(t *testing.T) {
124+
testLoopInTimeout(t, testAmt)
125+
})
126+
t.Run("external amount too high", func(t *testing.T) {
127+
testLoopInTimeout(t, testAmt+1)
128+
})
129+
t.Run("external amount too low", func(t *testing.T) {
130+
testLoopInTimeout(t, testAmt-1)
131+
})
132+
}
133+
134+
func testLoopInTimeout(t *testing.T, externalValue int64) {
119135
defer test.Guard(t)()
120136

121137
ctx := newLoopInTestContext(t)
@@ -128,9 +144,14 @@ func TestLoopInTimeout(t *testing.T) {
128144
server: ctx.server,
129145
}
130146

147+
req := testLoopInRequest
148+
if externalValue != 0 {
149+
req.ExternalHtlc = true
150+
}
151+
131152
swap, err := newLoopInSwap(
132153
context.Background(), cfg,
133-
height, &testLoopInRequest,
154+
height, &req,
134155
)
135156
if err != nil {
136157
t.Fatal(err)
@@ -152,8 +173,21 @@ func TestLoopInTimeout(t *testing.T) {
152173
ctx.assertState(loopdb.StateHtlcPublished)
153174
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
154175

155-
// Expect htlc to be published.
156-
htlcTx := <-ctx.lnd.SendOutputsChannel
176+
var htlcTx wire.MsgTx
177+
if externalValue == 0 {
178+
// Expect htlc to be published.
179+
htlcTx = <-ctx.lnd.SendOutputsChannel
180+
} else {
181+
// Create an external htlc publish tx.
182+
htlcTx = wire.MsgTx{
183+
TxOut: []*wire.TxOut{
184+
{
185+
PkScript: swap.htlc.PkScript,
186+
Value: externalValue,
187+
},
188+
},
189+
}
190+
}
157191

158192
// Expect register for htlc conf.
159193
<-ctx.lnd.RegisterConfChannel
@@ -175,6 +209,13 @@ func TestLoopInTimeout(t *testing.T) {
175209
// Let htlc expire.
176210
ctx.blockEpochChan <- swap.LoopInContract.CltvExpiry
177211

212+
// Expect a signing request for the htlc tx output value.
213+
signReq := <-ctx.lnd.SignOutputRawChannel
214+
if signReq.SignDescriptors[0].Output.Value != htlcTx.TxOut[0].Value {
215+
216+
t.Fatal("invalid signing amount")
217+
}
218+
178219
// Expect timeout tx to be published.
179220
timeoutTx := <-ctx.lnd.TxPublishChannel
180221

loopout_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ func TestCustomSweepConfTarget(t *testing.T) {
192192

193193
expiryChan <- time.Now()
194194

195+
// Expect a signing request for the HTLC success transaction.
196+
<-ctx.Lnd.SignOutputRawChannel
197+
195198
cfg.store.(*storeMock).assertLoopOutState(loopdb.StatePreimageRevealed)
196199
status := <-statusChan
197200
if status.State != loopdb.StatePreimageRevealed {
@@ -247,6 +250,9 @@ func TestCustomSweepConfTarget(t *testing.T) {
247250
blockEpochChan <- int32(defaultConfTargetHeight)
248251
expiryChan <- time.Now()
249252

253+
// Expect another signing request.
254+
<-ctx.Lnd.SignOutputRawChannel
255+
250256
// We should expect to see another sweep using the higher fee since the
251257
// spend hasn't been confirmed yet.
252258
sweepTx := assertSweepTx(DefaultSweepConfTarget)

test/lnd_services_mock.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/btcsuite/btcd/wire"
1010
"github.com/lightninglabs/loop/lndclient"
1111
"github.com/lightningnetwork/lnd/chainntnfs"
12+
"github.com/lightningnetwork/lnd/input"
1213
"github.com/lightningnetwork/lnd/lntypes"
1314
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
1415
"github.com/lightningnetwork/lnd/zpay32"
@@ -57,6 +58,8 @@ func NewMockLnd() *LndMockServices {
5758
RouterSendPaymentChannel: make(chan RouterPaymentChannelMessage),
5859
TrackPaymentChannel: make(chan TrackPaymentMessage),
5960

61+
SignOutputRawChannel: make(chan SignOutputRawRequest),
62+
6063
FailInvoiceChannel: make(chan lntypes.Hash, 2),
6164
epochChannel: make(chan int32),
6265
Height: testStartingHeight,
@@ -109,6 +112,12 @@ type SingleInvoiceSubscription struct {
109112
Err chan error
110113
}
111114

115+
// SignOutputRawRequest contains input data for a tx signing request.
116+
type SignOutputRawRequest struct {
117+
Tx *wire.MsgTx
118+
SignDescriptors []*input.SignDescriptor
119+
}
120+
112121
// LndMockServices provides a full set of mocked lnd services.
113122
type LndMockServices struct {
114123
lndclient.LndServices
@@ -130,6 +139,8 @@ type LndMockServices struct {
130139
RouterSendPaymentChannel chan RouterPaymentChannelMessage
131140
TrackPaymentChannel chan TrackPaymentMessage
132141

142+
SignOutputRawChannel chan SignOutputRawRequest
143+
133144
Height int32
134145
NodePubkey string
135146
Signature []byte

test/signer_mock.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ type mockSigner struct {
1616
func (s *mockSigner) SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
1717
signDescriptors []*input.SignDescriptor) ([][]byte, error) {
1818

19+
s.lnd.SignOutputRawChannel <- SignOutputRawRequest{
20+
Tx: tx,
21+
SignDescriptors: signDescriptors,
22+
}
23+
1924
rawSigs := [][]byte{{1, 2, 3}}
2025

2126
return rawSigs, nil

0 commit comments

Comments
 (0)