{"record":{"id":"5b9ece7e6b40a579","repo":"linera-io/linera-protocol","slug":"protocol-error-within-chain-client-a-quorum-confi","errorCode":null,"errorMessage":"Protocol error within chain client: A quorum confirmed with a justification commitment that does not match the validated certificate's justification chain","messagePattern":"Protocol error within chain client: A quorum confirmed with a justification commitment that does not match the validated certificate's justification chain","errorType":"exception","errorClass":"ChainClientError","httpStatus":null,"severity":"critical","filePath":"linera-core/src/client/mod.rs","lineNumber":1337,"sourceCode":"            certificate: Box::new(certificate),\n            delivery: self.options.cross_chain_message_delivery,\n        };\n        let quorum = self\n            .communicate_chain_action(committee, finalize_action, hashed_value)\n            .await?;\n        // Omit the chain iff the confirming votes attested that this is the chain's first round:\n        // such a block is always the lower one in any fork, so it never needs a chain of its own.\n        // Deciding from the quorum's signed attestation — rather than our local ownership view,\n        // which a concurrently finalized block could have advanced to a different first round —\n        // guarantees the certificate we assemble matches what the validators actually signed.\n        let justification = if quorum.first_round() {\n            JustificationChain::default()\n        } else {\n            full_justification\n        };\n        // The confirming votes committed to the chain they were shown; the chain we attach must\n        // be that one, or the assembled certificate would fail verification everywhere.\n        ensure!(\n            quorum.justification_commitment() == justification.commitment(quorum.hash()),\n            chain_client::Error::ProtocolError(\n                \"A quorum confirmed with a justification commitment that does not match the \\\n                 validated certificate's justification chain\",\n            )\n        );\n        let certificate = ConfirmedBlockCertificate::from_parts(quorum, justification);\n        self.receive_certificate_with_checked_signatures(\n            certificate.clone(),\n            ProcessConfirmedBlockMode::Execute,\n        )\n        .await?;\n        Ok(certificate)\n    }\n\n    /// Submits a block proposal to the validators.\n    #[instrument(level = \"trace\", skip_all)]\n    async fn submit_block_proposal<T: ProcessableCertificate>(","sourceCodeStart":1319,"sourceCodeEnd":1355,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-core/src/client/mod.rs#L1319-L1355","documentation":"Raised in Client::finalize_block after a confirming quorum forms on a ConfirmedBlock. The client assembles the final certificate from the quorum's votes plus a justification chain (empty iff the quorum attested first_round). If the justification commitment the votes signed does not equal the commitment of the chain the client attaches, the assembled certificate would fail signature verification everywhere, so the client refuses with this ProtocolError instead of broadcasting a broken certificate.","triggerScenarios":"A quorum of validators signed confirm votes over a justification chain derived from a different (competing) validated certificate than the one this client holds; concurrent finalization attempts on the same chain/height; byzantine validators returning votes fabricated over another chain; fork/race during process_pending_block.","commonSituations":"Two clients (or a retried client and a stale one) driving the same chain simultaneously; validators recovering from a restart with stale locking state; mixed validator versions after a protocol upgrade. Persistent occurrence on a healthy network indicates a real protocol bug or a faulty validator.","solutions":["Retry the operation: discard the pending block, re-prepare the chain (prepare_chain synchronizes from validators) and let process_pending_block rebuild and finalize","Make sure only one client instance proposes/finalizes on a given chain at a time (single owner per chain)","Check that all validators run the same (latest) linera version","If reproducible, capture the certificates and votes and report it as a protocol bug"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"fn is_justification_commitment_mismatch(err: &chain_client::Error) -> bool {\n    matches!(\n        err,\n        chain_client::Error::ProtocolError(\n            \"A quorum confirmed with a justification commitment that does not match the \\\n             validated certificate's justification chain\"\n        )\n    )\n}","tryCatchPattern":"match client.process_pending_block().await {\n    Err(e) if is_justification_commitment_mismatch(&e) => {\n        // Quorum signed a different justification chain; re-sync and retry once.\n        client.prepare_chain().await?;\n        client.process_pending_block().await\n    }\n    other => other,\n}","preventionTips":["Run exactly one active client per owner chain; concurrent finalizers produce exactly this race","Keep the whole committee on the same linera-protocol version","Log quorum attestation fields (first_round, justification_commitment) when the error fires — a pattern across rounds indicates a faulty validator"],"tags":["linera","consensus","byzantine","certificate","protocol-error"],"backgroundTag":"consensus-vote-mismatch","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}