use super::*;
use crate::{
fleet_install_plan::{
FleetInstallPlan, PersistedFleetInstallPlan, PlannedCanisterCreationFunding,
PlannedFleetCoordinator, PlannedFleetSubnetRoot,
},
test_support::temp_dir,
};
use candid::Principal;
use canic_core::{
bootstrap::parse_config_model,
cdk::types::Cycles,
dto::fleet_registry::{FleetSubnetRootEntry, FleetSubnetRootStatus},
ids::{
AppId, CanonicalNetworkId, ComponentSpecAdmission, CyclesFundingBudget, FleetBinding,
FleetCoordinatorBinding, FleetId, FleetKey, FleetRegistryAuthority, FleetSubnetRootLimits,
FleetSubnetRootReleaseSet, ReleaseBuildId, ReleaseBuildNonce, ReleaseSetDigest, SubnetId,
},
};
#[test]
fn journal_recovers_exact_atomic_registry_activation_evidence() {
let root = temp_dir("fleet-registry-activation-journal");
let (plan, topology, joining) = fixture(&root);
let request = || PlanFleetRegistryActivationRequest {
fleet_install_plan: &plan,
component_topology: topology.clone(),
joining_registry: joining.clone(),
};
let planned = plan_fleet_registry_activation(request()).expect("plan activation");
let in_flight = begin_registry_activation(&planned).expect("begin activation");
let response = FleetRegistryActivationResponse {
previous_version: in_flight.journal.request.expected_registry.clone(),
version: FleetRegistryOps::version(
&in_flight.journal.active_registry.authority,
&topology,
&in_flight.journal.active_registry,
)
.expect("active version"),
};
let activated =
record_registry_activated(&in_flight, response.clone()).expect("record activation");
let manifest = FleetRegistryOps::manifest(
&activated.journal.active_registry.authority,
&topology,
&activated.journal.active_registry,
)
.expect("active manifest");
let verified =
record_registry_activation_verified(&activated, manifest.clone(), response.version.clone())
.expect("verify activation");
assert_eq!(
verified.journal.phase,
FleetRegistryActivationPhase::Verified
);
assert_eq!(verified.journal.sequence, 3);
assert_eq!(verified.journal.response, Some(response));
assert_eq!(verified.journal.verified_manifest, Some(manifest));
assert!(
verified
.journal
.active_registry
.fleet_subnet_roots
.iter()
.all(|entry| entry.status == FleetSubnetRootStatus::Active)
);
assert_eq!(
plan_fleet_registry_activation(request())
.expect("recover exact activation")
.journal,
verified.journal
);
}
#[test]
fn journal_rejects_changed_source_or_response_authority() {
let root = temp_dir("fleet-registry-activation-journal-conflict");
let (plan, topology, joining) = fixture(&root);
let planned = plan_fleet_registry_activation(PlanFleetRegistryActivationRequest {
fleet_install_plan: &plan,
component_topology: topology.clone(),
joining_registry: joining.clone(),
})
.expect("plan activation");
let in_flight = begin_registry_activation(&planned).expect("begin activation");
let mut wrong_response = FleetRegistryActivationResponse {
previous_version: in_flight.journal.request.expected_registry.clone(),
version: FleetRegistryOps::version(
&joining.authority,
&topology,
&in_flight.journal.active_registry,
)
.expect("active version"),
};
wrong_response.version.content_hash[0] ^= 1;
assert!(matches!(
record_registry_activated(&in_flight, wrong_response),
Err(FleetRegistryActivationJournalError::InvalidDocument { .. })
));
let mut changed = joining;
changed.fleet_subnet_roots[0]
.limits
.maximum_managed_canisters += 1;
assert!(matches!(
plan_fleet_registry_activation(PlanFleetRegistryActivationRequest {
fleet_install_plan: &plan,
component_topology: topology,
joining_registry: changed,
}),
Err(FleetRegistryActivationJournalError::InvalidDocument { .. }
| FleetRegistryActivationJournalError::ConflictingAuthority { .. })
));
}
fn fixture(root: &Path) -> (PersistedFleetInstallPlan, ComponentTopology, FleetRegistry) {
let topology = parse_config_model(
r#"
[app]
name = "toko"
[roles.root]
kind = "root"
package = "root"
[roles.project]
kind = "canister"
package = "project"
[component_specs.projects]
component_role = "project"
maximum_instances = 2
"#,
)
.expect("valid config")
.compile_component_topology()
.expect("Component Topology");
let release_build_id =
ReleaseBuildId::from_nonce(ReleaseBuildNonce::from_random_bytes([6; 32]));
let spec = topology
.component_specs
.first()
.expect("one Component Spec");
let admission = ComponentSpecAdmission {
component_spec: spec.component_spec.clone(),
spec_hash: spec.spec_hash,
maximum_root_instances: 2,
};
let limits = root_limits();
let fleet = FleetBinding {
fleet: FleetKey {
canonical_network_id: CanonicalNetworkId::public_ic(),
fleet_id: FleetId::from_generated_bytes([4; 32]),
},
app: AppId::from("toko"),
};
let authority = FleetRegistryAuthority {
binding: FleetCoordinatorBinding {
fleet: fleet.clone(),
coordinator_subnet: subnet(1),
coordinator: principal(33),
},
epoch: 1,
};
let release_set = FleetSubnetRootReleaseSet {
release_build_id,
manifest_digest: ReleaseSetDigest::from_bytes([5; 32]),
};
let topology_digest = topology
.project_for_admissions(std::slice::from_ref(&admission))
.expect("root topology")
.digest()
.expect("topology digest");
let root_plan = PlannedFleetSubnetRoot {
placement_subnet: subnet(2),
component_admissions: vec![admission.clone()],
component_topology_digest: topology_digest,
initial_release_set: release_set,
limits: limits.clone(),
creation_funding: PlannedCanisterCreationFunding::Cycles {
cycles: 2_000_000_000_000,
},
};
let plan = PersistedFleetInstallPlan {
plan: FleetInstallPlan {
fleet: fleet.clone(),
release_build_id,
application_artifact_union_digest: [3; 32],
coordinator: PlannedFleetCoordinator {
coordinator_subnet: subnet(1),
creation_funding: PlannedCanisterCreationFunding::Cycles {
cycles: 2_000_000_000_000,
},
},
fleet_subnet_roots: vec![root_plan],
},
digest: [9; 32],
path: root.join("fleet-install-plan.json"),
root_release_sets: Vec::new(),
};
let mut joining =
FleetRegistryOps::compile_genesis(&fleet.app, authority, &topology).expect("genesis");
joining = FleetRegistryOps::compile_joining(
&joining.authority,
&topology,
&joining,
FleetSubnetRootEntry {
placement_subnet: subnet(2),
fleet_subnet_root: principal(44),
component_admissions: vec![admission],
component_topology_digest: topology_digest,
active_release_set: release_set,
limits,
status: FleetSubnetRootStatus::Joining,
},
)
.expect("Joining root");
(plan, topology, joining)
}
fn principal(byte: u8) -> Principal {
Principal::from_slice(&[byte; 29])
}
fn subnet(byte: u8) -> SubnetId {
SubnetId::from_principal(principal(byte))
}
fn root_limits() -> FleetSubnetRootLimits {
FleetSubnetRootLimits {
maximum_component_instances: 2,
maximum_managed_canisters: 20_000,
maximum_registry_bytes: 2_097_152,
maximum_wasm_store_bytes: 268_435_456,
cycles_funding: CyclesFundingBudget {
window_secs: 3_600,
maximum_cycles: Cycles::new(2_000_000_000_000),
},
}
}