Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 180 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use std::time::Duration;

use bitcoin::secp256k1::PublicKey;
use bitcoin::Network;
use lightning::ln::channelmanager::{OptionalBolt11PaymentParams, OptionalOfferPaymentParams};
use lightning::ln::msgs::SocketAddress;
use lightning::ln::outbound_payment::{RecipientCustomTlvs, Retry};
use lightning::routing::gossip::NodeAlias;
use lightning::routing::router::RouteParametersConfig;
use lightning::util::config::{
Expand All @@ -28,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;
Expand Down Expand Up @@ -63,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);

Expand Down Expand Up @@ -131,6 +131,7 @@ pub(crate) const LNURL_AUTH_TIMEOUT_SECS: u64 = 15;
/// | `probing_liquidity_limit_multiplier` | 3 |
/// | `log_level` | Debug |
/// | `anchor_channels_config` | Some(..) |
/// | `payment_retry_strategy` | Timeout(10s) |
/// | `route_parameters` | None |
/// | `tor_config` | None |
///
Expand Down Expand Up @@ -189,6 +190,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<AnchorChannelsConfig>,
/// The strategy used when retrying failed payments.
///
/// When a payment fails to route, LDK will automatically retry according to this strategy.
///
/// See [`PaymentRetryStrategy`] for available options.
pub payment_retry_strategy: PaymentRetryStrategy,
/// Configuration options for payment routing and pathfinding.
///
/// Setting the [`RouteParametersConfig`] provides flexibility to customize how payments are routed,
Expand Down Expand Up @@ -216,6 +223,7 @@ 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: PaymentRetryStrategy::default(),
tor_config: None,
route_parameters: None,
node_alias: None,
Expand Down Expand Up @@ -638,6 +646,175 @@ pub enum AsyncPaymentsRole {
Server,
}

/// Strategies available to retry payment path failures.
///
/// See [`Retry`] for details.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum PaymentRetryStrategy {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than duplicating the object here, can't we reuse LDK's Retry?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No we can't re-export it to the bindings because of the Duration inside.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, then it would be likely preferable to expose a remote type for Duration, too, rather than duplicating a lot of code?

/// Max number of attempts to retry payment.
///
/// Please refer to [`Retry`] for further details.
Attempts {
/// The maximum number of payment attempts.
max_attempts: u32,
},
/// Time elapsed before abandoning retries for a payment.
///
/// Please refer to [`Retry`] for further details.
Timeout {
/// The timeout in seconds after which we stop retrying.
timeout_secs: u64,
},
}

impl Default for PaymentRetryStrategy {
fn default() -> Self {
Self::Timeout { timeout_secs: DEFAULT_PAYMENT_RETRY_TIMEOUT_SECS }
}
}

impl From<PaymentRetryStrategy> for Retry {
fn from(value: PaymentRetryStrategy) -> Self {
match value {
PaymentRetryStrategy::Attempts { max_attempts } => Retry::Attempts(max_attempts),
PaymentRetryStrategy::Timeout { timeout_secs } => {
Retry::Timeout(Duration::from_secs(timeout_secs))
},
}
}
}

