wolovim @ Ethereum Foundation
August 15, 2023
Twitter @wolovim.eth
Created by:
Avatarwolovim @ Ethereum Foundation
August 15, 2023
Twitter @wolovim.eth
So, you’ve heard about this Ethereum thing and are ready to venture down the rabbit hole? This post will quickly cover some blockchain basics, then get you interacting with a simulated Ethereum node – reading block data, checking account balances, and sending transactions. Along the way, we’ll highlight the differences between traditional ways of building apps and this new decentralized paradigm. Some code is included, but the focus is on the concepts, not a final product.
This post aspires to be accessible to a wide range of developers. Python tools will be involved, but they are just a vehicle for the ideas. Reproducing the code is not required, but may help the ideas sink in. For those coding along, I'm making just a few assumptions about what you already know, so we can quickly move on the Ethereum-specific bits.
Assumptions, if you are going to reproduce the code:
Again, if any of these are untrue, just follow along conceptually.
There are many ways to describe Ethereum, but at its heart is a blockchain. Blockchains are made up of a series of blocks, so let’s start there. In the simplest terms, each block on the Ethereum blockchain is some metadata and a list of transactions. In JSON format, that looks something like this:
{ "number": 1234567, "hash": "0xabc123...", "parentHash": "0xdef456...", ..., "transactions": [...] }
Each block has a reference to the block that came before it; the
Note: Ethereum makes regular use of hash functions to produce fixed-size values (“hashes”). Hashes play an important role in Ethereum, but you can safely think of them as unique IDs for now.
A blockchain is essentially a linked list; each block has a reference to the previous block.
This data structure is nothing novel, but the rules (i.e., peer-to-peer protocols) that govern the network are. There’s no central authority; the network of peers must collaborate to sustain the network, and compete to decide which transactions to include in the next block. So, when you want to send some asset to a friend, you’ll need to broadcast that transaction to the network, then wait for it to be included in an upcoming block.
The only way for the blockchain to verify that asset was truly sent from one user to another is to use assets native to (i.e., created and governed by) that blockchain. In Ethereum, this native currency is called ether, and the Ethereum blockchain contains the only official record of account balances.
This new decentralized tech stack has spawned new developer tools. Such tools exist in many programming languages, but we’ll be looking through the Python lens. To reiterate: even if Python isn’t your language of choice, it shouldn’t be much trouble to follow along.
Python developers that want to interact with Ethereum are likely to reach for web3.py. web3.py is a library that greatly simplifies the way you connect to an Ethereum node, then send and receive data from it.
Note: “Ethereum node” and “Ethereum client” are often used interchangeably. In either case, it refers to the software that a participant in the Ethereum network runs. This software can read block data, receive updates when new blocks are added to the chain, broadcast new transactions, and more.
Ethereum clients can be configured to be reachable by IPC, HTTP, or Websockets. web3.py refers to these connection options as providers. You’ll want to choose one of the three providers to configure so that web3.py knows how to communicate with your node.
The Ethereum node and web3.py need to be configured to communicate via the same protocol, e.g., IPC in this diagram.
Once web3.py is properly configured, you can begin to interact with the blockchain. Here’s a couple of web3.py usage examples as a preview of what’s to come:
# read block data: w3.eth.get_block('latest') # send a transaction: w3.eth.send_transaction({'from': ..., 'to': ..., 'value': ...})
In this walkthrough, we’ll just be working within a Python interpreter. We won't be creating any directories, files, classes or functions.
Note: In the examples below, commands that begin with
First, install IPython for a user-friendly environment to explore in. IPython offers tab completion, among other features, making it much easier to see what’s possible within web3.py.
$ pip install ipython
web3.py is published under the name
$ pip install web3
One more thing – we're going to simulate a blockchain later, which requires a couple more dependencies. You can install those via:
$ pip install "web3[tester]"
You’re all set up to go!
Open up a new Python environment by running
$ ipython
This will print out some information about the versions of Python and IPython you’re running, then you should see a prompt waiting for input:
In [1]:
You’re looking at an interactive Python shell now. Essentially, its a sandbox to play in. If you’ve made it this far, its time to import web3.py:
In [1]: from web3 import Web3
Besides being a gateway to Ethereum, the Web3 module offers a few convenience functions. Let’s explore a couple.
In an Ethereum application, you will commonly need to convert currency denominations. The Web3 module provides a couple of helper methods just for this: from_wei and to_wei.
Note: Computers are notoriously bad at handling decimal math. To get around this, developers often store dollar amounts in cents. For example, an item with a price of $5.99 may be stored in the database as 599. A similar pattern is used when handling transactions in ether. However, instead of two decimal points, ether has 18! The smallest denomination of ether is called wei, so that’s the value specified when sending transactions.
1 ether = 1000000000000000000 wei
1 wei = 0.000000000000000001 ether
Try converting some values to and from wei. Note that there are names for many of the denominations in between ether and wei. One of the better known among them is gwei, as it’s often how transaction fees are represented.
In [2]: Web3.to_wei(1, 'ether') Out[2]: 1000000000000000000 In [3]: Web3.from_wei(500000000, 'gwei') Out[3]: Decimal('0.5')
Other utility methods on the Web3 module include data format converters (e.g., to_hex ), address helpers (e.g., is_address ), and hash functions (e.g., keccak ). Many of these will be covered later in the series. To view all the available methods and properties, utilize IPython’s auto-complete by typing
The convenience methods are lovely, but let’s move on to the blockchain. The next step is to configure web3.py to communicate with an Ethereum node. Here we have the option to use the IPC, HTTP, or Websocket providers.
We won't be going down this path, but an example of a complete workflow using the HTTP Provider might look something like this:
While this is one “real” way to do it, the syncing process takes hours and is unnecessary if you just want a development environment. web3.py exposes a fourth provider for this purpose, the EthereumTesterProvider. This tester provider links to a simulated Ethereum node with relaxed permissions and fake currency to play with.
The EthereumTesterProvider connects to a simulated node and is handy for quick development environments.
That simulated node is called eth-tester and we installed it as part of the
In [4]: w3 = Web3(Web3.EthereumTesterProvider())
Now you’re ready to surf the chain! That’s not a thing people say. I just made that up. Let’s take a quick tour.
First things first, a sanity check:
In [5]: w3.is_connected() Out[5]: True
Since we’re using the tester provider, this isn’t a very valuable test, but if it does fail, chances are you typed something in wrong when instantiating the
As a convenience, the tester provider created some accounts and preloaded them with test ether. First, let’s see a list of those accounts:
In [6]: w3.eth.accounts Out[6]: ['0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf', '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF', '0x6813Eb9362372EEF6200f3b1dbC3f819671cBA69', ...]
If you run this command, you should see a list of ten strings that begin with
As mentioned, the tester provider has preloaded each of these accounts with some test ether. Let’s find out how much is in the first account:
In [7]: w3.eth.get_balance(w3.eth.accounts[0]) Out[7]: 1000000000000000000000000
That’s a lot of zeros! Before you go laughing all the way to the fake bank, recall that lesson about currency denominations from earlier. Ether values are represented in the smallest denomination, wei. Convert that to ether:
In [8]: w3.from_wei(1000000000000000000000000, 'ether') Out[8]: Decimal('1000000')
One million test ether — still not too shabby.
Let’s take a peek at the state of this simulated blockchain:
In [9]: w3.eth.get_block('latest') Out[9]: AttributeDict({ 'number': 0, 'hash': HexBytes('0x9469878...'), 'parentHash': HexBytes('0x0000000...'), ... 'transactions': [] })
A lot of information gets returned about a block, but just a couple things to point out here:
We’re stuck at block zero until there’s a transaction to mine, so let’s give it one. If you wanted to send a few test ether from one account to another that code would look something like this:
In [10]: tx_hash = w3.eth.send_transaction({ 'from': w3.eth.accounts[0], 'to': w3.eth.accounts[1], 'value': w3.to_wei(3, 'ether') })
In the "real" version, this is typically the point where you’d wait for several seconds for your transaction to get included in a new block. The full process goes something like this:
Our simulated environment will add the transaction in a new block instantly, so we can immediately view the transaction:
In [11]: w3.eth.get_transaction(tx_hash) Out[11]: AttributeDict({ 'hash': HexBytes('0x15e9fb95dc39...'), 'blockNumber': 1, 'transactionIndex': 0, 'from': '0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf', 'to': '0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF', 'value': 3000000000000000000, ... })
You’ll see some familiar details here: the
We can also easily verify the success of this transaction by checking the balances of the two accounts involved. Three ether should have moved from one to another.
In [12]: w3.eth.get_balance(w3.eth.accounts[0]) Out[12]: 999996999999999999979000 In [13]: w3.eth.get_balance(w3.eth.accounts[1]) Out[13]: 1000003000000000000000000
The latter looks good! The balance went from 1,000,000 to 1,000,003 ether. But what happened to the first account? It appears to have lost slightly more than three ether. Alas, nothing in life is free, and using the Ethereum public network requires that you compensate your peers for their supporting role. A small transaction fee was deducted from the account making the transaction to the tune of 21000 wei.
Note: On the public network, transaction fees are variable based on network demand and how quickly you'd like a transaction to be processed.
This seems as good a place as any to take a break. The rabbit hole continues on, and we’ll continue exploring in part two of this series. Some concepts to come: a deeper look into accounts, smart contracts, tooling and how to stake out on your own as an Ethereum developer. Have follow-up questions? Requests for new content? Let me know on Twitter.
Note: This series is being converted from the Snake Charmers blog.