linera-io/linera-protocol · critical · ChainError
FalseFirstRoundAttestation
FalseFirstRoundAttestation
Error message
Certificate carries the first-round attestation but was not confirmed in the chain's first round
What it means
A ConfirmedBlockCertificate can carry a first-round attestation (certificate.first_round()), a cheap proof that the block was finalized in the chain's very first consensus round. When that attestation is present, execute_contiguous_block checks certificate.round() against ChainOwnership::first_round() (Round::Fast if super owners exist, Validator(0) with no owners, MultiLeader(0) when multi_leader_rounds > 0, else SingleLeader(0)). The error means the certificate claims the attestation but was actually certified in a different round, so the attestation is false.
Source
Thrown at linera-core/src/chain_worker/state.rs:1300
// This should always be true for valid certificates.
ensure!(
tip.block_hash == block.header.previous_block_hash,
WorkerError::InvalidBlockChaining
);
// Verify that the chain is active and that the epoch we used for verifying
// the certificate is actually the active one on the chain.
self.initialize_and_save_if_needed().await?;
let (epoch, _) = self.chain.current_committee().await?;
check_block_epoch(epoch, chain_id, block.header.epoch)?;
// The chain is initialized and this block has not executed yet, so the current ownership
// is the configuration the block was proposed under — even for the chain's first block,
// whose ownership comes from the just-applied chain description. This is the point where
// the first-round attestation can be checked against the actual first round; blocks that
// are only preprocessed skip it and rely on the nodes that execute the chain in order.
if certificate.first_round() {
ensure!(
certificate.round() == self.chain.ownership().await?.first_round(),
ChainError::FalseFirstRoundAttestation
);
}
let published_blobs = block
.published_blob_ids()
.iter()
.filter_map(|blob_id| blobs.remove(blob_id))
.collect::<Vec<_>>();
let local_time = self.storage.clock().current_time();
if block.header.timestamp.duration_since(local_time) > self.config.block_time_grace_period {
warn!(
block_timestamp = %block.header.timestamp,
%local_time,
"Confirmed block has a timestamp in the future beyond the block time grace period"
);View on GitHub (pinned to 6c226ddcb3)
Solutions
- Regenerate the certificate so first_round is set only when round == ownership.first_round(), then resubmit.
- Update the client/wallet (linera-service) and validators to matching versions.
- Verify the chain's current ownership (super owners, multi_leader_rounds) matches what the proposer assumed when it built the attestation.
- If neither side is wrong, treat it as a protocol violation and inspect the certificate's round and signature justification.
Example fix
// before: attestation copied from the original fast proposal let first_round = true; // after: claim the attestation only when the round really is the first let first_round = round == ownership.first_round(); let cert = make_certificate(value, round, first_round, justification);
Defensive patterns
Strategy: validation
Validate before calling
// Client-side mirror of the validator check, before submitting.
if certificate.first_round() {
let ownership = client.ownership(chain_id).await?;
ensure!(
certificate.round() == ownership.first_round(),
ChainError::FalseFirstRoundAttestation
);
} Try / catch
match result {
Err(NodeError::WorkerError(ref err))
if matches!(**err, WorkerError::ChainError(ref e)
if matches!(**e, ChainError::FalseFirstRoundAttestation)) =>
{
// Drop the certificate, rebuild it without the first-round attestation, resubmit.
}
result => result,
} Prevention
- Set the first-round attestation only when the certification round equals ChainOwnership::first_round().
- After any ownership change, discard in-flight certificates and re-propose.
- Keep client and validator versions in lockstep when consensus certificate formats change.
- Never hand-assemble certificates in tests; use the same builders as production code.
When it happens
Trigger: A client that re-proposes a timed-out fast block in a higher round but keeps the first_round flag set in the confirmation; ownership changed between proposal and confirmation (super owners removed or multi_leader_rounds changed) so first_round() now computes a different round; hand-crafted or malformed certificates in tests.
Common situations: Client/wallet and validator versions mismatched after the first-round-attestation feature landed; chains whose ownership was migrated while a block was in flight; test fixtures that build certificates manually instead of via the standard propose/confirm flow.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- CannotRejectMessage
- Certificate justification commitment does not match its just
- Certificate unlocking round does not match the top of its ju
- Justification chain must lie in rounds strictly below the ce
- Certificate carries the first-round attestation but was not
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/612246b721a79655.
Report an issue: GitHub.