pub mod attestation;
pub mod cycles;
pub mod install;
pub mod intent;
pub mod log;
pub mod timer;
use crate::{
InternalError, InternalErrorOrigin, VERSION,
domain::policy::env::{EnvInput, EnvPolicyError, validate_or_default},
dto::{abi::v1::CanisterInitPayload, subnet::SubnetIdentity},
ids::SubnetRole,
ops::{
config::ConfigOps,
ic::{IcOps, network::NetworkOps},
runtime::{
env::EnvOps,
memory::{MemoryRegistryInitSummary, MemoryRegistryOps},
},
storage::{
directory::{app::AppDirectoryOps, subnet::SubnetDirectoryOps},
registry::subnet::SubnetRegistryOps,
state::app::AppStateOps,
},
},
workflow::{self, env::EnvWorkflow, prelude::*},
};
pub struct RuntimeWorkflow;
impl RuntimeWorkflow {
pub fn start_all() {
workflow::runtime::log::LogRetentionWorkflow::start();
workflow::runtime::cycles::CycleTrackerWorkflow::start();
}
pub fn start_all_with_attestation_cache() {
log_delegation_proof_cache_policy();
workflow::runtime::attestation::AttestationKeyCacheWorkflow::start();
Self::start_all();
}
pub fn start_all_root() -> Result<(), InternalError> {
EnvOps::require_root().map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("root context required: {err}"),
)
})?;
Self::start_all();
workflow::pool::scheduler::PoolSchedulerWorkflow::start();
Ok(())
}
}
fn log_delegation_proof_cache_policy() {
match ConfigOps::delegation_proof_cache_policy() {
Ok(policy) => crate::log!(
Topic::Auth,
Info,
"delegation proof cache policy profile={} capacity={} active_window_secs={}",
policy.profile.as_str(),
policy.capacity,
policy.active_window_secs
),
Err(err) => crate::log!(
Topic::Auth,
Warn,
"delegation proof cache policy unavailable at runtime startup: {err}"
),
}
}
fn log_memory_summary(summary: &MemoryRegistryInitSummary) {
for range in &summary.ranges {
let used = summary
.entries
.iter()
.filter(|entry| entry.id >= range.start && entry.id <= range.end)
.count();
crate::log!(
Topic::Memory,
Info,
"💾 memory.range: {} [{}-{}] ({}/{} slots used)",
range.crate_name,
range.start,
range.end,
used,
range.end - range.start + 1,
);
}
}
fn init_post_upgrade_memory_registry() -> Result<MemoryRegistryInitSummary, InternalError> {
MemoryRegistryOps::bootstrap_registry().map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("memory init failed: {err}"),
)
})
}
pub fn init_root_canister(identity: SubnetIdentity) -> Result<(), InternalError> {
let memory_summary = match MemoryRegistryOps::bootstrap_registry() {
Ok(summary) => summary,
Err(err) => {
return Err(InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("memory init failed: {err}"),
));
}
};
crate::log::set_ready();
IcOps::println("");
IcOps::println("");
IcOps::println("");
crate::log!(
Topic::Init,
Info,
"🔧 --------------------- canic v{VERSION} -----------------------",
);
crate::log!(Topic::Init, Info, "🏁 init: root ({identity:?})");
log_memory_summary(&memory_summary);
let self_pid = IcOps::canister_self();
let (subnet_pid, subnet_role, prime_root_pid) = match identity {
SubnetIdentity::Prime => (self_pid, SubnetRole::PRIME, self_pid),
SubnetIdentity::Standard(params) => (self_pid, params.subnet_type, params.prime_root_pid),
SubnetIdentity::Manual => (IcOps::canister_self(), SubnetRole::PRIME, self_pid),
};
let input = EnvInput {
prime_root_pid: Some(prime_root_pid),
subnet_role: Some(subnet_role),
subnet_pid: Some(subnet_pid),
root_pid: Some(self_pid),
canister_role: Some(CanisterRole::ROOT),
parent_pid: Some(prime_root_pid),
};
let network = NetworkOps::build_network().ok_or_else(|| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
"runtime network unavailable; set DFX_NETWORK=local|ic at build time".to_string(),
)
})?;
crate::log!(Topic::Init, Info, "build network: {network}");
let validated = match validate_or_default(network, input) {
Ok(validated) => validated,
Err(EnvPolicyError::MissingEnvFields(missing)) => {
return Err(InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("env args missing {missing}; local builds require explicit env fields"),
));
}
};
if let Err(err) = EnvOps::import_validated(validated) {
return Err(InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("env import failed: {err}"),
));
}
let app_mode = ConfigOps::app_init_mode().map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("app mode init failed: {err}"),
)
})?;
AppStateOps::init_mode(app_mode);
let created_at = IcOps::now_secs();
SubnetRegistryOps::register_root(self_pid, created_at);
RuntimeWorkflow::start_all_root().map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("root service startup failed: {err}"),
)
})?;
Ok(())
}
pub fn post_upgrade_root_canister_after_memory_init(
memory_summary: MemoryRegistryInitSummary,
) -> Result<(), InternalError> {
crate::log::set_ready();
crate::log!(Topic::Init, Info, "🏁 post_upgrade_root_canister");
log_memory_summary(&memory_summary);
RuntimeWorkflow::start_all_root().map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("root service startup failed: {err}"),
)
})?;
Ok(())
}
pub fn init_nonroot_canister(
canister_role: CanisterRole,
payload: CanisterInitPayload,
) -> Result<(), InternalError> {
init_nonroot_canister_internal(canister_role, payload, false)
}
pub fn init_nonroot_canister_with_attestation_cache(
canister_role: CanisterRole,
payload: CanisterInitPayload,
) -> Result<(), InternalError> {
init_nonroot_canister_internal(canister_role, payload, true)
}
fn init_nonroot_canister_internal(
canister_role: CanisterRole,
payload: CanisterInitPayload,
with_attestation_cache: bool,
) -> Result<(), InternalError> {
let memory_summary = MemoryRegistryOps::bootstrap_registry().map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("memory init failed: {err}"),
)
})?;
crate::log::set_ready();
crate::log!(Topic::Init, Info, "🏁 init: {}", canister_role);
log_memory_summary(&memory_summary);
EnvWorkflow::init_env_from_args(payload.env, canister_role).map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("env import failed: {err}"),
)
})?;
AppDirectoryOps::import_args_allow_incomplete(payload.app_directory).map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("app directory import failed: {err}"),
)
})?;
SubnetDirectoryOps::import_args_allow_incomplete(payload.subnet_directory).map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("subnet directory import failed: {err}"),
)
})?;
let app_mode = ConfigOps::app_init_mode().map_err(|err| {
InternalError::invariant(
InternalErrorOrigin::Workflow,
format!("app mode init failed: {err}"),
)
})?;
AppStateOps::init_mode(app_mode);
if with_attestation_cache {
RuntimeWorkflow::start_all_with_attestation_cache();
} else {
RuntimeWorkflow::start_all();
}
Ok(())
}
pub fn post_upgrade_nonroot_canister_after_memory_init(
canister_role: CanisterRole,
memory_summary: MemoryRegistryInitSummary,
) {
post_upgrade_nonroot_canister_after_memory_init_internal(canister_role, memory_summary, false);
}
pub fn post_upgrade_nonroot_canister_after_memory_init_with_attestation_cache(
canister_role: CanisterRole,
memory_summary: MemoryRegistryInitSummary,
) {
post_upgrade_nonroot_canister_after_memory_init_internal(canister_role, memory_summary, true);
}
fn post_upgrade_nonroot_canister_after_memory_init_internal(
canister_role: CanisterRole,
memory_summary: MemoryRegistryInitSummary,
with_attestation_cache: bool,
) {
crate::log::set_ready();
crate::log!(
Topic::Init,
Info,
"🏁 post_upgrade_nonroot_canister: {}",
canister_role
);
log_memory_summary(&memory_summary);
if with_attestation_cache {
RuntimeWorkflow::start_all_with_attestation_cache();
} else {
RuntimeWorkflow::start_all();
}
}
pub fn init_memory_registry_post_upgrade() -> Result<MemoryRegistryInitSummary, InternalError> {
init_post_upgrade_memory_registry()
}