diff --git a/rs/boundary_node/rate_limits/api/src/lib.rs b/rs/boundary_node/rate_limits/api/src/lib.rs index c0753cf8cb5d..3f5e5181fb55 100644 --- a/rs/boundary_node/rate_limits/api/src/lib.rs +++ b/rs/boundary_node/rate_limits/api/src/lib.rs @@ -26,3 +26,8 @@ pub struct OutputRule { pub description: Option, pub disclosed_at: Option, } + +#[derive(CandidType, Deserialize, Debug)] +pub struct InitArg { + registry_polling_period_secs: u64, +} diff --git a/rs/boundary_node/rate_limits/canister/canister.rs b/rs/boundary_node/rate_limits/canister/canister.rs index 6330bc38f7fe..a06129193283 100644 --- a/rs/boundary_node/rate_limits/canister/canister.rs +++ b/rs/boundary_node/rate_limits/canister/canister.rs @@ -1,20 +1,22 @@ -use rate_limits_api::{GetConfigResponse, Version}; -use storage::VERSION; +use rate_limits_api::{GetConfigResponse, InitArg, Version}; +use storage::{get_stable_version, set_stable_version}; use types::{ConfigResponse, OutputConfig}; mod storage; mod types; +#[ic_cdk::init] +fn init(_init_arg: InitArg) { + // Initialize version to 1 + set_stable_version(1); + // TODO: init periodic timer for fetching API BNs principals. +} + #[ic_cdk_macros::update] -fn get_config(version: Option) -> GetConfigResponse { - let test_version_inc = VERSION.with(|v| { - let mut ver = v.borrow_mut(); - let current_version = ver.get(&()).unwrap_or(0); - ver.insert((), current_version + 1); - current_version - }); +fn get_config(_version: Option) -> GetConfigResponse { + let version = get_stable_version(); let response = ConfigResponse { - version: version.unwrap_or(test_version_inc), + version, active_since: 1, config: OutputConfig { rules: vec![] }, }; diff --git a/rs/boundary_node/rate_limits/canister/storage.rs b/rs/boundary_node/rate_limits/canister/storage.rs index dc6eba38ecc5..b6c556caf025 100644 --- a/rs/boundary_node/rate_limits/canister/storage.rs +++ b/rs/boundary_node/rate_limits/canister/storage.rs @@ -1,31 +1,182 @@ -use std::cell::RefCell; +use std::{borrow::Cow, cell::RefCell}; use ic_stable_structures::{ memory_manager::{MemoryId, MemoryManager, VirtualMemory}, - DefaultMemoryImpl, StableBTreeMap, + storable::Bound, + DefaultMemoryImpl, StableBTreeMap, Storable, }; +use serde::{Deserialize, Serialize}; -use crate::types::Version; +use crate::types::{RuleId, Timestamp, Version}; -// Stable Memory +// Type aliases for stable memory type Memory = VirtualMemory; type StableMap = StableBTreeMap; -type _StableSet = StableMap; type StableValue = StableMap<(), T>; +// Memory IDs for stable memory management const MEMORY_ID_VERSION: u8 = 0; +const _MEMORY_ID_CONFIGS: u8 = 1; +const _MEMORY_ID_RULES: u8 = 2; +const _MEMORY_ID_INCIDENTS: u8 = 3; -// Memory +// Storables +#[derive(Clone, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq)] +pub struct StorableVersion(pub Version); + +#[derive(Clone, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq)] +pub struct StorableRuleId(pub String); + +#[derive(Clone, Serialize, Deserialize, PartialOrd, Ord, PartialEq, Eq)] +pub struct StorableIncidentId(String); + +#[derive(Serialize, Deserialize)] +pub struct StorableRuleMetadata { + pub rule_raw: Vec, + pub description: String, + pub disclosed_at: Option, + pub added_in_version: Version, + pub removed_in_version: Option, +} + +#[derive(Serialize, Deserialize)] +struct StorableConfig { + active_since: Timestamp, + rule_ids: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct StorableRuleIds { + rule_ids: Vec, +} + +impl Storable for StorableVersion { + fn to_bytes(&self) -> Cow<[u8]> { + self.0.to_bytes() + } + + fn from_bytes(bytes: Cow<'_, [u8]>) -> Self { + Self(u64::from_bytes(bytes)) + } + + const BOUND: Bound = ::BOUND; +} + +impl Storable for StorableRuleId { + fn to_bytes(&self) -> Cow<[u8]> { + self.0.to_bytes() + } + + fn from_bytes(bytes: Cow<'_, [u8]>) -> Self { + Self(String::from_bytes(bytes)) + } + + // TODO: make it bounded + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for StorableIncidentId { + fn to_bytes(&self) -> Cow<[u8]> { + self.0.to_bytes() + } + + fn from_bytes(bytes: Cow<'_, [u8]>) -> Self { + Self(String::from_bytes(bytes)) + } + + // TODO: make it bounded + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for StorableConfig { + fn to_bytes(&self) -> Cow<[u8]> { + let mut buf = vec![]; + ciborium::ser::into_writer(self, &mut buf).expect("failed to encode StorableConfig"); + Cow::Owned(buf) + } + + fn from_bytes(bytes: Cow<'_, [u8]>) -> Self { + ciborium::de::from_reader(&bytes[..]).expect("failed to decode StorableConfig") + } + + // TODO: make it bounded + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for StorableRuleIds { + fn to_bytes(&self) -> Cow<[u8]> { + let mut buf = vec![]; + ciborium::ser::into_writer(self, &mut buf).expect("failed to encode StorableRuleIds"); + Cow::Owned(buf) + } + + fn from_bytes(bytes: Cow<'_, [u8]>) -> Self { + ciborium::de::from_reader(&bytes[..]).expect("failed to decode StorableRuleIds") + } + + // TODO: make it bounded + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for StorableRuleMetadata { + fn to_bytes(&self) -> Cow<[u8]> { + let mut buf = vec![]; + ciborium::ser::into_writer(self, &mut buf).expect("failed to encode StorableRuleMetadata"); + Cow::Owned(buf) + } + + fn from_bytes(bytes: Cow<'_, [u8]>) -> Self { + ciborium::de::from_reader(&bytes[..]).expect("failed to decode StorableRuleMetadata") + } + + // TODO: make it bounded + const BOUND: Bound = Bound::Unbounded; +} + +pub fn set_stable_version(version: Version) { + VERSION.with(|v| { + let mut v = v.borrow_mut(); + v.insert((), StorableVersion(version)); + }); +} + +pub fn get_stable_version() -> Version { + let version = VERSION.with(|v| { + let v = v.borrow(); + v.get(&()).expect("failed to get version") + }); + version.0 +} + +// Declare storage, initialized lazily thread_local! { static MEMORY_MANAGER: RefCell> = RefCell::new(MemoryManager::init(DefaultMemoryImpl::default())); } thread_local! { - pub static VERSION: RefCell> = RefCell::new( + pub static VERSION: RefCell> = RefCell::new( StableValue::init( MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(MEMORY_ID_VERSION))), ) ); + + pub static CONFIGS: RefCell> = RefCell::new( + StableMap::init( + MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(_MEMORY_ID_CONFIGS))), + ) + ); + + pub static RULES: RefCell> = RefCell::new( + StableMap::init( + MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(_MEMORY_ID_RULES))), + ) + ); + + pub static INCIDENTS: RefCell> = RefCell::new( + StableMap::init( + MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(_MEMORY_ID_INCIDENTS))), + ) + ); }