To understand the details of how Ethereum works I’ve been working my way through the yellow paper. And since there’s no better way to really understand things than to explain them, I thought I’d write up my own explanation for some of the things that stood out to me.
Fair warning: I’m no expert, there’s guaranteed to be sloppy usage of terminology and a high likelihood of mistakes. If you’re just looking for an easier to read version of the yellow paper, try the beige paper, it’s had some actually knowledgable people look it over. Comments and corrections are most welcome either via email or @ajsutton on twitter.
First up, what’s actually stored on the block chain? High level overviews of ethereum generally suggest that your account balance, contract code and all the data the contract stores are “on the block chain”. This is true but not in the sense that most articles would have you believe. If you looked through the actual data for each block in the block chain you probably won’t find any of those values stored explicitly. What the block chain actually stores is the set of instructions (transactions) which when followed build up the world state that includes all those things.
At the core of an ethereum client is basically an event sourcing system which reads and applies the transactions from each block in the block chain. The world state that’s built up is then stored separately to the block chain so that a snapshot of the state is available rather than having to reprocess every transaction from scratch. So the block chain effectively acts as the event journal for the system – it contains the instructions and all required inputs but not the actual state.
It’s important to note however, that this doesn’t mean the world state isn’t secured or reliable. Each block contains a cryptographic hash that covers the entire world state so to create a valid block you have to faithfully apply the transactions it contains because each node will verify the state hash matches.
So what is actually included in a block chain block?
- Block Header
- parentHash: identifies and verifies the parent of this block
- ommersHash: let’s skip over what ommers are for now but there’s a list of them in the body of the block and this is the hash of that list
- beneficiary: address of the account to pay for mining this block
- stateRoot: a hash covering the entire world state
- transactionsRoot: a hash of the transactions listed in the block body
- receiptsRoot: Hash of the transaction receipts generated as part of applying the transactions in this block. Note that the receipts themselves aren’t stored in the block, but this is enough to verify that every node generated the same receipts during processing.
- logsBloom: a bloom filter of the logs generated by transactions. This allows clients to quickly establish if a particular event they’re interested in occurred in this block (with some false positives) reducing the number of blocks they’d need to get the full logs for.
- difficulty: the difficulty level of this block (used as part of proof of work)
- number: the number of ancestor blocks (ie: what number block is this in the chain)
- gasLimit: Limit of gas expenditure per block
- gasUsed: Total amount of gas used by all transactions in this block
- timestamp: Time this block was created
- extraData: Up to 32 bytes of extra data.
- mixHash: part of the proof of work
- nonce: also part of the proof of work
- Block Body
- List of ommer block headers: we don’t want to talk about them right now…
- List of transactions: the actual transaction data each of which contain:
- nonce: interesting little number taken from the senders account state. Let’s save discussing that for another day.
- gasPrice: how much the transaction is offering to pay for gas
- gasLimit: maximum amount of gas to spend before giving up and aborting the transaction
- to: address of the message recipient or a 0 byte if creating a new contract
- value: how much ether to transfer to the recipient
- v, r, and s: transaction signature data to identify and verify who sent the transaction
- init: if creating a new contract (to is 0) this is the EVM code to execute, the result of which is stored as the new contract’s code
- data: if not creating a new contract, this is the parameter data carried along with the message. How to interpret it is up to the contract code.
So the block body provides the journal we expected, listing each transaction (and those ommers we’re sweeping under the carpet). If we gave up decentralisation and trusted everyone, all we’d need is the transaction list and the block number to provide ordering. We’d then be left with a pretty standard event sourcing architecture. The rest, particularly all the hashes in the header, are part of reaching consensus about which chain is the right one and verifying that the transactions were applied faithfully.
One side note on contract code: since the contract code is immutable and contracts are created by transactions you might expect to find the contract code as part of the transaction data. In many cases you will, but contract creation actually happens by executing the supplied initialisation code and then storing the result that returns as the contract code. In many cases the initialisation code will effectively just return a hardcoded form of the contract code but it could do pretty much anything to generate the contract code.