use crate::{
cdk::{candid::Principal, types::Cycles},
ids::CanisterRole,
};
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, BTreeSet},
fmt,
};
mod defaults {
use super::Cycles;
use crate::cdk::types::TC;
pub const fn initial_cycles() -> Cycles {
Cycles::new(5_000_000_000_000)
}
pub const fn topup_threshold() -> Cycles {
Cycles::new(10 * TC)
}
pub const fn topup_amount() -> Cycles {
Cycles::new(5 * TC)
}
}
const IMPLICIT_WASM_STORE_ROLE: CanisterRole = CanisterRole::WASM_STORE;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SubnetConfig {
#[serde(default)]
pub canisters: BTreeMap<CanisterRole, CanisterConfig>,
#[serde(default)]
pub auto_create: BTreeSet<CanisterRole>,
#[serde(default)]
pub subnet_index: BTreeSet<CanisterRole>,
#[serde(default)]
pub pool: CanisterPool,
}
impl SubnetConfig {
#[must_use]
pub fn get_canister(&self, role: &CanisterRole) -> Option<CanisterConfig> {
self.canisters.get(role).cloned().or_else(|| {
if *role == IMPLICIT_WASM_STORE_ROLE {
Some(implicit_wasm_store_canister_config())
} else {
None
}
})
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct PoolImport {
#[serde(default)]
pub initial: Option<u16>,
#[serde(default)]
pub local: Vec<Principal>,
#[serde(default)]
pub ic: Vec<Principal>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CanisterPool {
pub minimum_size: u8,
#[serde(default)]
pub import: PoolImport,
}
fn implicit_wasm_store_canister_config() -> CanisterConfig {
CanisterConfig {
kind: CanisterKind::Singleton,
initial_cycles: defaults::initial_cycles(),
topup: None,
randomness: RandomnessConfig::default(),
scaling: None,
sharding: None,
directory: None,
auth: CanisterAuthConfig::default(),
standards: StandardsCanisterConfig::default(),
metrics: MetricsCanisterConfig::default(),
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CanisterAuthConfig {
#[serde(default)]
pub delegated_token_signer: bool,
#[serde(default)]
pub role_attestation_cache: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct StandardsCanisterConfig {
#[serde(default)]
pub icrc21: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct CanisterConfig {
pub kind: CanisterKind,
#[serde(
default = "defaults::initial_cycles",
deserialize_with = "Cycles::from_config"
)]
pub initial_cycles: Cycles,
#[serde(default)]
pub topup: Option<TopupPolicy>,
#[serde(default)]
pub randomness: RandomnessConfig,
#[serde(default)]
pub scaling: Option<ScalingConfig>,
#[serde(default)]
pub sharding: Option<ShardingConfig>,
#[serde(default)]
pub directory: Option<DirectoryConfig>,
#[serde(default)]
pub auth: CanisterAuthConfig,
#[serde(default)]
pub standards: StandardsCanisterConfig,
#[serde(default)]
pub metrics: MetricsCanisterConfig,
}
impl CanisterConfig {
#[must_use]
pub fn resolved_metrics_profile(&self, role: &CanisterRole) -> MetricsProfile {
if let Some(profile) = self.metrics.profile {
return profile;
}
if self.kind == CanisterKind::Root || role.is_root() {
return MetricsProfile::Root;
}
if role.is_wasm_store() {
return MetricsProfile::Storage;
}
if self.scaling.is_some() || self.sharding.is_some() || self.directory.is_some() {
return MetricsProfile::Hub;
}
MetricsProfile::Leaf
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MetricsCanisterConfig {
#[serde(default)]
pub profile: Option<MetricsProfile>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MetricsProfile {
Leaf,
Hub,
Storage,
Root,
Full,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CanisterKind {
Root,
Singleton,
Replica,
Shard,
Instance,
}
impl fmt::Display for CanisterKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = match self {
Self::Root => "root",
Self::Singleton => "singleton",
Self::Replica => "replica",
Self::Shard => "shard",
Self::Instance => "instance",
};
f.write_str(label)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TopupPolicy {
#[serde(
default = "defaults::topup_threshold",
deserialize_with = "Cycles::from_config"
)]
pub threshold: Cycles,
#[serde(
default = "defaults::topup_amount",
deserialize_with = "Cycles::from_config"
)]
pub amount: Cycles,
}
impl Default for TopupPolicy {
fn default() -> Self {
Self {
threshold: defaults::topup_threshold(),
amount: defaults::topup_amount(),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct RandomnessConfig {
pub enabled: bool,
pub reseed_interval_secs: u64,
pub source: RandomnessSource,
}
impl Default for RandomnessConfig {
fn default() -> Self {
Self {
enabled: true,
reseed_interval_secs: 3600,
source: RandomnessSource::Ic,
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum RandomnessSource {
#[default]
Ic,
Time,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ScalingConfig {
#[serde(default)]
pub pools: BTreeMap<String, ScalePool>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ScalePool {
pub canister_role: CanisterRole,
#[serde(default)]
pub policy: ScalePoolPolicy,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct ScalePoolPolicy {
pub initial_workers: u32,
pub min_workers: u32,
pub max_workers: u32,
}
impl Default for ScalePoolPolicy {
fn default() -> Self {
Self {
initial_workers: 1,
min_workers: 1,
max_workers: 32,
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShardingConfig {
#[serde(default)]
pub pools: BTreeMap<String, ShardPool>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DirectoryConfig {
#[serde(default)]
pub pools: BTreeMap<String, DirectoryPool>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DirectoryPool {
pub canister_role: CanisterRole,
pub key_name: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ShardPool {
pub canister_role: CanisterRole,
#[serde(default)]
pub policy: ShardPoolPolicy,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, default)]
pub struct ShardPoolPolicy {
pub capacity: u32,
pub initial_shards: u32,
pub max_shards: u32,
}
impl Default for ShardPoolPolicy {
fn default() -> Self {
Self {
capacity: 1_000,
initial_shards: 1,
max_shards: 4,
}
}
}
#[cfg(test)]
mod tests;