FuelLabs/fuel-core · error · Error::Serialization
Could not convert balance_root to Bytes32: {}
Error message
Could not convert balance_root to Bytes32: {} What it means
Thrown while decoding a proto contract input: the balance_root bytes field must convert to Bytes32, which requires exactly 32 bytes; any other length fails the try_from and is wrapped as Error::Serialization. The error aborts the contract input, its transaction, and the block decode.
Source
Thrown at crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs:114
})?;
let value = Bytes32::try_from(proto.value.as_slice()).map_err(|e| {
Error::Serialization(anyhow!(
"Could not convert storage slot value to Bytes32: {}",
e
))
})?;
Ok(StorageSlot::new(key, value))
}
fn contract_input_from_proto(
proto: &ProtoContractInput,
) -> crate::result::Result<fuel_core_types::fuel_tx::input::contract::Contract> {
let utxo_proto = proto.utxo_id.as_ref().ok_or_else(|| {
Error::Serialization(anyhow!("Missing utxo_id on contract input"))
})?;
let utxo_id = utxo_id_from_proto(utxo_proto)?;
let balance_root = Bytes32::try_from(proto.balance_root.as_slice()).map_err(|e| {
Error::Serialization(anyhow!("Could not convert balance_root to Bytes32: {}", e))
})?;
let state_root = Bytes32::try_from(proto.state_root.as_slice()).map_err(|e| {
Error::Serialization(anyhow!("Could not convert state_root to Bytes32: {}", e))
})?;
let tx_pointer_proto = proto.tx_pointer.as_ref().ok_or_else(|| {
Error::Serialization(anyhow!("Missing tx_pointer on contract input"))
})?;
let tx_pointer = tx_pointer_from_proto(tx_pointer_proto)?;
let contract_id =
fuel_core_types::fuel_types::ContractId::try_from(proto.contract_id.as_slice())
.map_err(|e| Error::Serialization(anyhow!(e)))?;
Ok(fuel_core_types::fuel_tx::input::contract::Contract {
utxo_id,
balance_root,
state_root,
tx_pointer,
contract_id,View on GitHub (pinned to b9d4d170da)
Solutions
- Log the balance_root length for the failing input to confirm the mismatch.
- Validate len == 32 for balance_root and state_root on every contract input before decoding.
- Pin both ends to the same fuel-core/protobuf schema version.
- Reject and re-fetch the block from a compatible peer.
Example fix
// before
let balance_root = Bytes32::try_from(proto.balance_root.as_slice()).map_err(|e| {
Error::Serialization(anyhow!("Could not convert balance_root to Bytes32: {}", e))
})?;
// after: caller pre-check
if input_proto.balance_root.len() != 32 {
return Err(Error::Serialization(anyhow::anyhow!(
"balance_root length {} != 32", input_proto.balance_root.len()
)));
} Defensive patterns
Strategy: validation
Validate before calling
fn contract_input_roots_valid(p: &ProtoContractInput) -> bool {
p.balance_root.len() == 32 && p.state_root.len() == 32
} Type guard
fn is_bytes32(b: &[u8]) -> bool { b.len() == 32 } Try / catch
match contract_input_from_proto(p) {
Ok(input) => inputs.push(input),
Err(Error::Serialization(ctx)) if ctx.to_string().contains("balance_root") => {
tracing::warn!(len = p.balance_root.len(), "rejecting input with bad balance_root");
return Err(Error::Serialization(ctx));
}
Err(e) => return Err(e),
} Prevention
- Validate 32-byte fields on every contract input before decoding a block.
- Keep producer and consumer fuel-core versions aligned so roots are always fixed-size.
- Never hand-construct these proto messages without fixed-size byte arrays.
When it happens
Trigger: fuel_block_from_protobuf on a block where a ProtoContractInput.balance_root has a length other than 32 - empty field, truncated data, or a producer that serializes unbounded bytes.
Common situations: Incompatible fuel-core versions on producer/consumer; corrupted transfer or storage; hand-built test messages with default empty Vec fields.
Related errors
- Could not convert state_root to Bytes32: {}
- Could not convert storage slot key to Bytes32: {}
- Could not convert storage slot value to Bytes32: {}
- Missing utxo_id on contract input
- Missing tx_pointer on contract input
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/63aa36a3f76b49c3.
Report an issue: GitHub.