FuelLabs/fuel-core · error · Error::Serialization

Missing receipt variant

Error message

Missing receipt variant

What it means

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.

Source

Thrown at crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs:305

    Ok(result)
}

fn panic_instruction_from_proto(proto: &ProtoPanicInstruction) -> PanicInstruction {
    use crate::protobuf_types::PanicReason as ProtoPanicReason;

    let reason_proto =
        ProtoPanicReason::try_from(proto.reason).unwrap_or(ProtoPanicReason::Unknown);
    let reason = PanicReason::from(reason_proto as u8);
    PanicInstruction::error(reason, proto.instruction)
}

fn receipt_from_proto(
    proto_receipt: &crate::protobuf_types::Receipt,
) -> crate::result::Result<FuelReceipt> {
    let variant = proto_receipt
        .variant
        .as_ref()
        .ok_or_else(|| Error::Serialization(anyhow!("Missing receipt variant")))?;

    let receipt = match variant {
        ProtoReceiptVariant::Call(call) => {
            let id = ContractId::try_from(call.id.as_slice())
                .map_err(|e| Error::Serialization(anyhow!(e)))?;
            let to = ContractId::try_from(call.to.as_slice())
                .map_err(|e| Error::Serialization(anyhow!(e)))?;
            let asset_id = AssetId::try_from(call.asset_id.as_slice())
                .map_err(|e| Error::Serialization(anyhow!(e)))?;
            Ok(FuelReceipt::call(
                id,
                to,
                call.amount,
                asset_id,
                call.gas,
                call.param1,
                call.param2,
                call.pc,

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Locate the offending entry (the error lacks position info - log tx index and receipt index while iterating) and inspect its variant field.
  2. Fix the encoder to skip receipts it cannot map instead of writing an empty oneof.
  3. Filter out variant-less receipts before decoding if your pipeline treats them as absent.
  4. Reject the block and re-fetch from a compatible peer.

Example fix

// before: decode all receipts blindly
let receipts: Vec<Vec<FuelReceipt>> = v1_inner.receipts.iter()
    .map(|rs| rs.iter().map(receipt_from_proto).collect::<Result<Vec<_>>>())
    .collect::<Result<_>>()?;

// after: drop or reject empty variants explicitly before decoding
let receipts: Vec<Vec<FuelReceipt>> = v1_inner.receipts.iter()
    .map(|rs| rs.iter().enumerate()
        .filter(|(_, r)| r.variant.is_some())
        .map(|(i, r)| receipt_from_proto(r).map_err(|e| e.context(format!("receipt[{i}]"))))
        .collect::<Result<Vec<_>>>())
    .collect::<Result<_>>()?;
Defensive patterns

Strategy: type-guard

Validate before calling

fn receipts_have_variants(receipts: &[crate::protobuf_types::Receipt]) -> bool {
    receipts.iter().all(|r| r.variant.is_some())
}

Type guard

fn receipt_has_variant(r: &crate::protobuf_types::Receipt) -> bool {
    r.variant.is_some()
}

Try / catch

match receipt_from_proto(r) {
    Ok(receipt) => out.push(receipt),
    Err(Error::Serialization(ctx)) if ctx.to_string().contains("variant") => {
        tracing::warn!(?r, "skipping/rejecting receipt without variant");
        // policy: skip entry, or reject block
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: Interop with producers that write empty oneof messages for unmappable receipts; version skew; fuzz/corpus testing at decode boundaries.

Related errors


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/5c0a68526114db71. Report an issue: GitHub.