Build your own blockchain in Python: a practical guide

Bimo Putro Tristianto
11 min readDec 17, 2022

--

Photo by Shubham Dhage

In this tutorial, we will walk through the steps of building a blockchain from scratch using Python. We will start by implementing the core data structures and methods for storing and manipulating the blockchain. Next, we will add additional features to the blockchain, such as a proof-of-work system and a peer-to-peer network for distributed communication. Finally, we will create a simple HTTP API for interacting with the blockchain from external applications. By the end of this tutorial, you will have a good understanding of how a blockchain works and how to build one in Python.

This tutorial will be broken down into three parts:

  1. Setting up the project and creating the basic structure of the blockchain
  2. Implementing the core functionality of the blockchain, including adding new blocks, verifying the integrity of the blockchain, and handling conflicts
  3. Adding additional features to the blockchain, such as a proof-of-work system and a peer-to-peer network for distributed communication

Before we begin, it’s important to have a basic understanding of how a blockchain works. A blockchain is a distributed, decentralized, public ledger that records transactions on multiple computers. Each block in the chain contains a record of multiple transactions, and once a block is added to the chain, it cannot be altered.

Now, let’s get started!

1. Setting up the project and creating the basic structure of the blockchain

First, let’s create a new Python file and import the necessary libraries:

import hashlib
import json
from time import time

The hashlib library will be used to create hashes for the blocks, json will be used to encode and decode blocks when storing and loading them from a file, and time will be used to record the timestamp for each block.

Next, let’s define a class for the blockchain:

class Blockchain:
def __init__(self):
self.current_transactions = []
self.chain = []
self.nodes = set()

# Create the genesis block
self.new_block(previous_hash=1, proof=100)

def new_block(self, proof, previous_hash=None):
"""
Create a new Block in the Blockchain

:param proof: The proof given by the Proof of Work algorithm
:param previous_hash: Hash of previous Block
:return: New Block
"""
pass

def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block

:param sender: Address of the Sender
:param recipient: Address of the Recipient
:param amount: Amount
:return: The index of the Block that will hold this transaction
"""
pass

@staticmethod
def hash(block):
"""
Creates a SHA-256 hash of a Block

:param block: Block
"""
pass

@property
def last_block(self):
"""
Returns the last Block in the chain
"""
pass

The Blockchain class has several methods:

  • __init__(): Initializes the blockchain with a genesis block (the first block in the chain). The genesis block has a proof of 100 and a previous hash of 1.
  • new_block(): Creates a new block and adds it to the chain.
  • new_transaction(): Adds a new transaction to the list of transactions to be included in the next block.
  • hash(): Hashes a block using SHA-256.
  • last_block(): Returns the last block in the chain.

The current_transactions list will store the transactions that have not yet been added to a block, the chain list will store the blocks in the blockchain, and the nodes set will store the nodes in the network.

Now let’s fill in the new_block() method to create a new block and add it to the chain:

def new_block(self, proof, previous_hash=None):
"""
Create a new Block in the Blockchain

:param proof: The proof given by the Proof of Work algorithm
:param previous_hash: Hash of previous Block
:return: New Block
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash or self.hash(self.chain[-1]),
}

# Reset the current list of transactions
self.current_transactions = []

self.chain.append(block)
return block

This method creates a new block with a given proof and previous hash, and appends it to the chain list. It also resets the current_transactions list to prepare for the next block.

Next, let’s implement the new_transaction() method to add a new transaction to the list of transactions to be included in the next block:

def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block

:param sender: Address of the Sender
:param recipient: Address of the Recipient
:param amount: Amount
:return: The index of the Block that will hold this transaction
"""
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})

return self.last_block['index'] + 1

This method adds a new transaction to the current_transactions list and returns the index of the block that will hold this transaction (the next block to be mined).

Finally, let’s implement the hash() and last_block() methods:

@staticmethod
def hash(block):
"""
Creates a SHA-256 hash of a Block

