Anatomy of a Transaction
The part overview made the case that a transaction is the only way the world state ever changes: no signed transaction, no new state. That reframes the whole throughline of this book — how do untrusting strangers agree on the state of a shared world computer, and what does it cost to run one? — into something you can hold in your hand. A transaction is the message a stranger broadcasts, and the entire network’s job is to agree on whether to apply it and in what order.
So before we can talk about mempools, ordering, or fees, we need to open the envelope and name every field inside it. This page does exactly that: it walks each field of a transaction, explains what it controls, and shows why each one has to be there. Two ideas will recur — that the sender is never stated but recovered, and that two of the fields are a promise about cost. Both are load-bearing for everything after.
The nine things a sender signs
Section titled “The nine things a sender signs”A classic (“legacy”) Ethereum transaction is, at bottom, nine values. Later pages cover newer transaction types that reshape the fee fields, but the legacy layout is the clearest place to learn the anatomy, because every field is right there in the open:
┌──────────────────────────────────────────────────────────┐ │ nonce per-sender counter: which tx in my sequence │ │ gasPrice wei I pay per unit of gas │ │ gasLimit max units of gas this tx may consume │ │ to recipient address (empty ⇒ contract creation) │ │ value wei to transfer to `to` │ │ data calldata / init code (arbitrary bytes) │ ├──────────────────────────────────────────────────────────┤ │ v, r, s the ECDSA signature over the six fields above │ └──────────────────────────────────────────────────────────┘The top six are the intent — what the sender wants done. The bottom three, v/r/s, are the authorization — the cryptographic proof that the owner of an account actually asked for it. Split the envelope that way and the rest of the page is just naming each line.
nonce — my place in my own sequence
Section titled “nonce — my place in my own sequence”The nonce is a per-sender counter, starting at 0, that must equal the sender account’s current nonce for the transaction to be valid. Applying the transaction bumps that stored nonce by one. It answers a question every ledger must answer: which of my transactions is this, and in what order do mine apply?
This one field does three jobs at once:
- Ordering. Your transactions apply strictly in nonce order — nonce 5 can never be included before nonce 4. The network can’t run your withdrawal before your deposit if you numbered them that way.
- Replay protection. A signed transaction names one exact nonce. Once it’s mined, your stored nonce has moved past it, so re-broadcasting the identical bytes names a stale nonce and is rejected. You cannot be charged twice for one signed message. (Signing & replay protection takes this further to cross-chain replay.)
- Determinism for contract creation. As you’ll see below, a new contract’s address is derived partly from the creator’s nonce — so the nonce also fixes where a deployment lands.
The book’s companion crate, ethmini, models this exactly. Validation refuses any transaction whose claimed nonce doesn’t match the stored one, and the nonce is consumed even by a transaction that reverts:
// ethmini/src/state.rs — inside WorldState::applyif sender.nonce != tx.nonce { return Err(EthError::NonceMismatch { addr: tx.from, expected: sender.nonce, got: tx.nonce, });}// ...later, once the tx is valid to include:self.account_mut(tx.from).nonce += 1; // consumed no matter what happens nextThat “no matter what happens next” is the whole point of a nonce: it is what makes a message un-replayable the instant it’s included.
to — the recipient, or nobody
Section titled “to — the recipient, or nobody”The to field is a 20-byte address: where the transaction is directed. It has two modes, and the difference is stark:
tois set → sendvalueto that address, and if the address holds contract code, run that code withdataas its input.tois empty (the zero-length address) → this is a contract creation. There is no recipient because the recipient doesn’t exist yet; the transaction’s job is to bring one into being.
That second mode is easy to miss and worth stating plainly: an empty to is the signal for “deploy a contract.” Nothing else marks a deployment. The address of the new contract is then computed deterministically from the creator and the creator’s nonce — the same nonce field from above:
// ethmini/src/state.rs — new contract address from creator + nonce// Real Ethereum: keccak256(rlp([sender, nonce]))[12:]pub fn contract_address(creator: Address, nonce: u64) -> Address { /* ... */ }So the same account deploying twice gets two different addresses, and anyone can predict where a deployment will land before it’s mined. The accounts & state part explains why Ethereum has addressable accounts at all; here the point is narrower: to decides between “touch an account” and “create one.”
value — the native currency being moved
Section titled “value — the native currency being moved”value is the amount of ether to transfer to to, denominated in wei, the smallest unit: 1 ether = 10^18 wei. Ethereum tracks value as a 256-bit integer of wei precisely so it never needs fractions — everything is an exact whole number of the tiniest unit.
value can be zero. A transaction that only calls a contract — say, casting a vote or approving a token — often moves no ether at all; its whole effect lives in data. And value is independent of the fee: paying for gas is a separate charge (below), not carved out of value.
data — calldata, or init code
Section titled “data — calldata, or init code”data is an arbitrary byte string, and its meaning depends entirely on to:
- When
tois a contract,datais calldata — the input to the contract’s code. Its first four bytes conventionally select which function to run; the rest are the arguments. A plain ether send to a wallet leavesdataempty. - When
tois empty (a deployment),datais the init code — a small program that runs once at creation and returns the runtime bytecode that becomes the contract’s permanent code.
So data is where all the programmability lives. value moves money; data moves meaning. The smart contracts part is entirely about what those bytes can express.
to = 0xC0ffee… , data = 0xa9059cbb… → call transfer() on a token contract to = (empty) , data = 0x60806040… → deploy: init code returns runtime code to = 0xAlice… , data = (empty) → plain ether transfer, no code runsThe two fields that price the work
Section titled “The two fields that price the work”Everything above is what to do. The next two fields answer the book’s second question head-on — what does it cost? Running a transaction is real work on thousands of machines, and the network will only do it if the sender pays. That payment is metered in gas.
gasLimit — the ceiling on work
Section titled “gasLimit — the ceiling on work”gasLimit is the maximum number of gas units the transaction is allowed to consume. Every operation the transaction triggers — arithmetic, storage writes, calling other contracts — costs a fixed number of gas units, and the meter counts down from the limit. Two outcomes, and only two:
- The transaction finishes under budget. It consumes some
gas_used ≤ gas_limit, and the unused gas is refunded to the sender. - The transaction hits the limit first. It halts with an out-of-gas error, all its state changes are reverted — but the gas is still spent. The work was done; someone has to pay for it.
That asymmetry is deliberate and important: you are refunded for work you didn’t cause, but you are charged for work the network did, even if the result was thrown away. gasLimit is your protection against a buggy or malicious contract draining your account — it caps your maximum exposure before you sign.
The ethmini VM makes the out-of-gas ending concrete: an exceptional halt burns the whole budget and reverts, while a clean finish reports only the gas actually used.
// ethmini/src/state.rs — a contract call that overruns its budgetErr(reason) => Err(Reverted { gas_used: *gas_limit, // an exceptional halt burns the whole gas budget reason,}),gasPrice — what you pay per unit
Section titled “gasPrice — what you pay per unit”gasLimit bounds how much gas can be spent; gasPrice sets how much each unit costs, in wei per gas. The total fee is simply:
fee (wei) = gas_used × gas_price max fee = gas_limit × gas_price ← the most you could ever be chargedBecause gas is scarce blockspace, gasPrice is really a bid in an auction for inclusion: pay more per gas and a block producer is more inclined to include you sooner. On modern Ethereum this single field is split into a maxFeePerGas (the ceiling you’ll tolerate) and a maxPriorityFeePerGas (the tip on top of the protocol’s base fee) — a mechanism from EIP-1559 that transaction types covers in full. The gas & fees part explains the auction itself. For anatomy, the takeaway is fixed: one field bounds the work, another prices it, and together they cap your worst-case spend before you ever sign.
There is no from field
Section titled “There is no from field”Look back at the nine values: from is not one of them. This surprises almost everyone. If a transaction doesn’t state who sent it, how does the network know whose account to debit?
The answer is the second half of the envelope. The sender is not declared — it is recovered from the signature.
Under the hood — v, r, s, and recovering the sender
Section titled “Under the hood — v, r, s, and recovering the sender”The last three fields — v, r, and s — are the components of an ECDSA signature over the first six fields. Ethereum uses the secp256k1 curve (the same one Bitcoin uses). To send a transaction you:
- Serialize the six intent fields (in Ethereum, via RLP encoding) and hash them.
- Sign that hash with your account’s private key, producing two numbers
rands. - Append a small recovery id
vthat tells verifiers which of the possible public keys the signature corresponds to.
The magic of ECDSA is that from (hash, v, r, s) alone, anyone can run public-key recovery to compute the one public key that must have produced that signature — and from the public key, the 20-byte address. That recovered address is the sender. No separate from field is needed, and none could be trusted anyway: a claimed from would just be a value anyone could type, whereas a recovered from is a value only the private-key holder could have produced.
sign: privkey + hash(tx fields) ──▶ (v, r, s) recover: hash(tx fields) + (v, r, s) ──▶ public key ──▶ address = `from`This is why the companion crate carries a from field on its Transaction but the doc comment is careful to say it “stands in for the address a valid signature recovered to” — ethmini skips the elliptic-curve math to focus on the state machine, but the real transaction has no from at all:
pub struct Transaction { pub from: Address, // stands in for "the address a valid signature recovered to" pub nonce: u64, pub kind: TxKind,}Two consequences fall straight out of “the sender is recovered, not stated”:
- You cannot forge a transaction from someone else’s account. Without their private key you cannot produce a
(v, r, s)that recovers to their address. Authorization is the signature. - Tampering invalidates the signature. Change any of the six intent fields after signing and the hash changes, so the recovered address changes — it no longer matches an account you control, and the transaction is worthless. The signature binds the whole intent together.
The v field carries one more secret: it also encodes the chain id, which is how a signature made for Ethereum mainnet can’t be replayed on another chain. That’s EIP-155, and it’s the entire subject of the next page.
The architect’s lens
Section titled “The architect’s lens”- Why does it exist? A world computer changes state only in response to authorized instructions. The transaction is that instruction — a self-contained, signed message that says what to do, how much work to permit, and proves who asked. Without a rigid, verifiable format, untrusting strangers would have no common object to agree on.
- What problem does it solve? It packages three separable concerns — intent (
to/value/data), cost bounds (gasLimit/gasPrice), and authorization (v/r/s) — into one atomic unit that any node can validate independently, with no trust in the sender and no shared secret. - What are the trade-offs? The rigidity is the cost. A fixed field layout is easy to verify but hard to evolve: adding capabilities (new fee markets, blob data) requires whole new transaction types rather than a free-form schema. And the nonce’s strict ordering, while it gives clean replay protection, means one stuck low-nonce transaction can block every later one from the same account.
- When should I avoid it? Never, if you want to change L1 state — it’s the only door. But you avoid paying for a distinct transaction whenever cheaper structure exists: batching many actions into one contract call, or moving activity to an L2 where transactions are cheap and settled to L1 in bulk.
- What breaks if I remove it? Remove the signature and anyone could spend anyone’s balance — the ledger has no notion of ownership. Remove the nonce and every transaction becomes replayable and unordered. Remove the gas fields and a single infinite loop halts the whole network. Each field is load-bearing; drop one and a different guarantee collapses.
Check your understanding
Section titled “Check your understanding”- A legacy Ethereum transaction has no
fromfield. How does the network determine which account to debit, and why is a recovered sender more trustworthy than a stated one? - Name the three distinct jobs the
noncefield performs. What happens if you broadcast a transaction whose nonce is lower than your account’s current nonce? - Your transaction runs out of gas halfway through. What happens to your state changes, and what happens to the gas you’d committed? Why is that asymmetry deliberate?
- What single field distinguishes a contract creation from an ordinary call, and what does the
datafield mean in each of those two cases? - A plain ether transfer with empty
datacosts 21,000 gas. Where does that floor come from, and how would adding 50 non-zero calldata bytes change the intrinsic cost (using the 2024 rule of 16 gas per non-zero byte)?
Show answers
- The transaction carries an ECDSA signature (
v,r,s) over its six intent fields. Anyone can run public-key recovery on(hash, v, r, s)to obtain the single public key — and thus the 20-byte address — that produced the signature; that recovered address is the sender. A recovered sender is trustworthy because only the private-key holder could have produced a signature that recovers to their address, whereas a statedfromwould be a value anyone could type. - (a) Ordering — your transactions apply in strict ascending nonce order; (b) replay protection — once mined, your stored nonce moves past that value, so re-broadcasting the same bytes names a stale nonce and is rejected; (c) deterministic contract addresses — a deployment’s address is derived from the creator and the creator’s nonce. A transaction with a nonce below the account’s current nonce is rejected as a stale/replayed transaction and is not included.
- All of the transaction’s state changes are reverted (out-of-gas reverts). But the gas consumed up to the halt is still spent — it is not refunded. The asymmetry is deliberate: the network genuinely did the work up to the halt, and someone must pay for that burden on every node; you are refunded only for work you did not cause. Unused gas from a successful transaction, by contrast, is refunded.
- An empty
tofield signals contract creation; a settois an ordinary call/transfer. Whentois a contract,datais calldata (input to the contract’s code, function selector plus arguments). Whentois empty,datais the init code — a program that runs once and returns the runtime bytecode of the new contract. - The 21,000-gas floor is intrinsic gas: the fixed base cost of signature recovery, nonce and balance checks, and applying the transaction at all — it exists even with no
data. Adding 50 non-zero calldata bytes at 16 gas each adds50 × 16 = 800gas, for an intrinsic cost of21,000 + 800 = 21,800gas.