FuelLabs/fuel-core · error

No transactions

Error message

No transactions

What it means

During decompression of a VersionedCompressedBlock, the code patches the mandatory final mint transaction (rewriting its tx_pointer to the block height and index). If the decompressed transaction list is empty, there is no last element and decompression fails with 'No transactions' (crates/compression/src/decompress.rs:103). Valid Fuel blocks always end with a mint transaction, so an empty list means the compressed block is malformed or was assembled incorrectly.

Source

Thrown at crates/compression/src/decompress.rs:103

    let ctx = DecompressCtx {
        config,
        timestamp: block.consensus_header().time,
        db,
    };

    let mut transactions = <Vec<Transaction> as DecompressibleBy<_>>::decompress_with(
        block.transactions(),
        &ctx,
    )
    .await?;

    let transaction_count = transactions.len();

    // patch mint transaction
    let mint_tx = transactions
        .last_mut()
        .ok_or_else(|| anyhow::anyhow!("No transactions"))?;
    if let Transaction::Mint(mint) = mint_tx {
        let tx_pointer = mint.tx_pointer_mut();
        *tx_pointer = FuelTxPointer::new(
            block.consensus_header().height,
            #[allow(clippy::arithmetic_side_effects)]
            u16::try_from(transaction_count - 1)?,
        );
    } else {
        anyhow::bail!("Last transaction is not a mint");
    }

    #[cfg(feature = "fault-proving")]
    {
        match block {
            VersionedCompressedBlock::V0(_) => {}
            VersionedCompressedBlock::V1(ref block) => {
                let registry_root_after_decompression = ctx
                    .db

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Feed decompression a well-formed compressed block produced by the standard compression path (it always includes the mint).
  2. Validate the block's transaction count before decompressing (see validation snippet).
  3. If you produce blocks yourself, always append the mint transaction last.

Example fix

// before
let partial_block = decompress(&mut db, compressed_block).await?; // 'No transactions'

// after — reject empty compressed blocks up front
anyhow::ensure!(
    !compressed_block.transactions().is_empty(),
    "compressed block carries no transactions"
);
let partial_block = decompress(&mut db, compressed_block).await?;
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(
    !compressed_block.transactions().is_empty(),
    "compressed block carries no transactions"
);
let partial_block = decompress(&mut db, compressed_block).await?;

Type guard

fn has_transactions(block: &VersionedCompressedBlock) -> bool {
    !block.transactions().is_empty()
}

Try / catch

match decompress(&mut db, compressed_block).await {
    Err(e) if e.to_string().contains("No transactions") => {
        // malformed input: reject the block and alert the producer
        return Err(e.context("received empty compressed block"));
    }
    r => r?,
}

Prevention

When it happens

Trigger: Decompressing a compressed block whose transactions list is empty — hand-built blocks, truncated data, or a faulty producer that emitted zero transactions.

Common situations: Custom tooling that assembles VersionedCompressedBlock manually; serialization round-trip bugs dropping the transaction list; fuzzed or adversarial inputs in fault-proving setups.

Related errors


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