linera-io/linera-protocol · critical
Failed to deserialize message
Error message
Failed to deserialize message
What it means
The contract macro's execute_message entrypoint (linera-sdk/src/contract/mod.rs) BCS-deserializes incoming message bytes into the contract's Message type and expects success. The panic fires when a cross-application/cross-chain message was serialized from a different Message schema than this contract declares - e.g. the sending application was upgraded but the receiving contract was not. The panic aborts message execution in the receiving contract.
Source
Thrown at linera-sdk/src/contract/mod.rs:81
let operation = <$contract as $crate::abi::ContractAbi>::deserialize_operation(operation)
.expect("Failed to deserialize `Operation` in execute_operation");
let response = contract.execute_operation(operation).blocking_wait();
<$contract as $crate::abi::ContractAbi>::serialize_response(response)
.expect("Failed to serialize `Response` in execute_operation")
},
)
}
fn execute_message(message: Vec<u8>) {
use $crate::util::BlockingWait as _;
$crate::contract::run_async_entrypoint::<$contract, _, _>(
unsafe { &mut CONTRACT },
move |contract| {
let message: <$contract as $crate::Contract>::Message =
$crate::bcs::from_bytes(&message)
.expect("Failed to deserialize message");
contract.execute_message(message).blocking_wait()
},
)
}
fn process_streams(updates: Vec<
$crate::contract::wit::exports::linera::app::contract_entrypoints::StreamUpdate,
>) {
use $crate::util::BlockingWait as _;
$crate::contract::run_async_entrypoint::<$contract, _, _>(
unsafe { &mut CONTRACT },
move |contract| {
let updates = updates.into_iter().map(Into::into).collect();
contract.process_streams(updates).blocking_wait()
},
)
}View on GitHub (pinned to 6c226ddcb3)
Solutions
- Redeploy both sender and receiver from the same shared crate so Message types match exactly.
- Treat any type change to Message as a new application version: publish anew rather than reusing the ID.
- Pin dApp component versions in the manifest so all modules deploy together.
- Round-trip test messages (bcs::to_bytes/from_bytes) in CI to catch schema drift before deploy.
Defensive patterns
Strategy: validation
Validate before calling
// Before sending a message to another application, verify both sides share the ABI: use counter_app_abi::Message; // shared crate compiled into both applications let bytes = bcs::to_bytes(&message)?; bcs::from_bytes::<Message>(&bytes)?; // schema sanity check off-chain
Type guard
fn message_round_trips(msg: &Message) -> bool {
bcs::to_bytes(msg).ok().and_then(|b| bcs::from_bytes::<Message>(&b).ok()).is_some()
} Try / catch
// Deserialization happens inside the receiving contract's runtime; a panic fails the // message execution on-chain and is not catchable by the sender. Prevent by validating // ABI compatibility before dispatch (bytecode hash + shared crate version check).
Prevention
- Keep Message in the shared crate and depend on it from both sender and receiver.
- Upgrade communicating applications together in one deployment, never piecemeal.
- Pin application module versions in the manifest and verify bytecode hashes after publish.
When it happens
Trigger: A message arrives from an application instance whose Message type differs from the receiving contract's: sender upgraded with changed message variants while the receiver runs old bytecode, or an application ID reused across incompatible versions.
Common situations: Inter-application messaging between two versions of the same dApp; a frontend composing messages with stale shared types; partial redeployment where only the sender was upgraded.
Related errors
- Failed to deserialize `Operation` in execute_operation
- Application parameters must be deserializable
- Failed to deserialize `Response` in cross-application call
- owner should be different from spender
- invalid block export configuration: {message}
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/82acfcce644f980c.
Report an issue: GitHub.