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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion orange-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
103 changes: 16 additions & 87 deletions orange-sdk/src/dyn_store.rs
Original file line number Diff line number Diff line change
@@ -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<dyn KVStore>`. 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<dyn KVStore>`.
//!
//! 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<dyn DynStore>`; 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<dyn DynStore>`; 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,
Expand All @@ -37,27 +33,11 @@ pub(crate) trait DynStore: Send + Sync + 'static {
fn list_async(
&self, primary_namespace: &str, secondary_namespace: &str,
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, io::Error>> + Send + 'static>>;

fn read_sync(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str,
) -> Result<Vec<u8>, io::Error>;

fn write_sync(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec<u8>,
) -> 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<Vec<String>, io::Error>;
}

impl<T> 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,
Expand All @@ -82,33 +62,12 @@ where
) -> Pin<Box<dyn Future<Output = Result<Vec<String>, io::Error>> + Send + 'static>> {
Box::pin(<T as KVStore>::list(self, p, s))
}

fn read_sync(&self, p: &str, s: &str, k: &str) -> Result<Vec<u8>, io::Error> {
<T as KVStoreSync>::read(self, p, s, k)
}

fn write_sync(&self, p: &str, s: &str, k: &str, buf: Vec<u8>) -> Result<(), io::Error> {
<T as KVStoreSync>::write(self, p, s, k, buf)
}

fn remove_sync(&self, p: &str, s: &str, k: &str, lazy: bool) -> Result<(), io::Error> {
<T as KVStoreSync>::remove(self, p, s, k, lazy)
}

fn list_sync(&self, p: &str, s: &str) -> Result<Vec<String>, io::Error> {
<T as KVStoreSync>::list(self, p, s)
}
}

// Make `Arc<dyn DynStore>` 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<dyn DynStore>: 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<dyn DynStore>: KVStore` too.
impl KVStore for dyn DynStore {
fn read(
&self, p: &str, s: &str, k: &str,
Expand All @@ -132,24 +91,9 @@ impl KVStore for dyn DynStore {
}
}

impl KVStoreSync for dyn DynStore {
fn read(&self, p: &str, s: &str, k: &str) -> Result<Vec<u8>, io::Error> {
self.read_sync(p, s, k)
}
fn write(&self, p: &str, s: &str, k: &str, buf: Vec<u8>) -> 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<Vec<String>, io::Error> {
self.list_sync(p, s)
}
}

/// Cloneable handle wrapping `Arc<dyn DynStore>` 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<dyn DynStore>);

Expand All @@ -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<Vec<u8>, io::Error> {
self.0.read_sync(p, s, k)
}
fn write(&self, p: &str, s: &str, k: &str, buf: Vec<u8>) -> 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<Vec<String>, io::Error> {
self.0.list_sync(p, s)
}
}
10 changes: 5 additions & 5 deletions orange-sdk/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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:?}");
},
}

Expand Down
2 changes: 1 addition & 1 deletion orange-sdk/src/ffi/ldk_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 9 additions & 4 deletions orange-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
},
Expand Down Expand Up @@ -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?
Expand Down
27 changes: 10 additions & 17 deletions orange-sdk/src/lightning_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 => {
Expand All @@ -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,
)?;
Expand Down Expand Up @@ -376,13 +376,13 @@ impl LightningWallet {
pub(crate) async fn splice_all_into_channel(&self) -> Result<UserChannelId, NodeError> {
// 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 => {
Expand All @@ -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,
)?;
}
Expand Down Expand Up @@ -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
},
Expand All @@ -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(
Expand Down Expand Up @@ -550,10 +546,7 @@ impl From<PaymentStatus> 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());
}
Expand Down
14 changes: 5 additions & 9 deletions orange-sdk/src/trusted_wallet/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
Expand All @@ -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:?}"
);
Expand Down Expand Up @@ -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
},
Expand Down
4 changes: 2 additions & 2 deletions orange-sdk/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading