Store last pay index (#1077)

This commit is contained in:
thesimplekid
2025-09-16 14:08:43 +01:00
committed by GitHub
parent 2dbb418db7
commit 5ee405de89
19 changed files with 328 additions and 128 deletions

View File

@@ -32,6 +32,9 @@ pub enum Error {
/// Bolt12 Error
#[error("Bolt12 error: {0}")]
Bolt12(String),
/// Database Error
#[error("Database error: {0}")]
Database(String),
}
impl From<Error> for cdk_common::payment::Error {

View File

@@ -16,6 +16,7 @@ use async_trait::async_trait;
use bitcoin::hashes::sha256::Hash;
use cdk_common::amount::{to_unit, Amount};
use cdk_common::common::FeeReserve;
use cdk_common::database::mint::DynMintKVStore;
use cdk_common::nuts::{CurrencyUnit, MeltOptions, MeltQuoteState};
use cdk_common::payment::{
self, Bolt11IncomingPaymentOptions, Bolt11Settings, Bolt12IncomingPaymentOptions,
@@ -43,6 +44,11 @@ use uuid::Uuid;
pub mod error;
// KV Store constants for CLN
const CLN_KV_PRIMARY_NAMESPACE: &str = "cdk_cln_lightning_backend";
const CLN_KV_SECONDARY_NAMESPACE: &str = "payment_indices";
const LAST_PAY_INDEX_KV_KEY: &str = "last_pay_index";
/// CLN mint backend
#[derive(Clone)]
pub struct Cln {
@@ -50,16 +56,22 @@ pub struct Cln {
fee_reserve: FeeReserve,
wait_invoice_cancel_token: CancellationToken,
wait_invoice_is_active: Arc<AtomicBool>,
kv_store: DynMintKVStore,
}
impl Cln {
/// Create new [`Cln`]
pub async fn new(rpc_socket: PathBuf, fee_reserve: FeeReserve) -> Result<Self, Error> {
pub async fn new(
rpc_socket: PathBuf,
fee_reserve: FeeReserve,
kv_store: DynMintKVStore,
) -> Result<Self, Error> {
Ok(Self {
rpc_socket,
fee_reserve,
wait_invoice_cancel_token: CancellationToken::new(),
wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
kv_store,
})
}
}
@@ -114,14 +126,16 @@ impl MintPayment for Cln {
};
tracing::debug!("CLN: Creating stream processing pipeline");
let kv_store = self.kv_store.clone();
let stream = futures::stream::unfold(
(
cln_client,
last_pay_index,
self.wait_invoice_cancel_token.clone(),
Arc::clone(&self.wait_invoice_is_active),
kv_store,
),
|(mut cln_client, mut last_pay_idx, cancel_token, is_active)| async move {
|(mut cln_client, mut last_pay_idx, cancel_token, is_active, kv_store)| async move {
// Set the stream as active
is_active.store(true, Ordering::SeqCst);
tracing::debug!("CLN: Stream is now active, waiting for invoice events with lastpay_index: {:?}", last_pay_idx);
@@ -179,6 +193,23 @@ impl MintPayment for Cln {
last_pay_idx = wait_any_response.pay_index;
tracing::debug!("CLN: Updated last_pay_idx to {:?}", last_pay_idx);
// Store the updated pay index in KV store for persistence
if let Some(pay_index) = last_pay_idx {
let index_str = pay_index.to_string();
if let Ok(mut tx) = kv_store.begin_transaction().await {
if let Err(e) = tx.kv_write(CLN_KV_PRIMARY_NAMESPACE, CLN_KV_SECONDARY_NAMESPACE, LAST_PAY_INDEX_KV_KEY, index_str.as_bytes()).await {
tracing::warn!("CLN: Failed to write last pay index {} to KV store: {}", pay_index, e);
} else if let Err(e) = tx.commit().await {
tracing::warn!("CLN: Failed to commit last pay index {} to KV store: {}", pay_index, e);
} else {
tracing::debug!("CLN: Stored last pay index {} in KV store", pay_index);
}
} else {
tracing::warn!("CLN: Failed to begin KV transaction for storing pay index {}", pay_index);
}
}
let payment_hash = wait_any_response.payment_hash;
tracing::debug!("CLN: Payment hash: {}", payment_hash);
@@ -245,7 +276,7 @@ impl MintPayment for Cln {
tracing::info!("CLN: Created WaitPaymentResponse with amount {} msats", amount_msats.msat());
let event = Event::PaymentReceived(response);
break Some((event, (cln_client, last_pay_idx, cancel_token, is_active)));
break Some((event, (cln_client, last_pay_idx, cancel_token, is_active, kv_store)));
}
Err(e) => {
tracing::warn!("CLN: Error fetching invoice: {e}");
@@ -733,6 +764,27 @@ impl Cln {
/// Get last pay index for cln
async fn get_last_pay_index(&self) -> Result<Option<u64>, Error> {
// First try to read from KV store
if let Some(stored_index) = self
.kv_store
.kv_read(
CLN_KV_PRIMARY_NAMESPACE,
CLN_KV_SECONDARY_NAMESPACE,
LAST_PAY_INDEX_KV_KEY,
)
.await
.map_err(|e| Error::Database(e.to_string()))?
{
if let Ok(index_str) = std::str::from_utf8(&stored_index) {
if let Ok(index) = index_str.parse::<u64>() {
tracing::debug!("CLN: Retrieved last pay index {} from KV store", index);
return Ok(Some(index));
}
}
}
// Fall back to querying CLN directly
tracing::debug!("CLN: No stored last pay index found in KV store, querying CLN directly");
let mut cln_client = self.cln_client().await?;
let listinvoices_response = cln_client
.call_typed(&ListinvoicesRequest {
@@ -753,7 +805,7 @@ impl Cln {
}
}
/// Decode string
/// Decode string
#[instrument(skip(self))]
async fn decode_string(&self, string: String) -> Result<DecodeResponse, Error> {
let mut cln_client = self.cln_client().await?;

View File

@@ -88,3 +88,7 @@ pub trait MintAuthDatabase {
&self,
) -> Result<HashMap<ProtectedEndpoint, Option<AuthRequired>>, Self::Err>;
}
/// Type alias for trait objects
pub type DynMintAuthDatabase =
std::sync::Arc<dyn MintAuthDatabase<Err = super::Error> + Send + Sync>;

View File

@@ -22,7 +22,7 @@ mod auth;
pub mod test;
#[cfg(feature = "auth")]
pub use auth::{MintAuthDatabase, MintAuthTransaction};
pub use auth::{DynMintAuthDatabase, MintAuthDatabase, MintAuthTransaction};
/// Valid ASCII characters for namespace and key strings in KV store
pub const KVSTORE_NAMESPACE_KEY_ALPHABET: &str =
@@ -442,6 +442,9 @@ pub trait KVStore: KVStoreDatabase {
) -> Result<Box<dyn KVStoreTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
}
/// Type alias for Mint Kv store
pub type DynMintKVStore = std::sync::Arc<dyn KVStore<Err = Error> + Send + Sync>;
/// Mint Database trait
#[async_trait]
pub trait Database<Error>:
@@ -461,3 +464,6 @@ pub trait Database<Error>:
/// Get [`QuoteTTL`]
async fn get_quote_ttl(&self) -> Result<QuoteTTL, Error>;
}
/// Type alias for Mint Database
pub type DynMintDatabase = std::sync::Arc<dyn Database<Error> + Send + Sync>;

View File

@@ -7,7 +7,7 @@ mod wallet;
#[cfg(feature = "mint")]
pub use mint::{
Database as MintDatabase, DbTransactionFinalizer as MintDbWriterFinalizer,
Database as MintDatabase, DbTransactionFinalizer as MintDbWriterFinalizer, DynMintDatabase,
KVStore as MintKVStore, KVStoreDatabase as MintKVStoreDatabase,
KVStoreTransaction as MintKVStoreTransaction, KeysDatabase as MintKeysDatabase,
KeysDatabaseTransaction as MintKeyDatabaseTransaction, ProofsDatabase as MintProofsDatabase,
@@ -16,7 +16,7 @@ pub use mint::{
SignaturesTransaction as MintSignatureTransaction, Transaction as MintTransaction,
};
#[cfg(all(feature = "mint", feature = "auth"))]
pub use mint::{MintAuthDatabase, MintAuthTransaction};
pub use mint::{DynMintAuthDatabase, MintAuthDatabase, MintAuthTransaction};
#[cfg(feature = "wallet")]
pub use wallet::Database as WalletDatabase;

View File

@@ -327,6 +327,19 @@ pub enum Event {
PaymentReceived(WaitPaymentResponse),
}
impl Default for Event {
fn default() -> Self {
// We use this as a sentinel value for no-op events
// The actual processing will filter these out
Event::PaymentReceived(WaitPaymentResponse {
payment_identifier: PaymentIdentifier::CustomId("default".to_string()),
payment_amount: Amount::from(0),
unit: CurrencyUnit::Msat,
payment_id: "default".to_string(),
})
}
}
/// Wait any invoice response
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
pub struct WaitPaymentResponse {
@@ -599,3 +612,6 @@ where
result
}
}
/// Type alias for Mint Payment trait
pub type DynMintPayment = std::sync::Arc<dyn MintPayment<Err = Error> + Send + Sync>;

View File

@@ -6,7 +6,9 @@ use std::sync::Arc;
use anyhow::Result;
use cdk::types::FeeReserve;
use cdk_cln::Cln as CdkCln;
use cdk_common::database::mint::DynMintKVStore;
use cdk_lnd::Lnd as CdkLnd;
use cdk_sqlite::mint::memory;
use ldk_node::lightning::ln::msgs::SocketAddress;
use ldk_node::Node;
use ln_regtest_rs::bitcoin_client::BitcoinClient;
@@ -164,7 +166,8 @@ pub async fn create_cln_backend(cln_client: &ClnClient) -> Result<CdkCln> {
percent_fee_reserve: 1.0,
};
Ok(CdkCln::new(rpc_path, fee_reserve).await?)
let kv_store: DynMintKVStore = Arc::new(memory::empty().await?);
Ok(CdkCln::new(rpc_path, fee_reserve, kv_store).await?)
}
pub async fn create_lnd_backend(lnd_client: &LndClient) -> Result<CdkLnd> {
@@ -173,11 +176,14 @@ pub async fn create_lnd_backend(lnd_client: &LndClient) -> Result<CdkLnd> {
percent_fee_reserve: 1.0,
};
let kv_store: DynMintKVStore = Arc::new(memory::empty().await?);
Ok(CdkLnd::new(
lnd_client.address.clone(),
lnd_client.cert_file.clone(),
lnd_client.macaroon_file.clone(),
fee_reserve,
kv_store,
)
.await?)
}

View File

@@ -39,6 +39,9 @@ pub enum Error {
/// Could not read file
#[error("Could not read file")]
ReadFile,
/// Database Error
#[error("Database error: {0}")]
Database(String),
}
impl From<Error> for cdk_common::payment::Error {

View File

@@ -18,6 +18,7 @@ use async_trait::async_trait;
use cdk_common::amount::{to_unit, Amount, MSAT_IN_SAT};
use cdk_common::bitcoin::hashes::Hash;
use cdk_common::common::FeeReserve;
use cdk_common::database::mint::DynMintKVStore;
use cdk_common::nuts::{CurrencyUnit, MeltOptions, MeltQuoteState};
use cdk_common::payment::{
self, Bolt11Settings, CreateIncomingPaymentResponse, Event, IncomingPaymentOptions,
@@ -42,6 +43,12 @@ pub(crate) use proto::{lnrpc, routerrpc};
use crate::lnrpc::invoice::InvoiceState;
/// LND KV Store constants
const LND_KV_PRIMARY_NAMESPACE: &str = "cdk_lnd_lightning_backend";
const LND_KV_SECONDARY_NAMESPACE: &str = "payment_indices";
const LAST_ADD_INDEX_KV_KEY: &str = "last_add_index";
const LAST_SETTLE_INDEX_KV_KEY: &str = "last_settle_index";
/// Lnd mint backend
#[derive(Clone)]
pub struct Lnd {
@@ -50,6 +57,7 @@ pub struct Lnd {
_macaroon_file: PathBuf,
lnd_client: client::Client,
fee_reserve: FeeReserve,
kv_store: DynMintKVStore,
wait_invoice_cancel_token: CancellationToken,
wait_invoice_is_active: Arc<AtomicBool>,
settings: Bolt11Settings,
@@ -65,6 +73,7 @@ impl Lnd {
cert_file: PathBuf,
macaroon_file: PathBuf,
fee_reserve: FeeReserve,
kv_store: DynMintKVStore,
) -> Result<Self, Error> {
// Validate address is not empty
if address.is_empty() {
@@ -104,6 +113,7 @@ impl Lnd {
_macaroon_file: macaroon_file,
lnd_client,
fee_reserve,
kv_store,
wait_invoice_cancel_token: CancellationToken::new(),
wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
settings: Bolt11Settings {
@@ -115,6 +125,55 @@ impl Lnd {
},
})
}
/// Get last add and settle indices from KV store
#[instrument(skip_all)]
async fn get_last_indices(&self) -> Result<(Option<u64>, Option<u64>), Error> {
let add_index = if let Some(stored_index) = self
.kv_store
.kv_read(
LND_KV_PRIMARY_NAMESPACE,
LND_KV_SECONDARY_NAMESPACE,
LAST_ADD_INDEX_KV_KEY,
)
.await
.map_err(|e| Error::Database(e.to_string()))?
{
if let Ok(index_str) = std::str::from_utf8(stored_index.as_slice()) {
index_str.parse::<u64>().ok()
} else {
None
}
} else {
None
};
let settle_index = if let Some(stored_index) = self
.kv_store
.kv_read(
LND_KV_PRIMARY_NAMESPACE,
LND_KV_SECONDARY_NAMESPACE,
LAST_SETTLE_INDEX_KV_KEY,
)
.await
.map_err(|e| Error::Database(e.to_string()))?
{
if let Ok(index_str) = std::str::from_utf8(stored_index.as_slice()) {
index_str.parse::<u64>().ok()
} else {
None
}
} else {
None
};
tracing::debug!(
"LND: Retrieved last indices from KV store - add_index: {:?}, settle_index: {:?}",
add_index,
settle_index
);
Ok((add_index, settle_index))
}
}
#[async_trait]
@@ -142,11 +201,21 @@ impl MintPayment for Lnd {
) -> Result<Pin<Box<dyn Stream<Item = Event> + Send>>, Self::Err> {
let mut lnd_client = self.lnd_client.clone();
// Get last indices from KV store
let (last_add_index, last_settle_index) =
self.get_last_indices().await.unwrap_or((None, None));
let stream_req = lnrpc::InvoiceSubscription {
add_index: 0,
settle_index: 0,
add_index: last_add_index.unwrap_or(0),
settle_index: last_settle_index.unwrap_or(0),
};
tracing::debug!(
"LND: Starting invoice subscription with add_index: {}, settle_index: {}",
stream_req.add_index,
stream_req.settle_index
);
let stream = lnd_client
.lightning()
.subscribe_invoices(stream_req)
@@ -158,68 +227,119 @@ impl MintPayment for Lnd {
.into_inner();
let cancel_token = self.wait_invoice_cancel_token.clone();
let kv_store = self.kv_store.clone();
Ok(futures::stream::unfold(
let event_stream = futures::stream::unfold(
(
stream,
cancel_token,
Arc::clone(&self.wait_invoice_is_active),
kv_store,
last_add_index.unwrap_or(0),
last_settle_index.unwrap_or(0),
),
|(mut stream, cancel_token, is_active)| async move {
|(
mut stream,
cancel_token,
is_active,
kv_store,
mut current_add_index,
mut current_settle_index,
)| async move {
is_active.store(true, Ordering::SeqCst);
tokio::select! {
_ = cancel_token.cancelled() => {
// Stream is cancelled
is_active.store(false, Ordering::SeqCst);
tracing::info!("Waiting for lnd invoice ending");
None
loop {
tokio::select! {
_ = cancel_token.cancelled() => {
// Stream is cancelled
is_active.store(false, Ordering::SeqCst);
tracing::info!("Waiting for lnd invoice ending");
return None;
}
msg = stream.message() => {
match msg {
Ok(Some(msg)) => {
// Update indices based on the message
current_add_index = current_add_index.max(msg.add_index);
current_settle_index = current_settle_index.max(msg.settle_index);
}
msg = stream.message() => {
// Store the updated indices in KV store regardless of settlement status
let add_index_str = current_add_index.to_string();
let settle_index_str = current_settle_index.to_string();
match msg {
Ok(Some(msg)) => {
if msg.state() == InvoiceState::Settled {
if let Ok(mut tx) = kv_store.begin_transaction().await {
let mut has_error = false;
let hash_slice: Result<[u8;32], _> = msg.r_hash.try_into();
if let Err(e) = tx.kv_write(LND_KV_PRIMARY_NAMESPACE, LND_KV_SECONDARY_NAMESPACE, LAST_ADD_INDEX_KV_KEY, add_index_str.as_bytes()).await {
tracing::warn!("LND: Failed to write add_index {} to KV store: {}", current_add_index, e);
has_error = true;
}
if let Ok(hash_slice) = hash_slice {
let hash = hex::encode(hash_slice);
if let Err(e) = tx.kv_write(LND_KV_PRIMARY_NAMESPACE, LND_KV_SECONDARY_NAMESPACE, LAST_SETTLE_INDEX_KV_KEY, settle_index_str.as_bytes()).await {
tracing::warn!("LND: Failed to write settle_index {} to KV store: {}", current_settle_index, e);
has_error = true;
}
if !has_error {
if let Err(e) = tx.commit().await {
tracing::warn!("LND: Failed to commit indices to KV store: {}", e);
} else {
tracing::debug!("LND: Stored updated indices - add_index: {}, settle_index: {}", current_add_index, current_settle_index);
}
}
} else {
tracing::warn!("LND: Failed to begin KV transaction for storing indices");
}
// Only emit event for settled invoices
if msg.state() == InvoiceState::Settled {
let hash_slice: Result<[u8;32], _> = msg.r_hash.try_into();
if let Ok(hash_slice) = hash_slice {
let hash = hex::encode(hash_slice);
tracing::info!("LND: Payment for {} with amount {} msat", hash, msg.amt_paid_msat);
tracing::info!("LND: Processing payment with hash: {}", hash);
let wait_response = WaitPaymentResponse {
payment_identifier: PaymentIdentifier::PaymentHash(hash_slice), payment_amount: Amount::from(msg.amt_paid_msat as u64),
payment_identifier: PaymentIdentifier::PaymentHash(hash_slice),
payment_amount: Amount::from(msg.amt_paid_msat as u64),
unit: CurrencyUnit::Msat,
payment_id: hash,
};
tracing::info!("LND: Created WaitPaymentResponse with amount {} msat",
msg.amt_paid_msat);
let event = Event::PaymentReceived(wait_response);
Some((event, (stream, cancel_token, is_active)))
} else { None }
} else {
None
return Some((event, (stream, cancel_token, is_active, kv_store, current_add_index, current_settle_index)));
} else {
// Invalid hash, skip this message but continue streaming
tracing::error!("LND returned invalid payment hash");
// Continue the loop without yielding
continue;
}
} else {
// Not a settled invoice, continue but don't emit event
tracing::debug!("LND: Received non-settled invoice, continuing to wait for settled invoices");
// Continue the loop without yielding
continue;
}
}
Ok(None) => {
is_active.store(false, Ordering::SeqCst);
tracing::info!("LND invoice stream ended.");
return None;
}
Err(err) => {
is_active.store(false, Ordering::SeqCst);
tracing::warn!("Encountered error in LND invoice stream. Stream ending");
tracing::error!("{:?}", err);
return None;
}
}
}
}
Ok(None) => {
is_active.store(false, Ordering::SeqCst);
tracing::info!("LND invoice stream ended.");
None
}, // End of stream
Err(err) => {
is_active.store(false, Ordering::SeqCst);
tracing::warn!("Encountered error in LND invoice stream. Stream ending");
tracing::error!("{:?}", err);
None
}, // Handle errors gracefully, ends the stream on error
}
}
}
},
)
.boxed())
);
Ok(Box::pin(event_stream))
}
#[instrument(skip_all)]

View File

@@ -38,6 +38,7 @@ use cdk::nuts::{AuthRequired, Method, ProtectedEndpoint, RoutePath};
use cdk::nuts::{ContactInfo, MintVersion, PaymentMethod};
use cdk::types::QuoteTTL;
use cdk_axum::cache::HttpCache;
use cdk_common::database::DynMintDatabase;
// internal crate modules
#[cfg(feature = "prometheus")]
use cdk_common::payment::MetricsMintPayment;
@@ -97,7 +98,7 @@ async fn initial_setup(
settings: &config::Settings,
db_password: Option<String>,
) -> Result<(
Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
DynMintDatabase,
Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
Arc<dyn MintKVStore<Err = cdk_database::Error> + Send + Sync>,
)> {
@@ -257,7 +258,7 @@ async fn setup_database(
_work_dir: &Path,
_db_password: Option<String>,
) -> Result<(
Arc<dyn MintDatabase<cdk_database::Error> + Send + Sync>,
DynMintDatabase,
Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
Arc<dyn MintKVStore<Err = cdk_database::Error> + Send + Sync>,
)> {
@@ -426,7 +427,7 @@ async fn configure_lightning_backend(
.clone()
.expect("Config checked at load that cln is some");
let cln = cln_settings
.setup(settings, CurrencyUnit::Msat, None, work_dir, None)
.setup(settings, CurrencyUnit::Msat, None, work_dir, _kv_store)
.await?;
#[cfg(feature = "prometheus")]
let cln = MetricsMintPayment::new(cln);
@@ -462,7 +463,7 @@ async fn configure_lightning_backend(
LnBackend::Lnd => {
let lnd_settings = settings.clone().lnd.expect("Checked at config load");
let lnd = lnd_settings
.setup(settings, CurrencyUnit::Msat, None, work_dir, None)
.setup(settings, CurrencyUnit::Msat, None, work_dir, _kv_store)
.await?;
#[cfg(feature = "prometheus")]
let lnd = MetricsMintPayment::new(lnd);
@@ -634,10 +635,10 @@ async fn setup_authentication(
_password: Option<String>,
) -> Result<MintBuilder> {
if let Some(auth_settings) = settings.auth.clone() {
use cdk_common::database::DynMintAuthDatabase;
tracing::info!("Auth settings are defined. {:?}", auth_settings);
let auth_localstore: Arc<
dyn cdk_database::MintAuthDatabase<Err = cdk_database::Error> + Send + Sync,
> = match settings.database.engine {
let auth_localstore: DynMintAuthDatabase = match settings.database.engine {
#[cfg(feature = "sqlite")]
DatabaseEngine::Sqlite => {
#[cfg(feature = "sqlite")]

View File

@@ -47,7 +47,7 @@ impl LnBackendSetup for config::Cln {
_unit: CurrencyUnit,
_runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
_work_dir: &Path,
_kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
) -> anyhow::Result<cdk_cln::Cln> {
let cln_socket = expand_path(
self.rpc_path
@@ -61,7 +61,12 @@ impl LnBackendSetup for config::Cln {
percent_fee_reserve: self.fee_percent,
};
let cln = cdk_cln::Cln::new(cln_socket, fee_reserve).await?;
let cln = cdk_cln::Cln::new(
cln_socket,
fee_reserve,
kv_store.expect("Cln needs kv store"),
)
.await?;
Ok(cln)
}
@@ -110,7 +115,7 @@ impl LnBackendSetup for config::Lnd {
_unit: CurrencyUnit,
_runtime: Option<std::sync::Arc<tokio::runtime::Runtime>>,
_work_dir: &Path,
_kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
kv_store: Option<Arc<dyn MintKVStore<Err = cdk::cdk_database::Error> + Send + Sync>>,
) -> anyhow::Result<cdk_lnd::Lnd> {
let address = &self.address;
let cert_file = &self.cert_file;
@@ -126,6 +131,7 @@ impl LnBackendSetup for config::Lnd {
cert_file.clone(),
macaroon_file.clone(),
fee_reserve,
kv_store.expect("Lnd needs kv store"),
)
.await?;

View File

@@ -17,7 +17,7 @@ path = "src/bin/payment_processor.rs"
[features]
default = ["cln", "fake", "lnd"]
bench = []
cln = ["dep:cdk-cln"]
cln = ["dep:cdk-cln", "dep:cdk-sqlite"]
fake = ["dep:cdk-fake-wallet"]
lnd = ["dep:cdk-lnd"]
@@ -30,6 +30,7 @@ cdk-common = { workspace = true, features = ["mint"] }
cdk-cln = { workspace = true, optional = true }
cdk-lnd = { workspace = true, optional = true }
cdk-fake-wallet = { workspace = true, optional = true }
cdk-sqlite = { workspace = true, optional = true }
clap = { workspace = true, features = ["derive"] }
serde.workspace = true
thiserror.workspace = true

View File

@@ -14,6 +14,8 @@ use cdk_common::payment::{self, MintPayment};
use cdk_common::Amount;
#[cfg(feature = "fake")]
use cdk_fake_wallet::FakeWallet;
#[cfg(feature = "cln")]
use cdk_sqlite::MintSqliteDatabase;
use clap::Parser;
use serde::{Deserialize, Serialize};
#[cfg(any(feature = "cln", feature = "lnd", feature = "fake"))]
@@ -106,7 +108,8 @@ async fn main() -> anyhow::Result<()> {
percent_fee_reserve: cln_settings.fee_percent,
};
Arc::new(cdk_cln::Cln::new(cln_settings.rpc_path, fee_reserve).await?)
let kv_store = Arc::new(MintSqliteDatabase::new(":memory:").await?);
Arc::new(cdk_cln::Cln::new(cln_settings.rpc_path, fee_reserve, kv_store).await?)
}
#[cfg(feature = "fake")]
"FAKEWALLET" => {
@@ -136,12 +139,14 @@ async fn main() -> anyhow::Result<()> {
percent_fee_reserve: lnd_settings.fee_percent,
};
let kv_store = Arc::new(MintSqliteDatabase::new(":memory:").await?);
Arc::new(
cdk_lnd::Lnd::new(
lnd_settings.address,
lnd_settings.cert_file,
lnd_settings.macaroon_file,
fee_reserve,
kv_store,
)
.await?,
)

View File

@@ -4,23 +4,20 @@ use std::collections::HashMap;
use std::sync::Arc;
use bitcoin::bip32::DerivationPath;
use cdk_common::database::{self, MintDatabase, MintKeysDatabase};
use cdk_common::database::{DynMintDatabase, MintKeysDatabase};
use cdk_common::error::Error;
use cdk_common::nut04::MintMethodOptions;
use cdk_common::nut05::MeltMethodOptions;
use cdk_common::payment::Bolt11Settings;
use cdk_common::payment::{Bolt11Settings, DynMintPayment};
#[cfg(feature = "auth")]
use cdk_common::{nut21, nut22};
use cdk_common::{database::DynMintAuthDatabase, nut21, nut22};
use cdk_signatory::signatory::Signatory;
use super::nut17::SupportedMethods;
use super::nut19::{self, CachedEndpoint};
#[cfg(feature = "auth")]
use super::MintAuthDatabase;
use super::Nuts;
use crate::amount::Amount;
use crate::cdk_database;
use crate::cdk_payment::{self, MintPayment};
use crate::mint::Mint;
#[cfg(feature = "auth")]
use crate::nuts::ProtectedEndpoint;
@@ -33,18 +30,17 @@ use crate::types::PaymentProcessorKey;
/// Cashu Mint Builder
pub struct MintBuilder {
mint_info: MintInfo,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
localstore: DynMintDatabase,
#[cfg(feature = "auth")]
auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>>,
payment_processors:
HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>,
auth_localstore: Option<DynMintAuthDatabase>,
payment_processors: HashMap<PaymentProcessorKey, DynMintPayment>,
supported_units: HashMap<CurrencyUnit, (u64, u8)>,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
}
impl MintBuilder {
/// New [`MintBuilder`]
pub fn new(localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>) -> MintBuilder {
pub fn new(localstore: DynMintDatabase) -> MintBuilder {
let mint_info = MintInfo {
nuts: Nuts::new()
.nut07(true)
@@ -72,7 +68,7 @@ impl MintBuilder {
#[cfg(feature = "auth")]
pub fn with_auth(
mut self,
auth_localstore: Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>,
auth_localstore: DynMintAuthDatabase,
openid_discovery: String,
client_id: String,
protected_endpoints: Vec<ProtectedEndpoint>,
@@ -211,7 +207,7 @@ impl MintBuilder {
unit: CurrencyUnit,
method: PaymentMethod,
limits: MintMeltLimits,
payment_processor: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
payment_processor: DynMintPayment,
) -> Result<(), Error> {
let key = PaymentProcessorKey {
unit: unit.clone(),

View File

@@ -8,8 +8,8 @@ use cdk_common::melt::MeltQuoteRequest;
use cdk_common::mint::MeltPaymentRequest;
use cdk_common::nut05::MeltMethodOptions;
use cdk_common::payment::{
Bolt11OutgoingPaymentOptions, Bolt12OutgoingPaymentOptions, OutgoingPaymentOptions,
PaymentIdentifier,
Bolt11OutgoingPaymentOptions, Bolt12OutgoingPaymentOptions, DynMintPayment,
OutgoingPaymentOptions, PaymentIdentifier,
};
use cdk_common::quote_id::QuoteId;
use cdk_common::{MeltOptions, MeltQuoteBolt12Request};
@@ -23,7 +23,7 @@ use super::{
PaymentMethod, PublicKey, State,
};
use crate::amount::to_unit;
use crate::cdk_payment::{MakePaymentResponse, MintPayment};
use crate::cdk_payment::MakePaymentResponse;
use crate::mint::proof_writer::ProofWriter;
use crate::mint::verification::Verification;
use crate::mint::SigFlag;
@@ -586,7 +586,7 @@ impl Mint {
use std::sync::Arc;
async fn check_payment_state(
ln: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
ln: DynMintPayment,
lookup_id: &PaymentIdentifier,
) -> anyhow::Result<MakePaymentResponse> {
match ln.check_outgoing_payment(lookup_id).await {

View File

@@ -8,10 +8,10 @@ use arc_swap::ArcSwap;
use cdk_common::amount::to_unit;
use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
#[cfg(feature = "auth")]
use cdk_common::database::MintAuthDatabase;
use cdk_common::database::{self, MintDatabase, MintTransaction};
use cdk_common::database::DynMintAuthDatabase;
use cdk_common::database::{self, DynMintDatabase, MintTransaction};
use cdk_common::nuts::{self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind};
use cdk_common::payment::WaitPaymentResponse;
use cdk_common::payment::{DynMintPayment, WaitPaymentResponse};
pub use cdk_common::quote_id::QuoteId;
use cdk_common::secret;
#[cfg(feature = "prometheus")]
@@ -25,7 +25,6 @@ use tokio::sync::{Mutex, Notify};
use tokio::task::{JoinHandle, JoinSet};
use tracing::instrument;
use crate::cdk_payment::{self, MintPayment};
use crate::error::Error;
use crate::fees::calculate_fee;
use crate::nuts::*;
@@ -60,13 +59,12 @@ pub struct Mint {
/// be a gRPC client to a remote signatory server.
signatory: Arc<dyn Signatory + Send + Sync>,
/// Mint Storage backend
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
localstore: DynMintDatabase,
/// Auth Storage backend (only available with auth feature)
#[cfg(feature = "auth")]
auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>>,
auth_localstore: Option<DynMintAuthDatabase>,
/// Payment processors for mint
payment_processors:
HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>,
payment_processors: HashMap<PaymentProcessorKey, DynMintPayment>,
/// Subscription manager
pubsub_manager: Arc<PubSubManager>,
#[cfg(feature = "auth")]
@@ -91,11 +89,8 @@ impl Mint {
pub async fn new(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
payment_processors: HashMap<
PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>,
localstore: DynMintDatabase,
payment_processors: HashMap<PaymentProcessorKey, DynMintPayment>,
) -> Result<Self, Error> {
Self::new_internal(
mint_info,
@@ -113,12 +108,9 @@ impl Mint {
pub async fn new_with_auth(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
auth_localstore: Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>,
payment_processors: HashMap<
PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>,
localstore: DynMintDatabase,
auth_localstore: DynMintAuthDatabase,
payment_processors: HashMap<PaymentProcessorKey, DynMintPayment>,
) -> Result<Self, Error> {
Self::new_internal(
mint_info,
@@ -135,14 +127,9 @@ impl Mint {
async fn new_internal(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
#[cfg(feature = "auth")] auth_localstore: Option<
Arc<dyn database::MintAuthDatabase<Err = database::Error> + Send + Sync>,
>,
payment_processors: HashMap<
PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>,
localstore: DynMintDatabase,
#[cfg(feature = "auth")] auth_localstore: Option<DynMintAuthDatabase>,
payment_processors: HashMap<PaymentProcessorKey, DynMintPayment>,
) -> Result<Self, Error> {
let keysets = signatory.keysets().await?;
if !keysets
@@ -361,7 +348,7 @@ impl Mint {
&self,
unit: CurrencyUnit,
payment_method: PaymentMethod,
) -> Result<Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Error> {
) -> Result<DynMintPayment, Error> {
let key = PaymentProcessorKey::new(unit.clone(), payment_method.clone());
self.payment_processors.get(&key).cloned().ok_or_else(|| {
tracing::info!(
@@ -374,7 +361,7 @@ impl Mint {
}
/// Localstore
pub fn localstore(&self) -> Arc<dyn MintDatabase<database::Error> + Send + Sync> {
pub fn localstore(&self) -> DynMintDatabase {
Arc::clone(&self.localstore)
}
@@ -451,11 +438,8 @@ impl Mint {
/// Once invoice is paid mint quote status is updated
#[instrument(skip_all)]
async fn wait_for_paid_invoices(
payment_processors: &HashMap<
PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
payment_processors: &HashMap<PaymentProcessorKey, DynMintPayment>,
localstore: DynMintDatabase,
pubsub_manager: Arc<PubSubManager>,
shutdown: Arc<Notify>,
) -> Result<(), Error> {
@@ -527,8 +511,8 @@ impl Mint {
/// Handles payment waiting for a single processor
#[instrument(skip_all)]
async fn wait_for_processor_payments(
processor: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
processor: DynMintPayment,
localstore: DynMintDatabase,
pubsub_manager: Arc<PubSubManager>,
shutdown: Arc<Notify>,
) -> Result<(), Error> {
@@ -570,7 +554,7 @@ impl Mint {
/// This is a helper function that can be called with just the required components
#[instrument(skip_all)]
async fn handle_payment_notification(
localstore: &Arc<dyn MintDatabase<database::Error> + Send + Sync>,
localstore: &DynMintDatabase,
pubsub_manager: &Arc<PubSubManager>,
wait_payment_response: WaitPaymentResponse,
) -> Result<(), Error> {

View File

@@ -2,12 +2,11 @@
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use cdk_common::database::{self, MintDatabase, MintTransaction};
use cdk_common::database::{self, DynMintDatabase, MintTransaction};
use cdk_common::{Error, Proofs, ProofsMethods, PublicKey, QuoteId, State};
use super::subscription::PubSubManager;
type Db = Arc<dyn MintDatabase<database::Error> + Send + Sync>;
type Tx<'a, 'b> = Box<dyn MintTransaction<'a, database::Error> + Send + Sync + 'b>;
/// Proof writer
@@ -22,14 +21,14 @@ type Tx<'a, 'b> = Box<dyn MintTransaction<'a, database::Error> + Send + Sync + '
/// This struct is not fully ACID. If the process exits due to a panic, and the `Drop` function
/// cannot be run, the reset process should reset the state.
pub struct ProofWriter {
db: Option<Db>,
db: Option<DynMintDatabase>,
pubsub_manager: Arc<PubSubManager>,
proof_original_states: Option<HashMap<PublicKey, Option<State>>>,
}
impl ProofWriter {
/// Creates a new ProofWriter on top of the database
pub fn new(db: Db, pubsub_manager: Arc<PubSubManager>) -> Self {
pub fn new(db: DynMintDatabase, pubsub_manager: Arc<PubSubManager>) -> Self {
Self {
db: Some(db),
pubsub_manager,
@@ -203,7 +202,7 @@ async fn reset_proofs_to_original_state(
#[inline(always)]
async fn rollback(
db: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
db: DynMintDatabase,
ys: Vec<PublicKey>,
original_states: Vec<Option<State>>,
) -> Result<(), Error> {

View File

@@ -1,8 +1,7 @@
//! Specific Subscription for the cdk crate
use std::ops::Deref;
use std::sync::Arc;
use cdk_common::database::{self, MintDatabase};
use cdk_common::database::DynMintDatabase;
use cdk_common::mint::MintQuote;
use cdk_common::nut17::Notification;
use cdk_common::quote_id::QuoteId;
@@ -31,8 +30,8 @@ impl Default for PubSubManager {
}
}
impl From<Arc<dyn MintDatabase<database::Error> + Send + Sync>> for PubSubManager {
fn from(val: Arc<dyn MintDatabase<database::Error> + Send + Sync>) -> Self {
impl From<DynMintDatabase> for PubSubManager {
fn from(val: DynMintDatabase) -> Self {
PubSubManager(OnSubscription(Some(val)).into())
}
}

View File

@@ -1,9 +1,8 @@
//! On Subscription
//!
//! This module contains the code that is triggered when a new subscription is created.
use std::sync::Arc;
use cdk_common::database::{self, MintDatabase};
use cdk_common::database::DynMintDatabase;
use cdk_common::nut17::Notification;
use cdk_common::pub_sub::OnNewSubscription;
use cdk_common::quote_id::QuoteId;
@@ -17,7 +16,7 @@ use crate::nuts::{MeltQuoteBolt11Response, MintQuoteBolt11Response, ProofState,
/// This struct triggers code when a new subscription is created.
///
/// It is used to send the initial state of the subscription to the client.
pub struct OnSubscription(pub(crate) Option<Arc<dyn MintDatabase<database::Error> + Send + Sync>>);
pub struct OnSubscription(pub(crate) Option<DynMintDatabase>);
#[async_trait::async_trait]
impl OnNewSubscription for OnSubscription {