linera-io/linera-protocol · error · ChainError
Justification chain rounds must be strictly increasing
Error message
Justification chain rounds must be strictly increasing
What it means
Thrown by JustificationChain::verify when consecutive links of a justification chain do not have strictly increasing rounds (ChainError::JustificationRoundsNotIncreasing). A justification chain is an ordered sequence of quorums where each link must sit in a higher round than the link it justifies; equal or decreasing rounds break the chain's causal structure. verify scans every adjacent pair (windows(2)) and rejects the first non-increasing step.
Source
Thrown at linera-chain/src/justification/mod.rs:219
///
/// Link signatures are deliberately *not* verified here. The quorum built on top of this
/// chain signs the chain's commitment, so a single signature check over that quorum attests
/// every link below it: each link's voters verified the quorum beneath them before signing
/// over its hash. Signing over an invalid quorum is itself an attributable fault, so link
/// signatures are only re-checked when auditing a chain to blame the validators that attested
/// an invalid link.
///
/// The chain length needs no explicit cap: strictly increasing rounds mean a chain of `n`
/// links spans `n` distinct rounds, and each link must carry a genuine quorum, so a longer
/// chain necessarily reaches a higher round and cannot be inflated cheaply. The observed
/// length is recorded as a metric so real contention shows up in monitoring.
pub fn verify(&self, value_hash: CryptoHash) -> Result<Option<CryptoHash>, ChainError> {
#[cfg(with_metrics)]
metrics::JUSTIFICATION_CHAIN_LENGTH
.with_label_values(&[])
.observe(self.links.len() as f64);
for window in self.links.windows(2) {
ensure!(
window[0].round < window[1].round,
ChainError::JustificationRoundsNotIncreasing
);
}
Ok(self.commitment(value_hash))
}
}
/// A confirmed block's *header* together with the justification that makes it self-contained
/// evidence: the round and quorum of `ConfirmedBlock` votes that finalized it, and the chain of
/// `ValidatedBlock` quorums for the same block, with its top link in the round the block was
/// confirmed. Only the header travels, never the block body. The header hashes to the value the
/// votes sign (`CryptoHash::new(&header)`) and carries the chain ID and height that scope the
/// fault. This is the shape a `ConfirmedBlockCertificate` reduces to for fault attribution.
#[derive(Clone, Debug)]
pub struct JustifiedConfirmation {
/// The header of the confirmed block. Its hash is what the chain's `ValidatedBlock` and
/// `ConfirmedBlock` votes sign (`ValidatedBlock` and `ConfirmedBlock` wrap the same block).View on GitHub (pinned to 6c226ddcb3)
Solutions
- Append links in strictly increasing round order — sort collected quorums by round before building the chain.
- Validate round ordering at deserialization/network ingress with verify() before storing or forwarding the chain.
- If building from a map of round -> quorum, iterate rounds sorted ascending.
- Treat chains failing this check as malicious input; do not attempt repair.
Example fix
// before let chain = JustificationChain::new(links_as_received); // unordered chain.verify(value_hash)?; // Err: rounds not increasing // after let mut links = links_as_received; links.sort_by_key(|l| l.round); links.dedup_by_key(|l| l.round); let chain = JustificationChain::new(links); chain.verify(value_hash)?;
Defensive patterns
Strategy: validation
Validate before calling
// verify round ordering before accepting a chain
for pair in chain.links().windows(2) {
if pair[0].round >= pair[1].round {
return Err(anyhow::anyhow!("justification chain rounds not strictly increasing"));
}
} Type guard
fn has_increasing_rounds(chain: &JustificationChain) -> bool {
chain.links().windows(2).all(|w| w[0].round < w[1].round)
} Try / catch
match chain.verify(value_hash) {
Err(ChainError::JustificationRoundsNotIncreasing) => {
// malformed or malicious chain: reject and penalize the sender
Err(anyhow::anyhow!("rejecting malformed justification chain"))
}
other => other,
} Prevention
- Append links in ascending round order only (the append API requires it).
- Run verify() at network ingress before storing a received chain.
- Never attempt to repair a non-increasing chain by reordering — reject it.
When it happens
Trigger: Calling JustificationChain::verify(value_hash) on a chain built by appending links out of round order, appending a link whose round is <= the current top link's round (the append API documents that new links must be in higher rounds), or deserializing a network-received chain with malformed ordering.
Common situations: Assembling chains from votes collected out of order without sorting by round; a Byzantine validator shipping a crafted chain with repeated rounds to probe clients; serialization bugs that reorder links; tests constructing chains with default Round::new(0) for every link.
Related errors
- Justification chain must lie in rounds strictly below the ce
- EquivocationProofNoLockViolation
- EquivocationProofNoFirstRoundViolation
- JustificationUnlockingRoundMismatch
- no signer found for owner ${owner}
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/15c33f1ca623afe6.
Report an issue: GitHub.