@@ -2912,6 +2912,84 @@ async fn unified_send_receive_bip21_uri() {
29122912 assert_eq ! ( node_b. list_balances( ) . total_lightning_balance_sats, 200_000 ) ;
29132913}
29142914
2915+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
2916+ async fn unified_send_bolt11_duplicate_payment_no_onchain_fallback ( ) {
2917+ // Regression test for https://github.com/lightningdevkit/ldk-node/issues/1033
2918+ //
2919+ // Sending a unified BIP21 payment that resolves to BOLT11 should return
2920+ // Error::DuplicatePayment on retry, not fall back to the on-chain method.
2921+
2922+ let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
2923+ let chain_source = random_chain_source ( & bitcoind, & electrsd) ;
2924+
2925+ let ( node_a, node_b) = setup_two_nodes ( & chain_source, false , false ) ;
2926+
2927+ let address_a = node_a. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2928+ let premined_sats = 5_000_000 ;
2929+
2930+ premine_and_distribute_funds (
2931+ & bitcoind. client ,
2932+ & electrsd. client ,
2933+ vec ! [ address_a] ,
2934+ Amount :: from_sat ( premined_sats) ,
2935+ )
2936+ . await ;
2937+
2938+ node_a. sync_wallets ( ) . unwrap ( ) ;
2939+ open_channel ( & node_a, & node_b, 4_000_000 , true , & electrsd) . await ;
2940+ generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 6 ) . await ;
2941+
2942+ node_a. sync_wallets ( ) . unwrap ( ) ;
2943+ node_b. sync_wallets ( ) . unwrap ( ) ;
2944+
2945+ expect_channel_ready_event ! ( node_a, node_b. node_id( ) ) ;
2946+ expect_channel_ready_event ! ( node_b, node_a. node_id( ) ) ;
2947+
2948+ // Sleep until we broadcast a node announcement.
2949+ while node_b. status ( ) . latest_node_announcement_broadcast_timestamp . is_none ( ) {
2950+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 10 ) ) . await ;
2951+ }
2952+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 1 ) ) . await ;
2953+
2954+ let expected_amount_sats = 100_000 ;
2955+ let expiry_sec = 4_000 ;
2956+
2957+ // Receive a unified payment on node_b — this will produce a URI with BOLT12 offer + BOLT11 invoice.
2958+ let uri_str = node_b. unified_payment ( ) . receive ( expected_amount_sats, "asdf" , expiry_sec) . unwrap ( ) ;
2959+
2960+ // Strip the BOLT12 offer so the URI resolves to BOLT11 only (no BOLT12, no on-chain fallback).
2961+ let uri_str_bolt11_only = uri_str. split ( "&lno=" ) . next ( ) . unwrap ( ) ;
2962+
2963+ // First send: should succeed via BOLT11.
2964+ let first_result = node_a. unified_payment ( ) . send ( uri_str_bolt11_only, None , None ) . await ;
2965+ let first_payment_id = match first_result {
2966+ Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) => payment_id,
2967+ Ok ( other) => panic ! ( "Expected Bolt11 result on first send, got: {:?}" , other) ,
2968+ Err ( e) => panic ! ( "Expected Bolt11 result on first send, got error: {:?}" , e) ,
2969+ } ;
2970+ expect_payment_successful_event ! ( node_a, Some ( first_payment_id) , None ) ;
2971+
2972+ // Second send with the same URI: should return DuplicatePayment, NOT fall back to on-chain.
2973+ let second_result = node_a. unified_payment ( ) . send ( uri_str_bolt11_only, None , None ) . await ;
2974+ match second_result {
2975+ Err ( NodeError :: DuplicatePayment ) => {
2976+ // Expected — this is the fix for #1033.
2977+ } ,
2978+ Ok ( UnifiedPaymentResult :: Onchain { txid } ) => {
2979+ panic ! (
2980+ "Regression: Duplicate BOLT11 payment fell back to on-chain. txid={}. See #1033" ,
2981+ txid
2982+ ) ;
2983+ } ,
2984+ Ok ( other) => {
2985+ panic ! ( "Expected DuplicatePayment error on retry, got: {:?}" , other) ;
2986+ } ,
2987+ Err ( other) => {
2988+ panic ! ( "Expected DuplicatePayment error on retry, got: {:?}" , other) ;
2989+ } ,
2990+ }
2991+ }
2992+
29152993#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
29162994async fn lsps2_client_service_integration ( ) {
29172995 do_lsps2_client_service_integration ( true ) . await ;
0 commit comments