From 7b09666f2f94be8d44f661ab036aaa5f6bd96ad8 Mon Sep 17 00:00:00 2001 From: amackillop Date: Wed, 13 May 2026 11:13:41 -0700 Subject: [PATCH] Add Node::find_route accessor Public wrapper around the previously private `_router` so callers can resolve a route to a destination without dispatching a payment. The intended consumer is mdk's max-withdrawable estimator, which needs a real route to invert per-hop fees against payout destinations. The signature takes `Option`. With `None` the node-wide `Config::route_parameters` applies, matching the fallback already used by `SpontaneousPayment::send_inner`. Keeps both call sites consistent. `Error::RouteNotFound` is a new variant. This path performs lookup only and never dispatches anything, and a missing route under current liquidity is a routine outcome. The router's `&'static str` reason is logged at debug level and dropped; callers already get the typed error. `Route` and `RouteParametersConfig` are re-exported from `lib.rs` for Rust consumers. No UDL bindings in this commit: the `uniffi` build is currently broken on this branch from drift left over by the rust-lightning upgrade in e935695, so adding more UDL surface would fly blind. A follow-up that fixes the existing drift can layer `Route` bindings on top. The returned `Route` may contain multiple `Path` entries when `max_path_count > 1`, since the router is free to split the amount across MPP paths. Callers wanting a single-path response should set `max_path_count: 1` in the supplied config. --- src/error.rs | 3 ++ src/lib.rs | 57 +++++++++++++++++++++++++++++++++++++- src/payment/mod.rs | 1 + src/payment/spontaneous.rs | 2 +- 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 20b1cceab1..0262ad0be3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -39,6 +39,8 @@ pub enum Error { InvalidCustomTlvs, /// Sending a payment probe has failed. ProbeSendingFailed, + /// A route to the given destination could not be found. + RouteNotFound, /// A channel could not be opened. ChannelCreationFailed, /// A channel could not be closed. @@ -145,6 +147,7 @@ impl fmt::Display for Error { Self::PaymentSendingFailed => write!(f, "Failed to send the given payment."), Self::InvalidCustomTlvs => write!(f, "Failed to construct payment with custom TLVs."), Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."), + Self::RouteNotFound => write!(f, "Failed to find a route to the destination."), Self::ChannelCreationFailed => write!(f, "Failed to create channel."), Self::ChannelClosingFailed => write!(f, "Failed to close channel."), Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."), diff --git a/src/lib.rs b/src/lib.rs index bfaec913c2..a1183c1bb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,6 +144,8 @@ use lightning::ln::channelmanager::PaymentId; use lightning::ln::funding::SpliceContribution; use lightning::ln::msgs::SocketAddress; use lightning::routing::gossip::NodeAlias; +use lightning::routing::router::{PaymentParameters, RouteParameters}; +pub use lightning::routing::router::{Route, RouteParametersConfig}; use lightning::util::persist::KVStoreSync; use lightning_background_processor::process_events_async; use liquidity::{LSPS1Liquidity, LiquiditySource}; @@ -152,7 +154,7 @@ use payment::asynchronous::om_mailbox::OnionMessageMailbox; use payment::asynchronous::static_invoice_store::StaticInvoiceStore; use payment::{ Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment, - UnifiedQrPayment, + UnifiedQrPayment, LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA, }; use peer_store::{PeerInfo, PeerStore}; use rand::Rng; @@ -1888,6 +1890,59 @@ impl Node { .to_sat_per_kwu() } + /// Finds a route to `payee` for `amount_msat` using the node's internal router. + /// + /// Intended for callers that need to introspect routing fees ahead of + /// time (e.g. computing a max-sendable estimate by inverting per-hop fees). + /// + /// `params` overrides the node-wide [`RouteParametersConfig`] when `Some`; otherwise + /// the value from [`Config::route_parameters`] is used. + /// + /// [`Config::route_parameters`]: crate::config::Config::route_parameters + pub fn find_route( + &self, payee: PublicKey, amount_msat: u64, params: Option, + ) -> Result { + if !*self.is_running.read().unwrap() { + return Err(Error::NotRunning); + } + + let mut route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::from_node_id(payee, LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA), + amount_msat, + ); + + if let Some(RouteParametersConfig { + max_total_routing_fee_msat, + max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + }) = params.as_ref().or(self.config.route_parameters.as_ref()) + { + route_params.max_total_routing_fee_msat = *max_total_routing_fee_msat; + route_params.payment_params.max_total_cltv_expiry_delta = *max_total_cltv_expiry_delta; + route_params.payment_params.max_path_count = *max_path_count; + route_params.payment_params.max_channel_saturation_power_of_half = + *max_channel_saturation_power_of_half; + } + + let payer = self.channel_manager.get_our_node_id(); + let first_hops = self.channel_manager.list_usable_channels(); + let first_hops_refs: Vec<&LdkChannelDetails> = first_hops.iter().collect(); + let inflight_htlcs = self.channel_manager.compute_inflight_htlcs(); + + lightning::routing::router::Router::find_route( + &*self._router, + &payer, + &route_params, + Some(&first_hops_refs), + inflight_htlcs, + ) + .map_err(|e| { + log_debug!(self.logger, "Failed to find route to {}: {}", payee, e); + Error::RouteNotFound + }) + } + /// Exports the current state of the scorer. The result can be shared with and merged by light nodes that only have /// a limited view of the network. pub fn export_pathfinding_scores(&self) -> Result, Error> { diff --git a/src/payment/mod.rs b/src/payment/mod.rs index f629960e1d..23698ec7d3 100644 --- a/src/payment/mod.rs +++ b/src/payment/mod.rs @@ -19,6 +19,7 @@ pub use bolt11::Bolt11Payment; pub use bolt12::Bolt12Payment; pub use onchain::OnchainPayment; pub use spontaneous::SpontaneousPayment; +pub(crate) use spontaneous::LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA; pub use store::{ ConfirmationStatus, LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, }; diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 6c074f308f..3672236ced 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -22,7 +22,7 @@ use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, Payme use crate::types::{ChannelManager, CustomTlvRecord, KeysManager, PaymentStore}; // The default `final_cltv_expiry_delta` we apply when not set. -const LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA: u32 = 144; +pub(crate) const LDK_DEFAULT_FINAL_CLTV_EXPIRY_DELTA: u32 = 144; /// A payment handler allowing to send spontaneous ("keysend") payments. ///