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
- Print the raw GraphQL response to see the actual `(hash, height)` pair the wrapper choked on
- Rebuild/reinstall wrapper and node service from the same source version
- If the chain is expected unknown, confirm the service really returns null for both fields
- 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
- Build the cli_wrappers crate and `linera service` from the same commit — schema drift is the usual cause
- In tests that tolerate unknown chains, handle both the Ok(None) case and the parse error distinctly
- When debugging, reproduce the query (`query { block(chainId: ...) { hash block { header { height } } } }`) against the service to inspect the raw JSON
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
- Query "{}" failed after {} retries.
- Failed to start node service
- expected query to start with 'query', got: {s}
- expected whitespace after 'query' keyword
- expected an operation name after 'query', e.g. 'query MyQuer
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/fee73559319c27ab.
Report an issue: GitHub.