@@ -2967,6 +2967,84 @@ async fn unified_send_receive_bip21_uri() {
29672967 assert_eq ! ( node_b. list_balances( ) . total_lightning_balance_sats, 200_000 ) ;
29682968}
29692969
2970+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
2971+ async fn unified_send_bolt11_duplicate_payment_no_onchain_fallback ( ) {
2972+ // Regression test for https://github.com/lightningdevkit/ldk-node/issues/1033
2973+ //
2974+ // Sending a unified BIP21 payment that resolves to BOLT11 should return
2975+ // Error::DuplicatePayment on retry, not fall back to the on-chain method.
2976+
2977+ let ( bitcoind, electrsd) = setup_bitcoind_and_electrsd ( ) ;
2978+ let chain_source = random_chain_source ( & bitcoind, & electrsd) ;
2979+
2980+ let ( node_a, node_b) = setup_two_nodes ( & chain_source, false , false ) ;
2981+
2982+ let address_a = node_a. onchain_payment ( ) . new_address ( ) . unwrap ( ) ;
2983+ let premined_sats = 5_000_000 ;
2984+
2985+ premine_and_distribute_funds (
2986+ & bitcoind. client ,
2987+ & electrsd. client ,
2988+ vec ! [ address_a] ,
2989+ Amount :: from_sat ( premined_sats) ,
2990+ )
2991+ . await ;
2992+
2993+ node_a. sync_wallets ( ) . unwrap ( ) ;
2994+ open_channel ( & node_a, & node_b, 4_000_000 , true , & electrsd) . await ;
2995+ generate_blocks_and_wait ( & bitcoind. client , & electrsd. client , 6 ) . await ;
2996+
2997+ node_a. sync_wallets ( ) . unwrap ( ) ;
2998+ node_b. sync_wallets ( ) . unwrap ( ) ;
2999+
3000+ expect_channel_ready_event ! ( node_a, node_b. node_id( ) ) ;
3001+ expect_channel_ready_event ! ( node_b, node_a. node_id( ) ) ;
3002+
3003+ // Sleep until we broadcast a node announcement.
3004+ while node_b. status ( ) . latest_node_announcement_broadcast_timestamp . is_none ( ) {
3005+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 10 ) ) . await ;
3006+ }
3007+ tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 1 ) ) . await ;
3008+
3009+ let expected_amount_sats = 100_000 ;
3010+ let expiry_sec = 4_000 ;
3011+
3012+ // Receive a unified payment on node_b — this will produce a URI with BOLT12 offer + BOLT11 invoice.
3013+ let uri_str = node_b. unified_payment ( ) . receive ( expected_amount_sats, "asdf" , expiry_sec) . unwrap ( ) ;
3014+
3015+ // Strip the BOLT12 offer so the URI resolves to BOLT11 only (no BOLT12, no on-chain fallback).
3016+ let uri_str_bolt11_only = uri_str. split ( "&lno=" ) . next ( ) . unwrap ( ) ;
3017+
3018+ // First send: should succeed via BOLT11.
3019+ let first_result = node_a. unified_payment ( ) . send ( uri_str_bolt11_only, None , None ) . await ;
3020+ let first_payment_id = match first_result {
3021+ Ok ( UnifiedPaymentResult :: Bolt11 { payment_id } ) => payment_id,
3022+ Ok ( other) => panic ! ( "Expected Bolt11 result on first send, got: {:?}" , other) ,
3023+ Err ( e) => panic ! ( "Expected Bolt11 result on first send, got error: {:?}" , e) ,
3024+ } ;
3025+ expect_payment_successful_event ! ( node_a, Some ( first_payment_id) , None ) ;
3026+
3027+ // Second send with the same URI: should return DuplicatePayment, NOT fall back to on-chain.
3028+ let second_result = node_a. unified_payment ( ) . send ( uri_str_bolt11_only, None , None ) . await ;
3029+ match second_result {
3030+ Err ( NodeError :: DuplicatePayment ) => {
3031+ // Expected — this is the fix for #1033.
3032+ } ,
3033+ Ok ( UnifiedPaymentResult :: Onchain { txid } ) => {
3034+ panic ! (
3035+ "Regression: Duplicate BOLT11 payment fell back to on-chain. txid={}. See #1033" ,
3036+ txid
3037+ ) ;
3038+ } ,
3039+ Ok ( other) => {
3040+ panic ! ( "Expected DuplicatePayment error on retry, got: {:?}" , other) ;
3041+ } ,
3042+ Err ( other) => {
3043+ panic ! ( "Expected DuplicatePayment error on retry, got: {:?}" , other) ;
3044+ } ,
3045+ }
3046+ }
3047+
29703048#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
29713049async fn lsps2_client_service_integration ( ) {
29723050 do_lsps2_client_service_integration ( true ) . await ;
0 commit comments