{"record":{"id":"a3cc094f6f54aeca","repo":"FuelLabs/fuel-core","slug":"missing-protobuf-versioned-block","errorCode":null,"errorMessage":"Missing protobuf versioned_block","messagePattern":"Missing protobuf versioned_block","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":506,"sourceCode":"            Ok(FuelReceipt::burn(\n                sub_id,\n                contract_id,\n                burn.val,\n                burn.pc,\n                burn.is,\n            ))\n        }\n    }?;\n\n    Ok(receipt)\n}\n\npub fn fuel_block_from_protobuf(\n    proto_block: ProtoBlock,\n) -> crate::result::Result<(FuelBlock, Vec<Vec<FuelReceipt>>)> {\n    let versioned_block = proto_block\n        .versioned_block\n        .ok_or_else(|| anyhow::anyhow!(\"Missing protobuf versioned_block\"))\n        .map_err(Error::Serialization)?;\n    let (partial_header, event_inbox_root, txs, receipts) = match versioned_block {\n        ProtoVersionedBlock::V1(v1_inner) => {\n            let proto_header = v1_inner\n                .header\n                .ok_or_else(|| anyhow::anyhow!(\"Missing protobuf header\"))\n                .map_err(Error::Serialization)?;\n            let (partial_header, event_inbox_root) =\n                partial_header_from_proto_header(&proto_header)?;\n            let txs = v1_inner\n                .transactions\n                .iter()\n                .map(tx_from_proto_tx)\n                .collect::<crate::result::Result<_>>()?;\n            let receipts = v1_inner\n                .receipts\n                .iter()\n                .map(|rs| {","sourceCodeStart":488,"sourceCodeEnd":524,"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#L488-L524","documentation":"fuel_block_from_protobuf unwraps the Block.versioned_block oneof; prost decodes bytes that set no recognized variant into None, and the code maps that to Error::Serialization. This is the outermost structural check: it fires before any header/tx/receipt field is read.","triggerScenarios":"ProtoBlock::decode on empty or wrong-schema bytes succeeds (prost is lenient about unknown fields) but yields versioned_block == None; or the payload was encoded by a fuel-core version whose oneof tags differ from this build.","commonSituations":"Feeding truncated/garbage payloads into fuel_block_from_protobuf; cross-version block exchange where one side only knows V1 and the other emits a different variant; untrusted input at a network boundary.","solutions":["Check the payload before decode: non-empty, plausible length, and ideally a checksum or length prefix from the sender.","After ProtoBlock::decode, immediately assert versioned_block.is_some() and fail fast with payload metadata (len, first bytes) in the error.","Pin producer and consumer to the same fuel-core/protobuf schema version; keep the crate's serialize_block__roundtrip test green across upgrades.","Treat repeated occurrences as a protocol violation and drop or re-fetch from another peer."],"exampleFix":"// before\nlet (block, receipts) = fuel_block_from_protobuf(proto_block)?;\n\n// after: guard right after decode\nlet proto_block = ProtoBlock::decode(&*bytes)\n    .map_err(Error::serialization_error)?;\nif proto_block.versioned_block.is_none() {\n    return Err(Error::Serialization(anyhow::anyhow!(\n        \"payload of {} bytes has no versioned_block variant; wrong schema or corrupt data\",\n        bytes.len()\n    )));\n}","handlingStrategy":"type-guard","validationCode":"fn proto_block_has_versioned_payload(b: &ProtoBlock) -> bool {\n    b.versioned_block.is_some()\n}\n\n// right after decode\nlet proto_block = ProtoBlock::decode(&*bytes).map_err(Error::serialization_error)?;\nassert!(proto_block_has_versioned_payload(&proto_block), \"no versioned_block variant\");","typeGuard":"fn proto_block_decodable(b: &ProtoBlock) -> bool {\n    matches!(b.versioned_block, Some(ProtoVersionedBlock::V1(_)))\n}","tryCatchPattern":"match fuel_block_from_protobuf(proto_block) {\n    Ok((block, receipts)) => Ok((block, receipts)),\n    Err(Error::Serialization(ctx)) if ctx.to_string().contains(\"versioned_block\") => {\n        tracing::warn!(len = bytes.len(), \"payload has no versioned_block; wrong schema or corrupt\");\n        fetch_block_from_peer(height) // fallback source\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Never feed unvalidated bytes into fuel_block_from_protobuf; check non-empty and expected framing first.","Version-negotiate with peers and pin compatible fuel-core builds.","Keep the serialize_block__roundtrip test in CI across upgrades to catch schema drift early.","Treat this error at a network boundary as a protocol violation, not a data quirk."],"tags":["rust","fuel-core","protobuf","deserialization","missing-field","schema-mismatch"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}