use super::{
AllocationLedger, AllocationRecord, AllocationStageError, AllocationState, ClaimOutcome,
RecoveredLedger,
};
use crate::{
RuntimeBootstrapError, RuntimeBootstrapPolicy, declaration::AllocationDeclaration,
requests::DeclarationSnapshot,
};
pub fn stage_generation<P: RuntimeBootstrapPolicy>(
recovered: RecoveredLedger,
snapshot: DeclarationSnapshot,
policy: &P,
) -> Result<(AllocationLedger, Vec<AllocationDeclaration>), RuntimeBootstrapError<P::Error>> {
let mut next = recovered.into_ledger();
for declaration in snapshot.declarations() {
match crate::validation::check_declaration(&next, declaration, policy)? {
ClaimOutcome::Existing { record_index } => {
let record = &mut next.records[record_index];
record.state = AllocationState::Active;
}
ClaimOutcome::New => next.records.push(AllocationRecord::active(declaration)),
}
}
next.validate_bounds().map_err(AllocationStageError::from)?;
next.current_generation = checked_next_generation(next.current_generation)
.map_err(|generation| AllocationStageError::GenerationOverflow { generation })?;
Ok((next, snapshot.into_declarations()))
}
pub const fn checked_next_generation(current_generation: u64) -> Result<u64, u64> {
match current_generation.checked_add(1) {
Some(next_generation) => Ok(next_generation),
None => Err(current_generation),
}
}
#[cfg(test)]
mod tests {
use super::stage_generation;
use crate::requests::DeclarationSnapshot;
use crate::{
AllocationDeclaration, AllocationLedger, AllocationValidationError,
GenericAllocationPolicy, MemoryManagerSlot, RuntimeBootstrapPolicy,
};
use crate::{LedgerCommitStore, RuntimeBootstrapError};
#[derive(Debug, Eq, PartialEq)]
struct RejectActivePolicy;
impl RuntimeBootstrapPolicy for RejectActivePolicy {
type Error = &'static str;
fn validate_allocation(
&self,
_key: &crate::StableKey,
_slot: &MemoryManagerSlot,
) -> Result<(), Self::Error> {
Err("active slot rejected")
}
fn runtime_bootstrap_identity(
&self,
) -> Result<crate::PolicyIdentity, crate::PolicyIdentityError> {
crate::PolicyIdentity::new("validation-test", 1)
}
}
fn declaration() -> AllocationDeclaration {
AllocationDeclaration::new(
"app.users.v1",
MemoryManagerSlot::new(100).expect("usable slot"),
)
.expect("declaration")
}
#[test]
fn validated_commit_matches_recovered_generation() {
let mut store = LedgerCommitStore::default();
let recovered = store.recover_or_genesis().unwrap();
let snapshot = DeclarationSnapshot::for_test(vec![declaration()]);
let commit = stage_generation(recovered, snapshot, &GenericAllocationPolicy).unwrap();
let (next, declarations) = commit;
store.commit_generation(&next).unwrap();
let recovered = store.recover().unwrap();
assert_eq!(recovered.current_generation(), 1);
assert_eq!(recovered.ledger().records().len(), 1);
assert_eq!(
recovered.ledger().records()[0],
crate::AllocationRecord::active(&declaration())
);
assert_eq!(declarations, vec![declaration()]);
}
#[test]
fn reservation_policy_alone_does_not_activate_reserved_allocation() {
let mut store = LedgerCommitStore::default();
let reserved = crate::AllocationLedger::new(
1,
vec![crate::testing::allocation_record(
&declaration(),
crate::AllocationState::Reserved,
)],
)
.unwrap();
store.commit(&reserved).unwrap();
let recovered = store.recover().unwrap();
let snapshot = DeclarationSnapshot::for_test(vec![declaration()]);
let err = stage_generation(recovered, snapshot, &RejectActivePolicy)
.expect_err("active validation must run");
assert!(matches!(
err,
RuntimeBootstrapError::Validation(AllocationValidationError::Policy(
"active slot rejected"
))
));
assert_eq!(
store.recover().unwrap().ledger().records()[0].state(),
crate::AllocationState::Reserved
);
}
#[test]
fn later_policy_failure_discards_local_staging() {
let store = LedgerCommitStore::default();
let prior = store.recover_or_genesis().unwrap();
let snapshot = DeclarationSnapshot::for_test(vec![
AllocationDeclaration::memory_manager(
crate::IC_MEMORY_LEDGER_STABLE_KEY,
crate::MEMORY_MANAGER_LEDGER_ID,
)
.unwrap(),
AllocationDeclaration::memory_manager("zoo.rows.v1", 100).unwrap(),
]);
assert!(matches!(
stage_generation(prior, snapshot, &RejectActivePolicy),
Err(RuntimeBootstrapError::Validation(
AllocationValidationError::Policy("active slot rejected")
))
));
}
#[test]
fn staging_generation_overflow_follows_validation() {
let prior = crate::RecoveredLedger::from_trusted_ledger(
AllocationLedger::new(u64::MAX, Vec::new()).unwrap(),
);
let snapshot = DeclarationSnapshot::for_test(vec![declaration()]);
assert!(matches!(
stage_generation(
prior.clone(),
DeclarationSnapshot::for_test(vec![declaration()]),
&RejectActivePolicy
),
Err(RuntimeBootstrapError::Validation(
AllocationValidationError::Policy("active slot rejected")
))
));
assert!(matches!(
stage_generation(prior, snapshot, &GenericAllocationPolicy),
Err(RuntimeBootstrapError::Staging(
crate::AllocationStageError::GenerationOverflow {
generation: u64::MAX
}
))
));
}
}