From f35a839d7fe460ef967040a7e2d4ebdde0c6b6a8 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Thu, 12 Mar 2026 13:05:33 -0400 Subject: [PATCH] Expose full payment retry strategy via Config Replace payment_retry_timeout_secs with LDK's Retry type. Users can choose timeout-based or attempt-count retry strategies. The default remains a 10-second timeout. Co-authored-by: Claude Opus --- src/config.rs | 17 +++++++++++++---- src/ffi/types.rs | 13 +++++++++++++ src/payment/bolt11.rs | 6 +++--- src/payment/bolt12.rs | 9 ++++----- src/payment/spontaneous.rs | 6 +++--- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/config.rs b/src/config.rs index ad1b91181..c10c58ae0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,7 @@ use std::time::Duration; use bitcoin::secp256k1::PublicKey; use bitcoin::Network; use lightning::ln::msgs::SocketAddress; +use lightning::ln::outbound_payment::Retry; use lightning::routing::gossip::NodeAlias; use lightning::routing::router::RouteParametersConfig; use lightning::util::config::{ @@ -29,6 +30,7 @@ const DEFAULT_LDK_WALLET_SYNC_INTERVAL_SECS: u64 = 30; const DEFAULT_FEE_RATE_CACHE_UPDATE_INTERVAL_SECS: u64 = 60 * 10; const DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER: u64 = 3; const DEFAULT_ANCHOR_PER_CHANNEL_RESERVE_SATS: u64 = 25_000; +const DEFAULT_PAYMENT_RETRY_TIMEOUT_SECS: u64 = 10; // The default timeout after which we abort a wallet syncing operation. const DEFAULT_BDK_WALLET_SYNC_TIMEOUT_SECS: u64 = 60; @@ -64,9 +66,6 @@ pub(crate) const BDK_CLIENT_STOP_GAP: usize = 20; // The number of concurrent requests made against the API provider. pub(crate) const BDK_CLIENT_CONCURRENCY: usize = 4; -// The timeout after which we abandon retrying failed payments. -pub(crate) const LDK_PAYMENT_RETRY_TIMEOUT: Duration = Duration::from_secs(10); - // The time in-between peer reconnection attempts. pub(crate) const PEER_RECONNECTION_INTERVAL: Duration = Duration::from_secs(60); @@ -117,7 +116,7 @@ pub(crate) const LNURL_AUTH_TIMEOUT_SECS: u64 = 15; /// /// ### Defaults /// -/// | Parameter | Value | +/// | Parameter | Value | /// |----------------------------------------|--------------------------------------| /// | `storage_dir_path` | /tmp/ldk_node/ | /// | `network` | Bitcoin | @@ -127,6 +126,7 @@ pub(crate) const LNURL_AUTH_TIMEOUT_SECS: u64 = 15; /// | `trusted_peers_0conf` | [] | /// | `probing_liquidity_limit_multiplier` | 3 | /// | `anchor_channels_config` | Some(..) | +/// | `payment_retry_strategy` | Timeout(10s) | /// | `route_parameters` | None | /// | `tor_config` | None | /// | `hrn_config` | HumanReadableNamesConfig::default() | @@ -186,6 +186,12 @@ pub struct Config { /// closure. We *will* however still try to get the Anchor spending transactions confirmed /// on-chain with the funds available. pub anchor_channels_config: Option, + /// The strategy used when retrying failed payments. + /// + /// When a payment fails to route, LDK will automatically retry according to this strategy. + /// + /// See [`Retry`] for available options. + pub payment_retry_strategy: Retry, /// Configuration options for payment routing and pathfinding. /// /// Setting the [`RouteParametersConfig`] provides flexibility to customize how payments are routed, @@ -217,6 +223,9 @@ impl Default for Config { trusted_peers_0conf: Vec::new(), probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER, anchor_channels_config: Some(AnchorChannelsConfig::default()), + payment_retry_strategy: Retry::Timeout(Duration::from_secs( + DEFAULT_PAYMENT_RETRY_TIMEOUT_SECS, + )), tor_config: None, route_parameters: None, node_alias: None, diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 9bb03bb07..b81382d5a 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -28,6 +28,7 @@ pub use lightning::events::{ClosureReason, PaymentFailureReason}; use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo}; use lightning::ln::channelmanager::PaymentId; use lightning::ln::msgs::DecodeError; +pub use lightning::ln::outbound_payment::Retry; pub use lightning::ln::types::ChannelId; use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice; pub use lightning::offers::offer::OfferId; @@ -1439,6 +1440,18 @@ pub enum ChannelShutdownState { ShutdownComplete, } +/// Strategies available to retry payment path failures. +/// +/// See [`lightning::ln::outbound_payment::Retry`] for more details. +#[uniffi::remote(Enum)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Retry { + /// Max number of attempts to retry payment. + Attempts(u32), + /// Time elapsed before abandoning retries for a payment. + Timeout(Duration), +} + /// The reason the channel was closed. See individual variants for more details. #[uniffi::remote(Enum)] #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 4503dfa06..a42ed6113 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -17,14 +17,14 @@ use lightning::impl_writeable_tlv_based; use lightning::ln::channelmanager::{ Bolt11InvoiceParameters, OptionalBolt11PaymentParams, PaymentId, }; -use lightning::ln::outbound_payment::{Bolt11PaymentError, Retry, RetryableSendFailure}; +use lightning::ln::outbound_payment::{Bolt11PaymentError, RetryableSendFailure}; use lightning::routing::router::{PaymentParameters, RouteParameters, RouteParametersConfig}; use lightning_invoice::{ Bolt11Invoice as LdkBolt11Invoice, Bolt11InvoiceDescription as LdkBolt11InvoiceDescription, }; use lightning_types::payment::{PaymentHash, PaymentPreimage}; -use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; +use crate::config::Config; use crate::connection::ConnectionManager; use crate::data_store::DataStoreUpdateResult; use crate::error::Error; @@ -287,7 +287,7 @@ impl Bolt11Payment { let route_params_config = route_parameters.or(self.config.route_parameters).unwrap_or_default(); - let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + let retry_strategy = self.config.payment_retry_strategy; let payment_secret = Some(*invoice.payment_secret()); let payment_amount_msat = match amount_msat.or_else(|| invoice.amount_milli_satoshis()) { Some(amount_msat) => amount_msat, diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index d79aca6c2..64ce38ee5 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -15,7 +15,6 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use lightning::blinded_path::message::BlindedMessagePath; use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId}; -use lightning::ln::outbound_payment::Retry; use lightning::offers::offer::{Amount, Offer as LdkOffer, OfferFromHrn, Quantity}; use lightning::offers::parse::Bolt12SemanticError; use lightning::routing::router::RouteParametersConfig; @@ -24,7 +23,7 @@ use lightning::sign::EntropySource; use lightning::util::ser::{Readable, Writeable}; use lightning_types::string::UntrustedString; -use crate::config::{AsyncPaymentsRole, Config, LDK_PAYMENT_RETRY_TIMEOUT}; +use crate::config::{AsyncPaymentsRole, Config}; use crate::error::Error; use crate::ffi::{maybe_deref, maybe_wrap}; use crate::logger::{log_error, log_info, LdkLogger, Logger}; @@ -100,7 +99,7 @@ impl Bolt12Payment { let offer = maybe_deref(offer); let payment_id = PaymentId(self.keys_manager.get_secure_random_bytes()); - let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + let retry_strategy = self.config.payment_retry_strategy; let route_parameters = route_parameters.or(self.config.route_parameters).unwrap_or_default(); @@ -275,7 +274,7 @@ impl Bolt12Payment { let offer = maybe_deref(offer); let payment_id = PaymentId(self.keys_manager.get_secure_random_bytes()); - let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + let retry_strategy = self.config.payment_retry_strategy; let route_parameters = route_parameters.or(self.config.route_parameters).unwrap_or_default(); @@ -481,7 +480,7 @@ impl Bolt12Payment { let absolute_expiry = (SystemTime::now() + Duration::from_secs(expiry_secs as u64)) .duration_since(UNIX_EPOCH) .expect("system time must be after Unix epoch"); - let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); + let retry_strategy = self.config.payment_retry_strategy; let route_parameters = route_parameters.or(self.config.route_parameters).unwrap_or_default(); diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 45dab644d..3a3e89f05 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -12,13 +12,13 @@ use std::sync::{Arc, RwLock}; use bitcoin::secp256k1::PublicKey; use lightning::ln::channelmanager::PaymentId; use lightning::ln::outbound_payment::{ - RecipientCustomTlvs, RecipientOnionFields, Retry, RetryableSendFailure, + RecipientCustomTlvs, RecipientOnionFields, RetryableSendFailure, }; use lightning::routing::router::{PaymentParameters, RouteParameters, RouteParametersConfig}; use lightning::sign::EntropySource; use lightning_types::payment::{PaymentHash, PaymentPreimage}; -use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; +use crate::config::Config; use crate::error::Error; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; @@ -115,7 +115,7 @@ impl SpontaneousPayment { recipient_fields, PaymentId(payment_hash.0), route_params, - Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT), + self.config.payment_retry_strategy, ) { Ok(_hash) => { log_info!(self.logger, "Initiated sending {}msat to {}.", amount_msat, node_id);