Forráskód Böngészése

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 5 napja
szülő
commit
0a829f38e7

+ 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,

+ 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,

+ 20 - 0
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(

+ 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());

+ 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