Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into nut-17-ws-subscription
Browse files Browse the repository at this point in the history
  • Loading branch information
crodas committed Oct 31, 2024
2 parents bf84935 + d9fb5f8 commit 615bc37
Show file tree
Hide file tree
Showing 15 changed files with 95 additions and 204 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
- cdk: Move unit conversion util fn to amount module ([davidcaseria]).
- cdk: Remove spent proofs from db when check state is called ([mubarak23]).
- cdk: Use `MintUrl` directly in wallet client ([ok300]).
- cdk-cli: Change cdk-cli pay command to melt ([mubarak23]).
- cdk-cli: Change cdk-cli pay command to melt ([mubarak23]).
- cdk: Rename `Wallet::get_proofs` to `Wallet::get_unspent_proofs` ([ok300]).


### Added
Expand All @@ -49,6 +50,7 @@
- cdk: Wallet verifiys keyset id when first fetching keys ([thesimplekid]).
- cdk-mind: Add swagger docs ([ok300]).
- cdk: NUT18 payment request support ([thesimplekid]).
- cdk: Add `Wallet::get_proofs_with` ([ok300]).

### Removed
- cdk: Remove `MintMeltSettings` since it is no longer used ([lollerfirst]).
Expand Down
4 changes: 2 additions & 2 deletions crates/cdk-integration-tests/tests/fake_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async fn test_fake_melt_payment_fail() -> Result<()> {
assert!(melt.is_err());

// The mint should have unset proofs from pending since payment failed
let all_proof = wallet.get_proofs().await?;
let all_proof = wallet.get_unspent_proofs().await?;
let states = wallet.check_proofs_spent(all_proof).await?;
for state in states {
assert!(state.state == State::Unspent);
Expand Down Expand Up @@ -344,7 +344,7 @@ async fn test_fake_melt_change_in_quote() -> Result<()> {

let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());

let proofs = wallet.get_proofs().await?;
let proofs = wallet.get_unspent_proofs().await?;

let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;

Expand Down
4 changes: 2 additions & 2 deletions crates/cdk-integration-tests/tests/regtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ async fn test_restore() -> Result<()> {
assert!(wallet_2.total_balance().await? == 0.into());

let restored = wallet_2.restore().await?;
let proofs = wallet_2.get_proofs().await?;
let proofs = wallet_2.get_unspent_proofs().await?;

wallet_2
.swap(None, SplitTarget::default(), proofs, None, false)
Expand All @@ -194,7 +194,7 @@ async fn test_restore() -> Result<()> {

assert!(wallet_2.total_balance().await? == 100.into());

let proofs = wallet.get_proofs().await?;
let proofs = wallet.get_unspent_proofs().await?;

let states = wallet.check_proofs_spent(proofs).await?;

Expand Down
2 changes: 1 addition & 1 deletion crates/cdk/examples/proof-selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async fn main() {
println!("Minted {}", receive_amount);
}

let proofs = wallet.get_proofs().await.unwrap();
let proofs = wallet.get_unspent_proofs().await.unwrap();

let selected = wallet
.select_proofs_to_send(Amount::from(64), proofs, false)
Expand Down
59 changes: 39 additions & 20 deletions crates/cdk/src/mint/mint_nut04.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl Mint {
Ok(quote)
}

/// Check mint quote
/// CheckD mint quote
#[instrument(skip(self))]
pub async fn check_mint_quote(&self, quote_id: &str) -> Result<MintQuoteBolt11Response, Error> {
let quote = self
Expand All @@ -131,8 +131,6 @@ impl Mint {
.await?
.ok_or(Error::UnknownQuote)?;

let paid = quote.state == MintQuoteState::Paid;

// Since the pending state is not part of the NUT it should not be part of the
// response. In practice the wallet should not be checking the state of
// a quote while waiting for the mint response.
Expand All @@ -144,7 +142,6 @@ impl Mint {
Ok(MintQuoteBolt11Response {
quote: quote.id,
request: quote.request,
paid: Some(paid),
state,
expiry: Some(quote.expiry),
})
Expand Down Expand Up @@ -206,13 +203,41 @@ impl Mint {
.await
{
tracing::debug!(
"Quote {} paid by lookup id {}",
mint_quote.id,
request_lookup_id
"Received payment notification for mint quote {}",
mint_quote.id
);
self.localstore
.update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
.await?;

if mint_quote.state != MintQuoteState::Issued
&& mint_quote.state != MintQuoteState::Paid
{
let unix_time = unix_time();

if mint_quote.expiry < unix_time {
tracing::warn!(
"Mint quote {} paid at {} expired at {}, leaving current state",
mint_quote.id,
mint_quote.expiry,
unix_time,
);
return Err(Error::ExpiredQuote(mint_quote.expiry, unix_time));
}

tracing::debug!(
"Marking quote {} paid by lookup id {}",
mint_quote.id,
request_lookup_id
);

self.localstore
.update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
.await?;
} else {
tracing::debug!(
"{} Quote already {} continuing",
mint_quote.id,
mint_quote.state
);
}

self.pubsub_manager
.mint_quote_bolt11_status(&mint_quote, MintQuoteState::Paid);
Expand All @@ -226,18 +251,12 @@ impl Mint {
&self,
mint_request: nut04::MintBolt11Request,
) -> Result<nut04::MintBolt11Response, Error> {
// Check quote is known and not expired
let mint_quote = match self.localstore.get_mint_quote(&mint_request.quote).await? {
Some(quote) => {
if quote.expiry < unix_time() {
return Err(Error::ExpiredQuote(quote.expiry, unix_time()));
}
let mint_quote =
if let Some(quote) = self.localstore.get_mint_quote(&mint_request.quote).await? {
quote
}
None => {
} else {
return Err(Error::UnknownQuote);
}
};
};

let state = self
.localstore
Expand Down
76 changes: 2 additions & 74 deletions crates/cdk/src/nuts/nut04.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
use std::fmt;
use std::str::FromStr;

use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use super::nut00::{BlindSignature, BlindedMessage, CurrencyUnit, PaymentMethod};
Expand Down Expand Up @@ -80,96 +79,25 @@ impl FromStr for QuoteState {
}

/// Mint quote response [NUT-04]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
pub struct MintQuoteBolt11Response {
/// Quote Id
pub quote: String,
/// Payment request to fulfil
pub request: String,
// TODO: To be deprecated
/// Whether the the request haas be paid
/// Deprecated
pub paid: Option<bool>,
/// Quote State
pub state: MintQuoteState,
/// Unix timestamp until the quote is valid
pub expiry: Option<u64>,
}

// A custom deserializer is needed until all mints
// update some will return without the required state.
impl<'de> Deserialize<'de> for MintQuoteBolt11Response {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;

let quote: String = serde_json::from_value(
value
.get("quote")
.ok_or(serde::de::Error::missing_field("quote"))?
.clone(),
)
.map_err(|_| serde::de::Error::custom("Invalid quote id string"))?;

let request: String = serde_json::from_value(
value
.get("request")
.ok_or(serde::de::Error::missing_field("request"))?
.clone(),
)
.map_err(|_| serde::de::Error::custom("Invalid request string"))?;

let paid: Option<bool> = value.get("paid").and_then(|p| p.as_bool());

let state: Option<String> = value
.get("state")
.and_then(|s| serde_json::from_value(s.clone()).ok());

let (state, paid) = match (state, paid) {
(None, None) => return Err(serde::de::Error::custom("State or paid must be defined")),
(Some(state), _) => {
let state: QuoteState = QuoteState::from_str(&state)
.map_err(|_| serde::de::Error::custom("Unknown state"))?;
let paid = state == QuoteState::Paid;

(state, paid)
}
(None, Some(paid)) => {
let state = if paid {
QuoteState::Paid
} else {
QuoteState::Unpaid
};
(state, paid)
}
};

let expiry = value
.get("expiry")
.ok_or(serde::de::Error::missing_field("expiry"))?
.as_u64();

Ok(Self {
quote,
request,
paid: Some(paid),
state,
expiry,
})
}
}

#[cfg(feature = "mint")]
impl From<crate::mint::MintQuote> for MintQuoteBolt11Response {
fn from(mint_quote: crate::mint::MintQuote) -> MintQuoteBolt11Response {
let paid = mint_quote.state == QuoteState::Paid;
MintQuoteBolt11Response {
quote: mint_quote.id,
request: mint_quote.request,
paid: Some(paid),
state: mint_quote.state,
expiry: Some(mint_quote.expiry),
}
Expand Down
3 changes: 1 addition & 2 deletions crates/cdk/src/nuts/nut17.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,6 @@ impl From<&MintQuote> for MintQuoteBolt11Response {
MintQuoteBolt11Response {
quote: mint_quote.id.clone(),
request: mint_quote.request.clone(),
paid: Some(mint_quote.state == MintQuoteState::Paid),
state: mint_quote.state,
expiry: Some(mint_quote.expiry),
}
Expand Down Expand Up @@ -199,7 +198,7 @@ impl PubSubManager {
new_state: MintQuoteState,
) {
let mut event = quote.into();
event.paid = Some(new_state == MintQuoteState::Paid);
event.state = new_state;

self.broadcast(event.into());
}
Expand Down
45 changes: 9 additions & 36 deletions crates/cdk/src/wallet/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,24 @@ use std::collections::HashMap;

use tracing::instrument;

use crate::{
nuts::{CurrencyUnit, State},
Amount, Error, Wallet,
};
use crate::nuts::nut00::ProofsMethods;
use crate::{nuts::CurrencyUnit, Amount, Error, Wallet};

impl Wallet {
/// Total unspent balance of wallet
#[instrument(skip(self))]
pub async fn total_balance(&self) -> Result<Amount, Error> {
let proofs = self
.localstore
.get_proofs(
Some(self.mint_url.clone()),
Some(self.unit),
Some(vec![State::Unspent]),
None,
)
.await?;
let balance = Amount::try_sum(proofs.iter().map(|p| p.proof.amount))?;

Ok(balance)
Ok(self.get_unspent_proofs().await?.total_amount()?)
}

/// Total pending balance
#[instrument(skip(self))]
pub async fn total_pending_balance(&self) -> Result<HashMap<CurrencyUnit, Amount>, Error> {
let proofs = self
.localstore
.get_proofs(
Some(self.mint_url.clone()),
Some(self.unit),
Some(vec![State::Pending]),
None,
)
.await?;
let proofs = self.get_pending_proofs().await?;

// TODO If only the proofs for this wallet's unit are retrieved, why build a map with key = unit?
let balances = proofs.iter().fold(HashMap::new(), |mut acc, proof| {
*acc.entry(proof.unit).or_insert(Amount::ZERO) += proof.proof.amount;
*acc.entry(self.unit).or_insert(Amount::ZERO) += proof.amount;
acc
});

Expand All @@ -49,18 +29,11 @@ impl Wallet {
/// Total reserved balance
#[instrument(skip(self))]
pub async fn total_reserved_balance(&self) -> Result<HashMap<CurrencyUnit, Amount>, Error> {
let proofs = self
.localstore
.get_proofs(
Some(self.mint_url.clone()),
Some(self.unit),
Some(vec![State::Reserved]),
None,
)
.await?;
let proofs = self.get_reserved_proofs().await?;

// TODO If only the proofs for this wallet's unit are retrieved, why build a map with key = unit?
let balances = proofs.iter().fold(HashMap::new(), |mut acc, proof| {
*acc.entry(proof.unit).or_insert(Amount::ZERO) += proof.proof.amount;
*acc.entry(self.unit).or_insert(Amount::ZERO) += proof.amount;
acc
});

Expand Down
2 changes: 1 addition & 1 deletion crates/cdk/src/wallet/melt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ impl Wallet {

let inputs_needed_amount = quote_info.amount + quote_info.fee_reserve;

let available_proofs = self.get_proofs().await?;
let available_proofs = self.get_unspent_proofs().await?;

let input_proofs = self
.select_proofs_to_swap(inputs_needed_amount, available_proofs)
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl Wallet {
/// Get amounts needed to refill proof state
#[instrument(skip(self))]
pub async fn amounts_needed_for_state_target(&self) -> Result<Vec<Amount>, Error> {
let unspent_proofs = self.get_proofs().await?;
let unspent_proofs = self.get_unspent_proofs().await?;

let amounts_count: HashMap<usize, usize> =
unspent_proofs
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk/src/wallet/multi_mint_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl MultiMintWallet {
let mut mint_proofs = BTreeMap::new();

for (WalletKey { mint_url, unit: u }, wallet) in self.wallets.lock().await.iter() {
let wallet_proofs = wallet.get_proofs().await?;
let wallet_proofs = wallet.get_unspent_proofs().await?;
mint_proofs.insert(mint_url.clone(), (wallet_proofs, *u));
}
Ok(mint_proofs)
Expand Down
Loading

0 comments on commit 615bc37

Please sign in to comment.