# How Txpool v2 Manages Pending Transactions in Fuel Core

> Discover how Fuel Core's txpool v2 manages pending transactions. Learn about its PendingPool module, dependency tracking, and automatic on-chain promotion of UTXOs and contracts.

- Repository: [Fuel Labs/fuel-core](https://github.com/FuelLabs/fuel-core)
- Tags: internals
- Published: 2026-03-06

---

**Fuel Core’s txpool v2 uses a dedicated `PendingPool` module to store transactions with unresolved inputs, tracking dependencies through bidirectional hash maps and automatically promoting them to the main pool when their required UTXOs or contracts appear on chain.**

Fuel Core, the Rust-based full node powering the Fuel blockchain, separates transaction validation into distinct lifecycle stages to maximize throughput. When the txpool v2 receives transactions that reference inputs not yet existing in the current chain state—such as UTXOs from pending blocks or newly deployed contracts—it cannot immediately verify their validity. Rather than discarding these transactions, the implementation holds them in a pending pool, indexed by their missing dependencies, and resolves them asynchronously as the blockchain evolves.

## Core Data Structures in PendingPool

The pending pool implementation resides in [`crates/services/txpool_v2/src/pending_pool.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/txpool_v2/src/pending_pool.rs), where the `PendingPool` struct (defined at line 50) maintains three synchronized storage mechanisms to track orphan transactions efficiently:

- **`pending_txs_by_inputs: HashMap<MissingInput, HashSet<TxId>>`** — Maps each missing input (UTXO ID or contract) to the set of transactions waiting for it. This allows O(1) lookup when a new input becomes available.
- **`pending_inputs_by_tx: HashMap<TxId, PendingTx>`** — Stores the transaction object alongside its remaining list of missing inputs, enabling quick updates when partial resolution occurs.
- **`ttl_check: VecDeque<(SystemTime, TxId)>`** — A time-ordered queue that pairs each transaction with its expiration timestamp, supporting efficient background cleanup.

This dual-map architecture ensures that the pool can locate all transactions dependent on a specific input instantly, while also tracking per-transaction metadata and expiration deadlines.

## Inserting Transactions with Missing Inputs

When the txpool receives a transaction whose inputs are not fully known, it invokes `insert_transaction` (line 12 in [`pending_pool.rs`](https://github.com/FuelLabs/fuel-core/blob/main/pending_pool.rs)). This method performs the following operations:

1. Creates a `PendingTx` record containing the transaction and its list of missing inputs.
2. Updates the bidirectional mappings: for each missing input, it inserts the transaction ID into `pending_txs_by_inputs` and populates `pending_inputs_by_tx`.
3. Adjusts global pool counters for byte size, gas consumption, and transaction count.
4. Pushes a TTL entry onto `ttl_check` using the configured expiration time (`SystemTime::now() + ttl`).

The insertion source—whether from RPC, P2P gossip, or block import—is preserved to ensure proper error propagation if the transaction expires before resolution.

## Resolving Pending Transactions When Inputs Arrive

As the Fuel node imports new blocks or processes other transactions, previously missing UTXOs and contracts become available. The pending pool resolves these dependencies through `new_known_tx` (line 30), which accepts an iterator of `(UtxoId, &Output)` pairs representing newly created outputs.

The resolution workflow proceeds as follows:

- `new_known_tx` iterates over each new output and delegates to `new_known_input_from_output`.
- For each input, the method looks up `pending_txs_by_inputs` to find waiting transactions.
- It removes the resolved input from the transaction’s `missing_inputs` list in `pending_inputs_by_tx`.
- When a transaction’s missing input list becomes empty, the implementation removes it entirely from the pending pool and returns it to the caller for insertion into the main transaction pool.

This incremental resolution ensures that transactions are only promoted once all their dependencies are satisfied, preventing invalid transactions from entering the executable pool.

## TTL-Based Expiration and Garbage Collection

To prevent unbounded memory growth from transactions waiting indefinitely for inputs that may never materialize, txpool v2 implements strict TTL-based expiration. The `expire_transactions` method (line 84) runs periodically as a background task:

- It pops entries from the back of `ttl_check` while their timestamp is ≤ current system time.
- For each expired transaction, it decrements the pool’s size, gas, and transaction counters.
- It cleans the corresponding entries from `pending_txs_by_inputs` to remove stale dependency links.
- It emits an `ErrorInsertion` notification containing the original missing input, allowing upstream components to notify submitters or log the failure.

The TTL duration is configurable via `TxPoolConfig` in [`crates/services/txpool_v2/src/config.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/txpool_v2/src/config.rs), specifically through the `pending_pool_ttl` parameter read at node startup.

## Configuration and Monitoring

Operators can monitor pending pool health through metrics exposed in [`crates/metrics/src/txpool_metrics.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/metrics/src/txpool_metrics.rs). Key gauges include `number_of_transactions_pending_verification`, which reflects the current count in `pending_inputs_by_tx`, and associated byte/gas totals.

Integration tests in [`crates/services/txpool_v2/src/pending_pool.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/txpool_v2/src/pending_pool.rs) (line 103) verify the complete lifecycle: inserting a dependent transaction, supplying the missing UTXO via `new_known_tx`, and asserting that the pending pool empties as the transaction graduates to the main pool. Additional end-to-end coverage exists in [`tests/tests/tx/txpool.rs`](https://github.com/FuelLabs/fuel-core/blob/main/tests/tests/tx/txpool.rs).

## Summary

- **PendingPool** in [`crates/services/txpool_v2/src/pending_pool.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/txpool_v2/src/pending_pool.rs) uses bidirectional hash maps (`pending_txs_by_inputs` and `pending_inputs_by_tx`) to track transactions waiting for unknown inputs.
- **Insertion** via `insert_transaction` indexes transactions by their missing dependencies and schedules TTL expiration using a `VecDeque` timestamp queue.
- **Resolution** occurs through `new_known_tx`, which promotes transactions to the main pool once all inputs become available on chain.
- **Expiration** via `expire_transactions` removes stale entries based on configurable TTL, preventing memory leaks and notifying the system via `ErrorInsertion` events.
- **Observability** is provided through dedicated metrics in [`txpool_metrics.rs`](https://github.com/FuelLabs/fuel-core/blob/main/txpool_metrics.rs) and validated by unit tests at line 103 of the pending pool module.

## Frequently Asked Questions

### What happens to a transaction if its required UTXO never appears?

If the missing input does not materialize within the configured TTL window, `expire_transactions` removes the transaction from the pending pool and emits an `ErrorInsertion` notification. The transaction is dropped without entering the main pool, and the submitter can be notified that their inputs were unavailable.

### How does txpool v2 handle multiple missing inputs for a single transaction?

The `PendingTx` struct stores the complete list of missing inputs. Each input is registered in `pending_txs_by_inputs`, and the transaction remains pending until `new_known_input_from_output` removes the final dependency. Only when the list empties does the transaction return for main pool insertion.

### Where is the pending pool TTL configured?

The TTL is defined in `TxPoolConfig` within [`crates/services/txpool_v2/src/config.rs`](https://github.com/FuelLabs/fuel-core/blob/main/crates/services/txpool_v2/src/config.rs) via the `pending_pool_ttl` field. This duration is read at node startup and passed to the `PendingPool` constructor, determining how long transactions may wait for unresolved inputs before automatic expiration.

### Can pending transactions consume node resources indefinitely?

No. The `ttl_check` queue ensures that memory, gas, and byte counters are strictly bounded. Even if inputs never arrive, the periodic `expire_transactions` call purges old entries and updates metrics, preventing resource exhaustion regardless of network conditions.