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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = { version = "0.6.0" }
lightning = { version = "0.2.0" }
lightning-invoice = { version = "0.34.0" }
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" }

[profile.release]
panic = "abort"
Expand Down
43 changes: 18 additions & 25 deletions graduated-rebalancer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

use bitcoin_payment_instructions::amount::Amount;
use bitcoin_payment_instructions::PaymentMethod;
use lightning::bitcoin::hashes::Hash;
use lightning::bitcoin::hex::DisplayHex;
use lightning::bitcoin::OutPoint;
use lightning::util::logger::Logger;
Expand Down Expand Up @@ -278,7 +277,7 @@ where
self.logger,
"Attempting to pay invoice {inv} to rebalance for {transfer_amt:?}",
);
let expected_hash = *inv.payment_hash();
let expected_hash = inv.payment_hash();
match self.trusted.pay(PaymentMethod::LightningBolt11(inv), transfer_amt).await {
Ok(rebalance_id) => {
log_debug!(
Expand All @@ -295,29 +294,23 @@ where
})
.await;

let ln_payment = match self
.ln_wallet
.await_payment_receipt(expected_hash.to_byte_array())
.await
{
Some(receipt) => receipt,
None => {
log_error!(self.logger, "Failed to receive rebalance payment!");
return;
},
};

let trusted_payment = match self
.trusted
.await_payment_success(expected_hash.to_byte_array())
.await
{
Some(success) => success,
None => {
log_error!(self.logger, "Failed to send rebalance payment!");
return;
},
};
let ln_payment =
match self.ln_wallet.await_payment_receipt(expected_hash.0).await {
Some(receipt) => receipt,
None => {
log_error!(self.logger, "Failed to receive rebalance payment!");
return;
},
};

let trusted_payment =
match self.trusted.await_payment_success(expected_hash.0).await {
Some(success) => success,
None => {
log_error!(self.logger, "Failed to send rebalance payment!");
return;
},
};

log_info!(
self.logger,
Expand Down
14 changes: 7 additions & 7 deletions orange-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2024"
authors = ["benthecarman <benthecarman@live.com>"]
documentation = "https://docs.rs/orange-sdk/"
license = "MIT OR Apache-2.0"
keywords = [ "lightning", "bitcoin", "spark", "cashu" ]
keywords = ["lightning", "bitcoin", "spark", "cashu"]
readme = "../README.md"

[lib]
Expand All @@ -23,24 +23,24 @@ _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 = { version = "0.7.0" }
ldk-node = { git = "https://github.com/lightningdevkit/ldk-node", rev = "109978de1f57fc3b3f23f241f238b97d9deaa56a" }
lightning-macros = "0.2.0"
bitcoin-payment-instructions = { workspace = true, features = ["http"] }
chrono = { version = "0.4", default-features = false }
rand = { version = "0.8.5", optional = true }
breez-sdk-spark = { git = "https://github.com/breez/spark-sdk.git", rev = "1000f276d2f16592b5f6eb8fce29228f34ff88bf", default-features = false, optional = true }
breez-sdk-spark = { git = "https://github.com/breez/spark-sdk.git", rev = "0d8db793618a4f9eaf8098d759baefe58ea2da49", default-features = false, optional = true }
tokio = { version = "1.0", default-features = false, features = ["rt-multi-thread", "sync", "macros"] }
uuid = { version = "1.0", default-features = false, optional = true }
cdk = { version = "0.15.1", default-features = false, features = ["wallet"], optional = true }
cdk = { version = "0.16.0", default-features = false, features = ["wallet"], optional = true }
serde_json = { version = "1.0", optional = true }
async-trait = "0.1"
log = "0.4.28"

corepc-node = { version = "0.10.1", features = ["29_0", "download"], optional = true }
electrsd = { version = "0.36.1", default-features = false, features = ["esplora_a33e97e1", "corepc-node_29_0"], optional = true }
cdk-ldk-node = { version = "0.15.1", optional = true }
cdk-sqlite = { version = "0.15.1", optional = true }
cdk-axum = { version = "0.15.1", optional = true }
cdk-ldk-node = { version = "0.16.0", optional = true }
cdk-sqlite = { version = "0.16.0", optional = true }
cdk-axum = { version = "0.16.0", optional = true }
axum = { version = "0.8.1", optional = true }

uniffi = { version = "0.29", default-features = false, features = ["cli"], optional = true }
Expand Down
192 changes: 192 additions & 0 deletions orange-sdk/src/dyn_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Object-safe wrapper around ldk-node's `KVStore` + `KVStoreSync` traits.
//!
//! `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.
//!
//! 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`.

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};

/// Object-safe view of a `KVStore + KVStoreSync` 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,
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, io::Error>> + Send + 'static>>;

fn write_async(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, buf: Vec<u8>,
) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + Send + 'static>>;

fn remove_async(
&self, primary_namespace: &str, secondary_namespace: &str, key: &str, lazy: bool,
) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + Send + '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,
{
fn read_async(
&self, p: &str, s: &str, k: &str,
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, io::Error>> + Send + 'static>> {
Box::pin(<T as KVStore>::read(self, p, s, k))
}

fn write_async(
&self, p: &str, s: &str, k: &str, buf: Vec<u8>,
) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + Send + 'static>> {
Box::pin(<T as KVStore>::write(self, p, s, k, buf))
}

fn remove_async(
&self, p: &str, s: &str, k: &str, lazy: bool,
) -> Pin<Box<dyn Future<Output = Result<(), io::Error>> + Send + 'static>> {
Box::pin(<T as KVStore>::remove(self, p, s, k, lazy))
}

