FuelLabs/fuel-core · error · GasPriceError::CouldNotFetchL2Block

Block has no mint transaction

Error message

Block has no mint transaction

What it means

mint_values expects the LAST transaction of a block to be a Mint transaction — the fee/gas-price mint the executor appends at the end of every produced block. When the final tx is not a mint (or the block has no transactions, e.g. genesis), get_block_info fails with GasPriceError::CouldNotFetchL2Block wrapping this message. The gas price service therefore cannot compute BlockInfo for such a block.

Source

Thrown at crates/services/gas_price_service/src/common/fuel_core_storage_adapter.rs:198

    let used_gas = block_used_gas(fee, gas_price, gas_price_factor, block_gas_limit)?;
    let info = BlockInfo::Block {
        height: (*block.header().height()).into(),
        gas_used: used_gas,
        block_gas_capacity: block_gas_limit,
        block_bytes: Postcard::encode(block).len() as u64,
        block_fees: fee,
        gas_price,
    };
    Ok(info)
}

pub(crate) fn mint_values(block: &Block<Transaction>) -> GasPriceResult<(u64, u64)> {
    let mint = block
        .transactions()
        .last()
        .and_then(|tx| tx.as_mint())
        .ok_or(GasPriceError::CouldNotFetchL2Block {
            source_error: anyhow!("Block has no mint transaction"),
        })?;
    Ok((*mint.mint_amount(), *mint.gas_price()))
}

// TODO: Don't take a direct dependency on `Postcard` as it's not guaranteed to be the encoding format
// https://github.com/FuelLabs/fuel-core/issues/2443
pub(crate) fn block_bytes(block: &Block<Transaction>) -> u64 {
    Postcard::encode(block).len() as u64
}

fn block_used_gas(
    fee: u64,
    gas_price: u64,
    gas_price_factor: u64,
    max_used_gas: u64,
) -> GasPriceResult<u64> {
    let scaled_fee =
        fee.checked_mul(gas_price_factor)

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Skip the genesis block (start gas price tracking at height >= 1) so every processed block has an executor-appended mint.
  2. Ensure blocks are produced/imported by a fuel-core version whose executor appends the Mint transaction last (do not mix old-format blocks into a new chain).
  3. When constructing blocks manually for tests, append a Mint transaction as the last transaction.

Example fix

// before: feeding every block including genesis into the gas price service
for height in 0..=head { let info = get_block_info(&get_block(height)?, ...)?; }

// after: skip the genesis block (no mint transaction)
for height in 1..=head { let info = get_block_info(&get_block(height)?, ...)?; }
Defensive patterns

Strategy: type-guard

Type guard

use fuel_core_types::{blockchain::block::Block, fuel_tx::Transaction};

fn block_has_trailing_mint(block: &Block<Transaction>) -> bool {
    block.transactions().last().is_some_and(|tx| tx.as_mint().is_some())
}

if !block_has_trailing_mint(&block) {
    // genesis or legacy-format block: skip gas price processing
    continue;
}

Try / catch

match get_block_info(&block, factor, gas_limit) {
    Err(GasPriceError::CouldNotFetchL2Block { ref source_error })
        if source_error.to_string().contains("no mint transaction") =>
    {
        // expected for genesis/legacy blocks: skip this height, keep the service running
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling get_block_info(block) (gas price service initialization / block processing) on a block whose transactions() ends without a mint: the genesis block at height 0, blocks deserialized from an older fuel-core with a different mint placement/format, or hand-crafted test blocks built without appending a Mint.

Common situations: Gas price service starting at genesis height instead of height 1; replaying/importing blocks produced by older clients; custom test block builders that skip the mint transaction.

Related errors


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