{"record":{"id":"44a86149214fa7aa","repo":"FuelLabs/fuel-core","slug":"could-not-convert-tx-index-to-target-type","errorCode":null,"errorMessage":"Could not convert tx_index to target type: {}","messagePattern":"Could not convert tx_index to target type: (.+?)","errorType":"validation","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":83,"sourceCode":"            Policies as FuelPolicies,\n            PoliciesBits,\n            PolicyType,\n        },\n    },\n    fuel_types::{\n        AssetId,\n        ContractId,\n        Nonce,\n        SubAssetId,\n    },\n    tai64,\n};\n\nfn tx_pointer_from_proto(proto: &ProtoTxPointer) -> crate::result::Result<TxPointer> {\n    let block_height = proto.block_height.into();\n    #[allow(clippy::useless_conversion)]\n    let tx_index = proto.tx_index.try_into().map_err(|e| {\n        Error::Serialization(anyhow!(\"Could not convert tx_index to target type: {}\", e))\n    })?;\n    Ok(TxPointer::new(block_height, tx_index))\n}\n\nfn storage_slot_from_proto(\n    proto: &ProtoStorageSlot,\n) -> crate::result::Result<StorageSlot> {\n    let key = Bytes32::try_from(proto.key.as_slice()).map_err(|e| {\n        Error::Serialization(anyhow!(\n            \"Could not convert storage slot key to Bytes32: {}\",\n            e\n        ))\n    })?;\n    let value = Bytes32::try_from(proto.value.as_slice()).map_err(|e| {\n        Error::Serialization(anyhow!(\n            \"Could not convert storage slot value to Bytes32: {}\",\n            e\n        ))","sourceCodeStart":65,"sourceCodeEnd":101,"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#L65-L101","documentation":"Raised in tx_pointer_from_proto when converting a protobuf TxPointer into fuel_tx::TxPointer: the proto tx_index integer (a wide uint) is try_into-converted to the u16 transaction index that TxPointer::new requires, and the conversion fails when the value does not fit u16 (max 65535). It is wrapped as Error::Serialization and propagates out of fuel_block_from_protobuf / tx_from_proto_tx whenever a TxPointer is decoded (contract inputs, mint transactions).","triggerScenarios":"Decoding a proto block whose ProtoTxPointer.tx_index exceeds 65535 - e.g. produced by an encoder that stores tx_index as u32/u64 without fuel-core's u16 bound, a hand-crafted message, or a fuzz corpus with unconstrained integers.","commonSituations":"Version skew between the node that encoded the proto block and the node decoding it; third-party tools emitting the protobuf format without fuel-core type bounds; test fixtures generated by property testing that do not clamp tx_index.","solutions":["Log the raw proto value (proto.tx_index) for the failing TxPointer to confirm it exceeds u16::MAX.","Pin producer and consumer to matching fuel-core versions so tx_index is always serialized within the u16 range fuel_tx::TxPointer uses.","If you own the producer, bound or regenerate the field to 0..=65535 before re-encoding.","Reject the whole proto block and re-fetch it from a compatible peer rather than partially decoding it."],"exampleFix":"// before: blind conversion\nlet tx_pointer = tx_pointer_from_proto(tx_pointer_proto)?;\n\n// after: validate range at the boundary, reject with context\nlet tx_index: u16 = tx_pointer_proto.tx_index.try_into().map_err(|_| {\n    Error::Serialization(anyhow::anyhow!(\n        \"tx_index {} out of range for TxPointer (max 65535)\",\n        tx_pointer_proto.tx_index\n    ))\n})?;\nlet tx_pointer = TxPointer::new(tx_pointer_proto.block_height.into(), tx_index);","handlingStrategy":"validation","validationCode":"fn tx_pointer_valid(p: &ProtoTxPointer) -> bool {\n    p.tx_index <= u16::MAX as u32 // adjust to the proto integer width in your generated types\n}\n\n// gate the decode\nif !block_tx_pointers(&proto_block).iter().all(|p| tx_pointer_valid(p)) {\n    return Err(Error::Serialization(anyhow::anyhow!(\"tx_index out of u16 range\")));\n}","typeGuard":"fn tx_pointer_in_range(p: &ProtoTxPointer) -> bool {\n    (p.tx_index as u64) <= u16::MAX as u64\n}","tryCatchPattern":"match tx_pointer_from_proto(p) {\n    Ok(ptr) => Some(ptr),\n    Err(Error::Serialization(ctx)) if ctx.to_string().contains(\"tx_index\") => {\n        tracing::warn!(index = p.tx_index, \"rejecting TxPointer out of range\");\n        None // drop the field or the whole tx per protocol\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Only accept proto blocks from peers running a compatible fuel-core version (negotiate version in the handshake).","Generate test data with the same bounds as fuel-core types (u16 for tx_index).","Keep the roundtrip test (serialize_block__roundtrip) in CI to catch bound drift between proto and fuel types."],"tags":["rust","fuel-core","protobuf","deserialization","tx-pointer","integer-overflow"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}