Lifecycle: Submission to Inclusion
Every earlier page in this part handed you one part of a transaction. Anatomy gave you the fields. Signing & Replay Protection turned those fields into a self-authorizing object whose sender is recovered, not written. Transaction Types wrapped it in a versioned envelope. The Mempool & Propagation put it in the adversarial waiting room between broadcast and inclusion.
This page threads all of that into a single story: the life of one transaction, from the moment a wallet fills in the first field to the moment the change it made can no longer be undone. Along the way we ask the same question at every stage — is this transaction still valid, and who is checking? — because the honest answer changes as the transaction moves, and every place the answer can turn to “no” is a failure mode you can name.
The frame to hold, restated from the part overview: submitted is not confirmed, and confirmed is not final. A transaction can be perfectly signed, sitting in a thousand mempools, and still have changed nothing. It can be executed in a block and still be undone. This page makes both of those precise.
The path, on one page
Section titled “The path, on one page”Here is the whole journey. Read it top to bottom; the rest of the page is a walk down these stops.
WALLET NODE / RPC P2P NETWORK ────── ────────── ─────────── construct fields ──► eth_sendRawTransaction ──► gossip to peers sign (key → v,r,s) cheap validity checks each peer re-checks │ │ │ │ reject early mempool │ (bad sig, low fee, (pending / queued) │ bad nonce) │ ▼ ▼ a signed blob PROPOSER for the slot the sender can ── selects txs (fee order) no longer alter ── executes each in order ── computes new stateRoot │ ▼ tx in a BLOCK ──► receipt │ (status, ▼ gasUsed, confirmations logs) → justified → FINALIZED (irreversible)The single most useful thing to notice: nothing to the left of the proposer changes the world state. Construction, signing, broadcast, and every second spent in the mempool are off-chain and reversible. The world moves only when a proposer executes the transaction into a block — and even then, it is not yet permanent.
Stage 1 — Construct
Section titled “Stage 1 — Construct”A wallet builds the transaction by filling in the fields from Anatomy: the nonce (fetched as the sender’s current account nonce), the to address, the value, any data, the gas fields (gasLimit, and for a modern EIP-1559 transaction maxFeePerGas and maxPriorityFeePerGas), and the chainId.
Two of these are estimates the wallet has to guess right, because getting them wrong is a failure mode later:
gasLimit— the wallet simulates the transaction against a recent state to estimate how much gas execution will consume, then adds a margin. Guess too low and the transaction runs out of gas mid-execution (Stage 5). Guess too high and you have merely reserved blockspace you won’t use — you are only charged for gas actually consumed, so overestimating wastes nothing but the room it reserves in the block.- Fee fields — the wallet reads the current base fee and picks a
maxFeePerGasand priority tip. Guess too low and the transaction is underpriced: no proposer wants it, and it sits in the mempool (Stage 4).
Construction is entirely local. Nothing has happened on-chain; you have a struct in memory.
Stage 2 — Sign
Section titled “Stage 2 — Sign”The wallet hashes the transaction’s canonical bytes and signs that hash with the sender’s private key, producing the (v, r, s) signature covered in Signing & Replay Protection. Post-EIP-155, the chainId is folded into the signature, so a signature valid on mainnet does not verify on a testnet — replay protection across chains.
After signing, the transaction is frozen. Change any field — bump the value by one wei — and the signature no longer verifies, so the sender address recovered from it changes (or recovery fails outright). This is why the sender cannot be forged: there is no from field to tamper with, only a signature that resolves to exactly one address. Our companion crate ethmini skips the cryptography and lets from stand in for “the address a valid signature recovered to,” but the shape of the transaction it validates is exactly what a real node receives after recovery:
/// A transaction: who sent it, the nonce it claims, and what it does./// (`ethmini` omits signatures — `from` stands in for the recovered sender.)pub struct Transaction { pub from: Address, pub nonce: u64, pub kind: TxKind, // Transfer, Deploy, or Call}Stage 3 — Broadcast, and the first validity checks
Section titled “Stage 3 — Broadcast, and the first validity checks”The wallet submits the signed bytes to an Ethereum node, typically over JSON-RPC as eth_sendRawTransaction. The node does not just accept it. Admitting a transaction to the mempool means committing to store it and gossip it to peers, so the node runs a battery of cheap checks first — cheap meaning “checkable without executing the transaction against full state.” Fail any and the node rejects the submission immediately, before it ever reaches the mempool.
eth_sendRawTransaction(signed bytes) │ ├─ signature recovers to a valid address? no ─► reject ├─ chainId matches this node's chain? no ─► reject ├─ nonce ≥ sender's current account nonce? no ─► reject (stale) ├─ balance ≥ value + gasLimit × maxFeePerGas? no ─► reject (can't afford) ├─ gasLimit ≤ block gas limit? no ─► reject (too big) ├─ intrinsic gas ≤ gasLimit? no ─► reject └─ fee ≥ node's minimum / mempool floor? no ─► reject (underpriced) │ ▼ admitted to the mempoolTwo of these deserve care, because they are exactly the balance and nonce checks the state machine will enforce again at execution:
-
Affordability. The node checks the sender can cover the worst case: the full
valuemoved plus the entire gas budget priced at the cap,value + gasLimit × maxFeePerGas. Not the value alone — the sender must be able to pay for all the gas it might use, even though it will only be charged for what it does use. This is stricter than the up-front check inethmini, which (having no gas market) only compares balance against value:let value = tx.kind.value();if sender.balance < value {return Err(EthError::InsufficientBalance {addr: tx.from, balance: sender.balance, needed: value,});}Real Ethereum’s
neededisvalue + max gas cost; the idea — reject an unaffordable transaction before touching any state — is identical. -
Nonce. At broadcast time the node is lenient: it accepts a transaction whose nonce is the sender’s current nonce (ready to execute) or a small amount higher (a queued transaction, waiting for the gap to fill). It rejects a nonce that is already below the current nonce — that is a stale replay, and it can never become valid again.
Note what is not checked here: whether the transaction will succeed or revert. That requires execution, which the node does not do at broadcast. A transaction that is guaranteed to revert is still perfectly valid to admit and gossip — reverting is an inclusion outcome, not a rejection.
Stage 4 — The mempool: pending, queued, or stuck
Section titled “Stage 4 — The mempool: pending, queued, or stuck”Once admitted, the transaction gossips across the p2p network (Mempool & Propagation) and lands in every peer’s mempool. The mempool is not one global structure — it is thousands of independent, slightly-different copies. Each node sorts its copy into two buckets:
- Pending — the nonce is exactly the sender’s next nonce, so the transaction is executable right now. These are the transactions a proposer can actually include.
- Queued — the nonce is ahead of the sender’s next nonce; there is a gap. The transaction is valid but not yet executable, because a lower-nonce transaction from the same sender hasn’t arrived. It waits for the gap to fill and then graduates to pending.
This is where two failure modes trap a transaction indefinitely:
Failure mode: underpriced (stuck in the mempool)
Section titled “Failure mode: underpriced (stuck in the mempool)”If the transaction’s fee is too low relative to the current base fee and the tips other transactions are offering, no rational proposer will include it — including it earns less than including someone else’s. The transaction is valid; it simply loses the auction every slot. It sits in the pending pool until either the base fee falls enough that it becomes competitive, or the node evicts it to make room (mempools are bounded). Nothing has changed on-chain; the sender’s nonce has not moved.
The fix is not to wait — it is to replace the transaction: broadcast a new transaction with the same nonce and a higher fee (a “speed-up”). Nodes accept a replacement only if the fee bump clears a minimum (commonly ~10%), which prevents free spam via endless cheap re-broadcasts.
Failure mode: nonce gap (queued forever)
Section titled “Failure mode: nonce gap (queued forever)”Suppose the sender’s next nonce is 7, and they broadcast nonce 8 — perhaps nonce 7 was underpriced and dropped, or never sent. Nonce 8 is valid but sits in the queued bucket behind the gap. It can never be included until a transaction with nonce 7 lands. The sender must fill the gap: broadcast the missing nonce 7 (even a do-nothing self-transfer works) and the queued transaction graduates to pending.
Stage 5 — Selection and execution by the proposer
Section titled “Stage 5 — Selection and execution by the proposer”For each slot, Ethereum’s consensus layer selects one validator as the proposer for that slot (proposer selection is a consensus mechanism covered in the consensus part; here we only need that exactly one validator builds the block). The proposer — or, in practice today, a specialized block builder feeding it — pulls executable (pending) transactions from the mempool and orders them, typically to maximize fee revenue: highest effective tip first, subject to keeping each sender’s nonces in ascending order.
Ordering is where the adversarial behavior of the mempool becomes real value: because the proposer chooses the order, it can extract MEV — front-running, back-running, sandwiching — introduced in the previous page. Ordering is not neutral.
Once ordered, the proposer executes each transaction against the current world state, in sequence. Execution is the state-transition function — apply(state, tx) = state' — and it re-runs every validity check from Stage 3, now authoritatively and against the exact state at this point in the block, not a cheap estimate. This is the second, binding place the balance and nonce are checked. In ethmini, this is WorldState::apply:
pub fn apply(&mut self, tx: &Transaction) -> Result<Receipt> { // Re-validate against the *actual* state, in order: let sender = self.accounts.get(&tx.from) .ok_or(EthError::UnknownSender(tx.from))?; if sender.nonce != tx.nonce { /* NonceMismatch → reject */ } if sender.balance < tx.kind.value() { /* InsufficientBalance → reject */ }
// Valid to include. Snapshot in case execution reverts. let snapshot = self.accounts.clone(); self.account_mut(tx.from).nonce += 1; // consumed no matter what
match self.execute(tx) { Ok(receipt) => Ok(receipt), // Success Err(reverted) => { /* roll back state, keep the nonce bump */ } }}Notice the pivotal line: self.account_mut(tx.from).nonce += 1 runs before execution and survives even if execution fails. That is the crux of the next failure mode.
Failure mode: out-of-gas (reverts, but still charged)
Section titled “Failure mode: out-of-gas (reverts, but still charged)”The proposer runs the transaction with the gas meter set to the sender’s gasLimit. If execution needs more gas than the limit allows, the EVM raises an exceptional halt — out of gas — and the transaction reverts: every state change it made is rolled back to the pre-transaction snapshot. But — and this is the part that surprises people — the transaction is still included in the block, the sender’s nonce is still consumed, and the sender is still charged for the gas the failed execution burned. ethmini models exactly this distinction between a rejection (state untouched) and a revert (included, nonce spent, effects undone):
/// A reverted tx is still *included*: its nonce is consumed (so it can't be/// replayed) even though its state changes were undone. Only an EthError —/// a rejection — leaves the world completely unmoved.pub enum ExecStatus { Success, Reverted }The reason is economic. A proposer spent real computation running your transaction. If a failing transaction cost the sender nothing, spamming guaranteed-to-fail transactions would be a free denial-of-service on every node. So you pay for the work regardless of the outcome. Out-of-gas is the most common cause of a revert, but any exceptional halt — a stack underflow, an invalid jump, an assertion in the contract — reverts the same way.
The distinction to keep straight:
REJECTED (before execution) REVERTED (during execution) ─────────────────────────── ─────────────────────────── wrong nonce, can't afford, out-of-gas, contract threw, bad signature require()/assert() failed │ │ never enters a block IS in the block nonce NOT consumed nonce consumed no gas charged gas charged (for work done) world untouched state changes rolled backStage 6 — Inclusion produces a receipt
Section titled “Stage 6 — Inclusion produces a receipt”When a transaction is executed into a block, the network produces a receipt for it — the checkable record of what happened. A receipt is not the transaction; it is the result of running the transaction. Its core fields:
status—1for success,0for revert. This single bit tells you whether the transaction’s effects stuck or were rolled back.gasUsed— the gas actually consumed (and therefore what you paid for, times the effective gas price). Always ≤ yourgasLimit.logs— the events the contract emitted during execution (a Solidityeventbecomes a log). Logs are how off-chain systems — indexers, wallets, dashboards — learn what happened without re-executing the transaction. Reverted transactions produce no logs, because their effects, including their events, are rolled back.contractAddress— for a contract-creation transaction, the address of the new contract (recallethmini’sReceipt { created: Option<Address> }).
ethmini’s receipt is the same idea in miniature:
pub struct Receipt { pub status: ExecStatus, // Success | Reverted pub gas_used: u64, pub return_value: Option<u64>, // what a RETURN produced pub created: Option<Address>, // new contract address, for a Deploy}A crucial subtlety: status: 0 (revert) is a successful inclusion. The transaction did its job of being processed; its effect was to change nothing (beyond the nonce and the gas paid). “Did the transaction get mined?” and “did the transaction succeed?” are two different questions, answered by two different things — inclusion in a block, and the receipt’s status bit.
Stage 7 — Inclusion is not finality: confirmations
Section titled “Stage 7 — Inclusion is not finality: confirmations”Your transaction is in a block. Is it safe to consider the change permanent? Not yet. A block sits at the tip of the chain, and the tip can be reorganized (“reorged”): if a competing block at the same height wins, blocks on the losing branch are discarded — and any transaction that lived only in a discarded block is un-included. It returns to a pending state as if it were never mined. Its effects vanish; its nonce is freed again.
This is why “how many confirmations?” is a real question. A confirmation is a block built on top of the block containing your transaction. The more blocks stacked on top, the more work an attacker (or an honest reorg) must discard to undo yours, so the exponentially less likely it is to be reverted.
Ethereum’s proof-of-stake consensus adds something stronger than “probably safe”: explicit, checkpointed finality.
included → in the latest block; reversible by a reorg │ justified → a supermajority of stake has voted for the checkpoint │ FINALIZED → two checkpoints justified in a row; reverting it would require destroying ≥ 1/3 of all staked ETHUnder normal conditions, finality lands roughly two epochs after inclusion — about 13 minutes on mainnet as of 2024 (an epoch is 32 slots × 12 seconds ≈ 6.4 minutes). Before finalization, a transaction is included but not irreversible. After finalization, reverting it is not “unlikely” — it is economically catastrophic by construction, because the protocol would slash (burn) the stake of validators who tried, requiring an attacker to forfeit at least a third of all staked ETH. That is the sharpest form of the book’s throughline: untrusting strangers agree the change is permanent not because they trust each other, but because undoing it would cost the attacker more than the whole system is worth.
The practical rule most applications use: treat small-value transfers as good after a handful of confirmations, but wait for finalization before treating a high-value transaction (a large exchange withdrawal, a bridge transfer) as irreversible.
The architect’s lens
Section titled “The architect’s lens”The transaction lifecycle is the load-bearing process that connects a private key to a permanent change in shared state. Seen at altitude:
- Why does it exist? Because a signed request cannot change a shared world instantly — it must be validated, propagated, ordered, executed, and agreed upon by untrusting strangers before it can be trusted as permanent. Each stage is one of those verbs.
- What problem does it solve? It lets anyone submit a state change to a machine no one controls, and lets everyone else verify — without trust — both who authorized it (the signature) and whether it can still be undone (its depth toward finality).
- What are the trade-offs? Safety costs latency. Inclusion is seconds; irreversibility is minutes. The gap between “mined” and “finalized” is the price of a system where no single party can force a state change or freeze one out.
- When should I avoid relying on it? Never treat a transaction as done at submission, and don’t treat inclusion as finality for high-value actions — wait for the finalized checkpoint. For sub-minute UX, use optimistic UI that shows the pending state but doesn’t act on it as settled.
- What breaks if I remove a stage? Drop validation and the mempool is a spam sink; drop the mempool and there is no fee market or public ordering; drop ordering and execution is undefined; drop finality and every confirmed transaction is forever only probably real. The lifecycle is the minimum path from “I signed this” to “the world agrees this happened.”
This page closes the part: you can now trace a transaction from a wallet’s first field to an irreversible change in the world state, and name the check that guards each step and the failure that can trap it. Consolidate it in the part revision, then continue to the parts that order these transactions (consensus) and execute them (the EVM and gas).
Check your understanding
Section titled “Check your understanding”- A node runs several checks before admitting a transaction to its mempool at broadcast time, but it does not check one important thing that only gets decided later. What does the broadcast-time check not verify, and why not?
- Explain the affordability check precisely. Against what quantity is the sender’s balance compared at broadcast — and why is it stricter than “balance ≥ value”?
- A transaction is underpriced and a different one has a nonce gap. For each, say which mempool bucket it lands in, why it isn’t getting included, and the concrete action that unsticks it.
- Walk through what happens to state, nonce, gas, and logs when a transaction runs out of gas. Contrast every one of those with what happens when a transaction is rejected for a wrong nonce.
- Distinguish included, confirmed, and finalized. Which of these can still be reversed, what reverses it, and what does Ethereum’s proof-of-stake add that makes finalization qualitatively different from “many confirmations”?
Show answers
- It does not check whether the transaction will succeed or revert, because determining that requires executing the transaction against full state, which the node does not do at broadcast (the checks are deliberately cheap — signature, chainId, nonce, affordability, gas limits). A transaction guaranteed to revert is still valid to admit and gossip; reverting is an inclusion outcome, decided by the proposer at execution, not a rejection.
- The sender’s balance must be ≥
value + gasLimit × maxFeePerGas— the value being moved plus the entire gas budget priced at the fee cap. It is stricter than “balance ≥ value” because the sender must be able to pay for all the gas it might consume in the worst case, even though it will only be charged for the gas it actually uses. Checking only value would admit transactions that can move the money but not pay for the computation. - Underpriced → lands in the pending bucket (its nonce is executable), but it loses the fee auction every slot because a proposer earns more by including others; unstick it by replacing it — same nonce, higher fee (a bump above the ~10% minimum). Nonce gap → lands in the queued bucket (its nonce is ahead of the account’s next nonce), and can never be included until the missing lower nonce arrives; unstick it by broadcasting the missing nonce so the queued transaction graduates to pending.
- Out of gas: the transaction is included in the block; execution hits an exceptional halt and reverts, so all state changes are rolled back to the pre-transaction snapshot; the sender’s nonce is consumed; the sender is charged for the gas the failed run burned; and no logs survive (events are rolled back with everything else). Rejected for wrong nonce: the transaction is never included; the nonce is not consumed; no gas is charged; the world state is completely untouched; and there are no logs because it never executed. Revert = included-but-undone; rejection = never happened.
- Included = in the latest block at the chain tip; confirmed = some number of blocks are stacked on top; finalized = two consecutive checkpoints have been justified by a supermajority of stake. Both included and lightly-confirmed transactions can be reversed by a reorg — a competing branch winning discards the block and un-includes the transaction, freeing its nonce and erasing its effects. Finalization is qualitatively different: reverting a finalized block would require validators to be slashed, forfeiting at least one-third of all staked ETH — so it is not merely improbable (more confirmations = exponentially less likely) but economically prohibitive by construction.