FuelLabs/fuel-core · error · Error::Serialization
Missing script result payload
Error message
Missing script result payload
What it means
receipt_from_proto requires the ScriptResult receipt's result sub-message, which carries the ScriptExecutionResult; prost yields None when it is absent, and the code maps that to Error::Serialization. Without it there is no way to build FuelReceipt::script_result, so the block decode fails.
Source
Thrown at crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs:439
ProtoReceiptVariant::TransferOut(transfer) => {
let id = ContractId::try_from(transfer.id.as_slice())
.map_err(|e| Error::Serialization(anyhow!(e)))?;
let to = Address::try_from(transfer.to.as_slice())
.map_err(|e| Error::Serialization(anyhow!(e)))?;
let asset_id = AssetId::try_from(transfer.asset_id.as_slice())
.map_err(|e| Error::Serialization(anyhow!(e)))?;
Ok(FuelReceipt::transfer_out(
id,
to,
transfer.amount,
asset_id,
transfer.pc,
transfer.is,
))
}
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: {}",
eView on GitHub (pinned to b9d4d170da)
Solutions
- Debug-print the failing ScriptResult receipt to confirm result is None.
- Fix the encoder to always embed the ScriptExecutionResult sub-message.
- Pre-validate presence of result on ScriptResult receipts before decoding.
- Re-fetch the block from a compatible peer.
Example fix
// before
let script_result = result.result.as_ref().ok_or_else(|| {
Error::Serialization(anyhow!("Missing script result payload"))
})?;
// after: pre-validate with position context
if let ProtoReceiptVariant::ScriptResult(sr) = variant {
if sr.result.is_none() {
return Err(Error::Serialization(anyhow::anyhow!(
"receipt[{i}] ScriptResult missing result payload"
)));
}
} Defensive patterns
Strategy: type-guard
Validate before calling
fn script_result_payload_present(sr: &ProtoScriptResult) -> bool {
sr.result.is_some()
} Type guard
fn script_result_complete(sr: &ProtoScriptResult) -> bool {
sr.result.is_some()
} Try / catch
match receipt_from_proto(r) {
Ok(receipt) => out.push(receipt),
Err(Error::Serialization(ctx)) if ctx.to_string().contains("script result") => {
tracing::warn!(?r, "rejecting ScriptResult without payload");
return Err(Error::Serialization(ctx));
}
Err(e) => return Err(e),
} Prevention
- Encoders must always embed the ScriptExecutionResult sub-message in ScriptResult receipts.
- Pre-validate nested presence for oneof receipt variants after decode.
- Reject schema-drifted peers instead of lenient decoding.
When it happens
Trigger: Decoding a proto block where a ScriptResult receipt omits the nested result message - encoder skips it, data truncated, or a fixture that sets only gas_used.
Common situations: Version skew across schema changes; producers that write partial receipts; fuzzing.
Related errors
- Missing receipt variant
- Missing panic reason
- 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/3a09bf5c9b9bf6f1.
Report an issue: GitHub.