Building Ethereum from scratch in 10 minutes

Gabriel Guimaraes
Brex Tech Blog
Published in
6 min readOct 11, 2021

If you’re itching to see some code already, head over to pythereum. If you’re new to crypto and want to learn the basics, check out the crypto articles in our Learning Center

pythereum is Ethereum written in < 200 lines of Python

At Brex, we believe the financial systems powering the world can be 100x better than they are today. And we’re here to build that vision.

We’ve been increasingly interested in Ethereum’s role in this domain. Ethereum was the first blockchain ever to introduce the concept of smart contracts, a core primitive that powers a lot of the fundamentals behind NFTs, Crypto games, DeFi, and more.

As an engineer, I think the best way to understand something is to build it yourself. And while there are a ton of guides out there teaching you how to build your own smart contract on top of Ethereum, I haven’t found any guide teaching you how to build Ethereum itself.

So let’s do it in 10 minutes!

What is Ethereum?

One of my favorite definitions is that Ethereum is a decentralized computer.

The way we write backend code for any app has been the same for a while: we write some code that runs in a server hosted by something like AWS. That code listens to customer requests and then reads and writes from a database.

Ethereum turns that around by saying that there aren’t centralized servers and databases anymore. All backend code and data for a dApp can live in the blockchain, equally replicated across hundreds of thousands of nodes across the globe. Anyone can run a node, and any of them (or all of them) can be “the backend” for a dApp’s frontend.

In this tutorial we’ll go over all of the pieces needed to build a simple version of Ethereum in 200 lines of Python code.

Accounts

An Account is the basic piece of state in Ethereum. It’s uniquely identified by an address (a long unique string) and has 4 fields:

  • The nonce, a counter used to make sure each transaction can only be processed once
  • The account’s current ether balance
  • The account’s contract code, if present
  • The account’s storage (empty by default)

An account can be externally owned, in which case it just holds a balance, similar to a regular Bitcoin account. More interestingly it can also be a contract account which contains code and a data storage.

The “World State” of Ethereum — which comprises all state and all code for all Apps — is just a collection of accounts. You can derive the world state of a blockchain by reducing through all of the blocks in the chain.

The code above simply keeps track of those 4 fields and defines a __str__ function that knows how to stringify an account. You’ll see we define a similar __str__ function for each of our models, so we also get the ability to hash each of those models into an 8 char string.

Blocks

A blockchain is just a chain of blocks. And a block is just a bunch of transactions and some metadata. For simplicity in our case the metadata is just a single pointer to the previous block.

Transactions

A transaction is the main way to “do something” in Ethereum. You can use a transaction to:

  • Transfer ether
  • Create accounts (including contract accounts)
  • Call contract accounts (i.e. call the contract code and pass it some data)

Each transaction changes the world state S via apply_transaction(S, TX) -> S'. Similarly, a block changes the world state by applying all transactions in it one after the other, starting from the previous block’s end state.

As you can see in the above code snippet a Transaction has 5 fields:

  • The address of the sender account
  • The address of the receiver account
  • A nonce that must match the nonce in the sender account. This is essentially an idempotency key used to ensure the same transaction isn’t sent twice
  • The amount of ether to transfer from the sender to the receiver
  • An optional data field. In a contract creation this is the string containing the contract code. In a contract call this is the JSON argument that’s passed down to the contract code.

Ethereum State Transition Function

Image borrowed from https://ethereum.org/en/whitepaper/

The Ethereum state transition function is one of the most fundamental parts of the entire architecture. It defines how a transaction acts on the world state via `apply_transaction(S, TX) -> S’`. This is how we define it:

  1. Check that the nonce of the transaction matches the nonce of the sender account. If so, increment the nonce of the sender account
  2. Check that the sender has enough balance to send to the receiver
  3. If there is no receiver address present, this is a Contract creation. We create a new contract account with the code sent by the transaction data, and we use the hash of the code as the address of the new contract account.
  4. Transfer the transaction amount from the sender’s account to the receiving account. If the receiving account does not yet exist, create it.
  5. If the receiver account is an already existing contract account, call the contract’s code and pass it the transaction data as argument.

See below for the Python code:

Executing contract code

At the end of the apply_transaction code, you’ll see the very important Account.call_contract that executes the smart contract’s code. It is actually implemented in a single line of Python code via:

Note that our implementation is a simplification of Ethereum. Our smart contracts are itself written in Python instead of Solidity code. And we don’t really handle gas (meaning the amount of computational effort required to execute specific operations) or security considerations of executing arbitrary user code. If someone submits an infinite loop for instance, that’ll make our Ethereum implementation get stuck.

Tying everything together

The BlockChain class ties everything together in our implementation. It allows users to enqueue new transactions and then to mine a new block committing all of those transactions into a new block in the chain.

It also contains helpers to calculate the world state before and after each block. Remember that the world state is just a set of accounts, which can be fully derived from all of the blocks in the chain!

Note that before committing a block in mine_new_block, we need to calculate what the world state will be after we add that block (and all of its transactions) and then we store a signature (i.e. hash) of that end world state in the block itself. This can be used by other nodes in the network to validate that they’re arriving at the same end world state that we arrived.

The end state signature calculation is actually just ~7 lines of Python:

Running your Blockchain

Time to run your blockchain to move ether around, create smart contracts, and call them. The easiest way to do that is just to head over to the pythereum repo, clone it, and follow the instructions.

Once you run python blockchain.py, you can submit transactions formatted as from_addr to_addr nonce amount 'data'. For example the account deadbeef can create a new smart contract by submitting a transaction to the None address:

deadbeef None 0 0 'storage["foo"] = args["my_arg"]'

The last argument 'storage["foo"] = args["my_arg"]' is the actual code for the contract. Instead of Solidity and the EVM, our implementation accepts regular Python code.

To call the smart contract you can just submit a transaction to the contract’s address:

deadbeef 9b241ab9 1 0 '{"my_arg": 42}'

At the end the contract’s state should be

Contract Account: 9b241ab9
Nonce: 0, Balance: 0
Code: storage["foo"] = args["my_arg"]
Storage: {'foo': 42}

What next?

This implementation has many simplifications:

  • It’s not distributed (you’re just running a single pythereum node when you run python blockchain.py)
  • We don’t handle Gas and security concerns of executing arbitrary user code
  • We don’t implement any real notion of mining / proof of work / proof of stake

But hopefully it can be used as a starting point for people to understand Ethereum and Blockchain better. And we 100% encourage you to try and add the above features to the implementation. If you do, please submit a pull request to our repo!

Join us!

If you like the idea of reimagining how financial systems should work in the future, and want to join us in building that vision, take a look at Brex’s careers page.

--

--