From cf2bfd0ac27772e2c048564dbaff30e8c52bca8d Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 6 Feb 2025 16:14:34 +0100 Subject: [PATCH 01/16] WIP: Upgrade to LDK 0.2 --- Cargo.toml | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ce10d6ad..9fa4e6460 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,34 +28,46 @@ panic = 'abort' # Abort on panic default = [] [dependencies] -lightning = { version = "0.1.0", features = ["std"] } -lightning-types = { version = "0.2.0" } -lightning-invoice = { version = "0.33.0", features = ["std"] } -lightning-net-tokio = { version = "0.1.0" } -lightning-persister = { version = "0.1.0" } -lightning-background-processor = { version = "0.1.0", features = ["futures"] } -lightning-rapid-gossip-sync = { version = "0.1.0" } -lightning-block-sync = { version = "0.1.0", features = ["rpc-client", "tokio"] } -lightning-transaction-sync = { version = "0.1.0", features = ["esplora-async-https", "time", "electrum"] } -lightning-liquidity = { version = "0.1.0", features = ["std"] } +#lightning = { version = "0.1.0", features = ["std"] } +#lightning-types = { version = "0.2.0" } +#lightning-invoice = { version = "0.33.0", features = ["std"] } +#lightning-net-tokio = { version = "0.1.0" } +#lightning-persister = { version = "0.1.0" } +#lightning-background-processor = { version = "0.1.0" } +#lightning-rapid-gossip-sync = { version = "0.1.0" } +#lightning-block-sync = { version = "0.1.0", features = ["rpc-client", "tokio"] } +#lightning-transaction-sync = { version = "0.1.0", features = ["esplora-async-https", "time", "electrum"] } +#lightning-liquidity = { version = "0.1.0", features = ["std"] } #lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["std"] } #lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } #lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["std"] } #lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } #lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } -#lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["futures"] } +#lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } #lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } #lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["rpc-client", "tokio"] } #lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["esplora-async-https", "electrum", "time"] } #lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } +lightning = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b", features = ["std"] } +lightning-types = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b" } +lightning-invoice = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b", features = ["std"] } +lightning-net-tokio = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b" } +lightning-persister = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b" } +lightning-background-processor = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b" } +lightning-rapid-gossip-sync = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b" } +lightning-block-sync = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b", features = ["rpc-client", "tokio"] } +lightning-transaction-sync = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b", features = ["esplora-async-https", "electrum", "time"] } +lightning-liquidity = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b" } +lightning-macros = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b" } + #lightning = { path = "../rust-lightning/lightning", features = ["std"] } #lightning-types = { path = "../rust-lightning/lightning-types" } #lightning-invoice = { path = "../rust-lightning/lightning-invoice", features = ["std"] } #lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } #lightning-persister = { path = "../rust-lightning/lightning-persister" } -#lightning-background-processor = { path = "../rust-lightning/lightning-background-processor", features = ["futures"] } +#lightning-background-processor = { path = "../rust-lightning/lightning-background-processor" } #lightning-rapid-gossip-sync = { path = "../rust-lightning/lightning-rapid-gossip-sync" } #lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rpc-client", "tokio"] } #lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync", features = ["esplora-async-https", "electrum", "time"] } @@ -91,8 +103,9 @@ prost = { version = "0.11.6", default-features = false} winapi = { version = "0.3", features = ["winbase"] } [dev-dependencies] -lightning = { version = "0.1.0", features = ["std", "_test_utils"] } +#lightning = { version = "0.1.0", features = ["std", "_test_utils"] } #lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std", "_test_utils"] } +lightning = { git = "https://github.com/tnull/rust-lightning", rev = "367d0cc495f55d8d50d36276136e873ea628f44b", features = ["std", "_test_utils"] } #lightning = { path = "../rust-lightning/lightning", features = ["std", "_test_utils"] } proptest = "1.0.0" regex = "1.5.6" From 950a8a54fa8a0488a2a502ec51a9b7ef25d2246b Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:29:02 +0200 Subject: [PATCH 02/16] f Account for renamed `lightning-liquidity` types --- bindings/ldk_node.udl | 46 +++++++++++------------ src/liquidity.rs | 86 +++++++++++++++++++++---------------------- src/uniffi_types.rs | 28 +++++++------- 3 files changed, 81 insertions(+), 79 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index c2f0166c8..2bb33b941 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -160,8 +160,8 @@ interface Node { [Enum] interface Bolt11InvoiceDescription { - Hash(string hash); - Direct(string description); + Hash(string hash); + Direct(string description); }; interface Bolt11Payment { @@ -245,7 +245,7 @@ interface LSPS1Liquidity { [Throws=NodeError] LSPS1OrderStatus request_channel(u64 lsp_balance_sat, u64 client_balance_sat, u32 channel_expiry_blocks, boolean announce_channel); [Throws=NodeError] - LSPS1OrderStatus check_order_status(OrderId order_id); + LSPS1OrderStatus check_order_status(LSPS1OrderId order_id); }; [Error] @@ -459,13 +459,13 @@ dictionary CustomTlvRecord { }; dictionary LSPS1OrderStatus { - OrderId order_id; - OrderParameters order_params; - PaymentInfo payment_options; - ChannelOrderInfo? channel_state; + LSPS1OrderId order_id; + LSPS1OrderParams order_params; + LSPS1PaymentInfo payment_options; + LSPS1ChannelInfo? channel_state; }; -dictionary OrderParameters { +dictionary LSPS1OrderParams { u64 lsp_balance_sat; u64 client_balance_sat; u16 required_channel_confirmations; @@ -475,22 +475,22 @@ dictionary OrderParameters { boolean announce_channel; }; -dictionary PaymentInfo { - Bolt11PaymentInfo? bolt11; - OnchainPaymentInfo? onchain; +dictionary LSPS1PaymentInfo { + LSPS1Bolt11PaymentInfo? bolt11; + LSPS1OnchainPaymentInfo? onchain; }; -dictionary Bolt11PaymentInfo { - PaymentState state; - DateTime expires_at; +dictionary LSPS1Bolt11PaymentInfo { + LSPS1PaymentState state; + LSPSDateTime expires_at; u64 fee_total_sat; u64 order_total_sat; Bolt11Invoice invoice; }; -dictionary OnchainPaymentInfo { - PaymentState state; - DateTime expires_at; +dictionary LSPS1OnchainPaymentInfo { + LSPS1PaymentState state; + LSPSDateTime expires_at; u64 fee_total_sat; u64 order_total_sat; Address address; @@ -499,13 +499,13 @@ dictionary OnchainPaymentInfo { Address? refund_onchain_address; }; -dictionary ChannelOrderInfo { - DateTime funded_at; +dictionary LSPS1ChannelInfo { + LSPSDateTime funded_at; OutPoint funding_outpoint; - DateTime expires_at; + LSPSDateTime expires_at; }; -enum PaymentState { +enum LSPS1PaymentState { "ExpectPayment", "Paid", "Refunded", @@ -794,7 +794,7 @@ typedef string UntrustedString; typedef string NodeAlias; [Custom] -typedef string OrderId; +typedef string LSPS1OrderId; [Custom] -typedef string DateTime; +typedef string LSPSDateTime; diff --git a/src/liquidity.rs b/src/liquidity.rs index 47f3dcce4..afeb814e8 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -21,14 +21,16 @@ use lightning::routing::router::{RouteHint, RouteHintHop}; use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, InvoiceBuilder, RoutingFees}; -use lightning_liquidity::events::Event; -use lightning_liquidity::lsps0::ser::RequestId; +use lightning_liquidity::events::LiquidityEvent; +use lightning_liquidity::lsps0::ser::{LSPSDateTime, LSPSRequestId}; use lightning_liquidity::lsps1::client::LSPS1ClientConfig as LdkLSPS1ClientConfig; use lightning_liquidity::lsps1::event::LSPS1ClientEvent; -use lightning_liquidity::lsps1::msgs::{ChannelInfo, LSPS1Options, OrderId, OrderParameters}; +use lightning_liquidity::lsps1::msgs::{ + LSPS1ChannelInfo, LSPS1Options, LSPS1OrderId, LSPS1OrderParams, +}; use lightning_liquidity::lsps2::client::LSPS2ClientConfig as LdkLSPS2ClientConfig; use lightning_liquidity::lsps2::event::{LSPS2ClientEvent, LSPS2ServiceEvent}; -use lightning_liquidity::lsps2::msgs::{OpeningFeeParams, RawOpeningFeeParams}; +use lightning_liquidity::lsps2::msgs::{LSPS2OpeningFeeParams, LSPS2RawOpeningFeeParams}; use lightning_liquidity::lsps2::service::LSPS2ServiceConfig as LdkLSPS2ServiceConfig; use lightning_liquidity::lsps2::utils::compute_opening_fee; use lightning_liquidity::{LiquidityClientConfig, LiquidityServiceConfig}; @@ -40,7 +42,7 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1}; use tokio::sync::oneshot; -use chrono::{DateTime, Utc}; +use chrono::Utc; use rand::Rng; @@ -61,10 +63,10 @@ struct LSPS1Client { token: Option, ldk_client_config: LdkLSPS1ClientConfig, pending_opening_params_requests: - Mutex>>, - pending_create_order_requests: Mutex>>, + Mutex>>, + pending_create_order_requests: Mutex>>, pending_check_order_status_requests: - Mutex>>, + Mutex>>, } #[derive(Debug, Clone)] @@ -79,8 +81,8 @@ struct LSPS2Client { lsp_address: SocketAddress, token: Option, ldk_client_config: LdkLSPS2ClientConfig, - pending_fee_requests: Mutex>>, - pending_buy_requests: Mutex>>, + pending_fee_requests: Mutex>>, + pending_buy_requests: Mutex>>, } #[derive(Debug, Clone)] @@ -293,7 +295,7 @@ where pub(crate) async fn handle_next_event(&self) { match self.liquidity_manager.next_event_async().await { - Event::LSPS1Client(LSPS1ClientEvent::SupportedOptionsReady { + LiquidityEvent::LSPS1Client(LSPS1ClientEvent::SupportedOptionsReady { request_id, counterparty_node_id, supported_options, @@ -346,7 +348,7 @@ where ); } }, - Event::LSPS1Client(LSPS1ClientEvent::OrderCreated { + LiquidityEvent::LSPS1Client(LSPS1ClientEvent::OrderCreated { request_id, counterparty_node_id, order_id, @@ -404,7 +406,7 @@ where log_error!(self.logger, "Received unexpected LSPS1Client::OrderCreated event!"); } }, - Event::LSPS1Client(LSPS1ClientEvent::OrderStatus { + LiquidityEvent::LSPS1Client(LSPS1ClientEvent::OrderStatus { request_id, counterparty_node_id, order_id, @@ -462,7 +464,7 @@ where log_error!(self.logger, "Received unexpected LSPS1Client::OrderStatus event!"); } }, - Event::LSPS2Service(LSPS2ServiceEvent::GetInfo { + LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::GetInfo { request_id, counterparty_node_id, token, @@ -501,10 +503,8 @@ where } } - let mut valid_until: DateTime = Utc::now(); - valid_until += LSPS2_GETINFO_REQUEST_EXPIRY; - - let opening_fee_params = RawOpeningFeeParams { + let valid_until = LSPSDateTime(Utc::now() + LSPS2_GETINFO_REQUEST_EXPIRY); + let opening_fee_params = LSPS2RawOpeningFeeParams { min_fee_msat: service_config.min_channel_opening_fee_msat, proportional: service_config.channel_opening_fee_ppm, valid_until, @@ -532,7 +532,7 @@ where return; } }, - Event::LSPS2Service(LSPS2ServiceEvent::BuyRequest { + LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::BuyRequest { request_id, counterparty_node_id, opening_fee_params: _, @@ -599,7 +599,7 @@ where return; } }, - Event::LSPS2Service(LSPS2ServiceEvent::OpenChannel { + LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::OpenChannel { their_network_key, amt_to_forward_msat, opening_fee_msat: _, @@ -713,7 +713,7 @@ where }, } }, - Event::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady { + LiquidityEvent::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady { request_id, counterparty_node_id, opening_fee_params_menu, @@ -763,7 +763,7 @@ where ); } }, - Event::LSPS2Client(LSPS2ClientEvent::InvoiceParametersReady { + LiquidityEvent::LSPS2Client(LSPS2ClientEvent::InvoiceParametersReady { request_id, counterparty_node_id, intercept_scid, @@ -903,7 +903,7 @@ where return Err(Error::LiquidityRequestFailed); } - let order_params = OrderParameters { + let order_params = LSPS1OrderParams { lsp_balance_sat, client_balance_sat, required_channel_confirmations: lsp_limits.min_required_channel_confirmations, @@ -952,7 +952,7 @@ where } pub(crate) async fn lsps1_check_order_status( - &self, order_id: OrderId, + &self, order_id: LSPS1OrderId, ) -> Result { let lsps1_client = self.lsps1_client.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; let client_handler = self.liquidity_manager.lsps1_client_handler().ok_or_else(|| { @@ -1120,7 +1120,7 @@ where } async fn lsps2_send_buy_request( - &self, amount_msat: Option, opening_fee_params: OpeningFeeParams, + &self, amount_msat: Option, opening_fee_params: LSPS2OpeningFeeParams, ) -> Result { let lsps2_client = self.lsps2_client.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; @@ -1291,32 +1291,32 @@ pub(crate) struct LSPS1OpeningParamsResponse { #[derive(Debug, Clone)] pub struct LSPS1OrderStatus { /// The id of the channel order. - pub order_id: OrderId, + pub order_id: LSPS1OrderId, /// The parameters of channel order. - pub order_params: OrderParameters, + pub order_params: LSPS1OrderParams, /// Contains details about how to pay for the order. - pub payment_options: PaymentInfo, + pub payment_options: LSPS1PaymentInfo, /// Contains information about the channel state. - pub channel_state: Option, + pub channel_state: Option, } #[cfg(not(feature = "uniffi"))] -type PaymentInfo = lightning_liquidity::lsps1::msgs::PaymentInfo; +type LSPS1PaymentInfo = lightning_liquidity::lsps1::msgs::LSPS1PaymentInfo; /// Details regarding how to pay for an order. #[cfg(feature = "uniffi")] #[derive(Clone, Debug, PartialEq, Eq)] -pub struct PaymentInfo { +pub struct LSPS1PaymentInfo { /// A Lightning payment using BOLT 11. - pub bolt11: Option, + pub bolt11: Option, /// An onchain payment. - pub onchain: Option, + pub onchain: Option, } #[cfg(feature = "uniffi")] -impl From for PaymentInfo { - fn from(value: lightning_liquidity::lsps1::msgs::PaymentInfo) -> Self { - PaymentInfo { +impl From for LSPS1PaymentInfo { + fn from(value: lightning_liquidity::lsps1::msgs::LSPS1PaymentInfo) -> Self { + LSPS1PaymentInfo { bolt11: value.bolt11.map(|b| b.into()), onchain: value.onchain.map(|o| o.into()), } @@ -1326,11 +1326,11 @@ impl From for PaymentInfo { /// An onchain payment. #[cfg(feature = "uniffi")] #[derive(Clone, Debug, PartialEq, Eq)] -pub struct OnchainPaymentInfo { +pub struct LSPS1OnchainPaymentInfo { /// Indicates the current state of the payment. - pub state: lightning_liquidity::lsps1::msgs::PaymentState, + pub state: lightning_liquidity::lsps1::msgs::LSPS1PaymentState, /// The datetime when the payment option expires. - pub expires_at: chrono::DateTime, + pub expires_at: LSPSDateTime, /// The total fee the LSP will charge to open this channel in satoshi. pub fee_total_sat: u64, /// The amount the client needs to pay to have the requested channel openend. @@ -1349,8 +1349,8 @@ pub struct OnchainPaymentInfo { } #[cfg(feature = "uniffi")] -impl From for OnchainPaymentInfo { - fn from(value: lightning_liquidity::lsps1::msgs::OnchainPaymentInfo) -> Self { +impl From for LSPS1OnchainPaymentInfo { + fn from(value: lightning_liquidity::lsps1::msgs::LSPS1OnchainPaymentInfo) -> Self { Self { state: value.state, expires_at: value.expires_at, @@ -1366,7 +1366,7 @@ impl From for OnchainPayme #[derive(Debug, Clone)] pub(crate) struct LSPS2FeeResponse { - opening_fee_params_menu: Vec, + opening_fee_params_menu: Vec, } #[derive(Debug, Clone)] @@ -1456,7 +1456,7 @@ impl LSPS1Liquidity { } /// Connects to the configured LSP and checks for the status of a previously-placed order. - pub fn check_order_status(&self, order_id: OrderId) -> Result { + pub fn check_order_status(&self, order_id: LSPS1OrderId) -> Result { let liquidity_source = self.liquidity_source.as_ref().ok_or(Error::LiquiditySourceUnavailable)?; diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index 77f9348cc..a0d84f8b5 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -15,7 +15,9 @@ pub use crate::config::{ EsploraSyncConfig, MaxDustHTLCExposure, }; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; -pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig, OnchainPaymentInfo, PaymentInfo}; +pub use crate::liquidity::{ + LSPS1OnchainPaymentInfo, LSPS1OrderStatus, LSPS1PaymentInfo, LSPS2ServiceConfig, +}; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; pub use crate::payment::store::{ ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus, @@ -35,8 +37,10 @@ pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; pub use lightning_invoice::{Description, SignedRawBolt11Invoice}; -pub use lightning_liquidity::lsps1::msgs::ChannelInfo as ChannelOrderInfo; -pub use lightning_liquidity::lsps1::msgs::{OrderId, OrderParameters, PaymentState}; +pub use lightning_liquidity::lsps0::ser::LSPSDateTime; +pub use lightning_liquidity::lsps1::msgs::{ + LSPS1ChannelInfo, LSPS1OrderId, LSPS1OrderParams, LSPS1PaymentState, +}; pub use bitcoin::{Address, BlockHash, FeeRate, Network, OutPoint, Txid}; @@ -44,8 +48,6 @@ pub use bip39::Mnemonic; pub use vss_client::headers::{VssHeaderProvider, VssHeaderProviderError}; -pub type DateTime = chrono::DateTime; - use crate::UniffiCustomTypeConverter; use crate::builder::sanitize_alias; @@ -607,11 +609,11 @@ impl std::fmt::Display for Bolt11Invoice { /// A Lightning payment using BOLT 11. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Bolt11PaymentInfo { +pub struct LSPS1Bolt11PaymentInfo { /// Indicates the current state of the payment. - pub state: PaymentState, + pub state: LSPS1PaymentState, /// The datetime when the payment option expires. - pub expires_at: chrono::DateTime, + pub expires_at: LSPSDateTime, /// The total fee the LSP will charge to open this channel in satoshi. pub fee_total_sat: u64, /// The amount the client needs to pay to have the requested channel openend. @@ -620,8 +622,8 @@ pub struct Bolt11PaymentInfo { pub invoice: Arc, } -impl From for Bolt11PaymentInfo { - fn from(info: lightning_liquidity::lsps1::msgs::Bolt11PaymentInfo) -> Self { +impl From for LSPS1Bolt11PaymentInfo { + fn from(info: lightning_liquidity::lsps1::msgs::LSPS1Bolt11PaymentInfo) -> Self { Self { state: info.state, expires_at: info.expires_at, @@ -632,7 +634,7 @@ impl From for Bolt11Payment } } -impl UniffiCustomTypeConverter for OrderId { +impl UniffiCustomTypeConverter for LSPS1OrderId { type Builtin = String; fn into_custom(val: Self::Builtin) -> uniffi::Result { @@ -644,11 +646,11 @@ impl UniffiCustomTypeConverter for OrderId { } } -impl UniffiCustomTypeConverter for DateTime { +impl UniffiCustomTypeConverter for LSPSDateTime { type Builtin = String; fn into_custom(val: Self::Builtin) -> uniffi::Result { - Ok(DateTime::from_str(&val).map_err(|_| Error::InvalidDateTime)?) + Ok(LSPSDateTime::from_str(&val).map_err(|_| Error::InvalidDateTime)?) } fn from_custom(obj: Self) -> Self::Builtin { From 65d812544901a41c59625c952baff36f613bfcac Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:31:47 +0200 Subject: [PATCH 03/16] f Move `uniffi` LSPS1PaymentInfo types next to the pre-existing ones .. as having them in two places doesn't make sense --- src/liquidity.rs | 60 +------------------------------------------- src/uniffi_types.rs | 61 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 62 deletions(-) diff --git a/src/liquidity.rs b/src/liquidity.rs index afeb814e8..51c70237d 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -1303,66 +1303,8 @@ pub struct LSPS1OrderStatus { #[cfg(not(feature = "uniffi"))] type LSPS1PaymentInfo = lightning_liquidity::lsps1::msgs::LSPS1PaymentInfo; -/// Details regarding how to pay for an order. #[cfg(feature = "uniffi")] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LSPS1PaymentInfo { - /// A Lightning payment using BOLT 11. - pub bolt11: Option, - /// An onchain payment. - pub onchain: Option, -} - -#[cfg(feature = "uniffi")] -impl From for LSPS1PaymentInfo { - fn from(value: lightning_liquidity::lsps1::msgs::LSPS1PaymentInfo) -> Self { - LSPS1PaymentInfo { - bolt11: value.bolt11.map(|b| b.into()), - onchain: value.onchain.map(|o| o.into()), - } - } -} - -/// An onchain payment. -#[cfg(feature = "uniffi")] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LSPS1OnchainPaymentInfo { - /// Indicates the current state of the payment. - pub state: lightning_liquidity::lsps1::msgs::LSPS1PaymentState, - /// The datetime when the payment option expires. - pub expires_at: LSPSDateTime, - /// The total fee the LSP will charge to open this channel in satoshi. - pub fee_total_sat: u64, - /// The amount the client needs to pay to have the requested channel openend. - pub order_total_sat: u64, - /// An on-chain address the client can send [`Self::order_total_sat`] to to have the channel - /// opened. - pub address: bitcoin::Address, - /// The minimum number of block confirmations that are required for the on-chain payment to be - /// considered confirmed. - pub min_onchain_payment_confirmations: Option, - /// The minimum fee rate for the on-chain payment in case the client wants the payment to be - /// confirmed without a confirmation. - pub min_fee_for_0conf: Arc, - /// The address where the LSP will send the funds if the order fails. - pub refund_onchain_address: Option, -} - -#[cfg(feature = "uniffi")] -impl From for LSPS1OnchainPaymentInfo { - fn from(value: lightning_liquidity::lsps1::msgs::LSPS1OnchainPaymentInfo) -> Self { - Self { - state: value.state, - expires_at: value.expires_at, - fee_total_sat: value.fee_total_sat, - order_total_sat: value.order_total_sat, - address: value.address, - min_onchain_payment_confirmations: value.min_onchain_payment_confirmations, - min_fee_for_0conf: Arc::new(value.min_fee_for_0conf), - refund_onchain_address: value.refund_onchain_address, - } - } -} +type LSPS1PaymentInfo = crate::uniffi_types::LSPS1PaymentInfo; #[derive(Debug, Clone)] pub(crate) struct LSPS2FeeResponse { diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index a0d84f8b5..c23007ba7 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -15,9 +15,7 @@ pub use crate::config::{ EsploraSyncConfig, MaxDustHTLCExposure, }; pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo}; -pub use crate::liquidity::{ - LSPS1OnchainPaymentInfo, LSPS1OrderStatus, LSPS1PaymentInfo, LSPS2ServiceConfig, -}; +pub use crate::liquidity::{LSPS1OrderStatus, LSPS2ServiceConfig}; pub use crate::logger::{LogLevel, LogRecord, LogWriter}; pub use crate::payment::store::{ ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus, @@ -607,6 +605,24 @@ impl std::fmt::Display for Bolt11Invoice { } } +/// Details regarding how to pay for an order. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LSPS1PaymentInfo { + /// A Lightning payment using BOLT 11. + pub bolt11: Option, + /// An onchain payment. + pub onchain: Option, +} + +impl From for LSPS1PaymentInfo { + fn from(value: lightning_liquidity::lsps1::msgs::LSPS1PaymentInfo) -> Self { + LSPS1PaymentInfo { + bolt11: value.bolt11.map(|b| b.into()), + onchain: value.onchain.map(|o| o.into()), + } + } +} + /// A Lightning payment using BOLT 11. #[derive(Clone, Debug, PartialEq, Eq)] pub struct LSPS1Bolt11PaymentInfo { @@ -634,6 +650,45 @@ impl From for LSPS1Bol } } +/// An onchain payment. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LSPS1OnchainPaymentInfo { + /// Indicates the current state of the payment. + pub state: lightning_liquidity::lsps1::msgs::LSPS1PaymentState, + /// The datetime when the payment option expires. + pub expires_at: LSPSDateTime, + /// The total fee the LSP will charge to open this channel in satoshi. + pub fee_total_sat: u64, + /// The amount the client needs to pay to have the requested channel openend. + pub order_total_sat: u64, + /// An on-chain address the client can send [`Self::order_total_sat`] to to have the channel + /// opened. + pub address: bitcoin::Address, + /// The minimum number of block confirmations that are required for the on-chain payment to be + /// considered confirmed. + pub min_onchain_payment_confirmations: Option, + /// The minimum fee rate for the on-chain payment in case the client wants the payment to be + /// confirmed without a confirmation. + pub min_fee_for_0conf: Arc, + /// The address where the LSP will send the funds if the order fails. + pub refund_onchain_address: Option, +} + +impl From for LSPS1OnchainPaymentInfo { + fn from(value: lightning_liquidity::lsps1::msgs::LSPS1OnchainPaymentInfo) -> Self { + Self { + state: value.state, + expires_at: value.expires_at, + fee_total_sat: value.fee_total_sat, + order_total_sat: value.order_total_sat, + address: value.address, + min_onchain_payment_confirmations: value.min_onchain_payment_confirmations, + min_fee_for_0conf: Arc::new(value.min_fee_for_0conf), + refund_onchain_address: value.refund_onchain_address, + } + } +} + impl UniffiCustomTypeConverter for LSPS1OrderId { type Builtin = String; From 6f4f8e286fea335d7ed19dce9f29a835ff688fb0 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:34:58 +0200 Subject: [PATCH 04/16] f Account for `ChannelMonitors` being tracked by `ChannelId` --- src/builder.rs | 4 ++-- src/chain/mod.rs | 2 +- src/lib.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 31a0fee45..0da56a4e3 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1275,8 +1275,8 @@ fn build_with_store_internal( // Give ChannelMonitors to ChainMonitor for (_blockhash, channel_monitor) in channel_monitors.into_iter() { - let funding_outpoint = channel_monitor.get_funding_txo().0; - chain_monitor.watch_channel(funding_outpoint, channel_monitor).map_err(|e| { + let channel_id = channel_monitor.channel_id(); + chain_monitor.watch_channel(channel_id, channel_monitor).map_err(|e| { log_error!(logger, "Failed to watch channel monitor: {:?}", e); BuildError::InvalidChannelMonitor })?; diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 62627797e..111486cf7 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -445,7 +445,7 @@ impl ChainSource { if let Some(worst_channel_monitor_block_hash) = chain_monitor .list_monitors() .iter() - .flat_map(|(txo, _)| chain_monitor.get_monitor(*txo)) + .flat_map(|channel_id| chain_monitor.get_monitor(*channel_id)) .map(|m| m.current_best_block()) .min_by_key(|b| b.height) .map(|b| b.block_hash) diff --git a/src/lib.rs b/src/lib.rs index c3bfe16d8..62a24a846 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1426,8 +1426,8 @@ impl Node { let mut total_lightning_balance_sats = 0; let mut lightning_balances = Vec::new(); - for (funding_txo, channel_id) in self.chain_monitor.list_monitors() { - match self.chain_monitor.get_monitor(funding_txo) { + for channel_id in self.chain_monitor.list_monitors() { + match self.chain_monitor.get_monitor(channel_id) { Ok(monitor) => { // unwrap safety: `get_counterparty_node_id` will always be `Some` after 0.0.110 and // LDK Node 0.1 depended on 0.0.115 already. From 6493719549a3679fb108c8efb2c7d4999f18365b Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:53:22 +0200 Subject: [PATCH 05/16] f Account for `HTLCHandlingFailureType` --- src/event.rs | 4 ++-- src/liquidity.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/event.rs b/src/event.rs index 22848bec1..28fa45d7a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1040,9 +1040,9 @@ where LdkEvent::PaymentPathFailed { .. } => {}, LdkEvent::ProbeSuccessful { .. } => {}, LdkEvent::ProbeFailed { .. } => {}, - LdkEvent::HTLCHandlingFailed { failed_next_destination, .. } => { + LdkEvent::HTLCHandlingFailed { failure_type, .. } => { if let Some(liquidity_source) = self.liquidity_source.as_ref() { - liquidity_source.handle_htlc_handling_failed(failed_next_destination); + liquidity_source.handle_htlc_handling_failed(failure_type); } }, LdkEvent::PendingHTLCsForwardable { time_forwardable } => { diff --git a/src/liquidity.rs b/src/liquidity.rs index 51c70237d..151736c35 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -13,7 +13,7 @@ use crate::logger::{log_debug, log_error, log_info, LdkLogger, Logger}; use crate::types::{ChannelManager, KeysManager, LiquidityManager, PeerManager, Wallet}; use crate::{total_anchor_channels_reserve_sats, Config, Error}; -use lightning::events::HTLCDestination; +use lightning::events::HTLCHandlingFailureType; use lightning::ln::channelmanager::{InterceptId, MIN_FINAL_CLTV_EXPIRY_DELTA}; use lightning::ln::msgs::SocketAddress; use lightning::ln::types::ChannelId; @@ -1255,9 +1255,9 @@ where } } - pub(crate) fn handle_htlc_handling_failed(&self, failed_next_destination: HTLCDestination) { + pub(crate) fn handle_htlc_handling_failed(&self, failure_type: HTLCHandlingFailureType) { if let Some(lsps2_service_handler) = self.liquidity_manager.lsps2_service_handler() { - if let Err(e) = lsps2_service_handler.htlc_handling_failed(failed_next_destination) { + if let Err(e) = lsps2_service_handler.htlc_handling_failed(failure_type) { log_error!( self.logger, "LSPS2 service failed to handle HTLCHandlingFailed event: {:?}", From 00f94f43ebead3dced0177491ef4b55cc4d7305a Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:58:25 +0200 Subject: [PATCH 06/16] f Account for signer changes --- src/wallet/mod.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 65fa2e24d..33abb6b44 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -23,7 +23,7 @@ use lightning::chain::{BestBlock, Listen}; use lightning::events::bump_transaction::{Utxo, WalletSource}; use lightning::ln::channelmanager::PaymentId; use lightning::ln::inbound_payment::ExpandedKey; -use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage}; +use lightning::ln::msgs::UnsignedGossipMessage; use lightning::ln::script::ShutdownScript; use lightning::sign::{ ChangeDestinationSource, EntropySource, InMemorySigner, KeysManager, NodeSigner, OutputSpender, @@ -44,7 +44,7 @@ use bitcoin::key::XOnlyPublicKey; use bitcoin::psbt::Psbt; use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; -use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing}; +use bitcoin::secp256k1::{All, PublicKey, Scalar, Secp256k1, SecretKey}; use bitcoin::{ Address, Amount, FeeRate, Network, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, WitnessProgram, WitnessVersion, @@ -852,10 +852,10 @@ where L::Target: LdkLogger, { /// See [`KeysManager::spend_spendable_outputs`] for documentation on this method. - fn spend_spendable_outputs( + fn spend_spendable_outputs( &self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec, change_destination_script: ScriptBuf, feerate_sat_per_1000_weight: u32, - locktime: Option, secp_ctx: &Secp256k1, + locktime: Option, secp_ctx: &Secp256k1, ) -> Result { self.inner.spend_spendable_outputs( descriptors, @@ -887,20 +887,12 @@ where { type EcdsaSigner = InMemorySigner; - fn generate_channel_keys_id( - &self, inbound: bool, channel_value_satoshis: u64, user_channel_id: u128, - ) -> [u8; 32] { - self.inner.generate_channel_keys_id(inbound, channel_value_satoshis, user_channel_id) + fn generate_channel_keys_id(&self, inbound: bool, user_channel_id: u128) -> [u8; 32] { + self.inner.generate_channel_keys_id(inbound, user_channel_id) } - fn derive_channel_signer( - &self, channel_value_satoshis: u64, channel_keys_id: [u8; 32], - ) -> Self::EcdsaSigner { - self.inner.derive_channel_signer(channel_value_satoshis, channel_keys_id) - } - - fn read_chan_signer(&self, reader: &[u8]) -> Result { - self.inner.read_chan_signer(reader) + fn derive_channel_signer(&self, channel_keys_id: [u8; 32]) -> Self::EcdsaSigner { + self.inner.derive_channel_signer(channel_keys_id) } fn get_destination_script(&self, _channel_keys_id: [u8; 32]) -> Result { From 03f4365e4f9bdee0703eedb4d4fa41a01ac0b9b1 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 17:01:20 +0200 Subject: [PATCH 07/16] f Account for `PaymentClaimable` API changes --- src/event.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/event.rs b/src/event.rs index 28fa45d7a..b65208c98 100644 --- a/src/event.rs +++ b/src/event.rs @@ -563,13 +563,10 @@ where payment_hash, purpose, amount_msat, - receiver_node_id: _, - via_channel_id: _, - via_user_channel_id: _, claim_deadline, onion_fields, counterparty_skimmed_fee_msat, - payment_id: _, + .. } => { let payment_id = PaymentId(payment_hash.0); if let Some(info) = self.payment_store.get(&payment_id) { From 0e29817960313fcc724b3a1901a14f153c7df533 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 10:32:49 +0200 Subject: [PATCH 08/16] f Account for `get_counterparty_node_id` returning `PublicKey` --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 62a24a846..7af3fda14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1429,9 +1429,7 @@ impl Node { for channel_id in self.chain_monitor.list_monitors() { match self.chain_monitor.get_monitor(channel_id) { Ok(monitor) => { - // unwrap safety: `get_counterparty_node_id` will always be `Some` after 0.0.110 and - // LDK Node 0.1 depended on 0.0.115 already. - let counterparty_node_id = monitor.get_counterparty_node_id().unwrap(); + let counterparty_node_id = monitor.get_counterparty_node_id(); for ldk_balance in monitor.get_claimable_balances() { total_lightning_balance_sats += ldk_balance.claimable_amount_satoshis(); lightning_balances.push(LightningBalance::from_ldk_balance( From 3d05fa00efed59e0dac28562ec5448407adfb5fb Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 10:41:38 +0200 Subject: [PATCH 09/16] f Account for BP taking Sweeper and LiqMan as arguments now --- src/lib.rs | 5 +++++ src/liquidity.rs | 8 +++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7af3fda14..c7c89b524 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -551,6 +551,9 @@ impl Node { let background_chan_man = Arc::clone(&self.channel_manager); let background_gossip_sync = self.gossip_source.as_gossip_sync(); let background_peer_man = Arc::clone(&self.peer_manager); + let background_liquidity_man_opt = + self.liquidity_source.as_ref().map(|ls| Arc::clone(&ls.liquidity_manager())); + let background_sweeper = Arc::clone(&self.output_sweeper); let background_onion_messenger = Arc::clone(&self.onion_messenger); let background_logger = Arc::clone(&self.logger); let background_error_logger = Arc::clone(&self.logger); @@ -587,6 +590,8 @@ impl Node { Some(background_onion_messenger), background_gossip_sync, background_peer_man, + background_liquidity_man_opt, + Some(background_sweeper), background_logger, Some(background_scorer), sleeper, diff --git a/src/liquidity.rs b/src/liquidity.rs index 151736c35..8681090a2 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -276,13 +276,11 @@ where L::Target: LdkLogger, { pub(crate) fn set_peer_manager(&self, peer_manager: Arc) { - *self.peer_manager.write().unwrap() = Some(Arc::clone(&peer_manager)); - let process_msgs_callback = move || peer_manager.process_events(); - self.liquidity_manager.set_process_msgs_callback(process_msgs_callback); + *self.peer_manager.write().unwrap() = Some(peer_manager); } - pub(crate) fn liquidity_manager(&self) -> &LiquidityManager { - self.liquidity_manager.as_ref() + pub(crate) fn liquidity_manager(&self) -> Arc { + Arc::clone(&self.liquidity_manager) } pub(crate) fn get_lsps1_lsp_details(&self) -> Option<(PublicKey, SocketAddress)> { From 800084991da4c86f43f43af8538140c80761600a Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 10:45:04 +0200 Subject: [PATCH 10/16] f Account for `accept_inbound_channel` allowing override params .. we now apply the LSPS2 client-side overrides on a per-counterparty basis, not as soon as we act as an LSP client --- src/builder.rs | 11 ----------- src/event.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 0da56a4e3..0a7ca4506 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1187,17 +1187,6 @@ fn build_with_store_internal( }; let mut user_config = default_user_config(&config); - if liquidity_source_config.and_then(|lsc| lsc.lsps2_client.as_ref()).is_some() { - // Generally allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll - // check that they don't take too much before claiming. - user_config.channel_config.accept_underpaying_htlcs = true; - - // FIXME: When we're an LSPS2 client, set maximum allowed inbound HTLC value in flight - // to 100%. We should eventually be able to set this on a per-channel basis, but for - // now we just bump the default for all channels. - user_config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = - 100; - } if liquidity_source_config.and_then(|lsc| lsc.lsps2_service.as_ref()).is_some() { // If we act as an LSPS2 service, we need to to be able to intercept HTLCs and forward the diff --git a/src/event.rs b/src/event.rs index b65208c98..f1bfd8049 100644 --- a/src/event.rs +++ b/src/event.rs @@ -36,6 +36,9 @@ use lightning::impl_writeable_tlv_based_enum; use lightning::ln::channelmanager::PaymentId; use lightning::ln::types::ChannelId; use lightning::routing::gossip::NodeId; +use lightning::util::config::{ + ChannelConfigOverrides, ChannelConfigUpdate, ChannelHandshakeConfigUpdate, +}; use lightning::util::errors::APIError; use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer}; @@ -1156,17 +1159,44 @@ where let user_channel_id: u128 = rand::thread_rng().gen::(); let allow_0conf = self.config.trusted_peers_0conf.contains(&counterparty_node_id); + let mut channel_override_config = None; + if let Some((lsp_node_id, _)) = self + .liquidity_source + .as_ref() + .and_then(|ls| ls.as_ref().get_lsps2_lsp_details()) + { + if lsp_node_id == counterparty_node_id { + // When we're an LSPS2 client, allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll + // check that they don't take too much before claiming. + // + // We also set maximum allowed inbound HTLC value in flight + // to 100%. We should eventually be able to set this on a per-channel basis, but for + // now we just bump the default for all channels. + channel_override_config = Some(ChannelConfigOverrides { + handshake_overrides: Some(ChannelHandshakeConfigUpdate { + max_inbound_htlc_value_in_flight_percent_of_channel: Some(100), + ..Default::default() + }), + update_overrides: Some(ChannelConfigUpdate { + accept_underpaying_htlcs: Some(true), + ..Default::default() + }), + }); + } + } let res = if allow_0conf { self.channel_manager.accept_inbound_channel_from_trusted_peer_0conf( &temporary_channel_id, &counterparty_node_id, user_channel_id, + channel_override_config, ) } else { self.channel_manager.accept_inbound_channel( &temporary_channel_id, &counterparty_node_id, user_channel_id, + channel_override_config, ) }; From 280287c888df4d82ab41f49e41413673d9650fb4 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 11:09:15 +0200 Subject: [PATCH 11/16] f Account for `CMH` using `LengthLimitedRead` --- src/message_handler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/message_handler.rs b/src/message_handler.rs index cebd1ea07..25995a481 100644 --- a/src/message_handler.rs +++ b/src/message_handler.rs @@ -10,6 +10,7 @@ use crate::liquidity::LiquiditySource; use lightning::ln::peer_handler::CustomMessageHandler; use lightning::ln::wire::CustomMessageReader; use lightning::util::logger::Logger; +use lightning::util::ser::LengthLimitedRead; use lightning_types::features::{InitFeatures, NodeFeatures}; @@ -47,7 +48,7 @@ where { type CustomMessage = RawLSPSMessage; - fn read( + fn read( &self, message_type: u16, buffer: &mut RD, ) -> Result, lightning::ln::msgs::DecodeError> { match self { From 2502d599bd7e4017e03cba6f1e3d0d27f6391069 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 11:10:06 +0200 Subject: [PATCH 12/16] f Account for `get_current_default_config` changes --- src/liquidity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/liquidity.rs b/src/liquidity.rs index 8681090a2..54c19a5ca 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -671,7 +671,7 @@ where return; } - let mut config = *self.channel_manager.get_current_default_configuration(); + let mut config = self.channel_manager.get_current_default_configuration().clone(); // We set these LSP-specific values during Node building, here we're making sure it's actually set. debug_assert_eq!( From 8cb15cda7f8fd78d469880083f613d9ad163c9b9 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 11:13:34 +0200 Subject: [PATCH 13/16] f Account for BOLT12 methods taking `RouteParamsConfig` arg --- src/payment/bolt12.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index 8006f4bb9..5359bb0ad 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -20,6 +20,7 @@ use lightning::offers::invoice::Bolt12Invoice; use lightning::offers::offer::{Amount, Offer, Quantity}; use lightning::offers::parse::Bolt12SemanticError; use lightning::offers::refund::Refund; +use lightning::routing::router::RouteParametersConfig; use lightning::util::string::UntrustedString; use rand::RngCore; @@ -67,7 +68,7 @@ impl Bolt12Payment { rand::thread_rng().fill_bytes(&mut random_bytes); let payment_id = PaymentId(random_bytes); let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); - let max_total_routing_fee_msat = None; + let route_params_config = RouteParametersConfig::default(); let offer_amount_msat = match offer.amount() { Some(Amount::Bitcoin { amount_msats }) => amount_msats, @@ -88,7 +89,7 @@ impl Bolt12Payment { payer_note.clone(), payment_id, retry_strategy, - max_total_routing_fee_msat, + route_params_config, ) { Ok(()) => { let payee_pubkey = offer.issuer_signing_pubkey(); @@ -169,7 +170,7 @@ impl Bolt12Payment { rand::thread_rng().fill_bytes(&mut random_bytes); let payment_id = PaymentId(random_bytes); let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); - let max_total_routing_fee_msat = None; + let route_params_config = RouteParametersConfig::default(); let offer_amount_msat = match offer.amount() { Some(Amount::Bitcoin { amount_msats }) => amount_msats, @@ -194,7 +195,7 @@ impl Bolt12Payment { payer_note.clone(), payment_id, retry_strategy, - max_total_routing_fee_msat, + route_params_config, ) { Ok(()) => { let payee_pubkey = offer.issuer_signing_pubkey(); @@ -363,7 +364,7 @@ impl Bolt12Payment { .duration_since(UNIX_EPOCH) .unwrap(); let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); - let max_total_routing_fee_msat = None; + let route_params_config = RouteParametersConfig::default(); let mut refund_builder = self .channel_manager @@ -372,7 +373,7 @@ impl Bolt12Payment { absolute_expiry, payment_id, retry_strategy, - max_total_routing_fee_msat, + route_params_config, ) .map_err(|e| { log_error!(self.logger, "Failed to create refund builder: {:?}", e); From 00d4e0fe0d5206fe3cab9797e08779f016023d00 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 14:13:40 +0200 Subject: [PATCH 14/16] f Account for `async`'ified `ChangeDestinationSource` .. we implement the `async` version of `ChangeDestinationSource`, and, to make all involved objects `Send`, we drop the `Deref` generics for concrete `Arc`s everywhere. --- src/types.rs | 7 +-- src/wallet/mod.rs | 124 +++++++++++++++------------------------------- 2 files changed, 42 insertions(+), 89 deletions(-) diff --git a/src/types.rs b/src/types.rs index 3103ead3f..659a4369a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -76,11 +76,8 @@ pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManager< pub(crate) type Broadcaster = crate::tx_broadcaster::TransactionBroadcaster>; -pub(crate) type Wallet = - crate::wallet::Wallet, Arc, Arc>; - -pub(crate) type KeysManager = - crate::wallet::WalletKeysManager, Arc, Arc>; +pub(crate) type Wallet = crate::wallet::Wallet; +pub(crate) type KeysManager = crate::wallet::WalletKeysManager; pub(crate) type Router = DefaultRouter< Arc, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 33abb6b44..3432318b1 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -8,12 +8,12 @@ use persist::KVStoreWalletPersister; use crate::config::Config; -use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger}; +use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; -use crate::fee_estimator::{ConfirmationTarget, FeeEstimator}; +use crate::fee_estimator::{ConfirmationTarget, FeeEstimator, OnchainFeeEstimator}; use crate::payment::store::ConfirmationStatus; use crate::payment::{PaymentDetails, PaymentDirection, PaymentStatus}; -use crate::types::PaymentStore; +use crate::types::{Broadcaster, PaymentStore}; use crate::Error; use lightning::chain::chaininterface::BroadcasterInterface; @@ -50,7 +50,8 @@ use bitcoin::{ WitnessProgram, WitnessVersion, }; -use std::ops::Deref; +use std::future::Future; +use std::pin::Pin; use std::str::FromStr; use std::sync::{Arc, Mutex}; @@ -63,32 +64,23 @@ pub(crate) enum OnchainSendAmount { pub(crate) mod persist; pub(crate) mod ser; -pub(crate) struct Wallet -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +pub(crate) struct Wallet { // A BDK on-chain wallet. inner: Mutex>, persister: Mutex, - broadcaster: B, - fee_estimator: E, + broadcaster: Arc, + fee_estimator: Arc, payment_store: Arc, config: Arc, - logger: L, + logger: Arc, } -impl Wallet -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +impl Wallet { pub(crate) fn new( wallet: bdk_wallet::PersistedWallet, - wallet_persister: KVStoreWalletPersister, broadcaster: B, fee_estimator: E, - payment_store: Arc, config: Arc, logger: L, + wallet_persister: KVStoreWalletPersister, broadcaster: Arc, + fee_estimator: Arc, payment_store: Arc, + config: Arc, logger: Arc, ) -> Self { let inner = Mutex::new(wallet); let persister = Mutex::new(wallet_persister); @@ -559,12 +551,7 @@ where } } -impl Listen for Wallet -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +impl Listen for Wallet { fn filtered_block_connected( &self, _header: &bitcoin::block::Header, _txdata: &lightning::chain::transaction::TransactionData, _height: u32, @@ -624,12 +611,7 @@ where } } -impl WalletSource for Wallet -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +impl WalletSource for Wallet { fn list_confirmed_utxos(&self) -> Result, ()> { let locked_wallet = self.inner.lock().unwrap(); let mut utxos = Vec::new(); @@ -766,30 +748,20 @@ where /// Similar to [`KeysManager`], but overrides the destination and shutdown scripts so they are /// directly spendable by the BDK wallet. -pub(crate) struct WalletKeysManager -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +pub(crate) struct WalletKeysManager { inner: KeysManager, - wallet: Arc>, - logger: L, + wallet: Arc, + logger: Arc, } -impl WalletKeysManager -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +impl WalletKeysManager { /// Constructs a `WalletKeysManager` that overrides the destination and shutdown scripts. /// /// See [`KeysManager::new`] for more information on `seed`, `starting_time_secs`, and /// `starting_time_nanos`. pub fn new( - seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, - wallet: Arc>, logger: L, + seed: &[u8; 32], starting_time_secs: u64, starting_time_nanos: u32, wallet: Arc, + logger: Arc, ) -> Self { let inner = KeysManager::new(seed, starting_time_secs, starting_time_nanos); Self { inner, wallet, logger } @@ -808,12 +780,7 @@ where } } -impl NodeSigner for WalletKeysManager -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +impl NodeSigner for WalletKeysManager { fn get_node_id(&self, recipient: Recipient) -> Result { self.inner.get_node_id(recipient) } @@ -845,12 +812,7 @@ where } } -impl OutputSpender for WalletKeysManager -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +impl OutputSpender for WalletKeysManager { /// See [`KeysManager::spend_spendable_outputs`] for documentation on this method. fn spend_spendable_outputs( &self, descriptors: &[&SpendableOutputDescriptor], outputs: Vec, @@ -868,23 +830,13 @@ where } } -impl EntropySource for WalletKeysManager -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +impl EntropySource for WalletKeysManager { fn get_secure_random_bytes(&self) -> [u8; 32] { self.inner.get_secure_random_bytes() } } -impl SignerProvider for WalletKeysManager -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ +impl SignerProvider for WalletKeysManager { type EcdsaSigner = InMemorySigner; fn generate_channel_keys_id(&self, inbound: bool, user_channel_id: u128) -> [u8; 32] { @@ -922,16 +874,20 @@ where } } -impl ChangeDestinationSource for WalletKeysManager -where - B::Target: BroadcasterInterface, - E::Target: FeeEstimator, - L::Target: LdkLogger, -{ - fn get_change_destination_script(&self) -> Result { - let address = self.wallet.get_new_internal_address().map_err(|e| { - log_error!(self.logger, "Failed to retrieve new address from wallet: {}", e); - })?; - Ok(address.script_pubkey()) +impl ChangeDestinationSource for WalletKeysManager { + fn get_change_destination_script<'a>( + &self, + ) -> Pin> + Send + 'a>> { + let wallet = Arc::clone(&self.wallet); + let logger = Arc::clone(&self.logger); + Box::pin(async move { + wallet + .get_new_internal_address() + .map_err(|e| { + log_error!(logger, "Failed to retrieve new address from wallet: {}", e); + }) + .map(|addr| addr.script_pubkey()) + .map_err(|_| ()) + }) } } From a12ceafbe381d9fe8b94df9f6765f2da0c753fec Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 14:37:38 +0200 Subject: [PATCH 15/16] f Account for changes in BOLT11 interface --- bindings/ldk_node.udl | 30 ++--- src/config.rs | 12 +- src/payment/bolt11.rs | 215 ++++++++++++++++++-------------- src/payment/mod.rs | 84 ------------- src/payment/spontaneous.rs | 49 ++++---- src/uniffi_types.rs | 3 +- tests/integration_tests_rust.rs | 14 +-- 7 files changed, 173 insertions(+), 234 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 2bb33b941..75354d4a9 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -12,7 +12,7 @@ dictionary Config { sequence trusted_peers_0conf; u64 probing_liquidity_limit_multiplier; AnchorChannelsConfig? anchor_channels_config; - SendingParameters? sending_parameters; + RouteParametersConfig? route_parameters; }; dictionary AnchorChannelsConfig { @@ -166,13 +166,13 @@ interface Bolt11InvoiceDescription { interface Bolt11Payment { [Throws=NodeError] - PaymentId send([ByRef]Bolt11Invoice invoice, SendingParameters? sending_parameters); + PaymentId send([ByRef]Bolt11Invoice invoice, RouteParametersConfig? route_parameters); [Throws=NodeError] - PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, SendingParameters? sending_parameters); + PaymentId send_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, RouteParametersConfig? route_parameters); [Throws=NodeError] - void send_probes([ByRef]Bolt11Invoice invoice); + void send_probes([ByRef]Bolt11Invoice invoice, RouteParametersConfig? route_parameters); [Throws=NodeError] - void send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat); + void send_probes_using_amount([ByRef]Bolt11Invoice invoice, u64 amount_msat, RouteParametersConfig? route_parameters); [Throws=NodeError] void claim_for_hash(PaymentHash payment_hash, u64 claimable_amount_msat, PaymentPreimage preimage); [Throws=NodeError] @@ -208,9 +208,9 @@ interface Bolt12Payment { interface SpontaneousPayment { [Throws=NodeError] - PaymentId send(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters); + PaymentId send(u64 amount_msat, PublicKey node_id, RouteParametersConfig? route_parameters); [Throws=NodeError] - PaymentId send_with_custom_tlvs(u64 amount_msat, PublicKey node_id, SendingParameters? sending_parameters, sequence custom_tlvs); + PaymentId send_with_custom_tlvs(u64 amount_msat, PublicKey node_id, RouteParametersConfig? route_parameters, sequence custom_tlvs); [Throws=NodeError] void send_probes(u64 amount_msat, PublicKey node_id); }; @@ -446,11 +446,11 @@ dictionary PaymentDetails { u64 latest_update_timestamp; }; -dictionary SendingParameters { - MaxTotalRoutingFeeLimit? max_total_routing_fee_msat; - u32? max_total_cltv_expiry_delta; - u8? max_path_count; - u8? max_channel_saturation_power_of_half; +dictionary RouteParametersConfig { + u64? max_total_routing_fee_msat; + u32 max_total_cltv_expiry_delta; + u8 max_path_count; + u8 max_channel_saturation_power_of_half; }; dictionary CustomTlvRecord { @@ -511,12 +511,6 @@ enum LSPS1PaymentState { "Refunded", }; -[Enum] -interface MaxTotalRoutingFeeLimit { - None (); - Some ( u64 amount_msat ); -}; - [NonExhaustive] enum Network { "Bitcoin", diff --git a/src/config.rs b/src/config.rs index 4a39c1b56..a87a31924 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,10 +8,10 @@ //! Objects for configuring the node. use crate::logger::LogLevel; -use crate::payment::SendingParameters; use lightning::ln::msgs::SocketAddress; use lightning::routing::gossip::NodeAlias; +use lightning::routing::router::RouteParametersConfig; use lightning::util::config::ChannelConfig as LdkChannelConfig; use lightning::util::config::MaxDustHTLCExposure as LdkMaxDustHTLCExposure; use lightning::util::config::UserConfig; @@ -102,9 +102,9 @@ pub const WALLET_KEYS_SEED_LEN: usize = 64; /// | `probing_liquidity_limit_multiplier` | 3 | /// | `log_level` | Debug | /// | `anchor_channels_config` | Some(..) | -/// | `sending_parameters` | None | +/// | `route_parameters` | None | /// -/// See [`AnchorChannelsConfig`] and [`SendingParameters`] for more information regarding their +/// See [`AnchorChannelsConfig`] and [`RouteParametersConfig`] for more information regarding their /// respective default values. /// /// [`Node`]: crate::Node @@ -161,12 +161,12 @@ pub struct Config { pub anchor_channels_config: Option, /// Configuration options for payment routing and pathfinding. /// - /// Setting the `SendingParameters` provides flexibility to customize how payments are routed, + /// Setting the [`RouteParametersConfig`] provides flexibility to customize how payments are routed, /// including setting limits on routing fees, CLTV expiry, and channel utilization. /// /// **Note:** If unset, default parameters will be used, and you will be able to override the /// parameters on a per-payment basis in the corresponding method calls. - pub sending_parameters: Option, + pub route_parameters: Option, } impl Default for Config { @@ -179,7 +179,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()), - sending_parameters: None, + route_parameters: None, node_alias: None, } } diff --git a/src/payment/bolt11.rs b/src/payment/bolt11.rs index 052571818..15483eb4e 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -19,15 +19,13 @@ use crate::payment::store::{ LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, }; -use crate::payment::SendingParameters; use crate::peer_store::{PeerInfo, PeerStore}; use crate::types::{ChannelManager, PaymentStore}; -use lightning::ln::bolt11_payment; use lightning::ln::channelmanager::{ - Bolt11InvoiceParameters, PaymentId, RecipientOnionFields, Retry, RetryableSendFailure, + Bolt11InvoiceParameters, Bolt11PaymentError, PaymentId, Retry, RetryableSendFailure, }; -use lightning::routing::router::{PaymentParameters, RouteParameters}; +use lightning::routing::router::{PaymentParameters, RouteParameters, RouteParametersConfig}; use lightning_types::payment::{PaymentHash, PaymentPreimage}; @@ -120,10 +118,10 @@ impl Bolt11Payment { /// Send a payment given an invoice. /// - /// If `sending_parameters` are provided they will override the default as well as the - /// node-wide parameters configured via [`Config::sending_parameters`] on a per-field basis. + /// 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. pub fn send( - &self, invoice: &Bolt11Invoice, sending_parameters: Option, + &self, invoice: &Bolt11Invoice, route_parameters: Option, ) -> Result { let invoice = maybe_convert_invoice(invoice); let rt_lock = self.runtime.read().unwrap(); @@ -131,11 +129,6 @@ impl Bolt11Payment { return Err(Error::NotRunning); } - let (payment_hash, recipient_onion, mut route_params) = bolt11_payment::payment_parameters_from_invoice(&invoice).map_err(|_| { - log_error!(self.logger, "Failed to send payment due to the given invoice being \"zero-amount\". Please use send_using_amount instead."); - Error::InvalidInvoice - })?; - let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); if let Some(payment) = self.payment_store.get(&payment_id) { if payment.status == PaymentStatus::Pending @@ -146,29 +139,27 @@ impl Bolt11Payment { } } - let override_params = - sending_parameters.as_ref().or(self.config.sending_parameters.as_ref()); - if let Some(override_params) = override_params { - override_params - .max_total_routing_fee_msat - .map(|f| route_params.max_total_routing_fee_msat = f.into()); - override_params - .max_total_cltv_expiry_delta - .map(|d| route_params.payment_params.max_total_cltv_expiry_delta = d); - override_params.max_path_count.map(|p| route_params.payment_params.max_path_count = p); - override_params - .max_channel_saturation_power_of_half - .map(|s| route_params.payment_params.max_channel_saturation_power_of_half = s); - }; + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); + let payment_id = PaymentId(invoice.payment_hash().to_byte_array()); + if let Some(payment) = self.payment_store.get(&payment_id) { + if payment.status == PaymentStatus::Pending + || payment.status == PaymentStatus::Succeeded + { + log_error!(self.logger, "Payment error: an invoice must not be paid twice."); + return Err(Error::DuplicatePayment); + } + } - let payment_secret = Some(*invoice.payment_secret()); + let route_parameters = + 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()); - match self.channel_manager.send_payment( - payment_hash, - recipient_onion, + match self.channel_manager.pay_for_bolt11_invoice( + invoice, payment_id, - route_params, + None, + route_parameters, retry_strategy, ) { Ok(()) => { @@ -194,7 +185,19 @@ impl Bolt11Payment { Ok(payment_id) }, - Err(e) => { + Err(Bolt11PaymentError::InvalidAmount) => { + log_error!(self.logger, + "Failed to send payment due to the given invoice being \"zero-amount\". Please use send_using_amount instead." + ); + return Err(Error::InvalidInvoice); + }, + Err(Bolt11PaymentError::InvalidInvoice) => { + log_error!(self.logger, + "Failed to send payment due to the given invoice being invalid or incompatible." + ); + return Err(Error::InvalidInvoice); + }, + Err(Bolt11PaymentError::SendingFailed(e)) => { log_error!(self.logger, "Failed to send payment: {:?}", e); match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), @@ -228,11 +231,11 @@ 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 `sending_parameters` are provided they will override the default as well as the - /// node-wide parameters configured via [`Config::sending_parameters`] on a per-field basis. + /// 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. pub fn send_using_amount( &self, invoice: &Bolt11Invoice, amount_msat: u64, - sending_parameters: Option, + route_parameters: Option, ) -> Result { let invoice = maybe_convert_invoice(invoice); let rt_lock = self.runtime.read().unwrap(); @@ -260,46 +263,16 @@ impl Bolt11Payment { } } - let payment_secret = invoice.payment_secret(); - let expiry_time = invoice.duration_since_epoch().saturating_add(invoice.expiry_time()); - let mut payment_params = PaymentParameters::from_node_id( - invoice.recover_payee_pub_key(), - invoice.min_final_cltv_expiry_delta() as u32, - ) - .with_expiry_time(expiry_time.as_secs()) - .with_route_hints(invoice.route_hints()) - .map_err(|_| Error::InvalidInvoice)?; - if let Some(features) = invoice.features() { - payment_params = payment_params - .with_bolt11_features(features.clone()) - .map_err(|_| Error::InvalidInvoice)?; - } - let mut route_params = - RouteParameters::from_payment_params_and_value(payment_params, amount_msat); - - let override_params = - sending_parameters.as_ref().or(self.config.sending_parameters.as_ref()); - if let Some(override_params) = override_params { - override_params - .max_total_routing_fee_msat - .map(|f| route_params.max_total_routing_fee_msat = f.into()); - override_params - .max_total_cltv_expiry_delta - .map(|d| route_params.payment_params.max_total_cltv_expiry_delta = d); - override_params.max_path_count.map(|p| route_params.payment_params.max_path_count = p); - override_params - .max_channel_saturation_power_of_half - .map(|s| route_params.payment_params.max_channel_saturation_power_of_half = s); - }; - + let route_parameters = + route_parameters.or(self.config.route_parameters).unwrap_or_default(); let retry_strategy = Retry::Timeout(LDK_PAYMENT_RETRY_TIMEOUT); - let recipient_fields = RecipientOnionFields::secret_only(*payment_secret); + let payment_secret = Some(*invoice.payment_secret()); - match self.channel_manager.send_payment( - payment_hash, - recipient_fields, + match self.channel_manager.pay_for_bolt11_invoice( + invoice, payment_id, - route_params, + Some(amount_msat), + route_parameters, retry_strategy, ) { Ok(()) => { @@ -314,7 +287,7 @@ impl Bolt11Payment { let kind = PaymentKind::Bolt11 { hash: payment_hash, preimage: None, - secret: Some(*payment_secret), + secret: payment_secret, }; let payment = PaymentDetails::new( @@ -329,16 +302,28 @@ impl Bolt11Payment { Ok(payment_id) }, - Err(e) => { + Err(Bolt11PaymentError::InvalidAmount) => { + log_error!( + self.logger, + "Failed to send payment due to amount given being insufficient." + ); + return Err(Error::InvalidInvoice); + }, + Err(Bolt11PaymentError::InvalidInvoice) => { + log_error!(self.logger, + "Failed to send payment due to the given invoice being invalid or incompatible." + ); + return Err(Error::InvalidInvoice); + }, + Err(Bolt11PaymentError::SendingFailed(e)) => { log_error!(self.logger, "Failed to send payment: {:?}", e); - match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), _ => { let kind = PaymentKind::Bolt11 { hash: payment_hash, preimage: None, - secret: Some(*payment_secret), + secret: payment_secret, }; let payment = PaymentDetails::new( payment_id, @@ -348,8 +333,8 @@ impl Bolt11Payment { PaymentDirection::Outbound, PaymentStatus::Failed, ); - self.payment_store.insert(payment)?; + self.payment_store.insert(payment)?; Err(Error::PaymentSendingFailed) }, } @@ -741,18 +726,45 @@ impl Bolt11Payment { /// payment. To mitigate this issue, channels with available liquidity less than the required /// amount times [`Config::probing_liquidity_limit_multiplier`] won't be used to send /// pre-flight probes. - pub fn send_probes(&self, invoice: &Bolt11Invoice) -> Result<(), Error> { + /// + /// 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. + pub fn send_probes( + &self, invoice: &Bolt11Invoice, route_parameters: Option, + ) -> Result<(), Error> { let invoice = maybe_convert_invoice(invoice); let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { return Err(Error::NotRunning); } - let (_payment_hash, _recipient_onion, route_params) = bolt11_payment::payment_parameters_from_invoice(&invoice).map_err(|_| { + let payment_params = PaymentParameters::from_bolt11_invoice(invoice).map_err(|e| { + log_error!(self.logger, "Failed to send payment probes: {:?}", e); + Error::InvalidInvoice + })?; + + let amount_msat = invoice.amount_milli_satoshis().ok_or_else(|| { log_error!(self.logger, "Failed to send probes due to the given invoice being \"zero-amount\". Please use send_probes_using_amount instead."); Error::InvalidInvoice })?; + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params, 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, + }) = route_parameters.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 liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier); self.channel_manager @@ -771,9 +783,13 @@ impl Bolt11Payment { /// This can be used to send pre-flight probes for 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. + /// /// See [`Self::send_probes`] for more information. pub fn send_probes_using_amount( &self, invoice: &Bolt11Invoice, amount_msat: u64, + route_parameters: Option, ) -> Result<(), Error> { let invoice = maybe_convert_invoice(invoice); let rt_lock = self.runtime.read().unwrap(); @@ -781,26 +797,39 @@ impl Bolt11Payment { return Err(Error::NotRunning); } - let (_payment_hash, _recipient_onion, route_params) = if let Some(invoice_amount_msat) = - invoice.amount_milli_satoshis() - { + let payment_params = PaymentParameters::from_bolt11_invoice(invoice).map_err(|e| { + log_error!(self.logger, "Failed to send payment probes: {:?}", e); + Error::InvalidInvoice + })?; + + if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() { if amount_msat < invoice_amount_msat { log_error!( self.logger, - "Failed to send probes as the given amount needs to be at least the invoice amount: required {}msat, gave {}msat.", invoice_amount_msat, amount_msat); + "Failed to send probes 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); } + } - bolt11_payment::payment_parameters_from_invoice(&invoice).map_err(|_| { - log_error!(self.logger, "Failed to send probes due to the given invoice unexpectedly being \"zero-amount\"."); - Error::InvalidInvoice - })? - } else { - bolt11_payment::payment_parameters_from_variable_amount_invoice(&invoice, amount_msat).map_err(|_| { - log_error!(self.logger, "Failed to send probes due to the given invoice unexpectedly being not \"zero-amount\"."); - Error::InvalidInvoice - })? - }; + let mut route_params = + RouteParameters::from_payment_params_and_value(payment_params, 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, + }) = route_parameters.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 liquidity_limit_multiplier = Some(self.config.probing_liquidity_limit_multiplier); diff --git a/src/payment/mod.rs b/src/payment/mod.rs index b031e37fd..54f7894dc 100644 --- a/src/payment/mod.rs +++ b/src/payment/mod.rs @@ -22,87 +22,3 @@ pub use store::{ ConfirmationStatus, LSPFeeLimits, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, }; pub use unified_qr::{QrPaymentResult, UnifiedQrPayment}; - -/// Represents information used to send a payment. -#[derive(Clone, Debug, PartialEq)] -pub struct SendingParameters { - /// The maximum total fees, in millisatoshi, that may accrue during route finding. - /// - /// This limit also applies to the total fees that may arise while retrying failed payment - /// paths. - /// - /// Note that values below a few sats may result in some paths being spuriously ignored. - #[cfg(not(feature = "uniffi"))] - pub max_total_routing_fee_msat: Option>, - /// The maximum total fees, in millisatoshi, that may accrue during route finding. - /// - /// This limit also applies to the total fees that may arise while retrying failed payment - /// paths. - /// - /// Note that values below a few sats may result in some paths being spuriously ignored. - #[cfg(feature = "uniffi")] - pub max_total_routing_fee_msat: Option, - /// The maximum total CLTV delta we accept for the route. - /// - /// Defaults to [`DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA`]. - /// - /// [`DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA`]: lightning::routing::router::DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA - pub max_total_cltv_expiry_delta: Option, - /// The maximum number of paths that may be used by (MPP) payments. - /// - /// Defaults to [`DEFAULT_MAX_PATH_COUNT`]. - /// - /// [`DEFAULT_MAX_PATH_COUNT`]: lightning::routing::router::DEFAULT_MAX_PATH_COUNT - pub max_path_count: Option, - /// Selects the maximum share of a channel's total capacity which will be sent over a channel, - /// as a power of 1/2. - /// - /// A higher value prefers to send the payment using more MPP parts whereas - /// a lower value prefers to send larger MPP parts, potentially saturating channels and - /// increasing failure probability for those paths. - /// - /// Note that this restriction will be relaxed during pathfinding after paths which meet this - /// restriction have been found. While paths which meet this criteria will be searched for, it - /// is ultimately up to the scorer to select them over other paths. - /// - /// Examples: - /// - /// | Value | Max Proportion of Channel Capacity Used | - /// |-------|-----------------------------------------| - /// | 0 | Up to 100% of the channel’s capacity | - /// | 1 | Up to 50% of the channel’s capacity | - /// | 2 | Up to 25% of the channel’s capacity | - /// | 3 | Up to 12.5% of the channel’s capacity | - /// - /// Default value: 2 - pub max_channel_saturation_power_of_half: Option, -} - -/// Represents the possible states of [`SendingParameters::max_total_routing_fee_msat`]. -// -// Required only in bindings as UniFFI can't expose `Option>`. -#[cfg(feature = "uniffi")] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum MaxTotalRoutingFeeLimit { - None, - Some { amount_msat: u64 }, -} - -#[cfg(feature = "uniffi")] -impl From for Option { - fn from(value: MaxTotalRoutingFeeLimit) -> Self { - match value { - MaxTotalRoutingFeeLimit::Some { amount_msat } => Some(amount_msat), - MaxTotalRoutingFeeLimit::None => None, - } - } -} - -#[cfg(feature = "uniffi")] -impl From> for MaxTotalRoutingFeeLimit { - fn from(value: Option) -> Self { - value.map_or(MaxTotalRoutingFeeLimit::None, |amount_msat| MaxTotalRoutingFeeLimit::Some { - amount_msat, - }) - } -} diff --git a/src/payment/spontaneous.rs b/src/payment/spontaneous.rs index 1508b6cd8..821092e9d 100644 --- a/src/payment/spontaneous.rs +++ b/src/payment/spontaneous.rs @@ -11,11 +11,10 @@ use crate::config::{Config, LDK_PAYMENT_RETRY_TIMEOUT}; use crate::error::Error; use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; -use crate::payment::SendingParameters; use crate::types::{ChannelManager, CustomTlvRecord, KeysManager, PaymentStore}; use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry, RetryableSendFailure}; -use lightning::routing::router::{PaymentParameters, RouteParameters}; +use lightning::routing::router::{PaymentParameters, RouteParameters, RouteParametersConfig}; use lightning::sign::EntropySource; use lightning_types::payment::{PaymentHash, PaymentPreimage}; @@ -52,25 +51,26 @@ impl SpontaneousPayment { /// Send a spontaneous aka. "keysend", payment. /// - /// If `sending_parameters` are provided they will override the default as well as the - /// node-wide parameters configured via [`Config::sending_parameters`] on a per-field basis. + /// 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. pub fn send( - &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, + &self, amount_msat: u64, node_id: PublicKey, + route_parameters: Option, ) -> Result { - self.send_inner(amount_msat, node_id, sending_parameters, None) + self.send_inner(amount_msat, node_id, route_parameters, None) } /// Send a spontaneous payment including a list of custom TLVs. pub fn send_with_custom_tlvs( - &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, - custom_tlvs: Vec, + &self, amount_msat: u64, node_id: PublicKey, + route_parameters: Option, custom_tlvs: Vec, ) -> Result { - self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs)) + self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs)) } fn send_inner( - &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, - custom_tlvs: Option>, + &self, amount_msat: u64, node_id: PublicKey, + route_parameters: Option, custom_tlvs: Option>, ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { @@ -95,20 +95,19 @@ impl SpontaneousPayment { amount_msat, ); - let override_params = - sending_parameters.as_ref().or(self.config.sending_parameters.as_ref()); - if let Some(override_params) = override_params { - override_params - .max_total_routing_fee_msat - .map(|f| route_params.max_total_routing_fee_msat = f.into()); - override_params - .max_total_cltv_expiry_delta - .map(|d| route_params.payment_params.max_total_cltv_expiry_delta = d); - override_params.max_path_count.map(|p| route_params.payment_params.max_path_count = p); - override_params - .max_channel_saturation_power_of_half - .map(|s| route_params.payment_params.max_channel_saturation_power_of_half = s); - }; + if let Some(RouteParametersConfig { + max_total_routing_fee_msat, + max_total_cltv_expiry_delta, + max_path_count, + max_channel_saturation_power_of_half, + }) = route_parameters.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 recipient_fields = match custom_tlvs { Some(tlvs) => RecipientOnionFields::spontaneous_empty() diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index c23007ba7..a8d1421bb 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -20,7 +20,7 @@ pub use crate::logger::{LogLevel, LogRecord, LogWriter}; pub use crate::payment::store::{ ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus, }; -pub use crate::payment::{MaxTotalRoutingFeeLimit, QrPaymentResult, SendingParameters}; +pub use crate::payment::QrPaymentResult; pub use lightning::chain::channelmonitor::BalanceSource; pub use lightning::events::{ClosureReason, PaymentFailureReason}; @@ -29,6 +29,7 @@ pub use lightning::offers::invoice::Bolt12Invoice; pub use lightning::offers::offer::{Offer, OfferId}; pub use lightning::offers::refund::Refund; pub use lightning::routing::gossip::{NodeAlias, NodeId, RoutingFees}; +pub use lightning::routing::router::RouteParametersConfig; pub use lightning::util::string::UntrustedString; pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index db48eca23..1dd9824ab 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -20,12 +20,12 @@ use ldk_node::config::EsploraSyncConfig; use ldk_node::liquidity::LSPS2ServiceConfig; use ldk_node::payment::{ ConfirmationStatus, PaymentDirection, PaymentKind, PaymentStatus, QrPaymentResult, - SendingParameters, }; use ldk_node::{Builder, Event, NodeError}; use lightning::ln::channelmanager::PaymentId; use lightning::routing::gossip::{NodeAlias, NodeId}; +use lightning::routing::router::RouteParametersConfig; use lightning::util::persist::KVStore; use lightning_invoice::{Bolt11InvoiceDescription, Description}; @@ -200,11 +200,11 @@ fn multi_hop_sending() { // Sleep a bit for gossip to propagate. std::thread::sleep(std::time::Duration::from_secs(1)); - let sending_params = SendingParameters { - max_total_routing_fee_msat: Some(Some(75_000).into()), - max_total_cltv_expiry_delta: Some(1000), - max_path_count: Some(10), - max_channel_saturation_power_of_half: Some(2), + let route_params = RouteParametersConfig { + max_total_routing_fee_msat: Some(75_000), + max_total_cltv_expiry_delta: 1000, + max_path_count: 10, + max_channel_saturation_power_of_half: 2, }; let invoice_description = @@ -213,7 +213,7 @@ fn multi_hop_sending() { .bolt11_payment() .receive(2_500_000, &invoice_description.clone().into(), 9217) .unwrap(); - nodes[0].bolt11_payment().send(&invoice, Some(sending_params)).unwrap(); + nodes[0].bolt11_payment().send(&invoice, Some(route_params)).unwrap(); expect_event!(nodes[1], PaymentForwarded); From a88e206238aad0a5629c8f83e71384fcbfbe4578 Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Thu, 5 Jun 2025 17:38:54 +0200 Subject: [PATCH 16/16] storage: store the bolt12invoice invoice This commit enhances the BOLT12 payment storage capabilities by adding support for storing the actual BOLT12 invoice along with payment details. We modify the following components: - Added `bolt12_invoice` field to `PaymentKind::Bolt12Offer` and `PaymentKind::Bolt12Refund` variants in the payment store - Updated event handling in `event.rs` to capture and store the BOLT12 invoice when payments are processed - Modified BOLT12 payment creation logic to initialize the new field - Enhanced payment update mechanism to handle BOLT12 invoice updates - Added comprehensive test coverage for the new invoice storage feature This improvement allows for proof of payment and to access the BOLT12 invoice details. Signed-off-by: Vincenzo Palazzo --- src/event.rs | 3 +++ src/payment/bolt12.rs | 7 +++++++ src/payment/store.rs | 36 ++++++++++++++++++++++++++------- tests/integration_tests_rust.rs | 8 +++++++- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/event.rs b/src/event.rs index f1bfd8049..f7cc8760e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -745,6 +745,7 @@ where offer_id, payer_note, quantity, + bolt12_invoice: None, }; let payment = PaymentDetails::new( @@ -949,6 +950,7 @@ where payment_preimage, payment_hash, fee_paid_msat, + bolt12_invoice, .. } => { let payment_id = if let Some(id) = payment_id { @@ -963,6 +965,7 @@ where preimage: Some(Some(payment_preimage)), fee_paid_msat: Some(fee_paid_msat), status: Some(PaymentStatus::Succeeded), + bolt12_invoice: Some(bolt12_invoice), ..PaymentDetailsUpdate::new(payment_id) }; diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index 5359bb0ad..b490d1adb 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -15,6 +15,7 @@ use crate::logger::{log_error, log_info, LdkLogger, Logger}; use crate::payment::store::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus}; use crate::types::{ChannelManager, PaymentStore}; +use lightning::events::PaidBolt12Invoice; use lightning::ln::channelmanager::{PaymentId, Retry}; use lightning::offers::invoice::Bolt12Invoice; use lightning::offers::offer::{Amount, Offer, Quantity}; @@ -107,6 +108,7 @@ impl Bolt12Payment { offer_id: offer.id(), payer_note: payer_note.map(UntrustedString), quantity, + bolt12_invoice: None, }; let payment = PaymentDetails::new( payment_id, @@ -132,6 +134,7 @@ impl Bolt12Payment { offer_id: offer.id(), payer_note: payer_note.map(UntrustedString), quantity, + bolt12_invoice: None, }; let payment = PaymentDetails::new( payment_id, @@ -213,6 +216,7 @@ impl Bolt12Payment { offer_id: offer.id(), payer_note: payer_note.map(UntrustedString), quantity, + bolt12_invoice: None, }; let payment = PaymentDetails::new( payment_id, @@ -238,6 +242,7 @@ impl Bolt12Payment { offer_id: offer.id(), payer_note: payer_note.map(UntrustedString), quantity, + bolt12_invoice: None, }; let payment = PaymentDetails::new( payment_id, @@ -335,6 +340,7 @@ impl Bolt12Payment { secret: None, payer_note: refund.payer_note().map(|note| UntrustedString(note.0.to_string())), quantity: refund.quantity(), + bolt12_invoice: Some(PaidBolt12Invoice::Bolt12Invoice(invoice.clone())), }; let payment = PaymentDetails::new( @@ -401,6 +407,7 @@ impl Bolt12Payment { secret: None, payer_note: payer_note.map(|note| UntrustedString(note)), quantity, + bolt12_invoice: None, }; let payment = PaymentDetails::new( payment_id, diff --git a/src/payment/store.rs b/src/payment/store.rs index 75b2b1b2a..b57d31698 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -5,6 +5,7 @@ // http://opensource.org/licenses/MIT>, at your option. You may not use this file except in // accordance with one or both of these licenses. +use lightning::events::PaidBolt12Invoice; use lightning::ln::channelmanager::PaymentId; use lightning::ln::msgs::DecodeError; use lightning::offers::offer::OfferId; @@ -293,6 +294,18 @@ impl StorableObject for PaymentDetails { } } + if let Some(ref bolt12_invoice_opt) = update.bolt12_invoice { + match self.kind { + PaymentKind::Bolt12Offer { ref mut bolt12_invoice, .. } => { + update_if_necessary!(*bolt12_invoice, bolt12_invoice_opt.clone()); + }, + PaymentKind::Bolt12Refund { ref mut bolt12_invoice, .. } => { + update_if_necessary!(*bolt12_invoice, bolt12_invoice_opt.clone()); + }, + _ => {}, + } + } + if updated { self.latest_update_timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -417,6 +430,8 @@ pub enum PaymentKind { /// /// This will always be `None` for payments serialized with version `v0.3.0`. quantity: Option, + /// TODO: need to be documented + bolt12_invoice: Option, }, /// A [BOLT 12] 'refund' payment, i.e., a payment for a [`Refund`]. /// @@ -437,6 +452,8 @@ pub enum PaymentKind { /// /// This will always be `None` for payments serialized with version `v0.3.0`. quantity: Option, + /// TODO: need to be documented + bolt12_invoice: Option, }, /// A spontaneous ("keysend") payment. Spontaneous { @@ -471,6 +488,7 @@ impl_writeable_tlv_based_enum!(PaymentKind, (3, quantity, option), (4, secret, option), (6, offer_id, required), + (8, bolt12_invoice, option), }, (8, Spontaneous) => { (0, hash, required), @@ -482,6 +500,7 @@ impl_writeable_tlv_based_enum!(PaymentKind, (2, preimage, option), (3, quantity, option), (4, secret, option), + (6, bolt12_invoice, option), } ); @@ -542,6 +561,7 @@ pub(crate) struct PaymentDetailsUpdate { pub direction: Option, pub status: Option, pub confirmation_status: Option, + pub bolt12_invoice: Option>, } impl PaymentDetailsUpdate { @@ -557,19 +577,20 @@ impl PaymentDetailsUpdate { direction: None, status: None, confirmation_status: None, + bolt12_invoice: None, } } } impl From<&PaymentDetails> for PaymentDetailsUpdate { fn from(value: &PaymentDetails) -> Self { - let (hash, preimage, secret) = match value.kind { - PaymentKind::Bolt11 { hash, preimage, secret, .. } => (Some(hash), preimage, secret), - PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => (Some(hash), preimage, secret), - PaymentKind::Bolt12Offer { hash, preimage, secret, .. } => (hash, preimage, secret), - PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => (hash, preimage, secret), - PaymentKind::Spontaneous { hash, preimage, .. } => (Some(hash), preimage, None), - _ => (None, None, None), + let (hash, preimage, secret, bolt12_invoice) = match value.kind { + PaymentKind::Bolt11 { hash, preimage, secret, .. } => (Some(hash), preimage, secret, None), + PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => (Some(hash), preimage, secret, None), + PaymentKind::Bolt12Offer { hash, preimage, secret, ref bolt12_invoice, .. } => (hash, preimage, secret, Some(bolt12_invoice)), + PaymentKind::Bolt12Refund { hash, preimage, secret, ref bolt12_invoice, .. } => (hash, preimage, secret, Some(bolt12_invoice)), + PaymentKind::Spontaneous { hash, preimage, .. } => (Some(hash), preimage, None, None), + _ => (None, None, None, None), }; let confirmation_status = match value.kind { @@ -595,6 +616,7 @@ impl From<&PaymentDetails> for PaymentDetailsUpdate { direction: Some(value.direction), status: Some(value.status), confirmation_status, + bolt12_invoice: bolt12_invoice.cloned(), } } } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 1dd9824ab..7a60a5f00 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -817,12 +817,14 @@ fn simple_bolt12_send_receive() { offer_id, quantity: ref qty, payer_note: ref note, + bolt12_invoice: ref invoice, } => { assert!(hash.is_some()); assert!(preimage.is_some()); assert_eq!(offer_id, offer.id()); assert_eq!(&expected_quantity, qty); assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0); + assert!(invoice.is_some()); //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 //API currently doesn't allow to do that. }, @@ -883,12 +885,14 @@ fn simple_bolt12_send_receive() { offer_id, quantity: ref qty, payer_note: ref note, + bolt12_invoice: ref invoice, } => { assert!(hash.is_some()); assert!(preimage.is_some()); assert_eq!(offer_id, offer.id()); assert_eq!(&expected_quantity, qty); assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0); + assert!(invoice.is_some()); //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 //API currently doesn't allow to do that. hash.unwrap() @@ -950,11 +954,13 @@ fn simple_bolt12_send_receive() { secret: _, quantity: ref qty, payer_note: ref note, + bolt12_invoice: ref invoice, } => { assert!(hash.is_some()); assert!(preimage.is_some()); assert_eq!(&expected_quantity, qty); - assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0) + assert_eq!(expected_payer_note.unwrap(), note.clone().unwrap().0); + assert!(invoice.is_some()); //TODO: We should eventually set and assert the secret sender-side, too, but the BOLT12 //API currently doesn't allow to do that. },