@@ -3,6 +3,7 @@ use crate::routes::{BitcoinNetwork, TransactionType, TransferKind, TransferStatu
33use super :: * ;
44
55const TEST_DIR_BASE : & str = "tmp/payment/" ;
6+ const SHORT_EXPIRY_SEC : u32 = 1 ;
67
78#[ serial_test:: serial]
89#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
@@ -212,7 +213,7 @@ async fn success() {
212213#[ serial_test:: serial]
213214#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
214215#[ traced_test]
215- async fn same_invoice_twice ( ) {
216+ async fn same_invoice_twice_and_expired_inbound_payments ( ) {
216217 initialize ( ) ;
217218
218219 let test_dir_base = format ! ( "{TEST_DIR_BASE}same_invoice_twice/" ) ;
@@ -273,4 +274,87 @@ async fn same_invoice_twice() {
273274
274275 let decoded = decode_ln_invoice ( node1_addr, & invoice) . await ;
275276 wait_for_ln_payment ( node1_addr, & decoded. payment_hash , HTLCStatus :: Succeeded ) . await ;
277+ // create several invoices with a very short expiry that will NOT be paid
278+ let LNInvoiceResponse { invoice : invoice1 } =
279+ ln_invoice ( node2_addr, Some ( 50000 ) , None , None , SHORT_EXPIRY_SEC ) . await ;
280+ let LNInvoiceResponse { invoice : invoice2 } =
281+ ln_invoice ( node2_addr, Some ( 100000 ) , None , None , SHORT_EXPIRY_SEC ) . await ;
282+ let LNInvoiceResponse { invoice : invoice3 } =
283+ ln_invoice ( node2_addr, None , None , None , SHORT_EXPIRY_SEC ) . await ;
284+
285+ let decoded1 = decode_ln_invoice ( node2_addr, & invoice1) . await ;
286+ let decoded2 = decode_ln_invoice ( node2_addr, & invoice2) . await ;
287+ let decoded3 = decode_ln_invoice ( node2_addr, & invoice3) . await ;
288+
289+ // verify all three start as Pending on the receiver node
290+ let payments_before = list_payments ( node2_addr) . await ;
291+ let pending_before: Vec < _ > = payments_before
292+ . iter ( )
293+ . filter ( |p| {
294+ p. inbound
295+ && matches ! ( p. status, HTLCStatus :: Pending )
296+ && [
297+ decoded1. payment_hash . as_str ( ) ,
298+ decoded2. payment_hash . as_str ( ) ,
299+ decoded3. payment_hash . as_str ( ) ,
300+ ]
301+ . contains ( & p. payment_hash . as_str ( ) )
302+ } )
303+ . collect ( ) ;
304+ assert_eq ! (
305+ pending_before. len( ) ,
306+ 3 ,
307+ "expected all 3 unpaid invoices to be Pending"
308+ ) ;
309+
310+ // wait for the invoices to expire
311+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( SHORT_EXPIRY_SEC as u64 + 1 ) ) . await ;
312+
313+ // getting a payment should trigger expiration-based status transition
314+ let payment = get_payment ( node2_addr, & decoded1. payment_hash ) . await ;
315+ assert_eq ! (
316+ payment. status,
317+ HTLCStatus :: Failed ,
318+ "expected expired inbound payment {} to be Failed via getpayment, got {:?}" ,
319+ decoded1. payment_hash,
320+ payment. status
321+ ) ;
322+
323+ // listing payments should trigger expiration-based status transition
324+ let payments_after = list_payments ( node2_addr) . await ;
325+
326+ for hash in [
327+ decoded2. payment_hash . as_str ( ) ,
328+ decoded3. payment_hash . as_str ( ) ,
329+ ] {
330+ let payment = payments_after
331+ . iter ( )
332+ . find ( |p| p. payment_hash == hash)
333+ . unwrap_or_else ( || panic ! ( "payment {hash} not found" ) ) ;
334+ assert_eq ! (
335+ payment. status,
336+ HTLCStatus :: Failed ,
337+ "expected expired inbound payment {hash} to be Failed, got {:?}" ,
338+ payment. status
339+ ) ;
340+ }
341+
342+ // sanity: no new Pending inbound payments should have appeared
343+ let still_pending: Vec < _ > = payments_after
344+ . iter ( )
345+ . filter ( |p| {
346+ p. inbound
347+ && matches ! ( p. status, HTLCStatus :: Pending )
348+ && [
349+ decoded1. payment_hash . as_str ( ) ,
350+ decoded2. payment_hash . as_str ( ) ,
351+ decoded3. payment_hash . as_str ( ) ,
352+ ]
353+ . contains ( & p. payment_hash . as_str ( ) )
354+ } )
355+ . collect ( ) ;
356+ assert ! (
357+ still_pending. is_empty( ) ,
358+ "found expired inbound payments still Pending: {still_pending:?}"
359+ ) ;
276360}
0 commit comments