{"record":{"id":"f7f89b2b534b4b99","repo":"FuelLabs/fuel-core","slug":"could-not-convert-witness-index-to-u16","errorCode":null,"errorMessage":"Could not convert witness_index to u16: {}","messagePattern":"Could not convert witness_index to u16: (.+?)","errorType":"exception","errorClass":"Error::Serialization","httpStatus":null,"severity":"error","filePath":"crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs","lineNumber":770,"sourceCode":"            let outputs = proto_upload\n                .outputs\n                .iter()\n                .map(output_from_proto_output)\n                .collect::<crate::result::Result<Vec<_>>>()?;\n            let witnesses = proto_upload\n                .witnesses\n                .iter()\n                .map(|w| Ok(Witness::from(w.clone())))\n                .collect::<crate::result::Result<Vec<_>>>()?;\n            let root = Bytes32::try_from(proto_upload.root.as_slice()).map_err(|e| {\n                Error::Serialization(anyhow!(\n                    \"Could not convert upload root to Bytes32: {}\",\n                    e\n                ))\n            })?;\n            let witness_index =\n                u16::try_from(proto_upload.witness_index).map_err(|e| {\n                    Error::Serialization(anyhow!(\n                        \"Could not convert witness_index to u16: {}\",\n                        e\n                    ))\n                })?;\n            let subsection_index =\n                u16::try_from(proto_upload.subsection_index).map_err(|e| {\n                    Error::Serialization(anyhow!(\n                        \"Could not convert subsection_index to u16: {}\",\n                        e\n                    ))\n                })?;\n            let subsections_number = u16::try_from(proto_upload.subsections_number)\n                .map_err(|e| {\n                    Error::Serialization(anyhow!(\n                        \"Could not convert subsections_number to u16: {}\",\n                        e\n                    ))\n                })?;","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs#L752-L788","documentation":"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.","triggerScenarios":"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.","commonSituations":"Cross-language encoders (Go/TS) that treat witness_index as varint without range checks; sentinel values like 0xFFFFFFFF used to mean 'none'; corrupted payloads.","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"],"exampleFix":"// before\nlet witness_index = u16::try_from(proto_upload.witness_index).map_err(...)?;\n\n// after\nlet wi = proto_upload.witness_index;\nif !(0..=u16::MAX as u32).contains(&wi) || wi as usize >= proto_upload.witnesses.len() {\n    return Err(anyhow!(\"witness_index {wi} out of range (witnesses: {})\", proto_upload.witnesses.len()));\n}\nlet witness_index = wi as u16;","handlingStrategy":"validation","validationCode":"fn upload_witness_index_ok(u: &ProtoUpload) -> bool {\n    u.witness_index <= u16::MAX as u32 && (u.witness_index as usize) < u.witnesses.len()\n}","typeGuard":"fn fits_u16(v: u32) -> bool { v <= u16::MAX as u32 }","tryCatchPattern":"let witness_index = u16::try_from(u.witness_index).map_err(|_| {\n    anyhow!(\"witness_index {} out of range for tx {:?}\", u.witness_index, tx_id)\n})?;","preventionTips":["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"],"tags":["rust","fuel","protobuf","serialization","upload-transaction","integer-overflow","validation"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}