Riding blockchain
Andrei Zhozhin
| 9 minutes
It is very hard to ignore blockchain these days as it is a core for many coll technologies these days (starting from digital currencies like Bitcoin and Etherium finishing by NFT for artwork).
Let’s look at Etherium blockchain as one of the major competitors and try to write some code to understand how to work with it programmatically.
Basics
Blockchain
For simplicity, Blockchain is a sequence of blocks (that can hold any data) that are linked together via hashes.
The next block prevHash
is the same as the current block hash
. This structure protects blockchain from amendment because if the attacker change data in one block all consequent blocks would be invalidated as all hashes of all blocks would not match.
There are many properties (we will see all of them later) of the block but here we need to understand only a few to get an idea of how the whole thing works:
number
- block number, should be greater than previousparentHash
- value ofhash
property from previous block.0x0
for genesis block as there are no previous.nonce
- a number used once, a number that participates in mining process to find hash smaller than required (defined by difficulty).hash
- a hash value of the whole blocktransactions
Hash
Hash is one of the cornerstones of cryptography. This is a one-way function that takes the input array (could be very big) of data and return a short array (fixed size) of numbers that represents the input array. You can think about it as a data fingerprint that is “unique” (it is almost impossible to find two data sets that have the same hash). As the function is one way there is no possibility to restore original data using hash function value.
There are many types of hash functions, some of them might be known like MD5
, SHA-1
, SHA-256
, etc.
In Etherium blockchain Keccak-256
hash function is used to calculate hashes.
Here is a table of different hashes for the same input text (Hello World!
):
Hash function | Value |
---|---|
MD5 | ed076287532e86365e841e92bfc50d8c |
SHA-1 | 2ef7bde608ce5404e97d5f042f95f89f1c232871 |
SHA-256 | 7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069 |
Keccak-256 | 3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0 |
Mining
To “mine” a block you need to find a valid hash for the block, but it is not that trivial. There is difficulty in the process that preventing finding that hash quickly - defined constrain for the hash value - it should be less than defined target
.
The difficulty is used to calculate the target.
All information in the block is “static” and there is only one property that the miner can change: nonce
. So the process looks the following (starting with nonce = 0
):
- calculate hash of the block using a current nonce value
- check if
hash
belowtarget
(if it is block is “mined”) - if it is not -
nonce = nonce + 1
and repeat from first step
There is also a timestamp
property in the block, so the goal of the miner is to find nonce
within 1 second, otherwise, the timestamp would increment and the whole process would need to be restarted from nonce = 0
.
We would avoid real mining for our development purposes and our blockchain is running with difficulty = 0
Run local blockchain
For our experiments, we would run the local blockchain. To do that we would use Ganache
. You can download it from official site.
After installation, you need to run it and select “Quickstart (Etherium)” to get the new local network up and running.
So, now we have a blockchain in the following state:
- current block: 0 - as we just started we don’t have any transactions yet, but the network initialized with some start accounts and balances (this is called genesis block)
- we have 10 accounts with starting balances
100.00 ETH
Now we can start to do something with this network.
Connecting to etherium blockchain
I would be using python library web3.py
to interact with etherium blockchain but there is also popular web3.js
for javascript that could work in browser and would be a tool of choice for web apps.
Let’s install it and write some code to connect to the network
pip install web3
This code could connect to local blockchain on http://127.0.0.1:7545
and get information about block 0
.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:7545'))
print(w3.isConnected())
block = w3.eth.get_block('latest')
print(block)
The output of the code above (I’ve edited it a little bit to remove some clutter)
True
AttributeDict({
'number': 0,
'hash': HexBytes('0x5785...65ff'),
'parentHash': HexBytes('0x00...00'),
'mixHash': HexBytes('0x00...00'),
'nonce': HexBytes('0x0000000000000000'),
'sha3Uncles': HexBytes('0x1dcc...9347'),
'logsBloom': HexBytes('0x00...00'),
'transactionsRoot': HexBytes('0x56e8...b421'),
'stateRoot': HexBytes('0x14aa...c546'),
'receiptsRoot': HexBytes('0x56e8...b421'),
'miner': '0x00...00',
'difficulty': 0,
'totalDifficulty': 0,
'extraData': HexBytes('0x'),
'size': 1000,
'gasLimit': 6721975,
'gasUsed': 0,
'timestamp': 1634397979,
'transactions': [], 'uncles': []
})
Please note that difficulty
and totalDifficulty
are 0
(zeros) as we are running a local development blockchain and we don’t want to waste time on finding a valid hash. We just calculate hash with nonce
equal to zero and we are done.
Let’s try to read balances for our pre-populated accounts (just couple of them):
account0 = w3.eth.accounts[0]
balance0 = w3.eth.get_balance(account0)
print(f"Account0 add: {account0}, balance: {balance0}")
account1 = w3.eth.accounts[1]
balance1 = w3.eth.get_balance(account1)
print(f"Account1 add: {account1}, balance: {balance1}")
Output
Account0 addr: 0x2e3162B333b3739e7B8aA0EBB704D0Af39F52E28, balance: 100000000000000000000
Account1 addr: 0xaf85CAE5dE1c432c60F69689f0f80F43e385E008, balance: 100000000000000000000
You might ask why UI shows balance as 100.00 ETH
but here we get the HUGE number 100000000000000000000
, this is because 1 ETH = 10^18 Wei
, Wei
is the smallest piece of ETH
, so we see balance as is, but UI convert Wei to ETH to show it in the nice form to the user.
An important note here: as blockchain is “readable” by anyone you can check the balance of any account, but because you don’t know the association of accounts and people your privacy is not disclosed.
Transactions
Having some accounts with some ether is good but we need to do transactions to get the benefit of the network. To send ether from the account we need to own it - in terms of blockchain, it means owning a private key to be able to sign transactions and validate ownership.
In Ganache you can click on the key icon next to the account to see the private key.
Now when we know private key we can create account from code to be able to sign. Etherium address could be extracted from private key, so if we would create local account with private key of account0 we can see it’s address
private_key0 = "008755021c8870b213a3e2ae908f85460c1955e0e660f5594fe0fa2c5e4c6170"
acct0 = w3.eth.account.privateKeyToAccount(private_key0)
print(acct0.address)
Output (it is matched to UI and we haven’t specified it explicitly in the code)
0x2e3162B333b3739e7B8aA0EBB704D0Af39F52E28
Now let’s create, sign, and send transaction: move 1 ETH
from account0
to account1
amount = 1 # this is amount in ETH, it would be converted to Wei
address_to = '0xaf85CAE5dE1c432c60F69689f0f80F43e385E008' # account1
nonce = w3.eth.getTransactionCount(acct0.address)
tx = {
'from': acct0.address,
'to': address_to,
'value': w3.toWei(amount, 'ether'),
'gas': 21000,
'gasPrice': w3.eth.gas_price,
'nonce': nonce,
'chainId': 1337
}
signed_tx = w3.eth.account.signTransaction(tx, private_key=private_key0)
print (signed_tx)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Acct0 balance: {w3.eth.get_balance(w3.eth.accounts[0])}")
print(f"Acct1 balance: {w3.eth.get_balance(w3.eth.accounts[1])}")
A signed transaction would look like the following:
SignedTransaction(
rawTransaction=HexBytes('0xf86e808504a817........adb009cf'),
hash=HexBytes('0x1e52fc8133c2200c36a80218887d14371349d02f910a91a0d78ab7f35de0cbaa'),
r=106121795418760483923630680996704157212173332009377596846518426889201626712370,
s=38161356266923782681465688369360602713211657523927194070106774189916816411087,
v=2710
)
There are three important properties r
, s
, and v
- these are values of transaction signature. Values r
and s
are outputs of an ECDSA (Elliptical Curve Digital Signature Algorithm) and v
is the recovery id.
Balances after transaction execution:
Acct0 balance: 98999580000000000000
Acct1 balance: 101000000000000000000
You may notice that the account balance for Account1 is exactly 101 ETH
, but the balance for Account0 is less than 99 ETH
as you would expect. We have value 98.99958 ETH
as every transaction has fee, the fee is calculated based on the amount of gas
consumed by transaction and gas price
.
Gas is the number of operations that Etherium Virtual Machine has performed to execute the transaction, and we have associated gas price that was charged from the sender of the transaction to pay for it.
Transfer transaction consumed 21,000 gas
(with gas price: 20,000,000,000 Wei
), so total we have 420,000,000,000,000 Wei
or 0.00042 ETH
and now our transaction balanced.
Our first transaction was included in block1
here is how it looks like in UI:
Getting information for our transaction programmatically
tx_hash = '0x1e52fc8133c2200c36a80218887d14371349d02f910a91a0d78ab7f35de0cbaa'
tx = w3.eth.getTransaction(tx_hash)
print(tx)
Output with transaction details
AttributeDict({
'hash': HexBytes('0x1e52fc8133c2200c36a80218887d14371349d02f910a91a0d78ab7f35de0cbaa'),
'nonce': 0,
'blockHash':
HexBytes('0xd81ebff41bc43eba00a66342dc5f6cf3446baa1c4371a6fe22ecfdbe6a1ab663'),
'blockNumber': 1,
'transactionIndex': 0,
'from': '0x2e3162B333b3739e7B8aA0EBB704D0Af39F52E28',
'to': '0xaf85CAE5dE1c432c60F69689f0f80F43e385E008',
'value': 1000000000000000000,
'gas': 21000,
'gasPrice': 20000000000,
'input': '0x',
'v': 2710,
'r': HexBytes('0xea9ecec2e48084cb143580cab3165badc7aaa3f34dc5127e7dfde5d2ba810932'),
's': HexBytes('0x545e8fedd6a6687d8afd3fb7d0b48084a71abfbc09f01d307ada2ba9adb009cf')
})
Gas cost per operations
Every operation has its own gas price and here are some of them:
Operation | Gas | Description |
---|---|---|
ADD/SUB | 3 | Arithmetic op |
MUL/DIV | 5 | Arithmetic op |
POP | 2 | Stack op |
PUSH | 3 | Stack op |
BALANCE | 400 | Get balance of account |
CREATE | 32,000 | Create new account |
Standard price for transfer is 21000 units of gas.
As every transaction has gasLimit
property set to particular value (finite), it is protecting whole network from code that works infinitely as every operation consume some amount and when amount reach zero transaction is aborted.
Exploring mainnet
We were playing with our local blockchain now we can have a look on the real blockchain with real money. There are several official networks in Etherium:
- Mainnet - main production network (Proof of work)
- Testnets
- Görli - Proof of authority testnet
- Kovan - Proof of authority testnet (OpenEtherium client)
- Rinkeby - Proof of authority testnet (Geth client)
- Ropsten - Proof of work testnet
To get access to the blockchain you either need to run a blockchain node (full or light), or you can use the gateway. Let’s use one of the gateways to access Etherium Mainnet - Infura. Registration takes less than 1 min and you can create your project and credentials.
Click “Create Project” and enter the name of your project. And you’ll see the connection details.
Once we have a project we have an endpoint URL that we can use with the web3 client to get the last block details.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/v3/7f0bXXXXXXXXXXXXXXXXXXXXXXXX8def"))
print(w3.isConnected())
block = w3.eth.get_block('latest')
print(block)
The latest block in the mainnet (at the time of execution):
AttributeDict({
'baseFeePerGas': 72596235616,
'difficulty': 9657477492851337,
'extraData': HexBytes('0xe4b883e5bda9e7a59ee4bb99e9b1bc3d0a22'),
'gasLimit': 30058619,
'gasUsed': 17895327,
'hash': HexBytes('0x329cdcd9390bcf0918390a140ea8c7078cdc731d84a49715a065d7d8cc402b25'),
'logsBloom': HexBytes('0x982b59...2104b2'),
'miner': '0x829BD824B016326A401d083B33D092293333A830',
'mixHash': HexBytes('0xbaed2628927b1c463b6a699e974227965d324cd754fe758f2f05a1968df80dd4'),
'nonce': HexBytes('0x4b396a138872722b'),
'number': 13431334,
'parentHash':
HexBytes('0xe3d747c5c6611fb05bd0fa4f55dc623f1d6838875e5877d40fcbc07aeed51f6f'),
'receiptsRoot':
HexBytes('0x11818ef5a019097686d50a8b61d559d7eef1c083ba79e59380e1a144762e897d'),
'sha3Uncles':
HexBytes('0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347'),
'size': 125737,
'stateRoot':
HexBytes('0x85b790c15a6ba568bf541f39dcafdab7631f56479f16a5fc38e873441f986cc4'),
'timestamp': 1634418143,
'totalDifficulty': 32541291813132683718384,
'transactions': [
HexBytes('0xfa40b1a5207ebaa3f54fc910272595da515016a4ddc76806853eecc10d9fd78c'),
HexBytes('0x188da7edcebe2cd486cefa3b2344b02ff968294ac34786b0d2b85b329b43e50e'),
HexBytes('0x0eff5a78ef28c4343e436f1b82ff9220f87922159c90ea1728a4101ef21c80ac'),
...],
'transactionsRoot':
HexBytes('0x99be96af7c2e3bc4d1a25b04a3f92d204d3bd5b2925de774af14750fb9b91628'),
'uncles': []})
Now you can explore real networks programmatically and transfer ether.
Summary
We have installed local blockchain, got some (10) accounts for free to play with, wrote some code to read balance, send a transaction, and read information transactions. We also connected to production Etherium mainnet and got information about the latest block.
In the next article, I’ll cover smart contracts and tokens. Stay tuned.