fn list_async(
&self, p: &str, s: &str,
) -> 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.
impl KVStore for dyn DynStore {
fn read(
&self, p: &str, s: &str, k: &str,
) -> impl Future<Output = Result<Vec<u8>, io::Error>> + Send + 'static {
self.read_async(p, s, k)
}
fn write(
&self, p: &str, s: &str, k: &str, buf: Vec<u8>,
) -> impl Future<Output = Result<(), io::Error>> + Send + 'static {
self.write_async(p, s, k, buf)
}
fn remove(
&self, p: &str, s: &str, k: &str, lazy: bool,
) -> impl Future<Output = Result<(), io::Error>> + Send + 'static {
self.remove_async(p, s, k, lazy)
}
fn list(
&self, p: &str, s: &str,
) -> impl Future<Output = Result<Vec<String>, io::Error>> + Send + 'static {
self.list_async(p, s)
}
}

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`.
#[derive(Clone)]
pub(crate) struct LdkNodeStore(pub(crate) Arc<dyn DynStore>);

impl KVStore for LdkNodeStore {
fn read(
&self, p: &str, s: &str, k: &str,
) -> impl Future<Output = Result<Vec<u8>, io::Error>> + Send + 'static {
self.0.read_async(p, s, k)
}
fn write(
&self, p: &str, s: &str, k: &str, buf: Vec<u8>,
) -> impl Future<Output = Result<(), io::Error>> + Send + 'static {
self.0.write_async(p, s, k, buf)
}
fn remove(
&self, p: &str, s: &str, k: &str, lazy: bool,
) -> impl Future<Output = Result<(), io::Error>> + Send + 'static {
self.0.remove_async(p, s, k, lazy)
}
fn list(
&self, p: &str, s: &str,
) -> impl Future<Output = Result<Vec<String>, io::Error>> + Send + 'static {
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)
}
}
8 changes: 5 additions & 3 deletions orange-sdk/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::logging::Logger;
use crate::store::{self, PaymentId};

use crate::dyn_store::DynStore;
use ldk_node::bitcoin::secp256k1::PublicKey;
use ldk_node::bitcoin::{OutPoint, Txid};
use ldk_node::lightning::events::{ClosureReason, PaymentFailureReason};
Expand All @@ -11,7 +12,7 @@ use ldk_node::lightning::util::ser::{Writeable, Writer};
use ldk_node::lightning::{impl_writeable_tlv_based_enum, log_debug, log_error, log_warn};
use ldk_node::lightning_types::payment::{PaymentHash, PaymentPreimage};
use ldk_node::payment::{ConfirmationStatus, PaymentKind};
use ldk_node::{CustomTlvRecord, DynStore, UserChannelId};
use ldk_node::{CustomTlvRecord, UserChannelId};

use std::collections::VecDeque;
use std::sync::Arc;
Expand Down Expand Up @@ -207,12 +208,12 @@ impl_writeable_tlv_based_enum!(Event,
pub struct EventQueue {
queue: Arc<Mutex<VecDeque<Event>>>,
waker: Arc<Mutex<Option<Waker>>>,
kv_store: Arc<DynStore>,
kv_store: Arc<dyn DynStore>,
logger: Arc<Logger>,
}

impl EventQueue {
pub(crate) fn new(kv_store: Arc<DynStore>, logger: Arc<Logger>) -> Self {
pub(crate) fn new(kv_store: Arc<dyn DynStore>, logger: Arc<Logger>) -> Self {
let queue = Arc::new(Mutex::new(VecDeque::new()));
let waker = Arc::new(Mutex::new(None));
Self { queue, waker, kv_store, logger }
Expand Down Expand Up @@ -335,6 +336,7 @@ impl LdkEventHandler {
payment_hash,
payment_preimage,
fee_paid_msat,
bolt12_invoice: _,
} => {
let preimage = payment_preimage.unwrap(); // safe
let payment_id = PaymentId::SelfCustodial(payment_id.unwrap().0); // safe
Expand Down
9 changes: 6 additions & 3 deletions orange-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ use ldk_node::lightning::util::logger::Logger as _;
use ldk_node::lightning::{log_debug, log_error, log_info, log_trace, log_warn};
use ldk_node::lightning_invoice::Bolt11Invoice;
use ldk_node::payment::{PaymentDirection, PaymentKind};
use ldk_node::{BuildError, ChannelDetails, DynStore, NodeError};
use ldk_node::{BuildError, ChannelDetails, NodeError};

use crate::dyn_store::DynStore;

use std::collections::HashMap;
use std::fmt::{self, Debug, Write};
use std::sync::Arc;
use std::time::{Duration, SystemTime};

mod dyn_store;
mod event;
#[cfg(feature = "uniffi")]
mod ffi;
Expand Down Expand Up @@ -112,7 +115,7 @@ struct WalletImpl {
/// Metadata store for tracking transactions.
tx_metadata: TxMetadataStore,
/// Key-value store for persistent storage.
store: Arc<DynStore>,
store: Arc<dyn DynStore>,
/// Logger for logging wallet operations.
logger: Arc<Logger>,
/// The Tokio runtime for asynchronous operations.
Expand Down Expand Up @@ -519,7 +522,7 @@ impl Wallet {
BuildError::RuntimeSetupFailed
})?);

let store: Arc<DynStore> = match &config.storage_config {
let store: Arc<dyn DynStore> = match &config.storage_config {
StorageConfig::LocalSQLite(path) => {
Arc::new(SqliteStore::new(path.into(), Some("orange.sqlite".to_owned()), None)?)
},
Expand Down
Loading
Loading