linera-io/linera-protocol · error · ChainError
The previous block hash of a new block should match the…
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.
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
- Block timestamp must not be earlier than the parent block's…
- Empty blocks are not allowed
- Missing operations or messages from mandatory applications
- All operations on this chain must be from one of the…
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/801c9a281b7ca046.
Report an issue: GitHub.
Appendix: 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)