use crate::db::{
MemoryBootstrapAdmissionError, MemoryRuntimeBindingError, bind_memory_runtime, memory_runtime,
prepare_memory_bootstrap,
};
use std::{fmt, rc::Rc, sync::Arc};
use ic_storage::{
AdmissionError, AllocationGrants, BootstrapAdmission, MemoryRuntime, PolicyIdentity,
RuntimeBootstrapError, RuntimeBootstrapPolicy, RuntimeConstructionError, RuntimeOpenError,
SealedDeclarationSnapshot, StableKey, StorageConfig,
};
use ic_storage::DefaultMemoryImpl;
#[doc(hidden)]
pub fn ensure_default_storage_runtime(
authority: &str,
requirements: &SealedDeclarationSnapshot,
config: StorageConfig,
grants: impl FnOnce() -> Result<AllocationGrants, AdmissionError>,
) -> Result<(), DatabaseBootstrapError> {
let runtime = if let Some(runtime) = memory_runtime() {
runtime
} else {
let grants = grants()?;
if let Some(runtime) = memory_runtime() {
runtime
} else {
let mut runtime = MemoryRuntime::new_with_config(DefaultMemoryImpl::default(), config)?;
runtime.bootstrap(requirements, &grants, &DatabaseMemoryPolicy(requirements))?;
let runtime = Rc::new(runtime);
bind_memory_runtime(Rc::clone(&runtime))?;
runtime
}
};
runtime.verify_authority(requirements, authority)?;
Ok(())
}
struct DatabaseMemoryPolicy<'a>(&'a SealedDeclarationSnapshot);
impl RuntimeBootstrapPolicy for DatabaseMemoryPolicy<'_> {
type Error = MemoryBootstrapAdmissionError;
fn validate_allocation(&self, _: &StableKey) -> Result<(), Self::Error> {
Ok(())
}
fn prepare_bootstrap(&self, admission: &mut BootstrapAdmission<'_>) -> Result<(), Self::Error> {
prepare_memory_bootstrap(self.0, admission)
}
fn runtime_bootstrap_identity(&self) -> PolicyIdentity {
PolicyIdentity::new([
162, 211, 99, 60, 182, 106, 249, 175, 69, 210, 132, 12, 241, 103, 92, 177, 182, 86, 63,
5, 157, 250, 84, 228, 24, 141, 250, 92, 126, 87, 89, 68,
])
}
}
#[derive(Clone, Debug)]
pub enum DatabaseBootstrapError {
Construction(Arc<RuntimeConstructionError>),
Binding(MemoryRuntimeBindingError),
Bootstrap(Arc<RuntimeBootstrapError<MemoryBootstrapAdmissionError>>),
Adoption(Arc<RuntimeOpenError>),
}
impl From<MemoryRuntimeBindingError> for DatabaseBootstrapError {
fn from(source: MemoryRuntimeBindingError) -> Self {
Self::Binding(source)
}
}
impl From<RuntimeConstructionError> for DatabaseBootstrapError {
fn from(source: RuntimeConstructionError) -> Self {
Self::Construction(Arc::new(source))
}
}
impl From<RuntimeOpenError> for DatabaseBootstrapError {
fn from(source: RuntimeOpenError) -> Self {
Self::Adoption(Arc::new(source))
}
}
impl From<AdmissionError> for DatabaseBootstrapError {
fn from(source: AdmissionError) -> Self {
RuntimeBootstrapError::Admission(source).into()
}
}
impl From<RuntimeBootstrapError<MemoryBootstrapAdmissionError>> for DatabaseBootstrapError {
fn from(source: RuntimeBootstrapError<MemoryBootstrapAdmissionError>) -> Self {
Self::Bootstrap(Arc::new(source))
}
}
impl fmt::Display for DatabaseBootstrapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Construction(source) => source.fmt(f),
Self::Binding(source) => source.fmt(f),
Self::Bootstrap(source) => source.fmt(f),
Self::Adoption(source) => source.fmt(f),
}
}
}
impl std::error::Error for DatabaseBootstrapError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Construction(source) => Some(source.as_ref()),
Self::Binding(source) => Some(source),
Self::Bootstrap(source) => Some(source.as_ref()),
Self::Adoption(source) => Some(source.as_ref()),
}
}
}
#[cfg(test)]
mod tests {
mod public_failures;
use super::*;
use ic_storage::{GenericAllocationPolicy, MemoryAuthority, MemoryRequest, VectorMemory};
const OWNER: &str = "icydb_bootstrap_test";
const PREFIX: &str = "icydb.bootstrap_test.";
fn requirements() -> SealedDeclarationSnapshot {
SealedDeclarationSnapshot::new(
["commit.control", "startup.control", "integrity.progress"]
.into_iter()
.map(|role| MemoryRequest::new(OWNER, &format!("{PREFIX}{role}.v1")).unwrap())
.collect(),
)
.unwrap()
}
fn grants() -> Result<AllocationGrants, AdmissionError> {
AllocationGrants::new(vec![MemoryAuthority::new(OWNER, PREFIX)?])
}
#[test]
fn adopts_runtime_bound_during_grants_callback() {
std::thread::spawn(|| {
ensure_default_storage_runtime(
OWNER,
&requirements(),
StorageConfig::default(),
|| {
ensure_default_storage_runtime(
OWNER,
&requirements(),
StorageConfig::default(),
grants,
)
.unwrap();
grants()
},
)
.unwrap();
assert_eq!(memory_runtime().unwrap().committed_generation().unwrap(), 1);
})
.join()
.unwrap();
}
#[test]
fn adopts_host_policy_without_rebootstrap_or_regrant() {
std::thread::spawn(|| {
let mut runtime = MemoryRuntime::new(VectorMemory::default()).unwrap();
runtime.bootstrap(&requirements(), &grants().unwrap(), &GenericAllocationPolicy).unwrap();
let before = runtime.memory_allocations().unwrap();
let runtime = Rc::new(runtime);
bind_memory_runtime(runtime.clone()).unwrap();
ensure_default_storage_runtime(OWNER, &requirements(), StorageConfig::default(), || panic!("warm adoption must not call provider")).unwrap();
assert!(Rc::ptr_eq(&memory_runtime().unwrap(), &runtime));
assert_eq!(runtime.memory_allocations().unwrap(), before);
let error = ensure_default_storage_runtime("unknown", &requirements(), StorageConfig::default(), grants).unwrap_err();
assert!(matches!(error, DatabaseBootstrapError::Adoption(cause) if matches!(cause.as_ref(), RuntimeOpenError::NotAdmitted(_))));
assert_eq!(runtime.memory_allocations().unwrap(), before);
}).join().unwrap();
}
}