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
- Refresh the chain tip (both block_hash and next_block_height together) and rebuild the proposal on the new parent
- If the competing block is valid, re-derive your transactions on top of it instead of fighting it
- Coordinate via rounds/leader election so only one owner proposes per round
- 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
- Fetch height and parent hash together in one ChainInfo query
- Handle the lost-race case in multi-owner flows: rebase instead of resubmitting
- Ensure only one process proposes with a given owner key
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
- Chain is expecting a next block at height {expected_block_he
- Empty blocks are not allowed
- Block timestamp {new} must not be earlier than the parent bl
- Missing operations or messages from mandatory applications:
- no signer found for owner ${owner}
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/801c9a281b7ca046.
Report an issue: GitHub.