ic_query/subnet_catalog/model/
validation.rs1use super::{RoutingRange, SubnetCatalog, SubnetInfo};
2use crate::subnet_catalog::{
3 CATALOG_SCHEMA_VERSION, CatalogError, parse_principal, principal_bytes,
4 resolver::routing_range_sorts_after,
5};
6use std::collections::BTreeSet;
7
8impl SubnetCatalog {
9 pub fn validate(&self) -> Result<(), CatalogError> {
11 if self.catalog_schema_version != CATALOG_SCHEMA_VERSION {
12 return Err(CatalogError::UnsupportedSchemaVersion {
13 found: self.catalog_schema_version,
14 supported: CATALOG_SCHEMA_VERSION,
15 });
16 }
17 if self.subnets.is_empty() {
18 return Err(CatalogError::EmptySubnets);
19 }
20 if self.routing_ranges.is_empty() {
21 return Err(CatalogError::EmptyRoutingRanges);
22 }
23 parse_principal(&self.registry_canister_id, "registry_canister_id")?;
24
25 let mut subnet_principals = BTreeSet::new();
26 for subnet in &self.subnets {
27 parse_principal(&subnet.subnet_principal, "subnet_principal")?;
28 if !subnet_principals.insert(subnet.subnet_principal.clone()) {
29 return Err(CatalogError::DuplicateSubnet {
30 subnet_principal: subnet.subnet_principal.clone(),
31 });
32 }
33 }
34
35 let mut validated_ranges = Vec::with_capacity(self.routing_ranges.len());
36 for range in &self.routing_ranges {
37 if !subnet_principals.contains(&range.subnet_principal) {
38 return Err(CatalogError::UnknownRoutingSubnet {
39 subnet_principal: range.subnet_principal.clone(),
40 });
41 }
42 let start = principal_bytes(&range.start_canister_id, "start_canister_id")?;
43 let end = principal_bytes(&range.end_canister_id, "end_canister_id")?;
44 parse_principal(&range.subnet_principal, "routing_range.subnet_principal")?;
45 if routing_range_sorts_after(&start, &end) {
46 return Err(CatalogError::InvalidRoutingRange {
47 subnet_principal: range.subnet_principal.clone(),
48 start_canister_id: range.start_canister_id.clone(),
49 end_canister_id: range.end_canister_id.clone(),
50 });
51 }
52 validated_ranges.push((range, start, end));
53 }
54 validated_ranges
55 .sort_by(|left, right| left.1.cmp(&right.1).then_with(|| left.2.cmp(&right.2)));
56 for pair in validated_ranges.windows(2) {
57 let (first, _, first_end) = &pair[0];
58 let (second, second_start, _) = &pair[1];
59 if second_start <= first_end {
60 return Err(CatalogError::OverlappingRoutingRanges {
61 first: Box::new((*first).clone()),
62 second: Box::new((*second).clone()),
63 });
64 }
65 }
66
67 Ok(())
68 }
69
70 #[must_use]
71 pub fn subnet_by_principal(&self, subnet_principal: &str) -> Option<&SubnetInfo> {
72 self.subnets
73 .iter()
74 .find(|subnet| subnet.subnet_principal == subnet_principal)
75 }
76
77 #[must_use]
78 pub fn routing_ranges_for_subnet(&self, subnet_principal: &str) -> Vec<&RoutingRange> {
79 self.routing_ranges
80 .iter()
81 .filter(|range| range.subnet_principal == subnet_principal)
82 .collect()
83 }
84}