{"record":{"id":"ac4399b94e58683a","repo":"linera-io/linera-protocol","slug":"justification-chain-must-lie-in-rounds-strictly-be","errorCode":null,"errorMessage":"Justification chain must lie in rounds strictly below the certificate's round","messagePattern":"Justification chain must lie in rounds strictly below the certificate's round","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"critical","filePath":"linera-chain/src/certificate/lite.rs","lineNumber":181,"sourceCode":"        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)\n                                | Round::Validator(0)\n                        ),\n                        ChainError::FalseFirstRoundAttestation\n                    );","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/certificate/lite.rs#L163-L199","documentation":"For validated certificates, after matching the signed unlocking round to the chain top, LiteCertificate::check enforces at linera-chain/src/certificate/lite.rs:181 that the justification chain lies strictly below the certificate's own round (top.is_none_or(|top| top < self.round), else ChainError::JustificationChainNotBelowCertificate). A chain reaching the certified round or beyond is nonsensical: a certificate cannot be justified by evidence from its own or a higher round, which would enable circular justification and double-confirmation.","triggerScenarios":"A crafted certificate whose justification chain includes a link with round >= the certificate's round; a bug merging chains during view change that splices a same-round validation below a new certificate; replay of an old certificate with a chain refreshed by later-round evidence.","commonSituations":"Byzantine validators attempting to justify a value with fabricated future-round evidence; logic errors in view-change/retry code that append the wrong chain; test fixtures building chains with non-monotonic rounds.","solutions":["Reject the certificate; per protocol this can only be malformed or malicious input.","If produced by your own node, audit where full_justification/append is called: the appended quorum's round must be strictly below the new certificate's round.","Add invariant tests over round monotonicity when constructing justification chains.","Alert on frequency: sporadic failures point to a faulty peer, sustained failures to an adversarial one or a version bug."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match certificate.check(&committee) {\n    Ok(value) => value,\n    Err(ChainError::JustificationChainNotBelowCertificate) => {\n        // Impossible for honest participants: chain at/above the certified round.\n        tracing::error!(round = ?certificate.round, \"justification chain not below certificate round; flagging peer\");\n        self.penalize_peer(peer_id);\n        return Err(ChainError::JustificationChainNotBelowCertificate.into());\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Assert round monotonicity wherever justification chains are constructed or appended.","Do not splice chains during view change; rebuild from the current round's validation.","Fuzz certificate round combinations to prove the builder cannot emit non-monotonic chains."],"tags":["linera","consensus","certificate","rounds","rust"],"backgroundTag":"consensus-certificate-invalid","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}