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

Missing protobuf header

Error message

Missing protobuf header

What it means

While decoding a V1 proto block, fuel_block_from_protobuf requires the header sub-message on the V1Block; prost yields None when absent and the code maps it to Error::Serialization. A V1 block without a header cannot produce a FuelBlockHeader, so the decode aborts.

Source

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

            ))
        }
    }?;

    Ok(receipt)
}

pub fn fuel_block_from_protobuf(
    proto_block: ProtoBlock,
) -> crate::result::Result<(FuelBlock, Vec<Vec<FuelReceipt>>)> {
    let versioned_block = proto_block
        .versioned_block
        .ok_or_else(|| anyhow::anyhow!("Missing protobuf versioned_block"))
        .map_err(Error::Serialization)?;
    let (partial_header, event_inbox_root, txs, receipts) = match versioned_block {
        ProtoVersionedBlock::V1(v1_inner) => {
            let proto_header = v1_inner
                .header
                .ok_or_else(|| anyhow::anyhow!("Missing protobuf header"))
                .map_err(Error::Serialization)?;
            let (partial_header, event_inbox_root) =
                partial_header_from_proto_header(&proto_header)?;
            let txs = v1_inner
                .transactions
                .iter()
                .map(tx_from_proto_tx)
                .collect::<crate::result::Result<_>>()?;
            let receipts = v1_inner
                .receipts
                .iter()
                .map(|rs| {
                    rs.receipts
                        .iter()
                        .map(receipt_from_proto)
                        .collect::<crate::result::Result<Vec<_>>>()
                })
                .collect::<crate::result::Result<Vec<_>>>()?;

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Debug-print the V1Block to confirm header is the only missing piece.
  2. Fix the producer to always set header (see ProtobufBlockConverter::convert_block, which always attaches Some(proto_header)).
  3. Pre-validate v1.header.is_some() after decode and reject with block-level context.
  4. Re-fetch the block from a compatible peer.

Example fix

// before
let (block, receipts) = fuel_block_from_protobuf(proto_block)?;

// after: structural pre-check on the versioned payload
if let Some(ProtoVersionedBlock::V1(v1)) = proto_block.versioned_block.as_ref() {
    if v1.header.is_none() {
        return Err(Error::Serialization(anyhow::anyhow!(
            "V1 block payload missing header"
        )));
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

fn v1_block_has_header(v1: &ProtoV1Block) -> bool {
    v1.header.is_some()
}

Type guard

fn v1_block_decodable(v1: &ProtoV1Block) -> bool {
    v1.header.is_some()
}

Try / catch

match fuel_block_from_protobuf(proto_block) {
    Ok((block, receipts)) => Ok((block, receipts)),
    Err(Error::Serialization(ctx)) if ctx.to_string().contains("header") => {
        tracing::warn!("V1 payload missing header; re-fetching");
        fetch_block_from_peer(height)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: fuel_block_from_protobuf on a ProtoBlock whose V1 payload omitted header - encoder bug, truncated message, or a schema-skewed producer.

Common situations: Version mismatch between fuel-core releases; hand-built test blocks that fill only transactions/receipts; corruption.

Related errors


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