Skip to content

Commit 2d726d5

Browse files
authored
check mint status for quote,add cronjob, add to changelog,add cancel to mint (#524)
1 parent 1e1d5ca commit 2d726d5

File tree

14 files changed

+348
-10
lines changed

14 files changed

+348
-10
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
* Add default mint configuration options
55
* `default_mint_url`
66
* `default_mint_node_id`
7+
* Implement `request_to_mint`
78
* Add minting status flag to bill
89
* Add endpoint to fetch minting status for a bill
9-
* Implement `request_to_mint`
10+
* Add logic for checking mint request status on the mint
11+
* Add cronjob to check mint requests
12+
* Add endpoint to cancel mint requests
1013
* Change bitcoin addresses and descriptor to p2wpkh
1114
* Suppress logging from crates we don't control
1215

crates/bcr-ebill-api/src/external/mint.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use bcr_ebill_core::{
33
PostalAddress, ServiceTraitBounds,
44
bill::BitcreditBill,
55
contact::{BillAnonParticipant, BillIdentParticipant, BillParticipant, ContactType},
6-
util::BcrKeys,
6+
util::{BcrKeys, date::DateTimeUtc},
77
};
8-
use bcr_wdc_webapi::quotes::{BillInfo, EnquireReply, EnquireRequest};
9-
use cashu::nut01 as cdk01;
8+
use bcr_wdc_webapi::quotes::{BillInfo, EnquireReply, EnquireRequest, StatusReply};
9+
use cashu::{nut01 as cdk01, nut02 as cdk02};
1010
use thiserror::Error;
1111

1212
/// Generic result type
@@ -38,13 +38,20 @@ use crate::util;
3838
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
3939
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
4040
pub trait MintClientApi: ServiceTraitBounds {
41+
/// Request to mint a bill with a given mint
4142
async fn enquire_mint_quote(
4243
&self,
4344
mint_url: &str,
4445
requester_keys: &BcrKeys,
4546
bill: &BitcreditBill,
4647
endorsees: &[BillParticipant],
4748
) -> Result<String>;
49+
/// Look up a quote for a mint
50+
async fn lookup_quote_for_mint(
51+
&self,
52+
mint_url: &str,
53+
quote_id: &str,
54+
) -> Result<QuoteStatusReply>;
4855
}
4956

5057
#[derive(Debug, Clone, Default)]
@@ -113,6 +120,54 @@ impl MintClientApi for MintClient {
113120
let reply: EnquireReply = res.json().await.map_err(Error::from)?;
114121
Ok(reply.id.to_string())
115122
}
123+
124+
async fn lookup_quote_for_mint(
125+
&self,
126+
mint_url: &str,
127+
quote_id: &str,
128+
) -> Result<QuoteStatusReply> {
129+
let url = format!("{}/v1/mint/credit/quote/{quote_id}", mint_url);
130+
let res = self.cl.get(&url).send().await.map_err(Error::from)?;
131+
let reply: StatusReply = res.json().await.map_err(Error::from)?;
132+
Ok(reply.into())
133+
}
134+
}
135+
136+
#[derive(Debug, Clone)]
137+
pub enum QuoteStatusReply {
138+
Pending,
139+
Denied,
140+
Offered {
141+
keyset_id: cdk02::Id,
142+
expiration_date: DateTimeUtc,
143+
discounted: bitcoin::Amount,
144+
},
145+
Accepted {
146+
keyset_id: cdk02::Id,
147+
},
148+
Rejected {
149+
tstamp: DateTimeUtc,
150+
},
151+
}
152+
153+
impl From<StatusReply> for QuoteStatusReply {
154+
fn from(value: StatusReply) -> Self {
155+
match value {
156+
StatusReply::Pending => QuoteStatusReply::Pending,
157+
StatusReply::Denied => QuoteStatusReply::Denied,
158+
StatusReply::Offered {
159+
keyset_id,
160+
expiration_date,
161+
discounted,
162+
} => QuoteStatusReply::Offered {
163+
keyset_id,
164+
expiration_date,
165+
discounted,
166+
},
167+
StatusReply::Accepted { keyset_id } => QuoteStatusReply::Accepted { keyset_id },
168+
StatusReply::Rejected { tstamp } => QuoteStatusReply::Rejected { tstamp },
169+
}
170+
}
116171
}
117172

118173
// These are needed for now, because we use different `bcr-ebill-core` versions in wildcat and here

