# How Neo's MemoryPool Handles Transaction Verification and Fee Estimation

> Discover how Neo's MemoryPool verifies transactions, prioritizes them by fee, and estimates network fees. Learn about its eviction strategy for optimal performance.

- Repository: [The Neo Project/neo](https://github.com/neo-project/neo)
- Tags: internals
- Published: 2026-03-08

---

**Neo’s MemoryPool verifies transactions through state-dependent checks in `TryAdd`, prioritizes them by fee-per-byte in a sorted set, and estimates network fees based on transaction size and policy rates before eviction of low-fee transactions when capacity is exceeded.**

The MemoryPool in the [neo-project/neo](https://github.com/neo-project/neo) repository serves as the temporary holding area for transactions that have passed initial validation but await inclusion in a block. Understanding how Neo's MemoryPool handles transaction verification and fee estimation is critical for developers building wallets, RPC services, or consensus mechanisms on the Neo N3 blockchain.

## Transaction Verification Flow in Neo's MemoryPool

### State-Dependent Verification via TryAdd

When a transaction enters the pool, the `MemoryPool.TryAdd` method orchestrates the verification pipeline. Located in [`src/Neo/Ledger/MemoryPool.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Ledger/MemoryPool.cs) (lines 13–71), this method first checks for an optional `NewTransaction` policy handler that can reject transactions based on custom rules.

The core verification occurs through `tx.VerifyStateDependent(settings, snapshot, VerificationContext, conflicts)`. Unlike state-independent checks (signatures, format) that happen earlier in `Transaction.Verify()`, state-dependent verification ensures the sender has sufficient balance, valid claims, and contract-specific requirements against the current blockchain snapshot.

### Conflict Detection and Fee Comparison

Before state verification, `CheckConflicts` (MemoryPool.cs lines 81–110) ensures the transaction does not conflict with already-pooled transactions. If conflicts exist, the new transaction must offer a higher **network fee** than the sum of all conflicting transactions' fees to replace them.

This mechanism prevents spam and ensures that higher-paying transactions can bump lower-paying ones when they share inputs or conflicts.

### TransactionVerificationContext for Balance Tracking

The `TransactionVerificationContext` class (located in [`src/Neo/Ledger/TransactionVerificationContext.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Ledger/TransactionVerificationContext.cs)) tracks accumulated fees per sender across multiple pooled transactions. When `AddTransaction(tx)` is called (lines 45–48), it aggregates the sender's `SystemFee + NetworkFee` to prevent double-spending scenarios where a user might submit multiple transactions that collectively exceed their balance.

## Fee Estimation and Prioritization Mechanisms

### Calculating Network Fees Before Pool Entry

Neo calculates the **network fee** before a transaction reaches the MemoryPool using the formula implemented in `WalletHelper.CalculateNetworkFee` ([`src/Neo/Wallets/Helper.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Wallets/Helper.cs), lines 95–115):

```

NetworkFee = size * Policy.GetFeePerByte(snapshot) + sum(attribute fees) + excess

```

- **Size**: `Transaction.Size` determines the byte-length cost
- **FeePerByte**: Retrieved from native Policy contract
- **Attribute fees**: Special attributes like `Conflicts` or `NotaryAssisted` add additional costs via their `CalculateNetworkFee` implementations

This calculation ensures users pay for the computational and storage resources their transactions consume.

### PoolItem Comparison and Fee-per-Byte Ordering

Once in the pool, transactions are wrapped in `PoolItem` objects ([`src/Neo/Ledger/PoolItem.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Ledger/PoolItem.cs)). The `CompareTo` method (lines 47–60) implements a multi-level sorting strategy:

1. **High-priority flag** (transactions with this flag come first)
2. **Fee-per-byte** (`NetworkFee / Size`) - higher is better
3. **Network fee** (absolute amount) as tie-breaker (lines 55–57)
4. **Transaction hash** (for deterministic ordering)

This sorting ensures that miners and consensus nodes select the most profitable transactions when building blocks.

### Capacity Management and Low-Fee Eviction

The MemoryPool enforces a `Capacity` limit. When `TryAdd` would exceed this limit, `RemoveOverCapacity` (MemoryPool.cs lines 16–34) evicts the lowest-fee transaction retrieved via `GetLowestFeeTransaction`.

This eviction mechanism uses the same `PoolItem` comparison logic, meaning transactions with the lowest fee-per-byte are removed first, maintaining the pool's economic efficiency.

## Re-verification After Block Persistence

### Invalidating Verified Transactions

After a block is persisted to the blockchain, the MemoryPool must re-verify all transactions because the state snapshot has changed (balances, nonces, contract storage). The `InvalidateVerifiedTransactions` method (MemoryPool.cs lines 87–100) moves all transactions from the verified sets (`_unsortedTransactions`, `_sortedTransactions`) to the `_unverifiedTransactions` dictionary.

### Bounded Re-verification Process

The `ReverifyTransactions` method (MemoryPool.cs lines 83–115) processes unverified transactions in batches constrained by `MaxMillisecondsToReverifyTx` or `MaxMillisecondsToReverifyTxPerIdle`. This prevents the node from freezing during large re-verification tasks.

Each candidate transaction undergoes `VerifyStateDependent` again. Successful transactions are promoted back to the verified containers, and their fees are re-added to the `VerificationContext`. Failed transactions are permanently removed from the pool.

## Code Examples for MemoryPool Operations

### Adding a Transaction to the Memory Pool

```csharp
var system = NeoSystem.Create(settings, logger);
var pool   = system.MemPool;                // MemoryPool instance
var snapshot = system.Store.GetSnapshot();   // DataCache snapshot
var tx = new Transaction { /* fill fields */ };

var result = pool.TryAdd(tx, snapshot);
if (result == VerifyResult.Succeed)
    Console.WriteLine("Transaction accepted into mempool");
else
    Console.WriteLine($"Rejected: {result}");

```

*`TryAdd` runs the policy handler, conflict checks, and `VerifyStateDependent`.*

### Checking Whether a Transaction Fits in the Pool

```csharp
bool canFit = pool.CanTransactionFitInPool(tx);
Console.WriteLine(canFit ? "Fits" : "Would overflow pool");

```

*Uses the lowest-fee transaction as a benchmark (`GetLowestFeeTransaction`).*

### Retrieving the Highest-Fee Verified Transactions

```csharp
Transaction[] topTxes = pool.GetSortedVerifiedTransactions(count: 10);
foreach (var t in topTxes)
    Console.WriteLine($"{t.Hash} – fee per byte: {t.FeePerByte}");

```

*The sorted set already orders by `FeePerByte` then `NetworkFee`.*

### Re-verifying Stale Transactions After a Block

```csharp
// After block persistence
pool.UpdatePoolForBlockPersisted(persistedBlock, snapshot);
// The pool will automatically re-verify eligible unverified transactions.

```

## Summary

- **Neo’s MemoryPool** stores transactions that have passed state-dependent verification in sorted containers (`_sortedTransactions`), ordered by fee-per-byte and absolute network fee.
- **Verification** occurs in `MemoryPool.TryAdd` through `CheckConflicts` (ensuring higher fees replace conflicting transactions) and `Transaction.VerifyStateDependent` (validating balances against the current snapshot).
- **Fee estimation** happens before pool entry via `WalletHelper.CalculateNetworkFee`, computing costs based on transaction size, per-byte policy rates, and attribute-specific fees.
- **Capacity management** evicts the lowest fee-per-byte transactions when the pool exceeds its limit, using `PoolItem.CompareTo` to identify economically inefficient entries.
- **Re-verification** after block persistence invalidates all verified transactions and re-checks them against the new state snapshot in time-bounded batches.

## Frequently Asked Questions

### How does Neo's MemoryPool prevent double-spending across multiple pending transactions?

The `TransactionVerificationContext` class tracks the cumulative `SystemFee` and `NetworkFee` for each sender across all transactions currently in the pool. When `AddTransaction` is called (TransactionVerificationContext.cs lines 45–48), it aggregates these fees and validates that the sender's balance covers the total amount. If a new transaction would cause the sender to overspend, `VerifyStateDependent` returns a failure result and the transaction is rejected from the mempool.

### What determines the order in which transactions are selected for block inclusion?

Transactions are ordered by the `PoolItem.CompareTo` method (PoolItem.cs lines 47–60) using a hierarchical comparison: high-priority transactions come first, followed by those with higher **fee-per-byte** (`NetworkFee / Size`), then higher absolute **network fee** as a tie-breaker, and finally by transaction hash for deterministic ordering. This ensures miners select the most economically valuable transactions when building blocks.

### How does the MemoryPool handle transactions that become invalid after a block is persisted?

After block persistence, `InvalidateVerifiedTransactions` (MemoryPool.cs lines 87–100) moves all previously verified transactions to an unverified collection. The `ReverifyTransactions` method (MemoryPool.cs lines 83–115) then processes these in time-bounded batches (controlled by `MaxMillisecondsToReverifyTx`), re-running `VerifyStateDependent` against the updated blockchain snapshot. Transactions that fail re-verification are permanently removed, while successful ones return to the verified sorted set.