FuelLabs/fuel-core · error · Error::DB

while getting receipts for tx_id: {:?}

Error message

while getting receipts for tx_id: {:?}

What it means

Thrown by the block aggregator API's old-block source while streaming historical blocks: for each transaction in a block, StorageIterator::get_receipts calls the configured TxReceipts provider, and any failure is wrapped into Error::DB with the failing tx_id attached as context (crates/services/block_aggregator_api/src/blocks/old_block_source.rs:156). The error becomes an Err item in the iterator returned by blocks_starting_from, so block streaming stops at the height whose receipts cannot be read. The anyhow context preserves the underlying storage error as root cause.

Source

Thrown at crates/services/block_aggregator_api/src/blocks/old_block_source.rs:156

                Some(tx) => {
                    tracing::debug!("found tx id: {:?}", tx_id);
                    txs.push(tx.into_owned());
                }
                None => {
                    return Ok(vec![]);
                }
            }
        }
        Ok(txs)
    }

    fn get_receipts(&self, tx_ids: &[TxId]) -> Result<Vec<Vec<Receipt>>> {
        use itertools::Itertools;
        tx_ids
            .iter()
            .map(|tx_id| {
                self.receipts.get_receipts(tx_id).map_err(|err| {
                    Error::DB(anyhow::anyhow!(err).context(format!(
                        "while getting receipts for tx_id: {:?}",
                        tx_id
                    )))
                })
            })
            .try_collect()
    }
}

impl<Convertor, DB, Receipts> Iterator for StorageIterator<Convertor, DB, Receipts>
where
    DB: StorageInspect<FuelBlocks, Error = StorageError>,
    DB: StorageInspect<Transactions, Error = StorageError>,
    Receipts: TxReceipts,
    Convertor: BlockConverter,
{
    type Item = Result<(BlockHeight, Convertor::Block)>;

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Take the tx_id from the message and probe the receipts storage directly for that key to confirm whether the data exists or the read itself fails.
  2. Inspect the inner error chain (the anyhow context keeps the root cause) to distinguish 'not found' from IO/lock errors, and fix the storage layer accordingly.
  3. Verify the node version matches the version that produced the database snapshot and that receipts were not pruned; re-sync the range if data is absent.
  4. For transient IO/lock failures, restart the stream from the failing height (the iterator stops on Err, so restart rather than continue).
  5. If your protocol tolerates gaps, skip the affected height explicitly; the iterator itself will not advance past an Err item.

Example fix

// before: stream until it silently stops on the first Err
for item in source.blocks_starting_from(start) {
    let (height, block) = item?;
    publish(height, block);
}

// after: surface DB receipt failures with height and retry from the same height
let mut height = start;
'outer: loop {
    for item in source.blocks_starting_from(height) {
        match item {
            Ok((h, block)) => { publish(h, block); height = h.succ().unwrap_or(h); }
            Err(Error::DB(ctx)) => {
                tracing::error!(%ctx, from = ?height, "receipts unavailable");
                continue 'outer; // retry stream at same height after fixing storage
            }
            Err(e) => break 'outer,
        }
    }
    break;
}
Defensive patterns

Strategy: retry

Validate before calling

// Before starting the stream, verify receipts are readable for the start height's block
use fuel_core_storage::StorageInspect;
use fuel_core_types::fuel_tx::TxId;

fn receipts_available<R: TxReceipts>(receipts: &R, tx_ids: &[TxId]) -> bool {
    tx_ids.iter().all(|id| receipts.get_receipts(id).is_ok())
}

Try / catch

match source.blocks_starting_from(height).next() {
    Some(Ok((h, block))) => { /* forward block */ }
    Some(Err(Error::DB(ctx))) => {
        tracing::error!(%ctx, from = ?height, "receipts unavailable");
        // restart stream at the same height after fixing/backing off; do not silently skip
        restart_stream_at(height);
    }
    Some(Err(other)) => return Err(other),
    None => { /* stream end */ }
}

Prevention

When it happens

Trigger: Calling blocks_starting_from(height) (directly or via the aggregator's block-serving loop) when the receipts provider fails for one of the block's tx_ids: receipts table missing or pruned for that range, database handle closed or unopened, IO/corruption error inside the provider, or a test stub TxReceipts implementation that returns Err.

Common situations: Node restored from a pruned/partial snapshot that lacks receipt data; underlying DB (e.g. RocksDB) opened with an incompatible column-family layout by a different fuel-core version; disk or IO failures mid-stream; custom Receipts adapters in tests or plugins that error instead of returning data.

Related errors


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