From 1b0f3121c780d8d0c12d340a909232463c62a3ec Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 31 Jul 2025 08:54:54 +0200 Subject: [PATCH 01/45] Stop migrating spendable outputs from pre-v0.3 format As part of the version v0.3 release we switched to the upstreamed `OutputSweeper` which slightly changed our serialization format, having us run a migration step on startup for backwards compatibility ever since. Here we drop the migration code running on startup, for simplicity's sake, but also because it's going to be async going forward and we currently don't have a runtime available on startup (which might change soon, but still). As the v0.3 release now well over a year ago, it's very unlikely that there are any v0.2 (or even v0.3) users left. If there are any affected users left, they'll first have to upgrade to any version pre-v0.7, startup, and then upgrade to v0.7 or later. --- src/balance.rs | 13 +++++-- src/builder.rs | 16 +------- src/io/mod.rs | 5 --- src/io/utils.rs | 100 +----------------------------------------------- src/lib.rs | 1 - src/sweep.rs | 47 ----------------------- 6 files changed, 12 insertions(+), 170 deletions(-) delete mode 100644 src/sweep.rs diff --git a/src/balance.rs b/src/balance.rs index b5e2f5eb7..d0ebc310b 100644 --- a/src/balance.rs +++ b/src/balance.rs @@ -5,17 +5,16 @@ // 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 crate::sweep::value_from_descriptor; - use lightning::chain::channelmonitor::Balance as LdkBalance; use lightning::chain::channelmonitor::BalanceSource; use lightning::ln::types::ChannelId; +use lightning::sign::SpendableOutputDescriptor; use lightning::util::sweep::{OutputSpendStatus, TrackedSpendableOutput}; use lightning_types::payment::{PaymentHash, PaymentPreimage}; use bitcoin::secp256k1::PublicKey; -use bitcoin::{BlockHash, Txid}; +use bitcoin::{Amount, BlockHash, Txid}; /// Details of the known available balances returned by [`Node::list_balances`]. /// @@ -385,3 +384,11 @@ impl PendingSweepBalance { } } } + +fn value_from_descriptor(descriptor: &SpendableOutputDescriptor) -> Amount { + match &descriptor { + SpendableOutputDescriptor::StaticOutput { output, .. } => output.value, + SpendableOutputDescriptor::DelayedPaymentOutput(output) => output.output.value, + SpendableOutputDescriptor::StaticPaymentOutput(output) => output.output.value, + } +} diff --git a/src/builder.rs b/src/builder.rs index 7f15cced6..6a3746492 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -25,7 +25,7 @@ use crate::io::{ use crate::liquidity::{ LSPS1ClientConfig, LSPS2ClientConfig, LSPS2ServiceConfig, LiquiditySourceBuilder, }; -use crate::logger::{log_error, log_info, LdkLogger, LogLevel, LogWriter, Logger}; +use crate::logger::{log_error, LdkLogger, LogLevel, LogWriter, Logger}; use crate::message_handler::NodeCustomMessageHandler; use crate::peer_store::PeerStore; use crate::runtime::Runtime; @@ -1630,20 +1630,6 @@ fn build_with_store_internal( }, }; - match io::utils::migrate_deprecated_spendable_outputs( - Arc::clone(&output_sweeper), - Arc::clone(&kv_store), - Arc::clone(&logger), - ) { - Ok(()) => { - log_info!(logger, "Successfully migrated OutputSweeper data."); - }, - Err(e) => { - log_error!(logger, "Failed to migrate OutputSweeper data: {}", e); - return Err(BuildError::ReadFailed); - }, - } - let event_queue = match io::utils::read_event_queue(Arc::clone(&kv_store), Arc::clone(&logger)) { Ok(event_queue) => Arc::new(event_queue), diff --git a/src/io/mod.rs b/src/io/mod.rs index 3192dbb86..7a52a5c98 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -27,11 +27,6 @@ pub(crate) const PEER_INFO_PERSISTENCE_KEY: &str = "peers"; pub(crate) const PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE: &str = "payments"; pub(crate) const PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = ""; -/// The spendable output information used to persisted under this prefix until LDK Node v0.3.0. -pub(crate) const DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_PRIMARY_NAMESPACE: &str = - "spendable_outputs"; -pub(crate) const DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = ""; - /// The node metrics will be persisted under this key. pub(crate) const NODE_METRICS_PRIMARY_NAMESPACE: &str = ""; pub(crate) const NODE_METRICS_SECONDARY_NAMESPACE: &str = ""; diff --git a/src/io/utils.rs b/src/io/utils.rs index b5537ed7d..06a1017ba 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -15,7 +15,6 @@ use crate::io::{ }; use crate::logger::{log_error, LdkLogger, Logger}; use crate::peer_store::PeerStore; -use crate::sweep::DeprecatedSpendableOutputInfo; use crate::types::{Broadcaster, DynStore, KeysManager, Sweeper}; use crate::wallet::ser::{ChangeSetDeserWrapper, ChangeSetSerWrapper}; use crate::{Error, EventQueue, NodeMetrics, PaymentDetails}; @@ -33,7 +32,7 @@ use lightning::util::persist::{ }; use lightning::util::ser::{Readable, ReadableArgs, Writeable}; use lightning::util::string::PrintableString; -use lightning::util::sweep::{OutputSpendStatus, OutputSweeper}; +use lightning::util::sweep::OutputSweeper; use bdk_chain::indexer::keychain_txout::ChangeSet as BdkIndexerChangeSet; use bdk_chain::local_chain::ChangeSet as BdkLocalChainChangeSet; @@ -258,103 +257,6 @@ pub(crate) fn read_output_sweeper( }) } -/// Read previously persisted spendable output information from the store and migrate to the -/// upstreamed `OutputSweeper`. -/// -/// We first iterate all `DeprecatedSpendableOutputInfo`s and have them tracked by the new -/// `OutputSweeper`. In order to be certain the initial output spends will happen in a single -/// transaction (and safe on-chain fees), we batch them to happen at current height plus two -/// blocks. Lastly, we remove the previously persisted data once we checked they are tracked and -/// awaiting their initial spend at the correct height. -/// -/// Note that this migration will be run in the `Builder`, i.e., at the time when the migration is -/// happening no background sync is ongoing, so we shouldn't have a risk of interleaving block -/// connections during the migration. -pub(crate) fn migrate_deprecated_spendable_outputs( - sweeper: Arc, kv_store: Arc, logger: L, -) -> Result<(), std::io::Error> -where - L::Target: LdkLogger, -{ - let best_block = sweeper.current_best_block(); - - for stored_key in kv_store.list( - DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, - DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_SECONDARY_NAMESPACE, - )? { - let mut reader = Cursor::new(kv_store.read( - DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, - DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_SECONDARY_NAMESPACE, - &stored_key, - )?); - let output = DeprecatedSpendableOutputInfo::read(&mut reader).map_err(|e| { - log_error!(logger, "Failed to deserialize SpendableOutputInfo: {}", e); - std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Failed to deserialize SpendableOutputInfo", - ) - })?; - let descriptors = vec![output.descriptor.clone()]; - let spend_delay = Some(best_block.height + 2); - sweeper - .track_spendable_outputs(descriptors, output.channel_id, true, spend_delay) - .map_err(|_| { - log_error!(logger, "Failed to track spendable outputs. Aborting migration, will retry in the future."); - std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Failed to track spendable outputs. Aborting migration, will retry in the future.", - ) - })?; - - if let Some(tracked_spendable_output) = - sweeper.tracked_spendable_outputs().iter().find(|o| o.descriptor == output.descriptor) - { - match tracked_spendable_output.status { - OutputSpendStatus::PendingInitialBroadcast { delayed_until_height } => { - if delayed_until_height == spend_delay { - kv_store.remove( - DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, - DEPRECATED_SPENDABLE_OUTPUT_INFO_PERSISTENCE_SECONDARY_NAMESPACE, - &stored_key, - false, - )?; - } else { - debug_assert!(false, "Unexpected status in OutputSweeper migration."); - log_error!(logger, "Unexpected status in OutputSweeper migration."); - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to migrate OutputSweeper state.", - )); - } - }, - _ => { - debug_assert!(false, "Unexpected status in OutputSweeper migration."); - log_error!(logger, "Unexpected status in OutputSweeper migration."); - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to migrate OutputSweeper state.", - )); - }, - } - } else { - debug_assert!( - false, - "OutputSweeper failed to track and persist outputs during migration." - ); - log_error!( - logger, - "OutputSweeper failed to track and persist outputs during migration." - ); - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to migrate OutputSweeper state.", - )); - } - } - - Ok(()) -} - pub(crate) fn read_node_metrics( kv_store: Arc, logger: L, ) -> Result diff --git a/src/lib.rs b/src/lib.rs index 1604d1b46..434127734 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,6 @@ mod message_handler; pub mod payment; mod peer_store; mod runtime; -mod sweep; mod tx_broadcaster; mod types; mod wallet; diff --git a/src/sweep.rs b/src/sweep.rs deleted file mode 100644 index ba10869b8..000000000 --- a/src/sweep.rs +++ /dev/null @@ -1,47 +0,0 @@ -// This file is Copyright its original authors, visible in version control history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license , at your option. You may not use this file except in -// accordance with one or both of these licenses. - -//! The output sweeper used to live here before we upstreamed it to `rust-lightning` and migrated -//! to the upstreamed version with LDK Node v0.3.0 (May 2024). We should drop this module entirely -//! once sufficient time has passed for us to be confident any users completed the migration. - -use lightning::impl_writeable_tlv_based; -use lightning::ln::types::ChannelId; -use lightning::sign::SpendableOutputDescriptor; - -use bitcoin::{Amount, BlockHash, Transaction}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct DeprecatedSpendableOutputInfo { - pub(crate) id: [u8; 32], - pub(crate) descriptor: SpendableOutputDescriptor, - pub(crate) channel_id: Option, - pub(crate) first_broadcast_hash: Option, - pub(crate) latest_broadcast_height: Option, - pub(crate) latest_spending_tx: Option, - pub(crate) confirmation_height: Option, - pub(crate) confirmation_hash: Option, -} - -impl_writeable_tlv_based!(DeprecatedSpendableOutputInfo, { - (0, id, required), - (2, descriptor, required), - (4, channel_id, option), - (6, first_broadcast_hash, option), - (8, latest_broadcast_height, option), - (10, latest_spending_tx, option), - (12, confirmation_height, option), - (14, confirmation_hash, option), -}); - -pub(crate) fn value_from_descriptor(descriptor: &SpendableOutputDescriptor) -> Amount { - match &descriptor { - SpendableOutputDescriptor::StaticOutput { output, .. } => output.value, - SpendableOutputDescriptor::DelayedPaymentOutput(output) => output.output.value, - SpendableOutputDescriptor::StaticPaymentOutput(output) => output.output.value, - } -} From 95a1ee0d605882247542699a68b13403b41cdd9d Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 6 Feb 2025 16:14:34 +0100 Subject: [PATCH 02/45] WIP: Upgrade to LDK 0.2 --- Cargo.toml | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 96a9eea53..1dbb69686 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,36 +28,48 @@ 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", "rest-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 = ["rest-client", "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-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["rest-client", "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/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["std"] } +lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } +lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["std"] } +lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } +lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } +lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } +lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } +lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["rest-client", "rpc-client", "tokio"] } +lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["esplora-async-https", "electrum", "time"] } +lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } +lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } + #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-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rest-client", "rpc-client", "tokio"] } #lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync", features = ["esplora-async-https", "electrum", "time"] } #lightning-liquidity = { path = "../rust-lightning/lightning-liquidity", features = ["std"] } @@ -97,8 +109,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/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["std", "_test_utils"] } #lightning = { path = "../rust-lightning/lightning", features = ["std", "_test_utils"] } proptest = "1.0.0" regex = "1.5.6" From 003cc83f3e8d34f93cacfbd2b8079e5202cfca57 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 12 Jun 2025 16:36:33 +0200 Subject: [PATCH 03/45] f Add `lightning-macros` to alt `Cargo.toml` sources --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1dbb69686..dcaedaf10 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ default = [] #lightning-block-sync = { version = "0.1.0", features = ["rest-client", "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-macros = { version = "0.1.0" } #lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["std"] } #lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } @@ -49,6 +50,7 @@ default = [] #lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["rest-client", "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-macros = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["std"] } lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } @@ -72,6 +74,7 @@ lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", #lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rest-client", "rpc-client", "tokio"] } #lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync", features = ["esplora-async-https", "electrum", "time"] } #lightning-liquidity = { path = "../rust-lightning/lightning-liquidity", features = ["std"] } +#lightning-macros = { path = "../rust-lightning/lightning-macros" } bdk_chain = { version = "0.23.0", default-features = false, features = ["std"] } bdk_esplora = { version = "0.22.0", default-features = false, features = ["async-https-rustls", "tokio"]} From 3292d2e27071d6314febd3df253804c3dab268a5 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:29:02 +0200 Subject: [PATCH 04/45] f Account for renamed `lightning-liquidity` types --- bindings/ldk_node.udl | 42 ++++++++++----------- src/ffi/types.rs | 28 +++++++------- src/liquidity.rs | 86 +++++++++++++++++++++---------------------- 3 files changed, 79 insertions(+), 77 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 076d7fc9b..ac8875742 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -254,7 +254,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] @@ -469,13 +469,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; @@ -485,22 +485,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; @@ -509,13 +509,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", @@ -861,7 +861,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/ffi/types.rs b/src/ffi/types.rs index 984e4da8f..73a632597 100644 --- a/src/ffi/types.rs +++ b/src/ffi/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, @@ -33,8 +35,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}; @@ -42,8 +46,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; @@ -1068,11 +1070,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. @@ -1081,8 +1083,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, @@ -1093,7 +1095,7 @@ impl From for Bolt11Payment } } -impl UniffiCustomTypeConverter for OrderId { +impl UniffiCustomTypeConverter for LSPS1OrderId { type Builtin = String; fn into_custom(val: Self::Builtin) -> uniffi::Result { @@ -1105,11 +1107,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 { diff --git a/src/liquidity.rs b/src/liquidity.rs index 6ee8066c1..4eec26e26 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -22,14 +22,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}; @@ -41,7 +43,7 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1}; use tokio::sync::oneshot; -use chrono::{DateTime, Utc}; +use chrono::Utc; use rand::Rng; @@ -62,10 +64,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)] @@ -80,8 +82,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)] @@ -294,7 +296,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, @@ -347,7 +349,7 @@ where ); } }, - Event::LSPS1Client(LSPS1ClientEvent::OrderCreated { + LiquidityEvent::LSPS1Client(LSPS1ClientEvent::OrderCreated { request_id, counterparty_node_id, order_id, @@ -405,7 +407,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, @@ -463,7 +465,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, @@ -502,10 +504,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, @@ -533,7 +533,7 @@ where return; } }, - Event::LSPS2Service(LSPS2ServiceEvent::BuyRequest { + LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::BuyRequest { request_id, counterparty_node_id, opening_fee_params: _, @@ -600,7 +600,7 @@ where return; } }, - Event::LSPS2Service(LSPS2ServiceEvent::OpenChannel { + LiquidityEvent::LSPS2Service(LSPS2ServiceEvent::OpenChannel { their_network_key, amt_to_forward_msat, opening_fee_msat: _, @@ -714,7 +714,7 @@ where }, } }, - Event::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady { + LiquidityEvent::LSPS2Client(LSPS2ClientEvent::OpeningParametersReady { request_id, counterparty_node_id, opening_fee_params_menu, @@ -764,7 +764,7 @@ where ); } }, - Event::LSPS2Client(LSPS2ClientEvent::InvoiceParametersReady { + LiquidityEvent::LSPS2Client(LSPS2ClientEvent::InvoiceParametersReady { request_id, counterparty_node_id, intercept_scid, @@ -904,7 +904,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, @@ -953,7 +953,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(|| { @@ -1127,7 +1127,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)?; @@ -1316,32 +1316,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()), } @@ -1351,11 +1351,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. @@ -1374,8 +1374,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, @@ -1391,7 +1391,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)] @@ -1474,7 +1474,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)?; From 08f02053120cd6a70c1bed86d6068f9e7f8cd0ab Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:31:47 +0200 Subject: [PATCH 05/45] f Move `uniffi` LSPS1PaymentInfo types next to the pre-existing ones .. as having them in two places doesn't make sense --- src/ffi/types.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++--- src/liquidity.rs | 60 +--------------------------------------------- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 73a632597..4e4ee588b 100644 --- a/src/ffi/types.rs +++ b/src/ffi/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, @@ -1068,6 +1066,64 @@ impl std::fmt::Display for Bolt11Invoice { } } +#[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, + } + } +} /// A Lightning payment using BOLT 11. #[derive(Clone, Debug, PartialEq, Eq)] pub struct LSPS1Bolt11PaymentInfo { diff --git a/src/liquidity.rs b/src/liquidity.rs index 4eec26e26..20ea95688 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -1328,66 +1328,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::ffi::LSPS1PaymentInfo; #[derive(Debug, Clone)] pub(crate) struct LSPS2FeeResponse { From 122c855dcbf7bd1abffe77a7d9f656b4071ebeb7 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:34:58 +0200 Subject: [PATCH 06/45] f Account for `ChannelMonitors` being tracked by `ChannelId` --- src/builder.rs | 4 ++-- src/chain/bitcoind.rs | 2 +- src/lib.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 6a3746492..141fd1a54 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1450,8 +1450,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/bitcoind.rs b/src/chain/bitcoind.rs index c282a6141..7c996aca2 100644 --- a/src/chain/bitcoind.rs +++ b/src/chain/bitcoind.rs @@ -173,7 +173,7 @@ impl BitcoindChainSource { 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 434127734..c52bbb4de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1325,8 +1325,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 8a0fc55b77acb58bca3370afcead931d66bcc462 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:53:22 +0200 Subject: [PATCH 07/45] 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 ff94d51d1..382666266 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1043,9 +1043,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 20ea95688..c96a859c8 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -14,7 +14,7 @@ use crate::runtime::Runtime; 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; @@ -1280,9 +1280,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 e149d5e0579feb3ad3b77c97226e8541ce3e8c3b Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 16:58:25 +0200 Subject: [PATCH 08/45] 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 fbac1d1b6..9b0a79d0a 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, @@ -863,10 +863,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, @@ -898,20 +898,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 b02cb9f194d4d21c37845568054157b404277dfe Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 16 May 2025 17:01:20 +0200 Subject: [PATCH 09/45] 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 382666266..8f72c3917 100644 --- a/src/event.rs +++ b/src/event.rs @@ -565,13 +565,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 b09e5902a2ce3cb2bdc630513c4fbccd593e25a0 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 10:32:49 +0200 Subject: [PATCH 10/45] 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 c52bbb4de..48c7b6505 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1328,9 +1328,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 fa48d81685a9215134451efc6064cdf488c51fe3 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 10:41:38 +0200 Subject: [PATCH 11/45] 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 48c7b6505..80273f08f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -520,6 +520,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); @@ -554,6 +557,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 c96a859c8..2db821b53 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -277,13 +277,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 d2c6e97fecc07be84c9a7806e6b80c2383f879dc Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 10:45:04 +0200 Subject: [PATCH 12/45] 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 141fd1a54..80d76baef 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1362,17 +1362,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 8f72c3917..02d9ce1f8 100644 --- a/src/event.rs +++ b/src/event.rs @@ -38,6 +38,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 3b71ce3bc95916bcc79aa66abc8f89743152fc14 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 11:09:15 +0200 Subject: [PATCH 13/45] 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 9f43dea12383bea85ff35d61dc7e75355a76e4e8 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 11:10:06 +0200 Subject: [PATCH 14/45] 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 2db821b53..5cb48c700 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -672,7 +672,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 7785b95de6b5ead1ad10a82fb887c6bf75ec6f66 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 11:13:34 +0200 Subject: [PATCH 15/45] 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 8e10b9f4f..eee3ac436 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -19,6 +19,7 @@ use crate::types::{ChannelManager, PaymentStore}; use lightning::ln::channelmanager::{PaymentId, Retry}; use lightning::offers::offer::{Amount, Offer as LdkOffer, Quantity}; use lightning::offers::parse::Bolt12SemanticError; +use lightning::routing::router::RouteParametersConfig; use lightning::util::string::UntrustedString; use rand::RngCore; @@ -82,7 +83,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, @@ -103,7 +104,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(); @@ -185,7 +186,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, @@ -210,7 +211,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(); @@ -396,7 +397,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 @@ -405,7 +406,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 c646600c18c7fa882c24843a1b803e7a57d20fe6 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 14:13:40 +0200 Subject: [PATCH 16/45] 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 9b0a79d0a..f015ccf97 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); @@ -570,12 +562,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, @@ -635,12 +622,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(); @@ -777,30 +759,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 } @@ -819,12 +791,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) } @@ -856,12 +823,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, @@ -879,23 +841,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] { @@ -933,16 +885,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 955074d3fcb0add140dea0c2e2810ac759835cbd Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 12 Jun 2025 15:53:38 +0200 Subject: [PATCH 17/45] f Account for `PeerStorage` changes --- src/builder.rs | 26 ++++++++++++++++---------- src/types.rs | 2 ++ src/wallet/mod.rs | 6 +++++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 80d76baef..0b19586d3 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -48,7 +48,7 @@ use lightning::routing::router::DefaultRouter; use lightning::routing::scoring::{ ProbabilisticScorer, ProbabilisticScoringDecayParameters, ProbabilisticScoringFeeParameters, }; -use lightning::sign::EntropySource; +use lightning::sign::{EntropySource, NodeSigner}; use lightning::util::persist::{ read_channel_monitors, CHANNEL_MANAGER_PERSISTENCE_KEY, @@ -1278,15 +1278,6 @@ fn build_with_store_internal( }, }; - // Initialize the ChainMonitor - let chain_monitor: Arc = Arc::new(chainmonitor::ChainMonitor::new( - Some(Arc::clone(&chain_source)), - Arc::clone(&tx_broadcaster), - Arc::clone(&logger), - Arc::clone(&fee_estimator), - Arc::clone(&kv_store), - )); - // Initialize the KeysManager let cur_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map_err(|e| { log_error!(logger, "Failed to get current time: {}", e); @@ -1302,6 +1293,19 @@ fn build_with_store_internal( Arc::clone(&logger), )); + let peer_storage_key = keys_manager.get_peer_storage_key(); + + // Initialize the ChainMonitor + let chain_monitor: Arc = Arc::new(chainmonitor::ChainMonitor::new( + Some(Arc::clone(&chain_source)), + Arc::clone(&tx_broadcaster), + Arc::clone(&logger), + Arc::clone(&fee_estimator), + Arc::clone(&kv_store), + Arc::clone(&keys_manager), + peer_storage_key, + )); + // Initialize the network graph, scorer, and router let network_graph = match io::utils::read_network_graph(Arc::clone(&kv_store), Arc::clone(&logger)) { @@ -1552,6 +1556,7 @@ fn build_with_store_internal( as Arc, onion_message_handler: Arc::clone(&onion_messenger), custom_message_handler, + send_only_message_handler: Arc::clone(&chain_monitor), }, GossipSync::Rapid(_) => MessageHandler { chan_handler: Arc::clone(&channel_manager), @@ -1559,6 +1564,7 @@ fn build_with_store_internal( as Arc, onion_message_handler: Arc::clone(&onion_messenger), custom_message_handler, + send_only_message_handler: Arc::clone(&chain_monitor), }, GossipSync::None => { unreachable!("We must always have a gossip sync!"); diff --git a/src/types.rs b/src/types.rs index 659a4369a..42a7263bf 100644 --- a/src/types.rs +++ b/src/types.rs @@ -47,6 +47,7 @@ pub(crate) type ChainMonitor = chainmonitor::ChainMonitor< Arc, Arc, Arc, + Arc, >; pub(crate) type PeerManager = lightning::ln::peer_handler::PeerManager< @@ -57,6 +58,7 @@ pub(crate) type PeerManager = lightning::ln::peer_handler::PeerManager< Arc, Arc>>, Arc, + Arc, >; pub(crate) type LiquidityManager = diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index f015ccf97..045cb34e3 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -27,7 +27,7 @@ use lightning::ln::msgs::UnsignedGossipMessage; use lightning::ln::script::ShutdownScript; use lightning::sign::{ ChangeDestinationSource, EntropySource, InMemorySigner, KeysManager, NodeSigner, OutputSpender, - Recipient, SignerProvider, SpendableOutputDescriptor, + PeerStorageKey, Recipient, SignerProvider, SpendableOutputDescriptor, }; use lightning::util::message_signing; @@ -806,6 +806,10 @@ impl NodeSigner for WalletKeysManager { self.inner.get_inbound_payment_key() } + fn get_peer_storage_key(&self) -> PeerStorageKey { + self.inner.get_peer_storage_key() + } + fn sign_invoice( &self, invoice: &RawBolt11Invoice, recipient: Recipient, ) -> Result { From ad7ce830769bf54048a44a2197cac5cc8f1666f2 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 12 Jun 2025 16:22:26 +0200 Subject: [PATCH 18/45] f Account for Async bumping changes --- src/event.rs | 2 +- src/wallet/mod.rs | 150 ++++++++++++++++++++++++++-------------------- 2 files changed, 85 insertions(+), 67 deletions(-) diff --git a/src/event.rs b/src/event.rs index 02d9ce1f8..c9dc3c630 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1496,7 +1496,7 @@ where BumpTransactionEvent::HTLCResolution { .. } => {}, } - self.bump_tx_event_handler.handle_event(&bte); + self.bump_tx_event_handler.handle_event(&bte).await; }, LdkEvent::OnionMessageIntercepted { .. } => { debug_assert!(false, "We currently don't support onion message interception, so this event should never be emitted."); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 045cb34e3..0dd2684e3 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -310,7 +310,7 @@ impl Wallet { #[cfg(debug_assertions)] if balance.confirmed != Amount::ZERO { debug_assert!( - self.list_confirmed_utxos().map_or(false, |v| !v.is_empty()), + self.list_confirmed_utxos_inner().map_or(false, |v| !v.is_empty()), "Confirmed amounts should always be available for Anchor spending" ); } @@ -560,70 +560,8 @@ impl Wallet { Ok(txid) } -} - -impl Listen for Wallet { - fn filtered_block_connected( - &self, _header: &bitcoin::block::Header, - _txdata: &lightning::chain::transaction::TransactionData, _height: u32, - ) { - debug_assert!(false, "Syncing filtered blocks is currently not supported"); - // As far as we can tell this would be a no-op anyways as we don't have to tell BDK about - // the header chain of intermediate blocks. According to the BDK team, it's sufficient to - // only connect full blocks starting from the last point of disagreement. - } - - fn block_connected(&self, block: &bitcoin::Block, height: u32) { - let mut locked_wallet = self.inner.lock().unwrap(); - - let pre_checkpoint = locked_wallet.latest_checkpoint(); - if pre_checkpoint.height() != height - 1 - || pre_checkpoint.hash() != block.header.prev_blockhash - { - log_debug!( - self.logger, - "Detected reorg while applying a connected block to on-chain wallet: new block with hash {} at height {}", - block.header.block_hash(), - height - ); - } - - match locked_wallet.apply_block(block, height) { - Ok(()) => { - if let Err(e) = self.update_payment_store(&mut *locked_wallet) { - log_error!(self.logger, "Failed to update payment store: {}", e); - return; - } - }, - Err(e) => { - log_error!( - self.logger, - "Failed to apply connected block to on-chain wallet: {}", - e - ); - return; - }, - }; - let mut locked_persister = self.persister.lock().unwrap(); - match locked_wallet.persist(&mut locked_persister) { - Ok(_) => (), - Err(e) => { - log_error!(self.logger, "Failed to persist on-chain wallet: {}", e); - return; - }, - }; - } - - fn block_disconnected(&self, _header: &bitcoin::block::Header, _height: u32) { - // This is a no-op as we don't have to tell BDK about disconnections. According to the BDK - // team, it's sufficient in case of a reorg to always connect blocks starting from the last - // point of disagreement. - } -} - -impl WalletSource for Wallet { - fn list_confirmed_utxos(&self) -> Result, ()> { + fn list_confirmed_utxos_inner(&self) -> Result, ()> { let locked_wallet = self.inner.lock().unwrap(); let mut utxos = Vec::new(); let confirmed_txs: Vec = locked_wallet @@ -715,7 +653,7 @@ impl WalletSource for Wallet { Ok(utxos) } - fn get_change_script(&self) -> Result { + fn get_change_script_inner(&self) -> Result { let mut locked_wallet = self.inner.lock().unwrap(); let mut locked_persister = self.persister.lock().unwrap(); @@ -727,7 +665,7 @@ impl WalletSource for Wallet { Ok(address_info.address.script_pubkey()) } - fn sign_psbt(&self, mut psbt: Psbt) -> Result { + fn sign_psbt_inner(&self, mut psbt: Psbt) -> Result { let locked_wallet = self.inner.lock().unwrap(); // While BDK populates both `witness_utxo` and `non_witness_utxo` fields, LDK does not. As @@ -757,6 +695,86 @@ impl WalletSource for Wallet { } } +impl Listen for Wallet { + fn filtered_block_connected( + &self, _header: &bitcoin::block::Header, + _txdata: &lightning::chain::transaction::TransactionData, _height: u32, + ) { + debug_assert!(false, "Syncing filtered blocks is currently not supported"); + // As far as we can tell this would be a no-op anyways as we don't have to tell BDK about + // the header chain of intermediate blocks. According to the BDK team, it's sufficient to + // only connect full blocks starting from the last point of disagreement. + } + + fn block_connected(&self, block: &bitcoin::Block, height: u32) { + let mut locked_wallet = self.inner.lock().unwrap(); + + let pre_checkpoint = locked_wallet.latest_checkpoint(); + if pre_checkpoint.height() != height - 1 + || pre_checkpoint.hash() != block.header.prev_blockhash + { + log_debug!( + self.logger, + "Detected reorg while applying a connected block to on-chain wallet: new block with hash {} at height {}", + block.header.block_hash(), + height + ); + } + + match locked_wallet.apply_block(block, height) { + Ok(()) => { + if let Err(e) = self.update_payment_store(&mut *locked_wallet) { + log_error!(self.logger, "Failed to update payment store: {}", e); + return; + } + }, + Err(e) => { + log_error!( + self.logger, + "Failed to apply connected block to on-chain wallet: {}", + e + ); + return; + }, + }; + + let mut locked_persister = self.persister.lock().unwrap(); + match locked_wallet.persist(&mut locked_persister) { + Ok(_) => (), + Err(e) => { + log_error!(self.logger, "Failed to persist on-chain wallet: {}", e); + return; + }, + }; + } + + fn block_disconnected(&self, _header: &bitcoin::block::Header, _height: u32) { + // This is a no-op as we don't have to tell BDK about disconnections. According to the BDK + // team, it's sufficient in case of a reorg to always connect blocks starting from the last + // point of disagreement. + } +} + +impl WalletSource for Wallet { + fn list_confirmed_utxos<'a>( + &'a self, + ) -> Pin, ()>> + Send + 'a>> { + Box::pin(async move { self.list_confirmed_utxos_inner() }) + } + + fn get_change_script<'a>( + &'a self, + ) -> Pin> + Send + 'a>> { + Box::pin(async move { self.get_change_script_inner() }) + } + + fn sign_psbt<'a>( + &'a self, psbt: Psbt, + ) -> Pin> + Send + 'a>> { + Box::pin(async move { self.sign_psbt_inner(psbt) }) + } +} + /// Similar to [`KeysManager`], but overrides the destination and shutdown scripts so they are /// directly spendable by the BDK wallet. pub(crate) struct WalletKeysManager { From cd7de0293d0e6fdc6f2ac090720b3e2f53bb857e Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 12 Jun 2025 16:29:19 +0200 Subject: [PATCH 19/45] f Reuse the same esplora-client again --- Cargo.toml | 5 ----- src/chain/esplora.rs | 15 ++------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dcaedaf10..a2a15a711 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,11 +93,6 @@ rand = "0.8.5" chrono = { version = "0.4", default-features = false, features = ["clock"] } tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thread", "time", "sync", "macros" ] } esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"] } - -# FIXME: This was introduced to decouple the `bdk_esplora` and -# `lightning-transaction-sync` APIs. We should drop it as part of the upgrade -# to LDK 0.2. -esplora-client_0_11 = { package = "esplora-client", version = "0.11", default-features = false, features = ["tokio", "async-https-rustls"] } electrum-client = { version = "0.24.0", default-features = true } libc = "0.2" uniffi = { version = "0.28.3", features = ["build"], optional = true } diff --git a/src/chain/esplora.rs b/src/chain/esplora.rs index a8806a413..8e9a4dbd4 100644 --- a/src/chain/esplora.rs +++ b/src/chain/esplora.rs @@ -57,19 +57,6 @@ impl EsploraChainSource { kv_store: Arc, config: Arc, logger: Arc, node_metrics: Arc>, ) -> Self { - // FIXME / TODO: We introduced this to make `bdk_esplora` work separately without updating - // `lightning-transaction-sync`. We should revert this as part of of the upgrade to LDK 0.2. - let mut client_builder_0_11 = esplora_client_0_11::Builder::new(&server_url); - client_builder_0_11 = client_builder_0_11.timeout(DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS); - - for (header_name, header_value) in &headers { - client_builder_0_11 = client_builder_0_11.header(header_name, header_value); - } - - let esplora_client_0_11 = client_builder_0_11.build_async().unwrap(); - let tx_sync = - Arc::new(EsploraSyncClient::from_client(esplora_client_0_11, Arc::clone(&logger))); - let mut client_builder = esplora_client::Builder::new(&server_url); client_builder = client_builder.timeout(DEFAULT_ESPLORA_CLIENT_TIMEOUT_SECS); @@ -78,6 +65,8 @@ impl EsploraChainSource { } let esplora_client = client_builder.build_async().unwrap(); + let tx_sync = + Arc::new(EsploraSyncClient::from_client(esplora_client.clone(), Arc::clone(&logger))); let onchain_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed); let lightning_wallet_sync_status = Mutex::new(WalletSyncStatus::Completed); From 6167f59afdca6823692dd4597a5dc938dde1a121 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 12 Jun 2025 16:30:44 +0200 Subject: [PATCH 20/45] f Reuse `electrum-client` --- src/chain/electrum.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/chain/electrum.rs b/src/chain/electrum.rs index b6d37409b..40d929ce7 100644 --- a/src/chain/electrum.rs +++ b/src/chain/electrum.rs @@ -402,7 +402,7 @@ impl ElectrumRuntimeStatus { struct ElectrumRuntimeClient { electrum_client: Arc, - bdk_electrum_client: Arc>, + bdk_electrum_client: Arc>>, tx_sync: Arc>>, runtime: Arc, config: Arc, @@ -424,12 +424,7 @@ impl ElectrumRuntimeClient { Error::ConnectionFailed })?, ); - let electrum_client_2 = - ElectrumClient::from_config(&server_url, electrum_config).map_err(|e| { - log_error!(logger, "Failed to connect to electrum server: {}", e); - Error::ConnectionFailed - })?; - let bdk_electrum_client = Arc::new(BdkElectrumClient::new(electrum_client_2)); + let bdk_electrum_client = Arc::new(BdkElectrumClient::new(Arc::clone(&electrum_client))); let tx_sync = Arc::new( ElectrumSyncClient::new(server_url.clone(), Arc::clone(&logger)).map_err(|e| { log_error!(logger, "Failed to connect to electrum server: {}", e); From 1f900b0783ebf57e44a375808c515dc0d2b6806e Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 21 May 2025 14:37:38 +0200 Subject: [PATCH 21/45] f Account for changes in BOLT11 interface --- bindings/ldk_node.udl | 34 +++--- src/config.rs | 12 +- src/ffi/types.rs | 3 +- src/payment/bolt11.rs | 197 +++++++++++++++++--------------- src/payment/mod.rs | 84 -------------- src/payment/spontaneous.rs | 58 +++++----- tests/integration_tests_rust.rs | 15 +-- 7 files changed, 161 insertions(+), 242 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index ac8875742..899a3e655 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 { @@ -167,13 +167,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] @@ -213,13 +213,13 @@ 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] - PaymentId send_with_preimage(u64 amount_msat, PublicKey node_id, PaymentPreimage preimage, SendingParameters? sending_parameters); + PaymentId send_with_preimage(u64 amount_msat, PublicKey node_id, PaymentPreimage preimage, RouteParametersConfig? route_parameters); [Throws=NodeError] - PaymentId send_with_preimage_and_custom_tlvs(u64 amount_msat, PublicKey node_id, sequence custom_tlvs, PaymentPreimage preimage, SendingParameters? sending_parameters); + PaymentId send_with_preimage_and_custom_tlvs(u64 amount_msat, PublicKey node_id, sequence custom_tlvs, PaymentPreimage preimage, RouteParametersConfig? route_parameters); [Throws=NodeError] void send_probes(u64 amount_msat, PublicKey node_id); }; @@ -456,11 +456,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 { @@ -521,12 +521,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 02df8bbc7..84f62d220 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; @@ -114,9 +114,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 @@ -173,12 +173,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 { @@ -191,7 +191,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/ffi/types.rs b/src/ffi/types.rs index 4e4ee588b..967d674a9 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -20,13 +20,14 @@ 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}; pub use lightning::ln::types::ChannelId; pub use lightning::offers::offer::OfferId; 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/src/payment/bolt11.rs b/src/payment/bolt11.rs index 92d7fc948..b2931378c 100644 --- a/src/payment/bolt11.rs +++ b/src/payment/bolt11.rs @@ -20,16 +20,14 @@ use crate::payment::store::{ LSPFeeLimits, PaymentDetails, PaymentDetailsUpdate, PaymentDirection, PaymentKind, PaymentStatus, }; -use crate::payment::SendingParameters; use crate::peer_store::{PeerInfo, PeerStore}; use crate::runtime::Runtime; 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}; @@ -92,22 +90,16 @@ 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 { if !*self.is_running.read().unwrap() { return Err(Error::NotRunning); } let invoice = maybe_deref(invoice); - - 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 @@ -118,29 +110,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(()) => { @@ -166,7 +156,13 @@ 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::SendingFailed(e)) => { log_error!(self.logger, "Failed to send payment: {:?}", e); match e { RetryableSendFailure::DuplicatePayment => Err(Error::DuplicatePayment), @@ -200,18 +196,17 @@ 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 { 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!( @@ -232,46 +227,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(()) => { @@ -286,7 +251,7 @@ impl Bolt11Payment { let kind = PaymentKind::Bolt11 { hash: payment_hash, preimage: None, - secret: Some(*payment_secret), + secret: payment_secret, }; let payment = PaymentDetails::new( @@ -301,16 +266,22 @@ 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::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, @@ -320,8 +291,8 @@ impl Bolt11Payment { PaymentDirection::Outbound, PaymentStatus::Failed, ); - self.payment_store.insert(payment)?; + self.payment_store.insert(payment)?; Err(Error::PaymentSendingFailed) }, } @@ -798,18 +769,41 @@ 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> { if !*self.is_running.read().unwrap() { return Err(Error::NotRunning); } let invoice = maybe_deref(invoice); + let payment_params = PaymentParameters::from_bolt11_invoice(invoice); - let (_payment_hash, _recipient_onion, route_params) = bolt11_payment::payment_parameters_from_invoice(&invoice).map_err(|_| { + 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 @@ -828,36 +822,49 @@ 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> { if !*self.is_running.read().unwrap() { return Err(Error::NotRunning); } let invoice = maybe_deref(invoice); + let payment_params = PaymentParameters::from_bolt11_invoice(invoice); - let (_payment_hash, _recipient_onion, route_params) = if let Some(invoice_amount_msat) = - invoice.amount_milli_satoshis() - { + 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 3e48fd090..181307a0f 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,41 +51,43 @@ 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, None) + self.send_inner(amount_msat, node_id, route_parameters, None, 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), None) + self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), None) } /// Send a spontaneous payment with custom preimage pub fn send_with_preimage( &self, amount_msat: u64, node_id: PublicKey, preimage: PaymentPreimage, - sending_parameters: Option, + route_parameters: Option, ) -> Result { - self.send_inner(amount_msat, node_id, sending_parameters, None, Some(preimage)) + self.send_inner(amount_msat, node_id, route_parameters, None, Some(preimage)) } /// Send a spontaneous payment with custom preimage including a list of custom TLVs. pub fn send_with_preimage_and_custom_tlvs( &self, amount_msat: u64, node_id: PublicKey, custom_tlvs: Vec, - preimage: PaymentPreimage, sending_parameters: Option, + preimage: PaymentPreimage, route_parameters: Option, ) -> Result { - self.send_inner(amount_msat, node_id, sending_parameters, Some(custom_tlvs), Some(preimage)) + self.send_inner(amount_msat, node_id, route_parameters, Some(custom_tlvs), Some(preimage)) } fn send_inner( - &self, amount_msat: u64, node_id: PublicKey, sending_parameters: Option, - custom_tlvs: Option>, preimage: Option, + &self, amount_msat: u64, node_id: PublicKey, + route_parameters: Option, custom_tlvs: Option>, + preimage: Option, ) -> Result { if !*self.is_running.read().unwrap() { return Err(Error::NotRunning); @@ -112,20 +113,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/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 0932116ef..a48470479 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -22,12 +22,13 @@ use ldk_node::config::EsploraSyncConfig; use ldk_node::liquidity::LSPS2ServiceConfig; use ldk_node::payment::{ ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, - QrPaymentResult, SendingParameters, + QrPaymentResult, }; 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}; @@ -212,11 +213,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 = @@ -225,7 +226,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 40ac4ed50e7858f0a60556dd7c47d5f9146898f4 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 23 Jun 2025 14:20:46 +0200 Subject: [PATCH 22/45] f Account for `CurrencyCode` changes --- src/ffi/types.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 967d674a9..5a0a557b7 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -126,9 +126,8 @@ impl From for OfferAmount { fn from(ldk_amount: LdkAmount) -> Self { match ldk_amount { LdkAmount::Bitcoin { amount_msats } => OfferAmount::Bitcoin { amount_msats }, - LdkAmount::Currency { iso4217_code, amount } => OfferAmount::Currency { - iso4217_code: iso4217_code.iter().map(|&b| b as char).collect(), - amount, + LdkAmount::Currency { iso4217_code, amount } => { + OfferAmount::Currency { iso4217_code: iso4217_code.as_str().to_owned(), amount } }, } } From 4bd76f5a6fb38b999f5a0e1742da7ae3b28a80b3 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 9 Jul 2025 14:44:44 +0200 Subject: [PATCH 23/45] f Account for `lightning::util::string` re-exports being dropped --- src/ffi/types.rs | 2 +- src/io/sqlite_store/mod.rs | 3 ++- src/io/utils.rs | 3 ++- src/payment/bolt12.rs | 3 ++- src/payment/store.rs | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ffi/types.rs b/src/ffi/types.rs index 5a0a557b7..02d321787 100644 --- a/src/ffi/types.rs +++ b/src/ffi/types.rs @@ -28,7 +28,7 @@ pub use lightning::ln::types::ChannelId; pub use lightning::offers::offer::OfferId; pub use lightning::routing::gossip::{NodeAlias, NodeId, RoutingFees}; pub use lightning::routing::router::RouteParametersConfig; -pub use lightning::util::string::UntrustedString; +pub use lightning_types::string::UntrustedString; pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; diff --git a/src/io/sqlite_store/mod.rs b/src/io/sqlite_store/mod.rs index b72db5a2b..9ef30cc49 100644 --- a/src/io/sqlite_store/mod.rs +++ b/src/io/sqlite_store/mod.rs @@ -10,7 +10,8 @@ use crate::io::utils::check_namespace_key_validity; use lightning::io; use lightning::util::persist::KVStore; -use lightning::util::string::PrintableString; + +use lightning_types::string::PrintableString; use rusqlite::{named_params, Connection}; diff --git a/src/io/utils.rs b/src/io/utils.rs index 06a1017ba..a6af165a5 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -31,9 +31,10 @@ use lightning::util::persist::{ SCORER_PERSISTENCE_PRIMARY_NAMESPACE, SCORER_PERSISTENCE_SECONDARY_NAMESPACE, }; use lightning::util::ser::{Readable, ReadableArgs, Writeable}; -use lightning::util::string::PrintableString; use lightning::util::sweep::OutputSweeper; +use lightning_types::string::PrintableString; + use bdk_chain::indexer::keychain_txout::ChangeSet as BdkIndexerChangeSet; use bdk_chain::local_chain::ChangeSet as BdkLocalChainChangeSet; use bdk_chain::miniscript::{Descriptor, DescriptorPublicKey}; diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index eee3ac436..e58e212d6 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -20,7 +20,8 @@ use lightning::ln::channelmanager::{PaymentId, Retry}; use lightning::offers::offer::{Amount, Offer as LdkOffer, Quantity}; use lightning::offers::parse::Bolt12SemanticError; use lightning::routing::router::RouteParametersConfig; -use lightning::util::string::UntrustedString; + +use lightning_types::string::UntrustedString; use rand::RngCore; diff --git a/src/payment/store.rs b/src/payment/store.rs index 75b2b1b2a..568394b48 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -9,13 +9,13 @@ use lightning::ln::channelmanager::PaymentId; use lightning::ln::msgs::DecodeError; use lightning::offers::offer::OfferId; use lightning::util::ser::{Readable, Writeable}; -use lightning::util::string::UntrustedString; use lightning::{ _init_and_read_len_prefixed_tlv_fields, impl_writeable_tlv_based, impl_writeable_tlv_based_enum, write_tlv_fields, }; use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; +use lightning_types::string::UntrustedString; use bitcoin::{BlockHash, Txid}; From 203e0a6b4196b639bed00e5772eed6c83d763dd4 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 18 Jul 2025 10:20:31 +0200 Subject: [PATCH 24/45] f Account for `force_close_without_broadcasting` being dropped --- bindings/ldk_node.udl | 2 +- src/event.rs | 8 ++++---- src/io/test_utils.rs | 5 +++-- src/lib.rs | 39 ++++++++++----------------------------- 4 files changed, 18 insertions(+), 36 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 899a3e655..01dfe70ef 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -392,7 +392,7 @@ enum PaymentFailureReason { [Enum] interface ClosureReason { CounterpartyForceClosed(UntrustedString peer_msg); - HolderForceClosed(boolean? broadcasted_latest_txn); + HolderForceClosed(boolean? broadcasted_latest_txn, string message); LegacyCooperativeClosure(); CounterpartyInitiatedCooperativeClosure(); LocallyInitiatedCooperativeClosure(); diff --git a/src/event.rs b/src/event.rs index c9dc3c630..fda732c03 100644 --- a/src/event.rs +++ b/src/event.rs @@ -547,7 +547,7 @@ where Err(err) => { log_error!(self.logger, "Failed to create funding transaction: {}", err); self.channel_manager - .force_close_without_broadcasting_txn( + .force_close_broadcasting_latest_txn( &temporary_channel_id, &counterparty_node_id, "Failed to create funding transaction".to_string(), @@ -1084,7 +1084,7 @@ where log_error!(self.logger, "Rejecting inbound announced channel from peer {} due to missing configuration: {}", counterparty_node_id, err); self.channel_manager - .force_close_without_broadcasting_txn( + .force_close_broadcasting_latest_txn( &temporary_channel_id, &counterparty_node_id, "Channel request rejected".to_string(), @@ -1128,7 +1128,7 @@ where required_amount_sats, ); self.channel_manager - .force_close_without_broadcasting_txn( + .force_close_broadcasting_latest_txn( &temporary_channel_id, &counterparty_node_id, "Channel request rejected".to_string(), @@ -1145,7 +1145,7 @@ where counterparty_node_id, ); self.channel_manager - .force_close_without_broadcasting_txn( + .force_close_broadcasting_latest_txn( &temporary_channel_id, &counterparty_node_id, "Channel request rejected".to_string(), diff --git a/src/io/test_utils.rs b/src/io/test_utils.rs index df806779e..ff95388bc 100644 --- a/src/io/test_utils.rs +++ b/src/io/test_utils.rs @@ -145,18 +145,19 @@ pub(crate) fn do_test_store(store_0: &K, store_1: &K) { // Force close because cooperative close doesn't result in any persisted // updates. + let message = "Channel force-closed".to_owned(); nodes[0] .node .force_close_broadcasting_latest_txn( &nodes[0].node.list_channels()[0].channel_id, &nodes[1].node.get_our_node_id(), - "whoops".to_string(), + message.clone(), ) .unwrap(); check_closed_event!( nodes[0], 1, - ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) }, + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message }, [nodes[1].node.get_our_node_id()], 100000 ); diff --git a/src/lib.rs b/src/lib.rs index 80273f08f..b4d1375e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1237,35 +1237,16 @@ impl Node { open_channels.iter().find(|c| c.user_channel_id == user_channel_id.0) { if force { - if self.config.anchor_channels_config.as_ref().map_or(false, |acc| { - acc.trusted_peers_no_reserve.contains(&counterparty_node_id) - }) { - self.channel_manager - .force_close_without_broadcasting_txn( - &channel_details.channel_id, - &counterparty_node_id, - force_close_reason.unwrap_or_default(), - ) - .map_err(|e| { - log_error!( - self.logger, - "Failed to force-close channel to trusted peer: {:?}", - e - ); - Error::ChannelClosingFailed - })?; - } else { - self.channel_manager - .force_close_broadcasting_latest_txn( - &channel_details.channel_id, - &counterparty_node_id, - force_close_reason.unwrap_or_default(), - ) - .map_err(|e| { - log_error!(self.logger, "Failed to force-close channel: {:?}", e); - Error::ChannelClosingFailed - })?; - } + self.channel_manager + .force_close_broadcasting_latest_txn( + &channel_details.channel_id, + &counterparty_node_id, + force_close_reason.unwrap_or_default(), + ) + .map_err(|e| { + log_error!(self.logger, "Failed to force-close channel: {:?}", e); + Error::ChannelClosingFailed + })?; } else { self.channel_manager .close_channel(&channel_details.channel_id, &counterparty_node_id) From b1f891d6a941f33290ca39af16c52dc2b16dd0cb Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 10 Jul 2025 09:34:22 +0200 Subject: [PATCH 25/45] f Rename KVStore to KVStoreSync --- src/builder.rs | 6 +++--- src/io/sqlite_store/migrations.rs | 2 +- src/io/sqlite_store/mod.rs | 6 +++--- src/io/test_utils.rs | 6 +++--- src/io/vss_store.rs | 4 ++-- src/types.rs | 4 ++-- tests/common/mod.rs | 4 ++-- tests/integration_tests_rust.rs | 4 ++-- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 0b19586d3..2a981067e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -173,17 +173,17 @@ pub enum BuildError { RuntimeSetupFailed, /// We failed to read data from the [`KVStore`]. /// - /// [`KVStore`]: lightning::util::persist::KVStore + /// [`KVStore`]: lightning::util::persist::KVStoreSync ReadFailed, /// We failed to write data to the [`KVStore`]. /// - /// [`KVStore`]: lightning::util::persist::KVStore + /// [`KVStore`]: lightning::util::persist::KVStoreSync WriteFailed, /// We failed to access the given `storage_dir_path`. StoragePathAccessFailed, /// We failed to setup our [`KVStore`]. /// - /// [`KVStore`]: lightning::util::persist::KVStore + /// [`KVStore`]: lightning::util::persist::KVStoreSync KVStoreSetupFailed, /// We failed to setup the onchain wallet. WalletSetupFailed, diff --git a/src/io/sqlite_store/migrations.rs b/src/io/sqlite_store/migrations.rs index 0486b8a4f..15e60bcc2 100644 --- a/src/io/sqlite_store/migrations.rs +++ b/src/io/sqlite_store/migrations.rs @@ -78,7 +78,7 @@ mod tests { use crate::io::sqlite_store::SqliteStore; use crate::io::test_utils::{do_read_write_remove_list_persist, random_storage_path}; - use lightning::util::persist::KVStore; + use lightning::util::persist::KVStoreSync; use rusqlite::{named_params, Connection}; diff --git a/src/io/sqlite_store/mod.rs b/src/io/sqlite_store/mod.rs index 9ef30cc49..c2d59893f 100644 --- a/src/io/sqlite_store/mod.rs +++ b/src/io/sqlite_store/mod.rs @@ -9,7 +9,7 @@ use crate::io::utils::check_namespace_key_validity; use lightning::io; -use lightning::util::persist::KVStore; +use lightning::util::persist::KVStoreSync; use lightning_types::string::PrintableString; @@ -35,7 +35,7 @@ pub const DEFAULT_KV_TABLE_NAME: &str = "ldk_data"; // The current SQLite `user_version`, which we can use if we'd ever need to do a schema migration. const SCHEMA_USER_VERSION: u16 = 2; -/// A [`KVStore`] implementation that writes to and reads from an [SQLite] database. +/// A [`KVStoreSync`] implementation that writes to and reads from an [SQLite] database. /// /// [SQLite]: https://sqlite.org pub struct SqliteStore { @@ -130,7 +130,7 @@ impl SqliteStore { } } -impl KVStore for SqliteStore { +impl KVStoreSync for SqliteStore { fn read( &self, primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> io::Result> { diff --git a/src/io/test_utils.rs b/src/io/test_utils.rs index ff95388bc..a667cec6f 100644 --- a/src/io/test_utils.rs +++ b/src/io/test_utils.rs @@ -9,7 +9,7 @@ use lightning::ln::functional_test_utils::{ connect_block, create_announced_chan_between_nodes, create_chanmon_cfgs, create_dummy_block, create_network, create_node_cfgs, create_node_chanmgrs, send_payment, }; -use lightning::util::persist::{read_channel_monitors, KVStore, KVSTORE_NAMESPACE_KEY_MAX_LEN}; +use lightning::util::persist::{read_channel_monitors, KVStoreSync, KVSTORE_NAMESPACE_KEY_MAX_LEN}; use lightning::events::ClosureReason; use lightning::util::test_utils; @@ -29,7 +29,7 @@ pub(crate) fn random_storage_path() -> PathBuf { temp_path } -pub(crate) fn do_read_write_remove_list_persist(kv_store: &K) { +pub(crate) fn do_read_write_remove_list_persist(kv_store: &K) { let data = [42u8; 32]; let primary_namespace = "testspace"; @@ -80,7 +80,7 @@ pub(crate) fn do_read_write_remove_list_persist(kv_s // Integration-test the given KVStore implementation. Test relaying a few payments and check that // the persisted data is updated the appropriate number of times. -pub(crate) fn do_test_store(store_0: &K, store_1: &K) { +pub(crate) fn do_test_store(store_0: &K, store_1: &K) { let chanmon_cfgs = create_chanmon_cfgs(2); let mut node_cfgs = create_node_cfgs(2, &chanmon_cfgs); let chain_mon_0 = test_utils::TestChainMonitor::new( diff --git a/src/io/vss_store.rs b/src/io/vss_store.rs index 296eaabe3..9c40ab060 100644 --- a/src/io/vss_store.rs +++ b/src/io/vss_store.rs @@ -8,7 +8,7 @@ use crate::io::utils::check_namespace_key_validity; use bitcoin::hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine}; use lightning::io::{self, Error, ErrorKind}; -use lightning::util::persist::KVStore; +use lightning::util::persist::KVStoreSync; use prost::Message; use rand::RngCore; #[cfg(test)] @@ -127,7 +127,7 @@ impl VssStore { } } -impl KVStore for VssStore { +impl KVStoreSync for VssStore { fn read( &self, primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> io::Result> { diff --git a/src/types.rs b/src/types.rs index 42a7263bf..9dc56aa32 100644 --- a/src/types.rs +++ b/src/types.rs @@ -25,7 +25,7 @@ use lightning::routing::gossip; use lightning::routing::router::DefaultRouter; use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; use lightning::sign::InMemorySigner; -use lightning::util::persist::KVStore; +use lightning::util::persist::KVStoreSync; use lightning::util::ser::{Readable, Writeable, Writer}; use lightning::util::sweep::OutputSweeper; @@ -38,7 +38,7 @@ use bitcoin::OutPoint; use std::sync::{Arc, Mutex}; -pub(crate) type DynStore = dyn KVStore + Sync + Send; +pub(crate) type DynStore = dyn KVStoreSync + Sync + Send; pub(crate) type ChainMonitor = chainmonitor::ChainMonitor< InMemorySigner, diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 780e9bbf4..460ace4e0 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -21,7 +21,7 @@ use ldk_node::{ use lightning::ln::msgs::SocketAddress; use lightning::routing::gossip::NodeAlias; -use lightning::util::persist::KVStore; +use lightning::util::persist::KVStoreSync; use lightning::util::test_utils::TestStore; use lightning_invoice::{Bolt11InvoiceDescription, Description}; @@ -1236,7 +1236,7 @@ impl TestSyncStore { } } -impl KVStore for TestSyncStore { +impl KVStoreSync for TestSyncStore { fn read( &self, primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> lightning::io::Result> { diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index a48470479..fa88fe0cc 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -29,7 +29,7 @@ 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::util::persist::KVStoreSync; use lightning_invoice::{Bolt11InvoiceDescription, Description}; use lightning_types::payment::{PaymentHash, PaymentPreimage}; @@ -247,7 +247,7 @@ fn start_stop_reinit() { let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap()); - let test_sync_store: Arc = + let test_sync_store: Arc = Arc::new(TestSyncStore::new(config.node_config.storage_dir_path.clone().into())); let sync_config = EsploraSyncConfig { background_sync_config: None }; From fad42c0e220ebd6fb7831d7312e52e11dfc90ee4 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 10 Jul 2025 14:58:35 +0200 Subject: [PATCH 26/45] f Account for async `KVStore` and async background persistence --- src/builder.rs | 2 +- src/event.rs | 6 +++++- src/io/utils.rs | 2 +- src/lib.rs | 4 ++-- src/liquidity.rs | 2 +- src/types.rs | 5 +++-- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 2a981067e..369dd3e19 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1609,7 +1609,7 @@ fn build_with_store_internal( Ok(output_sweeper) => Arc::new(output_sweeper), Err(e) => { if e.kind() == std::io::ErrorKind::NotFound { - Arc::new(OutputSweeper::new( + Arc::new(OutputSweeper::new_with_kv_store_sync( channel_manager.current_best_block(), Arc::clone(&tx_broadcaster), Arc::clone(&fee_estimator), diff --git a/src/event.rs b/src/event.rs index fda732c03..31de6a06e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1062,7 +1062,11 @@ where self.runtime.spawn_cancellable_background_task(future); }, LdkEvent::SpendableOutputs { outputs, channel_id } => { - match self.output_sweeper.track_spendable_outputs(outputs, channel_id, true, None) { + match self + .output_sweeper + .track_spendable_outputs(outputs, channel_id, true, None) + .await + { Ok(_) => return Ok(()), Err(_) => { log_error!(self.logger, "Failed to track spendable outputs"); diff --git a/src/io/utils.rs b/src/io/utils.rs index a6af165a5..541e51187 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -252,7 +252,7 @@ pub(crate) fn read_output_sweeper( kv_store, logger.clone(), ); - OutputSweeper::read(&mut reader, args).map_err(|e| { + OutputSweeper::read_with_kv_store_sync(&mut reader, args).map_err(|e| { log_error!(logger, "Failed to deserialize OutputSweeper: {}", e); std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to deserialize OutputSweeper") }) diff --git a/src/lib.rs b/src/lib.rs index b4d1375e9..bac935162 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -158,7 +158,7 @@ use lightning::ln::channelmanager::PaymentId; use lightning::ln::msgs::SocketAddress; use lightning::routing::gossip::NodeAlias; -use lightning_background_processor::process_events_async; +use lightning_background_processor::process_events_async_with_kv_store_sync; use bitcoin::secp256k1::PublicKey; @@ -549,7 +549,7 @@ impl Node { }; self.runtime.spawn_background_processor_task(async move { - process_events_async( + process_events_async_with_kv_store_sync( background_persister, |e| background_event_handler.handle_event(e), background_chain_mon, diff --git a/src/liquidity.rs b/src/liquidity.rs index 5cb48c700..28caa23fe 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -484,7 +484,7 @@ where if token != Some(required) { log_error!( self.logger, - "Rejecting LSPS2 request {:?} from counterparty {} as the client provided an invalid token.", + "Rejecting LSPS2 request {:?} from counterparty {} as the client provided an invalid token.", request_id, counterparty_node_id ); diff --git a/src/types.rs b/src/types.rs index 9dc56aa32..507f27f7b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -26,9 +26,10 @@ use lightning::routing::router::DefaultRouter; use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParameters}; use lightning::sign::InMemorySigner; use lightning::util::persist::KVStoreSync; +use lightning::util::persist::KVStoreSyncWrapper; use lightning::util::ser::{Readable, Writeable, Writer}; -use lightning::util::sweep::OutputSweeper; +use lightning::util::sweep::OutputSweeper; use lightning_block_sync::gossip::{GossipVerifier, UtxoSource}; use lightning_net_tokio::SocketDescriptor; @@ -131,7 +132,7 @@ pub(crate) type Sweeper = OutputSweeper< Arc, Arc, Arc, - Arc, + KVStoreSyncWrapper>, Arc, Arc, >; From aacdb63f5a5311bd45ef1d95112149086a9a8ecc Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 31 Jul 2025 08:49:10 +0200 Subject: [PATCH 27/45] f Account for additional `NodeSigner` methods --- src/wallet/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 0dd2684e3..5b051dac4 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -828,6 +828,10 @@ impl NodeSigner for WalletKeysManager { self.inner.get_peer_storage_key() } + fn get_receive_auth_key(&self) -> lightning::sign::ReceiveAuthKey { + self.inner.get_receive_auth_key() + } + fn sign_invoice( &self, invoice: &RawBolt11Invoice, recipient: Recipient, ) -> Result { @@ -843,6 +847,9 @@ impl NodeSigner for WalletKeysManager { ) -> Result { self.inner.sign_bolt12_invoice(invoice) } + fn sign_message(&self, msg: &[u8]) -> Result { + self.inner.sign_message(msg) + } } impl OutputSpender for WalletKeysManager { From f4b8c24e70d05cc0c1845009ab9265afbdc414e5 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 31 Jul 2025 08:49:47 +0200 Subject: [PATCH 28/45] f Account for `lightning-liquidity` changes post-LSPS5 merge --- src/liquidity.rs | 12 +++++++++--- src/types.rs | 11 +++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/liquidity.rs b/src/liquidity.rs index 28caa23fe..8af49290f 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -223,16 +223,22 @@ where pub(crate) fn build(self) -> LiquiditySource { let liquidity_service_config = self.lsps2_service.as_ref().map(|s| { let lsps2_service_config = Some(s.ldk_service_config.clone()); + let lsps5_service_config = None; let advertise_service = s.service_config.advertise_service; - LiquidityServiceConfig { lsps2_service_config, advertise_service } + LiquidityServiceConfig { lsps2_service_config, lsps5_service_config, advertise_service } }); let lsps1_client_config = self.lsps1_client.as_ref().map(|s| s.ldk_client_config.clone()); let lsps2_client_config = self.lsps2_client.as_ref().map(|s| s.ldk_client_config.clone()); - let liquidity_client_config = - Some(LiquidityClientConfig { lsps1_client_config, lsps2_client_config }); + let lsps5_client_config = None; + let liquidity_client_config = Some(LiquidityClientConfig { + lsps1_client_config, + lsps2_client_config, + lsps5_client_config, + }); let liquidity_manager = Arc::new(LiquidityManager::new( + Arc::clone(&self.keys_manager), Arc::clone(&self.keys_manager), Arc::clone(&self.channel_manager), Some(Arc::clone(&self.chain_source)), diff --git a/src/types.rs b/src/types.rs index 507f27f7b..b9bc1c317 100644 --- a/src/types.rs +++ b/src/types.rs @@ -34,6 +34,8 @@ use lightning_block_sync::gossip::{GossipVerifier, UtxoSource}; use lightning_net_tokio::SocketDescriptor; +use lightning_liquidity::utils::time::DefaultTimeProvider; + use bitcoin::secp256k1::PublicKey; use bitcoin::OutPoint; @@ -62,8 +64,13 @@ pub(crate) type PeerManager = lightning::ln::peer_handler::PeerManager< Arc, >; -pub(crate) type LiquidityManager = - lightning_liquidity::LiquidityManager, Arc, Arc>; +pub(crate) type LiquidityManager = lightning_liquidity::LiquidityManager< + Arc, + Arc, + Arc, + Arc, + Arc, +>; pub(crate) type ChannelManager = lightning::ln::channelmanager::ChannelManager< Arc, From e51c2ca8e84cc4bebacc873f93579155dc071c20 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 31 Jul 2025 08:52:24 +0200 Subject: [PATCH 29/45] f Account for `PendingHTLCsForwardable` being dropped --- src/event.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/event.rs b/src/event.rs index 31de6a06e..ad246cb46 100644 --- a/src/event.rs +++ b/src/event.rs @@ -59,7 +59,6 @@ use core::task::{Poll, Waker}; use std::collections::VecDeque; use std::ops::Deref; use std::sync::{Arc, Condvar, Mutex}; -use std::time::Duration; /// An event emitted by [`Node`], which should be handled by the user. /// @@ -1048,19 +1047,6 @@ where liquidity_source.handle_htlc_handling_failed(failure_type); } }, - LdkEvent::PendingHTLCsForwardable { time_forwardable } => { - let forwarding_channel_manager = self.channel_manager.clone(); - let min = time_forwardable.as_millis() as u64; - - let future = async move { - let millis_to_sleep = thread_rng().gen_range(min..min * 5) as u64; - tokio::time::sleep(Duration::from_millis(millis_to_sleep)).await; - - forwarding_channel_manager.process_pending_htlc_forwards(); - }; - - self.runtime.spawn_cancellable_background_task(future); - }, LdkEvent::SpendableOutputs { outputs, channel_id } => { match self .output_sweeper @@ -1161,7 +1147,7 @@ where } } - let user_channel_id: u128 = rand::thread_rng().gen::(); + let user_channel_id: u128 = 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 From ded7ba17df2871f6266068cc1098a029dc7c4b49 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Thu, 31 Jul 2025 09:31:16 +0200 Subject: [PATCH 30/45] f Account for `create_offer_builder` not taking an expiry arg --- src/payment/bolt12.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index e58e212d6..4e968deb7 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -275,17 +275,17 @@ impl Bolt12Payment { pub(crate) fn receive_inner( &self, amount_msat: u64, description: &str, expiry_secs: Option, quantity: Option, ) -> Result { - let absolute_expiry = expiry_secs.map(|secs| { - (SystemTime::now() + Duration::from_secs(secs as u64)) - .duration_since(UNIX_EPOCH) - .unwrap() - }); + let mut offer_builder = self.channel_manager.create_offer_builder().map_err(|e| { + log_error!(self.logger, "Failed to create offer builder: {:?}", e); + Error::OfferCreationFailed + })?; - let offer_builder = - self.channel_manager.create_offer_builder(absolute_expiry).map_err(|e| { - log_error!(self.logger, "Failed to create offer builder: {:?}", e); - Error::OfferCreationFailed - })?; + if let Some(expiry_secs) = expiry_secs { + let absolute_expiry = (SystemTime::now() + Duration::from_secs(expiry_secs as u64)) + .duration_since(UNIX_EPOCH) + .unwrap(); + offer_builder = offer_builder.absolute_expiry(absolute_expiry); + } let mut offer = offer_builder.amount_msats(amount_msat).description(description.to_string()); @@ -321,17 +321,18 @@ impl Bolt12Payment { pub fn receive_variable_amount( &self, description: &str, expiry_secs: Option, ) -> Result { - let absolute_expiry = expiry_secs.map(|secs| { - (SystemTime::now() + Duration::from_secs(secs as u64)) + let mut offer_builder = self.channel_manager.create_offer_builder().map_err(|e| { + log_error!(self.logger, "Failed to create offer builder: {:?}", e); + Error::OfferCreationFailed + })?; + + if let Some(expiry_secs) = expiry_secs { + let absolute_expiry = (SystemTime::now() + Duration::from_secs(expiry_secs as u64)) .duration_since(UNIX_EPOCH) - .unwrap() - }); + .unwrap(); + offer_builder = offer_builder.absolute_expiry(absolute_expiry); + } - let offer_builder = - self.channel_manager.create_offer_builder(absolute_expiry).map_err(|e| { - log_error!(self.logger, "Failed to create offer builder: {:?}", e); - Error::OfferCreationFailed - })?; let offer = offer_builder.description(description.to_string()).build().map_err(|e| { log_error!(self.logger, "Failed to create offer: {:?}", e); Error::OfferCreationFailed From fc46fbaba12a16b764c0db94136e03141edc99e6 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 4 Aug 2025 10:08:56 +0200 Subject: [PATCH 31/45] f Account for `KVStore::write` taking `buf` by value --- src/data_store.rs | 22 +++++++++++----------- src/event.rs | 2 +- src/io/sqlite_store/mod.rs | 2 +- src/io/test_utils.rs | 13 +++++++------ src/io/utils.rs | 4 ++-- src/io/vss_store.rs | 4 ++-- src/peer_store.rs | 2 +- tests/common/mod.rs | 10 ++++++---- 8 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/data_store.rs b/src/data_store.rs index 78e3e7870..45802c272 100644 --- a/src/data_store.rs +++ b/src/data_store.rs @@ -143,18 +143,18 @@ where let store_key = object.id().encode_to_hex_str(); let data = object.encode(); self.kv_store - .write(&self.primary_namespace, &self.secondary_namespace, &store_key, &data) + .write(&self.primary_namespace, &self.secondary_namespace, &store_key, data) .map_err(|e| { - log_error!( - self.logger, - "Write for key {}/{}/{} failed due to: {}", - &self.primary_namespace, - &self.secondary_namespace, - store_key, - e - ); - Error::PersistenceFailed - })?; + log_error!( + self.logger, + "Write for key {}/{}/{} failed due to: {}", + &self.primary_namespace, + &self.secondary_namespace, + store_key, + e + ); + Error::PersistenceFailed + })?; Ok(()) } } diff --git a/src/event.rs b/src/event.rs index ad246cb46..b9fdd475d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -360,7 +360,7 @@ where EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE, EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE, EVENT_QUEUE_PERSISTENCE_KEY, - &data, + data, ) .map_err(|e| { log_error!( diff --git a/src/io/sqlite_store/mod.rs b/src/io/sqlite_store/mod.rs index c2d59893f..4006ab2cc 100644 --- a/src/io/sqlite_store/mod.rs +++ b/src/io/sqlite_store/mod.rs @@ -180,7 +180,7 @@ impl KVStoreSync for SqliteStore { } fn write( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: &[u8], + &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec, ) -> io::Result<()> { check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write")?; diff --git a/src/io/test_utils.rs b/src/io/test_utils.rs index a667cec6f..244dd9cdc 100644 --- a/src/io/test_utils.rs +++ b/src/io/test_utils.rs @@ -30,22 +30,23 @@ pub(crate) fn random_storage_path() -> PathBuf { } pub(crate) fn do_read_write_remove_list_persist(kv_store: &K) { - let data = [42u8; 32]; + let data = vec![42u8; 32]; let primary_namespace = "testspace"; let secondary_namespace = "testsubspace"; let key = "testkey"; // Test the basic KVStore operations. - kv_store.write(primary_namespace, secondary_namespace, key, &data).unwrap(); + kv_store.write(primary_namespace, secondary_namespace, key, data.clone()).unwrap(); // Test empty primary/secondary namespaces are allowed, but not empty primary namespace and non-empty // secondary primary_namespace, and not empty key. - kv_store.write("", "", key, &data).unwrap(); - let res = std::panic::catch_unwind(|| kv_store.write("", secondary_namespace, key, &data)); + kv_store.write("", "", key, data.clone()).unwrap(); + let res = + std::panic::catch_unwind(|| kv_store.write("", secondary_namespace, key, data.clone())); assert!(res.is_err()); let res = std::panic::catch_unwind(|| { - kv_store.write(primary_namespace, secondary_namespace, "", &data) + kv_store.write(primary_namespace, secondary_namespace, "", data.clone()) }); assert!(res.is_err()); @@ -63,7 +64,7 @@ pub(crate) fn do_read_write_remove_list_persist( // Ensure we have no issue operating with primary_namespace/secondary_namespace/key being KVSTORE_NAMESPACE_KEY_MAX_LEN let max_chars: String = std::iter::repeat('A').take(KVSTORE_NAMESPACE_KEY_MAX_LEN).collect(); - kv_store.write(&max_chars, &max_chars, &max_chars, &data).unwrap(); + kv_store.write(&max_chars, &max_chars, &max_chars, data.clone()).unwrap(); let listed_keys = kv_store.list(&max_chars, &max_chars).unwrap(); assert_eq!(listed_keys.len(), 1); diff --git a/src/io/utils.rs b/src/io/utils.rs index 541e51187..51e7be505 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -287,7 +287,7 @@ where NODE_METRICS_PRIMARY_NAMESPACE, NODE_METRICS_SECONDARY_NAMESPACE, NODE_METRICS_KEY, - &data, + data, ) .map_err(|e| { log_error!( @@ -442,7 +442,7 @@ macro_rules! impl_read_write_change_set_type { L::Target: LdkLogger, { let data = ChangeSetSerWrapper(value).encode(); - kv_store.write($primary_namespace, $secondary_namespace, $key, &data).map_err(|e| { + kv_store.write($primary_namespace, $secondary_namespace, $key, data).map_err(|e| { log_error!( logger, "Writing data to key {}/{}/{} failed due to: {}", diff --git a/src/io/vss_store.rs b/src/io/vss_store.rs index 9c40ab060..bfc74b353 100644 --- a/src/io/vss_store.rs +++ b/src/io/vss_store.rs @@ -163,11 +163,11 @@ impl KVStoreSync for VssStore { } fn write( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: &[u8], + &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec, ) -> io::Result<()> { check_namespace_key_validity(primary_namespace, secondary_namespace, Some(key), "write")?; let version = -1; - let storable = self.storable_builder.build(buf.to_vec(), version); + let storable = self.storable_builder.build(buf, version); let request = PutObjectRequest { store_id: self.store_id.clone(), global_version: None, diff --git a/src/peer_store.rs b/src/peer_store.rs index 4d1c65157..cf3755d23 100644 --- a/src/peer_store.rs +++ b/src/peer_store.rs @@ -73,7 +73,7 @@ where PEER_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PEER_INFO_PERSISTENCE_SECONDARY_NAMESPACE, PEER_INFO_PERSISTENCE_KEY, - &data, + data, ) .map_err(|e| { log_error!( diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 460ace4e0..f5bfe76fc 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1263,12 +1263,14 @@ impl KVStoreSync for TestSyncStore { } fn write( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: &[u8], + &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec, ) -> lightning::io::Result<()> { let _guard = self.serializer.write().unwrap(); - let fs_res = self.fs_store.write(primary_namespace, secondary_namespace, key, buf); - let sqlite_res = self.sqlite_store.write(primary_namespace, secondary_namespace, key, buf); - let test_res = self.test_store.write(primary_namespace, secondary_namespace, key, buf); + let fs_res = self.fs_store.write(primary_namespace, secondary_namespace, key, buf.clone()); + let sqlite_res = + self.sqlite_store.write(primary_namespace, secondary_namespace, key, buf.clone()); + let test_res = + self.test_store.write(primary_namespace, secondary_namespace, key, buf.clone()); assert!(self .do_list(primary_namespace, secondary_namespace) From 096e4b942793886ff2e7b1dd7699d487c11e699c Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 19 Aug 2025 10:55:46 +0200 Subject: [PATCH 32/45] f Account for new event types --- src/event.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/event.rs b/src/event.rs index b9fdd475d..bad1b84ab 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1494,6 +1494,15 @@ where LdkEvent::OnionMessagePeerConnected { .. } => { debug_assert!(false, "We currently don't support onion message interception, so this event should never be emitted."); }, + LdkEvent::PersistStaticInvoice { .. } => { + debug_assert!(false, "We currently don't support static invoice persistence, so this event should never be emitted."); + }, + LdkEvent::StaticInvoiceRequested { .. } => { + debug_assert!(false, "We currently don't support static invoice persistence, so this event should never be emitted."); + }, + LdkEvent::FundingTransactionReadyForSigning { .. } => { + debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted."); + }, } Ok(()) } From 82806c2058629ff418021189e31270b000cba93e Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Tue, 19 Aug 2025 11:29:01 +0200 Subject: [PATCH 33/45] f Account for changes in `ClosureReason` --- bindings/ldk_node.udl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 01dfe70ef..b9bab61e8 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -402,8 +402,9 @@ interface ClosureReason { DisconnectedPeer(); OutdatedChannelManager(); CounterpartyCoopClosedUnfundedChannel(); + LocallyCoopClosedUnfundedChannel(); FundingBatchClosure(); - HTLCsTimedOut(); + HTLCsTimedOut( PaymentHash? payment_hash ); PeerFeerateTooLow(u32 peer_feerate_sat_per_kw, u32 required_feerate_sat_per_kw); }; From 3b0c6413d783d6816b6e3dd3b53b5f99356c34ef Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 15 Aug 2025 09:42:47 +0200 Subject: [PATCH 34/45] Switch to use `rustls-ring` everywhere We switch to use `rustls-ring` everywhere, which is necessary for Swift builds, but also generally makes our lives easier. --- .github/workflows/kotlin.yml | 3 --- Cargo.toml | 10 +++++----- src/builder.rs | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 5cb1b8c27..a1711ba49 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -39,9 +39,6 @@ jobs: - name: Generate Kotlin JVM run: ./scripts/uniffi_bindgen_generate_kotlin.sh - - name: Install `bindgen-cli` - run: cargo install --force bindgen-cli - - name: Generate Kotlin Android run: ./scripts/uniffi_bindgen_generate_kotlin_android.sh diff --git a/Cargo.toml b/Cargo.toml index a2a15a711..9e766d113 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ default = [] #lightning-background-processor = { version = "0.1.0" } #lightning-rapid-gossip-sync = { version = "0.1.0" } #lightning-block-sync = { version = "0.1.0", features = ["rest-client", "rpc-client", "tokio"] } -#lightning-transaction-sync = { version = "0.1.0", features = ["esplora-async-https", "time", "electrum"] } +#lightning-transaction-sync = { version = "0.1.0", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } #lightning-liquidity = { version = "0.1.0", features = ["std"] } #lightning-macros = { version = "0.1.0" } @@ -48,7 +48,7 @@ default = [] #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 = ["rest-client", "rpc-client", "tokio"] } -#lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["esplora-async-https", "electrum", "time"] } +#lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } #lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } #lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } @@ -60,7 +60,7 @@ lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["rest-client", "rpc-client", "tokio"] } -lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["esplora-async-https", "electrum", "time"] } +lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } @@ -78,7 +78,7 @@ lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", bdk_chain = { version = "0.23.0", default-features = false, features = ["std"] } bdk_esplora = { version = "0.22.0", default-features = false, features = ["async-https-rustls", "tokio"]} -bdk_electrum = { version = "0.23.0", default-features = false, features = ["use-rustls"]} +bdk_electrum = { version = "0.23.0", default-features = false, features = ["use-rustls-ring"]} bdk_wallet = { version = "2.0.0", default-features = false, features = ["std", "keys-bip39"]} reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } @@ -93,7 +93,7 @@ rand = "0.8.5" chrono = { version = "0.4", default-features = false, features = ["clock"] } tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thread", "time", "sync", "macros" ] } esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"] } -electrum-client = { version = "0.24.0", default-features = true } +electrum-client = { version = "0.24.0", default-features = false, features = ["proxy", "use-rustls-ring"] } libc = "0.2" uniffi = { version = "0.28.3", features = ["build"], optional = true } serde = { version = "1.0.210", default-features = false, features = ["std", "derive"] } diff --git a/src/builder.rs b/src/builder.rs index 369dd3e19..7d2ac5ac9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1689,7 +1689,7 @@ fn optionally_install_rustls_cryptoprovider() { INIT_CRYPTO.call_once(|| { // Ensure we always install a `CryptoProvider` for `rustls` if it was somehow not previously installed by now. if rustls::crypto::CryptoProvider::get_default().is_none() { - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + let _ = rustls::crypto::ring::default_provider().install_default(); } // Refuse to startup without TLS support. Better to catch it now than even later at runtime. From e4a87d4ab4fd6e51742f023bf135176788f8e23b Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 25 Aug 2025 14:51:14 +0200 Subject: [PATCH 35/45] bump main --- Cargo.toml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9e766d113..baff63474 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,17 +52,17 @@ default = [] #lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } #lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } -lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["std"] } -lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } -lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["std"] } -lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } -lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } -lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } -lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } -lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["rest-client", "rpc-client", "tokio"] } -lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } -lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } -lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54" } +lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std"] } +lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std"] } +lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["rest-client", "rpc-client", "tokio"] } +lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } +lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } #lightning = { path = "../rust-lightning/lightning", features = ["std"] } #lightning-types = { path = "../rust-lightning/lightning-types" } @@ -109,7 +109,7 @@ winapi = { version = "0.3", features = ["winbase"] } [dev-dependencies] #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/lightningdevkit/rust-lightning", rev = "e1a31e1b6a55056a039c693246a5636957ed7a54", features = ["std", "_test_utils"] } +lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std", "_test_utils"] } #lightning = { path = "../rust-lightning/lightning", features = ["std", "_test_utils"] } proptest = "1.0.0" regex = "1.5.6" From 7a5850da2cfbb0b1df5e8ae28d7ece8b09bfb99e Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 19 Aug 2025 15:37:31 +0200 Subject: [PATCH 36/45] Expose set_paths_to_static_invoice_server --- src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bac935162..a885cef1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,6 +150,7 @@ pub use types::{ChannelDetails, CustomTlvRecord, PeerDetails, UserChannelId}; use logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger}; +use lightning::blinded_path::message::BlindedMessagePath; use lightning::chain::BestBlock; use lightning::events::bump_transaction::Wallet as LdkWallet; use lightning::impl_writeable_tlv_based; @@ -1461,6 +1462,14 @@ impl Node { Error::PersistenceFailed }) } + + /// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build [`Offer`]s with a + /// static invoice server, so the server can serve [`StaticInvoice`]s to payers on our behalf when we're offline. + pub fn set_paths_to_static_invoice_server( + &mut self, paths: Vec, + ) -> Result<(), ()> { + self.channel_manager.set_paths_to_static_invoice_server(paths) + } } impl Drop for Node { From 0bdd5b463b46d29da059e7aedae405015448beae Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 19 Aug 2025 15:48:21 +0200 Subject: [PATCH 37/45] expose get_async_receive_offer --- src/payment/bolt12.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/payment/bolt12.rs b/src/payment/bolt12.rs index 4e968deb7..e29c35d92 100644 --- a/src/payment/bolt12.rs +++ b/src/payment/bolt12.rs @@ -450,4 +450,13 @@ impl Bolt12Payment { Ok(maybe_wrap(refund)) } + + /// Retrieve an [`Offer`] for receiving async payments as an often-offline recipient. Will only return an offer if + /// [`Builder::set_paths_to_static_invoice_server`] was called and we succeeded in interactively building a + /// [`StaticInvoice`] with the static invoice server. + /// + /// Useful for posting offers to receive payments later, such as posting an offer on a website. + pub fn get_async_receive_offer(&self) -> Result { + self.channel_manager.get_async_receive_offer() + } } From a160cbf70e611f8a7f00bc5ccc2dfc74f2d47b94 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 25 Aug 2025 18:05:26 +0200 Subject: [PATCH 38/45] test wip --- src/lib.rs | 10 ++++++- tests/common/mod.rs | 3 ++ tests/integration_tests_rust.rs | 52 +++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a885cef1c..578705d55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1466,10 +1466,18 @@ impl Node { /// Sets the [`BlindedMessagePath`]s that we will use as an async recipient to interactively build [`Offer`]s with a /// static invoice server, so the server can serve [`StaticInvoice`]s to payers on our behalf when we're offline. pub fn set_paths_to_static_invoice_server( - &mut self, paths: Vec, + &self, paths: Vec, ) -> Result<(), ()> { self.channel_manager.set_paths_to_static_invoice_server(paths) } + + /// [`BlindedMessagePath`]s for an async recipient to communicate with this node and interactively + /// build [`Offer`]s and [`StaticInvoice`]s for receiving async payments. + pub fn blinded_paths_for_async_recipient( + &self, recipient_id: Vec, relative_expiry: Option, + ) -> Result, ()> { + self.channel_manager.blinded_paths_for_async_recipient(recipient_id, relative_expiry) + } } impl Drop for Node { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index f5bfe76fc..b75d5ea74 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -288,10 +288,13 @@ pub(crate) fn setup_two_nodes( ) -> (TestNode, TestNode) { println!("== Node A =="); let config_a = random_config(anchor_channels); + println!("Node A storage path: {}", config_a.node_config.storage_dir_path.clone()); + let node_a = setup_node(chain_source, config_a, None); println!("\n== Node B =="); let mut config_b = random_config(anchor_channels); + println!("Node B storage path: {}", config_b.node_config.storage_dir_path.clone()); if allow_0conf { config_b.node_config.trusted_peers_0conf.push(node_a.node_id()); } diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index fa88fe0cc..85fcc841b 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -1130,6 +1130,58 @@ fn simple_bolt12_send_receive() { assert_eq!(node_a_payments.first().unwrap().amount_msat, Some(overpaid_amount)); } +#[test] +fn static_invoice_server() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + let chain_source = TestChainSource::Esplora(&electrsd); + let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false); + + let address_a = node_a.onchain_payment().new_address().unwrap(); + let premine_amount_sat = 5_000_000; + premine_and_distribute_funds( + &bitcoind.client, + &electrsd.client, + vec![address_a], + Amount::from_sat(premine_amount_sat), + ); + + node_a.sync_wallets().unwrap(); + open_channel(&node_a, &node_b, 4_000_000, true, &electrsd); + + generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); + + node_a.sync_wallets().unwrap(); + node_b.sync_wallets().unwrap(); + + expect_channel_ready_event!(node_a, node_b.node_id()); + expect_channel_ready_event!(node_b, node_a.node_id()); + + // Sleep until we broadcasted a node announcement. + while node_b.status().latest_node_announcement_broadcast_timestamp.is_none() { + std::thread::sleep(std::time::Duration::from_millis(10)); + } + + // Sleep one more sec to make sure the node announcement propagates. + std::thread::sleep(std::time::Duration::from_secs(1)); + + let recipient_id = vec![1, 2, 3]; + let blinded_paths = node_a.blinded_paths_for_async_recipient(recipient_id, None).unwrap(); + node_b.set_paths_to_static_invoice_server(blinded_paths).unwrap(); + + // Sleep to get the cache filled. + std::thread::sleep(std::time::Duration::from_secs(1)); + + let mut iter = 0; + while node_b.bolt12_payment().get_async_receive_offer().is_err() { + std::thread::sleep(std::time::Duration::from_millis(1000)); + + iter += 1; + if iter > 10 { + assert!(false, "Failed to fetch static invoice offer"); + } + } +} + #[test] fn test_node_announcement_propagation() { let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); From a7d49a0b8cee054d89cbe1f4f28f87fd0ce2ab17 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 27 Aug 2025 11:08:35 +0200 Subject: [PATCH 39/45] cargo: use local ldk --- Cargo.toml | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index baff63474..f0bfb5971 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,29 +52,29 @@ default = [] #lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } #lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", branch = "main" } -lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std"] } -lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } -lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std"] } -lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } -lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } -lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } -lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } -lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["rest-client", "rpc-client", "tokio"] } -lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } -lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } -lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } - -#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" } -#lightning-rapid-gossip-sync = { path = "../rust-lightning/lightning-rapid-gossip-sync" } -#lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rest-client", "rpc-client", "tokio"] } -#lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync", features = ["esplora-async-https", "electrum", "time"] } -#lightning-liquidity = { path = "../rust-lightning/lightning-liquidity", features = ["std"] } -#lightning-macros = { path = "../rust-lightning/lightning-macros" } +#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std"] } +#lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +#lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std"] } +#lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +#lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +#lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +#lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +#lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["rest-client", "rpc-client", "tokio"] } +#lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } +#lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } +#lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf" } + +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" } +lightning-rapid-gossip-sync = { path = "../rust-lightning/lightning-rapid-gossip-sync" } +lightning-block-sync = { path = "../rust-lightning/lightning-block-sync", features = ["rest-client", "rpc-client", "tokio"] } +lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync", features = ["esplora-async-https", "electrum-rustls-ring", "time"] } +lightning-liquidity = { path = "../rust-lightning/lightning-liquidity", features = ["std"] } +lightning-macros = { path = "../rust-lightning/lightning-macros" } bdk_chain = { version = "0.23.0", default-features = false, features = ["std"] } bdk_esplora = { version = "0.22.0", default-features = false, features = ["async-https-rustls", "tokio"]} @@ -109,8 +109,8 @@ winapi = { version = "0.3", features = ["winbase"] } [dev-dependencies] #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/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std", "_test_utils"] } -#lightning = { path = "../rust-lightning/lightning", features = ["std", "_test_utils"] } +#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "6f78d570e407d1bab27921409a116cfeb8716bdf", features = ["std", "_test_utils"] } +lightning = { path = "../rust-lightning/lightning", features = ["std", "_test_utils"] } proptest = "1.0.0" regex = "1.5.6" From d0bd340cd0e214419580d895a35381c4976aebe7 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 27 Aug 2025 16:48:53 +0200 Subject: [PATCH 40/45] trace logging [dev] --- tests/common/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index b75d5ea74..887f2dfbf 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod logging; +use ldk_node::logger::LogLevel; use logging::TestLogWriter; use ldk_node::config::{Config, ElectrumSyncConfig, EsploraSyncConfig}; @@ -287,14 +288,14 @@ pub(crate) fn setup_two_nodes( anchors_trusted_no_reserve: bool, ) -> (TestNode, TestNode) { println!("== Node A =="); - let config_a = random_config(anchor_channels); - println!("Node A storage path: {}", config_a.node_config.storage_dir_path.clone()); + let mut config_a = random_config(anchor_channels); + config_a.node_config.storage_dir_path = "/tmp/node_a".into(); let node_a = setup_node(chain_source, config_a, None); println!("\n== Node B =="); let mut config_b = random_config(anchor_channels); - println!("Node B storage path: {}", config_b.node_config.storage_dir_path.clone()); + config_b.node_config.storage_dir_path = "/tmp/node_b".into(); if allow_0conf { config_b.node_config.trusted_peers_0conf.push(node_a.node_id()); } @@ -355,7 +356,7 @@ pub(crate) fn setup_node( match &config.log_writer { TestLogWriter::FileWriter => { - builder.set_filesystem_logger(None, None); + builder.set_filesystem_logger(None, Some(LogLevel::Trace)); }, TestLogWriter::LogFacade => { builder.set_log_facade_logger(); From e2f293b29b98364f9e1214205ae3e9e561269054 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 27 Aug 2025 16:49:08 +0200 Subject: [PATCH 41/45] wire up async receive offer fetch --- src/builder.rs | 2 +- src/event.rs | 62 ++++++++++++++++++++++++++++++--- src/types.rs | 2 +- tests/integration_tests_rust.rs | 10 ++---- 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 7d2ac5ac9..da0510b69 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1458,7 +1458,7 @@ fn build_with_store_internal( Arc::clone(&channel_manager), message_router, Arc::clone(&channel_manager), - IgnoringMessageHandler {}, + Arc::clone(&channel_manager), IgnoringMessageHandler {}, IgnoringMessageHandler {}, )); diff --git a/src/event.rs b/src/event.rs index bad1b84ab..e7c2f42c5 100644 --- a/src/event.rs +++ b/src/event.rs @@ -37,6 +37,8 @@ use lightning::events::{Event as LdkEvent, PaymentFailureReason}; use lightning::impl_writeable_tlv_based_enum; use lightning::ln::channelmanager::PaymentId; use lightning::ln::types::ChannelId; +use lightning::offers::static_invoice::StaticInvoice; +use lightning::onion_message::messenger::Responder; use lightning::routing::gossip::NodeId; use lightning::util::config::{ ChannelConfigOverrides, ChannelConfigUpdate, ChannelHandshakeConfigUpdate, @@ -56,7 +58,7 @@ use rand::{thread_rng, Rng}; use core::future::Future; use core::task::{Poll, Waker}; -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; use std::ops::Deref; use std::sync::{Arc, Condvar, Mutex}; @@ -458,6 +460,32 @@ where runtime: Arc, logger: L, config: Arc, + static_invoice_store: StaticInvoiceStore, +} + +struct StaticInvoiceStore { + pub static_invoices: Mutex, u16), StaticInvoice>>, +} + +impl StaticInvoiceStore { + fn new() -> Self { + Self { static_invoices: Mutex::new(HashMap::new()) } + } + + fn handle_static_invoice_requested( + &self, recipient_id: Vec, invoice_slot: u16, + ) -> Option { + let map = self.static_invoices.lock().unwrap(); + + map.get(&(recipient_id.clone(), invoice_slot)).cloned() + } + + fn handle_persist_static_invoice( + &self, invoice: StaticInvoice, invoice_slot: u16, recipient_id: Vec, + ) { + let mut map = self.static_invoices.lock().unwrap(); + map.insert((recipient_id, invoice_slot), invoice); + } } impl EventHandler @@ -487,6 +515,7 @@ where logger, runtime, config, + static_invoice_store: StaticInvoiceStore::new(), } } @@ -1494,11 +1523,34 @@ where LdkEvent::OnionMessagePeerConnected { .. } => { debug_assert!(false, "We currently don't support onion message interception, so this event should never be emitted."); }, - LdkEvent::PersistStaticInvoice { .. } => { - debug_assert!(false, "We currently don't support static invoice persistence, so this event should never be emitted."); + + LdkEvent::PersistStaticInvoice { + invoice, + invoice_slot, + recipient_id, + invoice_persisted_path, + } => { + self.static_invoice_store.handle_persist_static_invoice( + invoice, + invoice_slot, + recipient_id, + ); + + self.channel_manager.static_invoice_persisted(invoice_persisted_path); }, - LdkEvent::StaticInvoiceRequested { .. } => { - debug_assert!(false, "We currently don't support static invoice persistence, so this event should never be emitted."); + LdkEvent::StaticInvoiceRequested { recipient_id, invoice_slot, reply_path } => { + let invoice = self + .static_invoice_store + .handle_static_invoice_requested(recipient_id, invoice_slot); + + if let Some(invoice) = invoice { + match self.channel_manager.send_static_invoice(invoice, reply_path) { + Ok(()) => {}, + Err(_) => { + log_error!(self.logger, "Failed to send static invoice"); + }, + } + } }, LdkEvent::FundingTransactionReadyForSigning { .. } => { debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted."); diff --git a/src/types.rs b/src/types.rs index b9bc1c317..3635badff 100644 --- a/src/types.rs +++ b/src/types.rs @@ -123,7 +123,7 @@ pub(crate) type OnionMessenger = lightning::onion_message::messenger::OnionMesse Arc, Arc, Arc, - IgnoringMessageHandler, + Arc, IgnoringMessageHandler, IgnoringMessageHandler, >; diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 85fcc841b..95330b5a7 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -1171,15 +1171,9 @@ fn static_invoice_server() { // Sleep to get the cache filled. std::thread::sleep(std::time::Duration::from_secs(1)); - let mut iter = 0; - while node_b.bolt12_payment().get_async_receive_offer().is_err() { - std::thread::sleep(std::time::Duration::from_millis(1000)); + let offer = node_b.bolt12_payment().get_async_receive_offer().unwrap(); - iter += 1; - if iter > 10 { - assert!(false, "Failed to fetch static invoice offer"); - } - } + println!("Offer: {}", offer); } #[test] From 98c2551e45ff8146dded62858eec4a42571b8acf Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 28 Aug 2025 10:00:09 +0200 Subject: [PATCH 42/45] SIS sep file --- src/event.rs | 48 +++++++++---------------------------- src/lib.rs | 6 +++++ src/static_invoice_store.rs | 28 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 src/static_invoice_store.rs diff --git a/src/event.rs b/src/event.rs index e7c2f42c5..e155fe229 100644 --- a/src/event.rs +++ b/src/event.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 crate::static_invoice_store::StaticInvoiceStore; use crate::types::{CustomTlvRecord, DynStore, PaymentStore, Sweeper, Wallet}; use crate::{ @@ -37,8 +38,6 @@ use lightning::events::{Event as LdkEvent, PaymentFailureReason}; use lightning::impl_writeable_tlv_based_enum; use lightning::ln::channelmanager::PaymentId; use lightning::ln::types::ChannelId; -use lightning::offers::static_invoice::StaticInvoice; -use lightning::onion_message::messenger::Responder; use lightning::routing::gossip::NodeId; use lightning::util::config::{ ChannelConfigOverrides, ChannelConfigUpdate, ChannelHandshakeConfigUpdate, @@ -58,7 +57,7 @@ use rand::{thread_rng, Rng}; use core::future::Future; use core::task::{Poll, Waker}; -use std::collections::{HashMap, VecDeque}; +use std::collections::VecDeque; use std::ops::Deref; use std::sync::{Arc, Condvar, Mutex}; @@ -463,31 +462,6 @@ where static_invoice_store: StaticInvoiceStore, } -struct StaticInvoiceStore { - pub static_invoices: Mutex, u16), StaticInvoice>>, -} - -impl StaticInvoiceStore { - fn new() -> Self { - Self { static_invoices: Mutex::new(HashMap::new()) } - } - - fn handle_static_invoice_requested( - &self, recipient_id: Vec, invoice_slot: u16, - ) -> Option { - let map = self.static_invoices.lock().unwrap(); - - map.get(&(recipient_id.clone(), invoice_slot)).cloned() - } - - fn handle_persist_static_invoice( - &self, invoice: StaticInvoice, invoice_slot: u16, recipient_id: Vec, - ) { - let mut map = self.static_invoices.lock().unwrap(); - map.insert((recipient_id, invoice_slot), invoice); - } -} - impl EventHandler where L::Target: LdkLogger, @@ -498,8 +472,9 @@ where channel_manager: Arc, connection_manager: Arc>, output_sweeper: Arc, network_graph: Arc, liquidity_source: Option>>>, - payment_store: Arc, peer_store: Arc>, runtime: Arc, - logger: L, config: Arc, + payment_store: Arc, peer_store: Arc>, + static_invoice_store: StaticInvoiceStore, runtime: Arc, logger: L, + config: Arc, ) -> Self { Self { event_queue, @@ -515,7 +490,7 @@ where logger, runtime, config, - static_invoice_store: StaticInvoiceStore::new(), + static_invoice_store, } } @@ -1530,18 +1505,17 @@ where recipient_id, invoice_persisted_path, } => { - self.static_invoice_store.handle_persist_static_invoice( - invoice, - invoice_slot, - recipient_id, - ); + self.static_invoice_store + .handle_persist_static_invoice(invoice, invoice_slot, recipient_id) + .await; self.channel_manager.static_invoice_persisted(invoice_persisted_path); }, LdkEvent::StaticInvoiceRequested { recipient_id, invoice_slot, reply_path } => { let invoice = self .static_invoice_store - .handle_static_invoice_requested(recipient_id, invoice_slot); + .handle_static_invoice_requested(recipient_id, invoice_slot) + .await; if let Some(invoice) = invoice { match self.channel_manager.send_static_invoice(invoice, reply_path) { diff --git a/src/lib.rs b/src/lib.rs index 578705d55..111527d7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,6 +95,7 @@ mod message_handler; pub mod payment; mod peer_store; mod runtime; +mod static_invoice_store; mod tx_broadcaster; mod types; mod wallet; @@ -171,6 +172,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +use crate::static_invoice_store::StaticInvoiceStore; + #[cfg(feature = "uniffi")] uniffi::include_scaffolding!("ldk_node"); @@ -498,6 +501,8 @@ impl Node { Arc::clone(&self.logger), )); + let static_invoice_store = StaticInvoiceStore::new(); + let event_handler = Arc::new(EventHandler::new( Arc::clone(&self.event_queue), Arc::clone(&self.wallet), @@ -509,6 +514,7 @@ impl Node { self.liquidity_source.clone(), Arc::clone(&self.payment_store), Arc::clone(&self.peer_store), + static_invoice_store, Arc::clone(&self.runtime), Arc::clone(&self.logger), Arc::clone(&self.config), diff --git a/src/static_invoice_store.rs b/src/static_invoice_store.rs new file mode 100644 index 000000000..66c9c49c7 --- /dev/null +++ b/src/static_invoice_store.rs @@ -0,0 +1,28 @@ +use std::{collections::HashMap, sync::Mutex}; + +use lightning::offers::static_invoice::StaticInvoice; + +pub(crate) struct StaticInvoiceStore { + pub static_invoices: Mutex, u16), StaticInvoice>>, +} + +impl StaticInvoiceStore { + pub(crate) fn new() -> Self { + Self { static_invoices: Mutex::new(HashMap::new()) } + } + + pub(crate) async fn handle_static_invoice_requested( + &self, recipient_id: Vec, invoice_slot: u16, + ) -> Option { + let map = self.static_invoices.lock().unwrap(); + + map.get(&(recipient_id.clone(), invoice_slot)).cloned() + } + + pub(crate) async fn handle_persist_static_invoice( + &self, invoice: StaticInvoice, invoice_slot: u16, recipient_id: Vec, + ) { + let mut map = self.static_invoices.lock().unwrap(); + map.insert((recipient_id, invoice_slot), invoice); + } +} From e5c3a5c9d77afe505a7da7b60bc7ae0b3fef2625 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 28 Aug 2025 12:31:09 +0200 Subject: [PATCH 43/45] kvstore static invoices --- src/event.rs | 28 +++++++++++++++------ src/lib.rs | 2 +- src/static_invoice_store.rs | 50 ++++++++++++++++++++++++++++--------- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/event.rs b/src/event.rs index e155fe229..a711a25a0 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1505,9 +1505,16 @@ where recipient_id, invoice_persisted_path, } => { - self.static_invoice_store + match self + .static_invoice_store .handle_persist_static_invoice(invoice, invoice_slot, recipient_id) - .await; + .await + { + Ok(_) => {}, + Err(e) => { + log_error!(self.logger, "Failed to persist static invoice: {}", e); + }, + }; self.channel_manager.static_invoice_persisted(invoice_persisted_path); }, @@ -1517,13 +1524,18 @@ where .handle_static_invoice_requested(recipient_id, invoice_slot) .await; - if let Some(invoice) = invoice { - match self.channel_manager.send_static_invoice(invoice, reply_path) { - Ok(()) => {}, - Err(_) => { + match invoice { + Ok(Some(invoice)) => { + if let Err(_) = + self.channel_manager.send_static_invoice(invoice, reply_path) + { log_error!(self.logger, "Failed to send static invoice"); - }, - } + } + }, + Ok(None) => {}, + Err(e) => { + log_error!(self.logger, "Failed to retrieve static invoice: {}", e); + }, } }, LdkEvent::FundingTransactionReadyForSigning { .. } => { diff --git a/src/lib.rs b/src/lib.rs index 111527d7b..87a0ec7de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -501,7 +501,7 @@ impl Node { Arc::clone(&self.logger), )); - let static_invoice_store = StaticInvoiceStore::new(); + let static_invoice_store = StaticInvoiceStore::new(Arc::clone(&self.kv_store)); let event_handler = Arc::new(EventHandler::new( Arc::clone(&self.event_queue), diff --git a/src/static_invoice_store.rs b/src/static_invoice_store.rs index 66c9c49c7..d5e61e46f 100644 --- a/src/static_invoice_store.rs +++ b/src/static_invoice_store.rs @@ -1,28 +1,54 @@ -use std::{collections::HashMap, sync::Mutex}; - -use lightning::offers::static_invoice::StaticInvoice; +use crate::{hex_utils, types::DynStore}; +use bitcoin::hashes::{sha256, Hash}; +use lightning::{offers::static_invoice::StaticInvoice, util::ser::Writeable}; +use std::sync::Arc; pub(crate) struct StaticInvoiceStore { - pub static_invoices: Mutex, u16), StaticInvoice>>, + kv_store: Arc, } impl StaticInvoiceStore { - pub(crate) fn new() -> Self { - Self { static_invoices: Mutex::new(HashMap::new()) } + // Static invoices are stored at "static_invoices//". + // + // Example: + // static_invoices/039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81/0000 + const PRIMARY_NAMESPACE: &str = "static_invoices"; + + pub(crate) fn new(kv_store: Arc) -> Self { + Self { kv_store } } pub(crate) async fn handle_static_invoice_requested( &self, recipient_id: Vec, invoice_slot: u16, - ) -> Option { - let map = self.static_invoices.lock().unwrap(); + ) -> Result, lightning::io::Error> { + let (secondary_namespace, key) = Self::get_storage_location(invoice_slot, recipient_id); - map.get(&(recipient_id.clone(), invoice_slot)).cloned() + self.kv_store.read(Self::PRIMARY_NAMESPACE, &secondary_namespace, &key).and_then(|data| { + data.try_into().map(Some).map_err(|e| { + lightning::io::Error::new( + lightning::io::ErrorKind::InvalidData, + format!("Failed to parse static invoice: {:?}", e), + ) + }) + }) } pub(crate) async fn handle_persist_static_invoice( &self, invoice: StaticInvoice, invoice_slot: u16, recipient_id: Vec, - ) { - let mut map = self.static_invoices.lock().unwrap(); - map.insert((recipient_id, invoice_slot), invoice); + ) -> Result<(), lightning::io::Error> { + let (secondary_namespace, key) = Self::get_storage_location(invoice_slot, recipient_id); + + let mut buf = Vec::new(); + invoice.write(&mut buf)?; + + self.kv_store.write(Self::PRIMARY_NAMESPACE, &secondary_namespace, &key, buf) + } + + fn get_storage_location(invoice_slot: u16, recipient_id: Vec) -> (String, String) { + let hash = sha256::Hash::hash(&recipient_id).to_byte_array(); + let secondary_namespace = hex_utils::to_string(&hash); + + let key = hex_utils::to_string(&invoice_slot.to_be_bytes()); + (secondary_namespace, key) } } From 19ce3c48facebb34757279faf6efaebb1c49d45d Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 28 Aug 2025 13:30:52 +0200 Subject: [PATCH 44/45] add rate limiter --- src/lib.rs | 1 + src/rate_limiter.rs | 34 ++++++++++++++++++++++++++++++++++ src/static_invoice_store.rs | 25 +++++++++++++++++++++---- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 src/rate_limiter.rs diff --git a/src/lib.rs b/src/lib.rs index 87a0ec7de..f0b4c5c82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,6 +94,7 @@ pub mod logger; mod message_handler; pub mod payment; mod peer_store; +mod rate_limiter; mod runtime; mod static_invoice_store; mod tx_broadcaster; diff --git a/src/rate_limiter.rs b/src/rate_limiter.rs new file mode 100644 index 000000000..78020b3da --- /dev/null +++ b/src/rate_limiter.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +pub(crate) struct RateLimiter { + users: HashMap, Instant>, + min_interval: Duration, +} + +impl RateLimiter { + pub(crate) fn new(min_interval: Duration) -> Self { + Self { users: HashMap::new(), min_interval } + } + + pub(crate) fn allow(&mut self, user_id: &[u8]) -> bool { + let now = Instant::now(); + match self.users.get(user_id) { + Some(&last) if now.duration_since(last) < self.min_interval => false, + _ => { + let new_user = self.users.insert(user_id.to_vec(), now).is_none(); + + // Each time a new user is added, we take the opportunity to clean up old rate limits. + if new_user { + self.garbage_collect(); + } + true + }, + } + } + + fn garbage_collect(&mut self) { + let now = Instant::now(); + self.users.retain(|_, &mut last| now.duration_since(last) < self.min_interval); + } +} diff --git a/src/static_invoice_store.rs b/src/static_invoice_store.rs index d5e61e46f..d28b2bd4e 100644 --- a/src/static_invoice_store.rs +++ b/src/static_invoice_store.rs @@ -1,26 +1,41 @@ -use crate::{hex_utils, types::DynStore}; +use crate::{hex_utils, rate_limiter::RateLimiter, types::DynStore}; use bitcoin::hashes::{sha256, Hash}; use lightning::{offers::static_invoice::StaticInvoice, util::ser::Writeable}; -use std::sync::Arc; +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; pub(crate) struct StaticInvoiceStore { kv_store: Arc, + rate_limiter: Mutex, } impl StaticInvoiceStore { // Static invoices are stored at "static_invoices//". // // Example: - // static_invoices/039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81/0000 + // static_invoices/039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81/001f const PRIMARY_NAMESPACE: &str = "static_invoices"; pub(crate) fn new(kv_store: Arc) -> Self { - Self { kv_store } + Self { kv_store, rate_limiter: Mutex::new(RateLimiter::new(Duration::from_millis(100))) } + } + + fn check_rate_limit(&self, recipient_id: &[u8]) -> Result<(), lightning::io::Error> { + let mut limiter = self.rate_limiter.lock().unwrap(); + if !limiter.allow(recipient_id) { + Err(lightning::io::Error::new(lightning::io::ErrorKind::Other, "Rate limit exceeded")) + } else { + Ok(()) + } } pub(crate) async fn handle_static_invoice_requested( &self, recipient_id: Vec, invoice_slot: u16, ) -> Result, lightning::io::Error> { + self.check_rate_limit(&recipient_id)?; + let (secondary_namespace, key) = Self::get_storage_location(invoice_slot, recipient_id); self.kv_store.read(Self::PRIMARY_NAMESPACE, &secondary_namespace, &key).and_then(|data| { @@ -36,6 +51,8 @@ impl StaticInvoiceStore { pub(crate) async fn handle_persist_static_invoice( &self, invoice: StaticInvoice, invoice_slot: u16, recipient_id: Vec, ) -> Result<(), lightning::io::Error> { + self.check_rate_limit(&recipient_id)?; + let (secondary_namespace, key) = Self::get_storage_location(invoice_slot, recipient_id); let mut buf = Vec::new(); From 84001e5d32ad2037037c742417ff9fc6ab6a2ff3 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Thu, 28 Aug 2025 13:41:47 +0200 Subject: [PATCH 45/45] split rate limit --- src/static_invoice_store.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/static_invoice_store.rs b/src/static_invoice_store.rs index d28b2bd4e..6fe9be784 100644 --- a/src/static_invoice_store.rs +++ b/src/static_invoice_store.rs @@ -8,7 +8,8 @@ use std::{ pub(crate) struct StaticInvoiceStore { kv_store: Arc, - rate_limiter: Mutex, + request_rate_limiter: Mutex, + persist_rate_limiter: Mutex, } impl StaticInvoiceStore { @@ -19,11 +20,17 @@ impl StaticInvoiceStore { const PRIMARY_NAMESPACE: &str = "static_invoices"; pub(crate) fn new(kv_store: Arc) -> Self { - Self { kv_store, rate_limiter: Mutex::new(RateLimiter::new(Duration::from_millis(100))) } + Self { + kv_store, + request_rate_limiter: Mutex::new(RateLimiter::new(Duration::from_millis(100))), + persist_rate_limiter: Mutex::new(RateLimiter::new(Duration::from_millis(100))), + } } - fn check_rate_limit(&self, recipient_id: &[u8]) -> Result<(), lightning::io::Error> { - let mut limiter = self.rate_limiter.lock().unwrap(); + fn check_rate_limit( + limiter: &Mutex, recipient_id: &[u8], + ) -> Result<(), lightning::io::Error> { + let mut limiter = limiter.lock().unwrap(); if !limiter.allow(recipient_id) { Err(lightning::io::Error::new(lightning::io::ErrorKind::Other, "Rate limit exceeded")) } else { @@ -34,7 +41,7 @@ impl StaticInvoiceStore { pub(crate) async fn handle_static_invoice_requested( &self, recipient_id: Vec, invoice_slot: u16, ) -> Result, lightning::io::Error> { - self.check_rate_limit(&recipient_id)?; + Self::check_rate_limit(&self.request_rate_limiter, &recipient_id)?; let (secondary_namespace, key) = Self::get_storage_location(invoice_slot, recipient_id); @@ -51,7 +58,7 @@ impl StaticInvoiceStore { pub(crate) async fn handle_persist_static_invoice( &self, invoice: StaticInvoice, invoice_slot: u16, recipient_id: Vec, ) -> Result<(), lightning::io::Error> { - self.check_rate_limit(&recipient_id)?; + Self::check_rate_limit(&self.persist_rate_limiter, &recipient_id)?; let (secondary_namespace, key) = Self::get_storage_location(invoice_slot, recipient_id);