use super::{
NnsAuthenticatedRegistryArchive, NnsCertifiedRegistryArchivePublisher,
NnsCertifiedRegistryArchiveStorageError, NnsCertifiedRegistryArchiveStorageLimits,
nns_certified_registry_archive_manifest_path,
storage::{ArchiveBatchAuthenticator, BuiltInArchiveAuthenticator},
};
use crate::{
cache_file::{
CacheFileError, RefreshLockRequest, create_managed_parent_directory,
with_refresh_lock_async,
},
nns::{
LiveNnsSource,
registry::{
NnsCertifiedRegistryBootstrapRequest, NnsCertifiedRegistryDeltaBatchRequest,
NnsCertifiedRegistryDeltaSource, NnsRegistryHostError, NnsRegistryReplayError,
fetch_nns_certified_registry_delta_batch_with_source_async,
},
},
};
use std::path::{Path, PathBuf};
use thiserror::Error as ThisError;
const ARCHIVE_REFRESH_LOCK_FILE_NAME: &str = "refresh.lock";
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NnsCertifiedRegistryArchiveBootstrapRequest {
pub bootstrap: NnsCertifiedRegistryBootstrapRequest,
pub cache_root: PathBuf,
pub archive_root: PathBuf,
pub storage_limits: NnsCertifiedRegistryArchiveStorageLimits,
}
impl NnsCertifiedRegistryArchiveBootstrapRequest {
#[must_use]
pub fn new(
bootstrap: NnsCertifiedRegistryBootstrapRequest,
cache_root: impl Into<PathBuf>,
archive_root: impl Into<PathBuf>,
storage_limits: NnsCertifiedRegistryArchiveStorageLimits,
) -> Self {
Self {
bootstrap,
cache_root: cache_root.into(),
archive_root: archive_root.into(),
storage_limits,
}
}
}
#[derive(Debug, ThisError)]
pub enum NnsCertifiedRegistryArchiveBootstrapError {
#[error(transparent)]
Replay(#[from] NnsRegistryReplayError),
#[error(
"certified Registry archive batch after version {requested_version} failed local authentication: {source}"
)]
BatchAuthentication {
requested_version: u64,
#[source]
source: NnsRegistryHostError,
},
#[error(transparent)]
Storage(#[from] NnsCertifiedRegistryArchiveStorageError),
}
#[must_use]
pub fn nns_certified_registry_archive_refresh_lock_path(archive_root: &Path) -> PathBuf {
archive_root.join(ARCHIVE_REFRESH_LOCK_FILE_NAME)
}
pub async fn bootstrap_nns_certified_registry_archive_async(
request: &NnsCertifiedRegistryArchiveBootstrapRequest,
) -> Result<NnsAuthenticatedRegistryArchive, NnsCertifiedRegistryArchiveBootstrapError> {
bootstrap_nns_certified_registry_archive_with_source_async(request, &LiveNnsSource::default())
.await
}
pub async fn bootstrap_nns_certified_registry_archive_with_source_async(
request: &NnsCertifiedRegistryArchiveBootstrapRequest,
source: &dyn NnsCertifiedRegistryDeltaSource,
) -> Result<NnsAuthenticatedRegistryArchive, NnsCertifiedRegistryArchiveBootstrapError> {
bootstrap_archive_with_authenticator_async(request, source, &BuiltInArchiveAuthenticator).await
}
pub(in crate::nns::registry::replay) async fn bootstrap_archive_with_authenticator_async(
request: &NnsCertifiedRegistryArchiveBootstrapRequest,
source: &dyn NnsCertifiedRegistryDeltaSource,
authenticator: &dyn ArchiveBatchAuthenticator,
) -> Result<NnsAuthenticatedRegistryArchive, NnsCertifiedRegistryArchiveBootstrapError> {
super::enforce_archive_mainnet_network(&request.bootstrap.network)?;
let manifest_path = nns_certified_registry_archive_manifest_path(&request.archive_root);
let lock_path = nns_certified_registry_archive_refresh_lock_path(&request.archive_root);
create_managed_parent_directory(&request.cache_root, &manifest_path)
.map_err(archive_filesystem_error)?;
with_refresh_lock_async(
RefreshLockRequest {
cache_root: &request.cache_root,
lock_path: &lock_path,
},
archive_filesystem_error,
async || collect_and_publish(request, source, authenticator).await,
)
.await
}
async fn collect_and_publish(
request: &NnsCertifiedRegistryArchiveBootstrapRequest,
source: &dyn NnsCertifiedRegistryDeltaSource,
authenticator: &dyn ArchiveBatchAuthenticator,
) -> Result<NnsAuthenticatedRegistryArchive, NnsCertifiedRegistryArchiveBootstrapError> {
let publisher = NnsCertifiedRegistryArchivePublisher::new(
&request.cache_root,
&request.archive_root,
request.bootstrap.limits,
request.storage_limits,
);
collect_archive(
publisher,
&request.bootstrap,
source,
authenticator,
|requested_version, source| {
NnsCertifiedRegistryArchiveBootstrapError::BatchAuthentication {
requested_version,
source,
}
},
)
.await
}
pub(super) async fn collect_archive<Error>(
mut publisher: NnsCertifiedRegistryArchivePublisher,
request: &NnsCertifiedRegistryBootstrapRequest,
source: &dyn NnsCertifiedRegistryDeltaSource,
authenticator: &dyn ArchiveBatchAuthenticator,
authentication_error: impl Fn(u64, NnsRegistryHostError) -> Error,
) -> Result<NnsAuthenticatedRegistryArchive, Error>
where
Error: From<NnsRegistryReplayError> + From<NnsCertifiedRegistryArchiveStorageError>,
{
let (maximum_batch_query_calls, maximum_batch_response_bytes) =
super::super::bootstrap::batch_reservation()?;
loop {
publisher.ensure_next_batch_slot()?;
let replay = publisher.replay_session();
if replay.is_complete() {
replay.ensure_next_extension_source_call_capacity(
maximum_batch_query_calls,
maximum_batch_response_bytes,
)?;
} else {
replay.ensure_next_source_call_capacity(
maximum_batch_query_calls,
maximum_batch_response_bytes,
)?;
}
let batch_request = NnsCertifiedRegistryDeltaBatchRequest::new(
&request.network,
&request.source_endpoint,
replay.state().through_version(),
request.now_unix_secs,
);
let report =
fetch_nns_certified_registry_delta_batch_with_source_async(&batch_request, source)
.await
.map_err(NnsRegistryReplayError::from)?;
let authenticated = authenticator
.authenticate(&batch_request, &report)
.map_err(|source| authentication_error(batch_request.requested_version, source))?;
publisher.apply_batch(&authenticated)?;
if publisher.replay_session().is_complete() {
return publisher.finish().map_err(Into::into);
}
}
}
const fn archive_filesystem_error(
source: CacheFileError,
) -> NnsCertifiedRegistryArchiveBootstrapError {
NnsCertifiedRegistryArchiveBootstrapError::Storage(
NnsCertifiedRegistryArchiveStorageError::FileOperation { source },
)
}