In today’s tutorial we will go from the first steps to building a simple blockchain network in python language. Giving us the best overview of how a blockchain works.
Using microframework Flask to create endpoints, then run on multiple machines to create a decentralized network. We will learn how to build a simple user interface to interact with blockchain and store information for every use case, such as peer-to-peer payments, chat, or e-commerce.
The goal is to build an application that allows users to share information by posting posts. Content will be stored on the blockchain, so it will not be changed and exist forever. Users will interact with the application through a simple web interface.
We will use the bottom-up approach. Start by defining the data structure that we will store in the blockchain. Each post will include three essential elements:
Here we will go into the steps:
Here we will store in the widely used format that is JSON. And here is what a post will be stored in the blockchain:
{
"author": "some_author_name",
"content": "Some thoughts that author wants to share",
"timestamp": "The time at which the content was created"
}
The term data
in blockchain is often replaced by transaction
. So here we are unified to use the term transaction
to refer data
to this application.
Transactions will be packaged into blocks. A block may contain one or more transactions. Blocks containing transactions are generated regularly and added to the blockchain. Because there are many blocks, each block will have a ID
unique:
Block class:
def __init __ (self, index, transactions, timestamp):
"" "
Constructor for a `Block` class.
: param index: The unique ID number of a block.
: param transactions: List of transactions.
: param timestamp: Block creation time.
"" "
self.index = index
self.transactions = transactions
self.timestamp = timestamp
To prevent fake data from being stored inside the blocks and to detect this we will use the hash function.
A hash function is a function that takes data of any size and creates data of a fixed size, often used to determine input data. Ideal hash functions often have the following characteristics:
The results of these properties are:
There are various common hash functions. Here is an example in Python we use the hash function SHA-256
:
>>> from hashlib import sha256
>>> data = b "Some variable length data"
>>> sha256 (data) .hexdigest ()
'b919fbbcae38e2bdaebb6c04ed4098e5c70563d2dc51e085f784c058ff208516'
>>> sha256 (data) .hexdigest () # no matter how many times you run it, the result will be the same string of 256 characters.
'b919fbbcae38e2bdaebb6c04ed4098e5c70563d2dc51e085f784c058ff208516'
>>> data = b "Some variable length data2" # Added a character at the end.
'9fcaab521baf8e83f07512a7de7a0f567f6eef2688e8b9490694ada0a3ddeec8'
# Note: the hash value has changed completely!
We will store the block’s hash value into a field inside the object Block
and it will act like the digital signature (or signature) of the data contained therein:
from hashlib import sha256
import json
def compute_hash (block):
"" "
Converts the block object into a JSON string and then returns the hash value.
"" "
block_string = json.dumps (self .__ dict__, sort_keys = True)
return sha256 (block_string.encode ()). hexdigest ()
We need a way to ensure that any changes in the previous blocks will invalidate the entire chain. The way Bitcoin works is to create dependencies between successive blocks by stringing them with the block’s hash value immediately. This means saving the hash of the previous block in the current block when adding a new field to the name previous_hash
.
There will be a question: what about the first block? That block is called the genesis block and it can be created manually or through some logic.
from hashlib import sha256
import json
import time
Block class:
def__init __ (self, index, transactions, timestamp, previous_hash):
"" "
Constructor for a `Block` class.
: param index: The unique ID number of a block.
: param transactions: List of transactions.
: param timestamp: Block creation time.
: param previous_hash: Contains the hash value of the preceding block in the chain.
"" "
self.index = index
self.transactions = transactions
self.timestamp = timestamp
self.previous_hash = previous_hash # add previous_hash field
def compute_hash (self):
"" "
Converts the block object into a JSON string and then returns the hash value.
"" "
block_string = json.dumps (self .__ dict__, sort_keys = True)
return sha256 (block_string.encode ()). hexdigest ()
Blockchain class:
def __init __ (self):
"" "
Constructor of the `Blockchain` class.
"" "
self.chain = []
self.create_genesis_block ()
def create_genesis_block (self):
"" "
The function generates `block genesis` and adds it to the chain. This block has an index
equals to 0, previous_hash is 0 and a valid hash value.
"" "
genesis_block = Block (0, [], time.time (), "0")
genesis_block.hash = genesis_block.compute_hash ()
self.chain.append (genesis_block)
@property
def last_block (self):
"" "
A simple way to get the last block in a chain. Note the chain will always
There is one block which is the genesis block
"" "
return self.chain [-1]
Now, if the contents of any previous blocks change:
In the end, the whole chain as a replacement block is disabled and the only way to fix it is to recalculate the entire chain.
The problem, though, is that the hash value of all subsequent blocks can be easily recalculated to create another valid blockchain. To prevent this, we can exploit the asymmetry of the hash function we discussed above to make the task of calculating the hash value more difficult and random. What this means: Instead of accepting any hash value for the block, we add some constraints to it. Let’s add a constraint that our hash value will start with n leading zeros where n is a positive integer.
Here we will add some dummy data that we can change. We will add a new field which is the nonce field . The nonce number is a number that we can keep changing until we have a constrained hash function. The fact that the nonce satisfies the constraints acts as evidence that some calculations have been performed. This technique is a simplified version of the Hashcash algorithm used in Bitcoin. The number of zeroes specified in the constraint determines the difficulty of the PoW algorithm (the greater the number of zeroes, the harder it is to find nonce).
Also due to the asymmetry, Proof-of-work (PoW) is difficult to calculate but very easy to verify once you find the nonce (you just need to run the hash function again):
Blockchain class:
# Difficulty level of the POW algorithm
difficulty = 2
......
def proof_of_work (self, block):
"" "
The function tests different values of nonce to get a satisfied hash value
"" "
block.nonce = 0
computed_hash = block.compute_hash ()
while not computed_hash.startswith ('0' * Blockchain.difficulty):
block.nonce + = 1
computed_hash = block.compute_hash ()
return computed_hash
To add a block to the chain, we must first verify that:
Blockchain class:
.....
def add_block (self, block, proof):
"" "
Function to add blocks to the chain after verification. Verification includes:
* Check if proof is right.
* The previous_hash field is correct and is the hash value of the block
Newest in chain.
"" "
previous_hash = self.last_block.hash
if previous_hash! = block.previous_hash:
return False
if not Blockchain.is_valid_proof (block, proof):
return False
block.hash = proof
self.chain.append (block)
return True
def is_valid_proof (self, block, block_hash):
"" "
Checks whether block_hash is the valid hash value of the block or not
and meet the difficulty criteria.
"" "
return (block_hash.startswith ('0' * Blockchain.difficulty) and
block_hash == block.compute_hash ())
The original transactions will be stored as a group of unconfirmed transactions. The process of putting unconfirmed transactions into a block and calculating the POW is called Mining blocks. When the nonce that meets the constraints is found, we can say that a block has been mined and it can be put into the blockchain.
In most cryptocurrencies (including Bitcoin), miners can be given a number of cryptocurrencies as a reward for using their computational power to calculate POW. Here is the function mining:
Blockchain class:
def __init __ (self):
self.unconfirmed_transactions = [] # list of transactions not yet confirmed
self.chain = []
self.create_genesis_block ()
....
def add_new_transaction (self, transaction):
self.unconfirmed_transactions.append (transaction)
def mine (self):
"" "
The function adds transactions pending
into the blockchain by adding them to the block and releasing PoW.
"" "
if not self.unconfirmed_transactions:
return False
last_block = self.last_block
new_block = Block (index = last_block.index + 1,
transactions = self.unconfirmed_transactions,
timestamp = time.time (),
previous_hash = last_block.hash)
proof = self.proof_of_work (new_block)
self.add_block (new_block, proof)
self.unconfirmed_transactions = []
return new_block.index
Ok this is the code of the processing: source code
In this section, we will create the interface for the blockchain node to interact with the application we have just built. Here will use a popular Python microframework called Flask to create interactive REST APIs and invoke various actions in our blockchain node. If you’ve worked with any previous web framework, the code below won’t be hard to follow.
from flask import Flask, request
import requests
# Run at flask application
app = Flask (__ name__)
# Initialize the Blockchain object
blockchain = Blockchain ()
We need a route for the application to create a new transaction and this is the post
@ app.route ('/ new_transaction', methods = ['POST'])
def new_transaction ():
tx_data = request.get_json ()
required_fields = ["author", "content"]
for field in required_fields:
if not tx_data.get (field):
return "Invalid transaction data", 404
tx_data ["timestamp"] = time.time ()
blockchain.add_new_transaction (tx_data)
return "Success", 201
There will be a route that returns a copy of the chain. And this route the application will use to query all data to display:
@ app.route('/ chain', methods = ['GET'])
def get_chain():
chain_data = []
for block in blockchain.chain:
chain_data.append(block .__ dict__)
return json.dumps({"length": len(chain_data),
"chain": chain_data})
And here is the route to send a mine request - validating unverified transactions (if any). We will use it to start a mine command from our own application:
@app.route('/mine', methods=['GET'])
def mine_unconfirmed_transactions():
result = blockchain.mine()
if not result:
return "No transactions to mine"
return "Block #{} is mined.".format(result)
@app.route('/pending_tx')
def get_pending_tx():
return json.dumps(blockchain.unconfirmed_transactions)
There is a blockchain problem that we have deployed running on a computer. Although we have linked the blocks with the hash value and applied POW, it is still not possible to trust a single entity (in this case, a single machine). We need distributed data or multiple nodes to maintain the blockchain. So, to move from a single node to a peer-to-peer network, let’s first create a mechanism so that a new node can learn about other peers in the network:
# Contains the host address of other network participants
peers = set()
# route adds a new peer to the network
@app.route('/register_node', methods=['POST'])
def register_new_peers():
# Host address to peer nodes
node_address = request.get_json()["node_address"]
if not node_address:
return "Invalid data", 400
# Add node addresses to the list
peers.add(node_address)
# Return the new blockchain
return get_chain()
@app.route('/register_with', methods=['POST'])
def register_with_existing_node():
"""
Internally call the route `register_node` to
Register the current node with the remote node specified in
request and update the blockchain network
"""
node_address = request.get_json()["node_address"]
if not node_address:
return "Invalid data", 400
data = {"node_address": request.host_url}
headers = {'Content-Type': "application/json"}
# Reuqest register with the remote node and get information
response = requests.post(node_address + "/register_node",
data=json.dumps(data), headers=headers)
if response.status_code == 200:
global blockchain
global peers
# update chain and peers
chain_dump = response.json()['chain']
blockchain = create_chain_from_dump(chain_dump)
peers.update(response.json()['peers'])
return "Registration successful", 200
else:
# If an error occurs, the API will return a response
return response.content, response.status_code
def create_chain_from_dump(chain_dump):
blockchain = Blockchain ()
for idx, block_data in enumerate(chain_dump):
block = Block(block_data["index"],
block_data["transactions"],
block_data["timestamp"],
block_data["previous_hash"])
proof = block_data['hash']
if idx > 0:
added = blockchain.add_block(block, proof)
if not added:
raise Exception("The chain dump is tampered!!")
else: # this block is a genesis block so no verification is needed
blockchain.chain.append(block)
return blockchain
A new node joining the network can call the function register_with_existing_node
to register with existing nodes in the network. This will help the following:
However, there is a problem with many nodes due to intentional or unintentional reasons (such as network latency), the chain copy of several nodes may be different. In that case the nodes need to agree with some version of the chain to maintain the integrity of the entire system. In other words, we need to reach a consensus.
A simple consensus algorithm can agree with the longest valid chain when the chains of participating nodes in the network appear forked. The reason behind this method is that the longest chain proves the most work done (remember PoW is very fastidious):
class Blockchain
....
def check_chain_validity(cls, chain):
"""
A helper function to check if the entire blockchain is correct
"""
result = True
previous_hash = "0"
# Repeat over all the blocks
for block in chain:
block_hash = block.hash
# delete the hashed field to recalculate the hash value
# use the `compute_hash` method.
delattr(block, "hash")
if not cls.is_valid_proof(block, block.hash) or \
previous_hash != block.previous_hash:
result = False
break
block.hash, previous_hash = block_hash, block_hash
return result
def consensus():
"""
The simple consensus algorithm there is if a valid string is longer than possible
Found, our chain will replace it.
"""
global blockchain
longest_chain = None
current_len = len(blockchain.chain)
for node in peers:
response = requests.get('{}/chain'.format(node))
length = response.json()['length']
chain = response.json()['chain']
if length > current_len and blockchain.check_chain_validity(chain):
# Longer valid chain found!
current_len = length
longest_chain = chain
if longest_chain:
blockchain = longest_chain
return True
return False
Next we need to develop a way for any node to inform the network that it has mined a block so that everyone can update their blockchain and move on to mining other blocks. Other nodes may only need to verify the PoW and add the newly mined block to their respective chain (remember that verification is easy when knowing the nonce):
# This route to add another block just mined.
# The block needs to be verified first and then added to the chain
@app.route('/add_block', methods=['POST'])
def verify_and_add_block():
block_data = request.get_json()
block = Block(block_data["index"],
block_data["transactions"],
block_data["timestamp"],
block_data["previous_hash"])
proof = block_data['hash']
added = blockchain.add_block(block, proof)
if not added:
return "The block was discarded by the node", 400
return "Block added to the chain", 201
def announce_new_block(block):
"""
A function informs the network after a block has been mined.
Other blocks can only verify PoW and add it
corresponding string.
"""
for peer in peers:
url = "{}add_block".format(peer)
requests.post(url, data=json.dumps(block.__dict__, sort_keys=True))
The function announce_new_block
should be called after each block is mined by nodes so other peers can add it to their chain.
@app.route('/mine', methods=['GET'])
def mine_unconfirmed_transactions():
result = blockchain.mine()
if not result:
return "No transactions to mine"
else:
# Make sure we have the longest chain before communicating with the network
chain_length = len(blockchain.chain)
consensus()
if chain_length == len(blockchain.chain):
# recently announced block notifications to the network
announce_new_block(blockchain.last_block)
return "Block #{} is mined.".format(blockchain.last_block.index
So the blockchain server is set up. You can see the source code here .
Now it’s time to start developing the web application interface. We have used the Jinja2 template to display views and some CSS to make things look good.
The application needs to connect to a node in the blockchain network to fetch data and also to send new data.
import datetime
import json
import requests
from flask import render_template, redirect, request
from app import app
# Node that we will connect to fetch data about
CONNECTED_NODE_ADDRESS = "http://127.0.0.1:8000"
posts = []
Function fetch_posts
:
def fetch_posts():
"""
Get chain function from node, data analysis and local storage
"""
get_chain_address = "{}/chain".format(CONNECTED_NODE_ADDRESS)
response = requests.get(get_chain_address)
if response.status_code == 200:
content = []
chain = json.loads(response.content)
for block in chain["chain"]:
for tx in block["transactions"]:
tx["index"] = block["index"]
tx["hash"] = block["previous_hash"]
content.append(tx)
global posts
posts = sorted(content,
key=lambda k: k['timestamp'],
reverse=True)
The application has an HTML form to enter the user input and then makes a POST request to the connected node to add transactions to the unconfirmed transactions group. The transaction is then mined by the network and finally fetched after reloading the page:
@app.route('/submit', methods=['POST'])
def submit_textarea():
"""
Route to create a new transaction
"""
post_content = request.form["content"]
author = request.form["author"]
post_object = {
'author': author,
'content': post_content,
}
# Submit transaction
new_tx_address = "{}/new_transaction".format(CONNECTED_NODE_ADDRESS)
requests.post(new_tx_address,
json=post_object,
headers={'Content-type': 'application/json'})
# Return to homepage
return redirect('/')
Here we have it! source code
Place to place:
$ cd python_blockchain_app
$ pip install -r requirements.txt
Run a blockchain node on port 8000
$ export FLASK_APP=node_server.py
$ flask run --port 8000
run the web application:
$ python run_app.py
The application will run at http: // localhost: 5000 .
We will run multiple nodes by running on different ports. Use it register_with
to register as a peer network:
# already running
$ flask run --port 8000 &
# spinning up new nodes
$ flask run --port 8001 &
$ flask run --port 8002 &
$ curl -X POST \
http://127.0.0.1:8001/register_with \
-H 'Content-Type: application/json' \
-d '{"node_address": "http://127.0.0.1:8000"}'
$ curl -X POST \
http://127.0.0.1:8002/register_with \
-H 'Content-Type: application/json' \
-d '{"node_address": "http://127.0.0.1:8000"}'
You can run the application (python run_app.py) and create transactions (post via the web interface) and when you mined transactions, all nodes in the network will update the chain. And nodes can also be checked by calling with cURL or Postman.
$ curl -X GET http://localhost:8001/chain
$ curl -X GET http://localhost:8002/chain
You may have noticed an application flaw: Anyone can change the name and post any content. Also, posts are easily fake while sending transactions on the blockchain network. One way to solve this is to create a user account, using the public key cryptography. Each new user needs a public key and a private key to post in the application:
This tutorial covers the fundamentals of a public blockchain. If you’ve followed that up, you can now deploy a blockchain from scratch and build a simple application that allows users to share information on the blockchain. Hopefully the article will bring you useful and very interesting knowledge and see you in the next article.
#python #programming #blockchain