FuelLabs/fuel-core · error · Error::Serialization
Could not convert receipts_root to Bytes32: {}
Error message
Could not convert receipts_root to Bytes32: {} What it means
While decoding a Script transaction, the receipts_root bytes field must convert to Bytes32, which requires exactly 32 bytes; any other length fails try_from and is wrapped as Error::Serialization, failing the transaction and block. Note the setter path: the decoded Script tx is built first and receipts_root_mut() is overwritten from the proto field.
Source
Thrown at crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs:613
let witnesses = proto_script
.witnesses
.iter()
.map(|w| Ok(Witness::from(w.clone())))
.collect::<crate::result::Result<Vec<_>>>()?;
let mut script_tx = FuelTransaction::script(
proto_script.script_gas_limit,
proto_script.script.clone(),
proto_script.script_data.clone(),
policies,
inputs,
outputs,
witnesses,
);
*script_tx.receipts_root_mut() = Bytes32::try_from(
proto_script.receipts_root.as_slice(),
)
.map_err(|e| {
Error::Serialization(anyhow!(
"Could not convert receipts_root to Bytes32: {}",
e
))
})?;
Ok(FuelTransaction::Script(script_tx))
}
ProtoTransactionVariant::Create(proto_create) => {
let policies = proto_create
.policies
.clone()
.map(|p| policies_from_proto_policies(&p))
.unwrap_or_default();
let inputs = proto_create
.inputs
.iter()
.map(input_from_proto_input)
.collect::<crate::result::Result<Vec<_>>>()?;View on GitHub (pinned to b9d4d170da)
Solutions
- Log the receipts_root length of the failing Script tx.
- Pre-validate len == 32 before decoding the block.
- Pin matching fuel-core versions on both ends so receipts_root is always 32 bytes.
- Re-fetch the block from a known-good peer.
Example fix
// before
*script_tx.receipts_root_mut() =
Bytes32::try_from(proto_script.receipts_root.as_slice()).map_err(|e| {
Error::Serialization(anyhow!("Could not convert receipts_root to Bytes32: {}", e))
})?;
// after: caller-side pre-check
if proto_script.receipts_root.len() != 32 {
return Err(Error::Serialization(anyhow::anyhow!(
"receipts_root length {} != 32", proto_script.receipts_root.len()
)));
} Defensive patterns
Strategy: validation
Validate before calling
fn script_receipts_root_valid(script: &ProtoScript) -> bool {
script.receipts_root.len() == 32
} Type guard
fn is_bytes32(b: &[u8]) -> bool { b.len() == 32 } Try / catch
match tx_from_proto_tx(t) {
Ok(tx) => txs.push(tx),
Err(Error::Serialization(ctx)) if ctx.to_string().contains("receipts_root") => {
tracing::warn!(len = script.receipts_root.len(), "rejecting Script tx with bad receipts_root");
return Err(Error::Serialization(ctx));
}
Err(e) => return Err(e),
} Prevention
- Validate receipts_root length (32) on Script transactions before block decode.
- Pin compatible fuel-core versions so roots are always fixed-size.
- Re-encode from canonical on-chain data instead of repairing proto bytes.
When it happens
Trigger: fuel_block_from_protobuf on a block where a Script transaction's receipts_root is empty or not 32 bytes - corruption, truncation, or a producer that does not fix the field size.
Common situations: Producer/consumer version mismatch; corrupted storage or transfer; test fixtures with default empty byte fields.
Related errors
- Could not convert storage slot key to Bytes32: {}
- Could not convert storage slot value to Bytes32: {}
- Could not convert balance_root to Bytes32: {}
- Could not convert state_root to Bytes32: {}
- Could not convert return data digest to Bytes32: {}
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/fc7bc82955e2c0fb.
Report an issue: GitHub.