linera-io/linera-protocol · error · ChainClientError

Protocol error within chain client: A quorum voted with an u

Error message

Protocol error within chain client: A quorum voted with an unlocking round that does not match the proposal's justification chain

What it means

Raised in Client::submit_block_proposal on the validated-retry path: the winning quorum signed an unlocking_round that does not equal the top unlocking round of the justification chain carried by our proposal. Per the source comment, the quorum may have formed from a competing proposal that cited a different certificate, so its votes sign a different unlocking round; gluing our chain onto those votes would build a certificate that fails verification downstream. The client rejects it as a retryable error rather than assembling the mismatched certificate.

Source

Thrown at linera-core/src/client/mod.rs:1434

                    }
                }
            }
        });

        let quorum = self
            .communicate_chain_action(&committee, submit_action, value)
            .await?;

        clock_skew_check_handle.await;

        // The justification chain comes from our own proposal, but the winning quorum may have
        // been formed from a competing proposal that cited a different certificate: its votes then
        // sign a different unlocking round and justification commitment, and gluing our chain onto
        // them would build a certificate that fails verification downstream. Reject that here with
        // a retryable error rather than assembling a mismatched certificate. (For confirmed and
        // timeout quorums both sides are `None`, so this only bites the validated-retry case it is
        // meant to guard.)
        ensure!(
            quorum.unlocking_round() == justification.top_unlocking_round(),
            chain_client::Error::ProtocolError(
                "A quorum voted with an unlocking round that does not match the proposal's \
                 justification chain",
            )
        );
        ensure!(
            quorum.justification_commitment() == justification.commitment(quorum.hash()),
            chain_client::Error::ProtocolError(
                "A quorum voted with a justification commitment that does not match the \
                 proposal's justification chain",
            )
        );
        let certificate = T::make_certificate(quorum, justification);
        self.handle_certificate::<T>(certificate.clone()).await?;
        Ok(certificate)
    }

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Retry: the pending-block loop re-synchronizes chain state from validators and re-proposes on top of whatever was validated
  2. Ensure a single writer per chain (one client/process proposing) to avoid competing proposals
  3. Align validator versions across the committee
  4. If it persists on an idle chain, collect the proposals/certificates involved and report a protocol bug
Defensive patterns

Strategy: retry

Type guard

fn is_unlocking_round_mismatch(err: &chain_client::Error) -> bool {
    matches!(
        err,
        chain_client::Error::ProtocolError(
            "A quorum voted with an unlocking round that does not match the proposal's \
             justification chain"
        )
    )
}

Try / catch

match client.process_pending_block().await {
    Err(e) if is_unlocking_round_mismatch(&e) => {
        // Competing proposal won the validated quorum; absorb it and rebuild on top.
        client.synchronize_chain_state_from_all_validators().await?;
        client.process_pending_block().await
    }
    other => other,
}

Prevention

When it happens

Trigger: Regular (validated) retry of a block proposal while validators concurrently validated a competing proposal citing a different certificate; two proposers racing in the same round; a validator set that latched onto a competing validated block. Confirmed/timeout quorums have unlocking_round None on both sides, so only the validated-retry case triggers this.

Common situations: Concurrent clients retrying a failed block on the same owner chain; leader-timeout churn where multiple proposals circulate; mixed-version validators disagreeing on justification handling during a protocol upgrade.

Related errors


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