ic_query/subnet_catalog/
mod.rs1mod host;
2mod model;
3mod report;
4mod resolver;
5mod text;
6mod time;
7
8use crate::ic_registry::DEFAULT_MAINNET_ENDPOINT;
9use candid::Principal;
10pub(crate) use host::{
11 LiveNnsRegistryRefreshSource, SubnetCatalogCacheRequest, SubnetCatalogHostError,
12 SubnetCatalogRefreshRequest, SubnetCatalogRefreshSource, load_or_refresh_subnet_catalog,
13 refresh_subnet_catalog,
14};
15#[cfg(test)]
16pub(crate) use host::{
17 load_cached_subnet_catalog, refresh_subnet_catalog_with_source, subnet_catalog_path,
18 subnet_catalog_refresh_lock_path,
19};
20pub use model::{
21 ClassificationSource, GeographicScope, RoutingRange, SubnetCatalog, SubnetInfo, SubnetKind,
22 SubnetSpecialization,
23};
24pub(crate) use report::{
25 CatalogStaleStatus, SubnetCatalogFilters, SubnetCatalogInfoReport, SubnetCatalogInfoRequest,
26 SubnetCatalogListReport, SubnetCatalogListRequest, SubnetCatalogRefreshReport,
27 build_subnet_catalog_info_report, build_subnet_catalog_list_report,
28};
29#[cfg(test)]
30pub(crate) use report::{SubnetCatalogSubnetRow, build_subnet_catalog_list_report_with_source};
31pub use resolver::{ResolveAs, ResolvedSubnet, ResolvedSubnetSubject};
32#[cfg(test)]
33pub(crate) use text::compact_principal;
34pub(crate) use text::{
35 subnet_catalog_info_report_text, subnet_catalog_list_report_text,
36 subnet_catalog_list_report_verbose_text, subnet_catalog_refresh_report_text,
37};
38use thiserror::Error as ThisError;
39#[cfg(test)]
40pub(crate) use time::parse_stale_after_duration;
41pub(crate) use time::{catalog_stale_status, format_utc_timestamp_secs};
42
43pub const CATALOG_SCHEMA_VERSION: u32 = 1;
44pub const MAINNET_NETWORK: &str = "ic";
45pub const MAINNET_REGISTRY_CANISTER_ID: &str = "rwlgt-iiaaa-aaaaa-aaaaa-cai";
46pub(crate) const DEFAULT_STALE_AFTER_SECONDS: u64 = 7 * 24 * 60 * 60;
47pub(crate) const DEFAULT_REFRESH_LOCK_STALE_SECONDS: u64 = 30 * 60;
48pub(crate) const DEFAULT_SUBNET_CATALOG_SOURCE_ENDPOINT: &str = DEFAULT_MAINNET_ENDPOINT;
49pub(crate) const SUBNET_CATALOG_LIST_REPORT_SCHEMA_VERSION: u32 = 1;
50pub(crate) const SUBNET_CATALOG_INFO_REPORT_SCHEMA_VERSION: u32 = 1;
51pub(crate) const SUBNET_CATALOG_REFRESH_REPORT_SCHEMA_VERSION: u32 = 1;
52
53#[derive(Debug, ThisError)]
57pub enum CatalogError {
58 #[error(transparent)]
59 Json(#[from] serde_json::Error),
60
61 #[error("unsupported subnet catalog schema version {found}; supported version is {supported}")]
62 UnsupportedSchemaVersion { found: u32, supported: u32 },
63
64 #[error("subnet catalog must contain at least one subnet")]
65 EmptySubnets,
66
67 #[error("subnet catalog must contain at least one routing range")]
68 EmptyRoutingRanges,
69
70 #[error("invalid principal in {field}: {value}: {reason}")]
71 InvalidPrincipal {
72 field: &'static str,
73 value: String,
74 reason: String,
75 },
76
77 #[error("duplicate subnet principal in catalog: {subnet_principal}")]
78 DuplicateSubnet { subnet_principal: String },
79
80 #[error("routing range references unknown subnet: {subnet_principal}")]
81 UnknownRoutingSubnet { subnet_principal: String },
82
83 #[error(
84 "invalid routing range for {subnet_principal}: start {start_canister_id} sorts after end {end_canister_id}"
85 )]
86 InvalidRoutingRange {
87 subnet_principal: String,
88 start_canister_id: String,
89 end_canister_id: String,
90 },
91
92 #[error("subnet principal {subnet_principal} was not found in the cached catalog")]
93 UnknownSubnet { subnet_principal: String },
94
95 #[error("principal prefix {prefix:?} did not match cached subnet principals")]
96 PrincipalPrefixNotFound { prefix: String },
97
98 #[error("principal prefix {prefix:?} is ambiguous; matches: {matches:?}")]
99 AmbiguousPrincipalPrefix {
100 prefix: String,
101 matches: Vec<String>,
102 },
103
104 #[error(
105 "canister principal {canister_principal} was not covered by cached routing ranges at registry_version={registry_version}, catalog_schema_version={catalog_schema_version}"
106 )]
107 RouteNotFound {
108 canister_principal: String,
109 registry_version: u64,
110 catalog_schema_version: u32,
111 },
112}
113
114pub fn parse_catalog_json(data: &str) -> Result<SubnetCatalog, CatalogError> {
116 let catalog = serde_json::from_str::<SubnetCatalog>(data)?;
117 catalog.validate()?;
118 Ok(catalog)
119}
120
121pub fn catalog_to_pretty_json(catalog: &SubnetCatalog) -> Result<String, CatalogError> {
123 Ok(serde_json::to_string_pretty(catalog)?)
124}
125
126pub fn canonical_principal_text(value: &str) -> Result<String, CatalogError> {
128 Ok(parse_principal(value, "principal")?.to_text())
129}
130
131pub(crate) fn parse_principal(value: &str, field: &'static str) -> Result<Principal, CatalogError> {
132 Principal::from_text(value).map_err(|err| CatalogError::InvalidPrincipal {
133 field,
134 value: value.to_string(),
135 reason: err.to_string(),
136 })
137}
138
139pub(crate) fn principal_bytes(value: &str, field: &'static str) -> Result<Vec<u8>, CatalogError> {
140 Ok(parse_principal(value, field)?.as_slice().to_vec())
141}
142
143#[cfg(test)]
144mod core_tests;
145#[cfg(test)]
146mod tests;