Revision · Transactions & the Mempool
This part introduced the verb of Ethereum. In Accounts & State we built the noun — a map from address to a mutable account record — and in State & the Merkle-Patricia Trie we saw that whole map committed by a single stateRoot. A root describes a world at rest. A transaction is the one and only thing that can move it: apply(state, tx) = state'. Everything else in Ethereum — validators, gossip, forks, the EVM — exists to order and apply these signed requests.
This page walks the whole part back in one breath, from the fields inside a transaction to the moment a proposer stamps it into a block. No new machinery — just the throughline, tightened: how do untrusting strangers agree who is allowed to change the shared world, and what does it cost them to do it? Read each section, then close the page and try to re-derive it before checking the Check your understanding questions at the end.
What a transaction is
Section titled “What a transaction is”A transaction is a precise, self-authorizing data structure. Precise, because every field is load-bearing and the network hashes the exact bytes; self-authorizing, because the transaction carries inside itself the cryptographic proof of who may spend from the sender’s account. You do not log in to Ethereum. You sign a message, and the signature is the authorization.
Only an externally-owned account (EOA) — an account controlled by a private key, with no code — can originate a transaction, because originating one requires a signature and only a key can sign. Contracts have code but no keys, so a contract never starts a transaction; it only runs as a consequence of an EOA’s transaction reaching it. That asymmetry is the whole reason the world computer has a single, guarded write path.
world state (a map) world state' ┌────────────────────┐ transaction ┌────────────────────┐ │ 0xAli → {nonce:7, │ (signed by Ali) │ 0xAli → {nonce:8, │ │ bal: 5.0} │ ───────────────► │ bal: 4.0} │ │ 0xBob → {nonce:2, │ │ 0xBob → {nonce:2, │ │ bal: 1.0} │ │ bal: 2.0} │ └────────────────────┘ └────────────────────┘ stateRoot: 0x9f… stateRoot: 0x3c…The fields — and the one that isn’t a field
Section titled “The fields — and the one that isn’t a field”Anatomy of a Transaction took the structure apart field by field. Each answers one question the network must resolve before it can apply the transaction:
nonce— which of the sender’s transactions is this? A per-account counter that must equal the sender’s current nonce. It pins the transaction to one moment in the account’s history.to— who receives it? An address for a transfer or call, or empty (nil) for a contract deployment.value— how much ether moves? Denominated in wei.data— what should run? The calldata: a function selector plus arguments for a call, or the init code for a deployment. Empty for a plain transfer.gasLimit— how much work will the sender allow? The ceiling on computation; exceed it and execution reverts but the gas is still spent.- fee fields — what will the sender pay per unit of gas? One price for legacy transactions, two caps (
maxFeePerGasandmaxPriorityFeePerGas) after EIP-1559. chainId— which chain is this for? Folded into the signature so a signature valid on one chain is meaningless on another.
And then the field that is conspicuously not in that list: the sender. There is no from field to write, and therefore nothing to forge. The sender’s address is recovered from the signature. You sign the transaction bytes with your private key; anyone verifying runs the recovery algorithm on the signature and the bytes and derives the one address that could have produced it. Our companion crate ethmini deliberately omits signatures and lets a plain from stand in for “the address a valid signature recovered to”:
/// A transaction: who sent it, the nonce it claims, and what it does.////// The `nonce` must equal the sender account's *current* nonce; applying the/// transaction bumps that nonce by one. Re-broadcasting the same bytes replays a/// stale nonce and is rejected. (`ethmini` doesn't model signatures — `from`/// stands in for "the address a valid signature recovered to".)pub struct Transaction { pub from: Address, pub nonce: u64, pub kind: TxKind, // Transfer, Deploy, or Call}Signing, the nonce, and two flavors of replay protection
Section titled “Signing, the nonce, and two flavors of replay protection”Signing & Replay Protection (EIP-155) covered how the signature is made and what it defends against. The private key signs a hash of the transaction’s bytes with ECDSA over the secp256k1 curve, producing three numbers: (v, r, s). r and s are the signature proper; v is the recovery id, the small hint that lets a verifier pick the one public key — and therefore the one address — that the signature resolves to. Recovery is what turns “a valid signature exists” into “and it belongs to exactly this account.”
Two distinct replay problems must be defeated, and the part named both:
- Replay within one chain is stopped by the nonce. Applying a transaction requires
tx.nonce == sender.nonce, and success bumps the sender’s nonce by one. So a transaction with nonce n is valid only when the account is exactly at n; afterward the account is at n+1 and those exact bytes can never apply again. This is the mechanismethminimodels directly. - Replay across different chains is stopped by EIP-155, which folds the chain id into the data that gets signed. Before EIP-155 (a 2016 hard fork), a transaction signed on Ethereum mainnet was byte-for-byte valid on any secp256k1 chain that shared the account — a real hazard once Ethereum and Ethereum Classic split. After it, the signature commits to a specific
chainId, so a mainnet transaction simply fails recovery-and-validation on a testnet or on Classic.
nonce → "usable once, in one order, on THIS chain" (in-chain replay) chainId → "valid on THIS chain only" (cross-chain replay) signature → "authorized by THIS account" (authorization)Together the nonce and the EIP-155 chain id give every transaction a single, unforgeable place in exactly one account’s history on exactly one chain.
The transaction types — each an answer to a cost problem
Section titled “The transaction types — each an answer to a cost problem”Transaction Types: Legacy to Blob showed that the transaction format is versioned. EIP-2718 introduced a typed-transaction envelope: a transaction begins with a single type byte, and the fields that follow are interpreted according to that type. This is what lets Ethereum add new transaction shapes without breaking the old ones — the type byte is a forward-compatible hinge. Crucially, each type exists to answer a specific cost or scaling problem, not for novelty:
| Type | EIP | The problem it answers |
|---|---|---|
| Legacy | — | The original format: one gasPrice, no type byte. First-price auction for blockspace, with all the guesswork that implies. |
| Access-list | EIP-2930 | Certain post-2019 gas repricings made cold storage access expensive; declaring the addresses and storage keys a transaction will touch up front makes those accesses cheaper and pricing predictable. Also restores compatibility for the typed envelope itself. |
| EIP-1559 | EIP-1559 | The first-price auction made fee estimation miserable and overpayment normal. 1559 splits the fee into a protocol-set base fee (burned) and a priority fee (tip to the proposer), so users bid a max they’ll tolerate and usually pay less. |
| Blob | EIP-4844 | Rollups posting data to L1 competed for the same expensive calldata as everyone else. Blob transactions attach large blobs of data in a separate, cheaper fee market that is pruned after ~18 days — enough for data-availability, without permanently bloating state. |
Read the column on the right as the story of the fee market maturing: from a blunt single-price auction, to predictable access costs, to a two-part fee that de-risks bidding, to a dedicated lane for the scaling workload that dominates modern block space. The format changed because the economics changed.
The mempool — a per-node waiting room, not a global queue
Section titled “The mempool — a per-node waiting room, not a global queue”The Mempool & Propagation corrected the most common beginner picture. There is no single global mempool. When you broadcast a signed transaction, a node validates it cheaply — signature recovers, nonce is plausible, fee clears the node’s floor, sender can afford it — and then gossips it to its peers, who validate and re-gossip. Each node keeps its own pending pool, so the “mempool” is really thousands of overlapping, slightly-different views of the same pending set, converging by propagation.
Because a node’s pool is finite, it must make three kinds of decision, and a proposer building a block leans on all three:
- Order. A proposer choosing which transactions to include generally sorts by what it earns — highest effective priority fee first — subject to each sender’s nonces being applied in ascending order. Ordering is a local, profit-driven choice, which is exactly where MEV (maximal extractable value) enters: the freedom to order and insert transactions is worth money, so it gets contested.
- Replace. A sender can resubmit a transaction with the same nonce but a higher fee to bump the original — the classic “speed up” or “cancel.” Nodes accept the replacement only if the fee jumps by a required margin (commonly ~10%), so replacement can’t be used to spam free rewrites.
- Evict. When the pool is full, low-fee or stale transactions are dropped to make room. A transaction can therefore silently disappear from the pending set without ever being included — it was never on-chain, so nothing needs undoing.
you ─broadcast─► node A ─gossip─► node B ─gossip─► node C ... │pool │pool │pool └── each node holds its OWN pending set ──┘
proposer building a block: ORDER by fee · REPLACE on higher-fee resubmit · EVICT the low/stale when fullThe lifecycle — submission to inclusion, and inclusion vs finality
Section titled “The lifecycle — submission to inclusion, and inclusion vs finality”Lifecycle: Submission to Inclusion put the whole path on one line and drew the sharp distinctions at its end. A transaction moves: construct → sign → broadcast → mempool → included → executed, and at any point before inclusion it can be replaced (higher-fee resubmit at the same nonce) or dropped (evicted, or invalidated by a lower nonce landing first).
Everything up to and including the mempool is off-chain and reversible: a pending transaction has changed nothing. The world state moves only when the transaction is included in a block and executed in order, producing a new stateRoot. That is the part’s single most important idea: submitted is not the same as confirmed.
But inclusion itself is not the end of the story, and the page drew the second line carefully:
- Inclusion means a proposer placed the transaction in a block and executed it. The state moved in that block.
- Finality means that block can no longer be reverted by the consensus process. On Ethereum’s proof-of-stake chain, a block is included the moment it is proposed, but it becomes finalized only after two epochs of attestations (~13 minutes under normal conditions, as of 2024). Before finality, a proposed block can still be reorganized away, taking your “included” transaction back into the pending set.
So the honest mental model has three states, not two: pending (in a mempool, changed nothing), included (in a block, state moved but reversible), and finalized (economically irreversible). A wallet that shows “confirmed” after one block is showing you inclusion, not finality — which is why exchanges and bridges wait for several blocks, and why the depth they wait for is a risk decision, not a UI detail.
pending ──include──► included ──finalize──► finalized (mempool, (in a block, (irreversible; nothing state moved but ~2 epochs of changed) reorg-able) attestations)The cost lens, restated
Section titled “The cost lens, restated”Zoom out to the thread that ran through every page: every transaction is a bid for scarce blockspace, metered in gas. A block can hold only so many bytes and only so much execution, and a world that thousands of strangers must independently recompute cannot be free to spam, or it collapses. So each operation the network performs on your behalf has a fixed gas price; you attach a gasLimit and a fee, and you pay gas_used × price for the privilege of moving shared state.
Every design decision in this part is downstream of that scarcity:
- The nonce rations ordering — one slot per account per step, so a spammer can’t flood a single account’s history.
- The typed transaction formats are successive attempts to price blockspace better — predictably (access lists), fairly and with less overpayment (EIP-1559’s base + tip), and cheaply enough for rollups (blobs’ separate market).
- The mempool’s ordering, replacement, and eviction are all fee-driven: the pending set is a live auction, and a proposer is a rational bidder-picker maximizing what a scarce block earns.
- Inclusion vs finality is itself a cost: waiting for finality trades latency for certainty, and how much you wait is priced by how much a reversal would cost you.
That is the answer to the book’s question, made concrete in a single object. The agreement half: a transaction is self-authorizing, so any stranger can verify who is allowed to make a change — the signature recovers to one address, the nonce pins it to one moment, the chain id pins it to one chain — without trusting anyone. The cost half: a transaction is a bid for scarce blockspace, metered in gas, so the shared world stays too expensive to spam and too cheap to monopolize. Every later part — consensus, the EVM, gas, scaling — is about ordering, executing, or cheapening these signed requests. The transaction is the atom of the whole system.
Next, Gas & Fees opens up the pricing machinery this part only named — the base fee, the burn, and how the network sets the price of blockspace block by block.
Check your understanding
Section titled “Check your understanding”- A transaction has a
nonce,to,value,data,gasLimit, fee fields, and achainId— but nofromfield. Where does the sender’s address come from, and why does the absence of afromfield mean there is nothing to forge? - The nonce and the EIP-155 chain id each defeat a different kind of replay. Name both kinds, and explain which mechanism stops which — and why a transaction signed before EIP-155 was a hazard once Ethereum and Ethereum Classic split.
- Match each transaction type — legacy, EIP-2930 access-list, EIP-1559, EIP-4844 blob — to the specific cost or scaling problem it was introduced to solve.
- There is no single global mempool. Describe what actually exists, and name the three decisions a node makes about its pending pool that a proposer relies on when building a block.
- Distinguish pending, included, and finalized. Why is “submitted is not confirmed” the most important idea in this part, and why do exchanges wait past the first block even though the state has already moved?
Show answers
- The sender’s address is recovered from the signature: the key signs a hash of the transaction bytes, and a verifier runs the recovery algorithm on
(v, r, s)and the bytes to derive the one address that could have produced that signature. Because there is nofromfield to write, there is nothing to forge — an attacker can’t claim to be someone else; they’d have to produce a signature that recovers to that someone, which requires the private key. - In-chain replay (re-broadcasting the same bytes to apply twice) is stopped by the nonce: a transaction is valid only when
tx.nonce == sender.nonce, and applying it bumps the nonce, so those exact bytes can never apply again. Cross-chain replay (a signature valid on chain A being replayed on chain B) is stopped by EIP-155, which folds thechainIdinto the signed data so the signature commits to one chain. Before EIP-155, a signature carried no chain id, so a transaction signed on mainnet was byte-for-byte valid on Ethereum Classic (which shared the same accounts after the fork) — anyone could replay your mainnet transaction on Classic and move your funds there. - Legacy — the original single-
gasPriceformat (a plain first-price auction). EIP-2930 access-list — declares the addresses and storage slots a transaction will touch up front, making otherwise-expensive cold accesses cheaper and pricing predictable (and restoring compatibility under the typed-transaction envelope). EIP-1559 — replaces the guesswork of a first-price auction with a protocol-set base fee (burned) plus a priority tip, so users bid a max and usually overpay less. EIP-4844 blob — gives rollups a separate, cheaper fee market for large data blobs that are pruned after ~18 days, so L2 data-availability doesn’t compete for expensive calldata or permanently bloat state. - What exists is a per-node pending pool: when you broadcast, a node validates the transaction and gossips it to peers, who re-validate and re-gossip, so the “mempool” is thousands of overlapping, slightly different views converging by propagation — not one global queue. A node’s three decisions are: order (which pending transactions to include and in what sequence — generally highest effective fee first, respecting per-sender nonce order, which is where MEV enters), replace (accept a same-nonce resubmit only if the fee jumps by a required margin), and evict (drop low-fee or stale transactions when the pool is full).
- Pending = sitting in a mempool, having changed nothing and still droppable or replaceable. Included = placed in a block and executed, so state moved — but the block can still be reorganized away before finality. Finalized = the block is economically irreversible (on PoS, after ~two epochs of attestations, ~13 minutes as of 2024). “Submitted is not confirmed” matters because a pending transaction has moved nothing — treating a broadcast as done invites double-spend and lost-funds mistakes. Exchanges wait past the first block because inclusion is not finality: a reorg can pull an included transaction back into the pending set, so they wait for enough depth that a reversal is economically implausible — a risk decision, not a UI detail.