linera-io/linera-protocol · error · ChainError

The previous block hash of a new block should match the last

Error message

The previous block hash of a new block should match the last block of the chain

What it means

The second half of verify_block_chaining (linera-chain/src/chain.rs:420-423): the proposal's previous_block_hash must equal the chain tip's current block_hash. The height can match while the parent differs — that means your block extends a different (forked or stale) version of the chain at that height. The validator rejects it to prevent two conflicting blocks at the same height from the same view.

Source

Thrown at linera-chain/src/chain.rs:420

pub struct ChainTipState {
    /// Hash of the latest certified block in this chain, if any.
    pub block_hash: Option<CryptoHash>,
    /// Sequence number tracking blocks.
    pub next_block_height: BlockHeight,
}

impl ChainTipState {
    /// Checks that the proposed block is suitable, i.e. at the expected height and with the
    /// expected parent.
    pub fn verify_block_chaining(&self, new_block: &ProposedBlock) -> Result<(), ChainError> {
        ensure!(
            new_block.height == self.next_block_height,
            ChainError::UnexpectedBlockHeight {
                expected_block_height: self.next_block_height,
                found_block_height: new_block.height
            }
        );
        ensure!(
            new_block.previous_block_hash == self.block_hash,
            ChainError::UnexpectedPreviousBlockHash
        );
        Ok(())
    }

    /// Returns `true` if the validated block's height is below the tip height. Returns an error if
    /// it is higher than the tip.
    pub fn already_validated_block(&self, height: BlockHeight) -> Result<bool, ChainError> {
        ensure!(
            self.next_block_height >= height,
            ChainError::MissingEarlierBlocks {
                current_block_height: self.next_block_height,
            }
        );
        Ok(self.next_block_height > height)
    }
}

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Refresh the chain tip (both block_hash and next_block_height together) and rebuild the proposal on the new parent
  2. If the competing block is valid, re-derive your transactions on top of it instead of fighting it
  3. Coordinate via rounds/leader election so only one owner proposes per round
  4. Never cache one of (height, parent_hash) without the other

Example fix

// before: only height checked/refreshed
let block = ProposedBlock { height: tip.next_block_height, previous_block_hash: old_parent, .. };

// after: parent must be the CURRENT tip hash
let tip = client.chain_info(chain_id).await?.info.chain_tip();
assert_eq!(block.previous_block_hash, tip.block_hash); // rebase if not
let block = if block.previous_block_hash != tip.block_hash {
    rebuild_on(tip).await?
} else { block };
Defensive patterns

Strategy: validation

Validate before calling

// Verify the parent hash against the CURRENT tip before submitting:
let tip = client.chain_info(chain_id).await?.info.chain_tip();
if block.previous_block_hash != tip.block_hash {
    anyhow::bail!("parent hash {:?} is not the tip {:?}; rebase the block", 
        block.previous_block_hash, tip.block_hash);
}

Try / catch

match result {
    Err(WorkerError::ChainError(ChainError::UnexpectedPreviousBlockHash)) => {
        // our parent lost the race: fetch the winning tip, re-derive transactions on it, retry once
    }
    other => other?,
}

Prevention

When it happens

Trigger: Another block at the same height got confirmed first (your parent is no longer the tip); concurrent proposers on a multi-owner chain picked different parents; submitting a proposal built from a pre-reorg/stale ChainInfo; replaying a recorded proposal after a different block won the round.

Common situations: Race between two owners of a multi-owner chain; client refreshed the height but not the parent hash (or vice versa); network delivered a competing proposal first; test harnesses reusing fixtures whose parent hash no longer matches.

Related errors


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