/// Represents options which allow to override the default sending parameters for BOLT11 payments
/// on a per-payment basis.
///
/// These can be used to override the parameters set in [`Config::route_parameters`] and
/// [`Config::payment_retry_strategy`].
///
/// See [`OptionalBolt11PaymentParams`] for the underlying LDK type.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct Bolt11SendingParameters {
/// Configuration options for payment routing and pathfinding.
///
/// Setting the [`RouteParametersConfig`] provides flexibility to customize how payments are
/// routed, including setting limits on routing fees, CLTV expiry, and channel utilization.
///
/// If set, these will override the node-wide parameters configured via
/// [`Config::route_parameters`] for this payment.
pub route_parameters: Option<RouteParametersConfig>,
/// The strategy used when retrying failed payments.
///
/// If set, this will override the node-wide retry strategy configured via
/// [`Config::payment_retry_strategy`] for this payment.
pub retry_strategy: Option<PaymentRetryStrategy>,
/// A set of custom TLVs to include in the onion message to the recipient.
///
/// Each TLV is a `(type, value)` pair where the type number must be >= 2^16 (i.e., in the
/// custom range) and unique. Invalid TLV types will be ignored.
pub custom_tlvs: Option<Vec<CustomTlv>>,
/// If the payment being made from this node is part of a larger MPP payment from multiple
/// nodes (i.e., because a single payment is being made from multiple wallets), you can specify
/// the total amount being paid here.
///
/// If set, it must be at least the amount of the invoice. Further, if set, the amount sent
/// from this node may be lower than the invoice amount (as the payment from this node may be
/// a small part of the total).
pub declared_total_mpp_value_msat_override: Option<u64>,
}

/// Represents options which allow to override the default sending parameters for BOLT12 payments
/// on a per-payment basis.
///
/// These can be used to override the parameters set in [`Config::route_parameters`] and
/// [`Config::payment_retry_strategy`].
///
/// See [`OptionalOfferPaymentParams`] for the underlying LDK type.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct Bolt12SendingParameters {
/// Configuration options for payment routing and pathfinding.
///
/// Setting the [`RouteParametersConfig`] provides flexibility to customize how payments are
/// routed, including setting limits on routing fees, CLTV expiry, and channel utilization.
///
/// If set, these will override the node-wide parameters configured via
/// [`Config::route_parameters`] for this payment.
pub route_parameters: Option<RouteParametersConfig>,
/// The strategy used when retrying failed payments.
///
/// If set, this will override the node-wide retry strategy configured via
/// [`Config::payment_retry_strategy`] for this payment.
pub retry_strategy: Option<PaymentRetryStrategy>,
}

/// A custom TLV entry that can be included in the onion message to the recipient.
///
/// TLV type numbers must be unique and >= 2^16 (in the custom range).
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct CustomTlv {
/// The type number for this TLV. Must be >= 2^16.
pub tlv_type: u64,
/// The serialized value for this TLV.
pub value: Vec<u8>,
}

impl Bolt11SendingParameters {
pub(crate) fn into_ldk_params(
params: Option<Self>, config: &Config,
) -> OptionalBolt11PaymentParams {
if let Some(params) = params {
let route_params_config =
params.route_parameters.or(config.route_parameters).unwrap_or_default();
let retry_strategy = params
.retry_strategy
.map(Into::into)
.unwrap_or_else(|| config.payment_retry_strategy.into());
let custom_tlvs = params
.custom_tlvs
.map(|tlvs| tlvs.into_iter().map(|t| (t.tlv_type, t.value)).collect())
.and_then(|tlvs| RecipientCustomTlvs::new(tlvs).ok())
.unwrap_or_else(|| RecipientCustomTlvs::new(vec![]).unwrap());
OptionalBolt11PaymentParams {
route_params_config,
retry_strategy,
custom_tlvs,
declared_total_mpp_value_msat_override: params
.declared_total_mpp_value_msat_override,
}
} else {
OptionalBolt11PaymentParams {
route_params_config: config.route_parameters.unwrap_or_default(),
retry_strategy: config.payment_retry_strategy.into(),
..Default::default()
}
}
}
}

