@@ -115,7 +115,7 @@ use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
115115use crate::routing::gossip::NodeId;
116116use crate::routing::router::{
117117 BlindedTail, FixedRouter, InFlightHtlcs, Path, Payee, PaymentParameters, Route,
118- RouteParameters, RouteParametersConfig, Router,
118+ RouteHop, RouteParameters, RouteParametersConfig, Router,
119119};
120120use crate::sign::ecdsa::EcdsaChannelSigner;
121121use crate::sign::{EntropySource, NodeSigner, Recipient, SignerProvider};
@@ -5612,6 +5612,33 @@ impl<
56125612 &self.pending_events, |args| self.send_payment_along_path(args), &logger)
56135613 }
56145614
5615+ /// Sends a spontaneous payment along a given route.
5616+ #[rustfmt::skip]
5617+ pub fn send_spontaneous_payment_with_route(
5618+ &self, mut route: Route, payment_preimage: Option<PaymentPreimage>,
5619+ recipient_onion: RecipientOnionFields, payment_id: PaymentId
5620+ ) -> Result<PaymentHash, RetryableSendFailure> {
5621+ let best_block_height = self.best_block.read().unwrap().height;
5622+ let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5623+ let route_params = route.route_params.clone().unwrap_or_else(|| {
5624+ let (payee_node_id, cltv_delta) = route.paths.first()
5625+ .and_then(|path| path.hops.last().map(|hop| (hop.pubkey, hop.cltv_expiry_delta as u32)))
5626+ .unwrap_or_else(|| (PublicKey::from_slice(&[2; 32]).unwrap(), MIN_FINAL_CLTV_EXPIRY_DELTA as u32));
5627+ let dummy_payment_params = PaymentParameters::from_node_id(payee_node_id, cltv_delta);
5628+ RouteParameters::from_payment_params_and_value(dummy_payment_params, route.get_total_amount())
5629+ });
5630+ if route.route_params.is_none() { route.route_params = Some(route_params.clone()); }
5631+ let router = FixedRouter::new(route);
5632+ let payment_hash = payment_preimage.map(|preimage| preimage.into());
5633+ let logger =
5634+ WithContext::for_payment(&self.logger, None, None, payment_hash, payment_id);
5635+ self.pending_outbound_payments
5636+ .send_spontaneous_payment(payment_preimage, recipient_onion, payment_id, Retry::Attempts(0),
5637+ route_params, &&router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
5638+ &self.entropy_source, &self.node_signer, best_block_height,
5639+ &self.pending_events, |args| self.send_payment_along_path(args), &logger)
5640+ }
5641+
56155642 /// Sends a payment to the route found using the provided [`RouteParameters`], retrying failed
56165643 /// payment paths based on the provided `Retry`.
56175644 ///
@@ -6115,6 +6142,93 @@ impl<
61156142 )
61166143 }
61176144
6145+ /// Performs a circular rebalancing payment: funds exit our node over `outbound_channel_id`,
6146+ /// traverse the Lightning Network, and re-enter our node through `inbound_channel_id`.
6147+ ///
6148+ /// This is a convenient helper for moving liquidity between two of our channels without
6149+ /// requiring a counterparty invoice. It is equivalent to constructing an appropriate circular
6150+ /// [`Route`] and sending a spontaneous (keysend) payment over it.
6151+ ///
6152+ /// # How it works
6153+ ///
6154+ /// The router finds a path from our node to the `inbound_channel_id`'s counterparty, forced to
6155+ /// start with `outbound_channel_id`. We then manually append a final hop back to ourselves over
6156+ /// the `inbound_channel_id`. The route is sent as a spontaneous payment.
6157+ ///
6158+ /// # Limitations
6159+ ///
6160+ /// - Only single-path routing (no MPP support) is currently available.
6161+ /// - The payment is not recorded by the `Scorer`.
6162+ ///
6163+ /// # Errors
6164+ ///
6165+ /// Returns [`RetryableSendFailure::RouteNotFound`] if channel validation fails or no route can be
6166+ /// found. Payment-level errors (e.g. HTLC failures mid-flight) are reported asynchronously
6167+ /// via [`Event::PaymentFailed`].
6168+ ///
6169+ /// [`Route`]: crate::routing::router::Route
6170+ /// [`Event::PaymentFailed`]: crate::events::Event::PaymentFailed
6171+ pub fn send_circular_payment(
6172+ &self, outbound_channel_id: ChannelId, inbound_channel_id: ChannelId, amount_msat: u64,
6173+ payment_id: PaymentId,
6174+ ) -> Result<PaymentHash, RetryableSendFailure> {
6175+ if outbound_channel_id == inbound_channel_id {
6176+ return Err(RetryableSendFailure::RouteNotFound);
6177+ }
6178+
6179+ let usable_channels = self.list_usable_channels();
6180+ let out_chan = usable_channels
6181+ .iter()
6182+ .find(|c| c.channel_id == outbound_channel_id)
6183+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6184+
6185+ let in_chan = usable_channels
6186+ .iter()
6187+ .find(|c| c.channel_id == inbound_channel_id)
6188+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6189+
6190+ let our_node_id = self.get_our_node_id();
6191+ let forwarding_fee = in_chan.counterparty.forwarding_info.as_ref().map_or(0, |info| {
6192+ info.fee_base_msat as u64
6193+ + (info.fee_proportional_millionths as u64 * amount_msat) / 1000000
6194+ });
6195+
6196+ let route_params = RouteParameters::from_payment_params_and_value(
6197+ PaymentParameters::from_node_id(
6198+ in_chan.counterparty.node_id,
6199+ MIN_FINAL_CLTV_EXPIRY_DELTA as u32,
6200+ ),
6201+ amount_msat + forwarding_fee,
6202+ );
6203+
6204+ let first_hops: [&ChannelDetails; 1] = [out_chan];
6205+ let inflight_htlcs = self.compute_inflight_htlcs();
6206+ let mut route = self.router
6207+ .find_route(&our_node_id, &route_params, Some(&first_hops), inflight_htlcs)
6208+ .map_err(|_| RetryableSendFailure::RouteNotFound)?;
6209+ let inbound_scid = in_chan.get_inbound_payment_scid()
6210+ .ok_or(RetryableSendFailure::RouteNotFound)?;
6211+ let last_hop = RouteHop {
6212+ pubkey: our_node_id,
6213+ node_features: NodeFeatures::empty(),
6214+ short_channel_id: inbound_scid,
6215+ channel_features: ChannelFeatures::empty(),
6216+ fee_msat: amount_msat,
6217+ cltv_expiry_delta: MIN_FINAL_CLTV_EXPIRY_DELTA as u32,
6218+ maybe_announced_channel: in_chan.is_announced,
6219+ };
6220+ for path in route.paths.iter_mut() {
6221+ if let Some(prev_last) = path.hops.last_mut() {
6222+ prev_last.fee_msat = forwarding_fee;
6223+ }
6224+ path.hops.push(last_hop.clone());
6225+ }
6226+ let preimage = PaymentPreimage(self.entropy_source.get_secure_random_bytes());
6227+ let onion = RecipientOnionFields::spontaneous_empty(amount_msat);
6228+ route.route_params = None;
6229+ self.send_spontaneous_payment_with_route(route, Some(preimage), onion, payment_id)
6230+ }
6231+
61186232 /// Send a payment that is probing the given route for liquidity. We calculate the
61196233 /// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
61206234 /// us to easily discern them from real payments.
0 commit comments