@@ -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; 33]).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,25 +5616,51 @@ 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(),
56115626 &self.entropy_source, &self.node_signer, best_block_height,
56125627 &self.pending_events, |args| self.send_payment_along_path(args), &logger)
56135628 }
56145629
5630+ /// Sends a spontaneous payment along a given route. See
5631+ /// [`Self::send_spontaneous_payment`] for more info.
5632+ ///
5633+ /// LDK will not automatically retry this payment, though it may be manually
5634+ /// re-sent after an
5635+ /// [`Event::PaymentFailed`] is generated.
5636+ pub fn send_spontaneous_payment_with_route(
5637+ &self, mut route: Route, payment_preimage: Option<PaymentPreimage>,
5638+ recipient_onion: RecipientOnionFields, payment_id: PaymentId,
5639+ ) -> Result<PaymentHash, RetryableSendFailure> {
5640+ let best_block_height = self.best_block.read().unwrap().height;
5641+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5642+ let route_params = Self::route_params_for_fixed_route(&mut route);
5643+ let router = FixedRouter::new(route);
5644+ let payment_hash = payment_preimage.map(|preimage| preimage.into());
5645+ let logger = WithContext::for_payment(&self.logger, None, None, payment_hash, payment_id);
5646+ self.pending_outbound_payments.send_spontaneous_payment(
5647+ payment_preimage,
5648+ recipient_onion,
5649+ payment_id,
5650+ Retry::Attempts(0),
5651+ route_params,
5652+ &router,
5653+ self.list_usable_channels(),
5654+ || self.compute_inflight_htlcs(),
5655+ &self.entropy_source,
5656+ &self.node_signer,
5657+ best_block_height,
5658+ &self.pending_events,
5659+ |args| self.send_payment_along_path(args),
5660+ &logger,
5661+ )
5662+ }
5663+
56155664 /// Sends a payment to the route found using the provided [`RouteParameters`], retrying failed
56165665 /// payment paths based on the provided `Retry`.
56175666 ///
@@ -6115,6 +6164,96 @@ impl<
61156164 )
61166165 }
61176166
6167+ /// Performs a circular rebalancing payment: funds exit our node over `outbound_channel_id`,
6168+ /// traverse the Lightning Network, and re-enter our node through `inbound_channel_id`.
6169+ ///
6170+ /// This is a convenient helper for moving liquidity between two of our channels without
6171+ /// requiring a counterparty invoice. It is equivalent to constructing an appropriate circular
6172+ /// [`Route`] and sending a spontaneous (keysend) payment over it.
6173+ ///
6174+ /// # How it works
6175+ ///
6176+ /// The router finds a path from our node to the `inbound_channel_id`'s counterparty, forced to
6177+ /// start with `outbound_channel_id`. We then manually append a final hop back to ourselves over
6178+ /// the `inbound_channel_id`. The route is sent as a spontaneous payment.
6179+ ///
6180+ /// # Limitations
6181+ ///
6182+ /// - Only single-path routing (no MPP support) is currently available.
6183+ /// - The payment is not recorded by the `Scorer`.
6184+ ///
6185+ /// # Errors
6186+ ///
6187+ /// Returns [`RetryableSendFailure::RouteNotFound`] if channel validation fails or no route can be
6188+ /// found. Payment-level errors (e.g. HTLC failures mid-flight) are reported asynchronously
6189+ /// via [`Event::PaymentFailed`].
6190+ ///
6191+ /// [`Route`]: crate::routing::router::Route
6192+ /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
6193+ pub fn send_circular_payment(
6194+ &self, outbound_channel_id: ChannelId, inbound_channel_id: ChannelId, amount_msat: u64,
6195+ payment_id: PaymentId,
6196+ ) -> Result<PaymentHash, RetryableSendFailure> {
6197+ if outbound_channel_id == inbound_channel_id {
6198+ return Err(RetryableSendFailure::RouteNotFound);
6199+ }
6200+
6201+ let usable_channels = self.list_usable_channels();
6202+ let out_chan = usable_channels
6203+ .iter()
6204+ .find(|c| c.channel_id == outbound_channel_id)
6205+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6206+
6207+ let in_chan = usable_channels
6208+ .iter()
6209+ .find(|c| c.channel_id == inbound_channel_id)
6210+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6211+
6212+ let our_node_id = self.get_our_node_id();
6213+ let forwarding_info = in_chan
6214+ .counterparty
6215+ .forwarding_info
6216+ .as_ref()
6217+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6218+ let forwarding_fee = forwarding_info.fee_base_msat as u64
6219+ + (forwarding_info.fee_proportional_millionths as u64 * amount_msat) / 1000000;
6220+ let cltv_expiry_delta = forwarding_info.cltv_expiry_delta as u32;
6221+
6222+ let route_params = RouteParameters::from_payment_params_and_value(
6223+ PaymentParameters::from_node_id(in_chan.counterparty.node_id, cltv_expiry_delta),
6224+ amount_msat + forwarding_fee,
6225+ );
6226+
6227+ let first_hops: [&ChannelDetails; 1] = [out_chan];
6228+ let inflight_htlcs = self.compute_inflight_htlcs();
6229+ let mut route = self
6230+ .router
6231+ .find_route(&our_node_id, &route_params, Some(&first_hops), inflight_htlcs)
6232+ .map_err(|_| RetryableSendFailure::RouteNotFound)?;
6233+ let inbound_scid =
6234+ in_chan.get_inbound_payment_scid().ok_or(RetryableSendFailure::RouteNotFound)?;
6235+ let last_hop = RouteHop {
6236+ pubkey: our_node_id,
6237+ node_features: NodeFeatures::empty(),
6238+ short_channel_id: inbound_scid,
6239+ channel_features: ChannelFeatures::empty(),
6240+ fee_msat: amount_msat,
6241+ cltv_expiry_delta: MIN_FINAL_CLTV_EXPIRY_DELTA as u32,
6242+ maybe_announced_channel: in_chan.is_announced,
6243+ };
6244+ for path in route.paths.iter_mut() {
6245+ if let Some(prev_last) = path.hops.last_mut() {
6246+ prev_last.fee_msat = forwarding_fee;
6247+ prev_last.cltv_expiry_delta = cltv_expiry_delta;
6248+ }
6249+ path.hops.push(last_hop.clone());
6250+ }
6251+ let preimage = PaymentPreimage(self.entropy_source.get_secure_random_bytes());
6252+ let onion = RecipientOnionFields::spontaneous_empty(amount_msat);
6253+ route.route_params = None;
6254+ self.send_spontaneous_payment_with_route(route, Some(preimage), onion, payment_id)
6255+ }
6256+
61186257 /// Send a payment that is probing the given route for liquidity. We calculate the
61196258 /// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
61206259 /// us to easily discern them from real payments.
0 commit comments