{"record":{"id":"b25e8545df867cb2","repo":"linera-io/linera-protocol","slug":"invalidcommitteeremoval","errorCode":"InvalidCommitteeRemoval","errorMessage":"ExecutionError::InvalidCommitteeRemoval","messagePattern":"ExecutionError::InvalidCommitteeRemoval","errorType":"exception","errorClass":"ExecutionError","httpStatus":null,"severity":"error","filePath":"linera-execution/src/system.rs","lineNumber":539,"sourceCode":"                            .await?;\n                        self.blob_used(txn_tracker, blob_id).await?;\n                        self.committee_hash.set(Some(blob_hash));\n                        self.epoch.set(epoch);\n                        let event_data = EpochEventData {\n                            blob_hash,\n                            timestamp: context.timestamp,\n                        };\n                        let stream_id = StreamId::system(EPOCH_STREAM_NAME);\n                        let next_index = epoch.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;\n                        self.stream_event_counts.insert(&stream_id, next_index)?;\n                        txn_tracker.add_event(stream_id, epoch.0, bcs::to_bytes(&event_data)?);\n                    }\n                    AdminOperation::RemoveCommittee { epoch } => {\n                        let stream_id = StreamId::system(REMOVED_EPOCH_STREAM_NAME);\n                        let count = self.stream_event_counts.get(&stream_id).await?.unwrap_or(0);\n                        // Revocations must happen in increasing epoch order, so the stream's\n                        // indices stay sequential.\n                        ensure!(\n                            count == epoch.0 && epoch < *self.epoch.get(),\n                            ExecutionError::InvalidCommitteeRemoval\n                        );\n                        let next_index = epoch.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;\n                        self.stream_event_counts.insert(&stream_id, next_index)?;\n                        txn_tracker.add_event(stream_id, epoch.0, vec![]);\n                    }\n                }\n            }\n            PublishModule { module_id } => {\n                for blob_id in module_id.bytecode_blob_ids() {\n                    self.blob_published(&blob_id, txn_tracker)?;\n                }\n            }\n            CreateApplication {\n                module_id,\n                parameters,\n                instantiation_argument,","sourceCodeStart":521,"sourceCodeEnd":557,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-execution/src/system.rs#L521-L557","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: removing the current committee (no newer committee yet)\nlet op = AdminOperation::RemoveCommittee { epoch: current_epoch }; // InvalidCommitteeRemoval\n\n// after: rotate first, then remove the old epoch in order\n// 1) AdminOperation::CreateCommittee { epoch: current_epoch + 1, blob_hash }\n// 2) AdminOperation::RemoveCommittee { epoch: current_epoch }","handlingStrategy":"validation","validationCode":"// derive the expected next removable epoch from the removal stream before submitting\nlet count = node\n    .events(admin_chain_id, StreamId::system(REMOVED_EPOCH_STREAM_NAME))\n    .await?.len() as u32; // prior removals\nlet epoch_to_remove = count; // must equal count and be < current epoch\nassert!(Epoch(epoch_to_remove) < current_epoch);","typeGuard":null,"tryCatchPattern":"match result {\n    Err(ExecutionError::InvalidCommitteeRemoval) => {\n        // re-read removal count and current epoch, then resubmit the correct epoch\n    }\n    other => other,\n}","preventionTips":["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."],"tags":["linera","committee","epoch","admin-chain","governance"],"backgroundTag":"out-of-order-sequence-rejected","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}