Create your own blockchain & cryptocurrency! - understand blockchains by creating one in python - no knowledge required
What exactly is a blockchain?
- How does a blockchain work?
- What is Bitcoin or STEEM exactly?
- How does a blockchain make sure of its correctness
- What is mining exactly?
These are only some questions, probably many of you are asking yourselves.
I'm here to provide you with kind of an unusual answer.
My goal is to help you undestand the whole system behind blockchains, by actually creating a small blockchain yourself!
With the rise of cryptocurrencies and decentralized systems like Bitcoin or STEEM, many people want to really undestand, what this is all about. I will show you how the system works, by coding a blockchain from scratch in python with you. The guide will be mainly for Windows, but I will also add comments regarding Linux. You don't know a whole of python, to follow this guide, but it is advised to know the most basic basics.
Get ready
Setup Python and pip
First off, we need to install Python 3.6+ along with pip, with which you can install python packages.
- On Linux, you can install python, by simply typing
sudo apt-get install python3.6
into the terminal. If you encounter errors, please trysudo apt-get update
and then try to execute the command again. If it still doesn't work, please search for installation instructions for your specific system on your preferred search engine. - On Windows,
- first download the binary, that suits your system best, under the official python download page for windows.. Make sure you grab at least the version 3.6 or higher.
- Install the file you just downloaded. Make sure you check the option to install pip, when provided with an option to do so. If not, don't worry, it probably is enabled by default.
IDE or not?
As a next step, you want to decide, whether to utilize an IDE or not. An IDE is an environment, which supports you in the creation process of your application, by providing many features, with for example syntax highlighting, or code completion, to name just 2 possible features. My favorite IDE for Python development is probably PyCharm, which you can fetch from this link. Setting up PyCharm on Windows is as simple, as downloading the executable installer and installing it with the provided setup utility. PyCharm is free tu use, as long as you use the Community Edition.
You can also use your coding notepad of choice. We will only work in one file, which you have to execute, so as long as your text-editing software is able to produce files with the ending .py
, it will work fine.
Setup your workspace (PyCharm)
If you decided to use PyCharm, simply open it up and click on Create Project in the popup dialogue. In the next dialogue you want to specify your project's name. I chose to use MyBlockChain as my project name. Finally click on create in the bottom right corner. On your left hand side, you're now provided with a file tree.
- Right-Click on the folder which consists of your project's name, in my case MyBlockChain
- Go to new
- Finally click on Python File and
- Define a Filename. I chose
MyBlockChain.py
as my filename.
Create your file (Simple Text Editor)
If you use a simple text editor, just create a file anywhere you like with the file extension .py
. In my case I chose to use the filename MyBlockChain.py
.
Install the needed packages
PyCharm
In PyCharm you can install packages very easily and add them to your project:
- Make sure, you have your project open
- Go to File -> Settings -> Project: [your project name] -> Project Interpreter
- Then click on the green plus symbol.
- In the search field type in Flask first. Select the first entry, which simply says Flask. Then click on Install Package in the bottom left corner.
- Do the same thing for Requests.
Pip
If you chose not to use PyCharm, you simply need to open up the terminal on linux or the command prompt, or PowerShell on Windows (simply press Windows+R and type in cmd) and then execute pip install Flask==0.12.2 requests==2.18.4
. If this gives you an error, please double check that you have pip installed. If the problem still occurs, please troubleshoot the error message using google, as there are too many possibilities, what the error could be caused by, for me to discuss every one of them here.
Getting a HTTP-Client
You will also need a HTTP-Client, as we need to make GET
and POST
requests to our Flask instance, at some Point in time. Mainly, there are two options - Postman or cURL. I highly recommend Postman, as it is easy and intuitive to use, but if you just want a simple and quick solution, without having to register for an account, just use cURL. Online solutions, probably won't work, as the webserver you are going to be trying to access, is just on your local machine.
Just gimme the source code!
If you just want the source code and don't want to follow my instructions, please scroll down, to the very end of this post, where I will add the source code in its complete beauty.
The Blockchain - Your Blockchain
Step 1: Creating a simplistic Blockchain
A block chain is basically a chain of blocks that are linked together in some way, as you have already figured. In this case, every block contains a hash of the previous block. A hash is a hexadecimal number that is determined by a specific algorithm. Imagine a hash being a digital fingerprint of an item. A good hash algorithm will never return the same hash for two different items, so every hash is individual. In our case we will be using the SHA-256 algorithm. Furthermore the hash algorithm is not reversible, so you can't determine the object, by having its hash, but you can always apply the hash algorithm on the object, to get its hash. As every item has the hash of its previous item, the blockchain is protected against violation, as one would have to change every following block, as the hash changes with every change, as two different items can't have the same hash.
The Blockchain class
Our Blockchain class basically consists of a constructor and some functions. In the constructor, we will create two empty lists - one to store our blockchain in and another, for our transactions. As for now, our class will have several functions:
- A function to create new blocks
- A function to create new transactions
- A function, which hashes a block
- A function to fetch the last block
The python template looks like this:
class MyBlockChain(object):
def __init__(self):
self.blockchain = []
self.transactions = []
def create_new_block(self):
# This function creates a new block and appends it to the chain
pass
def create_new_transaction(self):
# This function creates a new transaction and adds it to the list of transactions
pass
@staticmethod
def hash(block):
# This method hashes the passed block
pass
@property
def last_block(self):
# This function returns the last block in the chain
pass
Step 2: The blocks
As described earlier, a blockchain consists of blocks. So I will cover them first.
Each Block has 5 fields:
- An index,
- an Unix timestamp,
- a list of transactions,
- a proof,
- and finally the hash of the previous block.
Just to clarify things, here is an example of a block
example_block = {
'index': 5,
'timestamp': 1515110045.123456,
'transactions':
[
{
'sender': "154d8554f9541e12354e1234f512a001",
'recipient': "8654e21254c6512a51bce156542d1e5c",
'amount': 212,
}
],
'proof': 488612354568,
'previous_hash': "c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e"
}
Step 3: Hashing it
As every block needs the hash of the previous block, we need a function, that calculates the hash of a block. To accomplish this, we simply modify our empty hash
function
import hashlib
import json
class MyBlockChain(object):
[...]
@staticmethod
def hash(block):
"""
This function hashes the block using the SHA-256 hash algorithm
The function is given a block and returns a string, consisting of the hash
:param block: <dict> Block
:return: <str>
"""
# As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes
block_sorted = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_sorted).hexdigest()
Step 4: Transactions
Our create_new_transaction
method is responsible for adding new transactions to the transactions
list, which will then be stored inside a block. The whole function is pretty self explanatory. But first we need a function to return the last block of the chain, as we will calculate the new index, at which the transaction will be stored, by increasing the index of the last block by 1
class MyBlockChain(object):
[...]
@property
def last_block(self):
return self.blockchain[-1]
Now we can fill out the create_new_transaction
function
class MyBlockChain(object):
[...]
def create_new_transaction(self, sender, recipient, amount):
"""
This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions.
:param sender: <str> Sender's address
:param recipient: <str> Recipient's address
:param amount: <int> Amount to be transferred
:return: <int> This is the index of the block that will contain this transaction
"""
self.transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
Step 5: Creating new Blocks
By now we almost have everything at our hands, what we need in order to make a new block. The only thing missing is the proof field. As this is a little harder to understand, I will explain it later.
But we still have one problem - if every block has the hash of the previous block, what shall we do with our first block, as there is no previous block for us to hash.
This first block is called the genesis block.
import hashlib
import json
from time import time
class MyBlockChain(object):
def __init__(self):
self.blockchain = []
self.transactions = []
# Creation of the mentioned genesis block
self.create_new_block(previous_hash=1, proof=100)
def create_new_block(self, proof, previous_hash=None):
"""
Creates a new block and adds it to the blockchain
:param proof: <int> This is generated by the proof of work algorithm
:param previous_hash: (Optional) <str> Hash of the preleading Block
:return: <dict> Return the new block
"""
new_block = {
'index': len(self.blockchain) + 1,
'timestamp': time(),
'transactions': self.transactions,
'proof': proof,
'previous_hash': previous_hash or self.hash(self.blockchain[-1]),
}
# As all pending transactions have been processed and added to the block, the list can be resetted
self.transactions = []
# Add the new block to the blockchain
self.blockchain.append(new_block)
return new_block
Step 6: What's up with the proof?!
The Proof of Work (PoW) algorithm
This system wants to proof that work has been done. Now a little bit less abstract. Basically we are searching for a number that matches a specific criteria. The thing is, for the system to work, the process of finding a number matching the criteria has to be difficult, but the verification process has to be quick and easy. This is how new blocks are created, or expressed in another, more commonly used term, mined.
Still too abstract? Ok here is an example:
We are looking for a hash of a product of two integers that ends with a 1.
So the result of the multiplication of a
and b
gets hashed and has to end with a 1. If we set a to a
static value, our algorithm will determine a value for b
, for which this criteria is matched.
from hashlib import sha256
a = 12
b = 0 # We don't know what b has to be. It will be determined by the algorithm
while sha256(f'{a*b}'.encode()).hexdigest()[-1] != "1":
b += 1
print(f'The solution is b = {b}')
This example outputs
The solution is b = 4
This means that the hash of 12*4
ends with a 1
hash(48) = 98010bd9270f9b100b6214a21754fd33bdc8d41b2bc9f9dd16ff54d3c34ffd71
As you can tell, the verification process doesn't need countless loops, until it finds a matching pair. You just have to run the hash algorithm once on the numbers and you can easily determine, whether the numbers match the criteria, or not.
Step 7: Implementing PoW
Firstoff, we need a criteria, or commonly referred to as a rule. For now, our rule is that the hash has to have 5 leading zeroes, so 000005bc845e...
would be a valid result, but 000019ea76c4
would not. As the difficulty rises extremely, if we would require 6 leading zeros, the difficulty of our simple algorithm can be adjusted by changing the amount of leading zeroes required. This is the implementation in python:
[...]
from uuid import uuid4
class MyBlockChain(object):
[...]
def pow(self, last_proof):
"""
Simple PoW Algorithm:
- Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof.
:param last_proof: <int>
:return: <int>
"""
current_proof = 0
while self.validate_proof(last_proof, current_proof) is False:
current_proof += 1
return current_proof
@staticmethod
def validate_proof(last_proof, current_proof):
"""
Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes.
:param last_proof: <int> Previous Proof Number
:param current_proof: <int> Current Proof Number
:return: <bool>
"""
possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest()
return possible_hash[:5] == "00000"
Step 8: Interacting with our class
Now we are able to create a blockchain. But we can't really interact with it, as we have only interacted with the functions and variables of our class.
We are going to use HTTP requests to interact with our blockchain. So let's set it up!
HTTP in the house!
To talk to our blockchain using Http requests, we need some help - which comes in the form of the Flask Framework. We will create and implement 3 basic methods for now:
/blockchain
to retrieve the full blockchain/mining
to make our simple server mine a block/transactions/add
to add a new transaction to the blockchain.
Step 9: Flask
First we need to implement Flask. Here is the code:
[...]
from textwrap import dedent
from flask import Flask, jsonify, request
class MyBlockChain(object):
[...]
app = Flask(__name__)
node_identifier = str(uuid4()).replace('-', '')
myblockchain = MyBlockChain()
@app.route('/blockchain', methods=['GET'])
def get_full_chain():
output = {
'chain': myblockchain.blockchain,
'length': len(myblockchain.blockchain),
}
return jsonify(output), 200
@app.route('/mining', methods=['GET'])
def mining():
pass
@app.route('/transactions/add', methods=['POST'])
def add_transaction():
pass
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Step 10: Adding Transactions
As this is commonly the main task of a block chain and also required for a new block, I will cover this first. The user sends a POST request to /transactions/add
with a JSON object, containing all required fields. This is an example, for a possible, valid request:
{
"sender": "154d8554f9541e12354e1234f512a001",
"recipient": "8654e21254c6512a51bce156542d1e5c",
"amount": 212
}
As you can see, a request has three fields:
- A sender field, with the sender id,
- a recipient field, with the recipient id
- and an amount, which defines how many units are to be transferred
Now we just need to implement this function
[...]
@app.route('/transactions/add', methods=['POST'])
def add_transaction():
values = request.get_json()
# The POST request has to have the following required fields
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'There are values missing', 400
# Adds a new transaction by utilizing our function
index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount'])
output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'}
return jsonify(output), 201
This function simply calls the create_new_transaction
method with the parameters of the POST request
Step 11: Setup your mines
As discussed earlier, future blocks have to be mined. If a new block is found/mined, all pending transactions are added to this block. To 'mine' a new block, our function hast to do three things
- Calculate the proof
- Reward the miner with a specific amount of coins (in our example 1 coin)
- Add the new mined block to the blockchain
(The fact that the recipient is our current node, will make more sense, as we will talk about decentralization later on.)
@app.route('/mining', methods=['GET'])
def mining():
# Calculate the new proof by using our PoW algorithm
last_block = myblockchain.last_block
last_proof = last_block['proof']
proof = myblockchain.pow(last_proof)
# For finding/mining the proof, the miner is granted a reward
# The sender is nobody, as this coin is coming out of the void
myblockchain.create_new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)
# Add the new created block to the chain
previous_hash = myblockchain.hash(last_block)
newblock = myblockchain.create_new_block(proof, previous_hash)
output = {
'message': "A new block was mined",
'index': newblock['index'],
'transactions': newblock['transactions'],
'proof': newblock['proof'],
'previous_hash': newblock['previous_hash'],
}
return jsonify(output), 200
Hello World!
Now, finally we will be interacting with our blockchain API. Grab your HTTP client of choice and get going! I will explain the procedure for Postman and cURL.
Step 12: Start up the chain
First off fire up your server.
In PyCharm simply click on Run -> Run... and select [your filename].py as the file to run, in my case myblockchain.py.
If you chose to use a simple python file without an IDE, fire up a terminal or command prompt window inside the directory your file is located in and execute the following command
python blockchain.py
as a result you will see something similar to this
$ python blockchain.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Note that you may be prompted with a dialogue of your firewall. Just accept it.
Step 13: Mining it all!
Ok let's try the most exciting thing first, mining. I'm sure you can't wait to mine your first block, so let's do it!
As you can see in the code
[...]
@app.route('/mining', methods=['GET'])
def mining():
[...]
we will be interacting with the mining
part of our API through GET
requests. This makes sense, as our only goal with this part of the API is, to mine a new block. We don't have to pass anything to the function at all, so a GET
request is the way to go.
Postman
If you chose to use Postman, here is a quick tutorial on how to make a post request.
- First you have to register for an account, if you haven't already done that
- and login after that.
- If you are logged in, you shoudl be provided with several options such as Request, Collection, Environment.
- You could simply use the option Request, but to keep things organized, we are going to select the Collection option.
- After that simply choose a name for your collection, in my case I'm using MyBlockChain as my name.
- Now click on the New Button, located in the top left corner and select Request this time. We are now going to be creating 3 requests.
- The first request is going to be our mining request, second is a new transaction request and finally we will have a get whole blockchain request. Of course you can customize the request names as you like.
- Make sure, as you create these 3 requests, that you select our newly created Collection in the bottom section of the dialogue!
- Now click on our Collection's folder in the left sidebar. You should see your 3 newly created requests. Select the mining request first.
- On the right you are now provided with an interface. As you can see by the selected item from the dropdown box, left to the text field, a
GET
request is the standard request type, so we don't have to change that yet. - In the text field
Enter request URL
we are now going to enter themining
URL of our API, so enterhttp://localhost:5000/mining
as the request URL.
cURL
If you chose to use cURL as your HTTP client, simply type curl -i -H "Content-Type: application/json" -X GET http://localhost:5000/mining
in a terminal window
Either way
With both methods you should see something similar to this as a result
{
"index": 2,
"message": "New Block Forged",
"previous_hash": "0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a",
"proof": 888273,
"transactions": [
{
"amount": 1,
"recipient": "491002ea048a42609d967027cc46a07b",
"sender": "0"
}
]
}
Step 14: Transactions are great!
As our mining algorithm seems to be working fine, let's create a new transaction, to be stored in a fresh mined block. To do that in
Postman
- Click on the new transaction request in the left sidebar.
- Click on the GET button with the small arrow pointing down.
- Select POST from the drop-down list.
- In the navbar below the text field, select Body
- and then mark the raw option.
- You should now be provided with an editor like text area, and another drop-down menu next to the binary option, where Text is currently selected. Select JSON (application/json) from this list.
- Now type in the following into the text area.
{
"sender": "your-node-address",
"recipient": "another-node-address",
"amount": 212
}
cURL
Just execute curl -X POST -H "Content-Type: application/json" -d '{"sender": "your-node-address", "recipient": "another-node-address", "amount": 212}' "http://localhost:5000/transactions/add"
in a terminal window.
In both cases
Replace your-node-address
with the recipient address from you mining request, as this is your node address. You can also select any other 32 digit hexadezimal number, for now. Furthermore replace another-node-address
with a random 32 digit hexadezimal number for now, as we currently don't have any other nodes.
Adjust the amount
to your liking.
And voilà! You have registered your first transaction!
Step 15: The Chain...
So let's inspect our chain! If you're using
Postman
Click on the get whole blockchain request in the left sidebar and simply enter http://localhost:5000/blockchain
in the Enter request URL
text field, as GET should be preselected. If not, please change it to the GET option.
cURL
just execute curl -i -H "Content-Type: application/json" -X GET http://localhost:5000/blockchain
in a terminal window.
Either way
you should get your whole blockchain in form of a JSON-Object. In my case, my blockchain looks like this, after mining 2 times, adding 3 transactions and mining another block:
{
"chain": [
{
"index": 1,
"previous_hash": "1",
"proof": 100,
"timestamp": 1515156902.4502413,
"transactions": []
},
{
"index": 2,
"previous_hash": "0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a",
"proof": 888273,
"timestamp": 1515156913.2686973,
"transactions": [
{
"amount": 1,
"recipient": "491002ea048a42609d967027cc46a07b",
"sender": "0"
}
]
},
{
"index": 3,
"previous_hash": "1d2ae4a41f4a82ce6f04944e8227bf9c4d951d560f8763b910d7314634dfe09c",
"proof": 1156297,
"timestamp": 1515158367.3596537,
"transactions": [
{
"amount": 212,
"recipient": "d4ee26eee15148ee92c6cd394edd974e",
"sender": "491002ea048a42609d967027cc46a07b"
},
{
"amount": 212,
"recipient": "5654c5654e1b551a65e15f5e4651c156",
"sender": "491002ea048a42609d967027cc46a07b"
},
{
"amount": 111,
"recipient": "491002ea048a42609d967027cc46a07b",
"sender": "5654c5654e1b551a65e15f5e4651c156"
},
{
"amount": 1,
"recipient": "491002ea048a42609d967027cc46a07b",
"sender": "0"
}
]
}
],
"length": 3
}
Decentralizing it
Ok, so we got a blockchain, that's working fine and we can interact with it. But one very important thing is still missing: Decentralization.
This is a core element of the blockchain idea.
First off by decentralizing the whole thing, safety and integrity of the blockchain is guaranteed, as a change in the blockchain will be recognized, by the other nodes, as they also have a copy of the blockchain, and discarded.
The second reason is that we somehow have to make sense of our transactions. For this we need other nodes and why shouldn't they contribute to our network, by being a part of the blockchain system?!
But we still have a conflict there: What if a node has a blockchain that differs from all the others?
For our simple blockchain we will always make the longest valid blockchain authorative.
Step 16: Someone out there?!
But first things first.
To get started, we first have to implement other nodes into our system.
To accomplish this task, we have to adjust a few things in our API. First off, we are going to implement some new endpoints:
/nodes/add
to add new nodes in form of a list of URLs/nodes/resolve
to resolve the conflict we discussed previously by using a consensus algorithm.
To do this, we have to modify our Blockchain class first:
[...]
from urllib.parse import urlparse
class MyBlockChain(object):
def __init__(self):
[...]
self.nodes = set()
def add_node(self, address):
"""
Register a new node
:param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000'
:return: None
"""
node_url = urlparse(address)
self.nodes.add(node_url.netloc)
Step 17: We need Consensus
To resolve the conflict of differing blockchains between the nodes, we are going to implement a consensus algorithm. As we discussed earlier, we are always utilizing the longest valid blockchain.
First we are implementing a function that validates a blockchain:
[...]
import requests
class MyBlockChain(object)
[...]
def validate_blockchain(self, test_chain):
"""
Evaluate, whether a blockchain is valid
:param chain: <list> The blockchain that shall be tested
:return: <bool> Returns true if the blockchain is valid
"""
last_block = test_chain[0]
current_index = 1
while current_index < len(test_chain):
block = test_chain[current_index]
# Determine, whether the hash is correct
if block['previous_hash'] != self.hash(last_block):
return False
# Determine, wheter the proof of work is correct
if not self.validate_proof(last_block['proof'], block['proof']):
return False
last_block = block
current_index += 1
return True
Now we just have to run every blockchain in our surrounding network through this consensus algorithm:
[...]
class MyBlockChain(object)
[...]
def resolve(self):
"""
This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously.
:return: <bool> Returns true, if our chain was substituted with an updated version
"""
surrounding_nodes = self.nodes
updated_chain = None
# If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain
max_length = len(self.blockchain)
# Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain.
for node_iterator in surrounding_nodes:
response = requests.get(f'http://{node_iterator}/blockchain')
if response.status_code == 200:
length = response.json()['length']
chain_iterator = response.json()['chain']
if length > max_length and self.validate_blockchain(chain_iterator):
max_length = length
updated_chain = chain_iterator
# If we found a new blockchain, replace our current blockchain with the new one
if updated_chain:
self.blockchain = updated_chain
return True
return False
Now we just have to register our new functions as new endpoints in the API:
@app.route('/nodes/add', methods=['POST'])
def add_nodes():
values = request.get_json()
nodes = values.get('nodes')
if nodes is None:
return "You didn't post a valid list of nodes. Please double check your input!", 400
for node in nodes:
myblockchain.add_node(node)
response = {
'message': 'All of your specified nodes have been added to the network!',
'total_nodes': list(myblockchain.nodes),
}
return jsonify(response), 201
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
replaced = myblockchain.resolve()
if replaced:
response = {
'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.',
'new_chain': myblockchain.blockchain
}
else:
response = {
'message': 'The blockchain of this node is already the longest valid chain within the network.',
'chain': myblockchain.blockchain
}
return jsonify(response), 200
Step 18: Friends are great. Even if they're virtual...
To test our decentralization algorithms, either grab some friends, to run some nodes for you, or just run the same program on different ports, so you would have to start up an instance of our blockchain-program, then change to port to 5001 for example, save, fire up an instance of this modified program, change the port to 5002, etc.
Step 19: Add them to your friends list
If you got some instances, you just have to register them. To do this, simply send a POST
request with the following content
{
"nodes": ["http://[ip-of-another-node-or-127.0.0.1-for-your-local-machine]:[the-port-number]"]
}
to http://[the-node-ip-you-want-to-register-it-to]:[port]/nodes/add
Step 20: Consensus check
After that try to mine some blocks and even add some transactions on let's say node 2 and then run http://[a-node-ip]:[port]/nodes/resolve
on let's say node 1. The consensus algorithm should step into action and update the blockchain of node 1.
The whole thang
I keep my promises
import hashlib
import json
import requests
from time import time
from uuid import uuid4
from textwrap import dedent
from flask import Flask, jsonify, request
from urllib.parse import urlparse
class MyBlockChain(object):
def __init__(self):
self.blockchain = []
self.transactions = []
# Creation of the mentioned genesis block
self.create_new_block(previous_hash=1, proof=100)
self.nodes = set()
def create_new_block(self, proof, previous_hash=None):
"""
Creates a new block and adds it to the blockchain
:param proof: <int> This is generated by the proof of work algorithm
:param previous_hash: (Optional) <str> Hash of the preleading Block
:return: <dict> Return the new block
"""
new_block = {
'index': len(self.blockchain) + 1,
'timestamp': time(),
'transactions': self.transactions,
'proof': proof,
'previous_hash': previous_hash or self.hash(self.blockchain[-1]),
}
# As all pending transactions have been processed and added to the block, the list can be resetted
self.transactions = []
# Add the new block to the blockchain
self.blockchain.append(new_block)
return new_block
def create_new_transaction(self, sender, recipient, amount):
"""
This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions.
:param sender: <str> Sender's address
:param recipient: <str> Recipient's address
:param amount: <int> Amount to be transferred
:return: <int> This is the index of the block that will contain this transaction
"""
self.transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
@staticmethod
def hash(block):
"""
This function hashes the block using the SHA-256 hash algorithm
The function is given a block and returns a string, consisting of the hash
:param block: <dict> Block
:return: <str>
"""
# As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes
block_sorted = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_sorted).hexdigest()
@property
def last_block(self):
return self.blockchain[-1]
def pow(self, last_proof):
"""
Simple PoW Algorithm:
- Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof.
:param last_proof: <int>
:return: <int>
"""
current_proof = 0
while self.validate_proof(last_proof, current_proof) is False:
current_proof += 1
return current_proof
@staticmethod
def validate_proof(last_proof, current_proof):
"""
Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes.
:param last_proof: <int> Previous Proof Number
:param current_proof: <int> Current Proof Number
:return: <bool>
"""
possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest()
return possible_hash[:5] == "00000"
def add_node(self, address):
"""
Register a new node
:param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000'
:return: None
"""
node_url = urlparse(address)
self.nodes.add(node_url.netloc)
def validate_blockchain(self, test_chain):
"""
Evaluate, whether a blockchain is valid
:param chain: <list> The blockchain that shall be tested
:return: <bool> Returns true if the blockchain is valid
"""
last_block = test_chain[0]
current_index = 1
while current_index < len(test_chain):
block = test_chain[current_index]
# Determine, whether the hash is correct
if block['previous_hash'] != self.hash(last_block):
return False
# Determine, wheter the proof of work is correct
if not self.validate_proof(last_block['proof'], block['proof']):
return False
last_block = block
current_index += 1
return True
def resolve(self):
"""
This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously.
:return: <bool> Returns true, if our chain was substituted with an updated version
"""
surrounding_nodes = self.nodes
updated_chain = None
# If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain
max_length = len(self.blockchain)
# Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain.
for node_iterator in surrounding_nodes:
response = requests.get(f'http://{node_iterator}/blockchain')
if response.status_code == 200:
length = response.json()['length']
chain_iterator = response.json()['chain']
if length > max_length and self.validate_blockchain(chain_iterator):
max_length = length
updated_chain = chain_iterator
# If we found a new blockchain, replace our current blockchain with the new one
if updated_chain:
self.blockchain = updated_chain
return True
return False
app = Flask(__name__)
node_identifier = str(uuid4()).replace('-', '')
myblockchain = MyBlockChain()
@app.route('/blockchain', methods=['GET'])
def get_full_chain():
output = {
'chain': myblockchain.blockchain,
'length': len(myblockchain.blockchain),
}
return jsonify(output), 200
@app.route('/mining', methods=['GET'])
def mining():
# Calculate the new proof by using our PoW algorithm
last_block = myblockchain.last_block
last_proof = last_block['proof']
proof = myblockchain.pow(last_proof)
# For finding/mining the proof, the miner is granted a reward
# The sender is nobody, as this coin is coming out of the void
myblockchain.create_new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)
# Add the new created block to the chain
previous_hash = myblockchain.hash(last_block)
newblock = myblockchain.create_new_block(proof, previous_hash)
output = {
'message': "A new block was mined",
'index': newblock['index'],
'transactions': newblock['transactions'],
'proof': newblock['proof'],
'previous_hash': newblock['previous_hash'],
}
return jsonify(output), 200
@app.route('/transactions/add', methods=['POST'])
def add_transaction():
values = request.get_json()
# The POST request has to have the following required fields
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'There are values missing', 400
# Adds a new transaction by utilizing our function
index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount'])
output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'}
return jsonify(output), 201
@app.route('/nodes/add', methods=['POST'])
def add_nodes():
values = request.get_json()
nodes = values.get('nodes')
if nodes is None:
return "You didn't post a valid list of nodes. Please double check your input!", 400
for node in nodes:
myblockchain.add_node(node)
response = {
'message': 'All of your specified nodes have been added to the network!',
'total_nodes': list(myblockchain.nodes),
}
return jsonify(response), 201
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
replaced = myblockchain.resolve()
if replaced:
response = {
'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.',
'new_chain': myblockchain.blockchain
}
else:
response = {
'message': 'The blockchain of this node is already the longest valid chain within the network.',
'chain': myblockchain.blockchain
}
return jsonify(response), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Final thoughts
And there you have it!
Your very own blockchain. Wasn't very hard, was it?
I sincerely hope you had fun thorughout this tutorial and were able to understand the concept and system behind it better. If you have any questions, please ask them in the comments and I'll try my best to answer them.
The obligatory begging
As this whole tutorial took me over 2 days to finish, I would love to see a lot of comments and feedback under this post. Also if you find any typos, grammar mistakes, etc. and care to tell me, i would be mor than happy about it, as English is not my native language. So please excuse any mistakes I made. I tried my very best.
Of course please upvote, if you worship my effort to enrich the community, and if you want to see more such content, consider following me.
I wish you only the best - See ya!
dustvoice!! Thank you, your Post.
You're welcome :)
Congratulations @dustvoice! You received a personal award!
Click here to view your Board
Congratulations @dustvoice! You received a personal award!
You can view your badges on your Steem Board and compare to others on the Steem Ranking
Vote for @Steemitboard as a witness to get one more award and increased upvotes!