Bladeren bron

Add ArcSwap to update Mint configuration at runtime

The main goal is to change settings without having multiple RwLock everywhere,
instead having ArcSwap to update the configuration without having access to a
mutable reference to the Mint.

This will allow the RPC Server, or any other medium to update the Mint without
minimum contention.
Cesar Rodas 2 maanden geleden
bovenliggende
commit
6ad03c0f22

+ 7 - 0
Cargo.lock

@@ -156,6 +156,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
 name = "arrayvec"
 version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -672,6 +678,7 @@ name = "cdk"
 version = "0.5.0"
 dependencies = [
  "anyhow",
+ "arc-swap",
  "async-trait",
  "bech32 0.9.1",
  "bip39",

+ 2 - 2
crates/cdk-integration-tests/tests/integration_tests_pure.rs

@@ -34,7 +34,7 @@ mod integration_tests_pure {
             write!(
                 f,
                 "DirectMintConnection {{ mint_info: {:?} }}",
-                self.mint.mint_info
+                self.mint.config.mint_info()
             )
         }
     }
@@ -191,7 +191,7 @@ mod integration_tests_pure {
         let connector = get_mint_connector(mint);
 
         let seed = random::<[u8; 32]>();
-        let mint_url = connector.mint.mint_url.to_string();
+        let mint_url = connector.mint.config.mint_url().to_string();
         let unit = CurrencyUnit::Sat;
 
         let localstore = WalletMemoryDatabase::default();

+ 1 - 1
crates/cdk-integration-tests/tests/mint.rs

@@ -72,7 +72,7 @@ async fn mint_proofs(
     let request_lookup = uuid::Uuid::new_v4().to_string();
 
     let quote = MintQuote::new(
-        mint.mint_url.clone(),
+        mint.config.mint_url(),
         "".to_string(),
         CurrencyUnit::Sat,
         amount,

+ 1 - 0
crates/cdk/Cargo.toml

@@ -58,6 +58,7 @@ uuid = { version = "1", features = ["v4", "serde"] }
 # -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 = { version = "1.21", features = [

+ 98 - 0
crates/cdk/src/mint/config.rs

@@ -0,0 +1,98 @@
+//! Active mint configuration
+//!
+//! This is the active configuration that can be updated at runtime.
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use arc_swap::ArcSwap;
+
+use super::{Id, MintInfo, MintKeySet};
+use crate::mint_url::MintUrl;
+
+/// Mint Inner configuration
+pub struct Inner {
+    /// Active Mint Keysets
+    pub keysets: HashMap<Id, MintKeySet>,
+    /// Mint url
+    pub mint_info: MintInfo,
+    /// Mint config
+    pub mint_url: MintUrl,
+}
+
+/// Mint configuration
+///
+/// This struct is used to configure the mint, and it is wrapped inside a ArcSwap, so it can be
+/// updated at runtime without locking the shared config nor without requiriming a mutable reference
+/// to the config
+///
+/// ArcSwap is used instead of a RwLock since the updates should be less frequent than the reads
+#[derive(Clone)]
+pub struct Config {
+    inner: Arc<ArcSwap<Inner>>,
+}
+
+impl Config {
+    /// Creates a new configuration instance
+    pub fn new(mint_url: MintUrl, mint_info: MintInfo, keysets: HashMap<Id, MintKeySet>) -> Self {
+        let inner = Inner {
+            keysets,
+            mint_info,
+            mint_url,
+        };
+
+        Self {
+            inner: Arc::new(ArcSwap::from_pointee(inner)),
+        }
+    }
+
+    /// Gets an Arc of the current configuration
+    pub fn get_config(&self) -> Arc<Inner> {
+        self.inner.load().clone()
+    }
+
+    /// Gets a copy of the mint url
+    pub fn mint_url(&self) -> MintUrl {
+        self.get_config().mint_url.clone()
+    }
+
+    /// Replace the current mint url with a new one
+    pub fn set_mint_url(&self, mint_url: MintUrl) {
+        let current_inner = self.get_config();
+        let new_inner = Inner {
+            mint_url,
+            mint_info: current_inner.mint_info.clone(),
+            keysets: current_inner.keysets.clone(),
+        };
+
+        self.inner.store(Arc::new(new_inner));
+    }
+
+    /// Gets a copy of the mint info
+    pub fn mint_info(&self) -> MintInfo {
+        self.get_config().mint_info.clone()
+    }
+
+    /// Replaces the current mint info with a new one
+    pub fn set_mint_info(&self, mint_info: MintInfo) {
+        let current_inner = self.get_config();
+        let new_inner = Inner {
+            mint_info,
+            mint_url: current_inner.mint_url.clone(),
+            keysets: current_inner.keysets.clone(),
+        };
+
+        self.inner.store(Arc::new(new_inner));
+    }
+
+    /// Replaces the current keysets with a new one
+    pub fn set_keysets(&self, keysets: HashMap<Id, MintKeySet>) {
+        let current_inner = self.get_config();
+        let new_inner = Inner {
+            mint_info: current_inner.mint_info.clone(),
+            mint_url: current_inner.mint_url.clone(),
+            keysets,
+        };
+
+        self.inner.store(Arc::new(new_inner));
+    }
+}

+ 8 - 8
crates/cdk/src/mint/info.rs

@@ -6,25 +6,25 @@ use crate::mint_url::MintUrl;
 impl Mint {
     /// Set Mint Url
     #[instrument(skip_all)]
-    pub fn set_mint_url(&mut self, mint_url: MintUrl) {
-        self.mint_url = mint_url;
+    pub fn set_mint_url(&self, mint_url: MintUrl) {
+        self.config.set_mint_url(mint_url);
     }
 
     /// Get Mint Url
     #[instrument(skip_all)]
-    pub fn get_mint_url(&self) -> &MintUrl {
-        &self.mint_url
+    pub fn get_mint_url(&self) -> MintUrl {
+        self.config.mint_url()
     }
 
     /// Set Mint Info
     #[instrument(skip_all)]
-    pub fn set_mint_info(&mut self, mint_info: MintInfo) {
-        self.mint_info = mint_info;
+    pub fn set_mint_info(&self, mint_info: MintInfo) {
+        self.config.set_mint_info(mint_info);
     }
 
     /// Get Mint Info
     #[instrument(skip_all)]
-    pub fn mint_info(&self) -> &MintInfo {
-        &self.mint_info
+    pub fn mint_info(&self) -> MintInfo {
+        self.config.mint_info()
     }
 }

+ 19 - 10
crates/cdk/src/mint/keysets.rs

@@ -15,8 +15,13 @@ impl Mint {
     #[instrument(skip(self))]
     pub async fn keyset_pubkeys(&self, keyset_id: &Id) -> Result<KeysResponse, Error> {
         self.ensure_keyset_loaded(keyset_id).await?;
-        let keysets = self.keysets.read().await;
-        let keyset = keysets.get(keyset_id).ok_or(Error::UnknownKeySet)?.clone();
+        let keyset = self
+            .config
+            .get_config()
+            .keysets
+            .get(keyset_id)
+            .ok_or(Error::UnknownKeySet)?
+            .clone();
         Ok(KeysResponse {
             keysets: vec![keyset.into()],
         })
@@ -34,9 +39,11 @@ impl Mint {
             self.ensure_keyset_loaded(id).await?;
         }
 
-        let keysets = self.keysets.read().await;
         Ok(KeysResponse {
-            keysets: keysets
+            keysets: self
+                .config
+                .get_config()
+                .keysets
                 .values()
                 .filter_map(|k| match active_keysets.contains(&k.id) {
                     true => Some(k.clone().into()),
@@ -75,7 +82,8 @@ impl Mint {
     #[instrument(skip(self))]
     pub async fn keyset(&self, id: &Id) -> Result<Option<KeySet>, Error> {
         self.ensure_keyset_loaded(id).await?;
-        let keysets = self.keysets.read().await;
+        let config = self.config.get_config();
+        let keysets = &config.keysets;
         let keyset = keysets.get(id).map(|k| k.clone().into());
         Ok(keyset)
     }
@@ -110,8 +118,9 @@ impl Mint {
         self.localstore.add_keyset_info(keyset_info).await?;
         self.localstore.set_active_keyset(unit, id).await?;
 
-        let mut keysets = self.keysets.write().await;
+        let mut keysets = self.config.get_config().keysets.clone();
         keysets.insert(id, keyset);
+        self.config.set_keysets(keysets);
 
         Ok(())
     }
@@ -119,20 +128,20 @@ impl Mint {
     /// Ensure Keyset is loaded in mint
     #[instrument(skip(self))]
     pub async fn ensure_keyset_loaded(&self, id: &Id) -> Result<(), Error> {
-        let keysets = self.keysets.read().await;
-        if keysets.contains_key(id) {
+        if self.config.get_config().keysets.contains_key(id) {
             return Ok(());
         }
-        drop(keysets);
 
+        let mut keysets = self.config.get_config().keysets.clone();
         let keyset_info = self
             .localstore
             .get_keyset_info(id)
             .await?
             .ok_or(Error::UnknownKeySet)?;
         let id = keyset_info.id;
-        let mut keysets = self.keysets.write().await;
         keysets.insert(id, self.generate_keyset(keyset_info));
+        self.config.set_keysets(keysets);
+
         Ok(())
     }
 

+ 2 - 1
crates/cdk/src/mint/melt.rs

@@ -27,7 +27,8 @@ impl Mint {
         unit: CurrencyUnit,
         method: PaymentMethod,
     ) -> Result<(), Error> {
-        let nut05 = &self.mint_info.nuts.nut05;
+        let mint_info = self.mint_info();
+        let nut05 = mint_info.nuts.nut05;
 
         if nut05.disabled {
             return Err(Error::MeltingDisabled);

+ 3 - 2
crates/cdk/src/mint/mint_nut04.rs

@@ -17,7 +17,8 @@ impl Mint {
         amount: Amount,
         unit: &CurrencyUnit,
     ) -> Result<(), Error> {
-        let nut04 = &self.mint_info.nuts.nut04;
+        let mint_info = self.mint_info();
+        let nut04 = &mint_info.nuts.nut04;
 
         if nut04.disabled {
             return Err(Error::MintingDisabled);
@@ -99,7 +100,7 @@ impl Mint {
             })?;
 
         let quote = MintQuote::new(
-            self.mint_url.clone(),
+            self.config.mint_url(),
             create_invoice_response.request.to_string(),
             unit.clone(),
             amount,

File diff suppressed because it is too large
+ 11 - 13
crates/cdk/src/mint/mod.rs


Some files were not shown because too many files changed in this diff