|
@@ -19,9 +19,9 @@ use cdk_common::database::mint::{
|
|
|
CompletedOperationsDatabase, CompletedOperationsTransaction, SagaDatabase, SagaTransaction,
|
|
CompletedOperationsDatabase, CompletedOperationsTransaction, SagaDatabase, SagaTransaction,
|
|
|
};
|
|
};
|
|
|
use cdk_common::database::{
|
|
use cdk_common::database::{
|
|
|
- self, ConversionError, DbTransactionFinalizer, Error, MintDatabase, MintKeyDatabaseTransaction,
|
|
|
|
|
- MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase, MintQuotesTransaction,
|
|
|
|
|
- MintSignatureTransaction, MintSignaturesDatabase,
|
|
|
|
|
|
|
+ self, Acquired, ConversionError, DbTransactionFinalizer, Error, MintDatabase,
|
|
|
|
|
+ MintKeyDatabaseTransaction, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
|
|
|
|
|
+ MintQuotesTransaction, MintSignatureTransaction, MintSignaturesDatabase,
|
|
|
};
|
|
};
|
|
|
use cdk_common::mint::{
|
|
use cdk_common::mint::{
|
|
|
self, IncomingPayment, Issuance, MeltPaymentRequest, MeltQuote, MintKeySetInfo, MintQuote,
|
|
self, IncomingPayment, Issuance, MeltPaymentRequest, MeltQuote, MintKeySetInfo, MintQuote,
|
|
@@ -31,7 +31,7 @@ use cdk_common::nut00::ProofsMethods;
|
|
|
use cdk_common::payment::PaymentIdentifier;
|
|
use cdk_common::payment::PaymentIdentifier;
|
|
|
use cdk_common::quote_id::QuoteId;
|
|
use cdk_common::quote_id::QuoteId;
|
|
|
use cdk_common::secret::Secret;
|
|
use cdk_common::secret::Secret;
|
|
|
-use cdk_common::state::{check_melt_quote_state_transition, check_state_transition};
|
|
|
|
|
|
|
+use cdk_common::state::check_melt_quote_state_transition;
|
|
|
use cdk_common::util::unix_time;
|
|
use cdk_common::util::unix_time;
|
|
|
use cdk_common::{
|
|
use cdk_common::{
|
|
|
Amount, BlindSignature, BlindSignatureDleq, BlindedMessage, CurrencyUnit, Id, MeltQuoteState,
|
|
Amount, BlindSignature, BlindSignatureDleq, BlindedMessage, CurrencyUnit, Id, MeltQuoteState,
|
|
@@ -80,31 +80,6 @@ where
|
|
|
inner: ConnectionWithTransaction<RM::Connection, PooledResource<RM>>,
|
|
inner: ConnectionWithTransaction<RM::Connection, PooledResource<RM>>,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-#[inline(always)]
|
|
|
|
|
-async fn get_current_states<C>(
|
|
|
|
|
- conn: &C,
|
|
|
|
|
- ys: &[PublicKey],
|
|
|
|
|
-) -> Result<HashMap<PublicKey, State>, Error>
|
|
|
|
|
-where
|
|
|
|
|
- C: DatabaseExecutor + Send + Sync,
|
|
|
|
|
-{
|
|
|
|
|
- if ys.is_empty() {
|
|
|
|
|
- return Ok(Default::default());
|
|
|
|
|
- }
|
|
|
|
|
- query(r#"SELECT y, state FROM proof WHERE y IN (:ys)"#)?
|
|
|
|
|
- .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
|
|
|
|
|
- .fetch_all(conn)
|
|
|
|
|
- .await?
|
|
|
|
|
- .into_iter()
|
|
|
|
|
- .map(|row| {
|
|
|
|
|
- Ok((
|
|
|
|
|
- column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice),
|
|
|
|
|
- column_as_string!(&row[1], State::from_str),
|
|
|
|
|
- ))
|
|
|
|
|
- })
|
|
|
|
|
- .collect::<Result<HashMap<_, _>, _>>()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
impl<RM> SQLMintDatabase<RM>
|
|
impl<RM> SQLMintDatabase<RM>
|
|
|
where
|
|
where
|
|
|
RM: DatabasePool + 'static,
|
|
RM: DatabasePool + 'static,
|
|
@@ -166,6 +141,8 @@ where
|
|
|
}?;
|
|
}?;
|
|
|
|
|
|
|
|
for proof in proofs {
|
|
for proof in proofs {
|
|
|
|
|
+ let y = proof.y()?;
|
|
|
|
|
+
|
|
|
query(
|
|
query(
|
|
|
r#"
|
|
r#"
|
|
|
INSERT INTO proof
|
|
INSERT INTO proof
|
|
@@ -174,7 +151,7 @@ where
|
|
|
(:y, :amount, :keyset_id, :secret, :c, :witness, :state, :quote_id, :created_time, :operation_kind, :operation_id)
|
|
(:y, :amount, :keyset_id, :secret, :c, :witness, :state, :quote_id, :created_time, :operation_kind, :operation_id)
|
|
|
"#,
|
|
"#,
|
|
|
)?
|
|
)?
|
|
|
- .bind("y", proof.y()?.to_bytes().to_vec())
|
|
|
|
|
|
|
+ .bind("y", y.to_bytes().to_vec())
|
|
|
.bind("amount", proof.amount.to_i64())
|
|
.bind("amount", proof.amount.to_i64())
|
|
|
.bind("keyset_id", proof.keyset_id.to_string())
|
|
.bind("keyset_id", proof.keyset_id.to_string())
|
|
|
.bind("secret", proof.secret.to_string())
|
|
.bind("secret", proof.secret.to_string())
|
|
@@ -200,7 +177,7 @@ where
|
|
|
ys: &[PublicKey],
|
|
ys: &[PublicKey],
|
|
|
new_state: State,
|
|
new_state: State,
|
|
|
) -> Result<Vec<Option<State>>, Self::Err> {
|
|
) -> Result<Vec<Option<State>>, Self::Err> {
|
|
|
- let mut current_states = get_current_states(&self.inner, ys).await?;
|
|
|
|
|
|
|
+ let mut current_states = get_current_states(&self.inner, ys, true).await?;
|
|
|
|
|
|
|
|
if current_states.len() != ys.len() {
|
|
if current_states.len() != ys.len() {
|
|
|
tracing::warn!(
|
|
tracing::warn!(
|
|
@@ -211,10 +188,6 @@ where
|
|
|
return Err(database::Error::ProofNotFound);
|
|
return Err(database::Error::ProofNotFound);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- for state in current_states.values() {
|
|
|
|
|
- check_state_transition(*state, new_state)?;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
query(r#"UPDATE proof SET state = :new_state WHERE y IN (:ys)"#)?
|
|
query(r#"UPDATE proof SET state = :new_state WHERE y IN (:ys)"#)?
|
|
|
.bind("new_state", new_state.to_string())
|
|
.bind("new_state", new_state.to_string())
|
|
|
.bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
|
|
.bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
|
|
@@ -261,7 +234,7 @@ where
|
|
|
|
|
|
|
|
if total_deleted != ys.len() {
|
|
if total_deleted != ys.len() {
|
|
|
// Query current states to provide detailed logging
|
|
// Query current states to provide detailed logging
|
|
|
- let current_states = get_current_states(&self.inner, ys).await?;
|
|
|
|
|
|
|
+ let current_states = get_current_states(&self.inner, ys, true).await?;
|
|
|
|
|
|
|
|
let missing_count = ys.len() - current_states.len();
|
|
let missing_count = ys.len() - current_states.len();
|
|
|
let spent_count = current_states
|
|
let spent_count = current_states
|
|
@@ -315,6 +288,7 @@ where
|
|
|
proof
|
|
proof
|
|
|
WHERE
|
|
WHERE
|
|
|
quote_id = :quote_id
|
|
quote_id = :quote_id
|
|
|
|
|
+ FOR UPDATE
|
|
|
"#,
|
|
"#,
|
|
|
)?
|
|
)?
|
|
|
.bind("quote_id", quote_id.to_string())
|
|
.bind("quote_id", quote_id.to_string())
|
|
@@ -353,6 +327,15 @@ where
|
|
|
})
|
|
})
|
|
|
.collect::<Result<Vec<_>, _>>()?)
|
|
.collect::<Result<Vec<_>, _>>()?)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ async fn get_proofs_states(
|
|
|
|
|
+ &mut self,
|
|
|
|
|
+ ys: &[PublicKey],
|
|
|
|
|
+ ) -> Result<Vec<Option<State>>, Self::Err> {
|
|
|
|
|
+ let mut current_states = get_current_states(&self.inner, ys, true).await?;
|
|
|
|
|
+
|
|
|
|
|
+ Ok(ys.iter().map(|y| current_states.remove(y)).collect())
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[async_trait]
|
|
#[async_trait]
|
|
@@ -391,6 +374,37 @@ where
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
#[inline(always)]
|
|
|
|
|
+async fn get_current_states<C>(
|
|
|
|
|
+ conn: &C,
|
|
|
|
|
+ ys: &[PublicKey],
|
|
|
|
|
+ for_update: bool,
|
|
|
|
|
+) -> Result<HashMap<PublicKey, State>, Error>
|
|
|
|
|
+where
|
|
|
|
|
+ C: DatabaseExecutor + Send + Sync,
|
|
|
|
|
+{
|
|
|
|
|
+ if ys.is_empty() {
|
|
|
|
|
+ return Ok(Default::default());
|
|
|
|
|
+ }
|
|
|
|
|
+ let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
|
|
|
|
|
+
|
|
|
|
|
+ query(&format!(
|
|
|
|
|
+ r#"SELECT y, state FROM proof WHERE y IN (:ys) {}"#,
|
|
|
|
|
+ for_update_clause
|
|
|
|
|
+ ))?
|
|
|
|
|
+ .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())
|
|
|
|
|
+ .fetch_all(conn)
|
|
|
|
|
+ .await?
|
|
|
|
|
+ .into_iter()
|
|
|
|
|
+ .map(|row| {
|
|
|
|
|
+ Ok((
|
|
|
|
|
+ column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice),
|
|
|
|
|
+ column_as_string!(&row[1], State::from_str),
|
|
|
|
|
+ ))
|
|
|
|
|
+ })
|
|
|
|
|
+ .collect::<Result<HashMap<_, _>, _>>()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[inline(always)]
|
|
|
async fn get_mint_quote_payments<C>(
|
|
async fn get_mint_quote_payments<C>(
|
|
|
conn: &C,
|
|
conn: &C,
|
|
|
quote_id: &QuoteId,
|
|
quote_id: &QuoteId,
|
|
@@ -642,6 +656,52 @@ where
|
|
|
.transpose()
|
|
.transpose()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+#[inline]
|
|
|
|
|
+async fn get_melt_quotes_by_request_lookup_id_inner<T>(
|
|
|
|
|
+ executor: &T,
|
|
|
|
|
+ request_lookup_id: &PaymentIdentifier,
|
|
|
|
|
+ for_update: bool,
|
|
|
|
|
+) -> Result<Vec<mint::MeltQuote>, Error>
|
|
|
|
|
+where
|
|
|
|
|
+ T: DatabaseExecutor,
|
|
|
|
|
+{
|
|
|
|
|
+ let for_update_clause = if for_update { "FOR UPDATE" } else { "" };
|
|
|
|
|
+ let query_str = format!(
|
|
|
|
|
+ r#"
|
|
|
|
|
+ SELECT
|
|
|
|
|
+ id,
|
|
|
|
|
+ unit,
|
|
|
|
|
+ amount,
|
|
|
|
|
+ request,
|
|
|
|
|
+ fee_reserve,
|
|
|
|
|
+ expiry,
|
|
|
|
|
+ state,
|
|
|
|
|
+ payment_preimage,
|
|
|
|
|
+ request_lookup_id,
|
|
|
|
|
+ created_time,
|
|
|
|
|
+ paid_time,
|
|
|
|
|
+ payment_method,
|
|
|
|
|
+ options,
|
|
|
|
|
+ request_lookup_id_kind
|
|
|
|
|
+ FROM
|
|
|
|
|
+ melt_quote
|
|
|
|
|
+ WHERE
|
|
|
|
|
+ request_lookup_id = :request_lookup_id
|
|
|
|
|
+ AND request_lookup_id_kind = :request_lookup_id_kind
|
|
|
|
|
+ {for_update_clause}
|
|
|
|
|
+ "#
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ query(&query_str)?
|
|
|
|
|
+ .bind("request_lookup_id", request_lookup_id.to_string())
|
|
|
|
|
+ .bind("request_lookup_id_kind", request_lookup_id.kind())
|
|
|
|
|
+ .fetch_all(executor)
|
|
|
|
|
+ .await?
|
|
|
|
|
+ .into_iter()
|
|
|
|
|
+ .map(sql_row_to_melt_quote)
|
|
|
|
|
+ .collect::<Result<Vec<_>, _>>()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
#[async_trait]
|
|
#[async_trait]
|
|
|
impl<RM> MintKeyDatabaseTransaction<'_, Error> for SQLTransaction<RM>
|
|
impl<RM> MintKeyDatabaseTransaction<'_, Error> for SQLTransaction<RM>
|
|
|
where
|
|
where
|
|
@@ -998,192 +1058,82 @@ where
|
|
|
Ok(())
|
|
Ok(())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- #[instrument(skip(self))]
|
|
|
|
|
- async fn increment_mint_quote_amount_paid(
|
|
|
|
|
|
|
+ async fn update_mint_quote(
|
|
|
&mut self,
|
|
&mut self,
|
|
|
- quote_id: &QuoteId,
|
|
|
|
|
- amount_paid: Amount,
|
|
|
|
|
- payment_id: String,
|
|
|
|
|
- ) -> Result<Amount, Self::Err> {
|
|
|
|
|
- if amount_paid == Amount::ZERO {
|
|
|
|
|
- tracing::warn!("Amount payments of zero amount should not be recorded.");
|
|
|
|
|
- return Err(Error::Duplicate);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Check if payment_id already exists in mint_quote_payments
|
|
|
|
|
- let exists = query(
|
|
|
|
|
- r#"
|
|
|
|
|
- SELECT payment_id
|
|
|
|
|
- FROM mint_quote_payments
|
|
|
|
|
- WHERE payment_id = :payment_id
|
|
|
|
|
- FOR UPDATE
|
|
|
|
|
- "#,
|
|
|
|
|
- )?
|
|
|
|
|
- .bind("payment_id", payment_id.clone())
|
|
|
|
|
- .fetch_one(&self.inner)
|
|
|
|
|
- .await?;
|
|
|
|
|
-
|
|
|
|
|
- if exists.is_some() {
|
|
|
|
|
- tracing::error!("Payment ID already exists: {}", payment_id);
|
|
|
|
|
- return Err(database::Error::Duplicate);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Get current amount_paid from quote
|
|
|
|
|
- let current_amount = query(
|
|
|
|
|
- r#"
|
|
|
|
|
- SELECT amount_paid
|
|
|
|
|
- FROM mint_quote
|
|
|
|
|
- WHERE id = :quote_id
|
|
|
|
|
- FOR UPDATE
|
|
|
|
|
- "#,
|
|
|
|
|
- )?
|
|
|
|
|
- .bind("quote_id", quote_id.to_string())
|
|
|
|
|
- .fetch_one(&self.inner)
|
|
|
|
|
- .await
|
|
|
|
|
- .inspect_err(|err| {
|
|
|
|
|
- tracing::error!("SQLite could not get mint quote amount_paid: {}", err);
|
|
|
|
|
- })?;
|
|
|
|
|
-
|
|
|
|
|
- let current_amount_paid = if let Some(current_amount) = current_amount {
|
|
|
|
|
- let amount: u64 = column_as_number!(current_amount[0].clone());
|
|
|
|
|
- Amount::from(amount)
|
|
|
|
|
|
|
+ quote: &mut Acquired<mint::MintQuote>,
|
|
|
|
|
+ ) -> Result<(), Self::Err> {
|
|
|
|
|
+ let mut changes = if let Some(changes) = quote.take_changes() {
|
|
|
|
|
+ changes
|
|
|
} else {
|
|
} else {
|
|
|
- Amount::ZERO
|
|
|
|
|
|
|
+ return Ok(());
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- // Calculate new amount_paid with overflow check
|
|
|
|
|
- let new_amount_paid = current_amount_paid
|
|
|
|
|
- .checked_add(amount_paid)
|
|
|
|
|
- .ok_or_else(|| database::Error::AmountOverflow)?;
|
|
|
|
|
-
|
|
|
|
|
- tracing::debug!(
|
|
|
|
|
- "Mint quote {} amount paid was {} is now {}.",
|
|
|
|
|
- quote_id,
|
|
|
|
|
- current_amount_paid,
|
|
|
|
|
- new_amount_paid
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- // Update the amount_paid
|
|
|
|
|
- query(
|
|
|
|
|
- r#"
|
|
|
|
|
- UPDATE mint_quote
|
|
|
|
|
- SET amount_paid = :amount_paid
|
|
|
|
|
- WHERE id = :quote_id
|
|
|
|
|
- "#,
|
|
|
|
|
- )?
|
|
|
|
|
- .bind("amount_paid", new_amount_paid.to_i64())
|
|
|
|
|
- .bind("quote_id", quote_id.to_string())
|
|
|
|
|
- .execute(&self.inner)
|
|
|
|
|
- .await
|
|
|
|
|
- .inspect_err(|err| {
|
|
|
|
|
- tracing::error!("SQLite could not update mint quote amount_paid: {}", err);
|
|
|
|
|
- })?;
|
|
|
|
|
-
|
|
|
|
|
- // Add payment_id to mint_quote_payments table
|
|
|
|
|
- query(
|
|
|
|
|
- r#"
|
|
|
|
|
- INSERT INTO mint_quote_payments
|
|
|
|
|
- (quote_id, payment_id, amount, timestamp)
|
|
|
|
|
- VALUES (:quote_id, :payment_id, :amount, :timestamp)
|
|
|
|
|
- "#,
|
|
|
|
|
- )?
|
|
|
|
|
- .bind("quote_id", quote_id.to_string())
|
|
|
|
|
- .bind("payment_id", payment_id)
|
|
|
|
|
- .bind("amount", amount_paid.to_i64())
|
|
|
|
|
- .bind("timestamp", unix_time() as i64)
|
|
|
|
|
- .execute(&self.inner)
|
|
|
|
|
- .await
|
|
|
|
|
- .map_err(|err| {
|
|
|
|
|
- tracing::error!("SQLite could not insert payment ID: {}", err);
|
|
|
|
|
- err
|
|
|
|
|
- })?;
|
|
|
|
|
-
|
|
|
|
|
- Ok(new_amount_paid)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- #[instrument(skip_all)]
|
|
|
|
|
- async fn increment_mint_quote_amount_issued(
|
|
|
|
|
- &mut self,
|
|
|
|
|
- quote_id: &QuoteId,
|
|
|
|
|
- amount_issued: Amount,
|
|
|
|
|
- ) -> Result<Amount, Self::Err> {
|
|
|
|
|
- // Get current amount_issued from quote
|
|
|
|
|
- let current_amounts = query(
|
|
|
|
|
- r#"
|
|
|
|
|
- SELECT amount_issued, amount_paid
|
|
|
|
|
- FROM mint_quote
|
|
|
|
|
- WHERE id = :quote_id
|
|
|
|
|
- FOR UPDATE
|
|
|
|
|
- "#,
|
|
|
|
|
- )?
|
|
|
|
|
- .bind("quote_id", quote_id.to_string())
|
|
|
|
|
- .fetch_one(&self.inner)
|
|
|
|
|
- .await
|
|
|
|
|
- .inspect_err(|err| {
|
|
|
|
|
- tracing::error!("SQLite could not get mint quote amount_issued: {}", err);
|
|
|
|
|
- })?
|
|
|
|
|
- .ok_or(Error::QuoteNotFound)?;
|
|
|
|
|
-
|
|
|
|
|
- let new_amount_issued = {
|
|
|
|
|
- // Make sure the db protects issuing not paid quotes
|
|
|
|
|
- unpack_into!(
|
|
|
|
|
- let (current_amount_issued, current_amount_paid) = current_amounts
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- let current_amount_issued: u64 = column_as_number!(current_amount_issued);
|
|
|
|
|
- let current_amount_paid: u64 = column_as_number!(current_amount_paid);
|
|
|
|
|
-
|
|
|
|
|
- let current_amount_issued = Amount::from(current_amount_issued);
|
|
|
|
|
- let current_amount_paid = Amount::from(current_amount_paid);
|
|
|
|
|
|
|
+ if changes.issuances.is_none() && changes.payments.is_none() {
|
|
|
|
|
+ return Ok(());
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Calculate new amount_issued with overflow check
|
|
|
|
|
- let new_amount_issued = current_amount_issued
|
|
|
|
|
- .checked_add(amount_issued)
|
|
|
|
|
- .ok_or_else(|| database::Error::AmountOverflow)?;
|
|
|
|
|
|
|
+ for payment in changes.payments.take().unwrap_or_default() {
|
|
|
|
|
+ query(
|
|
|
|
|
+ r#"
|
|
|
|
|
+ INSERT INTO mint_quote_payments
|
|
|
|
|
+ (quote_id, payment_id, amount, timestamp)
|
|
|
|
|
+ VALUES (:quote_id, :payment_id, :amount, :timestamp)
|
|
|
|
|
+ "#,
|
|
|
|
|
+ )?
|
|
|
|
|
+ .bind("quote_id", quote.id.to_string())
|
|
|
|
|
+ .bind("payment_id", payment.payment_id)
|
|
|
|
|
+ .bind("amount", payment.amount.to_i64())
|
|
|
|
|
+ .bind("timestamp", payment.time as i64)
|
|
|
|
|
+ .execute(&self.inner)
|
|
|
|
|
+ .await
|
|
|
|
|
+ .map_err(|err| {
|
|
|
|
|
+ tracing::error!("SQLite could not insert payment ID: {}", err);
|
|
|
|
|
+ err
|
|
|
|
|
+ })?;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- current_amount_paid
|
|
|
|
|
- .checked_sub(new_amount_issued)
|
|
|
|
|
- .ok_or(Error::Internal("Over-issued not allowed".to_owned()))?;
|
|
|
|
|
|
|
+ let current_time = unix_time();
|
|
|
|
|
|
|
|
- new_amount_issued
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ for amount_issued in changes.issuances.take().unwrap_or_default() {
|
|
|
|
|
+ query(
|
|
|
|
|
+ r#"
|
|
|
|
|
+ INSERT INTO mint_quote_issued
|
|
|
|
|
+ (quote_id, amount, timestamp)
|
|
|
|
|
+ VALUES (:quote_id, :amount, :timestamp);
|
|
|
|
|
+ "#,
|
|
|
|
|
+ )?
|
|
|
|
|
+ .bind("quote_id", quote.id.to_string())
|
|
|
|
|
+ .bind("amount", amount_issued.to_i64())
|
|
|
|
|
+ .bind("timestamp", current_time as i64)
|
|
|
|
|
+ .execute(&self.inner)
|
|
|
|
|
+ .await?;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Update the amount_issued
|
|
|
|
|
query(
|
|
query(
|
|
|
r#"
|
|
r#"
|
|
|
- UPDATE mint_quote
|
|
|
|
|
- SET amount_issued = :amount_issued
|
|
|
|
|
- WHERE id = :quote_id
|
|
|
|
|
|
|
+ UPDATE
|
|
|
|
|
+ mint_quote
|
|
|
|
|
+ SET
|
|
|
|
|
+ amount_issued = :amount_issued,
|
|
|
|
|
+ amount_paid = :amount_paid
|
|
|
|
|
+ WHERE
|
|
|
|
|
+ id = :quote_id
|
|
|
"#,
|
|
"#,
|
|
|
)?
|
|
)?
|
|
|
- .bind("amount_issued", new_amount_issued.to_i64())
|
|
|
|
|
- .bind("quote_id", quote_id.to_string())
|
|
|
|
|
|
|
+ .bind("quote_id", quote.id.to_string())
|
|
|
|
|
+ .bind("amount_issued", quote.amount_issued().to_i64())
|
|
|
|
|
+ .bind("amount_paid", quote.amount_paid().to_i64())
|
|
|
.execute(&self.inner)
|
|
.execute(&self.inner)
|
|
|
.await
|
|
.await
|
|
|
.inspect_err(|err| {
|
|
.inspect_err(|err| {
|
|
|
- tracing::error!("SQLite could not update mint quote amount_issued: {}", err);
|
|
|
|
|
|
|
+ tracing::error!("SQLite could not update mint quote amount_paid: {}", err);
|
|
|
})?;
|
|
})?;
|
|
|
|
|
|
|
|
- let current_time = unix_time();
|
|
|
|
|
-
|
|
|
|
|
- query(
|
|
|
|
|
- r#"
|
|
|
|
|
-INSERT INTO mint_quote_issued
|
|
|
|
|
-(quote_id, amount, timestamp)
|
|
|
|
|
-VALUES (:quote_id, :amount, :timestamp);
|
|
|
|
|
- "#,
|
|
|
|
|
- )?
|
|
|
|
|
- .bind("quote_id", quote_id.to_string())
|
|
|
|
|
- .bind("amount", amount_issued.to_i64())
|
|
|
|
|
- .bind("timestamp", current_time as i64)
|
|
|
|
|
- .execute(&self.inner)
|
|
|
|
|
- .await?;
|
|
|
|
|
-
|
|
|
|
|
- Ok(new_amount_issued)
|
|
|
|
|
|
|
+ Ok(())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#[instrument(skip_all)]
|
|
#[instrument(skip_all)]
|
|
|
- async fn add_mint_quote(&mut self, quote: MintQuote) -> Result<(), Self::Err> {
|
|
|
|
|
|
|
+ async fn add_mint_quote(&mut self, quote: MintQuote) -> Result<Acquired<MintQuote>, Self::Err> {
|
|
|
query(
|
|
query(
|
|
|
r#"
|
|
r#"
|
|
|
INSERT INTO mint_quote (
|
|
INSERT INTO mint_quote (
|
|
@@ -1197,7 +1147,7 @@ VALUES (:quote_id, :amount, :timestamp);
|
|
|
.bind("id", quote.id.to_string())
|
|
.bind("id", quote.id.to_string())
|
|
|
.bind("amount", quote.amount.map(|a| a.to_i64()))
|
|
.bind("amount", quote.amount.map(|a| a.to_i64()))
|
|
|
.bind("unit", quote.unit.to_string())
|
|
.bind("unit", quote.unit.to_string())
|
|
|
- .bind("request", quote.request)
|
|
|
|
|
|
|
+ .bind("request", quote.request.clone())
|
|
|
.bind("expiry", quote.expiry as i64)
|
|
.bind("expiry", quote.expiry as i64)
|
|
|
.bind(
|
|
.bind(
|
|
|
"request_lookup_id",
|
|
"request_lookup_id",
|
|
@@ -1210,7 +1160,7 @@ VALUES (:quote_id, :amount, :timestamp);
|
|
|
.execute(&self.inner)
|
|
.execute(&self.inner)
|
|
|
.await?;
|
|
.await?;
|
|
|
|
|
|
|
|
- Ok(())
|
|
|
|
|
|
|
+ Ok(quote.into())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
|
|
async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
|
|
@@ -1262,110 +1212,44 @@ VALUES (:quote_id, :amount, :timestamp);
|
|
|
|
|
|
|
|
async fn update_melt_quote_request_lookup_id(
|
|
async fn update_melt_quote_request_lookup_id(
|
|
|
&mut self,
|
|
&mut self,
|
|
|
- quote_id: &QuoteId,
|
|
|
|
|
|
|
+ quote: &mut Acquired<mint::MeltQuote>,
|
|
|
new_request_lookup_id: &PaymentIdentifier,
|
|
new_request_lookup_id: &PaymentIdentifier,
|
|
|
) -> Result<(), Self::Err> {
|
|
) -> Result<(), Self::Err> {
|
|
|
query(r#"UPDATE melt_quote SET request_lookup_id = :new_req_id, request_lookup_id_kind = :new_kind WHERE id = :id"#)?
|
|
query(r#"UPDATE melt_quote SET request_lookup_id = :new_req_id, request_lookup_id_kind = :new_kind WHERE id = :id"#)?
|
|
|
.bind("new_req_id", new_request_lookup_id.to_string())
|
|
.bind("new_req_id", new_request_lookup_id.to_string())
|
|
|
- .bind("new_kind",new_request_lookup_id.kind() )
|
|
|
|
|
- .bind("id", quote_id.to_string())
|
|
|
|
|
|
|
+ .bind("new_kind", new_request_lookup_id.kind())
|
|
|
|
|
+ .bind("id", quote.id.to_string())
|
|
|
.execute(&self.inner)
|
|
.execute(&self.inner)
|
|
|
.await?;
|
|
.await?;
|
|
|
|
|
+ quote.request_lookup_id = Some(new_request_lookup_id.clone());
|
|
|
Ok(())
|
|
Ok(())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async fn update_melt_quote_state(
|
|
async fn update_melt_quote_state(
|
|
|
&mut self,
|
|
&mut self,
|
|
|
- quote_id: &QuoteId,
|
|
|
|
|
|
|
+ quote: &mut Acquired<mint::MeltQuote>,
|
|
|
state: MeltQuoteState,
|
|
state: MeltQuoteState,
|
|
|
payment_proof: Option<String>,
|
|
payment_proof: Option<String>,
|
|
|
- ) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err> {
|
|
|
|
|
- let mut quote = query(
|
|
|
|
|
- r#"
|
|
|
|
|
- SELECT
|
|
|
|
|
- id,
|
|
|
|
|
- unit,
|
|
|
|
|
- amount,
|
|
|
|
|
- request,
|
|
|
|
|
- fee_reserve,
|
|
|
|
|
- expiry,
|
|
|
|
|
- state,
|
|
|
|
|
- payment_preimage,
|
|
|
|
|
- request_lookup_id,
|
|
|
|
|
- created_time,
|
|
|
|
|
- paid_time,
|
|
|
|
|
- payment_method,
|
|
|
|
|
- options,
|
|
|
|
|
- request_lookup_id_kind
|
|
|
|
|
- FROM
|
|
|
|
|
- melt_quote
|
|
|
|
|
- WHERE
|
|
|
|
|
- id=:id
|
|
|
|
|
- "#,
|
|
|
|
|
- )?
|
|
|
|
|
- .bind("id", quote_id.to_string())
|
|
|
|
|
- .fetch_one(&self.inner)
|
|
|
|
|
- .await?
|
|
|
|
|
- .map(sql_row_to_melt_quote)
|
|
|
|
|
- .transpose()?
|
|
|
|
|
- .ok_or(Error::QuoteNotFound)?;
|
|
|
|
|
-
|
|
|
|
|
- check_melt_quote_state_transition(quote.state, state)?;
|
|
|
|
|
|
|
+ ) -> Result<MeltQuoteState, Self::Err> {
|
|
|
|
|
+ let old_state = quote.state;
|
|
|
|
|
|
|
|
- // When transitioning to Pending, lock all quotes with the same lookup_id
|
|
|
|
|
- // and check if any are already pending or paid
|
|
|
|
|
- if state == MeltQuoteState::Pending {
|
|
|
|
|
- if let Some(ref lookup_id) = quote.request_lookup_id {
|
|
|
|
|
- // Lock all quotes with the same lookup_id to prevent race conditions
|
|
|
|
|
- let locked_quotes: Vec<(String, String)> = query(
|
|
|
|
|
- r#"
|
|
|
|
|
- SELECT id, state
|
|
|
|
|
- FROM melt_quote
|
|
|
|
|
- WHERE request_lookup_id = :lookup_id
|
|
|
|
|
- FOR UPDATE
|
|
|
|
|
- "#,
|
|
|
|
|
- )?
|
|
|
|
|
- .bind("lookup_id", lookup_id.to_string())
|
|
|
|
|
- .fetch_all(&self.inner)
|
|
|
|
|
- .await?
|
|
|
|
|
- .into_iter()
|
|
|
|
|
- .map(|row| {
|
|
|
|
|
- unpack_into!(let (id, state) = row);
|
|
|
|
|
- Ok((column_as_string!(id), column_as_string!(state)))
|
|
|
|
|
- })
|
|
|
|
|
- .collect::<Result<Vec<_>, Error>>()?;
|
|
|
|
|
-
|
|
|
|
|
- // Check if any other quote with the same lookup_id is pending or paid
|
|
|
|
|
- let has_conflict = locked_quotes.iter().any(|(id, state)| {
|
|
|
|
|
- id != "e_id.to_string()
|
|
|
|
|
- && (state == &MeltQuoteState::Pending.to_string()
|
|
|
|
|
- || state == &MeltQuoteState::Paid.to_string())
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if has_conflict {
|
|
|
|
|
- tracing::warn!(
|
|
|
|
|
- "Cannot transition quote {} to Pending: another quote with lookup_id {} is already pending or paid",
|
|
|
|
|
- quote_id,
|
|
|
|
|
- lookup_id
|
|
|
|
|
- );
|
|
|
|
|
- return Err(Error::Duplicate);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ check_melt_quote_state_transition(old_state, state)?;
|
|
|
|
|
|
|
|
let rec = if state == MeltQuoteState::Paid {
|
|
let rec = if state == MeltQuoteState::Paid {
|
|
|
let current_time = unix_time();
|
|
let current_time = unix_time();
|
|
|
|
|
+ quote.paid_time = Some(current_time);
|
|
|
|
|
+ quote.payment_preimage = payment_proof.clone();
|
|
|
query(r#"UPDATE melt_quote SET state = :state, paid_time = :paid_time, payment_preimage = :payment_preimage WHERE id = :id"#)?
|
|
query(r#"UPDATE melt_quote SET state = :state, paid_time = :paid_time, payment_preimage = :payment_preimage WHERE id = :id"#)?
|
|
|
.bind("state", state.to_string())
|
|
.bind("state", state.to_string())
|
|
|
.bind("paid_time", current_time as i64)
|
|
.bind("paid_time", current_time as i64)
|
|
|
.bind("payment_preimage", payment_proof)
|
|
.bind("payment_preimage", payment_proof)
|
|
|
- .bind("id", quote_id.to_string())
|
|
|
|
|
|
|
+ .bind("id", quote.id.to_string())
|
|
|
.execute(&self.inner)
|
|
.execute(&self.inner)
|
|
|
.await
|
|
.await
|
|
|
} else {
|
|
} else {
|
|
|
query(r#"UPDATE melt_quote SET state = :state WHERE id = :id"#)?
|
|
query(r#"UPDATE melt_quote SET state = :state WHERE id = :id"#)?
|
|
|
.bind("state", state.to_string())
|
|
.bind("state", state.to_string())
|
|
|
- .bind("id", quote_id.to_string())
|
|
|
|
|
|
|
+ .bind("id", quote.id.to_string())
|
|
|
.execute(&self.inner)
|
|
.execute(&self.inner)
|
|
|
.await
|
|
.await
|
|
|
};
|
|
};
|
|
@@ -1378,39 +1262,58 @@ VALUES (:quote_id, :amount, :timestamp);
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- let old_state = quote.state;
|
|
|
|
|
quote.state = state;
|
|
quote.state = state;
|
|
|
|
|
|
|
|
if state == MeltQuoteState::Unpaid || state == MeltQuoteState::Failed {
|
|
if state == MeltQuoteState::Unpaid || state == MeltQuoteState::Failed {
|
|
|
- self.delete_melt_request(quote_id).await?;
|
|
|
|
|
|
|
+ self.delete_melt_request("e.id).await?;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Ok((old_state, quote))
|
|
|
|
|
|
|
+ Ok(old_state)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- async fn get_mint_quote(&mut self, quote_id: &QuoteId) -> Result<Option<MintQuote>, Self::Err> {
|
|
|
|
|
- get_mint_quote_inner(&self.inner, quote_id, true).await
|
|
|
|
|
|
|
+ async fn get_mint_quote(
|
|
|
|
|
+ &mut self,
|
|
|
|
|
+ quote_id: &QuoteId,
|
|
|
|
|
+ ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
|
|
|
|
|
+ get_mint_quote_inner(&self.inner, quote_id, true)
|
|
|
|
|
+ .await
|
|
|
|
|
+ .map(|quote| quote.map(|inner| inner.into()))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async fn get_melt_quote(
|
|
async fn get_melt_quote(
|
|
|
&mut self,
|
|
&mut self,
|
|
|
quote_id: &QuoteId,
|
|
quote_id: &QuoteId,
|
|
|
- ) -> Result<Option<mint::MeltQuote>, Self::Err> {
|
|
|
|
|
- get_melt_quote_inner(&self.inner, quote_id, true).await
|
|
|
|
|
|
|
+ ) -> Result<Option<Acquired<mint::MeltQuote>>, Self::Err> {
|
|
|
|
|
+ get_melt_quote_inner(&self.inner, quote_id, true)
|
|
|
|
|
+ .await
|
|
|
|
|
+ .map(|quote| quote.map(|inner| inner.into()))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn get_melt_quotes_by_request_lookup_id(
|
|
|
|
|
+ &mut self,
|
|
|
|
|
+ request_lookup_id: &PaymentIdentifier,
|
|
|
|
|
+ ) -> Result<Vec<Acquired<mint::MeltQuote>>, Self::Err> {
|
|
|
|
|
+ get_melt_quotes_by_request_lookup_id_inner(&self.inner, request_lookup_id, true)
|
|
|
|
|
+ .await
|
|
|
|
|
+ .map(|quote| quote.into_iter().map(|inner| inner.into()).collect())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async fn get_mint_quote_by_request(
|
|
async fn get_mint_quote_by_request(
|
|
|
&mut self,
|
|
&mut self,
|
|
|
request: &str,
|
|
request: &str,
|
|
|
- ) -> Result<Option<MintQuote>, Self::Err> {
|
|
|
|
|
- get_mint_quote_by_request_inner(&self.inner, request, true).await
|
|
|
|
|
|
|
+ ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
|
|
|
|
|
+ get_mint_quote_by_request_inner(&self.inner, request, true)
|
|
|
|
|
+ .await
|
|
|
|
|
+ .map(|quote| quote.map(|inner| inner.into()))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async fn get_mint_quote_by_request_lookup_id(
|
|
async fn get_mint_quote_by_request_lookup_id(
|
|
|
&mut self,
|
|
&mut self,
|
|
|
request_lookup_id: &PaymentIdentifier,
|
|
request_lookup_id: &PaymentIdentifier,
|
|
|
- ) -> Result<Option<MintQuote>, Self::Err> {
|
|
|
|
|
- get_mint_quote_by_request_lookup_id_inner(&self.inner, request_lookup_id, true).await
|
|
|
|
|
|
|
+ ) -> Result<Option<Acquired<MintQuote>>, Self::Err> {
|
|
|
|
|
+ get_mint_quote_by_request_lookup_id_inner(&self.inner, request_lookup_id, true)
|
|
|
|
|
+ .await
|
|
|
|
|
+ .map(|quote| quote.map(|inner| inner.into()))
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1633,7 +1536,7 @@ where
|
|
|
|
|
|
|
|
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
|
|
async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
|
|
|
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
|
|
let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
|
|
|
- let mut current_states = get_current_states(&*conn, ys).await?;
|
|
|
|
|
|
|
+ let mut current_states = get_current_states(&*conn, ys, false).await?;
|
|
|
|
|
|
|
|
Ok(ys.iter().map(|y| current_states.remove(y)).collect())
|
|
Ok(ys.iter().map(|y| current_states.remove(y)).collect())
|
|
|
}
|
|
}
|