Przeglądaj źródła

feat(database): add get_keys method to DatabaseTransaction trait

Adds `get_keys` method to the `DatabaseTransaction` trait to enable reading
keyset keys within active database transactions. This prevents database
contention issues when checking for existing keys during transaction execution.

Implementations added for:
- cdk-sql-common: Queries key table within transaction context
- cdk-redb: Reads from MINT_KEYS_TABLE using transaction handle
- cdk-ffi: Bridges FFI database to CDK transaction interface with proper type
  conversions

This change allows mint_metadata_cache to check for existing keys using the
transaction handle (`tx.get_keys`) instead of the storage handle
(`storage.get_keys`), avoiding concurrent database access conflicts during
keyset synchronization.
Cesar Rodas 2 miesięcy temu
rodzic
commit
74aeaa2b3f

+ 3 - 0
crates/cdk-common/src/database/wallet.rs

@@ -46,6 +46,9 @@ pub trait DatabaseTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
     /// Get mint keyset by id
     async fn get_keyset_by_id(&mut self, keyset_id: &Id) -> Result<Option<KeySetInfo>, Error>;
 
+    /// Get [`Keys`] from storage
+    async fn get_keys(&mut self, id: &Id) -> Result<Option<Keys>, Self::Err>;
+
     /// Add mint keyset to storage
     async fn add_mint_keysets(
         &mut self,

+ 18 - 0
crates/cdk-ffi/src/database.rs

@@ -623,6 +623,24 @@ impl<'a> WalletDatabaseTransaction<'a, cdk::cdk_database::Error>
         Ok(result.map(Into::into))
     }
 
+    async fn get_keys(
+        &mut self,
+        id: &cdk::nuts::Id,
+    ) -> Result<Option<cdk::nuts::Keys>, cdk::cdk_database::Error> {
+        let ffi_id = (*id).into();
+        let result = self
+            .ffi_db
+            .get_keys(ffi_id)
+            .await
+            .map_err(|e| cdk::cdk_database::Error::Database(e.to_string().into()))?;
+        match result {
+            Some(keys) => Ok(Some(keys.try_into().map_err(|e: FfiError| {
+                cdk::cdk_database::Error::Database(e.to_string().into())
+            })?)),
+            None => Ok(None),
+        }
+    }
+
     async fn get_mint_quote(
         &mut self,
         quote_id: &str,

+ 1 - 3
crates/cdk-integration-tests/tests/fake_auth.rs

@@ -516,9 +516,7 @@ async fn test_reuse_auth_proof() {
     }
 
     let mut tx = wallet.localstore.begin_db_transaction().await.unwrap();
-    tx.update_proofs(vec![], proofs.iter().map(|p| p.y).collect())
-        .await
-        .unwrap();
+    tx.update_proofs(proofs, vec![]).await.unwrap();
     tx.commit().await.unwrap();
 
     {

+ 15 - 0
crates/cdk-redb/src/wallet/mod.rs

@@ -509,6 +509,21 @@ impl<'a> cdk_common::database::WalletDatabaseTransaction<'a, database::Error>
         result
     }
 
+    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
+    async fn get_keys(&mut self, keyset_id: &Id) -> Result<Option<Keys>, database::Error> {
+        let txn = self.txn().map_err(Into::<database::Error>::into)?;
+        let table = txn.open_table(MINT_KEYS_TABLE).map_err(Error::from)?;
+
+        if let Some(mint_info) = table
+            .get(keyset_id.to_string().as_str())
+            .map_err(Error::from)?
+        {
+            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
+        }
+
+        Ok(None)
+    }
+
     #[instrument(skip(self))]
     async fn add_mint(
         &mut self,

+ 21 - 1
crates/cdk-sql-common/src/wallet/mod.rs

@@ -567,6 +567,26 @@ where
         .transpose()?)
     }
 
+    #[instrument(skip(self), fields(keyset_id = %id))]
+    async fn get_keys(&mut self, id: &Id) -> Result<Option<Keys>, Error> {
+        Ok(query(
+            r#"
+            SELECT
+                keys
+            FROM key
+            WHERE id = :id
+            "#,
+        )?
+        .bind("id", id.to_string())
+        .pluck(&self.inner)
+        .await?
+        .map(|keys| {
+            let keys = column_as_string!(keys);
+            serde_json::from_str(&keys).map_err(Error::from)
+        })
+        .transpose()?)
+    }
+
     #[instrument(skip(self))]
     async fn get_mint_quote(&mut self, quote_id: &str) -> Result<Option<MintQuote>, Error> {
         Ok(query(
@@ -994,7 +1014,7 @@ where
                 request,
                 state,
                 expiry,
-                secret_key
+                secret_key,
                 payment_method,
                 amount_issued,
                 amount_paid

+ 5 - 1
crates/cdk-sqlite/src/wallet/mod.rs

@@ -36,10 +36,14 @@ mod tests {
         let mint_info = MintInfo::new().description("test");
         let mint_url = MintUrl::from_str("https://mint.xyz").unwrap();
 
-        db.add_mint(mint_url.clone(), Some(mint_info.clone()))
+        let mut tx = db.begin_db_transaction().await.expect("tx");
+
+        tx.add_mint(mint_url.clone(), Some(mint_info.clone()))
             .await
             .unwrap();
 
+        tx.commit().await.expect("commit");
+
         let res = db.get_mint(mint_url).await.unwrap();
         assert_eq!(mint_info, res.clone().unwrap());
         assert_eq!("test", &res.unwrap().description.unwrap());

+ 7 - 5
crates/cdk/src/wallet/auth/auth_wallet.rs

@@ -287,19 +287,21 @@ impl AuthWallet {
     #[instrument(skip(self))]
     pub async fn get_blind_auth_token(&self) -> Result<Option<BlindAuthToken>, Error> {
         let mut tx = self.localstore.begin_db_transaction().await?;
-        let unspent = tx
+
+        let auth_proof = match tx
             .get_proofs(
                 Some(self.mint_url.clone()),
                 Some(CurrencyUnit::Auth),
                 Some(vec![State::Unspent]),
                 None,
             )
-            .await?;
-
-        let auth_proof = match unspent.first() {
+            .await?
+            .pop()
+        {
             Some(proof) => {
                 tx.update_proofs(vec![], vec![proof.proof.y()?]).await?;
-                proof.proof.clone().try_into()?
+                tx.commit().await?;
+                proof.proof.try_into()?
             }
             None => return Ok(None),
         };

+ 1 - 1
crates/cdk/src/wallet/mint_metadata_cache.rs

@@ -522,7 +522,7 @@ impl MintMetadataCache {
         for (keyset_id, keys) in &metadata.keys {
             if let Some(keyset_info) = metadata.keysets.get(keyset_id) {
                 // Check if keys already exist in database to avoid duplicate insertion
-                if storage.get_keys(keyset_id).await.ok().flatten().is_some() {
+                if tx.get_keys(keyset_id).await.ok().flatten().is_some() {
                     tracing::trace!(
                         "Keys for keyset {} already in database, skipping insert",
                         keyset_id