@@ -114,7 +114,7 @@ use crate::onion_message::messenger::{
114114use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
115115use crate::routing::gossip::NodeId;
116116use crate::routing::router::{
117- BlindedTail, FixedRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route,
117+ BlindedTail, FixedRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route, RouteHop,
118118 RouteParameters, RouteParametersConfig, Router,
119119};
120120use crate::sign::ecdsa::EcdsaChannelSigner;
@@ -5582,6 +5582,29 @@ impl<
55825582 }
55835583 }
55845584
5585+ fn route_params_for_fixed_route(route: &mut Route) -> RouteParameters {
5586+ let params = route.route_params.clone().unwrap_or_else(|| {
5587+ let (payee_node_id, cltv_delta) = route
5588+ .paths
5589+ .first()
5590+ .and_then(|path| {
5591+ path.hops.last().map(|hop| (hop.pubkey, hop.cltv_expiry_delta as u32))
5592+ })
5593+ .unwrap_or_else(|| {
5594+ (PublicKey::from_slice(&[2; 32]).unwrap(), MIN_FINAL_CLTV_EXPIRY_DELTA as u32)
5595+ });
5596+ let dummy_payment_params = PaymentParameters::from_node_id(payee_node_id, cltv_delta);
5597+ RouteParameters::from_payment_params_and_value(
5598+ dummy_payment_params,
5599+ route.get_total_amount(),
5600+ )
5601+ });
5602+ if route.route_params.is_none() {
5603+ route.route_params = Some(params.clone());
5604+ }
5605+ params
5606+ }
5607+
55855608 /// Sends a payment along a given route. See [`Self::send_payment`] for more info.
55865609 ///
55875610 /// LDK will not automatically retry this payment, though it may be manually re-sent after an
@@ -5593,21 +5616,33 @@ impl<
55935616 ) -> Result<(), RetryableSendFailure> {
55945617 let best_block_height = self.best_block.read().unwrap().height;
55955618 let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5596- let route_params = route.route_params.clone().unwrap_or_else(|| {
5597- // Create a dummy route params since they're a required parameter but unused in this case
5598- let (payee_node_id, cltv_delta) = route.paths.first()
5599- .and_then(|path| path.hops.last().map(|hop| (hop.pubkey, hop.cltv_expiry_delta as u32)))
5600- .unwrap_or_else(|| (PublicKey::from_slice(&[2; 32]).unwrap(), MIN_FINAL_CLTV_EXPIRY_DELTA as u32));
5601- let dummy_payment_params = PaymentParameters::from_node_id(payee_node_id, cltv_delta);
5602- RouteParameters::from_payment_params_and_value(dummy_payment_params, route.get_total_amount())
5603- });
5604- if route.route_params.is_none() { route.route_params = Some(route_params.clone()); }
5619+ let route_params = Self::route_params_for_fixed_route(&mut route);
56055620 let router = FixedRouter::new(route);
56065621 let logger =
56075622 WithContext::for_payment(&self.logger, None, None, Some(payment_hash), payment_id);
56085623 self.pending_outbound_payments
56095624 .send_payment(payment_hash, recipient_onion, payment_id, Retry::Attempts(0),
5610- route_params, &&router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
5625+ route_params, &router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
5626+ &self.entropy_source, &self.node_signer, best_block_height,
5627+ &self.pending_events, |args| self.send_payment_along_path(args), &logger)
5628+ }
5629+
5630+ /// Sends a spontaneous payment along a given route.
5631+ #[rustfmt::skip]
5632+ pub fn send_spontaneous_payment_with_route(
5633+ &self, mut route: Route, payment_preimage: Option<PaymentPreimage>,
5634+ recipient_onion: RecipientOnionFields, payment_id: PaymentId
5635+ ) -> Result<PaymentHash, RetryableSendFailure> {
5636+ let best_block_height = self.best_block.read().unwrap().height;
5637+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5638+ let route_params = Self::route_params_for_fixed_route(&mut route);
5639+ let router = FixedRouter::new(route);
5640+ let payment_hash = payment_preimage.map(|preimage| preimage.into());
5641+ let logger =
5642+ WithContext::for_payment(&self.logger, None, None, payment_hash, payment_id);
5643+ self.pending_outbound_payments
5644+ .send_spontaneous_payment(payment_preimage, recipient_onion, payment_id, Retry::Attempts(0),
5645+ route_params, &router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
56115646 &self.entropy_source, &self.node_signer, best_block_height,
56125647 &self.pending_events, |args| self.send_payment_along_path(args), &logger)
56135648 }
@@ -6115,6 +6150,96 @@ impl<
61156150 )
61166151 }
61176152
6153+ /// Performs a circular rebalancing payment: funds exit our node over `outbound_channel_id`,
6154+ /// traverse the Lightning Network, and re-enter our node through `inbound_channel_id`.
6155+ ///
6156+ /// This is a convenient helper for moving liquidity between two of our channels without
6157+ /// requiring a counterparty invoice. It is equivalent to constructing an appropriate circular
6158+ /// [`Route`] and sending a spontaneous (keysend) payment over it.
6159+ ///
6160+ /// # How it works
6161+ ///
6162+ /// The router finds a path from our node to the `inbound_channel_id`'s counterparty, forced to
6163+ /// start with `outbound_channel_id`. We then manually append a final hop back to ourselves over
6164+ /// the `inbound_channel_id`. The route is sent as a spontaneous payment.
6165+ ///
6166+ /// # Limitations
6167+ ///
6168+ /// - Only single-path routing (no MPP support) is currently available.
6169+ /// - The payment is not recorded by the `Scorer`.
6170+ ///
6171+ /// # Errors
6172+ ///
6173+ /// Returns [`RetryableSendFailure::RouteNotFound`] if channel validation fails or no route can be
6174+ /// found. Payment-level errors (e.g. HTLC failures mid-flight) are reported asynchronously
6175+ /// via [`Event::PaymentFailed`].
6176+ ///
6177+ /// [`Route`]: crate::routing::router::Route
6178+ /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
6179+ pub fn send_circular_payment(
6180+ &self, outbound_channel_id: ChannelId, inbound_channel_id: ChannelId, amount_msat: u64,
6181+ payment_id: PaymentId,
6182+ ) -> Result<PaymentHash, RetryableSendFailure> {
6183+ if outbound_channel_id == inbound_channel_id {
6184+ return Err(RetryableSendFailure::RouteNotFound);
6185+ }
6186+
6187+ let usable_channels = self.list_usable_channels();
6188+ let out_chan = usable_channels
6189+ .iter()
6190+ .find(|c| c.channel_id == outbound_channel_id)
6191+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6192+
6193+ let in_chan = usable_channels
6194+ .iter()
6195+ .find(|c| c.channel_id == inbound_channel_id)
6196+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6197+
6198+ let our_node_id = self.get_our_node_id();
6199+ let forwarding_info = in_chan
6200+ .counterparty
6201+ .forwarding_info
6202+ .as_ref()
6203+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6204+ let forwarding_fee = forwarding_info.fee_base_msat as u64
6205+ + (forwarding_info.fee_proportional_millionths as u64 * amount_msat) / 1000000;
6206+ let cltv_expiry_delta = forwarding_info.cltv_expiry_delta as u32;
6207+
6208+ let route_params = RouteParameters::from_payment_params_and_value(
6209+ PaymentParameters::from_node_id(in_chan.counterparty.node_id, cltv_expiry_delta),
6210+ amount_msat + forwarding_fee,
6211+ );
6212+
6213+ let first_hops: [&ChannelDetails; 1] = [out_chan];
6214+ let inflight_htlcs = self.compute_inflight_htlcs();
6215+ let mut route = self
6216+ .router
6217+ .find_route(&our_node_id, &route_params, Some(&first_hops), inflight_htlcs)
6218+ .map_err(|_| RetryableSendFailure::RouteNotFound)?;
6219+ let inbound_scid =
6220+ in_chan.get_inbound_payment_scid().ok_or(RetryableSendFailure::RouteNotFound)?;
6221+ let last_hop = RouteHop {
6222+ pubkey: our_node_id,
6223+ node_features: NodeFeatures::empty(),
6224+ short_channel_id: inbound_scid,
6225+ channel_features: ChannelFeatures::empty(),
6226+ fee_msat: amount_msat,
6227+ cltv_expiry_delta: MIN_FINAL_CLTV_EXPIRY_DELTA as u32,
6228+ maybe_announced_channel: in_chan.is_announced,
6229+ };
6230+ for path in route.paths.iter_mut() {
6231+ if let Some(prev_last) = path.hops.last_mut() {
6232+ prev_last.fee_msat = forwarding_fee;
6233+ prev_last.cltv_expiry_delta = cltv_expiry_delta;
6234+ }
6235+ path.hops.push(last_hop.clone());
6236+ }
6237+ let preimage = PaymentPreimage(self.entropy_source.get_secure_random_bytes());
6238+ let onion = RecipientOnionFields::spontaneous_empty(amount_msat);
6239+ route.route_params = None;
6240+ self.send_spontaneous_payment_with_route(route, Some(preimage), onion, payment_id)
6241+ }
6242+
61186243 /// Send a payment that is probing the given route for liquidity. We calculate the
61196244 /// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
61206245 /// us to easily discern them from real payments.
@@ -9773,7 +9898,8 @@ impl<
97739898 ComplFunc: FnOnce(
97749899 Option<u64>,
97759900 bool,
9776- ) -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
9901+ )
9902+ -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
97779903 >(
97789904 &self, prev_hop: &HTLCPreviousHopData, payment_preimage: PaymentPreimage,
97799905 payment_info: Option<PaymentClaimDetails>, attribution_data: Option<AttributionData>,
@@ -9811,7 +9937,8 @@ impl<
98119937 ComplFunc: FnOnce(
98129938 Option<u64>,
98139939 bool,
9814- ) -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
9940+ )
9941+ -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
98159942 >(
98169943 &self, prev_hop: HTLCClaimSource, payment_preimage: PaymentPreimage,
98179944 payment_info: Option<PaymentClaimDetails>, attribution_data: Option<AttributionData>,
0 commit comments