FuelLabs/fuel-core · error · Error::Serialization
Could not convert return data digest to Bytes32: {}
Error message
Could not convert return data digest to Bytes32: {} What it means
Thrown while decoding a ReturnData receipt: the digest bytes field must be exactly 32 bytes to build fuel's Bytes32; any other length fails Bytes32::try_from and is wrapped as Error::Serialization, failing the receipt, its transaction, and the block.
Source
Thrown at crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs:336
call.amount,
asset_id,
call.gas,
call.param1,
call.param2,
call.pc,
call.is,
))
}
ProtoReceiptVariant::ReturnReceipt(ret) => {
let id = ContractId::try_from(ret.id.as_slice())
.map_err(|e| Error::Serialization(anyhow!(e)))?;
Ok(FuelReceipt::ret(id, ret.val, ret.pc, ret.is))
}
ProtoReceiptVariant::ReturnData(rd) => {
let id = ContractId::try_from(rd.id.as_slice())
.map_err(|e| Error::Serialization(anyhow!(e)))?;
let digest = Bytes32::try_from(rd.digest.as_slice()).map_err(|e| {
Error::Serialization(anyhow!(
"Could not convert return data digest to Bytes32: {}",
e
))
})?;
Ok(FuelReceipt::return_data_with_len(
id,
rd.ptr,
rd.len,
digest,
rd.pc,
rd.is,
rd.data.clone(),
))
}
ProtoReceiptVariant::Panic(panic_receipt) => {
let id = ContractId::try_from(panic_receipt.id.as_slice())
.map_err(|e| Error::Serialization(anyhow!(e)))?;
let reason_proto = panic_receiptView on GitHub (pinned to b9d4d170da)
Solutions
- Log the digest length of the failing ReturnData receipt.
- Pre-validate len == 32 for digest fields on ReturnData/LogData/MessageOut receipts before decoding.
- Align producer and consumer versions so digests are always 32 bytes.
- Re-fetch the block from a healthy source.
Example fix
// before
let digest = Bytes32::try_from(rd.digest.as_slice()).map_err(|e| {
Error::Serialization(anyhow!("Could not convert return data digest to Bytes32: {}", e))
})?;
// after
if rd.digest.len() != 32 {
return Err(Error::Serialization(anyhow::anyhow!(
"ReturnData digest length {} != 32", rd.digest.len()
)));
} Defensive patterns
Strategy: validation
Validate before calling
fn return_data_digest_valid(rd: &ProtoReturnData) -> bool {
rd.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("return data digest") => {
tracing::warn!(len = rd.digest.len(), "rejecting ReturnData with bad digest");
return Err(Error::Serialization(ctx));
}
Err(e) => return Err(e),
} Prevention
- Validate all digest fields (ReturnData, LogData, MessageOut) are 32 bytes before decoding.
- Pin producer/consumer versions so digests are always fixed-size.
- Corruption in digests usually means transport or storage damage - checksum payloads.
When it happens
Trigger: fuel_block_from_protobuf on a block where a ReturnData receipt's digest field is empty or not 32 bytes - corrupted blob, truncated frame, or an encoder that writes unbounded bytes for the digest.
Common situations: Version-incompatible producer; data corruption in transit/storage; test fixtures with arbitrary digest lengths.
Related errors
- Could not convert log data digest to Bytes32: {}
- Could not convert message digest to Bytes32: {}
- Could not convert storage slot key to Bytes32: {}
- Could not convert storage slot value to Bytes32: {}
- Could not convert balance_root to Bytes32: {}
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/e63af4fb7bcd56ad.
Report an issue: GitHub.