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
- Ensure the compressed block was produced by the standard compression path, which places the mint last.
- If building blocks manually, append the Mint transaction as the final transaction before compressing.
- 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
- Append the mint transaction as the final transaction when building blocks manually.
- Round-trip compress→decompress in tests for custom block builders to catch format mistakes.
- Beware off-by-one truncation of transaction lists — the trailing mint is lost first.
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
- No transactions
- Compression level {value} outside of allowed range 0..=22
- Fragments use different compressions.
- Failed to get registry root: {}
- Invalid timestamp ordering
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/6107fd9671d8882d.
Report an issue: GitHub.