From c6e27d0de9f07897c8c59a4cb29eaac14a50cb61 Mon Sep 17 00:00:00 2001 From: C Date: Wed, 25 Dec 2024 11:53:58 -0300 Subject: [PATCH] Add ArcSwap to update Mint configuration at runtime (#503) * 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. * Rename structs * Move quote_ttl to the new config * Fixed clippy issues --- Cargo.lock | 7 + .../tests/integration_tests_pure.rs | 4 +- crates/cdk-integration-tests/tests/mint.rs | 2 +- crates/cdk/Cargo.toml | 1 + crates/cdk/src/mint/config.rs | 128 ++++++++++++++++++ crates/cdk/src/mint/info.rs | 16 +-- crates/cdk/src/mint/keysets.rs | 29 ++-- crates/cdk/src/mint/melt.rs | 5 +- crates/cdk/src/mint/mint_nut04.rs | 7 +- crates/cdk/src/mint/mod.rs | 32 ++--- 10 files changed, 189 insertions(+), 42 deletions(-) create mode 100644 crates/cdk/src/mint/config.rs diff --git a/Cargo.lock b/Cargo.lock index d6ba00ac..0b4a06da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,6 +170,12 @@ dependencies = [ "backtrace", ] +[[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" @@ -726,6 +732,7 @@ name = "cdk" version = "0.6.0" dependencies = [ "anyhow", + "arc-swap", "async-trait", "bech32 0.9.1", "bip39", diff --git a/crates/cdk-integration-tests/tests/integration_tests_pure.rs b/crates/cdk-integration-tests/tests/integration_tests_pure.rs index 914fc2f1..9376214b 100644 --- a/crates/cdk-integration-tests/tests/integration_tests_pure.rs +++ b/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(); diff --git a/crates/cdk-integration-tests/tests/mint.rs b/crates/cdk-integration-tests/tests/mint.rs index af14a7ab..96892cc4 100644 --- a/crates/cdk-integration-tests/tests/mint.rs +++ b/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, diff --git a/crates/cdk/Cargo.toml b/crates/cdk/Cargo.toml index 3f1bf33b..9b88b8f8 100644 --- a/crates/cdk/Cargo.toml +++ b/crates/cdk/Cargo.toml @@ -21,6 +21,7 @@ http_subscription = [] [dependencies] +arc-swap = "1.7.1" async-trait = "0.1" anyhow = { version = "1.0.43", features = ["backtrace"] } bitcoin = { version = "0.32.2", features = [ diff --git a/crates/cdk/src/mint/config.rs b/crates/cdk/src/mint/config.rs new file mode 100644 index 00000000..a669cea0 --- /dev/null +++ b/crates/cdk/src/mint/config.rs @@ -0,0 +1,128 @@ +//! 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; +use crate::types::QuoteTTL; + +/// Mint Inner configuration +pub struct Config { + /// Active Mint Keysets + pub keysets: HashMap, + /// Mint url + pub mint_info: MintInfo, + /// Mint config + pub mint_url: MintUrl, + /// Quotes ttl + pub quote_ttl: QuoteTTL, +} + +/// 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 SwappableConfig { + config: Arc>, +} + +impl SwappableConfig { + /// Creates a new configuration instance + pub fn new( + mint_url: MintUrl, + quote_ttl: QuoteTTL, + mint_info: MintInfo, + keysets: HashMap, + ) -> Self { + let inner = Config { + keysets, + quote_ttl, + mint_info, + mint_url, + }; + + Self { + config: Arc::new(ArcSwap::from_pointee(inner)), + } + } + + /// Gets an Arc of the current configuration + pub fn load(&self) -> Arc { + self.config.load().clone() + } + + /// Gets a copy of the mint url + pub fn mint_url(&self) -> MintUrl { + self.load().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.load(); + let new_inner = Config { + mint_url, + quote_ttl: current_inner.quote_ttl, + mint_info: current_inner.mint_info.clone(), + keysets: current_inner.keysets.clone(), + }; + + self.config.store(Arc::new(new_inner)); + } + + /// Gets a copy of the quote ttl + pub fn quote_ttl(&self) -> QuoteTTL { + self.load().quote_ttl + } + + /// Replaces the current quote ttl with a new one + pub fn set_quote_ttl(&self, quote_ttl: QuoteTTL) { + let current_inner = self.load(); + let new_inner = Config { + mint_info: current_inner.mint_info.clone(), + mint_url: current_inner.mint_url.clone(), + quote_ttl, + keysets: current_inner.keysets.clone(), + }; + + self.config.store(Arc::new(new_inner)); + } + + /// Gets a copy of the mint info + pub fn mint_info(&self) -> MintInfo { + self.load().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.load(); + let new_inner = Config { + mint_info, + mint_url: current_inner.mint_url.clone(), + quote_ttl: current_inner.quote_ttl, + keysets: current_inner.keysets.clone(), + }; + + self.config.store(Arc::new(new_inner)); + } + + /// Replaces the current keysets with a new one + pub fn set_keysets(&self, keysets: HashMap) { + let current_inner = self.load(); + let new_inner = Config { + mint_info: current_inner.mint_info.clone(), + quote_ttl: current_inner.quote_ttl, + mint_url: current_inner.mint_url.clone(), + keysets, + }; + + self.config.store(Arc::new(new_inner)); + } +} diff --git a/crates/cdk/src/mint/info.rs b/crates/cdk/src/mint/info.rs index ba9455f5..4cbb174c 100644 --- a/crates/cdk/src/mint/info.rs +++ b/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() } } diff --git a/crates/cdk/src/mint/keysets.rs b/crates/cdk/src/mint/keysets.rs index 9ca8c7ec..abad5983 100644 --- a/crates/cdk/src/mint/keysets.rs +++ b/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 { 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 + .load() + .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 + .load() + .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, Error> { self.ensure_keyset_loaded(id).await?; - let keysets = self.keysets.read().await; + let config = self.config.load(); + 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.load().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.load().keysets.contains_key(id) { return Ok(()); } - drop(keysets); + let mut keysets = self.config.load().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(()) } diff --git a/crates/cdk/src/mint/melt.rs b/crates/cdk/src/mint/melt.rs index 55add5a1..8d2ddc06 100644 --- a/crates/cdk/src/mint/melt.rs +++ b/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); @@ -99,7 +100,7 @@ impl Mint { unit.clone(), payment_quote.amount, payment_quote.fee, - unix_time() + self.quote_ttl.melt_ttl, + unix_time() + self.config.quote_ttl().melt_ttl, payment_quote.request_lookup_id.clone(), ); diff --git a/crates/cdk/src/mint/mint_nut04.rs b/crates/cdk/src/mint/mint_nut04.rs index f456864e..f253486b 100644 --- a/crates/cdk/src/mint/mint_nut04.rs +++ b/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); @@ -79,7 +80,7 @@ impl Mint { Error::UnitUnsupported })?; - let quote_expiry = unix_time() + self.quote_ttl.mint_ttl; + let quote_expiry = unix_time() + self.config.quote_ttl().mint_ttl; if description.is_some() && !ln.get_settings().invoice_description { tracing::error!("Backend does not support invoice description"); @@ -100,7 +101,7 @@ impl Mint { })?; let quote = MintQuote::new( - self.mint_url.clone(), + self.config.mint_url(), create_invoice_response.request.to_string(), unit.clone(), amount, diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index 844a8d0d..e8e37b09 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -6,9 +6,10 @@ use std::sync::Arc; use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv}; use bitcoin::secp256k1::{self, Secp256k1}; +use config::SwappableConfig; use futures::StreamExt; use serde::{Deserialize, Serialize}; -use tokio::sync::{Notify, RwLock}; +use tokio::sync::Notify; use tokio::task::JoinSet; use tracing::instrument; use uuid::Uuid; @@ -26,6 +27,7 @@ use crate::Amount; mod builder; mod check_spendable; +pub mod config; mod info; mod keysets; mod melt; @@ -40,20 +42,14 @@ pub use types::{MeltQuote, MintQuote}; /// Cashu Mint #[derive(Clone)] pub struct Mint { - /// Mint Url - pub mint_url: MintUrl, - /// Mint Info - pub mint_info: MintInfo, - /// Quotes ttl - pub quote_ttl: QuoteTTL, + /// Mint Config + pub config: SwappableConfig, /// Mint Storage backend pub localstore: Arc + Send + Sync>, /// Ln backends for mint pub ln: HashMap + Send + Sync>>, /// Subscription manager pub pubsub_manager: Arc, - /// Active Mint Keysets - keysets: Arc>>, secp_ctx: Secp256k1, xpriv: Xpriv, } @@ -185,14 +181,16 @@ impl Mint { } Ok(Self { - mint_url: MintUrl::from_str(mint_url)?, - keysets: Arc::new(RwLock::new(active_keysets)), + config: SwappableConfig::new( + MintUrl::from_str(mint_url)?, + quote_ttl, + mint_info, + active_keysets, + ), pubsub_manager: Arc::new(localstore.clone().into()), secp_ctx, - quote_ttl, xpriv, localstore, - mint_info, ln, }) } @@ -315,7 +313,8 @@ impl Mint { return Err(Error::InactiveKeyset); } - let keysets = self.keysets.read().await; + let config = self.config.load(); + let keysets = &config.keysets; let keyset = keysets.get(keyset_id).ok_or(Error::UnknownKeySet)?; let key_pair = match keyset.keys.get(amount) { @@ -360,7 +359,8 @@ impl Mint { } self.ensure_keyset_loaded(&proof.keyset_id).await?; - let keysets = self.keysets.read().await; + let config = self.config.load(); + let keysets = &config.keysets; let keyset = keysets.get(&proof.keyset_id).ok_or(Error::UnknownKeySet)?; let keypair = match keyset.keys.get(&proof.amount) { @@ -847,7 +847,7 @@ mod tests { mint.rotate_keyset(CurrencyUnit::default(), 0, 32, 1, HashMap::new()) .await?; - let keys = mint.keysets.read().await; + let keys = mint.config.load().keysets.clone(); let expected_keys = r#"{"005f6e8c540c9e61":{"id":"005f6e8c540c9e61","unit":"sat","keys":{"1":{"public_key":"03e8aded7525acee36e3394e28f2dcbc012533ef2a2b085a55fc291d311afee3ef","secret_key":"32ee9fc0723772aed4c7b8ac0a02ffe390e54a4e0b037ec6035c2afa10ebd873"},"2":{"public_key":"02628c0919e5cb8ce9aed1f81ce313f40e1ab0b33439d5be2abc69d9bb574902e0","secret_key":"48384bf901bbe8f937d601001d067e73b28b435819c009589350c664f9ba872c"},"4":{"public_key":"039e7c7f274e1e8a90c61669e961c944944e6154c0794fccf8084af90252d2848f","secret_key":"1f039c1e54e9e65faae8ecf69492f810b4bb2292beb3734059f2bb4d564786d0"},"8":{"public_key":"02ca0e563ae941700aefcb16a7fb820afbb3258ae924ab520210cb730227a76ca3","secret_key":"ea3c2641d847c9b15c5f32c150b5c9c04d0666af0549e54f51f941cf584442be"},"16":{"public_key":"031dbab0e4f7fb4fb0030f0e1a1dc80668eadd0b1046df3337bb13a7b9c982d392","secret_key":"5b244f8552077e68b30b534e85bd0e8e29ae0108ff47f5cd92522aa524d3288f"},"32":{"public_key":"037241f7ad421374eb764a48e7769b5e2473582316844fda000d6eef28eea8ffb8","secret_key":"95608f61dd690aef34e6a2d4cbef3ad8fddb4537a14480a17512778058e4f5bd"},"64":{"public_key":"02bc9767b4abf88becdac47a59e67ee9a9a80b9864ef57d16084575273ac63c0e7","secret_key":"2e9cd067fafa342f3118bc1e62fbb8e53acdb0f96d51ce8a1e1037e43fad0dce"},"128":{"public_key":"0351e33a076f415c2cadc945bc9bcb75bf4a774b28df8a0605dea1557e5897fed8","secret_key":"7014f27be5e2b77e4951a81c18ae3585d0b037899d8a37b774970427b13d8f65"},"256":{"public_key":"0314b9f4300367c7e64fa85770da90839d2fc2f57d63660f08bb3ebbf90ed76840","secret_key":"1a545bd9c40fc6cf2ab281710e279967e9f4b86cd07761c741da94bc8042c8fb"},"512":{"public_key":"030d95abc7e881d173f4207a3349f4ee442b9e51cc461602d3eb9665b9237e8db3","secret_key":"622984ef16d1cb28e9adc7a7cfea1808d85b4bdabd015977f0320c9f573858b4"},"1024":{"public_key":"0351a68a667c5fc21d66c187baecefa1d65529d06b7ae13112d432b6bca16b0e8c","secret_key":"6a8badfa26129499b60edb96cda4cbcf08f8007589eb558a9d0307bdc56e0ff6"},"2048":{"public_key":"0376166d8dcf97d8b0e9f11867ff0dafd439c90255b36a25be01e37e14741b9c6a","secret_key":"48fe41181636716ce202b3a3303c2475e6d511991930868d907441e1bcbf8566"},"4096":{"public_key":"03d40f47b4e5c4d72f2a977fab5c66b54d945b2836eb888049b1dd9334d1d70304","secret_key":"66a25bf144a3b40c015dd1f630aa4ba81d2242f5aee845e4f378246777b21676"},"8192":{"public_key":"03be18afaf35a29d7bcd5dfd1936d82c1c14691a63f8aa6ece258e16b0c043049b","secret_key":"4ddac662e82f6028888c11bdefd07229d7c1b56987395f106cc9ea5b301695f6"},"16384":{"public_key":"028e9c6ce70f34cd29aad48656bf8345bb5ba2cb4f31fdd978686c37c93f0ab411","secret_key":"83676bd7d047655476baecad2864519f0ffd8e60f779956d2faebcc727caa7bd"},"32768":{"public_key":"0253e34bab4eec93e235c33994e01bf851d5caca4559f07d37b5a5c266de7cf840","secret_key":"d5be522906223f5d92975e2a77f7e166aa121bf93d5fe442d6d132bf67166b04"},"65536":{"public_key":"02684ede207f9ace309b796b5259fc81ef0d4492b4fb5d66cf866b0b4a6f27bec9","secret_key":"20d859b7052d768e007bf285ee11dc0b98a4abfe272a551852b0cce9fb6d5ad4"},"131072":{"public_key":"027cdf7be8b20a49ac7f2f065f7c53764c8926799877858c6b00b888a8aa6741a5","secret_key":"f6eef28183344b32fc0a1fba00cd6cf967614e51d1c990f0bfce8f67c6d9746a"},"262144":{"public_key":"026939b8f766c3ebaf26408e7e54fc833805563e2ef14c8ee4d0435808b005ec4c","secret_key":"690f23e4eaa250c652afeac24d4efb583095a66abf6b87a7f3d17b1f42c5f896"},"524288":{"public_key":"03772542057493a46eed6513b40386e766eedada16560ffde2f776b65794e9f004","secret_key":"fe36e61bea74665f8796b4b62f9501ae6e0d5b16733d2c05c146cd39f89475a0"},"1048576":{"public_key":"02b016346e5a322d371c6e6164b28b31b4d93a51572351ca2f26cdc12e916d9ac3","secret_key":"b9269779e057ce715964caa6d6b5b65672f255e86746e994b6b8c4780cb9d728"},"2097152":{"public_key":"028f25283e36a11df7713934a5287267381f8304aca3c1eb1b89fddce973ef1436","secret_key":"41aec998b9624ddcff97eb7341daa6385b2a8714ed3f12969ef39649f4d641ab"},"4194304":{"public_key":"03e5841d310819a49ec42dfb24839c61f68bbfc93ac68f6dad37fd5b2d204cc535","secret_key":"e5aef2509c56236f004e2df4343beab6406816fb187c3532d4340a9674857c64"},"8388608":{"public_key":"0307ebfeb87b7bca9baa03fad00499e5cc999fa5179ef0b7ad4f555568bcb946f5","secret_key":"369e8dcabcc69a2eabb7363beb66178cafc29e53b02c46cd15374028c3110541"},"16777216":{"public_key":"02f2508e7df981c32f7b0008a273e2a1f19c23bb60a1561dba6b2a95ed1251eb90","secret_key":"f93965b96ed5428bcacd684eff2f43a9777d03adfde867fa0c6efb39c46a7550"},"33554432":{"public_key":"0381883a1517f8c9979a84fcd5f18437b1a2b0020376ecdd2e515dc8d5a157a318","secret_key":"7f5e77c7ed04dff952a7c15564ab551c769243eb65423adfebf46bf54360cd64"},"67108864":{"public_key":"02aa648d39c9a725ef5927db15af6895f0d43c17f0a31faff4406314fc80180086","secret_key":"d34eda86679bf872dfb6faa6449285741bba6c6d582cd9fe5a9152d5752596cc"},"134217728":{"public_key":"0380658e5163fcf274e1ace6c696d1feef4c6068e0d03083d676dc5ef21804f22d","secret_key":"3ad22e92d497309c5b08b2dc01cb5180de3e00d3d703229914906bc847183987"},"268435456":{"public_key":"031526f03de945c638acccb879de837ac3fabff8590057cfb8552ebcf51215f3aa","secret_key":"3a740771e29119b171ab8e79e97499771439e0ab6a082ec96e43baf06a546372"},"536870912":{"public_key":"035eb3e7262e126c5503e1b402db05f87de6556773ae709cb7aa1c3b0986b87566","secret_key":"9b77ee8cd879128c0ea6952dd188e63617fbaa9e66a3bca0244bcceb9b1f7f48"},"1073741824":{"public_key":"03f12e6a0903ed0db87485a296b1dca9d953a8a6919ff88732238fbc672d6bd125","secret_key":"f3947bca4df0f024eade569c81c5c53e167476e074eb81fa6b289e5e10dd4e42"},"2147483648":{"public_key":"02cece3fb38a54581e0646db4b29242b6d78e49313dda46764094f9d128c1059c1","secret_key":"582d54a894cd41441157849e0d16750e5349bd9310776306e7313b255866950b"}}}}"#;