Geth's transaction pool (aka mempool) can be boiled down to a few key data structures and processes. Transactions are the main building block. They are stored on the heap while references to them populate four objects: "all", "priced", "queue", and "pending".
The "all" object is a mapping of the transaction's hash to the actual transaction object on the heap. It is the canonical source of transactions in the mempool and is used to build (and rebuild) the "priced" object.
The "priced" object is a heap (data structure) that orders the transactions in the mempool highest to lowest priced. When the mempool is completely saturated, "priced" is asked to find the N cheapest transactions so that they can be fully evicted from the mempool.
The "queue" object is a mapping between addresses and individualized price heaps. This structure is used to help order transactions per account. The price heaps use the same data structure backing as "priced". This is the entry point to the mempool for nearly every transaction.
Acceptance to "queue" is slightly less rigid than to "pending" as it only ensures that the db.Nonce(tx.From()) < tx.Nonce(). In other words, it accepts transactions that are not immediately executable.
The "pending" object is identical to "queue" except it enforces strict ordering on transactions (e.g. tx[n].Nonce() == tx[n+1].Nonce() - 1). Once a transaction reaches this point, it will either be included in a block or it will be dropped.
The mining module in geth is notified of changes to "pending". It draws from these transactions when constructing its block. Therefore, however the transactions are ordered when "pending" is flattened is how they'll appear in the block.
The mempool can receive transactions from two sources: the p2p network or locally via RPC. Transactions are treated slightly differently depending on their origin. If they originate from RPC, they are tagged as "local" for the rest of their time in *that* client's mempool.
The mempool keeps track of addresses that have submitted transactions via RPC and prioritizes them over non-local transactions for things like persistence and eviction. They are, however, treated equally by "priced", and are not mined at a higher rate than external transactions.
Adding a transaction to the mempool can result in several different outcomes and kick off multiple processes. I've done my best to simplify this process into a flow chart:
See if you can find the exception where a transaction will actually *skip* "queued" and go straight to "pending"!
Mempool reorgs can be triggered in a few ways. As shown above, the first way is when a new transaction is added. This allows fresh, profitable transactions to be quickly moved into the pending line.
The other time it's triggered is when the underlying chain head becomes a different block. This could be the result of a network reorg or a new incoming block. In either case, the reorg looks something like this:
I see that I have an extra reorg trigger at the bottom :)
To improve performance, recovered signatures are cached. This allows the mempool to quickly ascertain the sender of a transaction without having to recompute it every time.
I meant to mention this when discussing mempool evictions, *but* another mechanism the mempool uses to evict transactions is by age (heartbeat). This is done at a regular interval and all transactions older than a configurable value (default is 3 hours) are discarded.
• • •
Missing some Tweet in this thread? You can try to
force a refresh