From e711f94dba0d3bfffac99a83a477e3f35926a9da 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 the hardcoded payment retry timeout with LDK's Retry type. Users can choose timeout-based or attempt-count retry strategies. The default remains a 10-second timeout. This change was made with the help of Claude Code. --- 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 5365f0e27d..9dda0ad944 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,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::{RouteParameters, RouteParametersConfig}; use lightning::util::config::{ @@ -36,6 +37,7 @@ pub(crate) const DEFAULT_MAX_PROBE_LOCKED_MSAT: u64 = 100_000_000; // 100k sats pub(crate) const DEFAULT_MIN_PROBE_AMOUNT_MSAT: u64 = 1_000_000; // 1k sats pub(crate) const DEFAULT_MAX_PROBE_AMOUNT_MSAT: u64 = 10_000_000; // 10k sats 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; @@ -171,9 +173,6 @@ pub const ADDRESS_POOL_SIZE: u32 = 16; // 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); @@ -255,7 +254,7 @@ impl Default for ForwardedPaymentTrackingMode { /// /// ### Defaults /// -/// | Parameter | Value | +/// | Parameter | Value | /// |----------------------------------------|--------------------------------------| /// | `storage_dir_path` | /tmp/ldk_node/ | /// | `network` | Bitcoin | @@ -265,6 +264,7 @@ impl Default for ForwardedPaymentTrackingMode { /// | `trusted_peers_0conf` | [] | /// | `probing_liquidity_limit_multiplier` | 3 | /// | `anchor_channels_config` | AnchorChannelsConfig::default() | +/// | `payment_retry_strategy` | Timeout(10s) | /// | `route_parameters` | None | /// | `tor_config` | None | #[cfg_attr( @@ -320,6 +320,12 @@ pub struct Config { /// /// Please refer to [`AnchorChannelsConfig`] for further information on Anchor channels. pub anchor_channels_config: AnchorChannelsConfig, + /// 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, @@ -365,6 +371,9 @@ impl Default for Config { trusted_peers_0conf: Vec::new(), probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER, anchor_channels_config: 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 6d39bdaa46..0f85734b42 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -31,6 +31,7 @@ use lightning::ln::channel_state::{ }; 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; @@ -1835,6 +1836,18 @@ pub struct OutboundHTLCDetails { pub is_dust: bool, } +/// 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 e9996e9cc2..ea1c121e4c 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::{apply_route_parameters_config, Config, LDK_PAYMENT_RETRY_TIMEOUT}; +use crate::config::{apply_route_parameters_config, Config}; use crate::connection::ConnectionManager; use crate::data_store::DataStoreUpdateResult; use crate::error::Error; @@ -218,7 +218,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 3fe2024e71..be6a9df5b4 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::offers::payer_proof::PaidBolt12Invoice as LdkPaidBolt12Invoice; @@ -28,7 +27,7 @@ use lightning::util::ser::{Readable, Writeable}; use lightning_types::payment::PaymentPreimage; 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}; @@ -130,7 +129,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(); @@ -308,7 +307,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(); @@ -597,7 +596,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 bc2395fec3..15081dae1d 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::{apply_route_parameters_config, Config, LDK_PAYMENT_RETRY_TIMEOUT}; +use crate::config::{apply_route_parameters_config, Config}; use crate::error::Error; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; @@ -101,7 +101,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);