linera-io/linera-protocol · critical · ChainError

Justification chain must lie in rounds strictly below the ce

Error message

Justification chain must lie in rounds strictly below the certificate's round

What it means

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.

Source

Thrown at linera-chain/src/certificate/lite.rs:181

        let value = VoteValue(
            self.value.value_hash,
            self.round,
            self.value.kind,
            self.unlocking_round,
            self.first_round,
            self.justification_commitment,
        );
        check_signatures(&value, &self.signatures, committee)?;
        let top = self.justification.top_unlocking_round();
        match self.value.kind {
            CertificateKind::Validated => {
                // The signed unlocking round must be the top of the chain, which must lie strictly
                // below the certified round.
                ensure!(
                    self.unlocking_round == top,
                    ChainError::JustificationUnlockingRoundMismatch
                );
                ensure!(
                    top.is_none_or(|top| top < self.round),
                    ChainError::JustificationChainNotBelowCertificate
                );
            }
            CertificateKind::Confirmed => {
                // The first-round attestation can only be set in a round that could be a chain's
                // first one.
                if self.first_round {
                    ensure!(
                        matches!(
                            self.round,
                            Round::Fast
                                | Round::MultiLeader(0)
                                | Round::SingleLeader(0)
                                | Round::Validator(0)
                        ),
                        ChainError::FalseFirstRoundAttestation
                    );

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Reject the certificate; per protocol this can only be malformed or malicious input.
  2. 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.
  3. Add invariant tests over round monotonicity when constructing justification chains.
  4. Alert on frequency: sporadic failures point to a faulty peer, sustained failures to an adversarial one or a version bug.
Defensive patterns

Strategy: try-catch

Try / catch

match certificate.check(&committee) {
    Ok(value) => value,
    Err(ChainError::JustificationChainNotBelowCertificate) => {
        // Impossible for honest participants: chain at/above the certified round.
        tracing::error!(round = ?certificate.round, "justification chain not below certificate round; flagging peer");
        self.penalize_peer(peer_id);
        return Err(ChainError::JustificationChainNotBelowCertificate.into());
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/ac4399b94e58683a. Report an issue: GitHub.