use crate::{
model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::{MutationOutcomeRecord, MutationReceiptRequest},
ic_snapshot_download::{IcSnapshotDownloadPlan, IcSnapshotDownloadPlanningError},
ic_snapshot_transfer_read::{
IcSnapshotTransferReadError, IcSnapshotTransferReadPayload,
IcSnapshotTransferReadRequest, IcSnapshotTransferReadResponse,
},
},
ops::persistence::{
AttemptJournalError, AttemptJournalGuard, ExecutionProgressPersistenceError,
ExecutionStageGuard, ExecutionWorkflowPersistenceError, IcSnapshotArtifactError,
IcSnapshotArtifactWriter, read_execution_progress,
},
policy::ic_snapshot_transfer_read::{
IcSnapshotTransferReadAssociationError, IcSnapshotTransferReadReply, validate_response,
},
ports::ic_snapshot_transfer_read::IcSnapshotTransferReadProvider,
workflow::ic_snapshot_transfer_read::{IcSnapshotTransferReadExecutionError, read_snapshot},
};
use thiserror::Error;
pub fn download_snapshot<E: std::error::Error + 'static>(
stage: &ExecutionStageGuard<'_>,
download: &IcSnapshotDownloadPlan<'_, '_>,
mut writer: IcSnapshotArtifactWriter<'_, '_, '_>,
provider: &mut impl IcSnapshotTransferReadProvider,
mut admit: impl FnMut(&IcSnapshotTransferReadRequest<'_, '_>) -> Result<(), E>,
mut qualify: impl FnMut(
&IcSnapshotTransferReadRequest<'_, '_>,
&IcSnapshotTransferReadResponse,
) -> Result<MutationReceiptRequest, E>,
) -> Result<ArtifactChecksumRecord, IcSnapshotDownloadExecutionError<E>> {
download.validate_binding(stage.binding())?;
let plan = download
.plan()
.ok_or(IcSnapshotDownloadPlanningError::NoDataReads)?;
if stage.plan() != plan
|| download
.requests()
.iter()
.any(|request| request.metadata().digest() != writer.coverage().metadata().digest())
|| writer.coverage().covered_region_bytes() != [0; 3]
|| writer.coverage().covered_chunks() != 0
{
return Err(IcSnapshotDownloadExecutionError::OriginalMismatch);
}
writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
if progress.attempts.mutations_used != 0 || progress.attempts.observations_used != 0 {
return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
}
for (index, payload) in download.requests().iter().enumerate() {
let sequence =
u64::try_from(index).map_err(|_| IcSnapshotDownloadPlanningError::CountOverflow)?;
let response = read_snapshot(
stage,
sequence,
IcSnapshotTransferReadPayload::Data(payload),
provider,
&mut admit,
)?;
writer = append_qualified(stage, sequence, payload, writer, &response, &mut qualify)
.map_err(|source| IcSnapshotDownloadExecutionError::AfterReply {
operation_sequence: sequence,
source,
response: Box::new(response),
})?;
}
let progress = read_execution_progress(stage.layout()?, &plan.digest())?;
if progress.applied_operations != download.requests().len() {
return Err(IcSnapshotDownloadExecutionError::AlreadyAttempted);
}
writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
let checksum = writer.finish()?;
if let Err(source) = stage.layout() {
return Err(IcSnapshotDownloadExecutionError::AfterPublication { source, checksum });
}
Ok(checksum)
}
fn append_qualified<'journal, 'layout, 'metadata, E: std::error::Error + 'static>(
stage: &ExecutionStageGuard<'_>,
sequence: u64,
payload: &crate::model::ic_snapshot_data::IcSnapshotDataRequest<'_>,
writer: IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>,
response: &IcSnapshotTransferReadResponse,
qualify: &mut impl FnMut(
&IcSnapshotTransferReadRequest<'_, '_>,
&IcSnapshotTransferReadResponse,
) -> Result<MutationReceiptRequest, E>,
) -> Result<IcSnapshotArtifactWriter<'journal, 'layout, 'metadata>, IcSnapshotDownloadReplyError<E>>
{
let plan = stage.plan();
let authority = plan
.attempt_authority(sequence)
.map_err(IcSnapshotTransferReadError::from)?;
let mut journal = AttemptJournalGuard::open(stage.layout()?, &authority)?;
let request = IcSnapshotTransferReadRequest::new(
plan,
sequence,
journal.record()?,
IcSnapshotTransferReadPayload::Data(payload),
)?;
let admitted = validate_response(&request, journal.record()?, response)?;
let receipt =
qualify(&request, response).map_err(IcSnapshotDownloadReplyError::Qualification)?;
if receipt.outcome != MutationOutcomeRecord::Applied
|| receipt.attempt != request.mutation_attempt()
|| receipt.request != request.payload().digest().hash()
{
return Err(IcSnapshotDownloadReplyError::ReceiptRequired);
}
writer.validate_transfer_origin(stage.layout()?.root(), plan.digest().hash())?;
let IcSnapshotTransferReadReply::Data(reply) = admitted.reply() else {
return Err(IcSnapshotDownloadReplyError::ReceiptRequired);
};
let writer = writer.append(reply)?;
journal.record_mutation(receipt)?;
stage.layout()?;
Ok(writer)
}
#[derive(Debug, Error)]
pub enum IcSnapshotDownloadExecutionError<E: std::error::Error + 'static> {
#[error("snapshot download originals differ")]
OriginalMismatch,
#[error("snapshot download original data stage was already attempted")]
AlreadyAttempted,
#[error(transparent)]
Planning(#[from] IcSnapshotDownloadPlanningError),
#[error(transparent)]
Stage(#[from] ExecutionWorkflowPersistenceError),
#[error(transparent)]
Progress(#[from] ExecutionProgressPersistenceError),
#[error(transparent)]
Read(#[from] IcSnapshotTransferReadExecutionError<E>),
#[error(transparent)]
Artifact(#[from] IcSnapshotArtifactError),
#[error("snapshot download reply rejected for operation {operation_sequence}: {source}")]
AfterReply {
operation_sequence: u64,
source: IcSnapshotDownloadReplyError<E>,
response: Box<IcSnapshotTransferReadResponse>,
},
#[error("snapshot download stage changed after publication: {source}")]
AfterPublication {
source: ExecutionWorkflowPersistenceError,
checksum: ArtifactChecksumRecord,
},
}
#[derive(Debug, Error)]
pub enum IcSnapshotDownloadReplyError<E: std::error::Error + 'static> {
#[error("snapshot download reply qualification failed: {0}")]
Qualification(#[source] E),
#[error("snapshot download requires an explicit original Applied receipt")]
ReceiptRequired,
#[error(transparent)]
Stage(#[from] ExecutionWorkflowPersistenceError),
#[error(transparent)]
Journal(#[from] AttemptJournalError),
#[error(transparent)]
Request(#[from] IcSnapshotTransferReadError),
#[error(transparent)]
Association(#[from] IcSnapshotTransferReadAssociationError),
#[error(transparent)]
Artifact(#[from] IcSnapshotArtifactError),
}
#[cfg(test)]
mod tests;