FuelLabs/fuel-core · error · StorageError

The count of leaves - messages is zero

Error message

The count of leaves - messages is zero

What it means

block_history_proof computes the leaf index as message_merkle_metadata.version() - 1 (the count of message leaves recorded before this block). checked_sub(1) fails when version() == 0, meaning the message block's merkle metadata records zero leaves — there are no messages to prove at or before that block. Requesting an inclusion proof for a message whose block contains no message leaves is therefore invalid.

Source

Thrown at crates/fuel-core/src/database/block.rs:119

        let message_merkle_metadata = self
            .storage::<FuelBlockMerkleMetadata>()
            .get(&DenseMetadataKey::Primary(*message_block_height))?
            .ok_or(not_found!(FuelBlockMerkleMetadata))?;

        let commit_merkle_metadata = self
            .storage::<FuelBlockMerkleMetadata>()
            .get(&DenseMetadataKey::Primary(*commit_block_height))?
            .ok_or(not_found!(FuelBlockMerkleMetadata))?;

        let storage = self;
        let tree: MerkleTree<FuelBlockMerkleData, _> =
            MerkleTree::load(storage, commit_merkle_metadata.version())
                .map_err(|err| StorageError::Other(anyhow::anyhow!(err)))?;

        let proof_index = message_merkle_metadata
            .version()
            .checked_sub(1)
            .ok_or(anyhow::anyhow!("The count of leaves - messages is zero"))?;
        let (_, proof_set) = tree
            .prove(proof_index)
            .map_err(|err| StorageError::Other(anyhow::anyhow!(err)))?;

        Ok(MerkleProof {
            proof_set,
            proof_index,
        })
    }
}

#[allow(clippy::arithmetic_side_effects)]
#[cfg(test)]
mod tests {
    use super::*;
    use crate::database::Database;
    use fuel_core_storage::{
        StorageMutate,

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Verify the message is actually contained in message_block_height (check the message's committed height) before requesting the proof.
  2. If heights come from external metadata, refresh them after re-orgs or DA finality changes.
  3. Treat this error as 'no such message proof at this height' and surface an invalid-request error to callers.
Defensive patterns

Strategy: validation

Validate before calling

// Before proving, confirm the message block actually contains message leaves.
let meta = view
    .storage::<FuelBlockMerkleMetadata>()
    .get(&DenseMetadataKey::Primary(*message_block_height))?;
if meta.map(|m| *m.value().version() == 0).unwrap_or(true) {
    anyhow::bail!("no message leaves at block {}; wrong message height?", message_block_height);
}
let proof = view.block_history_proof(message_block_height, commit_block_height)?;

Try / catch

match view.block_history_proof(&msg_h, &commit_h) {
    Ok(p) => Ok(p),
    Err(e) if e.to_string().contains("leaves - messages is zero") => {
        // the block has no messages: the requested message cannot be proven there
        Err(not_found!(FuelBlockMerkleMetadata))
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Requesting a message proof where the block identified by message_block_height has FuelBlockMerkleMetadata version 0 (no messages committed up to that point) — e.g., proving against a wrong block height or a block that never contained the message.

Common situations: Bridge/relayer code deriving the message block height incorrectly and landing on an empty block; querying proofs for messages not yet committed; chain re-orgs leaving stale message-height references.

Related errors


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