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
This commit is contained in:
C
2024-12-25 11:53:58 -03:00
committed by GitHub
parent ccb1ee77d0
commit c6e27d0de9
10 changed files with 189 additions and 42 deletions

7
Cargo.lock generated
View File

@@ -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",

View File

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

View File

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

View File

@@ -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 = [

View File

@@ -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<Id, MintKeySet>,
/// 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<ArcSwap<Config>>,
}
impl SwappableConfig {
/// Creates a new configuration instance
pub fn new(
mint_url: MintUrl,
quote_ttl: QuoteTTL,
mint_info: MintInfo,
keysets: HashMap<Id, MintKeySet>,
) -> 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<Config> {
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<Id, MintKeySet>) {
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));
}
}

View File

@@ -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()
}
}

View File

@@ -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
.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<Option<KeySet>, 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(())
}

View File

@@ -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(),
);

View File

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

File diff suppressed because one or more lines are too long