mod canister;
mod icrc_analytics;
mod icrc_index;
mod metric;
mod network;
mod node_provider_reward;
mod node_status;
mod replica_version;
#[cfg(test)]
mod tests;
use crate::{
ic::{
IC_DASHBOARD_AUTHORITY, IC_DASHBOARD_NETWORK, IC_DASHBOARD_REPORT_SCHEMA_VERSION,
IcDashboardReportProvenance, IcHostError, IcSourceRequest,
},
subnet_catalog::format_utc_timestamp_secs,
};
use candid::Principal;
pub use canister::{IcCanisterCollectionSource, IcCanisterSource};
pub use icrc_analytics::IcIcrcAnalyticsSource;
pub use icrc_index::IcIcrcIndexSource;
pub use metric::IcMetricSource;
pub use network::IcNetworkSource;
pub use node_provider_reward::IcNodeProviderRewardSource;
pub use node_status::IcNodeStatusSource;
pub use replica_version::IcReplicaVersionSource;
pub(super) use canister::{
canonical_canister_id, canonical_page_cursors, count_report_from_source, normalized_filters,
page_report_from_source, report_from_source, validate_page_cursor_exclusivity,
validate_page_limit,
};
pub(super) use icrc_analytics::{
icrc_indexed_count_report_from_source, icrc_token_value_report_from_source,
icrc_total_supply_report_from_source, validate_icrc_token_value_query,
validate_icrc_token_value_request, validate_icrc_total_supply_query,
validate_icrc_total_supply_request,
};
pub(super) use icrc_index::{
icrc_account_info_report_from_source, icrc_account_list_report_from_source,
icrc_holder_list_report_from_source, normalized_account_list_query, validate_account_id,
validate_holder_list_query,
};
pub(super) use metric::{
metric_report_from_source, validate_metric_query, validate_metric_request,
};
pub(super) use network::{
boundary_node_data_centers_report_from_source, daily_stats_report_from_source,
validate_daily_stats_query, validate_daily_stats_request,
};
pub(super) use node_provider_reward::{
node_provider_reward_history_report_from_source, node_provider_reward_info_report_from_source,
node_provider_reward_list_report_from_source, validate_node_provider_reward_history_query,
validate_node_provider_reward_history_request, validate_node_provider_reward_list_query,
};
pub(super) use node_status::node_status_snapshot_from_source;
pub(super) use replica_version::{
replica_version_info_report_from_source, replica_version_list_report_from_source,
validate_replica_version_id, validate_replica_version_list_query,
};
pub fn dashboard_source_request(endpoint: &str, now_unix_secs: u64) -> IcSourceRequest {
IcSourceRequest::new(
endpoint,
format_utc_timestamp_secs(now_unix_secs),
"ic-query",
)
}
pub fn validate_dashboard_network(network: &str) -> Result<(), IcHostError> {
if network == IC_DASHBOARD_NETWORK {
return Ok(());
}
Err(IcHostError::InvalidRequest {
field: "network",
reason: format!("must be the supported mainnet identity {IC_DASHBOARD_NETWORK:?}"),
})
}
pub fn validate_provenance(
expected: &IcSourceRequest,
actual: &IcSourceRequest,
) -> Result<(), IcHostError> {
for (field, expected, actual) in [
(
"source_endpoint",
expected.endpoint.as_str(),
actual.endpoint.as_str(),
),
(
"fetched_at",
expected.fetched_at.as_str(),
actual.fetched_at.as_str(),
),
(
"fetched_by",
expected.fetched_by.as_str(),
actual.fetched_by.as_str(),
),
] {
if actual != expected {
return invalid_source(format!(
"{field} is {actual:?}, expected requested value {expected:?}"
));
}
}
Ok(())
}
fn validate_offset_page(
offset: u64,
total: u64,
returned_count: usize,
total_field: &str,
overflow_reason: &str,
) -> Result<Option<u64>, IcHostError> {
let returned_count = u64::try_from(returned_count).unwrap_or(u64::MAX);
if returned_count > total {
return invalid_source(format!(
"returned_count {returned_count} exceeds {total_field} {total}"
));
}
if returned_count == 0 {
return Ok(None);
}
if offset >= total {
return invalid_source(format!(
"nonempty page starts at offset {offset}, but {total_field} is {total}"
));
}
let consumed = offset
.checked_add(returned_count)
.ok_or_else(|| invalid_source_value(overflow_reason))?;
if consumed > total {
return invalid_source(format!(
"page ending at offset {consumed} exceeds {total_field} {total}"
));
}
Ok((consumed < total).then_some(consumed))
}
pub fn canonical_request_principal(
field: &'static str,
value: &str,
) -> Result<String, IcHostError> {
Principal::from_text(value)
.map(|principal| principal.to_text())
.map_err(|error| IcHostError::InvalidPrincipal {
field,
reason: error.to_string(),
})
}
pub fn validate_canonical_principal(field: &'static str, value: &str) -> Result<(), IcHostError> {
let principal = Principal::from_text(value)
.map_err(|error| invalid_source_value(format!("{field} {value:?}: {error}")))?;
let canonical = principal.to_text();
if canonical != value {
return invalid_source(format!(
"{field} {value:?} is not canonical principal text; expected {canonical:?}"
));
}
Ok(())
}
fn validate_principal_match(
field: &'static str,
expected: &str,
actual: &str,
) -> Result<(), IcHostError> {
validate_canonical_principal(field, actual)?;
if actual == expected {
return Ok(());
}
invalid_source(format!(
"{field} is {actual:?}, expected requested principal {expected:?}"
))
}
fn validate_collection_end(now_unix_secs: u64, end_unix_secs: u64) -> Result<(), IcHostError> {
if end_unix_secs > now_unix_secs {
return invalid_request(
"query.end_unix_secs",
"must not be later than the collection time",
);
}
Ok(())
}
fn inclusive_observation_count(start_unix_secs: u64, end_unix_secs: u64, step_secs: u32) -> u64 {
(end_unix_secs - start_unix_secs) / u64::from(step_secs) + 1
}
pub fn report_provenance(source: IcSourceRequest) -> IcDashboardReportProvenance {
IcDashboardReportProvenance {
schema_version: IC_DASHBOARD_REPORT_SCHEMA_VERSION,
network: IC_DASHBOARD_NETWORK.to_string(),
authority: IC_DASHBOARD_AUTHORITY.to_string(),
source_endpoint: source.endpoint,
fetched_at: source.fetched_at,
fetched_by: source.fetched_by,
certified: false,
point_in_time_guaranteed: false,
}
}
pub fn invalid_source<T>(reason: impl Into<String>) -> Result<T, IcHostError> {
Err(invalid_source_value(reason))
}
pub fn invalid_source_value(reason: impl Into<String>) -> IcHostError {
IcHostError::InvalidSourceData {
reason: reason.into(),
}
}
fn invalid_request<T>(field: &'static str, reason: impl Into<String>) -> Result<T, IcHostError> {
Err(IcHostError::InvalidRequest {
field,
reason: reason.into(),
})
}