diff --git a/akd/crates/common/Cargo.toml b/akd/crates/common/Cargo.toml index 32f959f15f..ae49ea990c 100644 --- a/akd/crates/common/Cargo.toml +++ b/akd/crates/common/Cargo.toml @@ -14,6 +14,7 @@ config = { workspace = true } serde = { workspace = true } thiserror = "2.0.17" tracing.workspace = true +hex = "0.4.3" [lints] workspace = true diff --git a/akd/crates/common/src/config/mod.rs b/akd/crates/common/src/config/mod.rs new file mode 100644 index 0000000000..5845bae535 --- /dev/null +++ b/akd/crates/common/src/config/mod.rs @@ -0,0 +1,44 @@ +use thiserror::Error; + +mod storage_manager_config; +mod vrf_config; + +pub use storage_manager_config::*; +pub use vrf_config::*; + +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("Failed to connect to database")] + DatabaseConnection(#[source] akd::errors::StorageError), + + #[error("Configuration value 'cache_item_lifetime_ms' is invalid: value {value} exceeds maximum allowed ({max})")] + CacheLifetimeOutOfRange { + value: usize, + max: u64, + #[source] + source: std::num::TryFromIntError, + }, + + #[error("Configuration value 'cache_clean_frequency_ms' is invalid: value {value} exceeds maximum allowed ({max})")] + CacheCleanFrequencyOutOfRange { + value: usize, + max: u64, + #[source] + source: std::num::TryFromIntError, + }, + + #[error("{0}")] + Custom(String), + + #[error("Invalid hex string for VRF key material")] + InvalidVrfKeyMaterialHex(#[source] hex::FromHexError), + + #[error("VRF key material must be exactly 32 bytes, got {actual} bytes")] + VrfKeyMaterialInvalidLength { actual: usize }, +} + +impl ConfigError { + pub fn new(message: impl Into) -> Self { + Self::Custom(message.into()) + } +} diff --git a/akd/crates/common/src/config/storage_manager_config.rs b/akd/crates/common/src/config/storage_manager_config.rs new file mode 100644 index 0000000000..6876471658 --- /dev/null +++ b/akd/crates/common/src/config/storage_manager_config.rs @@ -0,0 +1,59 @@ +use std::time::Duration; + +use crate::config::ConfigError; +use akd::storage::StorageManager; +use akd_storage::{db_config::DbConfig, DatabaseType}; +use serde::{Deserialize, Serialize}; + +/// items live for 30s by default +pub const DEFAULT_ITEM_LIFETIME_MS: usize = 30_000; +/// clean the cache every 15s by default +pub const DEFAULT_CACHE_CLEAN_FREQUENCY_MS: usize = 15_000; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct StorageManagerConfig { + pub db_config: DbConfig, + pub cache_limit_bytes: Option, + #[serde(default = "default_cache_item_lifetime_ms")] + pub cache_item_lifetime_ms: usize, + #[serde(default = "default_cache_clean_frequency_ms")] + pub cache_clean_frequency_ms: usize, +} + +fn default_cache_item_lifetime_ms() -> usize { + DEFAULT_ITEM_LIFETIME_MS +} + +fn default_cache_clean_frequency_ms() -> usize { + DEFAULT_CACHE_CLEAN_FREQUENCY_MS +} + +impl StorageManagerConfig { + pub async fn create(&self) -> Result, ConfigError> { + Ok(StorageManager::new( + self.db_config + .connect() + .await + .map_err(ConfigError::DatabaseConnection)?, + Some(Duration::from_millis( + self.cache_item_lifetime_ms.try_into().map_err(|source| { + ConfigError::CacheLifetimeOutOfRange { + value: self.cache_item_lifetime_ms, + max: u64::MAX, + source, + } + })?, + )), + self.cache_limit_bytes, + Some(Duration::from_millis( + self.cache_clean_frequency_ms.try_into().map_err(|source| { + ConfigError::CacheCleanFrequencyOutOfRange { + value: self.cache_clean_frequency_ms, + max: u64::MAX, + source, + } + })?, + )), + )) + } +} diff --git a/akd/crates/common/src/config/vrf_config.rs b/akd/crates/common/src/config/vrf_config.rs new file mode 100644 index 0000000000..cdd1bdd708 --- /dev/null +++ b/akd/crates/common/src/config/vrf_config.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::ConfigError; +use crate::VrfStorageType; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum VrfConfig { + HardCodedAkdVRF, + ConstantConfigurableVRF { key_material: String }, +} + +impl TryFrom<&VrfConfig> for VrfStorageType { + type Error = ConfigError; + fn try_from(config: &VrfConfig) -> Result { + match config { + VrfConfig::HardCodedAkdVRF => Ok(VrfStorageType::HardCodedAkdVRF), + VrfConfig::ConstantConfigurableVRF { key_material } => { + let key_material = + hex::decode(key_material).map_err(ConfigError::InvalidVrfKeyMaterialHex)?; + + if key_material.len() != 32 { + return Err(ConfigError::VrfKeyMaterialInvalidLength { + actual: key_material.len(), + }); + } + + Ok(VrfStorageType::ConstantConfigurableVRF { + key_material: key_material.clone(), + }) + } + } + } +} diff --git a/akd/crates/common/src/lib.rs b/akd/crates/common/src/lib.rs index 1cf48c4fa8..dc7481bb01 100644 --- a/akd/crates/common/src/lib.rs +++ b/akd/crates/common/src/lib.rs @@ -1,3 +1,4 @@ +pub mod config; mod vrf_type; pub use vrf_type::VrfStorageType; diff --git a/akd/crates/common/src/vrf_type.rs b/akd/crates/common/src/vrf_type.rs index 75d18c61e1..4d2c63282b 100644 --- a/akd/crates/common/src/vrf_type.rs +++ b/akd/crates/common/src/vrf_type.rs @@ -1,19 +1,5 @@ use akd::ecvrf::{HardCodedAkdVRF, VRFKeyStorage, VrfError}; use async_trait::async_trait; -use serde::{Deserialize}; - -#[derive(Debug, Clone, Deserialize)] -pub enum VrfConfig { - HardCodedAkdVRF, -} - -impl From<&VrfConfig> for VrfStorageType { - fn from(config: &VrfConfig) -> Self { - match config { - VrfConfig::HardCodedAkdVRF => VrfStorageType::HardCodedAkdVRF, - } - } -} #[derive(Debug, Clone)] pub enum VrfStorageType { @@ -22,6 +8,9 @@ pub enum VrfStorageType { /// /// const KEY_MATERIAL: &str = "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721"; HardCodedAkdVRF, + ConstantConfigurableVRF { + key_material: Vec, + }, } #[async_trait] @@ -29,6 +18,7 @@ impl VRFKeyStorage for VrfStorageType { async fn retrieve(&self) -> Result, VrfError> { match self { VrfStorageType::HardCodedAkdVRF => HardCodedAkdVRF.retrieve().await, + VrfStorageType::ConstantConfigurableVRF { key_material } => Ok(key_material.clone()), } } }