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

Move all keys and keysets to an ArcSwap.

Since the keys and keysets exist in RAM, most wrapping functions are infallible
and synchronous, improving performance and adding breaking API changes.

The signatory will provide this information on the boot and update when the
`rotate_keyset` is executed.

Todo: Implement a subscription key to reload the keys when the GRPC server
changes the keys. For the embedded mode, that makes no sense since there is a
single way to rotate keys, and that bit is already covered.
Cesar Rodas 1 hónapja
szülő
commit
23bbe3dca0

+ 2 - 7
crates/cdk-axum/src/auth.rs

@@ -103,12 +103,7 @@ where
 pub async fn get_auth_keysets(
     State(state): State<MintState>,
 ) -> Result<Json<KeysetResponse>, Response> {
-    let keysets = state.mint.auth_keysets().await.map_err(|err| {
-        tracing::error!("Could not get keysets: {}", err);
-        into_response(err)
-    })?;
-
-    Ok(Json(keysets))
+    Ok(Json(state.mint.auth_keysets()))
 }
 
 #[cfg_attr(feature = "swagger", utoipa::path(
@@ -125,7 +120,7 @@ pub async fn get_auth_keysets(
 pub async fn get_blind_auth_keys(
     State(state): State<MintState>,
 ) -> Result<Json<KeysResponse>, Response> {
-    let pubkeys = state.mint.auth_pubkeys().await.map_err(|err| {
+    let pubkeys = state.mint.auth_pubkeys().map_err(|err| {
         tracing::error!("Could not get keys: {}", err);
         into_response(err)
     })?;

+ 3 - 13
crates/cdk-axum/src/router_handlers.rs

@@ -86,12 +86,7 @@ post_cache_wrapper!(
 pub(crate) async fn get_keys(
     State(state): State<MintState>,
 ) -> Result<Json<KeysResponse>, Response> {
-    let pubkeys = state.mint.pubkeys().await.map_err(|err| {
-        tracing::error!("Could not get keys: {}", err);
-        into_response(err)
-    })?;
-
-    Ok(Json(pubkeys))
+    Ok(Json(state.mint.pubkeys()))
 }
 
 #[cfg_attr(feature = "swagger", utoipa::path(
@@ -114,7 +109,7 @@ pub(crate) async fn get_keyset_pubkeys(
     State(state): State<MintState>,
     Path(keyset_id): Path<Id>,
 ) -> Result<Json<KeysResponse>, Response> {
-    let pubkeys = state.mint.keyset_pubkeys(&keyset_id).await.map_err(|err| {
+    let pubkeys = state.mint.keyset_pubkeys(&keyset_id).map_err(|err| {
         tracing::error!("Could not get keyset pubkeys: {}", err);
         into_response(err)
     })?;
@@ -138,12 +133,7 @@ pub(crate) async fn get_keyset_pubkeys(
 pub(crate) async fn get_keysets(
     State(state): State<MintState>,
 ) -> Result<Json<KeysetResponse>, Response> {
-    let keysets = state.mint.keysets().await.map_err(|err| {
-        tracing::error!("Could not get keysets: {}", err);
-        into_response(err)
-    })?;
-
-    Ok(Json(keysets))
+    Ok(Json(state.mint.keysets()))
 }
 
 #[cfg_attr(feature = "swagger", utoipa::path(

+ 3 - 6
crates/cdk-integration-tests/src/init_pure_tests.rs

@@ -56,18 +56,15 @@ impl Debug for DirectMintConnection {
 #[async_trait]
 impl MintConnector for DirectMintConnection {
     async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
-        self.mint.pubkeys().await.map(|pks| pks.keysets)
+        Ok(self.mint.pubkeys().keysets)
     }
 
     async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
-        self.mint
-            .keyset(&keyset_id)
-            .await
-            .and_then(|res| res.ok_or(Error::UnknownKeySet))
+        self.mint.keyset(&keyset_id).ok_or(Error::UnknownKeySet)
     }
 
     async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
-        self.mint.keysets().await
+        Ok(self.mint.keysets())
     }
 
     async fn post_mint_quote(

+ 6 - 54
crates/cdk-integration-tests/tests/integration_tests_pure.rs

@@ -237,15 +237,7 @@ async fn test_mint_double_spend() {
         .await
         .expect("Could not get proofs");
 
-    let keys = mint_bob
-        .pubkeys()
-        .await
-        .unwrap()
-        .keysets
-        .first()
-        .unwrap()
-        .clone()
-        .keys;
+    let keys = mint_bob.pubkeys().keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
 
     let preswap = PreMintSecrets::random(
@@ -302,15 +294,7 @@ async fn test_attempt_to_swap_by_overflowing() {
 
     let amount = 2_u64.pow(63);
 
-    let keys = mint_bob
-        .pubkeys()
-        .await
-        .unwrap()
-        .keysets
-        .first()
-        .unwrap()
-        .clone()
-        .keys;
+    let keys = mint_bob.pubkeys().keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
 
     let pre_mint_amount =
@@ -431,15 +415,7 @@ pub async fn test_p2pk_swap() {
 
     let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
 
-    let keys = mint_bob
-        .pubkeys()
-        .await
-        .unwrap()
-        .keysets
-        .first()
-        .cloned()
-        .unwrap()
-        .keys;
+    let keys = mint_bob.pubkeys().keysets.first().cloned().unwrap().keys;
 
     let post_swap = mint_bob.process_swap_request(swap_request).await.unwrap();
 
@@ -555,15 +531,7 @@ async fn test_swap_overpay_underpay_fee() {
         .await
         .expect("Could not get proofs");
 
-    let keys = mint_bob
-        .pubkeys()
-        .await
-        .unwrap()
-        .keysets
-        .first()
-        .unwrap()
-        .clone()
-        .keys;
+    let keys = mint_bob.pubkeys().keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
 
     let preswap = PreMintSecrets::random(keyset_id, 9998.into(), &SplitTarget::default()).unwrap();
@@ -629,15 +597,7 @@ async fn test_mint_enforce_fee() {
         .await
         .expect("Could not get proofs");
 
-    let keys = mint_bob
-        .pubkeys()
-        .await
-        .unwrap()
-        .keysets
-        .first()
-        .unwrap()
-        .clone()
-        .keys;
+    let keys = mint_bob.pubkeys().keysets.first().unwrap().clone().keys;
     let keyset_id = Id::from(&keys);
 
     let five_proofs: Vec<_> = proofs.drain(..5).collect();
@@ -924,14 +884,6 @@ async fn test_concurrent_double_spend_melt() {
 }
 
 async fn get_keyset_id(mint: &Mint) -> Id {
-    let keys = mint
-        .pubkeys()
-        .await
-        .unwrap()
-        .keysets
-        .first()
-        .unwrap()
-        .clone()
-        .keys;
+    let keys = mint.pubkeys().keysets.first().unwrap().clone().keys;
     Id::from(&keys)
 }

+ 5 - 11
crates/cdk-integration-tests/tests/mint.rs

@@ -61,31 +61,25 @@ async fn test_correct_keyset() -> Result<()> {
     mint.rotate_next_keyset(CurrencyUnit::Sat, 32, 0).await?;
     mint.rotate_next_keyset(CurrencyUnit::Sat, 32, 0).await?;
 
-    let active = mint.get_active_keysets().await?;
+    let active = mint.get_active_keysets();
 
     let active = active
         .get(&CurrencyUnit::Sat)
         .expect("There is a keyset for unit");
 
-    let keyset_info = mint
-        .get_keyset_info(active)
-        .await?
-        .expect("There is keyset");
+    let keyset_info = mint.get_keyset_info(active).expect("There is keyset");
 
-    assert!(keyset_info.derivation_path_index == Some(2));
+    assert_eq!(keyset_info.derivation_path_index, Some(2));
 
     let mint = mint_builder.build().await?;
 
-    let active = mint.get_active_keysets().await?;
+    let active = mint.get_active_keysets();
 
     let active = active
         .get(&CurrencyUnit::Sat)
         .expect("There is a keyset for unit");
 
-    let keyset_info = mint
-        .get_keyset_info(active)
-        .await?
-        .expect("There is keyset");
+    let keyset_info = mint.get_keyset_info(active).expect("There is keyset");
 
     assert!(keyset_info.derivation_path_index == Some(2));
     Ok(())

+ 1 - 0
crates/cdk/Cargo.toml

@@ -45,6 +45,7 @@ jsonwebtoken = { workspace = true, optional = true }
 # -Z minimal-versions
 sync_wrapper = "0.1.2"
 bech32 = "0.9.1"
+arc-swap = "1.7.1"
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
 tokio = { workspace = true, features = [

+ 13 - 14
crates/cdk/src/mint/keysets/auth.rs

@@ -10,14 +10,14 @@ impl Mint {
     /// Retrieve the auth public keys of the active keyset for distribution to wallet
     /// clients
     #[instrument(skip_all)]
-    pub async fn auth_pubkeys(&self) -> Result<KeysResponse, Error> {
+    pub fn auth_pubkeys(&self) -> Result<KeysResponse, Error> {
         let key = self
-            .signatory
-            .keysets()
-            .await?
-            .into_iter()
+            .keysets
+            .load()
+            .iter()
             .find(|key| key.info.unit == CurrencyUnit::Auth)
-            .ok_or(Error::NoActiveKeyset)?;
+            .ok_or(Error::NoActiveKeyset)?
+            .clone();
 
         Ok(KeysResponse {
             keysets: vec![key.key],
@@ -26,21 +26,20 @@ impl Mint {
 
     /// Return a list of auth keysets
     #[instrument(skip_all)]
-    pub async fn auth_keysets(&self) -> Result<KeysetResponse, Error> {
-        Ok(KeysetResponse {
+    pub fn auth_keysets(&self) -> KeysetResponse {
+        KeysetResponse {
             keysets: self
-                .signatory
-                .keysets()
-                .await?
-                .into_iter()
+                .keysets
+                .load()
+                .iter()
                 .filter_map(|key| {
                     if key.info.unit == CurrencyUnit::Auth {
-                        Some(key.info.into())
+                        Some(key.info.clone().into())
                     } else {
                         None
                     }
                 })
                 .collect(),
-        })
+        }
     }
 }

+ 48 - 41
crates/cdk/src/mint/keysets/mod.rs

@@ -13,64 +13,59 @@ impl Mint {
     /// Retrieve the public keys of the active keyset for distribution to wallet
     /// clients
     #[instrument(skip(self))]
-    pub async fn keyset_pubkeys(&self, keyset_id: &Id) -> Result<KeysResponse, Error> {
-        self.signatory
-            .keysets()
-            .await?
-            .into_iter()
+    pub fn keyset_pubkeys(&self, keyset_id: &Id) -> Result<KeysResponse, Error> {
+        self.keysets
+            .load()
+            .iter()
             .find(|keyset| &keyset.key.id == keyset_id)
             .ok_or(Error::UnknownKeySet)
             .map(|key| KeysResponse {
-                keysets: vec![key.key],
+                keysets: vec![key.key.clone()],
             })
     }
 
     /// Retrieve the public keys of the active keyset for distribution to wallet
     /// clients
     #[instrument(skip_all)]
-    pub async fn pubkeys(&self) -> Result<KeysResponse, Error> {
-        Ok(KeysResponse {
+    pub fn pubkeys(&self) -> KeysResponse {
+        KeysResponse {
             keysets: self
-                .signatory
-                .keysets()
-                .await?
-                .into_iter()
+                .keysets
+                .load()
+                .iter()
                 .filter(|keyset| keyset.info.active && keyset.info.unit != CurrencyUnit::Auth)
-                .map(|key| key.key)
+                .map(|key| key.key.clone())
                 .collect::<Vec<_>>(),
-        })
+        }
     }
 
     /// Return a list of all supported keysets
     #[instrument(skip_all)]
-    pub async fn keysets(&self) -> Result<KeysetResponse, Error> {
-        let keysets = self
-            .signatory
-            .keysets()
-            .await?
-            .into_iter()
-            .filter(|k| k.key.unit != CurrencyUnit::Auth)
-            .map(|k| KeySetInfo {
-                id: k.key.id,
-                unit: k.key.unit,
-                active: k.info.active,
-                input_fee_ppk: k.info.input_fee_ppk,
-            })
-            .collect();
-
-        Ok(KeysetResponse { keysets })
+    pub fn keysets(&self) -> KeysetResponse {
+        KeysetResponse {
+            keysets: self
+                .keysets
+                .load()
+                .iter()
+                .filter(|k| k.key.unit != CurrencyUnit::Auth)
+                .map(|k| KeySetInfo {
+                    id: k.key.id,
+                    unit: k.key.unit.clone(),
+                    active: k.info.active,
+                    input_fee_ppk: k.info.input_fee_ppk,
+                })
+                .collect(),
+        }
     }
 
     /// Get keysets
     #[instrument(skip(self))]
-    pub async fn keyset(&self, id: &Id) -> Result<Option<KeySet>, Error> {
-        Ok(self
-            .signatory
-            .keysets()
-            .await?
-            .into_iter()
+    pub fn keyset(&self, id: &Id) -> Option<KeySet> {
+        self.keysets
+            .load()
+            .iter()
             .find(|key| &key.key.id == id)
-            .map(|x| x.key))
+            .map(|x| x.key.clone())
     }
 
     /// Add current keyset to inactive keysets
@@ -83,14 +78,20 @@ impl Mint {
         max_order: u8,
         input_fee_ppk: u64,
     ) -> Result<MintKeySetInfo, Error> {
-        self.signatory
+        let result = self
+            .signatory
             .rotate_keyset(RotateKeyArguments {
                 unit,
                 derivation_path_index: Some(derivation_path_index),
                 max_order,
                 input_fee_ppk,
             })
-            .await
+            .await?;
+
+        let new_keyset = self.signatory.keysets().await?;
+        self.keysets.store(new_keyset.into());
+
+        Ok(result)
     }
 
     /// Rotate to next keyset for unit
@@ -101,13 +102,19 @@ impl Mint {
         max_order: u8,
         input_fee_ppk: u64,
     ) -> Result<MintKeySetInfo, Error> {
-        self.signatory
+        let result = self
+            .signatory
             .rotate_keyset(RotateKeyArguments {
                 unit,
                 max_order,
                 derivation_path_index: None,
                 input_fee_ppk,
             })
-            .await
+            .await?;
+
+        let new_keyset = self.signatory.keysets().await?;
+        self.keysets.store(new_keyset.into());
+
+        Ok(result)
     }
 }

+ 25 - 24
crates/cdk/src/mint/mod.rs

@@ -3,6 +3,7 @@
 use std::collections::HashMap;
 use std::sync::Arc;
 
+use arc_swap::ArcSwap;
 use bitcoin::bip32::{DerivationPath, Xpriv};
 use bitcoin::secp256k1;
 use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
@@ -13,7 +14,7 @@ use cdk_common::nuts::{
     self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind, MintKeySet, Proof,
 };
 use cdk_common::secret;
-use cdk_signatory::signatory::Signatory;
+use cdk_signatory::signatory::{Signatory, SignatoryKeySet};
 use futures::StreamExt;
 #[cfg(feature = "auth")]
 use nut21::ProtectedEndpoint;
@@ -69,6 +70,8 @@ pub struct Mint {
     pub pubsub_manager: Arc<PubSubManager>,
     #[cfg(feature = "auth")]
     oidc_client: Option<OidcClient>,
+    /// In-memory keyset
+    keysets: Arc<ArcSwap<Vec<SignatoryKeySet>>>,
 }
 
 impl Mint {
@@ -150,6 +153,8 @@ impl Mint {
         let oidc_client =
             open_id_discovery.map(|openid_discovery| OidcClient::new(openid_discovery.clone()));
 
+        let keysets = signatory.keysets().await?;
+
         Ok(Self {
             signatory,
             pubsub_manager: Arc::new(localstore.clone().into()),
@@ -159,6 +164,7 @@ impl Mint {
             ln,
             #[cfg(feature = "auth")]
             auth_localstore,
+            keysets: Arc::new(ArcSwap::new(keysets.into())),
         })
     }
 
@@ -292,7 +298,6 @@ impl Mint {
             {
                 let mint_keyset_info = self
                     .get_keyset_info(&proof.keyset_id)
-                    .await?
                     .ok_or(Error::UnknownKeySet)?;
                 e.insert(mint_keyset_info.input_fee_ppk);
             }
@@ -309,12 +314,10 @@ impl Mint {
     }
 
     /// Get active keysets
-    pub async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Error> {
-        Ok(self
-            .signatory
-            .keysets()
-            .await?
-            .into_iter()
+    pub fn get_active_keysets(&self) -> HashMap<CurrencyUnit, Id> {
+        self.keysets
+            .load()
+            .iter()
             .filter_map(|keyset| {
                 if keyset.info.active {
                     Some((keyset.info.unit.clone(), keyset.info.id))
@@ -322,24 +325,22 @@ impl Mint {
                     None
                 }
             })
-            .collect())
+            .collect()
     }
 
     /// Get keyset info
-    pub async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Error> {
-        Ok(self
-            .signatory
-            .keysets()
-            .await?
-            .into_iter()
+    pub fn get_keyset_info(&self, id: &Id) -> Option<MintKeySetInfo> {
+        self.keysets
+            .load()
+            .iter()
             .filter_map(|keyset| {
                 if keyset.info.id == *id {
-                    Some(keyset.info)
+                    Some(keyset.info.clone())
                 } else {
                     None
                 }
             })
-            .next())
+            .next()
     }
 
     /// Blind Sign
@@ -467,7 +468,7 @@ impl Mint {
     /// Get the total amount issed by keyset
     #[instrument(skip_all)]
     pub async fn total_issued(&self) -> Result<HashMap<Id, Amount>, Error> {
-        let keysets = self.keysets().await?.keysets;
+        let keysets = self.keysets().keysets;
 
         let mut total_issued = HashMap::new();
 
@@ -488,7 +489,7 @@ impl Mint {
     /// Total redeemed for keyset
     #[instrument(skip_all)]
     pub async fn total_redeemed(&self) -> Result<HashMap<Id, Amount>, Error> {
-        let keysets = self.keysets().await?.keysets;
+        let keysets = self.keysets().keysets;
 
         let mut total_redeemed = HashMap::new();
 
@@ -604,14 +605,14 @@ mod tests {
         let mint = create_mint(config).await?;
 
         assert_eq!(
-            mint.pubkeys().await.unwrap(),
+            mint.pubkeys(),
             KeysResponse {
                 keysets: Vec::new()
             }
         );
 
         assert_eq!(
-            mint.keysets().await.unwrap(),
+            mint.keysets(),
             KeysetResponse {
                 keysets: Vec::new()
             }
@@ -637,13 +638,13 @@ mod tests {
         };
         let mint = create_mint(config).await?;
 
-        let keysets = mint.keysets().await.unwrap();
+        let keysets = mint.keysets();
         assert!(keysets.keysets.is_empty());
 
         // generate the first keyset and set it to active
         mint.rotate_keyset(CurrencyUnit::default(), 0, 1, 1).await?;
 
-        let keysets = mint.keysets().await.unwrap();
+        let keysets = mint.keysets();
         assert!(keysets.keysets.len().eq(&1));
         assert!(keysets.keysets[0].active);
         let first_keyset_id = keysets.keysets[0].id;
@@ -651,7 +652,7 @@ mod tests {
         // set the first keyset to inactive and generate a new keyset
         mint.rotate_keyset(CurrencyUnit::default(), 1, 1, 1).await?;
 
-        let keysets = mint.keysets().await.unwrap();
+        let keysets = mint.keysets();
 
         assert_eq!(2, keysets.keysets.len());
         for keyset in &keysets.keysets {

+ 2 - 2
crates/cdk/src/mint/verification.rs

@@ -66,7 +66,7 @@ impl Mint {
         let output_keyset_ids: HashSet<Id> = outputs.iter().map(|p| p.keyset_id).collect();
 
         for id in &output_keyset_ids {
-            match self.get_keyset_info(id).await? {
+            match self.get_keyset_info(id) {
                 Some(keyset) => {
                     if !keyset.active {
                         tracing::debug!(
@@ -114,7 +114,7 @@ impl Mint {
         let inputs_keyset_ids: HashSet<Id> = inputs.iter().map(|p| p.keyset_id).collect();
 
         for id in &inputs_keyset_ids {
-            match self.get_keyset_info(id).await? {
+            match self.get_keyset_info(id) {
                 Some(keyset) => {
                     keyset_units.insert(keyset.unit);
                 }

+ 1 - 1
justfile

@@ -57,7 +57,7 @@ test-pure db="memory": build
   fi
 
   # Run pure integration tests
-  CDK_TEST_DB_TYPE={{db}} cargo test -p cdk-integration-tests --test integration_tests_pure
+  CDK_TEST_DB_TYPE={{db}} cargo test -p cdk-integration-tests --test integration_tests_pure -- --test-threads 1
 
 test-all db="memory":
     #!/usr/bin/env bash