{"record":{"id":"ae5ea1af870d7abf","repo":"linera-io/linera-protocol","slug":"unexpected-quorum-validators-voted-for-block-hash","errorCode":null,"errorMessage":"Unexpected quorum: validators voted for block hash {hash} in {round}, expected block hash {expected_hash} in {expected_round}","messagePattern":"Unexpected quorum: validators voted for block hash (.+?) in (.+?), expected block hash (.+?) in (.+?)","errorType":"exception","errorClass":"ChainClientError","httpStatus":null,"severity":"error","filePath":"linera-core/src/client/mod.rs","lineNumber":1593,"sourceCode":"                        }\n                        result => result,\n                    }\n                })\n            },\n            self.options.quorum_grace_period,\n        )\n        .await;\n        let ((votes_hash, votes_round, _, _, _), votes) = match result {\n            Ok(quorum) => quorum,\n            Err(err) => {\n                // The round failed; absorb whatever the more advanced validators hold before\n                // surfacing the outcome, so the caller retries on top of a synchronized state.\n                self.process_lag_reports(lag_reports.into_inner().unwrap())\n                    .await;\n                return Err(err.into());\n            }\n        };\n        ensure!(\n            (votes_hash, votes_round) == (value.hash(), action.round()),\n            chain_client::Error::UnexpectedQuorum {\n                hash: votes_hash,\n                round: votes_round,\n                expected_hash: value.hash(),\n                expected_round: action.round(),\n            }\n        );\n        // Certificate is valid because\n        // * `communicate_with_quorum` ensured a sufficient \"weight\" of\n        // (non-error) answers were returned by validators.\n        // * each answer is a vote signed by the expected validator.\n        let certificate = LiteCertificate::try_from_votes(votes)\n            .ok_or_else(|| {\n                chain_client::Error::InternalError(\n                    \"Vote values or rounds don't match; this is a bug\",\n                )\n            })?","sourceCodeStart":1575,"sourceCodeEnd":1611,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-core/src/client/mod.rs#L1575-L1611","documentation":"Raised in Client::communicate_chain_action after communicate_with_quorum succeeds: votes are grouped by their full signed payload (hash, round, unlocking_round, first_round, justification_commitment), and the quorum that reached voting weight may be for a different block hash or round than the value and round this client submitted. The guard compares (votes_hash, votes_round) with (value.hash(), action.round()) and reports both sides. It signals that consensus moved somewhere else while we were asking — e.g. we got a timeout quorum when we asked for a block, or votes for a different block/round.","triggerScenarios":"request_leader_timeout racing with a block proposal in the same round (validators return the other outcome); finalize_block on a height where validators already confirmed a different block; submit_block_proposal where the aggregated quorum is for a competing value in the same round.","commonSituations":"High-contention chains with multiple proposers; client retries after crashes while the network advanced; clock skew causing rounds to be entered by different clients at different times.","solutions":["Retry the whole action: synchronize the chain from validators (the client already pulls lag reports) and re-run process_pending_block","If the returned quorum is a timeout/outdated round, treat it as a signal to advance the round and re-propose rather than as a fatal error","Reduce concurrent writers on the chain to one client","Check validator clock synchronization if timeouts and proposals keep crossing"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"fn unexpected_quorum(err: &chain_client::Error) -> Option<(CryptoHash, Round, CryptoHash, Round)> {\n    match err {\n        chain_client::Error::UnexpectedQuorum { hash, round, expected_hash, expected_round } => {\n            Some((*hash, *round, *expected_hash, *expected_round))\n        }\n        _ => None,\n    }\n}","tryCatchPattern":"match client.process_pending_block().await {\n    Err(e) if unexpected_quorum(&e).is_some() => {\n        // Validators moved to a different value/round; state is re-synced via lag reports, retry.\n        client.process_pending_block().await\n    }\n    other => other,\n}","preventionTips":["Avoid submitting proposals and leader timeouts for the same round from different processes","Synchronize validator clocks (the client warns about skew) so rounds are entered consistently","Design callers to treat a quorum for a different value as a normal retry signal, not a crash"],"tags":["linera","consensus","quorum","round","retry"],"backgroundTag":"consensus-vote-mismatch","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}