FuelLabs/fuel-core · error · StorageError
Transaction status already exists for tx {}
Error message
Transaction status already exists for tx {} What it means
While indexing an imported block, the worker writes each transaction's status with db.update_tx_status(id, status). The method returns the previously stored status; a Some return means this tx id already had a status recorded, i.e., the block/tx is being processed twice. The worker treats duplicate status writes as an inconsistency and aborts indexing instead of silently overwriting history.
Source
Thrown at crates/fuel-core/src/graphql_api/worker_service.rs:461
}
Ok(())
}
fn persist_transaction_status<T>(
import_result: &ImportResult,
asset_metadata_indexation_enabled: bool,
db: &mut T,
) -> StorageResult<()>
where
T: OffChainDatabaseTransaction,
{
for TransactionExecutionStatus { id, result } in import_result.tx_status.iter() {
let status =
from_executor_to_status(&import_result.sealed_block.entity, result.clone());
if db.update_tx_status(id, status)?.is_some() {
return Err(anyhow::anyhow!(
"Transaction status already exists for tx {}",
id
)
.into());
}
let TransactionExecutionResult::Success { receipts, .. } = result else {
continue
};
update_receipt_based_indexation(receipts, db, asset_metadata_indexation_enabled)?;
}
Ok(())
}
pub fn process_transactions<'a, I, T>(transactions: I, db: &mut T) -> StorageResult<()>
where
I: Iterator<Item = &'a Transaction>,View on GitHub (pinned to b9d4d170da)
Solutions
- Do not re-run the off-chain worker over blocks already indexed; resume from the off-chain DB's recorded height.
- If a full re-index is intended, wipe the off-chain database first so no prior statuses exist.
- Inspect import_result.tx_status for duplicate tx ids before indexing; deduplicate if the producer can emit repeats.
- Verify the off-chain DB snapshot matches the on-chain DB height before restarting workers.
Defensive patterns
Strategy: validation
Validate before calling
// Skip txs already indexed before writing statuses during re-processing.
let ids: HashSet<_> = import_result.tx_status.iter().map(|s| s.id).collect();
if ids.len() != import_result.tx_status.len() {
anyhow::bail!("import result contains duplicate tx statuses");
}
for TransactionExecutionStatus { id, result } in import_result.tx_status.iter() {
if db.update_tx_status(id, placeholder)?.is_some() {
anyhow::bail!("tx {} already indexed; block processed twice?", id);
}
} Try / catch
match update_block_metadata(&import_result, flag, &mut db) {
Ok(()) => Ok(()),
Err(e) if e.to_string().contains("status already exists") => {
// duplicate processing: verify heights, and for intentional re-index wipe the off-chain DB first
anyhow::bail!("duplicate import detected: {}", e)
}
Err(e) => Err(e),
} Prevention
- Resume off-chain workers from the off-chain DB's recorded height, never from an earlier block.
- Wipe the off-chain DB before any full re-index.
- Deduplicate import_result.tx_status at the producer if duplicates are possible.
- Restore on-chain and off-chain DBs only from consistent snapshot pairs.
When it happens
Trigger: The same TransactionExecutionStatus appears in an import result for a tx already indexed — duplicate block import, overlapping import results, or re-running the off-chain worker over an already-processed block.
Common situations: Re-importing a block after a crash where part of the off-chain transaction committed; worker replay without resetting the off-chain DB; bugs producing duplicate entries in import_result.tx_status; restoring off-chain DBs from mixed snapshots.
Related errors
- The block has more than `u16::MAX` transactions, {}
- off-chain database doesn't have height
- off-chain database height({off_chain_height}) is less than t
- Invalid coin type {:?}
- Unable to decode bytes
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/adc30d901d4b0af5.
Report an issue: GitHub.