linera-io/linera-protocol · critical · ExecutionError
InvalidCommitteeEpoch
InvalidCommitteeEpoch
Error message
ExecutionError::InvalidCommitteeEpoch { provided, expected } What it means
Linera's system runtime throws this when a committee-related operation supplies an epoch that is not exactly one greater than the chain's current epoch. check_next_epoch computes expected = current_epoch + 1 and rejects every other value, so committee epochs must advance sequentially with no gaps, repeats, or rollbacks. The error's fields tell you exactly which epoch was submitted (provided) and which one was required (expected).
Source
Thrown at linera-execution/src/system.rs:685
missing_events.is_empty(),
ExecutionError::EventsNotFound(missing_events)
);
}
Checkpoint => {
return Err(ExecutionError::InternalError(
"SystemOperation::Checkpoint must be dispatched at ExecutionStateView level",
));
}
}
Ok(new_application)
}
/// Returns an error if the `provided` epoch is not exactly one higher than the chain's current
/// epoch.
fn check_next_epoch(&self, provided: Epoch) -> Result<(), ExecutionError> {
let expected = self.epoch.get().try_add_one()?;
ensure!(
provided == expected,
ExecutionError::InvalidCommitteeEpoch { provided, expected }
);
Ok(())
}
async fn credit(&mut self, owner: &AccountOwner, amount: Amount) -> Result<(), ExecutionError> {
if owner == &AccountOwner::CHAIN {
let new_balance = self.balance.get().saturating_add(amount);
self.balance.set(new_balance);
} else {
let balance = self.balances.get_mut_or_default(owner).await?;
*balance = balance.saturating_add(amount);
}
Ok(())
}
async fn credit_or_send_message(View on GitHub (pinned to 6c226ddcb3)
Solutions
- Resubmit with the epoch reported as expected in the error payload: it is the chain's current epoch plus one.
- Before building the CreateCommittee operation, query the admin chain's fresh epoch state (e.g. GraphQL block/header epoch or the client chain-info API) instead of trusting a cached value.
- If the error came from a retry or replay, check whether that epoch was already created; if so, continue with the existing committee rather than re-submitting.
- For dev and test environments with desynchronized state, reset it: linera net reset, wipe local storage directories, or docker compose down -v.
Example fix
// before: hardcoded or stale epoch in the admin operation
let op = AdminOperation::CreateCommittee { epoch: Epoch(5), blob_hash };
// after: derive the next epoch from the admin chain's current state
let expected = current_epoch.try_add_one()?; // current_epoch + 1
let op = AdminOperation::CreateCommittee { epoch: expected, blob_hash }; Defensive patterns
Strategy: validation
Validate before calling
// Fetch the admin chain's current epoch and derive the next one before submitting
let current: Epoch = client.chain_info(admin_chain_id).await?.execution.epoch;
let next = current.try_add_one()?;
ensure!(
proposed_epoch == next,
"submit epoch {next} (current + 1), got {proposed_epoch}"
); Type guard
fn is_invalid_committee_epoch(e: &ExecutionError) -> bool {
matches!(e, ExecutionError::InvalidCommitteeEpoch { .. })
} Try / catch
match result {
Err(ExecutionError::InvalidCommitteeEpoch { expected, .. }) => {
// Epoch state moved on: adopt `expected` as the next epoch,
// re-read the admin chain state, and resubmit once.
}
Err(e) => return Err(e.into()),
Ok(value) => { /* ... */ }
} Prevention
- Always derive the new epoch from a fresh read of the admin chain instead of caching or hardcoding it.
- Serialize committee-creation proposals so only one submission per epoch is in flight.
- After any network reset or restore, re-sync wallets and clients before sending admin operations.
- Treat expected in the error as authoritative: it equals current_epoch + 1.
When it happens
Trigger: Executing AdminOperation::CreateCommittee { epoch, blob_hash } on the admin chain with epoch != current_epoch + 1 (e.g. creating epoch 5 while the chain is at 3, or re-creating an epoch that already exists), or executing SystemOperation::ProcessNewEpoch(epoch) on a user chain whose stored epoch is behind or ahead of the epoch carried by the message.
Common situations: Restarting or replaying a local/dev Linera network with leftover storage where the epoch counter was already incremented; a client or wallet that cached a stale epoch; two racing committee-creation proposals where the second submission is now stale; client and validator built from protocol versions whose epoch state has diverged after a network reset or restore.
Related errors
- Epoch is already revoked
- EventsNotFound
- InvalidCommitteeRemoval
- InvalidEpoch
- AdminOperationOnNonAdminChain
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/120097c59568167c.
Report an issue: GitHub.