@@ -2,8 +2,10 @@ package itest
22
33import (
44 "context"
5+ "crypto/rand"
56 "fmt"
67 "os"
8+ "sort"
79 "strings"
810 "testing"
911 "time"
@@ -14,8 +16,10 @@ import (
1416 "github.com/lightninglabs/taproot-assets/rpcutils"
1517 "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
1618 "github.com/lightningnetwork/lnd/lnrpc"
19+ "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
1720 "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
1821 "github.com/lightningnetwork/lnd/lntest"
22+ "github.com/lightningnetwork/lnd/lntypes"
1923 "github.com/stretchr/testify/require"
2024 "google.golang.org/grpc"
2125)
@@ -164,12 +168,296 @@ func runAccountSystemTest(t *harnessTest, node *HarnessNode, hostPort,
164168 ctxa , t , rawConn , charlie , acctBalance ,
165169 )
166170
171+ // Initiate a HODL payment (which remains IN_FLIGHT) and a FAILED
172+ // payment to verify that the AccountPayments RPC correctly retrieves
173+ // and reports payments across all potential lifecycles (SUCCEEDED,
174+ // IN_FLIGHT, FAILED).
175+ testCtx , testCancel := context .WithTimeout (ctxb , defaultTimeout )
176+ defer testCancel ()
177+
178+ routerClient := routerrpc .NewRouterClient (rawConn )
179+
180+ // 1. Initiate HODL payment (IN_FLIGHT)
181+ var preimage lntypes.Preimage
182+ _ , err = rand .Read (preimage [:])
183+ require .NoError (t .t , err )
184+ holdHash := preimage .Hash ()
185+
186+ holdInv , err := charlie .AddHoldInvoice (
187+ testCtx , & invoicesrpc.AddHoldInvoiceRequest {
188+ Hash : holdHash [:],
189+ Value : 2222 ,
190+ },
191+ )
192+ require .NoError (t .t , err )
193+
194+ sendReqHold := & routerrpc.SendPaymentRequest {
195+ PaymentRequest : holdInv .PaymentRequest ,
196+ TimeoutSeconds : 2 ,
197+ FeeLimitMsat : 1000 ,
198+ }
199+ holdStream , err := routerClient .SendPaymentV2 (ctxa , sendReqHold )
200+ require .NoError (t .t , err )
201+
202+ holdPayment , err := getPaymentResult (holdStream , true )
203+ require .NoError (t .t , err )
204+ require .Equal (t .t , lnrpc .Payment_IN_FLIGHT , holdPayment .Status )
205+
206+ // Wait for the hold invoice to be accepted by Charlie.
207+ require .Eventually (t .t , func () bool {
208+ inv , err := charlie .LookupInvoice (
209+ testCtx , & lnrpc.PaymentHash {
210+ RHash : holdHash [:],
211+ },
212+ )
213+
214+ return err == nil &&
215+ inv .State == lnrpc .Invoice_ACCEPTED
216+ }, defaultTimeout , 100 * time .Millisecond )
217+
218+ // 2. Initiate a FAILED payment
219+ var failedHash lntypes.Hash
220+ _ , err = rand .Read (failedHash [:])
221+ require .NoError (t .t , err )
222+
223+ var fakePubKey [33 ]byte
224+ fakePubKey [0 ] = 0x02
225+ sendReqFailed := & routerrpc.SendPaymentRequest {
226+ Dest : fakePubKey [:],
227+ Amt : 1111 ,
228+ PaymentHash : failedHash [:],
229+ TimeoutSeconds : 2 ,
230+ FeeLimitMsat : 1000 ,
231+ }
232+ failedStream , err := routerClient .SendPaymentV2 (ctxa , sendReqFailed )
233+ require .NoError (t .t , err )
234+
235+ failedPayment , err := getPaymentResult (failedStream , false )
236+ require .NoError (t .t , err )
237+ require .Equal (t .t , lnrpc .Payment_FAILED , failedPayment .Status )
238+
239+ // Test AccountPayments RPC with all 3 payments (succeeded,
240+ // in-flight, failed).
241+ paymentsResp , err := acctClient .AccountPayments (
242+ ctxm , & litrpc.AccountPaymentsRequest {
243+ Account : & litrpc.AccountIdentifier {
244+ Identifier : & litrpc.AccountIdentifier_Id {
245+ Id : acctResp .Account .Id ,
246+ },
247+ },
248+ CountTotalPayments : true ,
249+ },
250+ )
251+ require .NoError (t .t , err )
252+ require .Len (t .t , paymentsResp .Payments , 3 )
253+
254+ // Sort the payments by value descending to ensure index-based
255+ // assertions are deterministic regardless of database sorting by hash.
256+ sort .Slice (paymentsResp .Payments , func (i , j int ) bool {
257+ valI := paymentsResp .Payments [i ].ValueSat
258+ valJ := paymentsResp .Payments [j ].ValueSat
259+
260+ return valI > valJ
261+ })
262+
263+ // Check the succeeded payment. This corresponds to the 4444 sat
264+ // payment initiated earlier in testAccountRestrictions using this
265+ // account.
266+ require .Equal (
267+ t .t , uint64 (4444 ),
268+ uint64 (paymentsResp .Payments [0 ].ValueSat ),
269+ )
270+ require .Equal (
271+ t .t , lnrpc .Payment_SUCCEEDED ,
272+ paymentsResp .Payments [0 ].Status ,
273+ )
274+
275+ // Check In-flight payment
276+ require .Equal (
277+ t .t , uint64 (2222 ),
278+ uint64 (paymentsResp .Payments [1 ].ValueSat ),
279+ )
280+ require .Equal (
281+ t .t , lnrpc .Payment_IN_FLIGHT ,
282+ paymentsResp .Payments [1 ].Status ,
283+ )
284+
285+ // Check Failed payment
286+ require .Equal (
287+ t .t , uint64 (1111 ),
288+ uint64 (paymentsResp .Payments [2 ].ValueSat ),
289+ )
290+ require .Equal (
291+ t .t , lnrpc .Payment_FAILED ,
292+ paymentsResp .Payments [2 ].Status ,
293+ )
294+
295+ require .EqualValues (t .t , 3 , paymentsResp .TotalNumPayments )
296+ require .EqualValues (t .t , 0 , paymentsResp .FirstIndexOffset )
297+ require .EqualValues (t .t , 3 , paymentsResp .LastIndexOffset )
298+
299+ // Query by label.
300+ paymentsResp , err = acctClient .AccountPayments (
301+ ctxm , & litrpc.AccountPaymentsRequest {
302+ Account : & litrpc.AccountIdentifier {
303+ Identifier : & litrpc.AccountIdentifier_Label {
304+ Label : acctLabel ,
305+ },
306+ },
307+ },
308+ )
309+ require .NoError (t .t , err )
310+ require .Len (t .t , paymentsResp .Payments , 3 )
311+
312+ // Query with pagination limit.
313+ paymentsResp , err = acctClient .AccountPayments (
314+ ctxm , & litrpc.AccountPaymentsRequest {
315+ Account : & litrpc.AccountIdentifier {
316+ Identifier : & litrpc.AccountIdentifier_Id {
317+ Id : acctResp .Account .Id ,
318+ },
319+ },
320+ MaxPayments : 2 ,
321+ },
322+ )
323+ require .NoError (t .t , err )
324+ require .Len (t .t , paymentsResp .Payments , 2 )
325+ require .EqualValues (t .t , 0 , paymentsResp .FirstIndexOffset )
326+ require .EqualValues (t .t , 2 , paymentsResp .LastIndexOffset )
327+
328+ // Query with pagination offset out of bounds.
329+ paymentsResp , err = acctClient .AccountPayments (
330+ ctxm , & litrpc.AccountPaymentsRequest {
331+ Account : & litrpc.AccountIdentifier {
332+ Identifier : & litrpc.AccountIdentifier_Id {
333+ Id : acctResp .Account .Id ,
334+ },
335+ },
336+ IndexOffset : 3 ,
337+ },
338+ )
339+ require .NoError (t .t , err )
340+ require .Empty (t .t , paymentsResp .Payments )
341+ require .EqualValues (t .t , 0 , paymentsResp .FirstIndexOffset )
342+ require .EqualValues (t .t , 0 , paymentsResp .LastIndexOffset )
343+
344+ // Query with pagination offset inside bounds, where the number of
345+ // payments returned is fewer than MaxPayments.
346+ paymentsResp , err = acctClient .AccountPayments (
347+ ctxm , & litrpc.AccountPaymentsRequest {
348+ Account : & litrpc.AccountIdentifier {
349+ Identifier : & litrpc.AccountIdentifier_Id {
350+ Id : acctResp .Account .Id ,
351+ },
352+ },
353+ IndexOffset : 2 ,
354+ MaxPayments : 2 ,
355+ },
356+ )
357+ require .NoError (t .t , err )
358+ require .Len (t .t , paymentsResp .Payments , 1 )
359+ require .EqualValues (t .t , 2 , paymentsResp .FirstIndexOffset )
360+ require .EqualValues (t .t , 3 , paymentsResp .LastIndexOffset )
361+
362+ // Query with invalid pagination max_payments (exceeding 50).
363+ _ , err = acctClient .AccountPayments (
364+ ctxm , & litrpc.AccountPaymentsRequest {
365+ Account : & litrpc.AccountIdentifier {
366+ Identifier : & litrpc.AccountIdentifier_Id {
367+ Id : acctResp .Account .Id ,
368+ },
369+ },
370+ MaxPayments : 51 ,
371+ },
372+ )
373+ require .Error (t .t , err )
374+ require .Contains (t .t , err .Error (), "max_payments cannot exceed 50" )
375+
376+ // Query with invalid pagination index_offset (exceeding 31-bit int).
377+ _ , err = acctClient .AccountPayments (
378+ ctxm , & litrpc.AccountPaymentsRequest {
379+ Account : & litrpc.AccountIdentifier {
380+ Identifier : & litrpc.AccountIdentifier_Id {
381+ Id : acctResp .Account .Id ,
382+ },
383+ },
384+ IndexOffset : 0x80000000 ,
385+ },
386+ )
387+ require .Error (t .t , err )
388+ require .Contains (t .t , err .Error (), "index_offset out of range" )
389+
167390 // Test the same account restrictions with an LNC session that is bound
168391 // to the account.
169392 testAccountRestrictionsLNC (
170393 ctxm , t , rawConn , newAcctBalance , acctResp .Account .Id ,
171394 )
172395
396+ // Settle the HODL invoice to clean up node state.
397+ _ , err = charlie .SettleInvoice (testCtx , & invoicesrpc.SettleInvoiceMsg {
398+ Preimage : preimage [:],
399+ })
400+ require .NoError (t .t , err )
401+
402+ // Delete a single failed payment from LND to simulate a desync case
403+ // where this payment exists in LiT's account database but is
404+ // deleted/absent in LND.
405+ _ , err = node .LightningClient .DeletePayment (
406+ testCtx , & lnrpc.DeletePaymentRequest {
407+ PaymentHash : failedHash [:],
408+ },
409+ )
410+ require .NoError (t .t , err )
411+
412+ // Now call AccountPayments. It should find the 3 payments in the local
413+ // store but fail to track the deleted failed payment in LND, gracefully
414+ // skipping it and returning the other 2 payments.
415+ paymentsResp , err = acctClient .AccountPayments (
416+ ctxm , & litrpc.AccountPaymentsRequest {
417+ Account : & litrpc.AccountIdentifier {
418+ Identifier : & litrpc.AccountIdentifier_Id {
419+ Id : acctResp .Account .Id ,
420+ },
421+ },
422+ CountTotalPayments : true ,
423+ },
424+ )
425+ require .NoError (t .t , err )
426+ require .Len (t .t , paymentsResp .Payments , 2 )
427+
428+ // Sort the payments by value descending to ensure index-based
429+ // assertions are deterministic regardless of database sorting by hash.
430+ sort .Slice (paymentsResp .Payments , func (i , j int ) bool {
431+ valI := paymentsResp .Payments [i ].ValueSat
432+ valJ := paymentsResp .Payments [j ].ValueSat
433+
434+ return valI > valJ
435+ })
436+
437+ // Succeeded payment
438+ require .Equal (
439+ t .t , uint64 (4444 ),
440+ uint64 (paymentsResp .Payments [0 ].ValueSat ),
441+ )
442+ require .Equal (
443+ t .t , lnrpc .Payment_SUCCEEDED ,
444+ paymentsResp .Payments [0 ].Status ,
445+ )
446+
447+ // Settled hold payment
448+ require .Equal (
449+ t .t , uint64 (2222 ),
450+ uint64 (paymentsResp .Payments [1 ].ValueSat ),
451+ )
452+ require .Equal (
453+ t .t , lnrpc .Payment_SUCCEEDED ,
454+ paymentsResp .Payments [1 ].Status ,
455+ )
456+
457+ require .EqualValues (t .t , 3 , paymentsResp .TotalNumPayments )
458+ require .EqualValues (t .t , 0 , paymentsResp .FirstIndexOffset )
459+ require .EqualValues (t .t , 2 , paymentsResp .LastIndexOffset )
460+
173461 // Clean up our channel and payments, so we can start the next test
174462 // iteration with a clean slate.
175463 closeChannelAndAssert (t , net , node , channelOp , false )
@@ -240,7 +528,7 @@ func testAccountRestrictionsLNC(ctxm context.Context, t *harnessTest,
240528 // There should be invoices and payments from the previous test over RPC
241529 // directly.
242530 assertNumInvoices (ctxt , t .t , lightningClient , 1 )
243- assertNumPayments (ctxt , t .t , lightningClient , 1 )
531+ assertNumPayments (ctxt , t .t , lightningClient , 3 )
244532}
245533
246534// testAccountRestrictions tests the different scenarios in which the account
0 commit comments