{"record":{"id":"03cf3bc4c7fb0665","repo":"FuelLabs/fuel-core","slug":"missing-transaction-variant","errorCode":null,"errorMessage":"Missing transaction variant","messagePattern":"Missing transaction variant","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":576,"sourceCode":"\npub fn partial_header_from_proto_header(\n    proto_header: &ProtoHeader,\n) -> crate::result::Result<(PartialBlockHeader, Bytes32)> {\n    let partial_header = PartialBlockHeader {\n        consensus: proto_header_to_empty_consensus_header(proto_header)?,\n        application: proto_header_to_empty_application_header(proto_header)?,\n    };\n    let event_inbox_root = proto_header_to_event_inbox_root(proto_header)?;\n    Ok((partial_header, event_inbox_root))\n}\n\npub fn tx_from_proto_tx(\n    proto_tx: &ProtoTransaction,\n) -> crate::result::Result<FuelTransaction> {\n    let variant = proto_tx\n        .variant\n        .as_ref()\n        .ok_or_else(|| Error::Serialization(anyhow!(\"Missing transaction variant\")))?;\n\n    match variant {\n        ProtoTransactionVariant::Script(proto_script) => {\n            let policies = proto_script\n                .policies\n                .clone()\n                .map(|p| policies_from_proto_policies(&p))\n                .unwrap_or_default();\n            let inputs = proto_script\n                .inputs\n                .iter()\n                .map(input_from_proto_input)\n                .collect::<crate::result::Result<Vec<_>>>()?;\n            let outputs = proto_script\n                .outputs\n                .iter()\n                .map(output_from_proto_output)\n                .collect::<crate::result::Result<Vec<_>>>()?;","sourceCodeStart":558,"sourceCodeEnd":594,"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#L558-L594","documentation":"tx_from_proto_tx matches on the Transaction.variant oneof; prost leaves the oneof as None when the proto Transaction message has no variant set, and the code maps that to Error::Serialization. A variant-less transaction cannot map to any FuelTransaction variant, so the block decode fails.","triggerScenarios":"Decoding a proto block containing an empty Transaction message (no Script/Create/Mint/... variant) - placeholder entries from a faulty encoder, truncated data, or fuzzed input.","commonSituations":"Cross-version block exchange with schema drift; producers emitting default transactions for unmappable types; fuzz/corpus testing.","solutions":["Log the index of the failing transaction and inspect its variant field.","Fix the encoder to never emit variant-less transactions (skip unknown tx types instead).","Pre-validate every transaction has a variant before calling fuel_block_from_protobuf.","Reject the block and re-fetch from a compatible peer."],"exampleFix":"// before\nlet txs = v1_inner.transactions.iter().map(tx_from_proto_tx)\n    .collect::<Result<Vec<_>>>()?;\n\n// after: validate variant presence with position context\nfor (i, tx) in v1_inner.transactions.iter().enumerate() {\n    if tx.variant.is_none() {\n        return Err(Error::Serialization(anyhow::anyhow!(\n            \"transaction[{i}] has no variant\"\n        )));\n    }\n}\nlet txs = v1_inner.transactions.iter().map(tx_from_proto_tx)\n    .collect::<Result<Vec<_>>>()?;","handlingStrategy":"type-guard","validationCode":"fn all_txs_have_variants(txs: &[ProtoTransaction]) -> bool {\n    txs.iter().all(|t| t.variant.is_some())\n}","typeGuard":"fn tx_has_variant(t: &ProtoTransaction) -> bool {\n    t.variant.is_some()\n}","tryCatchPattern":"match tx_from_proto_tx(t) {\n    Ok(tx) => txs.push(tx),\n    Err(Error::Serialization(ctx)) if ctx.to_string().contains(\"transaction variant\") => {\n        tracing::warn!(?t, \"rejecting transaction without variant\");\n        return Err(Error::Serialization(ctx));\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Encoders must never emit variant-less transactions; skip unknown tx types instead.","Validate every transaction has a variant right after ProtoBlock::decode.","Log transaction indices during decode for fast location of bad entries."],"tags":["rust","fuel-core","protobuf","deserialization","transactions","missing-field"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}