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
- 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.
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
- 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.
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
- Missing panic reason
- Missing script result payload
- Missing utxo_id on contract input
- Missing tx_pointer on contract input
- Could not convert return data digest to Bytes32: {}
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/5c0a68526114db71.
Report an issue: GitHub.