impl Bolt12SendingParameters {
pub(crate) fn into_ldk_params(
params: Option<Self>, payer_note: Option<String>, config: &Config,
) -> OptionalOfferPaymentParams {
if let Some(params) = params {
let route_params_config =
params.route_parameters.or(config.route_parameters).unwrap_or_default();
let retry_strategy = params
.retry_strategy
.map(Into::into)
.unwrap_or_else(|| config.payment_retry_strategy.into());
OptionalOfferPaymentParams { payer_note, route_params_config, retry_strategy }
} else {
OptionalOfferPaymentParams {
payer_note,
route_params_config: config.route_parameters.unwrap_or_default(),
retry_strategy: config.payment_retry_strategy.into(),
}
}
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;
Expand Down
62 changes: 27 additions & 35 deletions src/payment/bolt11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,15 @@ use std::sync::{Arc, RwLock};

use bitcoin::hashes::sha256::Hash as Sha256;
use bitcoin::hashes::Hash;
use lightning::ln::channelmanager::{
Bolt11InvoiceParameters, OptionalBolt11PaymentParams, PaymentId,
};
use lightning::ln::outbound_payment::{Bolt11PaymentError, Retry, RetryableSendFailure};
use lightning::ln::channelmanager::{Bolt11InvoiceParameters, PaymentId};
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::{Bolt11SendingParameters, Config};
use crate::connection::ConnectionManager;
use crate::data_store::DataStoreUpdateResult;
use crate::error::Error;
Expand Down Expand Up @@ -236,10 +234,11 @@ impl Bolt11Payment {
impl Bolt11Payment {
/// Send a payment given an invoice.
///
/// If `route_parameters` are provided they will override the default as well as the
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
/// If [`Bolt11SendingParameters`] are provided they will override the default as well as the
/// node-wide parameters configured via [`Config::route_parameters`] and
/// [`Config::payment_retry_strategy`].
pub fn send(
&self, invoice: &Bolt11Invoice, route_parameters: Option<RouteParametersConfig>,
&self, invoice: &Bolt11Invoice, sending_parameters: Option<Bolt11SendingParameters>,
) -> Result<PaymentId, Error> {
if !*self.is_running.read().unwrap() {
return Err(Error::NotRunning);
Expand All @@ -257,16 +256,9 @@ 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 payment_secret = Some(*invoice.payment_secret());

let optional_params = OptionalBolt11PaymentParams {
retry_strategy,
route_params_config,
..Default::default()
};
let optional_params =
Bolt11SendingParameters::into_ldk_params(sending_parameters, &self.config);
match self.channel_manager.pay_for_bolt11_invoice(
invoice,
payment_id,
Expand Down Expand Up @@ -336,23 +328,30 @@ impl Bolt11Payment {
/// This can be used to pay a so-called "zero-amount" invoice, i.e., an invoice that leaves the
/// amount paid to be determined by the user.
///
/// If `route_parameters` are provided they will override the default as well as the
/// node-wide parameters configured via [`Config::route_parameters`] on a per-field basis.
/// If [`Bolt11SendingParameters`] are provided they will override the default as well as the
/// node-wide parameters configured via [`Config::route_parameters`] and
/// [`Config::payment_retry_strategy`].
pub fn send_using_amount(
&self, invoice: &Bolt11Invoice, amount_msat: u64,
route_parameters: Option<RouteParametersConfig>,
sending_parameters: Option<Bolt11SendingParameters>,
) -> Result<PaymentId, Error> {
if !*self.is_running.read().unwrap() {
return Err(Error::NotRunning);
}

let invoice = maybe_deref(invoice);
if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
if amount_msat < invoice_amount_msat {
log_error!(
self.logger,
"Failed to pay as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat);
return Err(Error::InvalidAmount);
let is_mpp_override = sending_parameters
.as_ref()
.and_then(|sp| sp.declared_total_mpp_value_msat_override)
.is_some();
if !is_mpp_override {
if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
if amount_msat < invoice_amount_msat {
log_error!(
self.logger,
"Failed to pay as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat);
return Err(Error::InvalidAmount);
}
}
}

Expand All @@ -367,16 +366,9 @@ 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 payment_secret = Some(*invoice.payment_secret());

let optional_params = OptionalBolt11PaymentParams {
retry_strategy,
route_params_config,
..Default::default()
};
let optional_params =
Bolt11SendingParameters::into_ldk_params(sending_parameters, &self.config);
match self.channel_manager.pay_for_bolt11_invoice(
invoice,
payment_id,
Expand Down
Loading
Loading