Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changes/fixed/3187.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Use `String` for block type in GraphQL response extensions
25 changes: 25 additions & 0 deletions crates/client/src/reqwest_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ use fuel_core_types::{
},
fuel_types::BlockHeight,
};
use serde::{
Deserialize,
de::{
self,
Deserializer,
},
};
use std::{
future::Future,
marker::PhantomData,
pin::Pin,
str::FromStr,
};

#[derive(Debug, Clone, serde::Serialize)]
Expand All @@ -24,6 +32,7 @@ pub struct ExtensionsRequest {
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ExtensionsResponse {
pub required_fuel_block_height: Option<BlockHeight>,
#[serde(default, deserialize_with = "deserialize_block_height_option")]
pub current_fuel_block_height: Option<BlockHeight>,
pub fuel_block_height_precondition_failed: Option<bool>,
pub current_stf_version: Option<StateTransitionBytecodeVersion>,
Expand Down Expand Up @@ -58,6 +67,22 @@ impl<Operation> FuelOperation<Operation> {
}
}

fn deserialize_block_height_option<'de, D>(
deserializer: D,
) -> Result<Option<BlockHeight>, D::Error>
where
D: Deserializer<'de>,
{
let value = Option::<String>::deserialize(deserializer)?;
match value {
None => Ok(None),
Some(value) => {
let height = BlockHeight::from_str(&value).map_err(de::Error::custom)?;
Ok(Some(height))
}
}
}

#[cfg(not(target_arch = "wasm32"))]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

Expand Down
22 changes: 17 additions & 5 deletions crates/fuel-core/src/graphql_api/extensions/chain_state_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,8 @@ fn set_current_state<E>(
Value::Number(current_stf_version.into()),
);

let current_block_height: u32 = *current_block_height;
extensions.set(
CURRENT_FUEL_BLOCK_HEIGHT,
Value::Number(current_block_height.into()),
);
let block_height_str = block_to_trimmed_string(current_block_height);
extensions.set(CURRENT_FUEL_BLOCK_HEIGHT, Value::String(block_height_str));
}

trait SetExtensionsResponse {
Expand All @@ -141,6 +138,21 @@ impl SetExtensionsResponse for BTreeMap<String, Value> {
}
}

fn block_to_trimmed_string(block_height: BlockHeight) -> String {
let mut block_height_str = block_height.to_string();

// trim leading zeros
let cut = block_height_str
.as_bytes()
.iter()
.position(|&b| b != b'0')
// if it's all zeros, keep a single "0"
.unwrap_or(block_height_str.len().saturating_sub(1));

block_height_str.drain(..cut);
block_height_str
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary leading zero trimming is dead code

Low Severity

The block_to_trimmed_string function contains logic to trim leading zeros from a string (lines 144-152), but this code path can never execute meaningfully. In Rust, u32::to_string() never produces leading zeros - it always outputs the minimal decimal representation. The entire trimming logic is dead code that adds unnecessary complexity. The function could be simplified to just u32::from(block_height).to_string().

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... that's not what I was getting. I was getting leading zeros and that's why I added it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimmed hex string breaks BlockHeight deserialization roundtrip

High Severity

block_to_trimmed_string strips leading zeros from BlockHeight::to_string()'s hex output (e.g., "00000064""64"), but the client-side deserialize_block_height_option calls BlockHeight::from_str on this trimmed value. Since BlockHeight is a fixed-width 4-byte hex type, from_str expects exactly 8 hex characters matching the Display format. Trimmed strings like "64" (wrong byte count) or "0" (odd-length hex) will fail to parse, breaking the response deserialization for current_fuel_block_height.

Additional Locations (1)
Fix in Cursor Fix in Web


#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading