FuelLabs/fuel-core · error · Error::Serialization
Could not convert witness_index to u16: {}
Error message
Could not convert witness_index to u16: {} What it means
The proto field witness_index is a wider integer (u32/i32) but the fuel-core UploadBody stores it as u16. u16::try_from fails when the value exceeds 65535 (or is negative when the proto field is int32), producing this Error::Serialization.
Source
Thrown at crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs:770
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
))
})?;
let subsections_number = u16::try_from(proto_upload.subsections_number)
.map_err(|e| {
Error::Serialization(anyhow!(
"Could not convert subsections_number to u16: {}",
e
))
})?;View on GitHub (pinned to b9d4d170da)
Solutions
- Fix the producer to cap witness_index at u16 (it indexes into the witnesses vector, so it must be < witnesses.len() anyway)
- Validate witness_index <= 65535 and < witnesses.len() before conversion and reject the transaction with a precise message
- If a sentinel like u32::MAX means 'unset' in your source data, map it explicitly during re-encoding instead of letting try_from fail
- Add proto-level contract tests asserting witness_index range for every upload transaction
Example fix
// before
let witness_index = u16::try_from(proto_upload.witness_index).map_err(...)?;
// after
let wi = proto_upload.witness_index;
if !(0..=u16::MAX as u32).contains(&wi) || wi as usize >= proto_upload.witnesses.len() {
return Err(anyhow!("witness_index {wi} out of range (witnesses: {})", proto_upload.witnesses.len()));
}
let witness_index = wi as u16; Defensive patterns
Strategy: validation
Validate before calling
fn upload_witness_index_ok(u: &ProtoUpload) -> bool {
u.witness_index <= u16::MAX as u32 && (u.witness_index as usize) < u.witnesses.len()
} Type guard
fn fits_u16(v: u32) -> bool { v <= u16::MAX as u32 } Try / catch
let witness_index = u16::try_from(u.witness_index).map_err(|_| {
anyhow!("witness_index {} out of range for tx {:?}", u.witness_index, tx_id)
})?; Prevention
- Range-check every proto integer against its target Rust type at the boundary
- Forbid sentinel values in proto contracts; use explicit optional semantics
- Index fields must also be validated against the length of the collection they reference
When it happens
Trigger: An Upload proto with witness_index > 65535 (e.g., set to u32::MAX as a sentinel by a buggy producer) or a negative i32 from mis-typed encoders. Note: an unset proto field defaults to 0 and passes, so this specifically means a large out-of-range value was encoded.
Common situations: Cross-language encoders (Go/TS) that treat witness_index as varint without range checks; sentinel values like 0xFFFFFFFF used to mean 'none'; corrupted payloads.
Related errors
- Could not convert subsection_index to u16: {}
- Could not convert subsections_number to u16: {}
- Could not convert upload root to Bytes32: {}
- Could not convert proof_set entry to Bytes32: {}
- Could not convert blob witness_index to u16: {}
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/f7f89b2b534b4b99.
Report an issue: GitHub.