linera-io/linera-protocol · error

Failed to deserialize event

Error message

Failed to deserialize event

What it means

ContractRuntime::read_event fetches raw event bytes from a subscribed stream via the host and then does bcs::from_bytes::<Application::EventValue>. The expect panics when the bytes stored in the stream cannot be decoded into the reader application's declared EventValue type — i.e. the writer's event schema and the reader's diverged.

Source

Thrown at linera-sdk/src/contract/runtime.rs:328

    /// Adds a new item to an event stream. Returns the new event's index in the stream.
    pub fn emit(&mut self, name: StreamName, value: &Application::EventValue) -> u32 {
        contract_wit::emit(
            &name.into(),
            &bcs::to_bytes(value).expect("Failed to serialize event"),
        )
    }

    /// Reads an event from a stream. Returns the event's value.
    ///
    /// Fails the block if the event doesn't exist.
    pub fn read_event(
        &mut self,
        chain_id: ChainId,
        name: StreamName,
        index: u32,
    ) -> Application::EventValue {
        let event = contract_wit::read_event(chain_id.into(), &name.into(), index);
        bcs::from_bytes(&event).expect("Failed to deserialize event")
    }

    /// Subscribes this application to an event stream.
    pub fn subscribe_to_events(
        &mut self,
        chain_id: ChainId,
        application_id: ApplicationId,
        name: StreamName,
    ) {
        contract_wit::subscribe_to_events(chain_id.into(), application_id.into(), &name.into())
    }

    /// Unsubscribes this application from an event stream.
    pub fn unsubscribe_from_events(
        &mut self,
        chain_id: ChainId,
        application_id: ApplicationId,
        name: StreamName,

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Ensure the subscriber is built against the same crate version as the application that emits the stream, and redeploy the subscriber after the emitter's EventValue changes
  2. Use unique StreamNames per application (namespace them with the application name) so foreign payloads never land in your stream
  3. Design EventValue as a versioned enum (e.g. { V1(...), V2(...) }) so older readers keep decoding newer events
  4. Replay/verify with a unit test that BCS-encodes a sample event from the emitter and decodes it as the subscriber's EventValue

Example fix

// before: raw struct that breaks on any field change
#[derive(Serialize, Deserialize)]
struct EventValue { from: AccountOwner, amount: Amount }

// after: versioned enum, old readers stay compatible
#[derive(Serialize, Deserialize)]
enum EventValue {
    V1 { from: AccountOwner, amount: Amount },
    V2 { from: AccountOwner, amount: Amount, memo: String },
}
Defensive patterns

Strategy: validation

Validate before calling

// Prove the emitter's schema decodes before subscribing.
#[test]
fn event_round_trip() {
    let ev: EventValue = bcs::from_bytes(&emitter_sample_bytes()).unwrap();
    assert_eq!(ev, sample_event());
}

Prevention

When it happens

Trigger: Calling runtime.read_event(chain_id, name, index) for a stream produced by an application whose EventValue type differs from the Application::EventValue the reading contract was compiled with (different struct layout, enum variants, or field types).

Common situations: Upgrading the emitting application (new event fields) while subscribers still run the old module; two applications accidentally sharing the same StreamName with different payload types; copy-paste of a stream name across unrelated apps.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/32093db2dc03daa8. Report an issue: GitHub.