:param block: Block
"""
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()

@property
def last_block(self):
"""
Returns the last Block in the chain
"""
return self.chain[-1]

The hash() method takes a block as input and returns its hash using SHA-256. The last_block() method returns the last block in the chain.

With these methods implemented, we now have a basic structure for our blockchain. In the next part, we will implement the core functionality of the blockchain, including adding new blocks, verifying the integrity of the blockchain, and handling conflicts.

2. Implementing the core functionality of the blockchain

In this part, we will implement the following methods:

  • proof_of_work(): A simple Proof of Work (PoW) algorithm that will be used to create new blocks.
  • valid_proof(): A method to validate the proof of a block.
  • valid_chain(): A method to determine if a given blockchain is valid.
  • resolve_conflicts(): A method to resolve conflicts between different versions of the blockchain.

Let’s start by implementing the proof_of_work() method:

def proof_of_work(self, last_proof):
"""
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous proof, and p' is the new proof

:param last_proof: <int>
:return: <int>
"""
proof = 0
while self.valid_proof(last_proof, proof) is False:
proof += 1

return proof

This method takes the last proof as input and tries to find a new proof by incrementing a counter until a valid proof is found (a proof that has four leading zeroes in its hash).

Next, let’s implement the valid_proof() method:

@staticmethod
def valid_proof(last_proof, proof):
"""
Validates the Proof

:param last_proof: <int> Previous Proof
:param proof: <int> Current Proof
:return: <bool> True if correct, False if not.
"""
guess = f'{last_proof}{proof}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"

This method takes the last proof and the current proof as input and checks if the hash of the concatenation of the two has four leading zeroes.

Next, let’s implement the valid_chain() method:

def valid_chain(self, chain):
"""
Determine if a given blockchain is valid

:param chain: <list> A blockchain
:return: <bool> True if valid, False if not
"""
last_block = chain[0]
current_index = 1

while current_index < len(chain):
block = chain[current_index]
print(f'{last_block}')
print(f'{block}')
print("\n-----------\n")
# Check that the hash of the block is correct
if block['previous_hash'] != self.hash(last_block):
return False

# Check that the Proof of Work is correct
if not self.valid_proof(last_block['proof'], block['proof']):
return False

last_block = block
current_index += 1

return True

This method iterates through the blockchain and checks that the hash of each block is correct and that the Proof of Work is valid. If any of these checks fail, it returns False. If the checks pass for all blocks in the chain, it returns True.

Finally, let’s implement the resolve_conflicts() method:

def resolve_conflicts(self):
"""
This is our consensus algorithm, it resolves conflicts
by replacing our chain with the longest one in the network.

:return: <bool> True if our chain was replaced, False if not
"""
neighbours = self.nodes
new_chain = None

# We're only looking for chains longer than ours
max_length = len(self.chain)

# Grab and verify the chains from all the nodes in our network
for node in neighbours:
response = requests.get(f'http://{node}/chain')

if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']

# Check if the length is longer and the chain is valid
if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain

# Replace our chain if we discovered a new, valid chain longer than ours
if new_chain:
self.chain = new_chain
return True

return False

This method checks all the nodes in the network for a longer, valid chain and, if found, replaces the current chain with the new one.

With these methods implemented, we now have a fully functional blockchain. In the next part, we will add additional features to the blockchain, such as a proof-of-work system and a peer-to-peer network for distributed communication.

3. Adding additional features to the blockchain

In this final part, we will add the following features to the blockchain:

  • A proof-of-work system to secure the blockchain and make it more difficult to tamper with.
  • A peer-to-peer network for distributed communication between nodes in the network.
  • An API for interacting with the blockchain from external applications.

First, let’s slightly modify the proof-of-work system we created earlier. Currently, the proof_of_work() method simply increments a counter until a valid proof is found, but this is not very secure. Instead, we can use a more secure proof-of-work algorithm such as Bitcoin's SHA-256 mining algorithm.

To implement this, we can modify the proof_of_work() method as follows:

def proof_of_work(self, last_block):
"""
Bitcoin's SHA-256 mining algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous proof, and p' is the new proof

:param last_block: <dict> last Block
:return: <int>
"""
last_proof = last_block['proof']
last_hash = self.hash(last_block)

proof = 0
while self.valid_proof(last_proof, proof, last_hash) is False:
proof += 1

return proof

This method now takes the last block as input and tries to find a new proof by incrementing a counter until a valid proof is found (a proof that has four leading zeroes in its hash). We also pass the hash of the last block to the valid_proof() method as an additional parameter.

Next, let’s modify the valid_proof() method to use the new proof-of-work algorithm:

@staticmethod
def valid_proof(last_proof, proof, last_hash):
"""
Validates the Proof

