From e9b67705d451c4d51876c86a4a0624f6023bf08e Mon Sep 17 00:00:00 2001 From: Shruti Sharma <98698727+shruti2522@users.noreply.github.com> Date: Tue, 20 Jan 2026 21:02:12 +0000 Subject: [PATCH] feat: allow self payment --- lightning/src/ln/outbound_payment.rs | 105 +++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs index 75fe55bfeac..edc2ada9fe4 100644 --- a/lightning/src/ln/outbound_payment.rs +++ b/lightning/src/ln/outbound_payment.rs @@ -31,7 +31,7 @@ use crate::routing::router::{ RouteParametersConfig, Router, }; use crate::sign::{EntropySource, NodeSigner, Recipient}; -use crate::types::features::Bolt12InvoiceFeatures; +use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::util::errors::APIError; use crate::util::logger::Logger; @@ -1484,10 +1484,51 @@ where IH: Fn() -> InFlightHtlcs, SP: Fn(SendAlongPathArgs) -> Result<(), APIError>, { - let route = self.find_initial_route( - payment_id, payment_hash, &recipient_onion, keysend_preimage, None, &mut route_params, router, - &first_hops, &inflight_htlcs, node_signer, best_block_height, - )?; + #[cfg(feature = "std")] { + if has_expired(&route_params) { + log_error!(self.logger, "Payment with id {} and hash {} had expired before we started paying", + payment_id, payment_hash); + return Err(RetryableSendFailure::PaymentExpired) + } + } + //If the router returns "Cannot generate a route to ourselves" and we have a preimage, + // treat this as a self-payment and fulfill it locally. + onion_utils::set_max_path_length( + &mut route_params, &recipient_onion, keysend_preimage, None, best_block_height + ).map_err(|()| { + log_error!(self.logger, "Can't construct an onion packet without exceeding 1300-byte onion \n\ + hop_data length for payment with id {} and hash {}", payment_id, payment_hash); + RetryableSendFailure::OnionPacketSizeExceeded + })?; + + let (route, is_self_payment) = match router.find_route_with_id( + &node_signer.get_node_id(Recipient::Node).unwrap(), &route_params, + Some(&first_hops.iter().collect::>()), inflight_htlcs(), + payment_hash, payment_id, + ) { + Ok(r) => (r, false), + Err(e) => { + if e == "Cannot generate a route to ourselves" && keysend_preimage.is_some() { + use crate::routing::router::{Route, Path, RouteHop}; + let payee_pk = node_signer.get_node_id(Recipient::Node).unwrap(); + let hop = RouteHop { + pubkey: payee_pk, + node_features: NodeFeatures::empty(), + short_channel_id: 0, + channel_features: ChannelFeatures::empty(), + fee_msat: 0, + cltv_expiry_delta: 0, + maybe_announced_channel: false, + }; + let path = Path { hops: vec![hop], blinded_tail: None }; + (Route { paths: vec![path], route_params: Some(route_params.clone()) }, true) + } else { + log_error!(self.logger, "Failed to find route for payment with id {} and hash {}", + payment_id, payment_hash); + return Err(RetryableSendFailure::RouteNotFound) + } + } + }; let onion_session_privs = self.add_new_pending_payment(payment_hash, recipient_onion.clone(), payment_id, keysend_preimage, &route, Some(retry_strategy), @@ -1498,6 +1539,18 @@ where RetryableSendFailure::DuplicatePayment })?; + // If self-payment with preimage, claim HTLCs locally immediately. + if is_self_payment { + if let Some(preimage) = keysend_preimage { + let mut ev_completion_action = None; + for (path, session_priv_bytes) in route.paths.iter().zip(onion_session_privs.iter()) { + let sk = SecretKey::from_slice(&session_priv_bytes[..]).expect("session priv has correct length"); + self.claim_htlc(payment_id, preimage, None, sk, path.clone(), false, &mut ev_completion_action, pending_events); + } + return Ok(()); + } + } + let res = self.pay_route_internal(&route, payment_hash, &recipient_onion, keysend_preimage, None, None, payment_id, None, &onion_session_privs, false, node_signer, best_block_height, &send_payment_along_path); @@ -2829,12 +2882,15 @@ mod tests { InFlightHtlcs, Path, PaymentParameters, Route, RouteHop, RouteParameters, RouteParametersConfig, }; + use crate::sign::{NodeSigner, Recipient}; use crate::sync::{Arc, Mutex, RwLock}; use crate::types::features::{Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures}; use crate::types::payment::{PaymentHash, PaymentPreimage}; use crate::util::errors::APIError; use crate::util::hash_tables::new_hash_map; use crate::util::test_utils; + use bitcoin::hashes::sha256::Hash as Sha256; + use bitcoin::hashes::Hash; use alloc::collections::VecDeque; @@ -3503,4 +3559,43 @@ mod tests { reason: Some(PaymentFailureReason::UserAbandoned), }, None)); } + + #[test] + #[rustfmt::skip] + fn self_spontaneous_payment_fulfills() { + let pending_events = Mutex::new(VecDeque::new()); + let logger = test_utils::TestLogger::new(); + let outbound_payments = OutboundPayments::new(new_hash_map(), &logger); + let network_graph = Arc::new(NetworkGraph::new(Network::Testnet, &logger)); + let scorer = RwLock::new(test_utils::TestScorer::new()); + let router = test_utils::TestRouter::new(network_graph, &logger, &scorer); + let keys_manager = test_utils::TestKeysInterface::new(&[0; 32], Network::Testnet); + + let our_pk = keys_manager.get_node_id(Recipient::Node).unwrap(); + let payment_params = PaymentParameters::from_node_id(our_pk, 0); + let mut route_params = RouteParameters::from_payment_params_and_value(payment_params.clone(), 1000); + + let preimage = PaymentPreimage([1; 32]); + let recipient_onion = RecipientOnionFields::spontaneous_empty(); + + // Adjust `route_params` like the send path before routing + crate::ln::onion_utils::set_max_path_length(&mut route_params, &recipient_onion, Some(preimage), None, 0).unwrap(); + + router.expect_find_route(route_params.clone(), Err("Cannot generate a route to ourselves")); + + let res = outbound_payments.send_spontaneous_payment(Some(preimage), RecipientOnionFields::spontaneous_empty(), + PaymentId([0; 32]), Retry::Attempts(0), route_params.clone(), &&router, vec![], || InFlightHtlcs::new(), + &&keys_manager, &&keys_manager, 0, &pending_events, |_| Ok(())); + assert!(res.is_ok()); + + let mut events = pending_events.lock().unwrap(); + + assert!(!events.is_empty()); + + if let Event::PaymentSent { payment_preimage: p, payment_hash, .. } = events.pop_front().unwrap().0 { + assert_eq!(p.0, [1; 32]); + let expected_hash = PaymentHash(Sha256::hash(&p.0).to_byte_array()); + assert_eq!(payment_hash, expected_hash); + } else { panic!("Expected PaymentSent event"); } + } }