{"record":{"id":"5c0a68526114db71","repo":"FuelLabs/fuel-core","slug":"missing-receipt-variant","errorCode":null,"errorMessage":"Missing receipt variant","messagePattern":"Missing receipt variant","errorType":"exception","errorClass":"Error::Serialization","httpStatus":500,"severity":"error","filePath":"crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs","lineNumber":305,"sourceCode":"    Ok(result)\n}\n\nfn panic_instruction_from_proto(proto: &ProtoPanicInstruction) -> PanicInstruction {\n    use crate::protobuf_types::PanicReason as ProtoPanicReason;\n\n    let reason_proto =\n        ProtoPanicReason::try_from(proto.reason).unwrap_or(ProtoPanicReason::Unknown);\n    let reason = PanicReason::from(reason_proto as u8);\n    PanicInstruction::error(reason, proto.instruction)\n}\n\nfn receipt_from_proto(\n    proto_receipt: &crate::protobuf_types::Receipt,\n) -> crate::result::Result<FuelReceipt> {\n    let variant = proto_receipt\n        .variant\n        .as_ref()\n        .ok_or_else(|| Error::Serialization(anyhow!(\"Missing receipt variant\")))?;\n\n    let receipt = match variant {\n        ProtoReceiptVariant::Call(call) => {\n            let id = ContractId::try_from(call.id.as_slice())\n                .map_err(|e| Error::Serialization(anyhow!(e)))?;\n            let to = ContractId::try_from(call.to.as_slice())\n                .map_err(|e| Error::Serialization(anyhow!(e)))?;\n            let asset_id = AssetId::try_from(call.asset_id.as_slice())\n                .map_err(|e| Error::Serialization(anyhow!(e)))?;\n            Ok(FuelReceipt::call(\n                id,\n                to,\n                call.amount,\n                asset_id,\n                call.gas,\n                call.param1,\n                call.param2,\n                call.pc,","sourceCodeStart":287,"sourceCodeEnd":323,"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#L287-L323","documentation":"receipt_from_proto matches on the Receipt.variant oneof; prost leaves a oneof as None when the Receipt message carries no variant at all, and that case maps to Error::Serialization('Missing receipt variant'). It fires while fuel_block_from_protobuf converts each transaction's receipt list.","triggerScenarios":"A proto block containing an empty Receipt message (no Call/Return/ReturnData/Panic/Log/... variant set) in any transaction's receipts - a faulty encoder emitting placeholder Receipt::default() entries, truncated data, or fuzzed input.","commonSituations":"Interop with producers that write empty oneof messages for unmappable receipts; version skew; fuzz/corpus testing at decode boundaries.","solutions":["Locate the offending entry (the error lacks position info - log tx index and receipt index while iterating) and inspect its variant field.","Fix the encoder to skip receipts it cannot map instead of writing an empty oneof.","Filter out variant-less receipts before decoding if your pipeline treats them as absent.","Reject the block and re-fetch from a compatible peer."],"exampleFix":"// before: decode all receipts blindly\nlet receipts: Vec<Vec<FuelReceipt>> = v1_inner.receipts.iter()\n    .map(|rs| rs.iter().map(receipt_from_proto).collect::<Result<Vec<_>>>())\n    .collect::<Result<_>>()?;\n\n// after: drop or reject empty variants explicitly before decoding\nlet receipts: Vec<Vec<FuelReceipt>> = v1_inner.receipts.iter()\n    .map(|rs| rs.iter().enumerate()\n        .filter(|(_, r)| r.variant.is_some())\n        .map(|(i, r)| receipt_from_proto(r).map_err(|e| e.context(format!(\"receipt[{i}]\"))))\n        .collect::<Result<Vec<_>>>())\n    .collect::<Result<_>>()?;","handlingStrategy":"type-guard","validationCode":"fn receipts_have_variants(receipts: &[crate::protobuf_types::Receipt]) -> bool {\n    receipts.iter().all(|r| r.variant.is_some())\n}","typeGuard":"fn receipt_has_variant(r: &crate::protobuf_types::Receipt) -> bool {\n    r.variant.is_some()\n}","tryCatchPattern":"match receipt_from_proto(r) {\n    Ok(receipt) => out.push(receipt),\n    Err(Error::Serialization(ctx)) if ctx.to_string().contains(\"variant\") => {\n        tracing::warn!(?r, \"skipping/rejecting receipt without variant\");\n        // policy: skip entry, or reject block\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Encoders must skip unmappable receipts instead of writing empty Receipt messages.","Filter or reject variant-less receipts right after ProtoBlock::decode.","Log tx/receipt indices during decode so failures are locatable in the blob."],"tags":["rust","fuel-core","protobuf","deserialization","receipts","missing-field"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}