@@ -10,7 +10,9 @@ import (
1010
1111 "github.com/btcsuite/btcd/btcutil"
1212 "github.com/lightningnetwork/lnd/input"
13+ "github.com/lightningnetwork/lnd/lncfg"
1314 "github.com/lightningnetwork/lnd/lnrpc"
15+ "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
1416 "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
1517 "github.com/lightningnetwork/lnd/lntest"
1618 "github.com/lightningnetwork/lnd/lntest/node"
@@ -20,9 +22,318 @@ import (
2022 "github.com/stretchr/testify/require"
2123)
2224
23- // testSendDirectPayment creates a topology Alice->Bob and then tests that Alice
24- // can send a direct payment to Bob. This test modifies the fee estimator to
25- // return floor fee rate(1 sat/vb).
25+ // testPaymentSucceededHTLCRemoteSwept checks that when an outgoing HTLC is
26+ // timed out and is swept by the remote via the direct preimage spend path, the
27+ // payment will be marked as succeeded. This test creates a topology from Alice
28+ // -> Bob, and let Alice send payments to Bob. Bob then goes offline, such that
29+ // Alice's outgoing HTLC will time out. Once the force close transaction is
30+ // broadcast by Alice, she then goes offline and Bob comes back online to take
31+ // her outgoing HTLC. And Alice should mark this payment as succeeded after she
32+ // comes back online again.
33+ func testPaymentSucceededHTLCRemoteSwept (ht * lntest.HarnessTest ) {
34+ // Set the feerate to be 10 sat/vb.
35+ ht .SetFeeEstimate (2500 )
36+
37+ // Open a channel with 100k satoshis between Alice and Bob with Alice
38+ // being the sole funder of the channel.
39+ chanAmt := btcutil .Amount (100_000 )
40+ openChannelParams := lntest.OpenChannelParams {
41+ Amt : chanAmt ,
42+ }
43+
44+ // Create a two hop network: Alice -> Bob.
45+ chanPoints , nodes := createSimpleNetwork (ht , nil , 2 , openChannelParams )
46+ chanPoint := chanPoints [0 ]
47+ alice , bob := nodes [0 ], nodes [1 ]
48+
49+ // We now create two payments, one above dust and the other below dust,
50+ // and we should see different behavior in terms of when the payment
51+ // will be marked as failed due to the HTLC timeout.
52+ //
53+ // First, create random preimages.
54+ preimage := ht .RandomPreimage ()
55+ dustPreimage := ht .RandomPreimage ()
56+
57+ // Get the preimage hashes.
58+ payHash := preimage .Hash ()
59+ dustPayHash := dustPreimage .Hash ()
60+
61+ // Create an hold invoice for Bob which expects a payment of 10k
62+ // satoshis from Alice.
63+ const paymentAmt = 10_000
64+ req := & invoicesrpc.AddHoldInvoiceRequest {
65+ Value : paymentAmt ,
66+ Hash : payHash [:],
67+ // Use a small CLTV value so we can mine fewer blocks.
68+ CltvExpiry : finalCltvDelta ,
69+ }
70+ invoice := bob .RPC .AddHoldInvoice (req )
71+
72+ // Create another hold invoice for Bob which expects a payment of 1k
73+ // satoshis from Alice.
74+ const dustAmt = 1000
75+ req = & invoicesrpc.AddHoldInvoiceRequest {
76+ Value : dustAmt ,
77+ Hash : dustPayHash [:],
78+ // Use a small CLTV value so we can mine fewer blocks.
79+ CltvExpiry : finalCltvDelta ,
80+ }
81+ dustInvoice := bob .RPC .AddHoldInvoice (req )
82+
83+ // Alice now sends both payments to Bob.
84+ payReq := & routerrpc.SendPaymentRequest {
85+ PaymentRequest : invoice .PaymentRequest ,
86+ TimeoutSeconds : 3600 ,
87+ }
88+ dustPayReq := & routerrpc.SendPaymentRequest {
89+ PaymentRequest : dustInvoice .PaymentRequest ,
90+ TimeoutSeconds : 3600 ,
91+ }
92+
93+ // We expect the payment to stay in-flight from both streams.
94+ ht .SendPaymentAssertInflight (alice , payReq )
95+ ht .SendPaymentAssertInflight (alice , dustPayReq )
96+
97+ // We also check the payments are marked as IN_FLIGHT in Alice's
98+ // database.
99+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_IN_FLIGHT )
100+ ht .AssertPaymentStatus (alice , dustPreimage , lnrpc .Payment_IN_FLIGHT )
101+
102+ // Bob should have two incoming HTLC.
103+ ht .AssertIncomingHTLCActive (bob , chanPoint , payHash [:])
104+ ht .AssertIncomingHTLCActive (bob , chanPoint , dustPayHash [:])
105+
106+ // Alice should have two outgoing HTLCs.
107+ ht .AssertOutgoingHTLCActive (alice , chanPoint , payHash [:])
108+ ht .AssertOutgoingHTLCActive (alice , chanPoint , dustPayHash [:])
109+
110+ // Let Bob go offline.
111+ restartBob := ht .SuspendNode (bob )
112+
113+ // Alice force closes the channel, which puts her commitment tx into
114+ // the mempool.
115+ ht .CloseChannelAssertPending (alice , chanPoint , true )
116+
117+ // We now let Alice go offline to avoid her sweeping her outgoing htlc.
118+ restartAlice := ht .SuspendNode (alice )
119+
120+ // Mine one block to confirm Alice's force closing tx.
121+ ht .MineBlocksAndAssertNumTxes (1 , 1 )
122+
123+ // Restart Bob to settle the invoice and sweep the htlc output.
124+ require .NoError (ht , restartBob ())
125+
126+ // Bob now settles the invoices, since his link with Alice is broken,
127+ // Alice won't know the preimages.
128+ bob .RPC .SettleInvoice (preimage [:])
129+ bob .RPC .SettleInvoice (dustPreimage [:])
130+
131+ // Once Bob comes back up, he should find the force closing transaction
132+ // from Alice and try to sweep the non-dust outgoing htlc via the
133+ // direct preimage spend.
134+ ht .AssertNumPendingSweeps (bob , 1 )
135+
136+ // Mine a block to trigger the sweep.
137+ //
138+ // TODO(yy): remove it once `blockbeat` is implemented.
139+ ht .MineEmptyBlocks (1 )
140+
141+ // Mine Bob's sweeping tx.
142+ ht .MineBlocksAndAssertNumTxes (1 , 1 )
143+
144+ // Let Alice come back up. Since the channel is now closed, we expect
145+ // different behaviors based on whether the HTLC is a dust.
146+ // - For dust payment, it should be failed now as the HTLC won't go
147+ // onchain.
148+ // - For non-dust payment, it should be marked as succeeded since her
149+ // outgoing htlc is swept by Bob.
150+ require .NoError (ht , restartAlice ())
151+
152+ // Since Alice is restarted, we need to track the payments again.
153+ payStream := alice .RPC .TrackPaymentV2 (payHash [:])
154+ dustPayStream := alice .RPC .TrackPaymentV2 (dustPayHash [:])
155+
156+ // Check that the dust payment is failed in both the stream and DB.
157+ ht .AssertPaymentStatus (alice , dustPreimage , lnrpc .Payment_FAILED )
158+ ht .AssertPaymentStatusFromStream (dustPayStream , lnrpc .Payment_FAILED )
159+
160+ // We expect the non-dust payment to marked as succeeded in Alice's
161+ // database and also from her stream.
162+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_SUCCEEDED )
163+ ht .AssertPaymentStatusFromStream (payStream , lnrpc .Payment_SUCCEEDED )
164+ }
165+
166+ // testPaymentFailedHTLCLocalSwept checks that when an outgoing HTLC is timed
167+ // out and claimed onchain via the timeout path, the payment will be marked as
168+ // failed. This test creates a topology from Alice -> Bob, and let Alice send
169+ // payments to Bob. Bob then goes offline, such that Alice's outgoing HTLC will
170+ // time out. Alice will also be restarted to make sure resumed payments are
171+ // also marked as failed.
172+ func testPaymentFailedHTLCLocalSwept (ht * lntest.HarnessTest ) {
173+ success := ht .Run ("fail payment" , func (t * testing.T ) {
174+ st := ht .Subtest (t )
175+ runTestPaymentHTLCTimeout (st , false )
176+ })
177+ if ! success {
178+ return
179+ }
180+
181+ ht .Run ("fail resumed payment" , func (t * testing.T ) {
182+ st := ht .Subtest (t )
183+ runTestPaymentHTLCTimeout (st , true )
184+ })
185+ }
186+
187+ // runTestPaymentHTLCTimeout is the helper function that actually runs the
188+ // testPaymentFailedHTLCLocalSwept.
189+ func runTestPaymentHTLCTimeout (ht * lntest.HarnessTest , restartAlice bool ) {
190+ // Set the feerate to be 10 sat/vb.
191+ ht .SetFeeEstimate (2500 )
192+
193+ // Open a channel with 100k satoshis between Alice and Bob with Alice
194+ // being the sole funder of the channel.
195+ chanAmt := btcutil .Amount (100_000 )
196+ openChannelParams := lntest.OpenChannelParams {
197+ Amt : chanAmt ,
198+ }
199+
200+ // Create a two hop network: Alice -> Bob.
201+ chanPoints , nodes := createSimpleNetwork (ht , nil , 2 , openChannelParams )
202+ chanPoint := chanPoints [0 ]
203+ alice , bob := nodes [0 ], nodes [1 ]
204+
205+ // We now create two payments, one above dust and the other below dust,
206+ // and we should see different behavior in terms of when the payment
207+ // will be marked as failed due to the HTLC timeout.
208+ //
209+ // First, create random preimages.
210+ preimage := ht .RandomPreimage ()
211+ dustPreimage := ht .RandomPreimage ()
212+
213+ // Get the preimage hashes.
214+ payHash := preimage .Hash ()
215+ dustPayHash := dustPreimage .Hash ()
216+
217+ // Create an hold invoice for Bob which expects a payment of 10k
218+ // satoshis from Alice.
219+ const paymentAmt = 20_000
220+ req := & invoicesrpc.AddHoldInvoiceRequest {
221+ Value : paymentAmt ,
222+ Hash : payHash [:],
223+ // Use a small CLTV value so we can mine fewer blocks.
224+ CltvExpiry : finalCltvDelta ,
225+ }
226+ invoice := bob .RPC .AddHoldInvoice (req )
227+
228+ // Create another hold invoice for Bob which expects a payment of 1k
229+ // satoshis from Alice.
230+ const dustAmt = 1000
231+ req = & invoicesrpc.AddHoldInvoiceRequest {
232+ Value : dustAmt ,
233+ Hash : dustPayHash [:],
234+ // Use a small CLTV value so we can mine fewer blocks.
235+ CltvExpiry : finalCltvDelta ,
236+ }
237+ dustInvoice := bob .RPC .AddHoldInvoice (req )
238+
239+ // Alice now sends both the payments to Bob.
240+ payReq := & routerrpc.SendPaymentRequest {
241+ PaymentRequest : invoice .PaymentRequest ,
242+ TimeoutSeconds : 3600 ,
243+ }
244+ dustPayReq := & routerrpc.SendPaymentRequest {
245+ PaymentRequest : dustInvoice .PaymentRequest ,
246+ TimeoutSeconds : 3600 ,
247+ }
248+
249+ // We expect the payment to stay in-flight from both streams.
250+ ht .SendPaymentAssertInflight (alice , payReq )
251+ ht .SendPaymentAssertInflight (alice , dustPayReq )
252+
253+ // We also check the payments are marked as IN_FLIGHT in Alice's
254+ // database.
255+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_IN_FLIGHT )
256+ ht .AssertPaymentStatus (alice , dustPreimage , lnrpc .Payment_IN_FLIGHT )
257+
258+ // Bob should have two incoming HTLC.
259+ ht .AssertIncomingHTLCActive (bob , chanPoint , payHash [:])
260+ ht .AssertIncomingHTLCActive (bob , chanPoint , dustPayHash [:])
261+
262+ // Alice should have two outgoing HTLCs.
263+ ht .AssertOutgoingHTLCActive (alice , chanPoint , payHash [:])
264+ ht .AssertOutgoingHTLCActive (alice , chanPoint , dustPayHash [:])
265+
266+ // Let Bob go offline.
267+ ht .Shutdown (bob )
268+
269+ // We'll now mine enough blocks to trigger Alice to broadcast her
270+ // commitment transaction due to the fact that the HTLC is about to
271+ // timeout. With the default outgoing broadcast delta of zero, this
272+ // will be the same height as the htlc expiry height.
273+ numBlocks := padCLTV (
274+ uint32 (req .CltvExpiry - lncfg .DefaultOutgoingBroadcastDelta ),
275+ )
276+ ht .MineBlocks (int (numBlocks ))
277+
278+ // Restart Alice if requested.
279+ if restartAlice {
280+ // Restart Alice to test the resumed payment is canceled.
281+ ht .RestartNode (alice )
282+ }
283+
284+ // We now subscribe to the payment status.
285+ payStream := alice .RPC .TrackPaymentV2 (payHash [:])
286+ dustPayStream := alice .RPC .TrackPaymentV2 (dustPayHash [:])
287+
288+ // Mine a block to confirm Alice's closing transaction.
289+ ht .MineBlocksAndAssertNumTxes (1 , 1 )
290+
291+ // Now the channel is closed, we expect different behaviors based on
292+ // whether the HTLC is a dust. For dust payment, it should be failed
293+ // now as the HTLC won't go onchain. For non-dust payment, it should
294+ // still be inflight. It won't be marked as failed unless the outgoing
295+ // HTLC is resolved onchain.
296+ //
297+ // NOTE: it's possible for Bob to race against Alice using the
298+ // preimage path. If Bob successfully claims the HTLC, Alice should
299+ // mark the non-dust payment as succeeded.
300+ //
301+ // Check that the dust payment is failed in both the stream and DB.
302+ ht .AssertPaymentStatus (alice , dustPreimage , lnrpc .Payment_FAILED )
303+ ht .AssertPaymentStatusFromStream (dustPayStream , lnrpc .Payment_FAILED )
304+
305+ // Check that the non-dust payment is still in-flight.
306+ //
307+ // NOTE: we don't check the payment status from the stream here as
308+ // there's no new status being sent.
309+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_IN_FLIGHT )
310+
311+ // We now have two possible cases for the non-dust payment:
312+ // - Bob stays offline, and Alice will sweep her outgoing HTLC, which
313+ // makes the payment failed.
314+ // - Bob comes back online, and claims the HTLC on Alice's commitment
315+ // via direct preimage spend, hence racing against Alice onchain. If
316+ // he succeeds, Alice should mark the payment as succeeded.
317+ //
318+ // TODO(yy): test the second case once we have the RPC to clean
319+ // mempool.
320+
321+ // Since Alice's force close transaction has been confirmed, she should
322+ // sweep her outgoing HTLC in next block.
323+ ht .MineBlocksAndAssertNumTxes (1 , 1 )
324+
325+ // Cleanup the channel.
326+ ht .CleanupForceClose (alice )
327+
328+ // We expect the non-dust payment to marked as failed in Alice's
329+ // database and also from her stream.
330+ ht .AssertPaymentStatus (alice , preimage , lnrpc .Payment_FAILED )
331+ ht .AssertPaymentStatusFromStream (payStream , lnrpc .Payment_FAILED )
332+ }
333+
334+ // testSendDirectPayment creates a topology Alice->Bob and then tests that
335+ // Alice can send a direct payment to Bob. This test modifies the fee estimator
336+ // to return floor fee rate(1 sat/vb).
26337func testSendDirectPayment (ht * lntest.HarnessTest ) {
27338 // Grab Alice and Bob's nodes for convenience.
28339 alice , bob := ht .Alice , ht .Bob
0 commit comments