{"record":{"id":"0ddab96de0fdf9bc","repo":"linera-io/linera-protocol","slug":"chain-is-expecting-a-next-block-at-height-expecte","errorCode":null,"errorMessage":"Chain is expecting a next block at height {expected_block_height} but the given block is at height {found_block_height} instead","messagePattern":"Chain is expecting a next block at height (.+?) but the given block is at height (.+?) instead","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/chain.rs","lineNumber":413,"sourceCode":"    /// no entry is queried before its first push; ones that leave the committee are pruned.\n    pub exported_heights: RegisterView<C, NonCanonicalBTreeMap<ValidatorPublicKey, BlockHeight>>,\n}\n\n/// Block-chaining state.\n#[cfg_attr(with_graphql, derive(async_graphql::SimpleObject))]\n#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize, Allocative)]\npub 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,","sourceCodeStart":395,"sourceCodeEnd":431,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/chain.rs#L395-L431","documentation":"ChainTipState::verify_block_chaining (linera-chain/src/chain.rs:412) requires a proposed block's height to equal the chain tip's next_block_height exactly — blocks must extend the chain contiguously, no gaps and no replays. The validator rejects the whole proposal with UnexpectedBlockHeight via try_handle_block_proposal (linera-core/src/chain_worker/state.rs:2524), and process_validated_block re-checks it (state.rs:883). Hitting it almost always means your view of the chain is stale: someone else's block at that height was already processed.","triggerScenarios":"Submitting a BlockProposal built from a cached ChainInfo while another block at the same height was already confirmed (concurrent proposers on a multi-owner chain); replaying an old proposal after the chain advanced; two clients sharing one owner key proposing in parallel.","commonSituations":"Client SDK caches chain height across successive block submissions instead of re-querying; multi-owner chains where another owner proposed first in the same round; a lagging node catching up and receiving proposals newer than its tip; tests replaying recorded proposals against a chain that has moved on.","solutions":["Re-fetch the chain's ChainInfoResponse and rebuild the block at the returned next_block_height, then resubmit","On multi-owner chains, ensure only the current round's leader proposes, or handle the loss by re-deriving transactions on the new tip","If a single-owner chain hits this, check for two processes using the same owner key and stop one","In retry loops, always refresh height and parent hash together instead of assuming the previous values still hold"],"exampleFix":"// before: building from a cached height\nlet block = Block::new(chain_id, cached_height, cached_parent, transactions);\nclient.submit_block(block).await?;\n\n// after: refresh the tip first\nlet info = client.chain_info(chain_id).await?;\nlet tip = info.info.chain_tip();\nlet block = ProposedBlock {\n    height: tip.next_block_height,\n    previous_block_hash: tip.block_hash,\n    // ...\n};\nclient.submit_block(block).await?;","handlingStrategy":"validation","validationCode":"// Before submitting, make sure the height matches the chain's tip:\nlet info = client.chain_info(chain_id).await?;\nlet next = info.info.next_block_height; // tip's next_block_height\nif block.height != next {\n    anyhow::bail!(\n        \"block height {} != expected {}; refresh the tip and rebuild\",\n        block.height, next\n    );\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(WorkerError::ChainError(ChainError::UnexpectedBlockHeight { expected_block_height, found_block_height })) => {\n        // stale view: refresh chain info, rebuild at `expected_block_height`, resubmit ONCE\n    }\n    other => other?,\n}","preventionTips":["Re-query next_block_height for every new proposal; never cache it across submissions","On multi-owner chains, propose only in rounds you lead","Keep (height, parent_hash) as one atomic snapshot of the tip"],"tags":["linera","block","height","stale-state","consensus","rust"],"backgroundTag":"wrong-block-height","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}