{"record":{"id":"59ad817570856710","repo":"linera-io/linera-protocol","slug":"incoming-message-bundle-in-block-proposed-to-chai","errorCode":null,"errorMessage":"Incoming message bundle in block proposed to {chain_id} has timestamp {bundle_timestamp:}, which is later than the block timestamp {block_timestamp:}.","messagePattern":"Incoming message bundle in block proposed to (.+?) has timestamp (.+?), which is later than the block timestamp (.+?)\\.","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/chain.rs","lineNumber":730,"sourceCode":"\n    /// Removes the incoming message bundles in the block from the inboxes.\n    ///\n    /// If `must_be_present` is `true`, an error is returned if any of the bundles have not been\n    /// added to the inbox yet. So this should be `true` if the bundles are in a block _proposal_,\n    /// and `false` if the block is already confirmed.\n    #[instrument(skip_all, fields(\n        chain_id = %self.chain_id(),\n    ))]\n    pub async fn remove_bundles_from_inboxes(\n        &mut self,\n        timestamp: Timestamp,\n        must_be_present: bool,\n        incoming_bundles: impl IntoIterator<Item = &IncomingBundle>,\n    ) -> Result<(), ChainError> {\n        let chain_id = self.chain_id();\n        let mut bundles_by_origin: BTreeMap<_, Vec<&MessageBundle>> = Default::default();\n        for IncomingBundle { bundle, origin, .. } in incoming_bundles {\n            ensure!(\n                bundle.timestamp <= timestamp,\n                ChainError::IncorrectBundleTimestamp {\n                    chain_id,\n                    bundle_timestamp: bundle.timestamp,\n                    block_timestamp: timestamp,\n                }\n            );\n            let bundles = bundles_by_origin.entry(*origin).or_default();\n            bundles.push(bundle);\n        }\n        let origins = bundles_by_origin.keys().copied().collect::<Vec<_>>();\n        let inboxes = self.inboxes.try_load_entries_mut(&origins).await?;\n        // When the bundles must already be present (block proposals), collect *every* missing\n        // `(origin, height)` rather than bailing on the first, so the caller can be told the\n        // full set of cross-chain updates to fetch in a single round-trip.\n        let mut missing_bundles = Vec::new();\n        for ((origin, bundles), mut inbox) in bundles_by_origin.into_iter().zip(inboxes) {\n            tracing::trace!(","sourceCodeStart":712,"sourceCodeEnd":748,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/chain.rs#L712-L748","documentation":"remove_bundles_from_inboxes (linera-chain/src/chain.rs:721) verifies that every incoming bundle's timestamp is <= the receiving block's timestamp (chain.rs:730-737). Bundles carry the timestamp of the sender block that produced them, so a block cannot claim to receive messages 'from the future' — its own timestamp must not be earlier than any bundle it includes. The check runs on the proposal path (must_be_present=true) before execution.","triggerScenarios":"Manually setting ProposedBlock.timestamp below the timestamp of an included incoming bundle; a client clock behind the sender chain's clock; test code pinning block timestamps to fixed values while bundles from another chain carry later timestamps.","commonSituations":"Developers overriding block timestamps instead of letting the client derive them (the default takes the max of parent timestamp, incoming bundle timestamps, and local time); clock skew between chains operated by different machines; fixtures with hardcoded timestamps that drift out of validity.","solutions":["Do not set block.timestamp manually — use the client's default derivation, which already accounts for bundle timestamps","If you must set it, use max(parent_timestamp, max(bundle.timestamp), local_time) or later","Retry after wall-clock time passes the highest bundle timestamp if timestamps come from local clocks"],"exampleFix":"// before: pinning the block timestamp\nlet block = ProposedBlock { timestamp: Timestamp::from(1_000), incoming_bundles, .. };\n\n// after: at least the newest bundle timestamp, e.g. let the client derive it\nlet max_bundle_ts = incoming_bundles.iter().map(|b| b.bundle.timestamp).max().unwrap_or_default();\nlet block = ProposedBlock { timestamp: local_time.max(max_bundle_ts), incoming_bundles, .. };","handlingStrategy":"validation","validationCode":"// Before proposing, ensure the block timestamp covers every bundle:\nlet max_bundle_ts = block\n    .incoming_bundles()\n    .map(|b| b.bundle.timestamp)\n    .max()\n    .unwrap_or_default();\nif block.timestamp < max_bundle_ts {\n    block.timestamp = max_bundle_ts.max(local_time); // or let the client derive it\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(ChainError::IncorrectBundleTimestamp { bundle_timestamp, block_timestamp, .. }) => {\n        // bump block.timestamp to >= bundle_timestamp and re-propose\n    }\n    other => other?,\n}","preventionTips":["Never pin block timestamps manually; use the client default (max of parent, bundles, local time)","Keep proposing machines' clocks NTP-synced","In tests, derive timestamps relative to the chains involved rather than constants"],"tags":["linera","timestamp","cross-chain","messages","validation","rust"],"backgroundTag":"message-timestamp-newer-than-block","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}