From 25f1d73629d02d43d6914ff3607deb6110ab3106 Mon Sep 17 00:00:00 2001 From: Carlos Jorge Date: Fri, 14 Nov 2025 04:42:39 +0000 Subject: [PATCH 1/2] graph: emit a block only when there's a block_ptr After this was merged https://github.com/graphprotocol/graph-node/pull/5160 substreams fatal errors can write SubgraphError{ block_ptr: None }. This introduces a bug that shows in Status API as those records can surface as fatalError.block = null violating the schema of type Block { hash: Bytes! number: BigInt! } A later commit https://github.com/graphprotocol/graph-node/pull/5971 makes it much easier to trigger this when doing a status API that returns all subgraphs (empty [] array for deployment ID) as failed substreams linger on in the indexing status set as paused instead of being unassigned. --- graph/src/data/subgraph/status.rs | 103 +++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/graph/src/data/subgraph/status.rs b/graph/src/data/subgraph/status.rs index e2c14751955..2f353ff7bb0 100644 --- a/graph/src/data/subgraph/status.rs +++ b/graph/src/data/subgraph/status.rs @@ -132,29 +132,6 @@ impl IntoValue for Info { history_blocks, } = self; - fn subgraph_error_to_value(subgraph_error: SubgraphError) -> r::Value { - let SubgraphError { - subgraph_id, - message, - block_ptr, - handler, - deterministic, - } = subgraph_error; - - object! { - __typename: "SubgraphError", - subgraphId: subgraph_id.to_string(), - message: message, - handler: handler, - block: object! { - __typename: "Block", - number: block_ptr.as_ref().map(|x| x.number), - hash: block_ptr.map(|x| r::Value::from(Value::Bytes(x.hash.into()))), - }, - deterministic: deterministic, - } - } - let non_fatal_errors: Vec<_> = non_fatal_errors .into_iter() .map(subgraph_error_to_value) @@ -176,3 +153,83 @@ impl IntoValue for Info { } } } + +fn subgraph_error_to_value(subgraph_error: SubgraphError) -> r::Value { + let SubgraphError { + subgraph_id, + message, + block_ptr, + handler, + deterministic, + } = subgraph_error; + + let block_value = block_ptr + .map(|ptr| { + object! { + __typename: "Block", + number: ptr.number, + hash: r::Value::from(Value::Bytes(ptr.hash.into())), + } + }) + .unwrap_or(r::Value::Null); + + object! { + __typename: "SubgraphError", + subgraphId: subgraph_id.to_string(), + message: message, + handler: handler, + block: block_value, + deterministic: deterministic, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::prelude::DeploymentHash; + use web3::types::H256; + + #[test] + fn subgraph_error_block_is_null_without_pointer() { + let deployment = DeploymentHash::new("QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco").unwrap(); + let value = subgraph_error_to_value(SubgraphError { + subgraph_id: deployment, + message: "boom".to_string(), + block_ptr: None, + handler: None, + deterministic: true, + }); + + match value { + r::Value::Object(map) => { + assert_eq!(map.get("block"), Some(&r::Value::Null)); + } + _ => panic!("expected object"), + } + } + + #[test] + fn subgraph_error_block_contains_data_when_present() { + let deployment = DeploymentHash::new("QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco").unwrap(); + let ptr = BlockPtr::new(H256::zero().into(), 42); + let value = subgraph_error_to_value(SubgraphError { + subgraph_id: deployment, + message: "boom".to_string(), + block_ptr: Some(ptr), + handler: None, + deterministic: true, + }); + + match value { + r::Value::Object(map) => { + match map.get("block").expect("block field present") { + r::Value::Object(block) => { + assert_eq!(block.get("number"), Some(&r::Value::Int(42.into()))); + } + other => panic!("unexpected block value {other:?}"), + } + } + _ => panic!("expected object"), + } + } +} From 16120924d18be0e2b1fc57c0e6d2bc3d372d2ff7 Mon Sep 17 00:00:00 2001 From: Carlos Jorge Date: Fri, 14 Nov 2025 04:51:21 +0000 Subject: [PATCH 2/2] fix fmt --- graph/src/data/subgraph/status.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/graph/src/data/subgraph/status.rs b/graph/src/data/subgraph/status.rs index 2f353ff7bb0..64a9e0d4051 100644 --- a/graph/src/data/subgraph/status.rs +++ b/graph/src/data/subgraph/status.rs @@ -191,7 +191,8 @@ mod tests { #[test] fn subgraph_error_block_is_null_without_pointer() { - let deployment = DeploymentHash::new("QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco").unwrap(); + let deployment = + DeploymentHash::new("QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco").unwrap(); let value = subgraph_error_to_value(SubgraphError { subgraph_id: deployment, message: "boom".to_string(), @@ -210,7 +211,8 @@ mod tests { #[test] fn subgraph_error_block_contains_data_when_present() { - let deployment = DeploymentHash::new("QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco").unwrap(); + let deployment = + DeploymentHash::new("QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco").unwrap(); let ptr = BlockPtr::new(H256::zero().into(), 42); let value = subgraph_error_to_value(SubgraphError { subgraph_id: deployment, @@ -221,14 +223,12 @@ mod tests { }); match value { - r::Value::Object(map) => { - match map.get("block").expect("block field present") { - r::Value::Object(block) => { - assert_eq!(block.get("number"), Some(&r::Value::Int(42.into()))); - } - other => panic!("unexpected block value {other:?}"), + r::Value::Object(map) => match map.get("block").expect("block field present") { + r::Value::Object(block) => { + assert_eq!(block.get("number"), Some(&r::Value::Int(42.into()))); } - } + other => panic!("unexpected block value {other:?}"), + }, _ => panic!("expected object"), } }