use super::{NnsAuthenticatedRegistryReplaySession, NnsRegistryReplaySession};
use crate::{
ic_registry::{
ROUTING_TABLE_KEY, SUBNET_LIST_KEY, proto::RoutingTable, proto::SubnetListRecord,
proto::SubnetRecord, routing_ranges_from_table, subnet_info_from_record, subnet_record_key,
},
subnet_catalog::{CatalogError, RoutingRange, SubnetInfo, canonicalize_subnet_catalog_content},
};
use candid::Principal;
use prost::Message;
use thiserror::Error as ThisError;
#[derive(Debug, Eq, PartialEq)]
pub struct NnsRegistrySubnetCatalogProjection<'a> {
session: &'a NnsRegistryReplaySession,
registry_version: u64,
subnets: Vec<SubnetInfo>,
routing_ranges: Vec<RoutingRange>,
}
impl<'a> NnsRegistrySubnetCatalogProjection<'a> {
#[must_use]
pub const fn replay_session(&self) -> &'a NnsRegistryReplaySession {
self.session
}
#[must_use]
pub const fn registry_version(&self) -> u64 {
self.registry_version
}
#[must_use]
pub fn subnets(&self) -> &[SubnetInfo] {
&self.subnets
}
#[must_use]
pub fn routing_ranges(&self) -> &[RoutingRange] {
&self.routing_ranges
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct NnsAuthenticatedRegistrySubnetCatalogProjection<'a> {
authenticated_session: &'a NnsAuthenticatedRegistryReplaySession,
projection: NnsRegistrySubnetCatalogProjection<'a>,
}
impl<'a> NnsAuthenticatedRegistrySubnetCatalogProjection<'a> {
#[must_use]
pub const fn authenticated_replay_session(&self) -> &'a NnsAuthenticatedRegistryReplaySession {
self.authenticated_session
}
#[must_use]
pub const fn projection(&self) -> &NnsRegistrySubnetCatalogProjection<'a> {
&self.projection
}
}
#[derive(Debug, ThisError)]
pub enum NnsRegistrySubnetCatalogProjectionError {
#[error(
"Registry replay session is incomplete: selected version {selected_version:?}, through version {through_version}"
)]
IncompleteSession {
selected_version: Option<u64>,
through_version: u64,
},
#[error("Registry replay selected version must be greater than zero for catalog projection")]
InvalidRegistryVersion,
#[error("complete Registry replay state is missing required key {key:?}")]
MissingRequiredRegistryKey {
key: String,
},
#[error("replayed Registry key {key:?} is not a valid {message}: {reason}")]
InvalidRegistryRecord {
key: String,
message: &'static str,
reason: String,
},
#[error(transparent)]
Catalog(#[from] CatalogError),
}
pub fn project_nns_registry_subnet_catalog(
session: &NnsRegistryReplaySession,
) -> Result<NnsRegistrySubnetCatalogProjection<'_>, NnsRegistrySubnetCatalogProjectionError> {
let selected_version = session.selected_version();
let (true, Some(registry_version)) = (
session.is_complete() && session.complete_state_digest().is_some(),
selected_version,
) else {
return Err(NnsRegistrySubnetCatalogProjectionError::IncompleteSession {
selected_version,
through_version: session.state().through_version(),
});
};
if registry_version == 0 {
return Err(NnsRegistrySubnetCatalogProjectionError::InvalidRegistryVersion);
}
let state = session.state();
let subnet_list =
decode_required_record::<SubnetListRecord>(state, SUBNET_LIST_KEY, "SubnetListRecord")?;
let routing_table =
decode_required_record::<RoutingTable>(state, ROUTING_TABLE_KEY, "RoutingTable")?;
let mut subnets = Vec::with_capacity(subnet_list.subnets.len());
for raw_subnet_principal in subnet_list.subnets {
let subnet_principal = Principal::try_from_slice(&raw_subnet_principal)
.map(|principal| principal.to_text())
.map_err(|error| invalid_record(SUBNET_LIST_KEY, "SubnetListRecord", error))?;
let record_key = subnet_record_key(&subnet_principal);
let record = decode_required_record::<SubnetRecord>(state, &record_key, "SubnetRecord")?;
subnets.push(subnet_info_from_record(&subnet_principal, &record));
}
let mut routing_ranges = routing_ranges_from_table(&routing_table)
.map_err(|error| invalid_record(ROUTING_TABLE_KEY, "RoutingTable", error))?;
canonicalize_subnet_catalog_content(&mut subnets, &mut routing_ranges)?;
Ok(NnsRegistrySubnetCatalogProjection {
session,
registry_version,
subnets,
routing_ranges,
})
}
pub fn project_nns_authenticated_registry_subnet_catalog(
session: &NnsAuthenticatedRegistryReplaySession,
) -> Result<
NnsAuthenticatedRegistrySubnetCatalogProjection<'_>,
NnsRegistrySubnetCatalogProjectionError,
> {
let projection = project_nns_registry_subnet_catalog(session.replay_session())?;
Ok(NnsAuthenticatedRegistrySubnetCatalogProjection {
authenticated_session: session,
projection,
})
}
fn decode_required_record<M>(
state: &super::NnsRegistryReplayState,
key: &str,
message: &'static str,
) -> Result<M, NnsRegistrySubnetCatalogProjectionError>
where
M: Message + Default,
{
let value = state.get(key.as_bytes()).ok_or_else(|| {
NnsRegistrySubnetCatalogProjectionError::MissingRequiredRegistryKey {
key: key.to_string(),
}
})?;
M::decode(value.value()).map_err(|error| invalid_record(key, message, error))
}
fn invalid_record(
key: &str,
message: &'static str,
error: impl ToString,
) -> NnsRegistrySubnetCatalogProjectionError {
NnsRegistrySubnetCatalogProjectionError::InvalidRegistryRecord {
key: key.to_string(),
message,
reason: error.to_string(),
}
}