use super::{
NnsRegistryReplayError, NnsRegistryReplayLimits, NnsRegistryReplayProgress,
NnsRegistryReplayState, apply_validated_batch_through, validated_batch_prefix_counts,
};
use crate::nns::registry::{
NnsCertifiedRegistryDeltaBatchReport, NnsCertifiedRegistryDeltaBatchRequest,
validate_nns_certified_registry_delta_batch,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NnsRegistryReplaySessionLimits {
pub max_registry_versions: u64,
pub max_batches: u64,
pub max_query_calls: u64,
pub max_response_bytes: u64,
pub state: NnsRegistryReplayLimits,
}
impl NnsRegistryReplaySessionLimits {
#[must_use]
pub const fn new(
max_registry_versions: u64,
max_batches: u64,
max_query_calls: u64,
max_response_bytes: u64,
state: NnsRegistryReplayLimits,
) -> Self {
Self {
max_registry_versions,
max_batches,
max_query_calls,
max_response_bytes,
state,
}
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct NnsRegistryReplaySession {
limits: NnsRegistryReplaySessionLimits,
state: NnsRegistryReplayState,
selected_version: Option<u64>,
highest_certified_latest_version: Option<u64>,
root_key_digest: Option<String>,
batch_count: u64,
query_call_count: u64,
response_bytes: u64,
applied_mutation_count: u64,
}
impl NnsRegistryReplaySession {
#[must_use]
pub const fn new(limits: NnsRegistryReplaySessionLimits) -> Self {
Self {
limits,
state: NnsRegistryReplayState::new(),
selected_version: None,
highest_certified_latest_version: None,
root_key_digest: None,
batch_count: 0,
query_call_count: 0,
response_bytes: 0,
applied_mutation_count: 0,
}
}
pub fn apply_batch(
&mut self,
request: &NnsCertifiedRegistryDeltaBatchRequest,
report: &NnsCertifiedRegistryDeltaBatchReport,
) -> Result<NnsRegistryReplayProgress, NnsRegistryReplayError> {
validate_nns_certified_registry_delta_batch(request, report)?;
if self.state.through_version() != report.requested_version {
return Err(NnsRegistryReplayError::VersionMismatch {
state_version: self.state.through_version(),
requested_version: report.requested_version,
});
}
if let Some(selected_version) = self.selected_version {
if self.state.through_version() == selected_version {
return Err(NnsRegistryReplayError::SessionComplete { selected_version });
}
if report.certified_latest_version < selected_version {
return Err(NnsRegistryReplayError::CertifiedVersionRegressed {
selected_version,
certified_latest_version: report.certified_latest_version,
});
}
}
if let Some(expected_root_key_digest) = &self.root_key_digest
&& expected_root_key_digest != &report.certification.root_key_digest
{
return Err(NnsRegistryReplayError::RootKeyDigestMismatch {
expected_root_key_digest: expected_root_key_digest.clone(),
actual_root_key_digest: report.certification.root_key_digest.clone(),
});
}
let selected_version = self
.selected_version
.unwrap_or(report.certified_latest_version);
enforce_session_limit(
"selected Registry versions",
selected_version,
self.limits.max_registry_versions,
)?;
let candidate_batch_count = checked_add(self.batch_count, 1)?;
enforce_session_limit(
"batch count",
candidate_batch_count,
self.limits.max_batches,
)?;
let candidate_query_call_count =
checked_add(self.query_call_count, report.query_call_count)?;
enforce_session_limit(
"query call count",
candidate_query_call_count,
self.limits.max_query_calls,
)?;
let report_response_bytes =
u64::try_from(report.response_bytes).map_err(|_| NnsRegistryReplayError::Accounting)?;
let candidate_response_bytes = checked_add(self.response_bytes, report_response_bytes)?;
enforce_session_limit(
"response bytes",
candidate_response_bytes,
self.limits.max_response_bytes,
)?;
let (_, batch_applied_mutation_count) =
validated_batch_prefix_counts(report, selected_version)?;
let batch_applied_mutation_count = u64::try_from(batch_applied_mutation_count)
.map_err(|_| NnsRegistryReplayError::Accounting)?;
let candidate_applied_mutation_count =
checked_add(self.applied_mutation_count, batch_applied_mutation_count)?;
let progress = apply_validated_batch_through(
&mut self.state,
report,
selected_version,
self.limits.state,
)?;
self.applied_mutation_count = candidate_applied_mutation_count;
self.selected_version = Some(selected_version);
self.highest_certified_latest_version = Some(
self.highest_certified_latest_version
.map_or(report.certified_latest_version, |version| {
version.max(report.certified_latest_version)
}),
);
self.root_key_digest = Some(report.certification.root_key_digest.clone());
self.batch_count = candidate_batch_count;
self.query_call_count = candidate_query_call_count;
self.response_bytes = candidate_response_bytes;
Ok(progress)
}
pub(super) fn ensure_next_source_call_capacity(
&self,
maximum_batch_query_calls: u64,
maximum_batch_response_bytes: u64,
) -> Result<(), NnsRegistryReplayError> {
if let Some(selected_version) = self.selected_version
&& self.state.through_version() == selected_version
{
return Err(NnsRegistryReplayError::SessionComplete { selected_version });
}
enforce_session_limit(
"batch count",
checked_add(self.batch_count, 1)?,
self.limits.max_batches,
)?;
enforce_session_limit(
"query call count",
checked_add(self.query_call_count, maximum_batch_query_calls)?,
self.limits.max_query_calls,
)?;
enforce_session_limit(
"response bytes",
checked_add(self.response_bytes, maximum_batch_response_bytes)?,
self.limits.max_response_bytes,
)
}
#[must_use]
pub const fn limits(&self) -> NnsRegistryReplaySessionLimits {
self.limits
}
#[must_use]
pub const fn state(&self) -> &NnsRegistryReplayState {
&self.state
}
#[must_use]
pub fn into_state(self) -> NnsRegistryReplayState {
self.state
}
#[must_use]
pub const fn selected_version(&self) -> Option<u64> {
self.selected_version
}
#[must_use]
pub const fn highest_certified_latest_version(&self) -> Option<u64> {
self.highest_certified_latest_version
}
#[must_use]
pub fn is_complete(&self) -> bool {
self.selected_version == Some(self.state.through_version())
}
#[must_use]
pub fn root_key_digest(&self) -> Option<&str> {
self.root_key_digest.as_deref()
}
#[must_use]
pub const fn batch_count(&self) -> u64 {
self.batch_count
}
#[must_use]
pub const fn query_call_count(&self) -> u64 {
self.query_call_count
}
#[must_use]
pub const fn response_bytes(&self) -> u64 {
self.response_bytes
}
#[must_use]
pub const fn applied_mutation_count(&self) -> u64 {
self.applied_mutation_count
}
}
fn checked_add(left: u64, right: u64) -> Result<u64, NnsRegistryReplayError> {
left.checked_add(right)
.ok_or(NnsRegistryReplayError::Accounting)
}
const fn enforce_session_limit(
field: &'static str,
actual: u64,
maximum: u64,
) -> Result<(), NnsRegistryReplayError> {
if actual > maximum {
Err(NnsRegistryReplayError::SessionLimitExceeded {
field,
maximum,
actual,
})
} else {
Ok(())
}
}