@@ -2957,3 +2957,72 @@ async fn splice_in_with_all_balance() {
29572957 node_a. stop ( ) . unwrap ( ) ;
29582958 node_b. stop ( ) . unwrap ( ) ;
29592959}
2960+
2961+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
2962+ async fn test_retry_on_routing_failure ( ) {
2963+ /// Although `send()` technically allows retries for `Failed` statuses,
2964+ /// `RouteNotFound` should never persist a record to the store in the first place.
2965+ /// This asserts that the payment store remains clean after a routing failure.
2966+ let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
2967+ let chain_source = random_chain_source ( & bitcoind, & electrsd) ;
2968+ let ( node_a, node_b) = setup_two_nodes ( & chain_source, false , true , true ) ;
2969+
2970+ let addr_a = node_a. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2971+ let addr_b = node_b. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2972+
2973+ let premine_amount_sat = 500_000 ;
2974+
2975+ premine_and_distribute_funds (
2976+ & bitcoind. client ,
2977+ & electrsd. client ,
2978+ vec ! [ addr_a, addr_b] ,
2979+ Amount :: from_sat ( premine_amount_sat) ,
2980+ )
2981+ . await ;
2982+ node_a. sync_wallets ( ) . unwrap ( ) ;
2983+ node_b. sync_wallets ( ) . unwrap ( ) ;
2984+ assert_eq ! ( node_a. list_balances( ) . spendable_onchain_balance_sats, premine_amount_sat) ;
2985+
2986+ let _funding_txo = open_channel_with_all ( & node_a, & node_b, false , & electrsd) . await ;
2987+
2988+ generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 6 ) . await ;
2989+
2990+ node_a. sync_wallets ( ) . unwrap ( ) ;
2991+ node_b. sync_wallets ( ) . unwrap ( ) ;
2992+
2993+ let _user_channel_id_a = expect_channel_ready_event ! ( node_a, node_b. node_id( ) ) ;
2994+ let _user_channel_id_b = expect_channel_ready_event ! ( node_b, node_a. node_id( ) ) ;
2995+ generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 6 ) . await ;
2996+
2997+ let invoice_description =
2998+ Bolt11InvoiceDescription :: Direct ( Description :: new ( String :: from ( "test-retry" ) ) . unwrap ( ) ) ;
2999+
3000+ // set the payment sum more than channel capacity to fail with RouteNotFound due to liquidity constraint
3001+ let invoice = node_b
3002+ . bolt11_payment ( )
3003+ . receive ( 1_000_000_000 , & invoice_description. clone ( ) . into ( ) , 3600 )
3004+ . unwrap ( ) ;
3005+
3006+ // first attempt should fail with RouteNotFound as expected
3007+ let first_attempt = node_a. bolt11_payment ( ) . send ( & invoice, None ) ;
3008+ assert ! ( first_attempt. is_err( ) ) ;
3009+ assert_eq ! ( first_attempt. unwrap_err( ) , NodeError :: PaymentSendingFailed ) ;
3010+
3011+ // check that the payment is not in the store as HTCL was not sent
3012+ let payments = node_a. list_payments ( ) ;
3013+ let payment_id = PaymentId ( invoice. payment_hash ( ) . 0 ) ;
3014+ let payment_in_store = payments. iter ( ) . find ( |p| p. id == payment_id) ;
3015+
3016+ assert ! (
3017+ payment_in_store. is_none( ) ,
3018+ "Check not working: payment with RouteNotFound error was saved into payment_store!"
3019+ ) ;
3020+
3021+ // second attempt to make sure that payment in the store and not treated as duplicate
3022+ let second_attempt = node_a. bolt11_payment ( ) . send ( & invoice, None ) ;
3023+ assert_ne ! ( second_attempt. unwrap_err( ) , NodeError :: DuplicatePayment ) ;
3024+ assert_eq ! ( second_attempt. unwrap_err( ) , NodeError :: PaymentSendingFailed ) ;
3025+
3026+ node_a. stop ( ) . unwrap ( ) ;
3027+ node_b. stop ( ) . unwrap ( ) ;
3028+ }
0 commit comments