use super::RuntimeBootstrapError;
use crate::{
capability::CommittedAllocations,
declaration::{AllocationDeclaration, DeclarationSnapshot},
ledger::{LedgerCommitStore, stage_generation},
policy::RuntimeBootstrapPolicy,
};
pub fn prepare_commit<P>(
store: &mut LedgerCommitStore,
prior: crate::RecoveredLedger,
snapshot: DeclarationSnapshot,
policy: &P,
) -> Result<PendingBootstrapCommit, RuntimeBootstrapError<P::Error>>
where
P: RuntimeBootstrapPolicy,
{
let (staged, declarations) = stage_generation(prior, snapshot, policy)?;
store.commit_generation(&staged)?;
Ok(PendingBootstrapCommit {
declarations,
generation: staged.current_generation(),
})
}
#[derive(Debug, Eq, PartialEq)]
#[must_use = "persist the owning ledger record, then confirm_persisted before opening allocations"]
pub struct PendingBootstrapCommit {
generation: u64,
declarations: Vec<AllocationDeclaration>,
}
impl PendingBootstrapCommit {
#[must_use]
pub fn confirm_persisted(self) -> CommittedAllocations {
CommittedAllocations::from_persisted(self.declarations, self.generation)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
AllocationDeclaration, AllocationLedger, AllocationValidationError,
GenericAllocationPolicy, MemoryManagerSlot, SchemaMetadata,
};
#[derive(Debug, Eq, PartialEq)]
struct RejectActivePolicy;
impl RuntimeBootstrapPolicy for RejectActivePolicy {
type Error = &'static str;
fn validate_key(&self, _key: &crate::StableKey) -> Result<(), Self::Error> {
Ok(())
}
fn validate_slot(
&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"),
SchemaMetadata::default(),
)
.expect("declaration")
}
#[test]
fn validated_commit_matches_recovered_generation() {
let mut store = LedgerCommitStore::default();
let recovered = store.recover_or_genesis().unwrap();
let snapshot = DeclarationSnapshot::canonicalize(vec![declaration()]).unwrap();
let commit = crate::runtime::bootstrap::prepare_commit(
&mut store,
recovered,
snapshot,
&GenericAllocationPolicy,
)
.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!(commit.confirm_persisted().generation(), 1);
}
#[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 before = store.clone();
let recovered = store.recover().unwrap();
let snapshot = DeclarationSnapshot::canonicalize(vec![declaration()]).unwrap();
let err = crate::runtime::bootstrap::prepare_commit(
&mut store,
recovered,
snapshot,
&RejectActivePolicy,
)
.expect_err("active validation must run");
assert!(matches!(
err,
RuntimeBootstrapError::Validation(AllocationValidationError::Policy(
"active slot rejected"
))
));
assert_eq!(store, before);
assert_eq!(
store.recover().unwrap().ledger().records()[0].state(),
crate::AllocationState::Reserved
);
}
#[test]
fn later_policy_failure_discards_local_staging_before_protected_commit() {
let mut store = LedgerCommitStore::default();
let prior = store.recover_or_genesis().unwrap();
let snapshot = DeclarationSnapshot::canonicalize(vec![
AllocationDeclaration::memory_manager(
crate::IC_MEMORY_LEDGER_STABLE_KEY,
crate::MEMORY_MANAGER_LEDGER_ID,
)
.unwrap(),
AllocationDeclaration::memory_manager("zoo.rows.v1", 100).unwrap(),
])
.unwrap();
let before = store.clone();
assert!(matches!(
prepare_commit(&mut store, prior, snapshot, &RejectActivePolicy),
Err(RuntimeBootstrapError::Validation(
AllocationValidationError::Policy("active slot rejected")
))
));
assert_eq!(store, before);
}
#[test]
fn staging_generation_overflow_preserves_protected_store() {
let mut store = LedgerCommitStore::default();
let prior = crate::RecoveredLedger::from_trusted_ledger(
AllocationLedger::new(u64::MAX, Vec::new()).unwrap(),
);
let snapshot = DeclarationSnapshot::canonicalize(vec![declaration()]).unwrap();
assert!(matches!(
prepare_commit(
&mut store,
prior.clone(),
DeclarationSnapshot::canonicalize(vec![declaration()]).unwrap(),
&RejectActivePolicy
),
Err(RuntimeBootstrapError::Validation(
AllocationValidationError::Policy("active slot rejected")
))
));
let before = store.clone();
assert!(matches!(
prepare_commit(&mut store, prior, snapshot, &GenericAllocationPolicy),
Err(RuntimeBootstrapError::Staging(
crate::AllocationStageError::GenerationOverflow {
generation: u64::MAX
}
))
));
assert_eq!(store, before);
}
}