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

Could not convert proof_set entry to Bytes32: {}

Error message

Could not convert proof_set entry to Bytes32: {}

What it means

The upload proof_set is a list of 32-byte Merkle proofs. Each entry goes through Bytes32::try_from(entry.as_slice()); any entry whose length is not exactly 32 bytes fails and is wrapped with this message, failing the whole Upload conversion.

Source

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

                u16::try_from(proto_upload.subsection_index).map_err(|e| {
                    Error::Serialization(anyhow!(
                        "Could not convert subsection_index to u16: {}",
                        e
                    ))
                })?;
            let subsections_number = u16::try_from(proto_upload.subsections_number)
                .map_err(|e| {
                    Error::Serialization(anyhow!(
                        "Could not convert subsections_number to u16: {}",
                        e
                    ))
                })?;
            let proof_set = proto_upload
                .proof_set
                .iter()
                .map(|entry| {
                    Bytes32::try_from(entry.as_slice()).map_err(|e| {
                        Error::Serialization(anyhow!(
                            "Could not convert proof_set entry to Bytes32: {}",
                            e
                        ))
                    })
                })
                .collect::<crate::result::Result<Vec<_>>>()?;

            let body = UploadBody {
                root,
                witness_index,
                subsection_index,
                subsections_number,
                proof_set,
            };

            let upload_tx =
                FuelTransaction::upload(body, policies, inputs, outputs, witnesses);

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Pre-validate: all proof_set entries must be exactly 32 bytes; report which index failed
  2. Fix the producer's Merkle proof generation to emit fuel-core-sized (32-byte) proofs
  3. Recompute the proof set from the blob root and subsection data instead of trusting the wire value if the source is recoverable
  4. Add payload integrity checks (re-serialization round-trip) at ingestion

Example fix

// before
let proof_set = proto_upload.proof_set.iter().map(|e| Bytes32::try_from(e.as_slice()).map_err(...)).collect::<Result<Vec<_>>>()?;

// after
if let Some(i) = proto_upload.proof_set.iter().position(|e| e.len() != 32) {
    return Err(anyhow!("proof_set[{i}] has {} bytes, expected 32", proto_upload.proof_set[i].len()));
}
let proof_set = proto_upload.proof_set.iter().map(|e| Bytes32::try_from(e.as_slice()).unwrap()).collect::<Vec<_>>();
Defensive patterns

Strategy: validation

Validate before calling

fn proof_set_is_valid(u: &ProtoUpload) -> bool {
    !u.proof_set.is_empty() && u.proof_set.iter().all(|e| e.len() == 32)
}

Type guard

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

Try / catch

for (i, entry) in u.proof_set.iter().enumerate() {
    Bytes32::try_from(entry.as_slice())
        .with_context(|| format!("proof_set[{i}] len {}", entry.len()))?;
}

Prevention

When it happens

Trigger: An Upload proto where one or more proof_set entries are empty (producer pushed Vec::new()) or wrong-length (31/33 bytes) due to an encoder bug or payload corruption. Note the check runs per entry, so one bad proof invalidates the transaction even if the root is valid.

Common situations: Producer building proofs with a different hash width; empty proof vectors used as placeholders; truncation in transport/storage; test fixtures with dummy proofs.

Related errors


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