use crate::policy::ic_mutation::IcMutationReplyView;
use crate::ports::IcProviderError;
use crate::{
model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::MutationReceiptRequest,
ic_mutation::{IcMutationAcknowledgement, IcMutationRequest, IcMutationRequestError},
ic_request::{IcManagementMethodRecord, IcManagementRequestRecord},
operation_plan::OperationPlanError,
},
ops::persistence::{
AttemptJournalError, ExecutionStageGuard, ExecutionWorkflowPersistenceError,
},
policy::ic_mutation::{IcMutationAssociationError, validate_acknowledgement},
ports::ic_mutation::IcMutationProvider,
};
use thiserror::Error;
pub async fn capture_snapshot<E: std::error::Error + 'static>(
stage: &ExecutionStageGuard<'_>,
operation_sequence: u64,
payload: &IcManagementRequestRecord,
provider: &mut impl IcMutationProvider,
admit: impl AsyncFnOnce(&IcMutationRequest<'_>) -> Result<(), E>,
qualify: impl AsyncFnOnce(
&IcMutationRequest<'_>,
&IcMutationAcknowledgement,
) -> Result<MutationReceiptRequest, E>,
) -> Result<(IcMutationAcknowledgement, ArtifactChecksumRecord), IcSnapshotCaptureExecutionError<E>>
{
if payload.method() != IcManagementMethodRecord::TakeCanisterSnapshot {
return Err(IcSnapshotCaptureExecutionError::CaptureRequired);
}
let plan = stage.plan();
let authority = plan.attempt_authority(operation_sequence)?;
let (acknowledgement, admission) = super::ic_mutation::submit_with(
stage,
&authority,
payload,
provider,
async |request| {
admit(request)
.await
.map_err(IcSnapshotCaptureExecutionError::Admission)?;
stage
.layout()
.map_err(super::ExecutionReservationError::from)?;
Ok::<_, IcSnapshotCaptureExecutionError<E>>(())
},
async |journal, request, acknowledgement| {
let admitted = validate_acknowledgement(request, journal.record()?, acknowledgement)?;
stage.layout()?;
let receipt = qualify(request, acknowledgement)
.await
.map_err(IcSnapshotCaptureReplyError::Qualification)?;
super::qualified_receipt::record_applied(
journal,
receipt,
request.mutation_attempt(),
payload.digest().hash(),
IcSnapshotCaptureReplyError::ReceiptRequired,
|| {
stage.layout()?;
Ok(())
},
)?;
stage.layout()?;
let IcMutationReplyView::Capture(reply) = admitted.reply() else {
return Err(IcSnapshotCaptureReplyError::ReceiptRequired);
};
Ok::<_, IcSnapshotCaptureReplyError<E>>(reply.digest())
},
)
.await?;
let admitted = admission;
super::qualified_receipt::retain_reply(acknowledgement, admitted, |source, acknowledgement| {
IcSnapshotCaptureExecutionError::AfterReply {
source,
acknowledgement,
}
})
}
#[derive(Debug, Error)]
pub enum IcSnapshotCaptureExecutionError<E: std::error::Error + 'static> {
#[error(transparent)]
Reservation(#[from] super::ExecutionReservationError),
#[error("snapshot capture requires take_canister_snapshot")]
CaptureRequired,
#[error(transparent)]
Plan(#[from] OperationPlanError),
#[error(transparent)]
Request(#[from] IcMutationRequestError),
#[error("fresh snapshot capture admission failed: {0}")]
Admission(#[source] E),
#[error(transparent)]
Provider(#[from] IcProviderError),
#[error("snapshot capture reply rejected: {source}")]
AfterReply {
source: IcSnapshotCaptureReplyError<E>,
acknowledgement: Box<IcMutationAcknowledgement>,
},
}
#[derive(Debug, Error)]
pub enum IcSnapshotCaptureReplyError<E: std::error::Error + 'static> {
#[error("snapshot capture qualification failed: {0}")]
Qualification(#[source] E),
#[error("snapshot capture requires an explicit original Applied receipt")]
ReceiptRequired,
#[error(transparent)]
Journal(#[from] AttemptJournalError),
#[error(transparent)]
Association(#[from] IcMutationAssociationError),
#[error(transparent)]
Stage(#[from] ExecutionWorkflowPersistenceError),
}
#[cfg(all(test, unix))]
mod tests;