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

Could not convert message digest to Bytes32: {}

Error message

Could not convert message digest to Bytes32: {}

What it means

Thrown while decoding a MessageOut receipt: the digest bytes field must be exactly 32 bytes for Bytes32; any other length fails try_from and is wrapped as Error::Serialization. The error fails the receipt, its transaction, and the block conversion.

Source

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

        ProtoReceiptVariant::ScriptResult(result) => {
            let script_result = result.result.as_ref().ok_or_else(|| {
                Error::Serialization(anyhow!("Missing script result payload"))
            })?;
            let execution_result = script_execution_result_from_proto(script_result)?;
            Ok(FuelReceipt::script_result(
                execution_result,
                result.gas_used,
            ))
        }
        ProtoReceiptVariant::MessageOut(msg) => {
            let sender = Address::try_from(msg.sender.as_slice())
                .map_err(|e| Error::Serialization(anyhow!(e)))?;
            let recipient = Address::try_from(msg.recipient.as_slice())
                .map_err(|e| Error::Serialization(anyhow!(e)))?;
            let nonce = Nonce::try_from(msg.nonce.as_slice())
                .map_err(|e| Error::Serialization(anyhow!(e)))?;
            let digest = Bytes32::try_from(msg.digest.as_slice()).map_err(|e| {
                Error::Serialization(anyhow!(
                    "Could not convert message digest to Bytes32: {}",
                    e
                ))
            })?;
            Ok(FuelReceipt::message_out_with_len(
                sender,
                recipient,
                msg.amount,
                nonce,
                msg.len,
                digest,
                msg.data.clone(),
            ))
        }
        ProtoReceiptVariant::Mint(mint) => {
            let sub_id = SubAssetId::try_from(mint.sub_id.as_slice())
                .map_err(|e| Error::Serialization(anyhow!(e)))?;
            let contract_id = ContractId::try_from(mint.contract_id.as_slice())

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Log the digest length of the failing MessageOut receipt.
  2. Pre-validate len == 32 for MessageOut digest before decoding.
  3. Align fuel-core versions on both ends.
  4. Reject and re-fetch the block.

Example fix

// before
let digest = Bytes32::try_from(msg.digest.as_slice()).map_err(|e| {
    Error::Serialization(anyhow!("Could not convert message digest to Bytes32: {}", e))
})?;

// after
if msg.digest.len() != 32 {
    return Err(Error::Serialization(anyhow::anyhow!(
        "MessageOut digest length {} != 32", msg.digest.len()
    )));
}
Defensive patterns

Strategy: validation

Validate before calling

fn message_out_digest_valid(msg: &ProtoMessageOut) -> bool {
    msg.digest.len() == 32
}

Type guard

fn is_bytes32(b: &[u8]) -> bool { b.len() == 32 }

Try / catch

match receipt_from_proto(r) {
    Ok(receipt) => out.push(receipt),
    Err(Error::Serialization(ctx)) if ctx.to_string().contains("message digest") => {
        tracing::warn!(len = msg.digest.len(), "rejecting MessageOut with bad digest");
        return Err(Error::Serialization(ctx));
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: fuel_block_from_protobuf on a block where a MessageOut receipt's digest is empty or not 32 bytes - corrupted blob, truncated frame, or variable-length encoding by the producer.

Common situations: Producer/consumer version mismatch; corruption in transit or storage; test fixtures with arbitrary digests.

Related errors


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