linera-io/linera-protocol · error · anyhow

Expected a tip hash string, but got {invalid_data:?} instead

Error message

Expected a tip hash string, but got {invalid_data:?} instead

What it means

`chain_tip` parses the response of `query { block(chainId: ...) { hash block { header { height } } } }`. It expects one of exactly two shapes: both `hash` and `height` null (unknown chain → `None`), or `hash` a JSON string and `height` a JSON number. Any other combination — one side null, hash as a number, height as a string — falls into the `invalid_data` arm and bails with the offending `(hash, height)` pair.

Source

Thrown at linera-service/src/cli_wrappers/wallet.rs:2033

            r#"query {{ block(chainId: "{chain}") {{
                hash
                block {{ header {{ height }} }}
            }} }}"#
        );

        let mut response = self.query_node(&query).await?;

        match (
            mem::take(&mut response["block"]["hash"]),
            mem::take(&mut response["block"]["block"]["header"]["height"]),
        ) {
            (Value::Null, Value::Null) => Ok(None),
            (Value::String(hash), Value::Number(height)) => Ok(Some((
                hash.parse()
                    .context("Received an invalid hash {hash:?} for chain tip")?,
                BlockHeight(height.as_u64().unwrap()),
            ))),
            invalid_data => bail!("Expected a tip hash string, but got {invalid_data:?} instead"),
        }
    }

    /// Subscribes to the node service and returns a stream of notifications about a chain.
    pub async fn notifications(
        &self,
        chain_id: ChainId,
    ) -> Result<Pin<Box<impl Stream<Item = Result<Notification>>>>> {
        let query = format!("subscription {{ notifications(chainId: \"{chain_id}\") }}",);
        let url = format!("ws://localhost:{}/ws", self.port);
        let mut request = url.into_client_request()?;
        request.headers_mut().insert(
            "Sec-WebSocket-Protocol",
            HeaderValue::from_str("graphql-transport-ws")?,
        );
        let (mut websocket, _) = async_tungstenite::tokio::connect_async(request).await?;
        let init_json = json!({
          "type": "connection_init",

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Print the raw GraphQL response to see the actual `(hash, height)` pair the wrapper choked on
  2. Rebuild/reinstall wrapper and node service from the same source version
  3. If the chain is expected unknown, confirm the service really returns null for both fields
  4. In throwaway scripts, parse defensively: treat any mismatched pair as 'no tip' instead of failing
Defensive patterns

Strategy: type-guard

Type guard

use serde_json::Value;

/// Returns Some(result) only for shapes chain_tip can parse.
fn as_chain_tip(hash: &Value, height: &Value) -> Option<Option<(CryptoHash, BlockHeight)>> {
    match (hash, height) {
        (Value::Null, Value::Null) => Some(None),
        (Value::String(h), Value::Number(n)) => {
            let height = n.as_u64()?;
            let hash = h.parse().ok()?;
            Some(Some((hash, BlockHeight(height))))
        }
        _ => None, // mismatched pair: reject before chain_tip bails
    }
}

Try / catch

match client.chain_tip(chain).await {
    Err(e) if e.to_string().contains("Expected a tip hash string") => {
        // schema/type mismatch with the service; dump the raw response and
        // check wrapper-vs-service version alignment before retrying
        dump_raw_block_response(chain).await;
        Err(e)
    }
    result => result,
}

Prevention

When it happens

Trigger: A node-service response where `block.hash`/`block.block.header.height` have unexpected JSON types or only one of the two is null — most often a version/schema mismatch between the wallet wrapper and the running service.

Common situations: Wrapper crate built from a different commit than the running `linera service`; a chain in a transient state where one field is populated but not the other; service responses shaped differently after a schema change.

Related errors


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