FuelLabs/fuel-core · error

Last transaction is not a mint

Error message

Last transaction is not a mint

What it means

After decompressing the transaction list, decompression requires the final transaction to be the Mint transaction, whose tx_pointer it rewrites to (block_height, transaction_count - 1). If the last transaction is any other variant, it fails with 'Last transaction is not a mint' (crates/compression/src/decompress.rs:112). Fuel blocks must end with exactly one mint transaction, so this indicates a malformed or incorrectly assembled block.

Source

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

        &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
                    .registry_root()
                    .map_err(|e| anyhow::anyhow!("Failed to get registry root: {}", e))?;
                let registry_root_after_compression = block.header.registry_root;
                if registry_root_after_decompression != registry_root_after_compression {
                    anyhow::bail!(
                        "Registry root mismatch. registry root after decompression: {:?}, registry root after compression: {:?}",
                        registry_root_after_decompression,
                        registry_root_after_compression
                    );

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Ensure the compressed block was produced by the standard compression path, which places the mint last.
  2. If building blocks manually, append the Mint transaction as the final transaction before compressing.
  3. Check for truncation of the transaction list — the missing mint at the end is the tell.

Example fix

// before
let partial_block = decompress(&mut db, compressed_block).await?; // 'Last transaction is not a mint'

// after — assemble blocks with mint last
let mut txs = regular_transactions;
txs.push(Transaction::Mint(mint_tx));
// then compress txs and decompress symmetrically
Defensive patterns

Strategy: validation

Validate before calling

// Producer-side: always assemble blocks with the mint transaction last
let mut txs = regular_transactions;
txs.push(Transaction::Mint(mint_tx));
// then compress txs; decompression will find the mint in the final slot

Type guard

fn ends_with_mint(txs: &[Transaction]) -> bool {
    matches!(txs.last(), Some(Transaction::Mint(_)))
}

Try / catch

match decompress(&mut db, compressed_block).await {
    Err(e) if e.to_string().contains("Last transaction is not a mint") => {
        // malformed block: reject and investigate the producer/truncation
        return Err(e.context("compressed block missing trailing mint transaction"));
    }
    r => r?,
}

Prevention

When it happens

Trigger: Decompressing a compressed block whose last entry is not the mint — e.g. a producer appended regular transactions after the mint, or the mint was dropped during serialization/truncation.

Common situations: Custom block builders not appending mint last; off-by-one truncation of the transaction list (the mint at the end is the first casualty); fuzzed inputs; older block formats without the mint convention.

Related errors


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