linera-io/linera-protocol · error

Query {argument:?} is invalid and could not be deserialized

Error message

Query {argument:?} is invalid and could not be deserialized

What it means

The linera-sdk service entrypoint macro deserializes incoming query bytes with serde_json into the app's Query type before calling handle_query. Bytes that are not valid JSON for that type panic the service guest with 'Query ... is invalid and could not be deserialized'. The request reaches the application service through GraphQL queries against the chain.

Source

Thrown at linera-sdk/src/service/mod.rs:46

///
/// Generates the necessary boilerplate for implementing the service WIT interface, exporting the
/// necessary resource types and functions so that the host can call the application service.
#[macro_export]
macro_rules! service {
    ($service:ident) => {
        #[doc(hidden)]
        static mut SERVICE: Option<$service> = None;

        /// Export the service interface.
        $crate::export_service!($service with_types_in $crate::service::wit);

        /// Mark the service type to be exported.
        impl $crate::service::wit::exports::linera::app::service_entrypoints::Guest for $service {
            fn handle_query(argument: Vec<u8>) -> Vec<u8> {
                use $crate::util::BlockingWait as _;
                $crate::ServiceLogger::install();
                let request = $crate::serde_json::from_slice(&argument)
                    .unwrap_or_else(|_| panic!("Query {argument:?} is invalid and could not be deserialized"));
                let response = $crate::service::run_async_entrypoint(
                    unsafe { &mut SERVICE },
                    move |service| service.handle_query(request).blocking_wait(),
                );
                $crate::serde_json::to_vec(&response)
                    .expect("Failed to serialize query response")
            }
        }

        /// Stub of a `main` entrypoint so that the binary doesn't fail to compile on targets other
        /// than WebAssembly.
        #[cfg(not(target_arch = "wasm32"))]
        fn main() {}
    };
}

/// Runs an asynchronous entrypoint in a blocking manner, by repeatedly polling the entrypoint
/// future.

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Match the JSON exactly to the app's Query type: correct variant names, field names, and casing
  2. Test the serialization client-side first: serde_json::from_slice::<Query>(bytes) must succeed before sending
  3. For enum queries use the tagged form the app's serde attributes produce (e.g. {"variant":{...}})
  4. Update query senders whenever the app's Query type changes and is re-published

Example fix

// before (wrong shape for enum Query)
let bytes = br#"{"account": "0xab.."}"#;

// after (correct serde_enum tagged form)
let bytes = serde_json::to_vec(&Query::Balance { owner })?; // e.g. {"balance":{"owner":"0xab.."}}
Defensive patterns

Strategy: validation

Validate before calling

// Client-side, before sending a query to the service:
let payload = serde_json::to_string(&query)?;
serde_json::from_str::<app::Query>(&payload)
    .expect("payload must round-trip as the app's Query type");
// now safe to submit via GraphQL

Prevention

When it happens

Trigger: Sending a GraphQL query (chain application service query) whose argument string is not valid JSON for the app's declared Query type: wrong field names/types, extra/missing fields, or an entirely different shape (e.g. reusing an operation-style argument for a query).

Common situations: Frontends sending snake_case where the app's serde config expects camelCase (or vice versa); querying a newly published app with arguments designed for its previous version; examples like fungible/counters where each app has its own Query enum variants.

Related errors


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