diff --git a/lightning-liquidity/tests/lsps2_integration_tests.rs b/lightning-liquidity/tests/lsps2_integration_tests.rs index 82f93b5990c..41e3e6df963 100644 --- a/lightning-liquidity/tests/lsps2_integration_tests.rs +++ b/lightning-liquidity/tests/lsps2_integration_tests.rs @@ -1221,6 +1221,7 @@ fn client_trusts_lsp_end_to_end_test() { &invoice, PaymentId(invoice.payment_hash().to_byte_array()), None, + vec![], Default::default(), Retry::Attempts(3), ) @@ -1694,6 +1695,7 @@ fn late_payment_forwarded_and_safe_after_force_close_does_not_broadcast() { &invoice, PaymentId(invoice.payment_hash().to_byte_array()), None, + vec![], Default::default(), Retry::Attempts(3), ) @@ -1885,6 +1887,7 @@ fn htlc_timeout_before_client_claim_results_in_handling_failed() { &invoice, PaymentId(invoice.payment_hash().to_byte_array()), None, + vec![], Default::default(), Retry::Attempts(3), ) @@ -2222,6 +2225,7 @@ fn client_trusts_lsp_partial_fee_does_not_trigger_broadcast() { &invoice, PaymentId(invoice.payment_hash().to_byte_array()), None, + vec![], Default::default(), Retry::Attempts(3), ) diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index 8e7fbdf94fd..01c3e0c7ef1 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -547,7 +547,10 @@ fn build_async_offer_and_init_payment( let offer = recipient.node.get_async_receive_offer().unwrap(); let payment_id = PaymentId([1; 32]); - sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + sender + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); // Forward invreq to server, pass static invoice back let (peer_id, invreq_om) = extract_invoice_request_om(sender, &[sender_lsp, invoice_server]); @@ -705,7 +708,10 @@ fn static_invoice_unknown_required_features() { // unknown required features. let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0] + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); // Don't forward the invreq since the invoice was created outside of the normal flow, instead // manually construct the response. @@ -777,7 +783,10 @@ fn ignore_unexpected_static_invoice() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0] + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let invreq_om = nodes[0] .onion_messenger @@ -906,7 +915,10 @@ fn ignore_duplicate_invoice() { let offer = async_recipient.node.get_async_receive_offer().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + sender + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let sender_node_id = sender.node.get_our_node_id(); let always_online_node_id = always_online_node.node.get_our_node_id(); @@ -1004,7 +1016,10 @@ fn ignore_duplicate_invoice() { // Now handle case where the sender pays regular invoice and ignores static invoice. let payment_id = PaymentId([2; 32]); - sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + sender + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let invreq_om = sender.onion_messenger.next_onion_message_for_peer(always_online_node_id).unwrap(); @@ -1110,7 +1125,10 @@ fn async_receive_flow_success() { let offer = nodes[2].node.get_async_receive_offer().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0] + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let release_held_htlc_om = pass_async_payments_oms( static_invoice.clone(), &nodes[0], @@ -1170,7 +1188,10 @@ fn expired_static_invoice_fail() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0] + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let invreq_om = nodes[0] .onion_messenger @@ -1253,7 +1274,10 @@ fn timeout_unreleased_payment() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + sender + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let invreq_om = sender.onion_messenger.next_onion_message_for_peer(server.node.get_our_node_id()).unwrap(); @@ -1347,7 +1371,10 @@ fn async_receive_mpp() { let amt_msat = 15_000_000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0] + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let release_held_htlc_om_3_0 = pass_async_payments_oms( static_invoice, &nodes[0], @@ -1445,7 +1472,10 @@ fn amount_doesnt_match_invreq() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0] + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let release_held_htlc_om_3_0 = pass_async_payments_oms( static_invoice, &nodes[0], @@ -1688,7 +1718,10 @@ fn invalid_async_receive_with_retry( let static_invoice = invoice_flow_res.invoice; let offer = nodes[2].node.get_async_receive_offer().unwrap(); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0] + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let release_held_htlc_om_2_0 = pass_async_payments_oms( static_invoice, &nodes[0], @@ -1782,7 +1815,10 @@ fn expired_static_invoice_message_path() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0] + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); // While the invoice is unexpired, respond with release_held_htlc. let (held_htlc_available_om, _release_held_htlc_om) = pass_async_payments_oms( @@ -1897,7 +1933,7 @@ fn expired_static_invoice_payment_path() { let payment_id = PaymentId([1; 32]); let mut params: OptionalOfferPaymentParams = Default::default(); params.retry_strategy = Retry::Attempts(0); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, params).unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], params).unwrap(); let release_held_htlc_om = pass_async_payments_oms( static_invoice, &nodes[0], @@ -2341,7 +2377,10 @@ fn refresh_static_invoices_for_used_offers() { let offer = recipient.node.get_async_receive_offer().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + sender + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let release_held_htlc_om = pass_async_payments_oms( updated_invoice.clone(), @@ -2673,7 +2712,10 @@ fn invoice_server_is_not_channel_peer() { let offer = recipient.node.get_async_receive_offer().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + sender + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); // Do the held_htlc_available --> release_held_htlc dance. let release_held_htlc_om = pass_async_payments_oms( @@ -2735,7 +2777,10 @@ fn invoice_request_forwarded_to_async_recipient() { let offer = async_recipient.node.get_async_receive_offer().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + sender + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); let sender_node_id = sender.node.get_our_node_id(); @@ -2836,7 +2881,10 @@ fn async_payment_e2e() { let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + sender + .node + .pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()) + .unwrap(); // Forward invreq to server, pass static invoice back, check that htlc was locked in/monitor was // added diff --git a/lightning/src/ln/bolt11_payment_tests.rs b/lightning/src/ln/bolt11_payment_tests.rs index 63c5576e333..7e33cd4ab65 100644 --- a/lightning/src/ln/bolt11_payment_tests.rs +++ b/lightning/src/ln/bolt11_payment_tests.rs @@ -55,6 +55,7 @@ fn payment_metadata_end_to_end_for_invoice_with_amount() { &invoice, PaymentId(payment_hash.0), Some(100), + vec![], RouteParametersConfig::default(), Retry::Attempts(0), ) { @@ -68,6 +69,7 @@ fn payment_metadata_end_to_end_for_invoice_with_amount() { &invoice, PaymentId(payment_hash.0), None, + vec![], RouteParametersConfig::default(), Retry::Attempts(0), ) @@ -123,6 +125,7 @@ fn payment_metadata_end_to_end_for_invoice_with_no_amount() { &invoice, PaymentId(payment_hash.0), None, + vec![], RouteParametersConfig::default(), Retry::Attempts(0), ) { @@ -136,6 +139,7 @@ fn payment_metadata_end_to_end_for_invoice_with_no_amount() { &invoice, PaymentId(payment_hash.0), Some(50_000), + vec![], RouteParametersConfig::default(), Retry::Attempts(0), ) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 72585d69f80..0b0c553a73e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2272,8 +2272,11 @@ where /// # let channel_manager = channel_manager.get_cm(); /// # let payment_id = PaymentId([42; 32]); /// # let payment_hash = PaymentHash((*invoice.payment_hash()).to_byte_array()); +/// # let custom_tlvs = vec![ +/// # (343493u64, b"hello".to_vec()), +/// # ]; /// match channel_manager.pay_for_bolt11_invoice( -/// invoice, payment_id, None, route_params_config, retry +/// invoice, payment_id, None, custom_tlvs, route_params_config, retry /// ) { /// Ok(()) => println!("Sending payment with hash {}", payment_hash), /// Err(e) => println!("Failed sending payment with hash {}: {:?}", payment_hash, e), @@ -2371,9 +2374,12 @@ where /// # channel_manager: T, offer: &Offer, amount_msats: Option, /// # ) { /// # let channel_manager = channel_manager.get_cm(); -/// let payment_id = PaymentId([42; 32]); +/// # let payment_id = PaymentId([42; 32]); +/// # let custom_tlvs = vec![ +/// # (343493u64, b"hello".to_vec()), +/// # ]; /// match channel_manager.pay_for_offer( -/// offer, amount_msats, payment_id, Default::default(), +/// offer, amount_msats, payment_id, custom_tlvs, Default::default(), /// ) { /// Ok(()) => println!("Requesting invoice for offer"), /// Err(e) => println!("Unable to request invoice for offer: {:?}", e), @@ -2428,10 +2434,13 @@ where /// # route_params_config: RouteParametersConfig /// # ) -> Result<(), Bolt12SemanticError> { /// # let channel_manager = channel_manager.get_cm(); -/// let payment_id = PaymentId([42; 32]); +/// # let payment_id = PaymentId([42; 32]); +/// # let custom_tlvs = vec![ +/// # (343493u64, b"hello".to_vec()), +/// # ]; /// let refund = channel_manager /// .create_refund_builder( -/// amount_msats, absolute_expiry, payment_id, retry, route_params_config +/// amount_msats, absolute_expiry, payment_id, custom_tlvs, retry, route_params_config /// )? /// # ; /// # // Needed for compiling for c_bindings @@ -5542,7 +5551,8 @@ where /// To use default settings, call the function with [`RouteParametersConfig::default`]. pub fn pay_for_bolt11_invoice( &self, invoice: &Bolt11Invoice, payment_id: PaymentId, amount_msats: Option, - route_params_config: RouteParametersConfig, retry_strategy: Retry, + custom_tlvs: Vec<(u64, Vec)>, route_params_config: RouteParametersConfig, + retry_strategy: Retry, ) -> Result<(), Bolt11PaymentError> { let best_block_height = self.best_block.read().unwrap().height; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -5550,6 +5560,7 @@ where invoice, payment_id, amount_msats, + custom_tlvs, route_params_config, retry_strategy, &self.router, @@ -12869,7 +12880,8 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments pub fn create_refund_builder( &$self, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, - retry_strategy: Retry, route_params_config: RouteParametersConfig + custom_tlvs: Vec<(u64, Vec)>, retry_strategy: Retry, + route_params_config: RouteParametersConfig ) -> Result<$builder, Bolt12SemanticError> { let entropy = &*$self.entropy_source; @@ -12883,7 +12895,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); $self.pending_outbound_payments .add_new_awaiting_invoice( - payment_id, expiration, retry_strategy, route_params_config, None, + payment_id, custom_tlvs, expiration, retry_strategy, route_params_config, None, ) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; @@ -12910,7 +12922,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn create_refund_builder_using_router( &$self, router: ME, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, - retry_strategy: Retry, route_params_config: RouteParametersConfig + custom_tlvs: Vec<(u64, Vec)>, retry_strategy: Retry, route_params_config: RouteParametersConfig ) -> Result<$builder, Bolt12SemanticError> where ME::Target: MessageRouter, @@ -12927,7 +12939,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { let expiration = StaleExpiration::AbsoluteTimeout(absolute_expiry); $self.pending_outbound_payments .add_new_awaiting_invoice( - payment_id, expiration, retry_strategy, route_params_config, None, + payment_id, custom_tlvs, expiration, retry_strategy, route_params_config, None, ) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; @@ -13043,12 +13055,13 @@ where /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments pub fn pay_for_offer( &self, offer: &Offer, amount_msats: Option, payment_id: PaymentId, - optional_params: OptionalOfferPaymentParams, + custom_tlvs: Vec<(u64, Vec)>, optional_params: OptionalOfferPaymentParams, ) -> Result<(), Bolt12SemanticError> { let create_pending_payment_fn = |retryable_invoice_request: RetryableInvoiceRequest| { self.pending_outbound_payments .add_new_awaiting_invoice( payment_id, + custom_tlvs, StaleExpiration::TimerTicks(1), optional_params.retry_strategy, optional_params.route_params_config, @@ -13072,12 +13085,13 @@ where /// identical to [`Self::pay_for_offer`]. pub fn pay_for_offer_from_hrn( &self, offer: &OfferFromHrn, amount_msats: u64, payment_id: PaymentId, - optional_params: OptionalOfferPaymentParams, + custom_tlvs: Vec<(u64, Vec)>, optional_params: OptionalOfferPaymentParams, ) -> Result<(), Bolt12SemanticError> { let create_pending_payment_fn = |retryable_invoice_request: RetryableInvoiceRequest| { self.pending_outbound_payments .add_new_awaiting_invoice( payment_id, + custom_tlvs, StaleExpiration::TimerTicks(1), optional_params.retry_strategy, optional_params.route_params_config, @@ -13114,12 +13128,14 @@ where /// [`InvoiceRequest::quantity`]: crate::offers::invoice_request::InvoiceRequest::quantity pub fn pay_for_offer_with_quantity( &self, offer: &Offer, amount_msats: Option, payment_id: PaymentId, - optional_params: OptionalOfferPaymentParams, quantity: u64, + custom_tlvs: Vec<(u64, Vec)>, optional_params: OptionalOfferPaymentParams, + quantity: u64, ) -> Result<(), Bolt12SemanticError> { let create_pending_payment_fn = |retryable_invoice_request: RetryableInvoiceRequest| { self.pending_outbound_payments .add_new_awaiting_invoice( payment_id, + custom_tlvs, StaleExpiration::TimerTicks(1), optional_params.retry_strategy, optional_params.route_params_config, @@ -15773,7 +15789,7 @@ where self.pay_for_offer_intern(&offer, None, Some(amt_msats), payer_note, payment_id, Some(name), |retryable_invoice_request| { self.pending_outbound_payments - .received_offer(payment_id, Some(retryable_invoice_request)) + .received_offer(payment_id, vec![], Some(retryable_invoice_request)) .map_err(|_| Bolt12SemanticError::DuplicatePaymentId) }); if offer_pay_res.is_err() { diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 425cc4d7eb6..a706d928b2d 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -614,6 +614,7 @@ where mod test { use super::*; use crate::chain::channelmonitor::HTLC_FAIL_BACK_BUFFER; + use crate::events::Event; use crate::ln::channelmanager::{ Bolt11InvoiceParameters, PaymentId, PhantomRouteHints, RecipientOnionFields, Retry, MIN_FINAL_CLTV_EXPIRY_DELTA, @@ -663,7 +664,7 @@ mod test { } #[test] - fn create_and_pay_for_bolt11_invoice() { + fn create_and_pay_for_bolt11_invoice_with_custom_tlvs() { let chanmon_cfgs = create_chanmon_cfgs(2); let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); @@ -694,6 +695,11 @@ mod test { Duration::from_secs(non_default_invoice_expiry_secs.into()) ); + let (payment_hash, payment_secret) = + (PaymentHash(invoice.payment_hash().to_byte_array()), *invoice.payment_secret()); + + let preimage = nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap(); + // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is // available. let chan = &nodes[1].node.list_usable_channels()[0]; @@ -708,9 +714,18 @@ mod test { assert_eq!(invoice.route_hints()[0].0[0].htlc_maximum_msat, chan.inbound_htlc_maximum_msat); let retry = Retry::Attempts(0); + let custom_tlvs = vec![(65537, vec![42; 42])]; + nodes[0] .node - .pay_for_bolt11_invoice(&invoice, PaymentId([42; 32]), None, Default::default(), retry) + .pay_for_bolt11_invoice( + &invoice, + PaymentId([42; 32]), + None, + custom_tlvs.clone(), + Default::default(), + retry, + ) .unwrap(); check_added_monitors(&nodes[0], 1); @@ -718,10 +733,30 @@ mod test { assert_eq!(events.len(), 1); let payment_event = SendEvent::from_event(events.remove(0)); nodes[1].node.handle_update_add_htlc(node_a_id, &payment_event.msgs[0]); - nodes[1].node.handle_commitment_signed_batch_test(node_a_id, &payment_event.commitment_msg); - check_added_monitors(&nodes[1], 1); - let events = nodes[1].node.get_and_clear_pending_msg_events(); - assert_eq!(events.len(), 2); + check_added_monitors!(&nodes[1], 0); + do_commitment_signed_dance( + &nodes[1], + &nodes[0], + &payment_event.commitment_msg, + false, + false, + ); + expect_and_process_pending_htlcs(&nodes[1], false); + + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + + match events[0] { + Event::PaymentClaimable { ref onion_fields, .. } => { + assert_eq!(onion_fields.clone().unwrap().custom_tlvs().clone(), custom_tlvs); + }, + _ => panic!("Unexpected event"), + } + + claim_payment_along_route( + ClaimAlongRouteArgs::new(&nodes[0], &[&[&nodes[1]]], preimage) + .with_custom_tlvs(custom_tlvs), + ); } fn do_create_invoice_min_final_cltv_delta(with_custom_delta: bool) { diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index f67ad442c29..c49f0c636c7 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -519,7 +519,13 @@ fn bolt12_invoice_too_large_blinded_paths() { let payment_id = PaymentId([1; 32]); nodes[0] .node - .pay_for_offer(&offer, Some(5000), payment_id, OptionalOfferPaymentParams::default()) + .pay_for_offer( + &offer, + Some(5000), + payment_id, + vec![], + OptionalOfferPaymentParams::default(), + ) .unwrap(); let invreq_om = nodes[0] .onion_messenger diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 4c53aefe58d..5d8f9a7e387 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -165,6 +165,12 @@ fn check_compact_path_introduction_node<'a, 'b, 'c>( fn route_bolt12_payment<'a, 'b, 'c>( node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice +) { + route_bolt12_payment_with_custom_tlvs(node, path, invoice, Vec::new()); +} + +fn route_bolt12_payment_with_custom_tlvs<'a, 'b, 'c>( + node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice, custom_tlvs: Vec<(u64, Vec)> ) { // Monitor added when handling the invoice onion message. check_added_monitors(node, 1); @@ -178,7 +184,8 @@ fn route_bolt12_payment<'a, 'b, 'c>( let amount_msats = invoice.amount_msats(); let payment_hash = invoice.payment_hash(); let args = PassAlongPathArgs::new(node, path, amount_msats, payment_hash, ev) - .without_clearing_recipient_events(); + .without_clearing_recipient_events() + .with_custom_tlvs(custom_tlvs); do_pass_along_path(args); } @@ -306,7 +313,7 @@ fn create_refund_with_no_blinded_path() { let router = NullMessageRouter {}; let refund = alice.node - .create_refund_builder_using_router(&router, 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder_using_router(&router, 10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_eq!(refund.amount_msats(), 10_000_000); @@ -473,7 +480,7 @@ fn check_dummy_hop_pattern_in_offer() { } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&compact_offer, None, payment_id, Default::default()).unwrap(); + bob.node.pay_for_offer(&compact_offer, None, payment_id, vec![], Default::default()).unwrap(); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); @@ -495,7 +502,7 @@ fn check_dummy_hop_pattern_in_offer() { assert!(padded_offer.paths().iter().all(|path| path.blinded_hops().len() == PADDED_PATH_LENGTH)); let payment_id = PaymentId([2; 32]); - bob.node.pay_for_offer(&padded_offer, None, payment_id, Default::default()).unwrap(); + bob.node.pay_for_offer(&padded_offer, None, payment_id, vec![], Default::default()).unwrap(); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); @@ -571,7 +578,7 @@ fn creates_short_lived_refund() { let absolute_expiry = bob.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; let payment_id = PaymentId([1; 32]); let refund = bob.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_eq!(refund.absolute_expiry(), Some(absolute_expiry)); @@ -602,7 +609,7 @@ fn creates_long_lived_refund() { let router = NodeIdMessageRouter::new(bob.network_graph, bob.keys_manager); let refund = bob.node - .create_refund_builder_using_router(&router, 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder_using_router(&router, 10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_eq!(refund.absolute_expiry(), Some(absolute_expiry)); @@ -662,7 +669,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { } let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, bob); @@ -758,7 +765,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = david.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_eq!(refund.amount_msats(), 10_000_000); @@ -827,7 +834,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -886,7 +893,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = bob.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_eq!(refund.amount_msats(), 10_000_000); @@ -948,7 +955,7 @@ fn pays_for_offer_without_blinded_paths() { assert!(offer.paths().is_empty()); let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -995,7 +1002,7 @@ fn pays_for_refund_without_blinded_paths() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = bob.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .clear_paths() .build().unwrap(); @@ -1075,7 +1082,7 @@ fn send_invoice_requests_with_distinct_reply_path() { } let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, bob); @@ -1149,7 +1156,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = alice.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_ne!(refund.payer_signing_pubkey(), alice_id); @@ -1208,7 +1215,7 @@ fn creates_and_pays_for_offer_with_retry() { assert!(check_compact_path_introduction_node(&path, bob, alice_id)); } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let _lost_onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1280,7 +1287,7 @@ fn pays_bolt12_invoice_asynchronously() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1348,6 +1355,128 @@ fn pays_bolt12_invoice_asynchronously() { ); } +/// Checks that a deferred invoice can be paid asynchronously from an Event::InvoiceReceived. +#[test] +fn pays_bolt12_invoice_asynchronously_with_custom_tlvs() { + let mut manually_pay_cfg = test_default_channel_config(); + manually_pay_cfg.manually_handle_bolt12_invoices = true; + + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_pay_cfg)]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let alice = &nodes[0]; + let bob = &nodes[1]; + let alice_id = alice.node.get_our_node_id(); + let bob_id = bob.node.get_our_node_id(); + + let offer = alice.node + .create_offer_builder().unwrap() + .amount_msats(10_000_000) + .build().unwrap(); + + const CUSTOM_TLV_TYPE: u64 = 65537; + let custom_tlvs = vec![(CUSTOM_TLV_TYPE, vec![42; 42])]; + let payment_id = PaymentId([1; 32]); + + bob.node + .pay_for_offer(&offer, None, payment_id, custom_tlvs.clone(), Default::default()) + .unwrap(); + expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); + + let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); + alice.onion_messenger.handle_onion_message(bob_id, &onion_message); + + let (invoice_request, _) = extract_invoice_request(alice, &onion_message); + let expected_payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext { + offer_id: offer.id(), + invoice_request: InvoiceRequestFields { + payer_signing_pubkey: invoice_request.payer_signing_pubkey(), + quantity: None, + payer_note_truncated: None, + human_readable_name: None, + }, + }); + + let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); + bob.onion_messenger.handle_onion_message(alice_id, &onion_message); + + // Re-process the same onion message to ensure idempotency — + // we should not generate a duplicate `InvoiceReceived` event. + bob.onion_messenger.handle_onion_message(alice_id, &onion_message); + + let mut events = bob.node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + + let (invoice, context) = match events.pop().unwrap() { + Event::InvoiceReceived { payment_id: actual, invoice, context, .. } => { + assert_eq!(actual, payment_id); + (invoice, context) + }, + _ => panic!("No Event::InvoiceReceived"), + }; + + assert_eq!(invoice.amount_msats(), 10_000_000); + assert_ne!(invoice.signing_pubkey(), alice_id); + assert!(!invoice.payment_paths().is_empty()); + for path in invoice.payment_paths() { + assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id)); + } + + assert!(bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()).is_ok()); + assert_eq!( + bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), + Err(Bolt12PaymentError::DuplicateInvoice), + ); + + route_bolt12_payment_with_custom_tlvs(bob, &[alice], &invoice, custom_tlvs.clone()); + expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id); + + let purpose = match get_event!(&alice, Event::PaymentClaimable) { + Event::PaymentClaimable { purpose, .. } => purpose, + _ => panic!("No Event::PaymentClaimable"), + }; + let payment_preimage = purpose.preimage().expect("No preimage in Event::PaymentClaimable"); + + match purpose { + PaymentPurpose::Bolt12OfferPayment { payment_context, .. } => { + assert_eq!(PaymentContext::Bolt12Offer(payment_context), expected_payment_context); + }, + _ => panic!("Unexpected payment purpose: {:?}", purpose), + } + + let route = &[&[alice] as &[&Node]]; + + let claim_payment_args = + ClaimAlongRouteArgs::new(bob, route, payment_preimage) + .with_custom_tlvs(custom_tlvs); + + if let Some(inv) = claim_payment_along_route(claim_payment_args).0 { + assert_eq!(inv, PaidBolt12Invoice::Bolt12Invoice(invoice.clone())); + } else { + panic!("Expected PaidBolt12Invoice::Bolt12Invoice"); + } + + expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id); + + assert_eq!( + bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), + Err(Bolt12PaymentError::DuplicateInvoice), + ); + + for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS { + bob.node.timer_tick_occurred(); + } + + assert_eq!( + bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()), + Err(Bolt12PaymentError::UnexpectedInvoice), + ); +} + /// Checks that an offer can be created using an unannounced node as a blinded path's introduction /// node. This is only preferred if there are no other options which may indicated either the offer /// is intended for the unannounced node or that the node is actually announced (e.g., an LSP) but @@ -1377,7 +1506,7 @@ fn creates_offer_with_blinded_path_using_unannounced_introduction_node() { } let payment_id = PaymentId([1; 32]); - bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + bob.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap(); @@ -1435,7 +1564,7 @@ fn creates_refund_with_blinded_path_using_unannounced_introduction_node() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = bob.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_ne!(refund.payer_signing_pubkey(), bob_id); @@ -1518,7 +1647,7 @@ fn fails_authentication_when_handling_invoice_request() { // Send the invoice request directly to Alice instead of using a blinded path. let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, alice); @@ -1543,7 +1672,7 @@ fn fails_authentication_when_handling_invoice_request() { // Send the invoice request to Alice using an invalid blinded path. let payment_id = PaymentId([2; 32]); - david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); match &mut david.node.flow.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { @@ -1619,7 +1748,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { // Initiate an invoice request, but abandon tracking it. let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); david.node.abandon_payment(payment_id); get_event!(david, Event::PaymentFailed); @@ -1635,7 +1764,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { }; let payment_id = PaymentId([2; 32]); - david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); // Swap out the reply path to force authentication to fail when handling the invoice since it @@ -1714,7 +1843,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = david.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_ne!(refund.payer_signing_pubkey(), david_id); @@ -1748,7 +1877,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { let invalid_path = refund.paths().first().unwrap().clone(); let payment_id = PaymentId([2; 32]); let refund = david.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); assert_ne!(refund.payer_signing_pubkey(), david_id); @@ -1819,7 +1948,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { let payment_id = PaymentId([1; 32]); - match david.node.pay_for_offer(&offer, None, payment_id, Default::default()) { + match david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -1830,7 +1959,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { args.send_channel_ready = (true, true); reconnect_nodes(args); - assert!(david.node.pay_for_offer(&offer, None, payment_id, Default::default()).is_ok()); + assert!(david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).is_ok()); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); } @@ -1871,7 +2000,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { let absolute_expiry = david.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; let payment_id = PaymentId([1; 32]); match david.node.create_refund_builder( - 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default() + 10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default() ) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), @@ -1882,7 +2011,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { reconnect_nodes(args); let refund = david.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); @@ -1917,7 +2046,7 @@ fn fails_creating_invoice_request_for_unsupported_chain() { .chain(Network::Signet) .build().unwrap(); - match bob.node.pay_for_offer(&offer, None, PaymentId([1; 32]), Default::default()) { + match bob.node.pay_for_offer(&offer, None, PaymentId([1; 32]), vec![], Default::default()) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), } @@ -1939,7 +2068,7 @@ fn fails_sending_invoice_with_unsupported_chain_for_refund() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = bob.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .chain(Network::Signet) .build().unwrap(); @@ -1974,7 +2103,7 @@ fn fails_creating_invoice_request_without_blinded_reply_path() { .amount_msats(10_000_000) .build().unwrap(); - match david.node.pay_for_offer(&offer, None, PaymentId([1; 32]), Default::default()) { + match david.node.pay_for_offer(&offer, None, PaymentId([1; 32]), vec![], Default::default()) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), } @@ -2007,10 +2136,10 @@ fn fails_creating_invoice_request_with_duplicate_payment_id() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - assert!(david.node.pay_for_offer( &offer, None, payment_id, Default::default()).is_ok()); + assert!(david.node.pay_for_offer( &offer, None, payment_id, vec![], Default::default()).is_ok()); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); - match david.node.pay_for_offer(&offer, None, payment_id, Default::default()) { + match david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), } @@ -2031,13 +2160,13 @@ fn fails_creating_refund_with_duplicate_payment_id() { let payment_id = PaymentId([1; 32]); assert!( nodes[0].node.create_refund_builder( - 10_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default() + 10_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default() ).is_ok() ); expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); match nodes[0].node.create_refund_builder( - 10_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default() + 10_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default() ) { Ok(_) => panic!("Expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), @@ -2089,7 +2218,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_offer() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); connect_peers(david, bob); @@ -2156,7 +2285,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_refund() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = david.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); @@ -2205,7 +2334,7 @@ fn fails_paying_invoice_more_than_once() { let absolute_expiry = Duration::from_secs(u64::MAX); let payment_id = PaymentId([1; 32]); let refund = david.node - .create_refund_builder(10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), RouteParametersConfig::default()) + .create_refund_builder(10_000_000, absolute_expiry, payment_id, vec![], Retry::Attempts(0), RouteParametersConfig::default()) .unwrap() .build().unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -2297,7 +2426,7 @@ fn fails_paying_invoice_with_unknown_required_features() { .build().unwrap(); let payment_id = PaymentId([1; 32]); - david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + david.node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); connect_peers(david, bob); @@ -2381,7 +2510,7 @@ fn rejects_keysend_to_non_static_invoice_path() { let offer = nodes[1].node.create_offer_builder().unwrap().build().unwrap(); let amt_msat = 5000; let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap(); + nodes[0].node.pay_for_offer(&offer, Some(amt_msat), payment_id, vec![], Default::default()).unwrap(); let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap(); nodes[1].onion_messenger.handle_onion_message(nodes[0].node.get_our_node_id(), &invreq_om); let invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap(); @@ -2466,7 +2595,7 @@ fn no_double_pay_with_stale_channelmanager() { assert!(offer.paths().is_empty()); let payment_id = PaymentId([1; 32]); - nodes[0].node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap(); + nodes[0].node.pay_for_offer(&offer, None, payment_id, vec![], Default::default()).unwrap(); expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 75fe55bfeac..72801b102f1 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -82,6 +82,7 @@ pub(crate) enum PendingOutboundPayment { retry_strategy: Retry, route_params_config: RouteParametersConfig, retryable_invoice_request: Option, + custom_tlvs: Vec<(u64, Vec)>, }, // Represents the state after the invoice has been received, transitioning from the corresponding // `AwaitingInvoice` state. @@ -93,6 +94,7 @@ pub(crate) enum PendingOutboundPayment { // race conditions where this field might be missing upon reload. It may be required // for future retries. route_params_config: RouteParametersConfig, + custom_tlvs: Vec<(u64, Vec)>, }, // This state applies when we are paying an often-offline recipient and another node on the // network served us a static invoice on the recipient's behalf in response to our invoice @@ -543,6 +545,8 @@ pub enum RetryableSendFailure { /// /// [`BlindedPaymentPath`]: crate::blinded_path::payment::BlindedPaymentPath OnionPacketSizeExceeded, + /// The provided [`RecipientOnionFields::custom_tlvs`] are of invalid range + InvalidCustomTlvs, } /// If a payment fails to send to a route, it can be in one of several states. This enum is returned @@ -919,6 +923,7 @@ where pub(super) fn pay_for_bolt11_invoice( &self, invoice: &Bolt11Invoice, payment_id: PaymentId, amount_msats: Option, + custom_tlvs: Vec<(u64, Vec)>, route_params_config: RouteParametersConfig, retry_strategy: Retry, router: &R, @@ -942,7 +947,9 @@ where (None, None) => return Err(Bolt11PaymentError::InvalidAmount), }; - let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); + let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()) + .with_custom_tlvs(custom_tlvs) + .map_err(|_| Bolt11PaymentError::SendingFailed(RetryableSendFailure::InvalidCustomTlvs))?; recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); let payment_params = PaymentParameters::from_bolt11_invoice(invoice) @@ -981,7 +988,7 @@ where SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { - let (payment_hash, retry_strategy, params_config, _) = self + let (payment_hash, retry_strategy, params_config, custom_tlvs, _) = self .mark_invoice_received_and_get_details(invoice, payment_id)?; if invoice.invoice_features().requires_unknown_bits_from(&features) { @@ -999,10 +1006,16 @@ where route_params.max_total_routing_fee_msat = Some(max_fee_msat); } let invoice = PaidBolt12Invoice::Bolt12Invoice(invoice.clone()); + + let recipient_onion = RecipientOnionFields::spontaneous_empty() + .with_custom_tlvs(custom_tlvs) + .map_err(|_| Bolt12PaymentError::SendingFailed(RetryableSendFailure::InvalidCustomTlvs))?; + self.send_payment_for_bolt12_invoice_internal( - payment_id, payment_hash, None, None, invoice, route_params, retry_strategy, false, router, - first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, - best_block_height, pending_events, send_payment_along_path + payment_id, payment_hash, None, None, invoice, recipient_onion, + route_params, retry_strategy, false, router, first_hops, inflight_htlcs, + entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height, pending_events, + send_payment_along_path ) } @@ -1012,7 +1025,7 @@ where >( &self, payment_id: PaymentId, payment_hash: PaymentHash, keysend_preimage: Option, invoice_request: Option<&InvoiceRequest>, - bolt12_invoice: PaidBolt12Invoice, + bolt12_invoice: PaidBolt12Invoice, recipient_onion: RecipientOnionFields, mut route_params: RouteParameters, retry_strategy: Retry, hold_htlcs_at_next_hop: bool, router: &R, first_hops: Vec, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1, best_block_height: u32, @@ -1045,11 +1058,6 @@ where } } - let recipient_onion = RecipientOnionFields { - payment_secret: None, - payment_metadata: None, - custom_tlvs: vec![], - }; let route = match self.find_initial_route( payment_id, payment_hash, &recipient_onion, keysend_preimage, invoice_request, &mut route_params, router, &first_hops, &inflight_htlcs, node_signer, best_block_height, @@ -1061,6 +1069,7 @@ where RetryableSendFailure::RouteNotFound => PaymentFailureReason::RouteNotFound, RetryableSendFailure::DuplicatePayment => PaymentFailureReason::UnexpectedError, RetryableSendFailure::OnionPacketSizeExceeded => PaymentFailureReason::UnexpectedError, + RetryableSendFailure::InvalidCustomTlvs => PaymentFailureReason::UnexpectedError, }; self.abandon_payment(payment_id, reason, pending_events); return Err(Bolt12PaymentError::SendingFailed(e)); @@ -1283,6 +1292,8 @@ where retry_strategy = Retry::Attempts(0); } + let recipient_onion = RecipientOnionFields::spontaneous_empty(); + let invoice = PaidBolt12Invoice::StaticInvoice(invoice); self.send_payment_for_bolt12_invoice_internal( payment_id, @@ -1290,6 +1301,7 @@ where Some(keysend_preimage), Some(&invoice_request), invoice, + recipient_onion, route_params, retry_strategy, hold_htlcs_at_next_hop, @@ -1974,7 +1986,8 @@ where #[cfg(feature = "dnssec")] #[rustfmt::skip] pub(super) fn received_offer( - &self, payment_id: PaymentId, retryable_invoice_request: Option, + &self, payment_id: PaymentId, custom_tlvs: Vec<(u64, Vec)>, + retryable_invoice_request: Option, ) -> Result<(), ()> { match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { hash_map::Entry::Occupied(entry) => match entry.get() { @@ -1986,6 +1999,7 @@ where retry_strategy: *retry_strategy, route_params_config: *route_params_config, retryable_invoice_request, + custom_tlvs, }; core::mem::swap(&mut new_val, entry.into_mut()); Ok(()) @@ -1997,7 +2011,8 @@ where } pub(super) fn add_new_awaiting_invoice( - &self, payment_id: PaymentId, expiration: StaleExpiration, retry_strategy: Retry, + &self, payment_id: PaymentId, custom_tlvs: Vec<(u64, Vec)>, + expiration: StaleExpiration, retry_strategy: Retry, route_params_config: RouteParametersConfig, retryable_invoice_request: Option, ) -> Result<(), ()> { @@ -2013,6 +2028,7 @@ where retry_strategy, route_params_config, retryable_invoice_request, + custom_tlvs, }); Ok(()) @@ -2025,7 +2041,7 @@ where &self, invoice: &Bolt12Invoice, payment_id: PaymentId ) -> Result<(), Bolt12PaymentError> { self.mark_invoice_received_and_get_details(invoice, payment_id) - .and_then(|(_, _, _, is_newly_marked)| { + .and_then(|(_, _, _, _, is_newly_marked)| { is_newly_marked .then_some(()) .ok_or(Bolt12PaymentError::DuplicateInvoice) @@ -2034,32 +2050,34 @@ where #[rustfmt::skip] fn mark_invoice_received_and_get_details( - &self, invoice: &Bolt12Invoice, payment_id: PaymentId - ) -> Result<(PaymentHash, Retry, RouteParametersConfig, bool), Bolt12PaymentError> { + &self, invoice: &Bolt12Invoice, payment_id: PaymentId, + ) -> Result<(PaymentHash, Retry, RouteParametersConfig, Vec<(u64, Vec)>, bool), Bolt12PaymentError> { match self.pending_outbound_payments.lock().unwrap().entry(payment_id) { - hash_map::Entry::Occupied(entry) => match entry.get() { + hash_map::Entry::Occupied(mut entry) => match entry.get_mut() { PendingOutboundPayment::AwaitingInvoice { - retry_strategy: retry, route_params_config, .. + retry_strategy: retry, route_params_config, custom_tlvs, .. } => { let payment_hash = invoice.payment_hash(); let retry = *retry; let config = *route_params_config; - *entry.into_mut() = PendingOutboundPayment::InvoiceReceived { + let custom = core::mem::take(custom_tlvs); + *entry.get_mut() = PendingOutboundPayment::InvoiceReceived { payment_hash, retry_strategy: retry, route_params_config: config, + custom_tlvs: custom.clone(), }; - Ok((payment_hash, retry, config, true)) + Ok((payment_hash, retry, config, custom, true)) }, // When manual invoice handling is enabled, the corresponding `PendingOutboundPayment` entry // is already updated at the time the invoice is received. This ensures that `InvoiceReceived` // event generation remains idempotent, even if the same invoice is received again before the // event is handled by the user. PendingOutboundPayment::InvoiceReceived { - retry_strategy, route_params_config, .. + retry_strategy, route_params_config, custom_tlvs, .. } => { - Ok((invoice.payment_hash(), *retry_strategy, *route_params_config, false)) + Ok((invoice.payment_hash(), *retry_strategy, *route_params_config, custom_tlvs.clone(), false)) }, _ => Err(Bolt12PaymentError::DuplicateInvoice), }, @@ -2751,6 +2769,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, |fee_msat| RouteParametersConfig::default().with_max_total_routing_fee_msat(fee_msat) ) ))), + (9, custom_tlvs, optional_vec), }, (7, InvoiceReceived) => { (0, payment_hash, required), @@ -2767,6 +2786,7 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, |fee_msat| RouteParametersConfig::default().with_max_total_routing_fee_msat(fee_msat) ) ))), + (7, custom_tlvs, optional_vec), }, // Added in 0.1. Prior versions will drop these outbounds on downgrade, which is safe because no // HTLCs are in-flight. @@ -3047,7 +3067,7 @@ mod tests { assert!(!outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, + payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -3077,14 +3097,14 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, + payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, + payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, ).is_err() ); } @@ -3102,7 +3122,7 @@ mod tests { assert!(!outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, + payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -3132,14 +3152,14 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, + payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, + payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, ).is_err() ); } @@ -3156,7 +3176,7 @@ mod tests { assert!(!outbound_payments.has_pending_payments()); assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, + payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -3195,7 +3215,7 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, + payment_id, vec![], expiration, Retry::Attempts(0), RouteParametersConfig::default(), None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); @@ -3261,7 +3281,7 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), + payment_id, vec![], expiration, Retry::Attempts(0), route_params_config, None, ).is_ok() ); @@ -3364,7 +3384,7 @@ mod tests { assert!( outbound_payments.add_new_awaiting_invoice( - payment_id, expiration, Retry::Attempts(0), route_params_config, None, + payment_id, vec![], expiration, Retry::Attempts(0), route_params_config, None, ).is_ok() ); assert!(outbound_payments.has_pending_payments()); diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 6c982738a52..508cab663f9 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -5400,7 +5400,10 @@ fn max_out_mpp_path() { let id = PaymentId([42; 32]); let retry = Retry::Attempts(0); - nodes[0].node.pay_for_bolt11_invoice(&invoice, id, None, route_params_cfg, retry).unwrap(); + nodes[0] + .node + .pay_for_bolt11_invoice(&invoice, id, None, vec![], route_params_cfg, retry) + .unwrap(); assert!(nodes[0].node.list_recent_payments().len() == 1); check_added_monitors(&nodes[0], 2); // one monitor update per MPP part