use crate::{
cache_file::{CacheFileError, HostCacheError},
ic_registry::RegistryFetchError,
network::enforce_mainnet_network_with,
runtime::RuntimeError,
subnet_catalog::{CatalogAssurance, CatalogError},
};
use std::path::PathBuf;
use thiserror::Error as ThisError;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubnetCatalogErrorCode {
UnsupportedNetwork,
MissingCatalog,
InvalidReadPolicy,
InvalidSourceSelection,
SourceEvidenceMismatch,
InsufficientAssurance,
AgreementEndpoint,
AgreementMismatch,
RegistryQueryCallCountOverflow,
RuntimeAdapter,
CacheOperation,
RegistryRefresh,
CatalogValidation,
}
impl SubnetCatalogErrorCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::UnsupportedNetwork => "unsupported_network",
Self::MissingCatalog => "missing_catalog",
Self::InvalidReadPolicy => "invalid_read_policy",
Self::InvalidSourceSelection => "invalid_source_selection",
Self::SourceEvidenceMismatch => "source_evidence_mismatch",
Self::InsufficientAssurance => "insufficient_assurance",
Self::AgreementEndpoint => "agreement_endpoint",
Self::AgreementMismatch => "agreement_mismatch",
Self::RegistryQueryCallCountOverflow => "registry_query_call_count_overflow",
Self::RuntimeAdapter => "runtime_adapter",
Self::CacheOperation => "cache_operation",
Self::RegistryRefresh => "registry_refresh",
Self::CatalogValidation => "catalog_validation",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubnetCatalogErrorCategory {
Input,
Missing,
CacheIo,
Confinement,
Network,
Authority,
Validation,
Runtime,
}
impl SubnetCatalogErrorCategory {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Input => "input",
Self::Missing => "missing",
Self::CacheIo => "cache_io",
Self::Confinement => "confinement",
Self::Network => "network",
Self::Authority => "authority",
Self::Validation => "validation",
Self::Runtime => "runtime",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubnetCatalogRetryability {
Retryable,
NotRetryable,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SubnetCatalogRemediation {
UseMainnet,
RefreshCatalog,
}
#[derive(Debug, ThisError)]
pub enum SubnetCatalogHostError {
#[error("unsupported Subnet Catalog network {network:?}; expected mainnet identity \"ic\"")]
UnsupportedNetwork { network: String },
#[error("subnet catalog cache is missing at {}", path.display())]
MissingCatalog { path: PathBuf },
#[error("invalid subnet catalog read policy: {reason}")]
InvalidReadPolicy {
reason: String,
},
#[error("invalid subnet catalog source selection: {reason}")]
InvalidSourceSelection {
reason: String,
},
#[error(
"subnet catalog source returned assurance {} and endpoints {actual_endpoints:?}; requested single endpoint was {requested:?}",
actual_assurance.as_str()
)]
SourceEvidenceMismatch {
requested: String,
actual_assurance: CatalogAssurance,
actual_endpoints: Vec<String>,
},
#[error(
"subnet catalog assurance {} does not meet required minimum {}",
actual.as_str(),
required.as_str()
)]
InsufficientAssurance {
required: CatalogAssurance,
actual: CatalogAssurance,
},
#[error("subnet catalog agreement endpoint {endpoint:?} failed: {source}")]
AgreementEndpoint {
endpoint: String,
source: Box<Self>,
},
#[error(
"subnet catalog endpoint agreement failed: {endpoint:?} returned registry_version={registry_version} digest={agreement_digest}, but {reference_endpoint:?} returned registry_version={reference_registry_version} digest={reference_agreement_digest}"
)]
AgreementMismatch {
reference_endpoint: String,
reference_registry_version: u64,
reference_agreement_digest: String,
endpoint: String,
registry_version: u64,
agreement_digest: String,
},
#[error("subnet catalog Registry query call count overflowed u64")]
RegistryQueryCallCountOverflow,
#[error(transparent)]
RuntimeAdapter(#[from] RuntimeError),
#[error(transparent)]
Cache(#[from] HostCacheError),
#[error("live NNS registry refresh failed: {0}")]
RegistryRefresh(#[from] RegistryFetchError),
#[error(transparent)]
Catalog(#[from] CatalogError),
}
impl SubnetCatalogHostError {
#[must_use]
pub const fn code(&self) -> SubnetCatalogErrorCode {
match self {
Self::UnsupportedNetwork { .. } => SubnetCatalogErrorCode::UnsupportedNetwork,
Self::MissingCatalog { .. } => SubnetCatalogErrorCode::MissingCatalog,
Self::InvalidReadPolicy { .. } => SubnetCatalogErrorCode::InvalidReadPolicy,
Self::InvalidSourceSelection { .. } => SubnetCatalogErrorCode::InvalidSourceSelection,
Self::SourceEvidenceMismatch { .. } => SubnetCatalogErrorCode::SourceEvidenceMismatch,
Self::InsufficientAssurance { .. } => SubnetCatalogErrorCode::InsufficientAssurance,
Self::AgreementEndpoint { .. } => SubnetCatalogErrorCode::AgreementEndpoint,
Self::AgreementMismatch { .. } => SubnetCatalogErrorCode::AgreementMismatch,
Self::RegistryQueryCallCountOverflow => {
SubnetCatalogErrorCode::RegistryQueryCallCountOverflow
}
Self::RuntimeAdapter(_) => SubnetCatalogErrorCode::RuntimeAdapter,
Self::Cache(_) => SubnetCatalogErrorCode::CacheOperation,
Self::RegistryRefresh(_) => SubnetCatalogErrorCode::RegistryRefresh,
Self::Catalog(_) => SubnetCatalogErrorCode::CatalogValidation,
}
}
#[must_use]
pub fn category(&self) -> SubnetCatalogErrorCategory {
match self {
Self::UnsupportedNetwork { .. }
| Self::InvalidReadPolicy { .. }
| Self::InvalidSourceSelection { .. } => SubnetCatalogErrorCategory::Input,
Self::MissingCatalog { .. } => SubnetCatalogErrorCategory::Missing,
Self::Cache(HostCacheError::Operation {
source:
CacheFileError::UnsupportedConfinementPlatform { .. }
| CacheFileError::Confinement { .. }
| CacheFileError::UnsafeManagedPermissions { .. },
..
}) => SubnetCatalogErrorCategory::Confinement,
Self::Cache(_) => SubnetCatalogErrorCategory::CacheIo,
Self::RegistryRefresh(_) => SubnetCatalogErrorCategory::Network,
Self::AgreementEndpoint { source, .. } => source.category(),
Self::SourceEvidenceMismatch { .. }
| Self::InsufficientAssurance { .. }
| Self::AgreementMismatch { .. }
| Self::Catalog(
CatalogError::NetworkMismatch { .. }
| CatalogError::RegistryCanisterMismatch { .. }
| CatalogError::UnsupportedAssurance { .. }
| CatalogError::ClassificationPolicyVersionMismatch { .. }
| CatalogError::ClassificationPolicyDigestMismatch { .. }
| CatalogError::ResolverPolicyMismatch { .. }
| CatalogError::CatalogDigestMismatch { .. },
) => SubnetCatalogErrorCategory::Authority,
Self::RuntimeAdapter(_) => SubnetCatalogErrorCategory::Runtime,
Self::RegistryQueryCallCountOverflow | Self::Catalog(_) => {
SubnetCatalogErrorCategory::Validation
}
}
}
#[must_use]
pub fn retryability(&self) -> SubnetCatalogRetryability {
match self {
Self::AgreementEndpoint { source, .. } => source.retryability(),
Self::RegistryRefresh(_) | Self::AgreementMismatch { .. } | Self::RuntimeAdapter(_) => {
SubnetCatalogRetryability::Retryable
}
_ => SubnetCatalogRetryability::NotRetryable,
}
}
#[must_use]
pub const fn remediation(&self) -> Option<SubnetCatalogRemediation> {
match self {
Self::UnsupportedNetwork { .. } => Some(SubnetCatalogRemediation::UseMainnet),
Self::MissingCatalog { .. } => Some(SubnetCatalogRemediation::RefreshCatalog),
_ => None,
}
}
}
pub(super) fn enforce_mainnet_network(network: &str) -> Result<(), SubnetCatalogHostError> {
enforce_mainnet_network_with(network, |network| {
SubnetCatalogHostError::UnsupportedNetwork { network }
})
}
pub(super) fn subnet_cache_error(err: CacheFileError) -> SubnetCatalogHostError {
HostCacheError::operation("subnet catalog", err).into()
}