crates/bcr-ebill-api/src/service/bill_service/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ pub enum Error {
6262
#[error("Can not get recoursee identity from contacts.")]
6363
RecourseeNotInContacts,
6464

65+
/// errors that stem from trying to cancel a mint request that's not pending
66+
#[error("Mint request can only be cancelled if it's pending.")]
67+
CancelMintRequestNotPending,
68+
6569
/// errors that stem from bill validation errors
6670
#[error("bill validation error {0}")]
6771
Validation(#[from] bcr_ebill_core::ValidationError),

crates/bcr-ebill-api/src/service/bill_service/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,19 @@ pub trait BillServiceApi: ServiceTraitBounds {
187187
current_identity_node_id: &str,
188188
) -> Result<Vec<MintRequestState>>;
189189

190+
/// Cancel a pending request to mint
191+
async fn cancel_request_to_mint(
192+
&self,
193+
mint_request_id: &str,
194+
current_identity_node_id: &str,
195+
) -> Result<()>;
196+
197+
/// Check mint state for a given bill
198+
async fn check_mint_state(&self, bill_id: &str, current_identity_node_id: &str) -> Result<()>;
199+
200+
/// Check mint state for all bills
201+
async fn check_mint_state_for_all_bills(&self) -> Result<()>;
202+
190203
/// Clear the bill cache
191204
async fn clear_bill_cache(&self) -> Result<()>;
192205
}

crates/bcr-ebill-api/src/service/bill_service/service.rs

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::data::{
1414
identity::Identity,
1515
};
1616
use crate::external::bitcoin::BitcoinClientApi;
17-
use crate::external::mint::MintClientApi;
17+
use crate::external::mint::{MintClientApi, QuoteStatusReply};
1818
use crate::get_config;
1919
use crate::persistence::bill::BillChainStoreApi;
2020
use crate::persistence::bill::BillStoreApi;
@@ -36,7 +36,7 @@ use bcr_ebill_core::constants::{
3636
};
3737
use bcr_ebill_core::contact::{BillAnonParticipant, BillParticipant, Contact};
3838
use bcr_ebill_core::identity::{IdentityType, IdentityWithAll};
39-
use bcr_ebill_core::mint::{MintRequestState, MintRequestStatus};
39+
use bcr_ebill_core::mint::{MintRequest, MintRequestState, MintRequestStatus};
4040
use bcr_ebill_core::notification::ActionType;
4141
use bcr_ebill_core::util::currency;
4242
use bcr_ebill_core::{ServiceTraitBounds, Validate, ValidationError};
@@ -276,6 +276,86 @@ impl BillService {
276276
}
277277
Ok(())
278278
}
279+
280+
/// Checks a given mint quote and updates the bill mint state, if there was a change
281+
pub(super) async fn check_mint_quote_and_update_bill_mint_state(
282+
&self,
283+
mint_request: &MintRequest,
284+
) -> Result<()> {
285+
debug!(
286+
"Checking mint request for quote {}",
287+
&mint_request.mint_request_id
288+
);
289+
// if it doesn't have a 'finished' state, we check the quote at the mint
290+
if !matches!(
291+
mint_request.status,
292+
MintRequestStatus::Cancelled
293+
| MintRequestStatus::Rejected
294+
| MintRequestStatus::Expired
295+
| MintRequestStatus::Denied
296+
| MintRequestStatus::Accepted
297+
) {
298+
let mint_cfg = &get_config().mint_config;
299+
// for now, we only support the default mint
300+
if mint_request.mint_node_id == mint_cfg.default_mint_node_id {
301+
let updated_status = self
302+
.mint_client
303+
.lookup_quote_for_mint(
304+
&mint_cfg.default_mint_url,
305+
&mint_request.mint_request_id,
306+
)
307+
.await?;
308+
// only update, if changed
309+
match updated_status {
310+
QuoteStatusReply::Pending => {
311+
if !matches!(mint_request.status, MintRequestStatus::Pending) {
312+
self.mint_store
313+
.update_request(
314+
&mint_request.mint_request_id,
315+
&MintRequestStatus::Pending,
316+
)
317+
.await?;
318+
}
319+
}
320+
QuoteStatusReply::Denied => {
321+
// checked above, that it's not denied
322+
self.mint_store
323+
.update_request(&mint_request.mint_request_id, &MintRequestStatus::Denied)
324+
.await?;
325+
}
326+
QuoteStatusReply::Offered {
327+
..
328+
// keyset_id,
329+
// expiration_date,
330+
// discounted,
331+
} => {
332+
if !matches!(mint_request.status, MintRequestStatus::Offered) {
333+
// TODO next task: if not offered yet, persist offer
334+
self.mint_store
335+
.update_request(
336+
&mint_request.mint_request_id,
337+
&MintRequestStatus::Offered,
338+
)
339+
.await?;
340+
}
341+
}
342+
QuoteStatusReply::Accepted { .. } => {
343+
// checked above, that it's not accepted
344+
self.mint_store
345+
.update_request(&mint_request.mint_request_id, &MintRequestStatus::Accepted)
346+
.await?;
347+
}
348+
QuoteStatusReply::Rejected { .. } => {
349+
// checked above, that it's not rejected
350+
self.mint_store
351+
.update_request(&mint_request.mint_request_id, &MintRequestStatus::Rejected)
352+
.await?;
353+
}
354+
};
355+
}
356+
}
357+
Ok(())
358+
}
279359
}
280360

