Skip to main content

ic_query/subnet_catalog/
error.rs

1use crate::subnet_catalog::RoutingRange;
2use thiserror::Error as ThisError;
3
4///
5/// SubnetCatalogField
6///
7/// Typed field identity known by the Subnet Catalog collector or validator.
8///
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum SubnetCatalogField {
12    /// Principal supplied without a narrower catalog subject.
13    Principal,
14    /// Caller-supplied canister principal to resolve.
15    CanisterPrincipal,
16    /// One principal entry in the Registry Subnet list.
17    SubnetListEntry,
18    /// The range object in one routing-table entry.
19    RoutingTableRange,
20    /// The target Subnet id in one routing-table entry.
21    RoutingTableSubnetId,
22    /// The starting canister id in one routing range.
23    RoutingRangeStart,
24    /// The ending canister id in one routing range.
25    RoutingRangeEnd,
26    /// Catalog network provenance.
27    Network,
28    /// Catalog Registry-canister provenance.
29    RegistryCanister,
30    /// Catalog Registry-version provenance.
31    RegistryVersion,
32    /// Catalog source endpoint provenance.
33    SourceEndpoint,
34    /// One Subnet principal field.
35    SubnetPrincipal,
36    /// Catalog collection timestamp provenance.
37    CollectionTimestamp,
38    /// Catalog classification evidence.
39    Classification,
40    /// Catalog agreement digest evidence.
41    AgreementDigest,
42    /// Catalog authority digest evidence.
43    CatalogDigest,
44    /// Catalog provenance not narrowed to another field.
45    Provenance,
46}
47
48impl std::fmt::Display for SubnetCatalogField {
49    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        formatter.write_str(match self {
51            Self::Principal => "principal",
52            Self::CanisterPrincipal => "canister_principal",
53            Self::SubnetListEntry => "subnet_list_entry",
54            Self::RoutingTableRange => "routing_table_range",
55            Self::RoutingTableSubnetId => "routing_range.subnet_principal",
56            Self::RoutingRangeStart => "start_canister_id",
57            Self::RoutingRangeEnd => "end_canister_id",
58            Self::Network => "provenance.network",
59            Self::RegistryCanister => "provenance.registry_canister_id",
60            Self::RegistryVersion => "provenance.registry_version",
61            Self::SourceEndpoint => "provenance.source_endpoints",
62            Self::SubnetPrincipal => "subnet_principal",
63            Self::CollectionTimestamp => "provenance.fetched_at",
64            Self::Classification => "classification",
65            Self::AgreementDigest => "provenance.agreement_digest",
66            Self::CatalogDigest => "catalog_digest",
67            Self::Provenance => "provenance",
68        })
69    }
70}
71
72///
73/// CatalogError
74///
75/// Errors returned while parsing, validating, or resolving a subnet catalog.
76///
77
78#[derive(Debug, ThisError)]
79pub enum CatalogError {
80    /// Catalog JSON could not be encoded or decoded.
81    #[error(transparent)]
82    Json(#[from] serde_json::Error),
83
84    /// The catalog uses a schema other than the current version-1 contract.
85    #[error("unsupported subnet catalog schema version {found}; supported version is {supported}")]
86    UnsupportedSchemaVersion {
87        /// Schema identifier found in the input.
88        found: u32,
89        /// Current supported schema identifier.
90        supported: u32,
91    },
92
93    /// Catalog network identity does not match the required authority.
94    #[error("subnet catalog network is {actual:?}; expected {expected:?}")]
95    NetworkMismatch {
96        /// Required network identity.
97        expected: String,
98        /// Catalog network identity.
99        actual: String,
100    },
101
102    /// Catalog Registry canister identity does not match mainnet authority.
103    #[error("subnet catalog Registry canister is {actual}; expected {expected}")]
104    RegistryCanisterMismatch {
105        /// Required Registry canister principal.
106        expected: String,
107        /// Catalog Registry canister principal.
108        actual: String,
109    },
110
111    /// Registry version zero cannot identify an authority snapshot.
112    #[error("subnet catalog Registry version must be greater than zero")]
113    InvalidRegistryVersion,
114
115    /// The catalog contains no Subnet records.
116    #[error("subnet catalog must contain at least one subnet")]
117    EmptySubnets,
118
119    /// The catalog contains no routing authority ranges.
120    #[error("subnet catalog must contain at least one routing range")]
121    EmptyRoutingRanges,
122
123    /// A catalog identity or routing boundary is not a valid IC principal.
124    #[error("invalid principal in {field}: {value}: {reason}")]
125    InvalidPrincipal {
126        /// Catalog field containing the invalid identity.
127        field: SubnetCatalogField,
128        /// Rejected principal text.
129        value: String,
130        /// Principal parsing diagnostic.
131        reason: String,
132    },
133
134    /// Multiple Subnet rows claim the same principal.
135    #[error("duplicate subnet principal in catalog: {subnet_principal}")]
136    DuplicateSubnet {
137        /// Repeated Subnet principal.
138        subnet_principal: String,
139    },
140
141    /// Subnet rows are not strictly ordered by canonical principal text.
142    #[error("noncanonical subnet order: {previous} must sort before {current}")]
143    NonCanonicalSubnetOrder {
144        /// Previous Subnet principal.
145        previous: String,
146        /// Current Subnet principal.
147        current: String,
148    },
149
150    /// A routing range points to a Subnet absent from the catalog.
151    #[error("routing range references unknown subnet: {subnet_principal}")]
152    UnknownRoutingSubnet {
153        /// Target Subnet principal missing its record.
154        subnet_principal: String,
155    },
156
157    /// A routing range's principal-byte boundaries are reversed.
158    #[error(
159        "invalid routing range for {subnet_principal}: start {start_canister_id} sorts after end {end_canister_id}"
160    )]
161    InvalidRoutingRange {
162        /// Subnet to which this range routes.
163        subnet_principal: String,
164        /// Inclusive start principal of the rejected range.
165        start_canister_id: String,
166        /// Inclusive end principal of the rejected range.
167        end_canister_id: String,
168    },
169
170    /// Two inclusive routing ranges cover a common principal.
171    #[error("overlapping routing ranges: {first} overlaps {second}")]
172    OverlappingRoutingRanges {
173        /// Earlier range in canonical routing order.
174        first: Box<RoutingRange>,
175        /// Later range that overlaps the earlier range.
176        second: Box<RoutingRange>,
177    },
178
179    /// Routing ranges are not strictly ordered by principal bytes and target.
180    #[error("noncanonical routing range order: {previous} must sort before {current}")]
181    NonCanonicalRoutingOrder {
182        /// Previous routing range.
183        previous: Box<RoutingRange>,
184        /// Current routing range.
185        current: Box<RoutingRange>,
186    },
187
188    /// Derived Subnet kind contradicts its raw Registry numeric code.
189    #[error(
190        "subnet {subnet_principal} kind {actual} contradicts Registry subnet_type={registry_subnet_type}; expected {expected}"
191    )]
192    SubnetKindMismatch {
193        /// Subnet principal.
194        subnet_principal: String,
195        /// Raw Registry numeric discriminant.
196        registry_subnet_type: i32,
197        /// Kind required by the raw discriminant.
198        expected: String,
199        /// Kind supplied by the raw catalog.
200        actual: String,
201    },
202
203    /// Charging default contradicts the raw Registry Subnet kind.
204    #[error(
205        "subnet {subnet_principal} charges_apply_by_default={actual} contradicts expected {expected}"
206    )]
207    ChargingPolicyMismatch {
208        /// Subnet principal.
209        subnet_principal: String,
210        /// Charging default required by the raw kind.
211        expected: bool,
212        /// Charging default supplied by the raw catalog.
213        actual: bool,
214    },
215
216    /// Classification metadata contradicts its policy or source.
217    #[error("subnet {subnet_principal} classification field {field} is invalid: {reason}")]
218    ClassificationMismatch {
219        /// Subnet principal.
220        subnet_principal: String,
221        /// Invalid classification field.
222        field: &'static str,
223        /// Deterministic mismatch reason.
224        reason: String,
225    },
226
227    /// A provenance timestamp is not valid canonical UTC text.
228    #[error("invalid catalog timestamp in {field}: {value:?}")]
229    InvalidTimestamp {
230        /// Timestamp field.
231        field: &'static str,
232        /// Invalid timestamp text.
233        value: String,
234    },
235
236    /// A provenance timestamp is beyond the caller's future-skew policy.
237    #[error(
238        "catalog timestamp in {field} is in the future: {value}; latest accepted unix time is {latest_allowed_unix_secs}"
239    )]
240    FutureTimestamp {
241        /// Timestamp field.
242        field: &'static str,
243        /// Future timestamp text.
244        value: String,
245        /// Latest accepted Unix time.
246        latest_allowed_unix_secs: u64,
247    },
248
249    /// A source endpoint is not a clean credential-free HTTP(S) base URL.
250    #[error("invalid catalog source endpoint {endpoint:?}: {reason}")]
251    InvalidSourceEndpoint {
252        /// Invalid endpoint.
253        endpoint: String,
254        /// Endpoint validation reason.
255        reason: String,
256    },
257
258    /// Provenance evidence is internally inconsistent.
259    #[error("invalid catalog provenance field {field}: {reason}")]
260    InvalidProvenance {
261        /// Invalid provenance field.
262        field: &'static str,
263        /// Deterministic validation reason.
264        reason: String,
265    },
266
267    /// Classification policy schema is not supported by this build.
268    #[error("classification policy schema {found} is unsupported; expected {supported}")]
269    ClassificationPolicyVersionMismatch {
270        /// Catalog policy schema.
271        found: u32,
272        /// Supported policy schema.
273        supported: u32,
274    },
275
276    /// Classification policy digest does not match this build.
277    #[error("classification policy digest mismatch: found {actual}; expected {expected}")]
278    ClassificationPolicyDigestMismatch {
279        /// Expected policy digest.
280        expected: String,
281        /// Catalog policy digest.
282        actual: String,
283    },
284
285    /// Resolver implementation identity does not match this build.
286    #[error(
287        "resolver policy mismatch: version {actual_version} backend {actual_backend:?}; expected version {expected_version} backend {expected_backend:?}"
288    )]
289    ResolverPolicyMismatch {
290        /// Expected resolver schema.
291        expected_version: u32,
292        /// Catalog resolver schema.
293        actual_version: u32,
294        /// Expected resolver backend.
295        expected_backend: String,
296        /// Catalog resolver backend.
297        actual_backend: String,
298    },
299
300    /// Catalog digest is not exactly 32 lowercase hexadecimal bytes.
301    #[error("invalid catalog digest {value:?}; expected 64 lowercase hexadecimal characters")]
302    InvalidCatalogDigest {
303        /// Invalid digest text.
304        value: String,
305    },
306
307    /// Agreement digest is absent or not exactly 32 lowercase hexadecimal bytes.
308    #[error(
309        "invalid catalog agreement digest {value:?}; expected 64 lowercase hexadecimal characters"
310    )]
311    InvalidAgreementDigest {
312        /// Invalid digest text.
313        value: String,
314    },
315
316    /// Stored agreement digest does not match the canonical Registry payload.
317    #[error("catalog agreement digest mismatch: found {actual}; expected {expected}")]
318    AgreementDigestMismatch {
319        /// Recomputed agreement digest.
320        expected: String,
321        /// Stored agreement digest.
322        actual: String,
323    },
324
325    /// Stored catalog digest does not match the canonical authority payload.
326    #[error("catalog digest mismatch: found {actual}; expected {expected}")]
327    CatalogDigestMismatch {
328        /// Recomputed digest.
329        expected: String,
330        /// Stored digest.
331        actual: String,
332    },
333
334    /// An exact Subnet lookup did not match a catalog row.
335    #[error("subnet principal {subnet_principal} was not found in the cached catalog")]
336    UnknownSubnet {
337        /// Requested Subnet principal.
338        subnet_principal: String,
339    },
340
341    /// A Subnet principal prefix has no catalog match.
342    #[error("principal prefix {prefix:?} did not match cached subnet principals")]
343    PrincipalPrefixNotFound {
344        /// Requested principal prefix.
345        prefix: String,
346    },
347
348    /// A Subnet principal prefix matches more than one catalog row.
349    #[error("principal prefix {prefix:?} is ambiguous; matches: {matches:?}")]
350    AmbiguousPrincipalPrefix {
351        /// Requested principal prefix.
352        prefix: String,
353        /// Canonical Subnet principals matching the prefix.
354        matches: Vec<String>,
355    },
356
357    /// No routing range covers the requested canister at this Registry version.
358    #[error(
359        "canister principal {canister_principal} was not covered by cached routing ranges at registry_version={registry_version}, catalog_schema_version={catalog_schema_version}"
360    )]
361    RouteNotFound {
362        /// Canonical canister principal whose route was requested.
363        canister_principal: String,
364        /// Registry version represented by the catalog.
365        registry_version: u64,
366        /// Schema identifier retained by the catalog.
367        catalog_schema_version: u32,
368    },
369}