From 284ce8e68786b25c180d74e8143bab366cc87a50 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Wed, 24 Jun 2026 21:49:12 -0500 Subject: [PATCH] Update ldk-node and align lightning deps Bump ldk-node to 8a54260. The new revision builds against rust-lightning 3dfcc4c, so lightning, lightning-invoice, and the bitcoin-payment-instructions fork are moved to revisions pinned to that same commit. Without sharing one lightning version, invoice and offer types crossing the ldk-node boundary fail to unify. Migrate to the changed ldk-node API: - PaymentKind::Bolt11Jit is folded into Bolt11, which now carries counterparty_skimmed_fee_msat directly. - The SplicePending and SpliceFailed events are renamed to SpliceNegotiated and SpliceNegotiationFailed. - ChannelDetails exposes the counterparty node id through a nested counterparty field rather than counterparty_node_id. - Builder liquidity setup is renamed: set_liquidity_source_lsps2 becomes add_liquidity_source (trusting the LSP for 0conf as before) and set_liquidity_provider_lsps2 becomes enable_liquidity_provider. The bundled SqliteStore and VssStore now implement only the async KVStore trait, so the DynStore wrapper drops its KVStoreSync half; build_with_store no longer requires the sync interface and nothing else in the crate used it. Co-Authored-By: Claude Opus 4.8 (1M context) --- Cargo.toml | 6 +- orange-sdk/Cargo.toml | 2 +- orange-sdk/src/dyn_store.rs | 103 ++++--------------------- orange-sdk/src/event.rs | 10 +-- orange-sdk/src/ffi/ldk_node.rs | 2 +- orange-sdk/src/lib.rs | 13 +++- orange-sdk/src/lightning_wallet.rs | 27 +++---- orange-sdk/src/trusted_wallet/dummy.rs | 14 ++-- orange-sdk/tests/integration_tests.rs | 4 +- orange-sdk/tests/test_utils.rs | 2 +- 10 files changed, 53 insertions(+), 130 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d90585b..cac0297 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,9 @@ codegen-units = 1 # Reduce number of codegen units to increase optimizations. panic = 'abort' # Abort on panic [workspace.dependencies] -bitcoin-payment-instructions = { git = "https://github.com/jkczyz/bitcoin-payment-instructions", rev = "679dac50cc0d81ec4d31da94b93d467e5308f16a" } -lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144" } -lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "369a2cf9c8ef810deea0cd2b4cf6ed0691b78144" } +bitcoin-payment-instructions = { git = "https://github.com/tnull/bitcoin-payment-instructions", rev = "ff09ce9401afa448549a8f101172700bcd14d7bb" } +lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "3dfcc4cca1866c5e5d4d4eaf3b82e09584e2ce5c" } +lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "3dfcc4cca1866c5e5d4d4eaf3b82e09584e2ce5c" } [profile.release] panic = "abort" diff --git a/orange-sdk/Cargo.toml b/orange-sdk/Cargo.toml index 1852672..0920b31 100644 --- a/orange-sdk/Cargo.toml +++ b/orange-sdk/Cargo.toml @@ -23,7 +23,7 @@ _cashu-tests = ["_test-utils", "cdk-ldk-node", "cdk/mint", "cdk-sqlite", "cdk-ax [dependencies] graduated-rebalancer = { path = "../graduated-rebalancer", version = "0.1.0" } -ldk-node = { git = "https://github.com/lightningdevkit/ldk-node", rev = "109978de1f57fc3b3f23f241f238b97d9deaa56a" } +ldk-node = { git = "https://github.com/lightningdevkit/ldk-node", rev = "8a5426044bdcae6369d7a847697c6143676e2df5" } lightning-macros = "0.2.0" bitcoin-payment-instructions = { workspace = true, features = ["http"] } chrono = { version = "0.4", default-features = false } diff --git a/orange-sdk/src/dyn_store.rs b/orange-sdk/src/dyn_store.rs index 7501f57..d41c559 100644 --- a/orange-sdk/src/dyn_store.rs +++ b/orange-sdk/src/dyn_store.rs @@ -1,26 +1,22 @@ -//! Object-safe wrapper around ldk-node's `KVStore` + `KVStoreSync` traits. +//! Object-safe wrapper around ldk-node's async `KVStore` trait. //! //! `lightning`'s `KVStore` returns `impl Future` from its methods, which makes the trait not -//! object-safe — orange-sdk can't share a backend across components as `Arc`. The -//! supertrait `SyncAndAsyncKVStore` that ldk-node exposes inherits the same problem, and even -//! if it didn't, no `Deref` blanket impl exists for `KVStoreSync`, so `Arc<...>` doesn't satisfy -//! it on its own. +//! object-safe — orange-sdk can't share a backend across components as `Arc`. //! -//! This module defines `DynStore`, an object-safe trait covering both sync and async kv -//! methods (async ones return boxed futures), with a blanket impl over any concrete type that -//! implements `KVStore + KVStoreSync`. The whole crate stores backends as -//! `Arc`; conversion to a value ldk-node accepts happens at the call site -//! through a thin newtype that delegates both traits back to `DynStore`. +//! This module defines `DynStore`, an object-safe trait covering the kv methods (returning +//! boxed futures), with a blanket impl over any concrete type that implements `KVStore`. The +//! whole crate stores backends as `Arc`; conversion to a value ldk-node accepts +//! happens at the call site through a thin newtype that delegates back to `DynStore`. use std::future::Future; use std::pin::Pin; use std::sync::Arc; use ldk_node::lightning::io; -use ldk_node::lightning::util::persist::{KVStore, KVStoreSync}; +use ldk_node::lightning::util::persist::KVStore; -/// Object-safe view of a `KVStore + KVStoreSync` backend. Async methods return boxed -/// futures so the trait can be used through `dyn`. +/// Object-safe view of a `KVStore` backend. Async methods return boxed futures so the trait +/// can be used through `dyn`. pub(crate) trait DynStore: Send + Sync + 'static { fn read_async( &self, primary_namespace: &str, secondary_namespace: &str, key: &str, @@ -37,27 +33,11 @@ pub(crate) trait DynStore: Send + Sync + 'static { fn list_async( &self, primary_namespace: &str, secondary_namespace: &str, ) -> Pin, io::Error>> + Send + 'static>>; - - fn read_sync( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, - ) -> Result, io::Error>; - - fn write_sync( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec, - ) -> Result<(), io::Error>; - - fn remove_sync( - &self, primary_namespace: &str, secondary_namespace: &str, key: &str, lazy: bool, - ) -> Result<(), io::Error>; - - fn list_sync( - &self, primary_namespace: &str, secondary_namespace: &str, - ) -> Result, io::Error>; } impl DynStore for T where - T: KVStore + KVStoreSync + Send + Sync + 'static, + T: KVStore + Send + Sync + 'static, { fn read_async( &self, p: &str, s: &str, k: &str, @@ -82,33 +62,12 @@ where ) -> Pin, io::Error>> + Send + 'static>> { Box::pin(::list(self, p, s)) } - - fn read_sync(&self, p: &str, s: &str, k: &str) -> Result, io::Error> { - ::read(self, p, s, k) - } - - fn write_sync(&self, p: &str, s: &str, k: &str, buf: Vec) -> Result<(), io::Error> { - ::write(self, p, s, k, buf) - } - - fn remove_sync(&self, p: &str, s: &str, k: &str, lazy: bool) -> Result<(), io::Error> { - ::remove(self, p, s, k, lazy) - } - - fn list_sync(&self, p: &str, s: &str) -> Result, io::Error> { - ::list(self, p, s) - } } -// Make `Arc` itself implement `KVStore` + `KVStoreSync` so the same handle -// orange-sdk shares internally can be handed to ldk-node's `build_with_store`. We give it -// `KVStore::read` etc. by forwarding to the boxed-future variants on the trait, and the -// sync trait by forwarding to the sync variants. -// -// Note: we impl on `dyn DynStore` (which is local), not `Arc` directly — that gives us -// `&dyn DynStore: KVStore + KVStoreSync` and, via lightning's `Deref` blanket impl for -// `KVStore`, `Arc: KVStore`. For `KVStoreSync` (no `Deref` blanket exists) -// callers wrap the `Arc` in `LdkNodeStore` below before handing it to ldk-node. +// Make `dyn DynStore` itself implement `KVStore` so the same handle orange-sdk shares +// internally can be handed to ldk-node's `build_with_store`. We forward to the boxed-future +// variants on the trait. Via lightning's `Deref` blanket impl for `KVStore`, this gives us +// `Arc: KVStore` too. impl KVStore for dyn DynStore { fn read( &self, p: &str, s: &str, k: &str, @@ -132,24 +91,9 @@ impl KVStore for dyn DynStore { } } -impl KVStoreSync for dyn DynStore { - fn read(&self, p: &str, s: &str, k: &str) -> Result, io::Error> { - self.read_sync(p, s, k) - } - fn write(&self, p: &str, s: &str, k: &str, buf: Vec) -> Result<(), io::Error> { - self.write_sync(p, s, k, buf) - } - fn remove(&self, p: &str, s: &str, k: &str, lazy: bool) -> Result<(), io::Error> { - self.remove_sync(p, s, k, lazy) - } - fn list(&self, p: &str, s: &str) -> Result, io::Error> { - self.list_sync(p, s) - } -} - /// Cloneable handle wrapping `Arc` that satisfies ldk-node's -/// `SyncAndAsyncKVStore + Send + Sync + 'static` bound on `build_with_store`. Both trait -/// impls just forward to the underlying `dyn DynStore`. +/// `KVStore + Send + Sync + 'static` bound on `build_with_store`. The trait impl just +/// forwards to the underlying `dyn DynStore`. #[derive(Clone)] pub(crate) struct LdkNodeStore(pub(crate) Arc); @@ -175,18 +119,3 @@ impl KVStore for LdkNodeStore { self.0.list_async(p, s) } } - -impl KVStoreSync for LdkNodeStore { - fn read(&self, p: &str, s: &str, k: &str) -> Result, io::Error> { - self.0.read_sync(p, s, k) - } - fn write(&self, p: &str, s: &str, k: &str, buf: Vec) -> Result<(), io::Error> { - self.0.write_sync(p, s, k, buf) - } - fn remove(&self, p: &str, s: &str, k: &str, lazy: bool) -> Result<(), io::Error> { - self.0.remove_sync(p, s, k, lazy) - } - fn list(&self, p: &str, s: &str) -> Result, io::Error> { - self.0.list_sync(p, s) - } -} diff --git a/orange-sdk/src/event.rs b/orange-sdk/src/event.rs index e36127c..df1e960 100644 --- a/orange-sdk/src/event.rs +++ b/orange-sdk/src/event.rs @@ -383,7 +383,7 @@ impl LdkEventHandler { } => { let payment_id = payment_id.expect("this is safe"); let lsp_fee_msats = self.ldk_node.payment(&payment_id).and_then(|p| { - if let PaymentKind::Bolt11Jit { counterparty_skimmed_fee_msat, .. } = p.kind { + if let PaymentKind::Bolt11 { counterparty_skimmed_fee_msat, .. } = p.kind { counterparty_skimmed_fee_msat } else { None @@ -466,13 +466,13 @@ impl LdkEventHandler { return; } }, - ldk_node::Event::SplicePending { + ldk_node::Event::SpliceNegotiated { channel_id, user_channel_id, counterparty_node_id, new_funding_txo, } => { - log_debug!(self.logger, "Received SplicePending event {event:?}"); + log_debug!(self.logger, "Received SpliceNegotiated event {event:?}"); // Reserve the metadata slot before delivering so any task waking on the // inbox (the rebalancer's `OnChainRebalanceInitiated` for splice-in, // `pay_lightning` for splice-out) sees an entry to upsert. @@ -493,8 +493,8 @@ impl LdkEventHandler { return; } }, - ldk_node::Event::SpliceFailed { .. } => { - log_warn!(self.logger, "Received SpliceFailed event: {event:?}"); + ldk_node::Event::SpliceNegotiationFailed { .. } => { + log_warn!(self.logger, "Received SpliceNegotiationFailed event: {event:?}"); }, } diff --git a/orange-sdk/src/ffi/ldk_node.rs b/orange-sdk/src/ffi/ldk_node.rs index fb21e87..a15c579 100644 --- a/orange-sdk/src/ffi/ldk_node.rs +++ b/orange-sdk/src/ffi/ldk_node.rs @@ -69,7 +69,7 @@ impl ChannelDetails { /// The node ID of the counterparty. pub fn counterparty_node_id(&self) -> String { - self.0.counterparty_node_id.to_string() + self.0.counterparty.node_id.to_string() } /// The funding transaction output. diff --git a/orange-sdk/src/lib.rs b/orange-sdk/src/lib.rs index b234d4f..005712d 100644 --- a/orange-sdk/src/lib.rs +++ b/orange-sdk/src/lib.rs @@ -893,8 +893,9 @@ impl Wallet { for payment in lightning_payments { use ldk_node::payment::PaymentDirection; let lightning_receive_fee = match payment.kind { - PaymentKind::Bolt11Jit { counterparty_skimmed_fee_msat, .. } => { - let msats = counterparty_skimmed_fee_msat.unwrap_or(0); + // A skimmed fee is only ever set on an inbound JIT-channel receive; outbound and + // regular inbound Bolt11 payments leave it `None`. + PaymentKind::Bolt11 { counterparty_skimmed_fee_msat: Some(msats), .. } => { debug_assert_eq!(payment.direction, PaymentDirection::Inbound); Some(Amount::from_milli_sats(msats).expect("Must be valid")) }, @@ -1101,10 +1102,14 @@ impl Wallet { let res = self.inner.ln_wallet.get_bolt11_invoice(amount).await; match res { Ok(inv) => inv, - Err(NodeError::ConnectionFailed) => { + Err( + NodeError::ConnectionFailed + | NodeError::LiquidityRequestFailed + | NodeError::LiquiditySourceUnavailable, + ) => { log_warn!( self.inner.logger, - "Failed to connect to LSP when getting BOLT 11 invoice, falling back to trusted wallet" + "Failed to source inbound liquidity from LSP when getting BOLT 11 invoice, falling back to trusted wallet" ); from_trusted = true; self.inner.trusted.get_bolt11_invoice(amount).await? diff --git a/orange-sdk/src/lightning_wallet.rs b/orange-sdk/src/lightning_wallet.rs index 3e2b2ac..bfc3d78 100644 --- a/orange-sdk/src/lightning_wallet.rs +++ b/orange-sdk/src/lightning_wallet.rs @@ -102,7 +102,7 @@ impl LightningWallet { } let (lsp_socket_addr, lsp_node_id, lsp_token) = config.lsp; - builder.set_liquidity_source_lsps2(lsp_node_id, lsp_socket_addr.clone(), lsp_token); + builder.add_liquidity_source(lsp_node_id, lsp_socket_addr.clone(), lsp_token, true); match config.chain_source { ChainSource::Esplora { url, username, password } => { let sync_config = if config.network == Network::Regtest { @@ -329,7 +329,7 @@ impl LightningWallet { // find existing channel to splice out of let channels = self.inner.ldk_node.list_channels(); let channel = - channels.iter().find(|c| c.counterparty_node_id == self.inner.lsp_node_id); + channels.iter().find(|c| c.counterparty.node_id == self.inner.lsp_node_id); match channel { None => { @@ -339,7 +339,7 @@ impl LightningWallet { Some(chan) => { self.inner.ldk_node.splice_out( &chan.user_channel_id, - chan.counterparty_node_id, + chan.counterparty.node_id, address, amount_sats, )?; @@ -376,13 +376,13 @@ impl LightningWallet { pub(crate) async fn splice_all_into_channel(&self) -> Result { // find existing channel to splice into let channels = self.inner.ldk_node.list_channels(); - let channel = channels.iter().find(|c| c.counterparty_node_id == self.inner.lsp_node_id); + let channel = channels.iter().find(|c| c.counterparty.node_id == self.inner.lsp_node_id); match channel { Some(chan) => { self.inner .ldk_node - .splice_in_with_all(&chan.user_channel_id, chan.counterparty_node_id)?; + .splice_in_with_all(&chan.user_channel_id, chan.counterparty.node_id)?; Ok(chan.user_channel_id) }, None => { @@ -409,11 +409,11 @@ impl LightningWallet { if chan.is_usable { self.inner .ldk_node - .close_channel(&chan.user_channel_id, chan.counterparty_node_id)?; + .close_channel(&chan.user_channel_id, chan.counterparty.node_id)?; } else { self.inner.ldk_node.force_close_channel( &chan.user_channel_id, - chan.counterparty_node_id, + chan.counterparty.node_id, None, )?; } @@ -464,11 +464,7 @@ impl graduated_rebalancer::LightningWallet for LightningWallet { loop { if let Some(payment) = self.inner.ldk_node.payment(&id) { let counterparty_skimmed_fee_msat = match payment.kind { - PaymentKind::Bolt11 { hash, .. } => { - debug_assert!(hash.0 == payment_hash, "Payment Hash mismatch"); - None - }, - PaymentKind::Bolt11Jit { hash, counterparty_skimmed_fee_msat, .. } => { + PaymentKind::Bolt11 { hash, counterparty_skimmed_fee_msat, .. } => { debug_assert!(hash.0 == payment_hash, "Payment Hash mismatch"); counterparty_skimmed_fee_msat }, @@ -492,7 +488,7 @@ impl graduated_rebalancer::LightningWallet for LightningWallet { fn has_channel_with_lsp(&self) -> bool { let channels = self.inner.ldk_node.list_channels(); - channels.iter().any(|c| c.counterparty_node_id == self.inner.lsp_node_id) + channels.iter().any(|c| c.counterparty.node_id == self.inner.lsp_node_id) } fn open_channel_with_lsp( @@ -550,10 +546,7 @@ impl From for TxStatus { impl From<&PaymentDetails> for PaymentType { fn from(d: &PaymentDetails) -> PaymentType { match (&d.kind, d.direction == PaymentDirection::Outbound) { - ( - PaymentKind::Bolt11 { preimage, .. } | PaymentKind::Bolt11Jit { preimage, .. }, - true, - ) => { + (PaymentKind::Bolt11 { preimage, .. }, true) => { if d.status == PaymentStatus::Succeeded { debug_assert!(preimage.is_some()); } diff --git a/orange-sdk/src/trusted_wallet/dummy.rs b/orange-sdk/src/trusted_wallet/dummy.rs index 04a2e8f..36146ad 100644 --- a/orange-sdk/src/trusted_wallet/dummy.rs +++ b/orange-sdk/src/trusted_wallet/dummy.rs @@ -210,8 +210,8 @@ impl DummyTrustedWallet { Event::ChannelPending { .. } => {}, Event::ChannelReady { .. } => {}, Event::ChannelClosed { .. } => {}, - Event::SplicePending { .. } => {}, - Event::SpliceFailed { .. } => {}, + Event::SpliceNegotiated { .. } => {}, + Event::SpliceNegotiationFailed { .. } => {}, } println!("dummy: {event:?}"); if let Err(e) = events_ref.event_handled() { @@ -294,7 +294,7 @@ impl DummyTrustedWallet { lsp_clone .list_channels() .iter() - .any(|c| c.counterparty_node_id == ldk_node_id && c.is_usable) + .any(|c| c.counterparty.node_id == ldk_node_id && c.is_usable) }, ) .await; @@ -312,7 +312,7 @@ impl DummyTrustedWallet { move || lsp_clone.list_channels(), ) .await; - if !lsp_channels.iter().any(|c| c.counterparty_node_id == ldk_node_id && c.is_usable) { + if !lsp_channels.iter().any(|c| c.counterparty.node_id == ldk_node_id && c.is_usable) { panic!( "No usable LSP-side channel found for dummy node {ldk_node_id}: {lsp_channels:?}" ); @@ -462,11 +462,7 @@ impl TrustedWalletInterface for DummyTrustedWallet { loop { if let Some(payment) = self.ldk_node.payment(&id) { let counterparty_skimmed_fee_msat = match payment.kind { - PaymentKind::Bolt11 { hash, .. } => { - debug_assert!(hash.0 == payment_hash, "Payment Hash mismatch"); - None - }, - PaymentKind::Bolt11Jit { hash, counterparty_skimmed_fee_msat, .. } => { + PaymentKind::Bolt11 { hash, counterparty_skimmed_fee_msat, .. } => { debug_assert!(hash.0 == payment_hash, "Payment Hash mismatch"); counterparty_skimmed_fee_msat }, diff --git a/orange-sdk/tests/integration_tests.rs b/orange-sdk/tests/integration_tests.rs index 6e69d34..e5138c0 100644 --- a/orange-sdk/tests/integration_tests.rs +++ b/orange-sdk/tests/integration_tests.rs @@ -1160,11 +1160,11 @@ async fn test_force_close_handling() { let channel = lsp .list_channels() .into_iter() - .find(|c| c.counterparty_node_id == wallet.node_id()) + .find(|c| c.counterparty.node_id == wallet.node_id()) .unwrap(); // force close the channel - lsp.force_close_channel(&channel.user_channel_id, channel.counterparty_node_id, None) + lsp.force_close_channel(&channel.user_channel_id, channel.counterparty.node_id, None) .unwrap(); // wait for the channel to be closed diff --git a/orange-sdk/tests/test_utils.rs b/orange-sdk/tests/test_utils.rs index 7704e00..5b299b5 100644 --- a/orange-sdk/tests/test_utils.rs +++ b/orange-sdk/tests/test_utils.rs @@ -173,7 +173,7 @@ fn create_lsp(uuid: Uuid, bitcoind: &Bitcoind) -> Arc { client_trusts_lsp: true, disable_client_reserve: false, }; - builder.set_liquidity_provider_lsps2(lsps2_service_config); + builder.enable_liquidity_provider(lsps2_service_config); let port = get_available_port().unwrap(); let addr = SocketAddress::TcpIpV4 { addr: [127, 0, 0, 1], port };