Skip to content

Conversation

@mraszyk
Copy link
Contributor

@mraszyk mraszyk commented Nov 26, 2025

Description

This PR implements support for canister ID migration in dfx: Canister ID migration can be performed using dfx canister migrate-id and its status can be checked out using dfx canister migration-status.

How Has This Been Tested?

An end-to-end test is introduced in this PR.

Checklist:

  • The title of this PR complies with Conventional Commits.
  • I have edited the CHANGELOG accordingly.
  • I have made corresponding changes to the documentation.

@mraszyk mraszyk requested a review from a team as a code owner November 26, 2025 12:35
@mraszyk mraszyk marked this pull request as draft November 26, 2025 14:16
Comment on lines 37 to 91
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValidationError::MigrationsDisabled(Reserved) => {
write!(f, "Canister migrations are disabled at the moment.")
}
ValidationError::RateLimited(Reserved) => write!(
f,
"Canister migration has been rate-limited. Try again later."
),
ValidationError::ValidationInProgress { canister } => write!(
f,
"Validation for canister {canister} is already in progress."
),
ValidationError::MigrationInProgress { canister } => write!(
f,
"Canister migration for canister {canister} is already in progress."
),
ValidationError::CanisterNotFound { canister } => {
write!(f, "The canister {canister} does not exist.")
}
ValidationError::SameSubnet(Reserved) => {
write!(f, "Both canisters are on the same subnet.")
}
ValidationError::CallerNotController { canister } => write!(
f,
"The canister {canister} is not controlled by the calling identity."
),
ValidationError::NotController { canister } => write!(
f,
"The NNS canister sbzkb-zqaaa-aaaaa-aaaiq-cai is not a controller of canister {canister}."
),
ValidationError::MigratedNotStopped(Reserved) => {
write!(f, "The migrated canister is not stopped.")
}
ValidationError::MigratedNotReady(Reserved) => write!(
f,
"The migrated canister is not ready for migration. Try again later."
),
ValidationError::ReplacedNotStopped(Reserved) => {
write!(f, "The replaced canister is not stopped.")
}
ValidationError::ReplacedHasSnapshots(Reserved) => {
write!(f, "The replaced canister has snapshots.")
}
ValidationError::MigratedInsufficientCycles(Reserved) => write!(
f,
"The migrated canister does not have enough cycles for canister migration. Top up the migrated canister with the required amount of cycles."
),
ValidationError::CallFailed { reason } => {
write!(f, "Internal IC error: a call failed due to {reason}")
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointer! I refactored the code into using thiserror, but still wonder if the following code

    let (result,): (Result<(), Option<ValidationError>>,) = canister
        .update(MIGRATE_CANISTER_METHOD)
        .with_arg(arg)
        .build()
        .await?;
        
    match result {
        Ok(()) => Ok(()),
        Err(None) => Err(anyhow::anyhow!("Validation failed with an unknown error.")),
        Err(Some(err)) => Err(anyhow::anyhow!("Validation failed: {err}")),
    }

dealing with the ValidationError is fine or should somehow avoid the "manual" anyhow::anyhow!("Validation failed: {err}")?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants