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"}}}}"#;