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

  1. Regenerate the certificate so first_round is set only when round == ownership.first_round(), then resubmit.
  2. Update the client/wallet (linera-service) and validators to matching versions.
  3. Verify the chain's current ownership (super owners, multi_leader_rounds) matches what the proposer assumed when it built the attestation.
  4. 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

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

Related errors


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