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

  1. Check root.len() == 32 before calling the conversion and reject/log the transaction id for follow-up
  2. Fix the producer so it always writes the 32-byte Merkle root of the uploaded bytecode
  3. Verify payload integrity (checksum/re-serialization round-trip) on the transport that delivered the proto
  4. 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

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


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/4388900806690c84. Report an issue: GitHub.