{"record":{"id":"801c9a281b7ca046","repo":"linera-io/linera-protocol","slug":"the-previous-block-hash-of-a-new-block-should-matc","errorCode":null,"errorMessage":"The previous block hash of a new block should match the last block of the chain","messagePattern":"The previous block hash of a new block should match the last block of the chain","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/chain.rs","lineNumber":420,"sourceCode":"pub struct ChainTipState {\n    /// Hash of the latest certified block in this chain, if any.\n    pub block_hash: Option<CryptoHash>,\n    /// Sequence number tracking blocks.\n    pub next_block_height: BlockHeight,\n}\n\nimpl ChainTipState {\n    /// Checks that the proposed block is suitable, i.e. at the expected height and with the\n    /// expected parent.\n    pub fn verify_block_chaining(&self, new_block: &ProposedBlock) -> Result<(), ChainError> {\n        ensure!(\n            new_block.height == self.next_block_height,\n            ChainError::UnexpectedBlockHeight {\n                expected_block_height: self.next_block_height,\n                found_block_height: new_block.height\n            }\n        );\n        ensure!(\n            new_block.previous_block_hash == self.block_hash,\n            ChainError::UnexpectedPreviousBlockHash\n        );\n        Ok(())\n    }\n\n    /// Returns `true` if the validated block's height is below the tip height. Returns an error if\n    /// it is higher than the tip.\n    pub fn already_validated_block(&self, height: BlockHeight) -> Result<bool, ChainError> {\n        ensure!(\n            self.next_block_height >= height,\n            ChainError::MissingEarlierBlocks {\n                current_block_height: self.next_block_height,\n            }\n        );\n        Ok(self.next_block_height > height)\n    }\n}","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/chain.rs#L402-L438","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: only height checked/refreshed\nlet block = ProposedBlock { height: tip.next_block_height, previous_block_hash: old_parent, .. };\n\n// after: parent must be the CURRENT tip hash\nlet tip = client.chain_info(chain_id).await?.info.chain_tip();\nassert_eq!(block.previous_block_hash, tip.block_hash); // rebase if not\nlet block = if block.previous_block_hash != tip.block_hash {\n    rebuild_on(tip).await?\n} else { block };","handlingStrategy":"validation","validationCode":"// Verify the parent hash against the CURRENT tip before submitting:\nlet tip = client.chain_info(chain_id).await?.info.chain_tip();\nif block.previous_block_hash != tip.block_hash {\n    anyhow::bail!(\"parent hash {:?} is not the tip {:?}; rebase the block\", \n        block.previous_block_hash, tip.block_hash);\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(WorkerError::ChainError(ChainError::UnexpectedPreviousBlockHash)) => {\n        // our parent lost the race: fetch the winning tip, re-derive transactions on it, retry once\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["linera","block","fork","parent-hash","stale-state","rust"],"backgroundTag":"parent-hash-mismatch","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}