FuelLabs/fuel-core · error · Error::Serialization
Could not convert upload root to Bytes32: {}
Error message
Could not convert upload root to Bytes32: {} What it means
Upload transactions carry a Merkle root as a fixed 32-byte Bytes32. The convertor does Bytes32::try_from(proto_upload.root.as_slice()); if the bytes field is any length other than 32, try_from fails and the error is wrapped with this message. It aborts construction of the UploadBody.
Source
Thrown at crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs:763
.map(|p| policies_from_proto_policies(&p))
.unwrap_or_default();
let inputs = proto_upload
.inputs
.iter()
.map(input_from_proto_input)
.collect::<crate::result::Result<Vec<_>>>()?;
let outputs = proto_upload
.outputs
.iter()
.map(output_from_proto_output)
.collect::<crate::result::Result<Vec<_>>>()?;
let witnesses = proto_upload
.witnesses
.iter()
.map(|w| Ok(Witness::from(w.clone())))
.collect::<crate::result::Result<Vec<_>>>()?;
let root = Bytes32::try_from(proto_upload.root.as_slice()).map_err(|e| {
Error::Serialization(anyhow!(
"Could not convert upload root to Bytes32: {}",
e
))
})?;
let witness_index =
u16::try_from(proto_upload.witness_index).map_err(|e| {
Error::Serialization(anyhow!(
"Could not convert witness_index to u16: {}",
e
))
})?;
let subsection_index =
u16::try_from(proto_upload.subsection_index).map_err(|e| {
Error::Serialization(anyhow!(
"Could not convert subsection_index to u16: {}",
e
))
})?;View on GitHub (pinned to b9d4d170da)
Solutions
- Check root.len() == 32 before calling the conversion and reject/log the transaction id for follow-up
- Fix the producer so it always writes the 32-byte Merkle root of the uploaded bytecode
- Verify payload integrity (checksum/re-serialization round-trip) on the transport that delivered the proto
- If reading legacy data, re-encode with the correct root rather than padding
Example fix
// before
let root = Bytes32::try_from(proto_upload.root.as_slice()).map_err(...)?;
// after
fn is_bytes32(b: &[u8]) -> bool { b.len() == 32 }
if !is_bytes32(&proto_upload.root) {
return Err(anyhow!("upload root has {} bytes, expected 32", proto_upload.root.len()));
}
let root = Bytes32::try_from(proto_upload.root.as_slice()).map_err(...)?; Defensive patterns
Strategy: validation
Validate before calling
fn upload_root_is_valid(u: &ProtoUpload) -> bool { u.root.len() == 32 } Type guard
fn is_bytes32(b: &[u8]) -> bool { b.len() == 32 } Try / catch
match Bytes32::try_from(u.root.as_slice()) {
Ok(root) => Ok(root),
Err(e) => Err(anyhow!("upload root invalid (len {}): {}", u.root.len(), e)),
} Prevention
- Assert fixed-width (32-byte) fields before any conversion call
- Compute the root from the uploaded bytecode and compare with the wire value
- Checksum payloads at the transport layer to catch truncation
When it happens
Trigger: An Upload proto whose root field is empty (0 bytes, producer never set it — try_from on empty slice fails) or a wrong-size value (31/33 bytes) from a buggy encoder or corrupted payload.
Common situations: Producer omitted root (prost bytes default to empty); truncation/corruption of proto payloads in transport or storage; encoder from another chain version using different root length.
Related errors
- Could not convert proof_set entry to Bytes32: {}
- Could not convert witness_index to u16: {}
- Could not convert subsection_index to u16: {}
- Could not convert subsections_number to u16: {}
- Could not convert blob witness_index to u16: {}
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/4388900806690c84.
Report an issue: GitHub.