diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 8c034b23343..d402211830d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4906,6 +4906,7 @@ where path, payment_hash, recipient_onion, total_value, cur_height, payment_id, keysend_preimage, invoice_request, bolt12_invoice, session_priv_bytes } = args; + // The top-level caller should hold the total_consistency_lock read lock. debug_assert!(self.total_consistency_lock.try_write().is_err()); let prng_seed = self.entropy_source.get_secure_random_bytes(); @@ -4920,6 +4921,52 @@ where e })?; + // Check if this is a self-payment (indicated by short_channel_id == 0) + if path.hops.len() == 1 && path.hops.first().unwrap().short_channel_id == 0 { + // This is a self-payment, handle it directly + let logger = WithContext::from(&self.logger, Some(self.get_our_node_id()), None, Some(*payment_hash)); + log_trace!(logger, "Processing self-payment with payment hash {}", payment_hash); + // For self-payments, we immediately generate the PaymentClaimable event + // since we are both the sender and receiver + let mut pending_events = self.pending_events.lock().unwrap(); + // Generate PaymentClaimable event + let purpose = if let Some(preimage) = keysend_preimage { + events::PaymentPurpose::SpontaneousPayment(*preimage) + } else if let Some(payment_secret) = recipient_onion.payment_secret { + events::PaymentPurpose::Bolt11InvoicePayment { + payment_preimage: None, + payment_secret, + } + } else { + return Err(APIError::APIMisuseError{ + err: "Self-payment requires either keysend preimage or payment secret".to_owned() + }); + }; + pending_events.push_back((events::Event::PaymentClaimable { + receiver_node_id: Some(self.get_our_node_id()), + payment_hash: *payment_hash, + onion_fields: Some(recipient_onion.clone()), + amount_msat: htlc_msat, + counterparty_skimmed_fee_msat: 0, + purpose, + via_channel_ids: Vec::new(), + claim_deadline: None, + payment_id: Some(payment_id), + }, None)); + // For spontaneous payments, also generate PaymentSent event immediately + if keysend_preimage.is_some() { + pending_events.push_back((events::Event::PaymentSent { + payment_id: Some(payment_id), + payment_preimage: keysend_preimage.unwrap(), + payment_hash: *payment_hash, + amount_msat: Some(htlc_msat), + fee_paid_msat: Some(0), // No fees for self-payments + bolt12_invoice: None, + }, None)); + } + return Ok(()); + } + let err: Result<(), _> = loop { let (counterparty_node_id, id) = match self.short_to_chan_info.read().unwrap().get(&path.hops.first().unwrap().short_channel_id) { None => { @@ -7704,7 +7751,8 @@ where ComplFunc: FnOnce( Option, bool, - ) -> (Option, Option), + ) + -> (Option, Option), >( &self, prev_hop: HTLCPreviousHopData, payment_preimage: PaymentPreimage, payment_info: Option, completion_action: ComplFunc, diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 503b040de41..8cbbe3970a5 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -5259,3 +5259,55 @@ fn max_out_mpp_path() { check_added_monitors(&nodes[0], 2); // one monitor update per MPP part nodes[0].node.get_and_clear_pending_msg_events(); } + +#[test] +fn test_self_payment() { + 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]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + let self_node = nodes[0].node.get_our_node_id(); + let _other_node = nodes[1].node.get_our_node_id(); + + create_announced_chan_between_nodes(&nodes, 0, 1); + + let amt_msat = 10_000; + let payment_params = PaymentParameters::from_node_id(self_node, TEST_FINAL_CLTV) + .with_bolt11_features(nodes[0].node.bolt11_invoice_features()) + .unwrap(); + let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); + + let preimage = Some(PaymentPreimage([42; 32])); + let onion = RecipientOnionFields::spontaneous_empty(); + let retry = Retry::Attempts(0); // payment to self should not be retried , it should succeed in one go ideally + let id = PaymentId([42; 32]); + nodes[0].node.send_spontaneous_payment(preimage, onion, id, route_params, retry).unwrap(); + + check_added_monitors!(nodes[0], 0); // Self-payments don't add monitors since no actual channel update occurs + + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + + let mut claimable_found = false; + let mut sent_found = false; + + for event in &events { + match event { + Event::PaymentClaimable { purpose, .. } => { + if let PaymentPurpose::SpontaneousPayment(_preimage) = purpose { + // For self-payments, we don't need to manually claim since the payment + // is automatically processed. The PaymentSent event is already generated. + claimable_found = true; + } + }, + Event::PaymentSent { .. } => { + sent_found = true; + }, + _ => panic!("Unexpected event: {:?}", event), + } + } + + assert!(claimable_found, "Expected PaymentClaimable event"); + assert!(sent_found, "Expected PaymentSent event"); +} diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 5ad5c3e7786..9bf91cfec28 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -18,6 +18,7 @@ use crate::blinded_path::payment::{ }; use crate::blinded_path::{BlindedHop, Direction, IntroductionNode}; use crate::crypto::chacha20::ChaCha20; +use crate::io; use crate::ln::channel_state::ChannelDetails; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields, MIN_FINAL_CLTV_EXPIRY_DELTA}; use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT}; @@ -25,6 +26,7 @@ use crate::ln::onion_utils; use crate::offers::invoice::Bolt12Invoice; #[cfg(async_payments)] use crate::offers::static_invoice::StaticInvoice; +use crate::prelude::*; use crate::routing::gossip::{ DirectedChannelInfo, EffectiveCapacity, NetworkGraph, NodeId, ReadOnlyNetworkGraph, }; @@ -37,9 +39,6 @@ use crate::types::features::{ use crate::types::payment::{PaymentHash, PaymentPreimage}; use crate::util::logger::Logger; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; - -use crate::io; -use crate::prelude::*; use alloc::collections::BinaryHeap; use core::ops::Deref; use core::{cmp, fmt}; @@ -2453,7 +2452,7 @@ where L::Target: Logger { let our_node_id = NodeId::from_pubkey(&our_node_pubkey); if payee_node_id_opt.map_or(false, |payee| payee == our_node_id) { - return Err("Cannot generate a route to ourselves"); + return create_self_payment_route(our_node_pubkey, route_params); } if our_node_id == maybe_dummy_payee_node_id { return Err("Invalid origin node id provided, use a different one"); @@ -3727,6 +3726,24 @@ where L::Target: Logger { Ok(route) } +fn create_self_payment_route( + our_node_pubkey: &PublicKey, route_params: &RouteParameters, +) -> Result { + let path = Path { + hops: vec![RouteHop { + pubkey: our_node_pubkey.clone(), + short_channel_id: 0, // Dummy short_channel_id specifying self payment + fee_msat: route_params.final_value_msat, // last hop send the entire amount + cltv_expiry_delta: MIN_FINAL_CLTV_EXPIRY_DELTA.into(), + node_features: NodeFeatures::empty(), + channel_features: ChannelFeatures::empty(), + maybe_announced_channel: false, + }], + blinded_tail: None, + }; + Ok(Route { paths: vec![path], route_params: Some(route_params.clone()) }) +} + // When an adversarial intermediary node observes a payment, it may be able to infer its // destination, if the remaining CLTV expiry delta exactly matches a feasible path in the network // graph. In order to improve privacy, this method obfuscates the CLTV expiry deltas along the