linera-io/linera-protocol · error · ExecutionError
InvalidCommitteeRemoval
InvalidCommitteeRemoval
Error message
ExecutionError::InvalidCommitteeRemoval
What it means
RemoveCommittee deregisters an old committee (epoch) on the admin chain. The system tracks how many epochs were already removed in the REMOVED_EPOCH_STREAM_NAME system stream; the check at system.rs:539 requires count == epoch.0 AND epoch < current epoch. So removals must start at epoch 0 and proceed strictly one-by-one (0, 1, 2, ...) with no repeats or gaps, and a newer committee must already be active before the old one is removed.
Source
Thrown at linera-execution/src/system.rs:539
.await?;
self.blob_used(txn_tracker, blob_id).await?;
self.committee_hash.set(Some(blob_hash));
self.epoch.set(epoch);
let event_data = EpochEventData {
blob_hash,
timestamp: context.timestamp,
};
let stream_id = StreamId::system(EPOCH_STREAM_NAME);
let next_index = epoch.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
self.stream_event_counts.insert(&stream_id, next_index)?;
txn_tracker.add_event(stream_id, epoch.0, bcs::to_bytes(&event_data)?);
}
AdminOperation::RemoveCommittee { epoch } => {
let stream_id = StreamId::system(REMOVED_EPOCH_STREAM_NAME);
let count = self.stream_event_counts.get(&stream_id).await?.unwrap_or(0);
// Revocations must happen in increasing epoch order, so the stream's
// indices stay sequential.
ensure!(
count == epoch.0 && epoch < *self.epoch.get(),
ExecutionError::InvalidCommitteeRemoval
);
let next_index = epoch.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
self.stream_event_counts.insert(&stream_id, next_index)?;
txn_tracker.add_event(stream_id, epoch.0, vec![]);
}
}
}
PublishModule { module_id } => {
for blob_id in module_id.bytecode_blob_ids() {
self.blob_published(&blob_id, txn_tracker)?;
}
}
CreateApplication {
module_id,
parameters,
instantiation_argument,View on GitHub (pinned to 6c226ddcb3)
Solutions
- Create the next committee first (CreateCommittee with the next epoch) so the epoch to remove is strictly older than the current one.
- Remove epochs one at a time in increasing order, starting from the current removal count (first removal must be epoch 0).
- Query the admin chain's REMOVED_EPOCH_STREAM_NAME event count (or the chain state) to learn the next expected epoch before submitting.
- Drop duplicated or replayed removal operations from pending batches; each removal is single-use.
Example fix
// before: removing the current committee (no newer committee yet)
let op = AdminOperation::RemoveCommittee { epoch: current_epoch }; // InvalidCommitteeRemoval
// after: rotate first, then remove the old epoch in order
// 1) AdminOperation::CreateCommittee { epoch: current_epoch + 1, blob_hash }
// 2) AdminOperation::RemoveCommittee { epoch: current_epoch } Defensive patterns
Strategy: validation
Validate before calling
// derive the expected next removable epoch from the removal stream before submitting
let count = node
.events(admin_chain_id, StreamId::system(REMOVED_EPOCH_STREAM_NAME))
.await?.len() as u32; // prior removals
let epoch_to_remove = count; // must equal count and be < current epoch
assert!(Epoch(epoch_to_remove) < current_epoch); Try / catch
match result {
Err(ExecutionError::InvalidCommitteeRemoval) => {
// re-read removal count and current epoch, then resubmit the correct epoch
}
other => other,
} Prevention
- Automate committee rotation as a strict ordered pipeline: create next committee, then remove previous epoch.
- Never replay admin blocks: each removal is valid exactly once and in order.
- Log the removal stream count alongside every rotation step for auditability.
When it happens
Trigger: AdminOperation::RemoveCommittee { epoch } where epoch does not equal the number of prior removals (skipping an epoch, removing out of order, or re-submitting an already-removed epoch), or where epoch is greater than or equal to the chain's current epoch (attempting to remove the currently active committee before a newer one was created).
Common situations: Committee-rotation scripts that remove epochs in bulk or assume a different ordering; retrying a removal that already executed in an earlier block; removing the current committee immediately after publishing but before creating the next one; test harnesses jumping epochs non-sequentially.
Related errors
- Epoch is already revoked
- EventsNotFound
- InvalidCommitteeEpoch
- MetaMask is not connected with the requested owner: ${owner}
- Block advances the chain's epoch from {start_epoch} to {end_
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/b25e8545df867cb2.
Report an issue: GitHub.