281361
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -691,7 +771,7 @@ impl BillServiceApi for BillService {
691771
.mint_store
692772
.get_requests(&signer_public_data.node_id(), bill_id, mint_node_id)
693773
.await?;
694-
// If there are any active (i.e. pending, accepted or offered) requests, we can't make another one
774+
// If there are any active, or accepted (i.e. pending, accepted or offered) requests, we can't make another one
695775
if requests_to_mint_for_bill_and_mint.iter().any(|rtm| {
696776
matches!(
697777
rtm.status,
@@ -1118,6 +1198,66 @@ impl BillServiceApi for BillService {
11181198
.collect())
11191199
}
11201200

1201+
async fn cancel_request_to_mint(
1202+
&self,
1203+
mint_request_id: &str,
1204+
current_identity_node_id: &str,
1205+
) -> Result<()> {
1206+
debug!("trying to cancel request to mint {mint_request_id}");
1207+
match self.mint_store.get_request(mint_request_id).await {
1208+
Ok(Some(req)) => {
1209+
if req.requester_node_id == current_identity_node_id {
1210+
if matches!(req.status, MintRequestStatus::Pending) {
1211+
// TODO next task: call endpoint on mint to cancel
1212+
self.mint_store
1213+
.update_request(mint_request_id, &MintRequestStatus::Cancelled)
1214+
.await?;
1215+
Ok(())
1216+
} else {
1217+
Err(Error::CancelMintRequestNotPending)
1218+
}
1219+
} else {
1220+
Err(Error::NotFound)
1221+
}
1222+
}
1223+
Ok(None) => Err(Error::NotFound),
1224+
Err(e) => Err(e.into()),
1225+
}
1226+
}
1227+
1228+
async fn check_mint_state(&self, bill_id: &str, current_identity_node_id: &str) -> Result<()> {
1229+
debug!("checking mint requests for bill {bill_id}");
1230+
let requests = self
1231+
.mint_store
1232+
.get_requests_for_bill(current_identity_node_id, bill_id)
1233+
.await?;
1234+
for req in requests {
1235+
if let Err(e) = self.check_mint_quote_and_update_bill_mint_state(&req).await {
1236+
error!(
1237+
"Could not check mint state for {}: {e}",
1238+
&req.mint_request_id
1239+
);
1240+
}
1241+
}
1242+
Ok(())
1243+
}
1244+
1245+
async fn check_mint_state_for_all_bills(&self) -> Result<()> {
1246+
debug!("checking all active mint requests");
1247+
// get all active (offered, pending) requests
1248+
let requests = self.mint_store.get_all_active_requests().await?;
1249+
debug!("checking all active mint requests ({})", requests.len());
1250+
for req in requests {
1251+
if let Err(e) = self.check_mint_quote_and_update_bill_mint_state(&req).await {
1252+
error!(
1253+
"Could not check mint state for {}: {e}",
1254+
&req.mint_request_id
1255+
);
1256+
}
1257+
}
1258+
Ok(())
1259+
}
1260+
11211261
async fn clear_bill_cache(&self) -> Result<()> {
11221262
self.store.clear_bill_cache().await?;
11231263
Ok(())

crates/bcr-ebill-api/src/tests/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub mod tests {
1414
company::{Company, CompanyKeys},
1515
contact::{BillIdentParticipant, BillParticipant, Contact, ContactType},
1616
identity::{ActiveIdentityState, Identity, IdentityType, IdentityWithAll},
17-
mint::MintRequest,
17+
mint::{MintRequest, MintRequestStatus},
1818
nostr_contact::{HandshakeStatus, NostrContact, TrustLevel},
1919
notification::{ActionType, Notification, NotificationType},
2020
util::crypto::BcrKeys,
@@ -59,6 +59,7 @@ pub mod tests {
5959
#[async_trait]
6060
impl MintStoreApi for MintStore {
6161
async fn exists_for_bill(&self, requester_node_id: &str, bill_id: &str) -> Result<bool>;
62+
async fn get_all_active_requests(&self) -> Result<Vec<MintRequest>>;
6263
async fn get_requests(
6364
&self,
6465
requester_node_id: &str,
@@ -78,6 +79,12 @@ pub mod tests {
7879
mint_request_id: &str,
7980
timestamp: u64,
8081
) -> Result<()>;
82+
async fn get_request(&self, mint_request_id: &str) -> Result<Option<MintRequest>>;
83+
async fn update_request(
84+
&self,
85+
mint_request_id: &str,
86+
new_status: &MintRequestStatus,
87+
) -> Result<()>;
8188
}
8289
}
8390

crates/bcr-ebill-persistence/src/constants.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ pub const DB_OP_CODE: &str = "op_code";
2121

2222
pub const DB_COMPANY_ID: &str = "company_id";
2323
pub const DB_BILL_ID: &str = "bill_id";
24+
pub const DB_STATUS: &str = "status";
25+
pub const DB_STATUS_OFFERED: &str = "status_offered";
26+
pub const DB_STATUS_PENDING: &str = "status_pending";
2427
pub const DB_MINT_NODE_ID: &str = "mint_node_id";
28+
pub const DB_MINT_REQUEST_ID: &str = "mint_request_id";
2529
pub const DB_MINT_REQUESTER_NODE_ID: &str = "requester_node_id";
2630
pub const DB_SEARCH_TERM: &str = "search_term";
2731

0 commit comments

Comments
 (0)