{"record":{"id":"b518303e178f46e4","repo":"linera-io/linera-protocol","slug":"certificate-unlocking-round-does-not-match-the-top","errorCode":null,"errorMessage":"Certificate unlocking round does not match the top of its justification chain","messagePattern":"Certificate unlocking round does not match the top of its justification chain","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"critical","filePath":"linera-chain/src/certificate/lite.rs","lineNumber":177,"sourceCode":"        ensure!(\n            self.justification_commitment == derived_commitment,\n            ChainError::JustificationCommitmentMismatch\n        );\n        let value = VoteValue(\n            self.value.value_hash,\n            self.round,\n            self.value.kind,\n            self.unlocking_round,\n            self.first_round,\n            self.justification_commitment,\n        );\n        check_signatures(&value, &self.signatures, committee)?;\n        let top = self.justification.top_unlocking_round();\n        match self.value.kind {\n            CertificateKind::Validated => {\n                // The signed unlocking round must be the top of the chain, which must lie strictly\n                // below the certified round.\n                ensure!(\n                    self.unlocking_round == top,\n                    ChainError::JustificationUnlockingRoundMismatch\n                );\n                ensure!(\n                    top.is_none_or(|top| top < self.round),\n                    ChainError::JustificationChainNotBelowCertificate\n                );\n            }\n            CertificateKind::Confirmed => {\n                // The first-round attestation can only be set in a round that could be a chain's\n                // first one.\n                if self.first_round {\n                    ensure!(\n                        matches!(\n                            self.round,\n                            Round::Fast\n                                | Round::MultiLeader(0)\n                                | Round::SingleLeader(0)","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/certificate/lite.rs#L159-L195","documentation":"For a validated certificate, LiteCertificate::check requires at linera-chain/src/certificate/lite.rs:177 that the signed unlocking_round equals the top unlocking round of the carried justification chain (top_unlocking_round()). The signed value must describe exactly the chain being presented; a mismatch means the quorum signed an unlocking round that is not the head of the attached chain — an inconsistent or manipulated certificate.","triggerScenarios":"A certificate assembled from votes citing one justification but carrying a different (e.g., longer or shorter) chain; a buggy validator copying unlocking_round from an older vote; retry proposals where the chain was refreshed but the signed unlocking_round was not regenerated.","commonSituations":"Version skew in how unlocking_round is computed from chains; test code mixing fields from different certificates; a Byzantine validator crafting certificates to unlock locked values prematurely.","solutions":["Reject the certificate and re-fetch it from the source; consistent certificates verify as a unit.","If you build certificates in tests, construct them from a single vote set via try_from_votes so round, unlocking_round, and justification come from the same votes.","Check for software version mismatch between the certificate producer and verifier.","Persist certificates unmodified once checked; do not rebuild them from parts."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match certificate.check(&committee) {\n    Ok(value) => value,\n    Err(ChainError::JustificationUnlockingRoundMismatch) => {\n        tracing::warn!(\n            round = ?certificate.round,\n            signed_unlocking = ?certificate.unlocking_round,\n            \"signed unlocking round inconsistent with justification chain; discarding certificate\"\n        );\n        self.request_full_certificate(certificate.value.value_hash).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Build certificates only from a single consistent vote set (try_from_votes).","Never mix fields (round, unlocking_round, justification) from different certificates.","Treat mismatched-round certificates as Byzantine evidence and count them per peer."],"tags":["linera","consensus","certificate","view-change","rust"],"backgroundTag":"consensus-certificate-invalid","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}