Procházet zdrojové kódy

Do not call the signatory while having an outgoing database transaction

Cesar Rodas před 2 měsíci
rodič
revize
6ec8c43811

+ 1 - 0
crates/cdk-integration-tests/tests/fake_wallet.rs

@@ -1204,6 +1204,7 @@ async fn test_fake_mint_duplicate_proofs_swap() {
 
     let blinded_message = pre_mint.blinded_messages();
 
+    let inputs = vec![proofs[0].clone()];
     let outputs = vec![blinded_message[0].clone(), blinded_message[0].clone()];
 
     let swap_request = SwapRequest::new(inputs, outputs);

+ 0 - 2
crates/cdk-sql-common/src/pool.rs

@@ -170,7 +170,6 @@ where
         loop {
             if let Some((stale, resource)) = resources.pop() {
                 if !stale.load(Ordering::SeqCst) {
-                    tracing::warn!("Got resource");
                     drop(resources);
                     self.in_use.fetch_add(1, Ordering::AcqRel);
 
@@ -182,7 +181,6 @@ where
             }
 
             if self.in_use.load(Ordering::Relaxed) < self.max_size {
-                tracing::warn!("Got resource");
                 drop(resources);
                 self.in_use.fetch_add(1, Ordering::AcqRel);
                 let stale: Arc<AtomicBool> = Arc::new(false.into());

+ 6 - 7
crates/cdk/src/mint/issue/mod.rs

@@ -496,6 +496,12 @@ impl Mint {
 
         self.check_mint_quote_paid(&mut mint_quote).await?;
 
+        // get the blind signatures before having starting the db transaction, if there are any
+        // rollbacks this blind_signatures will be lost, and the signature is stateless. It is not a
+        // good idea to call an external service (which is really a trait, it could be anything
+        // anywhere) while keeping a database transaction on-going
+        let blind_signatures = self.blind_sign(mint_request.outputs.clone()).await?;
+
         let mut tx = self.localstore.begin_transaction().await?;
 
         let mint_quote = tx
@@ -568,13 +574,6 @@ impl Mint {
         let unit = unit.ok_or(Error::UnsupportedUnit).unwrap();
         ensure_cdk!(unit == mint_quote.unit, Error::UnsupportedUnit);
 
-        let mut blind_signatures = Vec::with_capacity(mint_request.outputs.len());
-
-        for blinded_message in mint_request.outputs.iter() {
-            let blind_signature = self.blind_sign(blinded_message.clone()).await?;
-            blind_signatures.push(blind_signature);
-        }
-
         tx.add_blind_signatures(
             &mint_request
                 .outputs

+ 20 - 9
crates/cdk/src/mint/melt.rs

@@ -448,6 +448,7 @@ impl Mint {
     pub async fn verify_melt_request(
         &self,
         tx: &mut Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
+        input_verification: Verification,
         melt_request: &MeltRequest<Uuid>,
     ) -> Result<(ProofWriter, MeltQuote), Error> {
         let (state, quote) = tx
@@ -467,7 +468,7 @@ impl Mint {
         let Verification {
             amount: input_amount,
             unit: input_unit,
-        } = self.verify_inputs(melt_request.inputs()).await?;
+        } = input_verification;
 
         ensure_cdk!(input_unit.is_some(), Error::UnsupportedUnit);
 
@@ -545,10 +546,12 @@ impl Mint {
             }
         }
 
+        let verification = self.verify_inputs(melt_request.inputs()).await?;
+
         let mut tx = self.localstore.begin_transaction().await?;
 
         let (proof_writer, quote) = self
-            .verify_melt_request(&mut tx, melt_request)
+            .verify_melt_request(&mut tx, verification, melt_request)
             .await
             .map_err(|err| {
                 tracing::debug!("Error attempting to verify melt quote: {}", err);
@@ -785,7 +788,6 @@ impl Mint {
                 let change_target = melt_request.inputs_amount()? - total_spent - fee;
 
                 let mut amounts = change_target.split();
-                let mut change_sigs = Vec::with_capacity(amounts.len());
 
                 if outputs.len().lt(&amounts.len()) {
                     tracing::debug!(
@@ -801,14 +803,20 @@ impl Mint {
                 }
 
                 let mut outputs = outputs;
+                let mut blinded_messages = vec![];
 
                 for (amount, blinded_message) in amounts.iter().zip(&mut outputs) {
                     blinded_message.amount = *amount;
-
-                    let blinded_signature = self.blind_sign(blinded_message.clone()).await?;
-                    change_sigs.push(blinded_signature)
+                    blinded_messages.push(blinded_message.clone());
                 }
 
+                // commit db transaction before calling the signatory
+                tx.commit().await?;
+
+                let change_sigs = self.blind_sign(blinded_messages).await?;
+
+                let mut tx = self.localstore.begin_transaction().await?;
+
                 tx.add_blind_signatures(
                     &outputs[0..change_sigs.len()]
                         .iter()
@@ -820,6 +828,12 @@ impl Mint {
                 .await?;
 
                 change = Some(change_sigs);
+
+                proof_writer.commit();
+                tx.commit().await?;
+            } else {
+                proof_writer.commit();
+                tx.commit().await?;
             }
         }
 
@@ -830,9 +844,6 @@ impl Mint {
             MeltQuoteState::Paid,
         );
 
-        proof_writer.commit();
-        tx.commit().await?;
-
         Ok(MeltQuoteBolt11Response {
             amount: quote.amount,
             paid: Some(true),

+ 4 - 8
crates/cdk/src/mint/mod.rs

@@ -201,7 +201,7 @@ impl Mint {
     /// - Invoice payment monitoring across all configured payment processors
     ///
     /// Future services may include:
-    /// - Quote cleanup and expiration management  
+    /// - Quote cleanup and expiration management
     /// - Periodic database maintenance
     /// - Health check monitoring
     /// - Metrics collection
@@ -635,13 +635,9 @@ impl Mint {
     #[tracing::instrument(skip_all)]
     pub async fn blind_sign(
         &self,
-        blinded_message: BlindedMessage,
-    ) -> Result<BlindSignature, Error> {
-        self.signatory
-            .blind_sign(vec![blinded_message])
-            .await?
-            .pop()
-            .ok_or(Error::Internal)
+        blinded_messages: Vec<BlindedMessage>,
+    ) -> Result<Vec<BlindSignature>, Error> {
+        Ok(self.signatory.blind_sign(blinded_messages).await?)
     }
 
     /// Verify [`Proof`] meets conditions and is signed

+ 16 - 8
crates/cdk/src/mint/swap.rs

@@ -12,10 +12,25 @@ impl Mint {
         &self,
         swap_request: SwapRequest,
     ) -> Result<SwapResponse, Error> {
+        // Do the external call before beginning the db transaction
+        let promises = self.blind_sign(swap_request.outputs().to_owned()).await?;
+        let input_verification =
+            self.verify_inputs(swap_request.inputs())
+                .await
+                .map_err(|err| {
+                    tracing::debug!("Input verification failed: {:?}", err);
+                    err
+                })?;
+
         let mut tx = self.localstore.begin_transaction().await?;
 
         if let Err(err) = self
-            .verify_transaction_balanced(&mut tx, swap_request.inputs(), swap_request.outputs())
+            .verify_transaction_balanced(
+                &mut tx,
+                input_verification,
+                swap_request.inputs(),
+                swap_request.outputs(),
+            )
             .await
         {
             tracing::debug!("Attempt to swap unbalanced transaction, aborting: {err}");
@@ -30,13 +45,6 @@ impl Mint {
             .add_proofs(&mut tx, swap_request.inputs())
             .await?;
 
-        let mut promises = Vec::with_capacity(swap_request.outputs().len());
-
-        for blinded_message in swap_request.outputs() {
-            let blinded_signature = self.blind_sign(blinded_message.clone()).await?;
-            promises.push(blinded_signature);
-        }
-
         proof_writer
             .update_proofs_states(&mut tx, &input_ys, State::Spent)
             .await?;

+ 3 - 9
crates/cdk/src/mint/verification.rs

@@ -58,10 +58,7 @@ impl Mint {
     ///
     /// Checks that the outputs are all of the same unit and the keyset is active
     #[instrument(skip_all)]
-    pub async fn verify_outputs_keyset(
-        &self,
-        outputs: &[BlindedMessage],
-    ) -> Result<CurrencyUnit, Error> {
+    pub fn verify_outputs_keyset(&self, outputs: &[BlindedMessage]) -> Result<CurrencyUnit, Error> {
         let mut keyset_units = HashSet::new();
 
         let output_keyset_ids: HashSet<Id> = outputs.iter().map(|p| p.keyset_id).collect();
@@ -189,7 +186,7 @@ impl Mint {
         Mint::check_outputs_unique(outputs)?;
         self.check_output_already_signed(tx, outputs).await?;
 
-        let unit = self.verify_outputs_keyset(outputs).await?;
+        let unit = self.verify_outputs_keyset(outputs)?;
 
         let amount = Amount::try_sum(outputs.iter().map(|o| o.amount).collect::<Vec<Amount>>())?;
 
@@ -221,6 +218,7 @@ impl Mint {
     pub async fn verify_transaction_balanced(
         &self,
         tx: &mut Box<dyn cdk_database::MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
+        input_verification: Verification,
         inputs: &Proofs,
         outputs: &[BlindedMessage],
     ) -> Result<(), Error> {
@@ -228,10 +226,6 @@ impl Mint {
             tracing::debug!("Output verification failed: {:?}", err);
             err
         })?;
-        let input_verification = self.verify_inputs(inputs).await.map_err(|err| {
-            tracing::debug!("Input verification failed: {:?}", err);
-            err
-        })?;
 
         if output_verification.unit != input_verification.unit {
             tracing::debug!(

+ 18 - 3
justfile

@@ -79,8 +79,18 @@ test-all db="memory":
     #!/usr/bin/env bash
     just test {{db}}
     ./misc/itests.sh "{{db}}"
+    status=$?
+    if [ $status -ne 0 ]; then
+       echo "Failed test with status {$status}"
+       exit $status
+    fi
     ./misc/fake_itests.sh "{{db}}" external_signatory
-    ./misc/fake_itests.sh "{{db}}"
+    status=$?
+    if [ $status -ne 0 ]; then
+       echo "Failed test with status {$status}"
+       exit $status
+    fi
+    exit ./misc/fake_itests.sh "{{db}}"
     
 test-nutshell:
   #!/usr/bin/env bash
@@ -149,13 +159,18 @@ goose-changelog-commits *COMMITS="5":
 
 itest db:
   #!/usr/bin/env bash
-  ./misc/itests.sh "{{db}}"
+  exit ./misc/itests.sh "{{db}}"
 
   
 fake-mint-itest db:
   #!/usr/bin/env bash
   ./misc/fake_itests.sh "{{db}}" external_signatory
-  ./misc/fake_itests.sh "{{db}}"
+  status=$?
+  if [ $status -ne 0 ]; then
+     echo "Failed test with status {$status}"
+     exit $status
+  fi
+  exit ./misc/fake_itests.sh "{{db}}"
 
   
 itest-payment-processor ln: