{"record":{"id":"9e96c95633aac416","repo":"FuelLabs/fuel-core","slug":"missing-purpose-on-upgrade-transaction","errorCode":null,"errorMessage":"Missing purpose on upgrade transaction","messagePattern":"Missing purpose on upgrade transaction","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":707,"sourceCode":"            let mint_asset_id = fuel_core_types::fuel_types::AssetId::try_from(\n                proto_mint.mint_asset_id.as_slice(),\n            )\n            .map_err(|e| Error::Serialization(anyhow!(e)))?;\n\n            let mint_tx = FuelTransaction::mint(\n                tx_pointer,\n                input_contract,\n                output_contract,\n                proto_mint.mint_amount,\n                mint_asset_id,\n                proto_mint.gas_price,\n            );\n\n            Ok(FuelTransaction::Mint(mint_tx))\n        }\n        ProtoTransactionVariant::Upgrade(proto_upgrade) => {\n            let purpose_proto = proto_upgrade.purpose.as_ref().ok_or_else(|| {\n                Error::Serialization(anyhow!(\"Missing purpose on upgrade transaction\"))\n            })?;\n            let upgrade_purpose = upgrade_purpose_from_proto(purpose_proto)?;\n            let policies = proto_upgrade\n                .policies\n                .clone()\n                .map(|p| policies_from_proto_policies(&p))\n                .unwrap_or_default();\n            let inputs = proto_upgrade\n                .inputs\n                .iter()\n                .map(input_from_proto_input)\n                .collect::<crate::result::Result<Vec<_>>>()?;\n            let outputs = proto_upgrade\n                .outputs\n                .iter()\n                .map(output_from_proto_output)\n                .collect::<crate::result::Result<Vec<_>>>()?;\n            let witnesses = proto_upgrade","sourceCodeStart":689,"sourceCodeEnd":725,"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#L689-L725","documentation":"Upgrade transactions carry a purpose (consensus parameters change, state transition upgrade, etc.) modeled as a nested proto message. If ProtoTransactionVariant::Upgrade arrives with purpose unset, upgrade_purpose_from_proto cannot run and the convertor raises Error::Serialization immediately.","triggerScenarios":"Decoding an Upgrade transaction whose proto encoding predates the purpose field or omitted it. Also triggered by test/hand-built upgrade protos that only set policies/inputs/outputs/witnesses.","commonSituations":"Schema drift after a fuel-core upgrade that added or reshaped UpgradePurpose variants; blocks relayed from a peer running different fuel-core version; stale cached proto blocks.","solutions":["Ensure the encoder serializes upgrade_purpose for every Upgrade transaction and re-send the block","Pin matching fuel-core / proto versions on both ends of the block source","Ingest with a compatibility layer that maps legacy upgrade encodings to an explicit purpose before conversion","Pre-validate: reject Upgrade protos with purpose None and log the offending transaction id for re-encoding"],"exampleFix":"// before\nlet up = ProtoUpgrade { policies: Some(p), ..Default::default() }; // purpose: None\n\n// after\nlet up = ProtoUpgrade {\n    purpose: Some(ProtoUpgradePurpose { variant: Some(purpose_variant) }),\n    policies: Some(p),\n    ..Default::default()\n};","handlingStrategy":"validation","validationCode":"fn upgrade_tx_is_complete(u: &ProtoUpgrade) -> bool { u.purpose.is_some() }","typeGuard":"fn has_upgrade_purpose(u: &ProtoUpgrade) -> bool { u.purpose.as_ref().map(|p| p.variant.is_some()).unwrap_or(false) }","tryCatchPattern":"match convert_transaction(&proto_tx) {\n    Err(e) if e.to_string().contains(\"Missing purpose on upgrade\") => {\n        log::warn!(\"dropping malformed upgrade tx {:?}\", tx_id(&proto_tx)); Ok(None)\n    }\n    other => other.map(Some),\n}","preventionTips":["Upgrade producers and consumers together when UpgradePurpose variants change","Reject upgrade protos without purpose in producer-side validation before broadcast","Keep fixture generators in sync with the latest proto schema"],"tags":["rust","fuel","protobuf","serialization","upgrade-transaction","conversion"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}