use crate::fleet_ensure::{
model::{
FleetEnsureJournalRecord, FleetEnsurePlan, MAX_FLEET_ENSURE_CANISTERS,
funding_observation::*,
},
ops::funding_observation as records,
policy::startup_funding::{live_binding, relay_quote::observation_bounds},
};
use std::collections::BTreeSet;
pub(in crate::fleet_ensure) fn verify(
plan: &FleetEnsurePlan,
journal: &FleetEnsureJournalRecord,
) -> Result<(), FundingObservationError> {
if journal.funding_observations.is_empty() {
return Ok(());
}
let desired = records::resolved(plan, journal)?;
let mut total = 0u128;
for (root, review) in &journal.funding_observations {
if root != &review.body.root {
return Err(FundingObservationError::AuthorityMismatch);
}
record(plan, &desired, review)?;
total = total
.checked_add(consumed(review)?)
.ok_or(FundingObservationError::ArithmeticOverflow)?;
}
plan.conservation
.maximum_execution_burn_cycles
.checked_add(total)
.ok_or(FundingObservationError::ArithmeticOverflow)?;
Ok(())
}
pub(in crate::fleet_ensure) fn consumed(
review: &FundingObservationReviewRecord,
) -> Result<u128, FundingObservationError> {
review
.body
.per_attempt_cycles
.checked_mul(review.attempts.len() as u128)
.ok_or(FundingObservationError::ArithmeticOverflow)
}
pub(in crate::fleet_ensure) fn total(
journal: &FleetEnsureJournalRecord,
) -> Result<u128, FundingObservationError> {
journal
.funding_observations
.values()
.try_fold(0u128, |total, review| {
total
.checked_add(consumed(review)?)
.ok_or(FundingObservationError::ArithmeticOverflow)
})
}
pub(in crate::fleet_ensure) fn record(
plan: &FleetEnsurePlan,
desired: &crate::fleet_ensure::model::DesiredFleet,
review: &FundingObservationReviewRecord,
) -> Result<(), FundingObservationError> {
record_authority(desired, &plan.operation_id, &plan.plan_sha256, review)
}
fn record_authority(
desired: &crate::fleet_ensure::model::DesiredFleet,
operation: &str,
plan_digest: &str,
review: &FundingObservationReviewRecord,
) -> Result<(), FundingObservationError> {
let invalid = FundingObservationError::AuthorityMismatch;
let body = &review.body;
records::configuration(desired, review)?;
if body.operation_id != operation
|| body.plan_sha256 != plan_digest
|| review.review_sha256 != records::digest(body)
{
return Err(invalid);
}
let bootstrap = desired.bootstrap.as_ref().ok_or(invalid)?;
let bounds =
observation_bounds(desired, &body.root, !body.requests.is_empty()).map_err(|_| invalid)?;
let minimum = bounds.recovery_floor_cycles;
let attempt = bounds.per_attempt_cycles;
if body.per_attempt_cycles != attempt
|| body.recovery_floor_cycles != minimum
|| body.maximum_cycles
!= attempt
.checked_mul(body.requests.len() as u128)
.ok_or(invalid)?
|| body.requests.len() > MAX_FLEET_ENSURE_CANISTERS
|| body.authority.revision == 0
|| body.authority.content_hash == [0; 32]
|| body.authority.components.len() > MAX_FLEET_ENSURE_CANISTERS
|| body
.authority
.components
.values()
.any(|head| head.revision == 0 || head.content_hash == [0; 32])
{
return Err(invalid);
}
let mut bindings = Vec::new();
let mut components = BTreeSet::new();
let mut previous = None;
for request in &body.requests {
let order = (request.parent, request.child);
if previous.is_some_and(|prior| prior >= order)
|| request.component.authority != body.authority.registry
|| !body
.authority
.components
.contains_key(&request.component.component)
{
return Err(invalid);
}
previous = Some(order);
let binding = records::binding(request);
live_binding::selected(desired, &body.root, &binding).map_err(|_| invalid)?;
components.insert(binding.component.component);
bindings.push(binding);
}
if components != body.authority.components.keys().copied().collect() {
return Err(invalid);
}
if live_binding::graph(
&bootstrap
.component_deployment_configuration
.component_topology,
bootstrap.release_build_id,
bindings.iter(),
)
.values()
.any(Result::is_err)
{
return Err(invalid);
}
attempts(review)
}
fn attempts(review: &FundingObservationReviewRecord) -> Result<(), FundingObservationError> {
let invalid = FundingObservationError::AuthorityMismatch;
let body = &review.body;
let mut seen = BTreeSet::new();
if body
.requests
.iter()
.any(|request| !seen.insert(request.child))
|| review.attempts.len() > body.requests.len()
|| (!review.approved && !review.attempts.is_empty())
{
return Err(invalid);
}
if review.final_root_cycles.is_some()
&& (review.attempts.len() != body.requests.len()
|| !review.attempts.iter().all(|attempt| {
matches!(
attempt.outcome,
Some(FundingObservationOutcomeRecord::Observed(_))
)
}))
{
return Err(invalid);
}
for (index, attempt) in review.attempts.iter().enumerate() {
if attempt.request_index != index
|| (attempt.outcome.is_none() && index + 1 != review.attempts.len())
{
return Err(invalid);
}
if let Some(FundingObservationOutcomeRecord::Observed(value)) = &attempt.outcome
&& !usage(&body.requests[index], value)
{
return Err(invalid);
}
}
Ok(())
}
pub(in crate::fleet_ensure) fn usage(
request: &FundingObservationRequestRecord,
value: &FundingChildAccountingRecord,
) -> bool {
value.parent == request.parent
&& value.child == request.child
&& value.last_accounted_at_secs <= value.observed_at_ns / 1_000_000_000
&& (value.pending_operations > 0 || value.reserved_cycles == Some(0))
}
pub(in crate::fleet_ensure) fn source_allowance(
desired: &crate::fleet_ensure::model::DesiredFleet,
operation: &str,
plan_digest: &str,
reviews: &std::collections::BTreeMap<String, FundingObservationReviewRecord>,
) -> Result<u128, FundingObservationError> {
reviews.iter().try_fold(0u128, |total, (root, review)| {
if root != &review.body.root {
return Err(FundingObservationError::AuthorityMismatch);
}
record_authority(desired, operation, plan_digest, review)?;
total
.checked_add(consumed(review)?)
.ok_or(FundingObservationError::ArithmeticOverflow)
})
}