linera-io/linera-protocol · warning · ChainClientError
Epoch is already revoked
Error message
Epoch is already revoked
What it means
Thrown by ChainClient::revoke_epochs (admin chains only) when every epoch from 0 up to and including the requested revoked_epoch already has a RemoveCommittee admin event on the REMOVED_EPOCH_STREAM_NAME stream. The method builds one RemoveCommittee operation per still-active epoch; if the loop skipped all of them, `operations` is empty and the guard fires instead of submitting an empty block. It is an idempotency guard: the requested revocation goal is already fully achieved on chain.
Source
Thrown at linera-core/src/client/chain_client/mod.rs:2976
let current_epoch = self.chain_info().await?.epoch;
ensure!(
revoked_epoch < current_epoch,
Error::CannotRevokeCurrentEpoch(current_epoch)
);
let mut operations = Vec::new();
for epoch_index in 0..=revoked_epoch.0 {
let epoch = Epoch(epoch_index);
if self
.has_admin_event(REMOVED_EPOCH_STREAM_NAME, epoch.0)
.await?
{
continue;
}
operations.push(Operation::system(SystemOperation::Admin(
AdminOperation::RemoveCommittee { epoch },
)));
}
ensure!(!operations.is_empty(), Error::EpochAlreadyRevoked);
self.execute_operations(operations, vec![]).await
}
/// Sends money to a chain.
/// Do not check balance. (This may block the client)
/// Do not confirm the transaction.
#[cfg(with_testing)]
#[instrument(level = "trace")]
pub async fn transfer_to_account_unsafe_unconfirmed(
&self,
owner: AccountOwner,
amount: Amount,
recipient: Account,
) -> Result<ClientOutcome<ConfirmedBlockCertificate>, Error> {
self.execute_operation(SystemOperation::Transfer {
owner,
recipient,
amount,View on GitHub (pinned to 6c226ddcb3)
Solutions
- Treat the error as success: the revocation you asked for is already in place, so catch Error::EpochAlreadyRevoked and return the previous outcome
- Before calling, query the chain's admin stream (REMOVED_EPOCH_STREAM_NAME events) or the current committee state to see which epochs are still active
- If your intent is to revoke more, pass a higher revoked_epoch that is still below the current epoch
Example fix
// before
let outcome = client.revoke_epochs(epoch).await?; // Err(EpochAlreadyRevoked) on retry
// after
let outcome = match client.revoke_epochs(epoch).await {
Err(chain_client::Error::EpochAlreadyRevoked) => {
// All epochs <= `epoch` are already revoked; nothing to do.
return Ok(ClientOutcome::Committed(previous_certificate.clone()));
}
result => result?,
}; Defensive patterns
Strategy: try-catch
Type guard
fn is_epoch_already_revoked(err: &chain_client::Error) -> bool {
matches!(err, chain_client::Error::EpochAlreadyRevoked)
} Try / catch
match client.revoke_epochs(epoch).await {
Err(chain_client::Error::EpochAlreadyRevoked) => {
// Goal already achieved on chain; treat as idempotent success.
}
other => other?,
} Prevention
- In admin automation, remember the highest revoked epoch per chain and skip re-invoking revoke_epochs with the same or lower value
- Treat revoke_epochs as an idempotent operation: always match on EpochAlreadyRevoked and continue
- Before scripting revocation, read the current epoch and the admin chain's REMOVED_EPOCH_STREAM_NAME events to compute which epochs still need removing
When it happens
Trigger: Calling client.revoke_epochs(epoch) where every epoch 0..=epoch was already revoked earlier (e.g. a script or CLI re-runs the same admin command). Also revoked_epoch must be < current epoch, otherwise CannotRevokeCurrentEpoch fires first, so this error specifically means 'old epochs, all already removed'.
Common situations: Automation/wallet tooling that retries epoch revocation after a first success; operators passing a stale epoch value read from an old config; idempotent deployment scripts that assume re-invocation is a no-op but don't handle the error variant.
Related errors
- EventsNotFound
- InvalidCommitteeRemoval
- InvalidCommitteeEpoch
- AdminOperationOnNonAdminChain
- MetaMask is not connected with the requested owner: ${owner}
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/4aca0f052ae02bf1.
Report an issue: GitHub.