Skip to content

Commit a599eb2

Browse files
committed
fix: batch mint quote subscription snapshots
Fetch mint quotes for subscription snapshots in one database call instead of looking up each quote while walking the requested topics. This keeps the emitted events in request order while avoiding repeated quote queries for batch mint quote subscriptions.
1 parent fc1a84e commit a599eb2

1 file changed

Lines changed: 119 additions & 20 deletions

File tree

crates/cdk/src/mint/subscription.rs

Lines changed: 119 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,36 @@ pub struct MintPubSubSpec {
2929
}
3030

3131
impl MintPubSubSpec {
32-
/// Call Mint::check_mint_quote_payments to update the quote pinging the payment backend
33-
async fn get_mint_quote(
32+
/// Call Mint::check_mint_quote_payments to update quotes by pinging the payment backend
33+
async fn get_mint_quotes(
3434
&self,
35-
quote_id: &QuoteId,
36-
) -> Result<Option<MintQuote>, cdk_common::Error> {
37-
let mut quote = if let Some(quote) = self.db.get_mint_quote(quote_id).await? {
38-
quote
39-
} else {
40-
return Ok(None);
41-
};
35+
quote_ids: &[QuoteId],
36+
) -> Result<HashMap<QuoteId, MintQuote>, cdk_common::Error> {
37+
if quote_ids.is_empty() {
38+
return Ok(HashMap::new());
39+
}
4240

43-
Mint::check_mint_quote_payments(
44-
self.db.clone(),
45-
self.payment_processors.clone(),
46-
None,
47-
&mut quote,
48-
)
49-
.await?;
41+
let mut quotes = HashMap::new();
42+
43+
for mut quote in self
44+
.db
45+
.get_mint_quotes_by_ids(quote_ids)
46+
.await?
47+
.into_iter()
48+
.flatten()
49+
{
50+
Mint::check_mint_quote_payments(
51+
self.db.clone(),
52+
self.payment_processors.clone(),
53+
None,
54+
&mut quote,
55+
)
56+
.await?;
57+
58+
quotes.insert(quote.id.clone(), quote);
59+
}
5060

51-
Ok(Some(quote))
61+
Ok(quotes)
5262
}
5363

5464
async fn get_events_from_db(
@@ -57,6 +67,19 @@ impl MintPubSubSpec {
5767
) -> Result<Vec<MintEvent<QuoteId>>, String> {
5868
let mut to_return = vec![];
5969
let mut public_keys: Vec<PublicKey> = Vec::new();
70+
let mint_quote_ids = request
71+
.iter()
72+
.filter_map(|idx| match idx {
73+
NotificationId::MintQuoteBolt11(uuid) | NotificationId::MintQuoteBolt12(uuid) => {
74+
Some(uuid.clone())
75+
}
76+
_ => None,
77+
})
78+
.collect::<Vec<_>>();
79+
let mint_quotes = self
80+
.get_mint_quotes(&mint_quote_ids)
81+
.await
82+
.map_err(|e| e.to_string())?;
6083

6184
for idx in request.iter() {
6285
match idx {
@@ -86,9 +109,7 @@ impl MintPubSubSpec {
86109
}
87110
}
88111
NotificationId::MintQuoteBolt11(uuid) | NotificationId::MintQuoteBolt12(uuid) => {
89-
if let Some(mint_quote) =
90-
self.get_mint_quote(uuid).await.map_err(|e| e.to_string())?
91-
{
112+
if let Some(mint_quote) = mint_quotes.get(uuid).cloned() {
92113
let mint_quote = match idx {
93114
NotificationId::MintQuoteBolt11(_) => {
94115
let response: MintQuoteBolt11Response<QuoteId> = mint_quote.into();
@@ -305,3 +326,81 @@ impl Deref for PubSubManager {
305326
&self.0
306327
}
307328
}
329+
330+
#[cfg(test)]
331+
mod tests {
332+
use cdk_common::database::DynMintDatabase;
333+
use cdk_common::mint::MintQuote;
334+
use cdk_common::payment::PaymentIdentifier;
335+
use cdk_common::QuoteId;
336+
337+
use super::*;
338+
339+
fn paid_bolt11_quote(id: QuoteId, amount: u64) -> MintQuote {
340+
MintQuote::new(
341+
Some(id),
342+
format!("lnbc1test{amount}"),
343+
CurrencyUnit::Sat,
344+
Some(Amount::new(amount, CurrencyUnit::Sat)),
345+
0,
346+
PaymentIdentifier::CustomId(format!("lookup-{amount}")),
347+
None,
348+
Amount::new(0, CurrencyUnit::Sat),
349+
Amount::new(0, CurrencyUnit::Sat),
350+
cdk_common::PaymentMethod::Known(cdk_common::nut00::KnownMethod::Bolt11),
351+
0,
352+
vec![],
353+
vec![],
354+
None,
355+
)
356+
}
357+
358+
async fn add_mint_quote(db: &DynMintDatabase, quote: MintQuote) {
359+
let payment_amount = quote.amount.clone().expect("quote amount");
360+
let payment_id = format!("payment-{}", quote.id);
361+
let mut tx = db.begin_transaction().await.expect("begin transaction");
362+
let mut quote = tx.add_mint_quote(quote).await.expect("add mint quote");
363+
quote
364+
.add_payment(payment_amount, payment_id, Some(0))
365+
.expect("add payment");
366+
tx.update_mint_quote(&mut quote)
367+
.await
368+
.expect("update mint quote");
369+
tx.commit().await.expect("commit transaction");
370+
}
371+
372+
#[tokio::test]
373+
async fn get_events_from_db_batches_multiple_mint_quote_filters() {
374+
let db: DynMintDatabase = Arc::new(
375+
cdk_sqlite::mint::memory::empty()
376+
.await
377+
.expect("in-memory mint database"),
378+
);
379+
let first_quote_id = QuoteId::new_uuid();
380+
let second_quote_id = QuoteId::new_uuid();
381+
add_mint_quote(&db, paid_bolt11_quote(first_quote_id.clone(), 21)).await;
382+
add_mint_quote(&db, paid_bolt11_quote(second_quote_id.clone(), 34)).await;
383+
384+
let spec = MintPubSubSpec {
385+
db,
386+
payment_processors: Arc::new(HashMap::new()),
387+
};
388+
let events = spec
389+
.get_events_from_db(&[
390+
NotificationId::MintQuoteBolt11(first_quote_id.clone()),
391+
NotificationId::MintQuoteBolt11(second_quote_id.clone()),
392+
])
393+
.await
394+
.expect("get events");
395+
396+
let quote_ids = events
397+
.into_iter()
398+
.map(|event| match event.into_inner() {
399+
NotificationPayload::MintQuoteBolt11Response(response) => response.quote,
400+
payload => panic!("unexpected payload: {payload:?}"),
401+
})
402+
.collect::<Vec<_>>();
403+
404+
assert_eq!(quote_ids, vec![first_quote_id, second_quote_id]);
405+
}
406+
}

0 commit comments

Comments
 (0)