:param last_proof: <int> Previous Proof
:param proof: <int> Current Proof
:param last_hash: <str> The hash of the Previous Block
:return: <bool> True if correct, False if not.
"""
guess = f'{last_proof}{proof}{last_hash}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"

This method now takes the hash of the last block as an additional parameter and uses it to validate the proof.

Now that we have a secure proof-of-work system in place, let’s implement a peer-to-peer network for distributed communication between nodes in the network. To do this, we will use the Flask library to create a simple HTTP API for the blockchain.

First, let’s import the Flask library and create a new Blockchain object:

from flask import Flask

app = Flask(__name__)

# Generate a globally unique address for this node
node_identifier = str(uuid4()).replace('-', '')

# Instantiate the Blockchain
blockchain = Blockchain()

Next, let’s define the following routes for the API:

  • /mine: A route to mine a new block.
  • /transactions/new: A route to create a new transaction.
  • /chain: A route to return the full blockchain.

Here is the code for these routes:

@app.route('/mine', methods=['GET'])
def mine():
# We run the proof of work algorithm to get the next proof...
last_block = blockchain.last_block
proof = blockchain.proof_of_work(last_block)

# We must receive a reward for finding the proof.
# The sender is "0" to signify that this node has mined a new coin.
blockchain.new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)

# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(proof, previous_hash)

response = {
'message': "New Block Forged",
'index': block['index'],
'transactions': block['transactions'],
'proof': block['proof'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 200

@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()

# Check that the required fields are in the POST'ed data
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'Missing values', 400

# Create a new Transaction
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])

response = {'message': f'Transaction will be added to Block {index}'}
return jsonify(response), 201

@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200

The mine() route mines a new block by running the proof-of-work algorithm and adding a new transaction for the miner (with a reward of 1 coin). The new_transaction() route creates a new transaction and adds it to the list of transactions to be included in the next block. The full_chain() route returns the full blockchain.

With these routes defined, we can now start the server and interact with the blockchain through the API. To start the server, add the following code at the end of the file:

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

This will start the server on the local machine, listening on port 5000. You can now interact with the blockchain by making HTTP requests to the server.

Finally, let’s add support for a peer-to-peer network by allowing nodes to register with the server and broadcast transactions and blocks to other nodes in the network.

To do this, we will need to modify the /mine and /transactions/new routes to broadcast the new block or transaction to the network. We will also need to add a new route for registering new nodes in the network.

Here is the code for these additional routes:

@app.route('/nodes/register', methods=['POST'])
def register_nodes():
values = request.get_json()

nodes = values.get('nodes')
if nodes is None:
return "Error: Please supply a valid list of nodes", 400

for node in nodes:
blockchain.register_node(node)

response = {
'message': 'New nodes have been added',
'total_nodes': list(blockchain.nodes),
}
return jsonify(response), 201

@app.route('/nodes/resolve', methods=['GET'])
def consensus():
replaced = blockchain.resolve_conflicts()

if replaced:
response = {
'message': 'Our chain was replaced',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'Our chain is authoritative',
'chain': blockchain.chain
}

return jsonify(response), 200

The register_nodes() route allows nodes to register with the server and add them to the list of nodes in the network. The consensus() route implements the consensus algorithm and resolves any conflicts by replacing the current chain with the longest valid chain in the network.

With these additional features added, we now have a fully functional blockchain system in Python. You can now use the API to interact with the blockchain, mine new blocks, create new transactions, and join the peer-to-peer network.

You can use the following HTTP methods to interact with the API:

  • GET /mine: Mine a new block and add it to the blockchain.
  • POST /transactions/new: Create a new transaction and add it to the list of transactions to be included in the next block.
  • GET /chain: Return the full blockchain.
  • POST /nodes/register: Register new nodes with the server.
  • GET /nodes/resolve: Run the consensus algorithm to resolve conflicts and ensure that the current chain is the longest valid chain in the network.

Here is an example of how you might use the API to create a new transaction:

import requests

# Create a new transaction
new_transaction = {
'sender': 'Alice',
'recipient': 'Bob',
'amount': 5,
}

# Send the transaction to the server
response = requests.post('http://localhost:5000/transactions/new', json=new_transaction)
print(response.json())

This will send a POST request to the /transactions/new route with the new transaction as the request body, and print the response from the server.

I hope this tutorial has been helpful in understanding how to create a fully functional blockchain system in Python. If you have any questions or need further clarification, please don’t hesitate to ask.

--

--