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
.dbView on GitHub (pinned to b9d4d170da)
Solutions
- Feed decompression a well-formed compressed block produced by the standard compression path (it always includes the mint).
- Validate the block's transaction count before decompressing (see validation snippet).
- 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
- Only feed blocks produced by the standard compression path, which always includes the mint.
- Validate transaction counts against the block header before decompressing.
- In fuzzing/adversarial setups, expect this error and reject the input rather than retrying.
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
- Last transaction is not a mint
- 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/597a129104db9cbd.
Report an issue: GitHub.