Skip to main content

ic_query/subnet_catalog/model/
validation.rs

1use 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    /// Validate schema, principal syntax, and routing references.
10    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}