diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index cf5af7d784e..42913628872 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -12,6 +12,7 @@ use bitcoin::hashes::hmac::Hmac; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; +use bitcoin::secp256k1::ecdh::SharedSecret; use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp}; use crate::blinded_path::utils; @@ -170,15 +171,8 @@ impl BlindedPaymentPath { NL::Target: NodeIdLookUp, T: secp256k1::Signing + secp256k1::Verification, { - let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.inner_path.blinding_point, None)?; - let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); - let encrypted_control_tlvs = &self.inner_path.blinded_hops.get(0).ok_or(())?.encrypted_payload; - let mut s = Cursor::new(encrypted_control_tlvs); - let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); - match ChaChaPolyReadAdapter::read(&mut reader, rho) { - Ok(ChaChaPolyReadAdapter { - readable: BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }) - }) => { + match self.decrypt_intro_payload::(node_signer) { + Ok((BlindedPaymentTlvs::Forward(ForwardTlvs { short_channel_id, .. }), control_tlvs_ss)) => { let next_node_id = match node_id_lookup.next_node_id(short_channel_id) { Some(node_id) => node_id, None => return Err(()), @@ -195,6 +189,20 @@ impl BlindedPaymentPath { } } + pub(crate) fn decrypt_intro_payload( + &self, node_signer: &NS + ) -> Result<(BlindedPaymentTlvs, SharedSecret), ()> where NS::Target: NodeSigner { + let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &self.inner_path.blinding_point, None)?; + let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); + let encrypted_control_tlvs = &self.inner_path.blinded_hops.get(0).ok_or(())?.encrypted_payload; + let mut s = Cursor::new(encrypted_control_tlvs); + let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64); + match ChaChaPolyReadAdapter::read(&mut reader, rho) { + Ok(ChaChaPolyReadAdapter { readable, .. }) => Ok((readable, control_tlvs_ss)), + _ => Err(()) + } + } + pub(crate) fn inner_blinded_path(&self) -> &BlindedPath { &self.inner_path } diff --git a/lightning/src/ln/async_payments_tests.rs b/lightning/src/ln/async_payments_tests.rs index a2d7755eac7..2d1ae0f1efc 100644 --- a/lightning/src/ln/async_payments_tests.rs +++ b/lightning/src/ln/async_payments_tests.rs @@ -8,33 +8,100 @@ // licenses. use crate::blinded_path::message::{MessageContext, OffersContext}; -use crate::events::{Event, HTLCDestination, MessageSendEventsProvider, PaymentFailureReason}; -use crate::ln::blinded_payment_tests::get_blinded_route_parameters; +use crate::blinded_path::payment::PaymentContext; +use crate::blinded_path::payment::{AsyncBolt12OfferContext, BlindedPaymentTlvs}; +use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS}; +use crate::events::{ + Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider, PaymentFailureReason, +}; +use crate::ln::blinded_payment_tests::{fail_blinded_htlc_backwards, get_blinded_route_parameters}; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::functional_test_utils::*; +use crate::ln::msgs; use crate::ln::msgs::ChannelMessageHandler; use crate::ln::msgs::OnionMessageHandler; use crate::ln::offers_tests; use crate::ln::onion_utils::INVALID_ONION_BLINDING; +use crate::ln::outbound_payment::PendingOutboundPayment; use crate::ln::outbound_payment::Retry; +use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::offer::Offer; use crate::offers::static_invoice::StaticInvoice; -use crate::onion_message::async_payments::{ - AsyncPaymentsMessage, AsyncPaymentsMessageHandler, ReleaseHeldHtlc, -}; +use crate::onion_message::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler}; use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions}; use crate::onion_message::offers::OffersMessage; use crate::onion_message::packet::ParsedOnionMessageContents; use crate::prelude::*; +use crate::routing::router::{Payee, PaymentParameters}; +use crate::sign::NodeSigner; +use crate::sync::Mutex; use crate::types::features::Bolt12InvoiceFeatures; -use crate::types::payment::{PaymentPreimage, PaymentSecret}; +use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; +use bitcoin::constants::ChainHash; +use bitcoin::network::Network; use bitcoin::secp256k1; use bitcoin::secp256k1::Secp256k1; use core::convert::Infallible; use core::time::Duration; +// Goes through the async receive onion message flow, returning the final release_held_htlc OM. +// +// Assumes the held_htlc_available message will be sent: +// sender -> always_online_recipient_counterparty -> recipient. +// +// Returns: (held_htlc_available_om, release_held_htlc_om) +fn pass_async_payments_oms( + static_invoice: StaticInvoice, sender: &Node, always_online_recipient_counterparty: &Node, + recipient: &Node, +) -> (msgs::OnionMessage, msgs::OnionMessage) { + let sender_node_id = sender.node.get_our_node_id(); + let always_online_node_id = always_online_recipient_counterparty.node.get_our_node_id(); + + // Don't forward the invreq since we don't support retrieving the static invoice from the + // recipient's LSP yet, instead manually construct the response. + let invreq_om = + sender.onion_messenger.next_onion_message_for_peer(always_online_node_id).unwrap(); + let invreq_reply_path = + offers_tests::extract_invoice_request(always_online_recipient_counterparty, &invreq_om).1; + + always_online_recipient_counterparty + .onion_messenger + .send_onion_message( + ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( + static_invoice, + )), + MessageSendInstructions::WithoutReplyPath { + destination: Destination::BlindedPath(invreq_reply_path), + }, + ) + .unwrap(); + let static_invoice_om = always_online_recipient_counterparty + .onion_messenger + .next_onion_message_for_peer(sender_node_id) + .unwrap(); + sender.onion_messenger.handle_onion_message(always_online_node_id, &static_invoice_om); + + let held_htlc_available_om_0_1 = + sender.onion_messenger.next_onion_message_for_peer(always_online_node_id).unwrap(); + always_online_recipient_counterparty + .onion_messenger + .handle_onion_message(sender_node_id, &held_htlc_available_om_0_1); + let held_htlc_available_om_1_2 = always_online_recipient_counterparty + .onion_messenger + .next_onion_message_for_peer(recipient.node.get_our_node_id()) + .unwrap(); + recipient + .onion_messenger + .handle_onion_message(always_online_node_id, &held_htlc_available_om_1_2); + + ( + held_htlc_available_om_1_2, + recipient.onion_messenger.next_onion_message_for_peer(sender_node_id).unwrap(), + ) +} + fn create_static_invoice( always_online_counterparty: &Node, recipient: &Node, relative_expiry: Option, secp_ctx: &Secp256k1, @@ -319,17 +386,25 @@ fn ignore_unexpected_static_invoice() { } #[test] -fn pays_static_invoice() { - // Test that we support the async payments flow up to and including sending the actual payment. - // Async receive is not yet supported so we don't complete the payment yet. +fn async_receive_flow_success() { + // Test that an always-online sender can successfully pay an async receiver. let secp_ctx = Secp256k1::new(); let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); - let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + let node_chanmgrs = + create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + // Set the random bytes so we can predict the payment preimage and hash. + let hardcoded_random_bytes = [42; 32]; + let keysend_preimage = PaymentPreimage(hardcoded_random_bytes); + let payment_hash: PaymentHash = keysend_preimage.into(); + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(hardcoded_random_bytes); + let relative_expiry = Duration::from_secs(1000); let (offer, static_invoice) = create_static_invoice(&nodes[1], &nodes[2], Some(relative_expiry), &secp_ctx); @@ -342,68 +417,16 @@ fn pays_static_invoice() { .node .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) .unwrap(); - - // Don't forward the invreq since we don't support retrieving the static invoice from the - // recipient's LSP yet, instead manually construct the response. - let invreq_om = nodes[0] - .onion_messenger - .next_onion_message_for_peer(nodes[1].node.get_our_node_id()) - .unwrap(); - let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1; - - nodes[1] - .onion_messenger - .send_onion_message( - ParsedOnionMessageContents::::Offers(OffersMessage::StaticInvoice( - static_invoice, - )), - MessageSendInstructions::WithoutReplyPath { - destination: Destination::BlindedPath(invreq_reply_path), - }, - ) - .unwrap(); - let static_invoice_om = nodes[1] - .onion_messenger - .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) - .unwrap(); - nodes[0] - .onion_messenger - .handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om); - let mut async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node); - assert!(!async_pmts_msgs.is_empty()); - assert!(async_pmts_msgs - .iter() - .all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_)))); - - // Manually send the message and context releasing the HTLC since the recipient doesn't support - // responding themselves yet. - let held_htlc_avail_reply_path = match async_pmts_msgs.pop().unwrap().1 { - MessageSendInstructions::WithSpecifiedReplyPath { reply_path, .. } => reply_path, - _ => panic!(), - }; - nodes[2] - .onion_messenger - .send_onion_message( - ParsedOnionMessageContents::::AsyncPayments( - AsyncPaymentsMessage::ReleaseHeldHtlc(ReleaseHeldHtlc {}), - ), - MessageSendInstructions::WithoutReplyPath { - destination: Destination::BlindedPath(held_htlc_avail_reply_path), - }, - ) - .unwrap(); - - let release_held_htlc_om = nodes[2] - .onion_messenger - .next_onion_message_for_peer(nodes[0].node.get_our_node_id()) - .unwrap(); + let release_held_htlc_om = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]).1; nodes[0] .onion_messenger .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); // Check that we've queued the HTLCs of the async keysend payment. - let htlc_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); - assert_eq!(htlc_updates.update_add_htlcs.len(), 1); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); check_added_monitors!(nodes[0], 1); // Receiving a duplicate release_htlc message doesn't result in duplicate payment. @@ -411,6 +434,12 @@ fn pays_static_invoice() { .onion_messenger .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty()); + + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage); + do_pass_along_path(args); + claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage)); } #[cfg(not(feature = "std"))] @@ -483,3 +512,579 @@ fn expired_static_invoice_fail() { // The sender doesn't reply with InvoiceError right now because the always-online node doesn't // currently provide them with a reply path to do so. } + +#[test] +fn async_receive_mpp() { + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(4); + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + let node_chanmgrs = create_node_chanmgrs( + 4, + &node_cfgs, + &[None, Some(allow_priv_chan_fwds_cfg.clone()), Some(allow_priv_chan_fwds_cfg), None], + ); + let nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + // Create this network topology: + // n1 + // / \ + // n0 n3 + // \ / + // n2 + create_announced_chan_between_nodes(&nodes, 0, 1); + create_announced_chan_between_nodes(&nodes, 0, 2); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 3, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + let (offer, static_invoice) = create_static_invoice(&nodes[1], &nodes[3], None, &secp_ctx); + + // In other tests we hardcode the sender's random bytes so we can predict the keysend preimage to + // check later in the test, but that doesn't work for MPP because it causes the session_privs for + // the different MPP parts to not be unique. + let amt_msat = 15_000_000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), None) + .unwrap(); + let release_held_htlc_om_3_0 = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[3]).1; + nodes[0] + .onion_messenger + .handle_onion_message(nodes[3].node.get_our_node_id(), &release_held_htlc_om_3_0); + check_added_monitors(&nodes[0], 2); + + let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]]; + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 2); + + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let payment_hash = match ev { + MessageSendEvent::UpdateHTLCs { ref updates, .. } => { + updates.update_add_htlcs[0].payment_hash + }, + _ => panic!(), + }; + + let args = PassAlongPathArgs::new(&nodes[0], expected_route[0], amt_msat, payment_hash, ev) + .without_claimable_event(); + do_pass_along_path(args); + + let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); + let args = PassAlongPathArgs::new(&nodes[0], expected_route[1], amt_msat, payment_hash, ev); + let claimable_ev = do_pass_along_path(args).unwrap(); + let keysend_preimage = match claimable_ev { + crate::events::Event::PaymentClaimable { + purpose: crate::events::PaymentPurpose::Bolt12OfferPayment { payment_preimage, .. }, + .. + } => payment_preimage.unwrap(), + _ => panic!(), + }; + claim_payment_along_route(ClaimAlongRouteArgs::new( + &nodes[0], + expected_route, + keysend_preimage, + )); +} + +#[test] +fn amount_doesnt_match_invreq() { + // Ensure that we'll fail an async payment backwards if the amount in the HTLC is lower than the + // amount from the original invoice request. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(4); + let node_cfgs = create_node_cfgs(4, &chanmon_cfgs); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + // Make one blinded path's fees slightly higher so they are tried in a deterministic order. + let mut higher_fee_chan_cfg = allow_priv_chan_fwds_cfg.clone(); + higher_fee_chan_cfg.channel_config.forwarding_fee_base_msat += 5000; + let node_chanmgrs = create_node_chanmgrs( + 4, + &node_cfgs, + &[None, Some(allow_priv_chan_fwds_cfg), Some(higher_fee_chan_cfg), None], + ); + let nodes = create_network(4, &node_cfgs, &node_chanmgrs); + + // Create this network topology so nodes[0] has a blinded route hint to retry over. + // n1 + // / \ + // n0 n3 + // \ / + // n2 + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_announced_chan_between_nodes_with_value(&nodes, 0, 2, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 3, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0); + + let (offer, static_invoice) = create_static_invoice(&nodes[1], &nodes[3], None, &secp_ctx); + + // Set the random bytes so we can predict the payment preimage and hash. + let hardcoded_random_bytes = [42; 32]; + let keysend_preimage = PaymentPreimage(hardcoded_random_bytes); + let payment_hash: PaymentHash = keysend_preimage.into(); + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(hardcoded_random_bytes); + + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), None) + .unwrap(); + let release_held_htlc_om_3_0 = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[3]).1; + + // Replace the invoice request contained within outbound_payments before sending so the invreq + // amount doesn't match the onion amount when the HTLC gets to the recipient. + let mut outbounds = + nodes[0].node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + let valid_invreq; + match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::StaticInvoiceReceived { invoice_request, .. }) => { + valid_invreq = invoice_request.clone(); + *invoice_request = offer + .request_invoice( + &nodes[0].keys_manager.get_inbound_payment_key(), + Nonce::from_entropy_source(nodes[0].keys_manager), + &secp_ctx, + payment_id, + ) + .unwrap() + .amount_msats(amt_msat + 1) + .unwrap() + .chain_hash(ChainHash::using_genesis_block(Network::Testnet)) + .unwrap() + .build_and_sign() + .unwrap(); + }, + _ => panic!(), + } + core::mem::drop(outbounds); + + nodes[0] + .onion_messenger + .handle_onion_message(nodes[3].node.get_our_node_id(), &release_held_htlc_om_3_0); + check_added_monitors(&nodes[0], 1); + + // Check that we've queued the HTLCs of the async keysend payment. + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + assert!(matches!( + ev, MessageSendEvent::UpdateHTLCs { ref updates, .. } if updates.update_add_htlcs.len() == 1)); + + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[3]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage) + .without_claimable_event() + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + + // Modify the invoice request stored in our outbounds to be the correct one, to make sure the + // payment retry will succeed after we finish failing the invalid HTLC back. + let mut outbounds = + nodes[0].node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { invoice_request, .. }) => { + *invoice_request = Some(valid_invreq); + }, + _ => panic!(), + } + core::mem::drop(outbounds); + + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[3]], true); + + // The retry with the correct invoice request should succeed. + nodes[0].node.process_pending_htlc_forwards(); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); + assert!(matches!( + ev, MessageSendEvent::UpdateHTLCs { ref updates, .. } if updates.update_add_htlcs.len() == 1)); + check_added_monitors!(nodes[0], 1); + let route: &[&[&Node]] = &[&[&nodes[2], &nodes[3]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage); + do_pass_along_path(args); + claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage)); +} + +#[test] +fn reject_missing_invreq() { + // Ensure we'll fail an async payment backwards if the HTLC onion doesn't contain the sender's + // original invoice request. + let mut valid_invreq: Mutex> = Mutex::new(None); + + invalid_async_receive_with_retry( + |sender, _, payment_id| { + // Remove the invoice request from our Retryable payment so we don't include it in the onion on + // retry. + let mut outbounds = + sender.node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + let valid_invreq_from_outbounds = match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { invoice_request, .. }) => { + assert!(invoice_request.is_some()); + invoice_request.take() + }, + _ => panic!(), + }; + *valid_invreq.lock().unwrap() = valid_invreq_from_outbounds; + }, + |sender, payment_id| { + // Re-add the invoice request so we include it in the onion on the next retry. + let mut outbounds = + sender.node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { invoice_request, .. }) => { + *invoice_request = valid_invreq.lock().unwrap().take(); + }, + _ => panic!(), + } + }, + ); +} + +#[test] +fn reject_bad_payment_secret() { + // Ensure we'll fail an async payment backwards if the payment secret in the onion is invalid. + + let mut valid_payment_params: Mutex> = Mutex::new(None); + invalid_async_receive_with_retry( + |sender, recipient, payment_id| { + // Store invalid payment paths in the sender's outbound Retryable payment to induce the failure + // on the recipient's end. Store multiple paths so the sender still thinks they can retry after + // the failure we're about to cause below. + let mut invalid_blinded_payment_paths = Vec::new(); + for i in 0..2 { + let mut paths = recipient + .node + .test_create_blinded_payment_paths( + None, + PaymentSecret([42; 32]), // invalid payment secret + PaymentContext::AsyncBolt12Offer(AsyncBolt12OfferContext { + // We don't reach the point of checking the invreq nonce due to the invalid payment secret + offer_nonce: Nonce([i; Nonce::LENGTH]), + }), + u32::MAX, + ) + .unwrap(); + invalid_blinded_payment_paths.append(&mut paths); + } + + // Modify the outbound payment parameters to use payment paths with an invalid payment secret. + let mut outbounds = + sender.node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + let valid_params_from_outbounds = match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { ref mut payment_params, .. }) => { + assert!(payment_params.is_some()); + let valid_params = payment_params.clone(); + if let Payee::Blinded { ref mut route_hints, .. } = + &mut payment_params.as_mut().unwrap().payee + { + core::mem::swap(route_hints, &mut invalid_blinded_payment_paths); + } else { + panic!() + } + valid_params + }, + _ => panic!(), + }; + *valid_payment_params.lock().unwrap() = valid_params_from_outbounds; + }, + |sender, payment_id| { + // Re-add the valid payment params so we use the right payment secret on the next retry. + let mut outbounds = + sender.node.pending_outbound_payments.pending_outbound_payments.lock().unwrap(); + match outbounds.get_mut(&payment_id) { + Some(PendingOutboundPayment::Retryable { payment_params, .. }) => { + *payment_params = valid_payment_params.lock().unwrap().take(); + }, + _ => panic!(), + } + }, + ); +} + +fn invalid_async_receive_with_retry( + mut modify_outbounds_for_failure: F1, mut modify_outbounds_for_success: F2, +) where + F1: FnMut(&Node, &Node, PaymentId), + F2: FnMut(&Node, PaymentId), +{ + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + let node_chanmgrs = + create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + let blinded_paths_to_always_online_node = nodes[1] + .message_router + .create_blinded_paths( + nodes[1].node.get_our_node_id(), + MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }), + Vec::new(), + &secp_ctx, + ) + .unwrap(); + let (offer_builder, offer_nonce) = nodes[2] + .node + .create_async_receive_offer_builder(blinded_paths_to_always_online_node) + .unwrap(); + let offer = offer_builder.build().unwrap(); + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + + // Hardcode the payment paths so nodes[0] has something to retry over. Set all of these paths to + // use the same nodes to avoid complicating the test with a bunch of extra nodes. + let mut static_invoice_paths = Vec::new(); + for _ in 0..3 { + let static_inv_for_path = nodes[2] + .node + .create_static_invoice_builder(&offer, offer_nonce, None) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap(); + static_invoice_paths.push(static_inv_for_path.payment_paths()[0].clone()); + } + nodes[2].router.expect_blinded_payment_paths(static_invoice_paths); + + let static_invoice = nodes[2] + .node + .create_static_invoice_builder(&offer, offer_nonce, None) + .unwrap() + .build_and_sign(&secp_ctx) + .unwrap(); + + // Set the random bytes so we can predict the payment preimage and hash. + let hardcoded_random_bytes = [42; 32]; + let keysend_preimage = PaymentPreimage(hardcoded_random_bytes); + let payment_hash: PaymentHash = keysend_preimage.into(); + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(hardcoded_random_bytes); + + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(2), None) + .unwrap(); + let release_held_htlc_om_2_0 = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]).1; + nodes[0] + .onion_messenger + .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om_2_0); + check_added_monitors(&nodes[0], 1); + + // Check that we've queued the HTLCs of the async keysend payment. + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + assert!(matches!( + ev, MessageSendEvent::UpdateHTLCs { ref updates, .. } if updates.update_add_htlcs.len() == 1)); + + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage); + do_pass_along_path(args); + + // Fail the HTLC backwards to enable us to more easily modify the now-Retryable outbound to test + // failures on the recipient's end. + nodes[2].node.fail_htlc_backwards(&payment_hash); + expect_pending_htlcs_forwardable_conditions( + nodes[2].node.get_and_clear_pending_events(), + &[HTLCDestination::FailedPayment { payment_hash }], + ); + nodes[2].node.process_pending_htlc_forwards(); + check_added_monitors!(nodes[2], 1); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2]], true); + + // Trigger a retry and make sure it fails after calling the closure that induces recipient + // failure. + modify_outbounds_for_failure(&nodes[0], &nodes[2], payment_id); + nodes[0].node.process_pending_htlc_forwards(); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + assert!(matches!( + ev, MessageSendEvent::UpdateHTLCs { ref updates, .. } if updates.update_add_htlcs.len() == 1)); + check_added_monitors!(nodes[0], 1); + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage) + .without_claimable_event() + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2]], true); + + // The retry after calling the 2nd closure should succeed. + modify_outbounds_for_success(&nodes[0], payment_id); + nodes[0].node.process_pending_htlc_forwards(); + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let mut ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + check_added_monitors!(nodes[0], 1); + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage); + do_pass_along_path(args); + claim_payment_along_route(ClaimAlongRouteArgs::new(&nodes[0], route, keysend_preimage)); +} + +#[cfg(not(feature = "std"))] +#[test] +fn expired_static_invoice_message_path() { + // Test that if we receive a held_htlc_available message over an expired blinded path, we'll + // ignore it. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + const INVOICE_EXPIRY_SECS: u32 = 10; + let (offer, static_invoice) = create_static_invoice( + &nodes[1], + &nodes[2], + Some(Duration::from_secs(INVOICE_EXPIRY_SECS as u64)), + &secp_ctx, + ); + + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), None) + .unwrap(); + + // While the invoice is unexpired, respond with release_held_htlc. + let (held_htlc_available_om, _release_held_htlc_om) = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]); + + // After the invoice is expired, ignore inbound held_htlc_available messages over the path. + let path_absolute_expiry = crate::ln::inbound_payment::calculate_absolute_expiry( + nodes[2].node.duration_since_epoch().as_secs(), + INVOICE_EXPIRY_SECS, + ); + let block = create_dummy_block( + nodes[2].best_block_hash(), + (path_absolute_expiry + 1) as u32, + Vec::new(), + ); + connect_block(&nodes[2], &block); + nodes[2] + .onion_messenger + .handle_onion_message(nodes[1].node.get_our_node_id(), &held_htlc_available_om); + for i in 0..2 { + assert!(nodes[2] + .onion_messenger + .next_onion_message_for_peer(nodes[i].node.get_our_node_id()) + .is_none()); + } +} + +#[test] +fn expired_static_invoice_payment_path() { + // Test that we'll reject inbound payments to expired payment paths. + let secp_ctx = Secp256k1::new(); + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let mut allow_priv_chan_fwds_cfg = test_default_channel_config(); + allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true; + let node_chanmgrs = + create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + + // Make sure all nodes are at the same block height in preparation for CLTV timeout things. + let node_max_height = + nodes.iter().map(|node| node.blocks.lock().unwrap().len()).max().unwrap() as u32; + connect_blocks(&nodes[0], node_max_height - nodes[0].best_block_info().1); + connect_blocks(&nodes[1], node_max_height - nodes[1].best_block_info().1); + connect_blocks(&nodes[2], node_max_height - nodes[2].best_block_info().1); + + // Set the random bytes so we can predict the payment preimage and hash. + let hardcoded_random_bytes = [42; 32]; + let keysend_preimage = PaymentPreimage(hardcoded_random_bytes); + let payment_hash: PaymentHash = keysend_preimage.into(); + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some(hardcoded_random_bytes); + + // Hardcode the blinded payment path returned by the router so we can expire it via mining blocks. + let (_, static_invoice_expired_paths) = + create_static_invoice(&nodes[1], &nodes[2], None, &secp_ctx); + nodes[2] + .router + .expect_blinded_payment_paths(static_invoice_expired_paths.payment_paths().to_vec()); + + // Extract the expiry height from the to-be-expired blinded payment path. + let final_max_cltv_expiry = { + let mut blinded_path = static_invoice_expired_paths.payment_paths().to_vec().pop().unwrap(); + blinded_path + .advance_path_by_one(&nodes[1].keys_manager, &nodes[1].node, &secp_ctx) + .unwrap(); + match blinded_path.decrypt_intro_payload(&nodes[2].keys_manager).unwrap().0 { + BlindedPaymentTlvs::Receive(tlvs) => tlvs.tlvs.payment_constraints.max_cltv_expiry, + _ => panic!(), + } + }; + + // Mine a bunch of blocks so the hardcoded path's `max_cltv_expiry` is expired at the recipient's + // end by the time the payment arrives. + let min_cltv_expiry_delta = test_default_channel_config().channel_config.cltv_expiry_delta; + connect_blocks( + &nodes[0], + final_max_cltv_expiry + - nodes[0].best_block_info().1 + - min_cltv_expiry_delta as u32 + - HTLC_FAIL_BACK_BUFFER + - LATENCY_GRACE_PERIOD_BLOCKS + - 1, + ); + connect_blocks( + &nodes[1], + final_max_cltv_expiry + - nodes[1].best_block_info().1 + // Don't expire the path for nodes[1] + - min_cltv_expiry_delta as u32 + - HTLC_FAIL_BACK_BUFFER + - LATENCY_GRACE_PERIOD_BLOCKS + - 1, + ); + connect_blocks(&nodes[2], final_max_cltv_expiry - nodes[2].best_block_info().1); + + let (offer, static_invoice) = create_static_invoice(&nodes[1], &nodes[2], None, &secp_ctx); + let amt_msat = 5000; + let payment_id = PaymentId([1; 32]); + nodes[0] + .node + .pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None) + .unwrap(); + let release_held_htlc_om = + pass_async_payments_oms(static_invoice, &nodes[0], &nodes[1], &nodes[2]).1; + nodes[0] + .onion_messenger + .handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + check_added_monitors!(nodes[0], 1); + + let route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]]; + let args = PassAlongPathArgs::new(&nodes[0], route[0], amt_msat, payment_hash, ev) + .with_payment_preimage(keysend_preimage) + .without_claimable_event() + .expect_failure(HTLCDestination::FailedPayment { payment_hash }); + do_pass_along_path(args); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2]], false); + nodes[2].logger.assert_log_contains( + "lightning::ln::channelmanager", + "violated blinded payment constraints", + 1, + ); +} diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 9708dbd6d88..a760f9573f4 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -107,13 +107,17 @@ pub fn get_blinded_route_parameters( pub fn fail_blinded_htlc_backwards( payment_hash: PaymentHash, intro_node_idx: usize, nodes: &[&Node], + retry_expected: bool ) { for i in (0..nodes.len()).rev() { match i { 0 => { let mut payment_failed_conditions = PaymentFailedConditions::new() .expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]); - expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); + if retry_expected { + payment_failed_conditions = payment_failed_conditions.retry_expected(); + } + expect_payment_failed_conditions(&nodes[0], payment_hash, false, payment_failed_conditions); }, i if i <= intro_node_idx => { let unblinded_node_updates = get_htlc_update_msgs!(nodes[i], nodes[i-1].node.get_our_node_id()); @@ -604,7 +608,7 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck, if intro_fails { cause_error!(nodes[0], nodes[1], nodes[2], chan_id_1_2, chan_upd_1_2.short_channel_id); check_added_monitors!(nodes[1], 1); - fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]]); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]], false); return } @@ -696,7 +700,7 @@ fn do_blinded_intercept_payment(intercept_node_fails: bool) { expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]); nodes[1].node.process_pending_htlc_forwards(); check_added_monitors!(&nodes[1], 1); - fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]]); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1]], false); return } @@ -802,7 +806,7 @@ fn three_hop_blinded_path_fail() { ); nodes[3].node.process_pending_htlc_forwards(); check_added_monitors!(nodes[3], 1); - fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2], &nodes[3]]); + fail_blinded_htlc_backwards(payment_hash, 1, &[&nodes[0], &nodes[1], &nodes[2], &nodes[3]], false); } #[derive(PartialEq)] diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ca5d07cf3cc..b444b9bf46b 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2430,6 +2430,9 @@ where /// See `PendingOutboundPayment` documentation for more info. /// /// See `ChannelManager` struct-level documentation for lock order requirements. + #[cfg(test)] + pub(super) pending_outbound_payments: OutboundPayments, + #[cfg(not(test))] pending_outbound_payments: OutboundPayments, /// SCID/SCID Alias -> forward infos. Key of 0 means payments received. @@ -10558,6 +10561,16 @@ where ) } + #[cfg(test)] + pub(super) fn test_create_blinded_payment_paths( + &self, amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext, + relative_expiry_seconds: u32 + ) -> Result, ()> { + self.create_blinded_payment_paths( + amount_msats, payment_secret, payment_context, relative_expiry_seconds + ) + } + /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids /// are used when constructing the phantom invoice's route hints. /// diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 3d67a57c837..8edc0bbdf1c 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2494,6 +2494,7 @@ pub struct PaymentFailedConditions<'a> { pub(crate) expected_blamed_scid: Option, pub(crate) expected_blamed_chan_closed: Option, pub(crate) expected_mpp_parts_remain: bool, + pub(crate) retry_expected: bool, } impl<'a> PaymentFailedConditions<'a> { @@ -2503,6 +2504,7 @@ impl<'a> PaymentFailedConditions<'a> { expected_blamed_scid: None, expected_blamed_chan_closed: None, expected_mpp_parts_remain: false, + retry_expected: false, } } pub fn mpp_parts_remain(mut self) -> Self { @@ -2521,6 +2523,10 @@ impl<'a> PaymentFailedConditions<'a> { self.expected_htlc_error_data = Some((code, data)); self } + pub fn retry_expected(mut self) -> Self { + self.retry_expected = true; + self + } } #[cfg(test)] @@ -2586,7 +2592,7 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( }, _ => panic!("Unexpected event"), }; - if !conditions.expected_mpp_parts_remain { + if !conditions.expected_mpp_parts_remain && !conditions.retry_expected { match &payment_failed_events[1] { Event::PaymentFailed { ref payment_hash, ref payment_id, ref reason } => { assert_eq!(*payment_hash, Some(expected_payment_hash), "unexpected second payment_hash"); @@ -2599,6 +2605,11 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>( } _ => panic!("Unexpected second event"), } + } else if conditions.retry_expected { + match &payment_failed_events[1] { + Event::PendingHTLCsForwardable { .. } => {}, + _ => panic!("Unexpected second event"), + } } } @@ -2749,8 +2760,12 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret); }, PaymentPurpose::Bolt12OfferPayment { payment_preimage, payment_secret, .. } => { - assert_eq!(expected_preimage, *payment_preimage); - assert_eq!(our_payment_secret.unwrap(), *payment_secret); + if let Some(preimage) = expected_preimage { + assert_eq!(preimage, payment_preimage.unwrap()); + } + if let Some(secret) = our_payment_secret { + assert_eq!(secret, *payment_secret); + } assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret); }, PaymentPurpose::Bolt12RefundPayment { payment_preimage, payment_secret, .. } => { @@ -2772,7 +2787,11 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option } event = Some(events_2[0].clone()); } else if let Some(ref failure) = expected_failure { - assert_eq!(events_2.len(), 2); + // If we successfully decode the HTLC onion but then fail later in + // process_pending_htlc_forwards, then we'll queue the failure and generate a new + // `ProcessPendingHTLCForwards` event. If we fail during the process of decoding the HTLC, + // we'll fail it directly with no intermediate forwarding event. + assert!(events_2.len() == 1 || events_2.len() == 2); expect_htlc_handling_failed_destinations!(events_2, &[failure]); node.node.process_pending_htlc_forwards(); check_added_monitors!(node, 1);