Part I: Class

A Blockchain is a data structure that contains blocks of information chained by hashes. In Python, we will first create a BlockChain class, which is simply initiated with a chain list. The @property is a built-in decorator that turn method into attributes.

1
2
3
4
5
6
7
8
9
class Blockchain(object):
def __init__(self):
self.chain = []

@property
def last_block(self):
return self.chain[-1]

blockchain = Blockchain()

We then need to set up a genesis block as a starting point of the chain. We achieve this by creating a method to add new blocks to the class instance.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from time import time

class Blockchain(object):
def __init__(self):
self.chain = []
self.new_block()

def new_block(self):
block = {
'index': len(self.chain) + 1,
'timestamp' = time()
}
self.chain.append(block)
return block

So now we have a chain of blocks following the genesis block. This blockchain can be useful in storing any type of information. However, we specifically want to have a way to encrypt the blockchain chronologically such that it does not allow alteration in any previous blocks.

For example, one day you record some information at block #2:

1
block #0 -> block #1 -> block #2 {'my.info': '123'}

Three days later, the blockchain grow to four blocks, with new info added by other people. However, you look at it and found that your previous information has be altered without your approval.

1
2
3
4
5
Your blockchain:
block #0 -> block #1 -> block #2 {'my.info': '123'} -> block #3 -> block #4

Attacker's blockchain
block #0 -> block #1 -> block #2 {'my.info': '234'} -> block #3 -> block #4

This is a the famous double-spending problem that Satoshi Nakamoto laborious tried to tackly in the original Bitcoin white paper. Put aside being a currency for now, let’s just consider making our blockchain to be a secure, dependable data structure for record keeping purpose, we need to add two important features: hash function and consensus mechanism.

  • A hash function, such as SHA-2 (Secure Hash Algorithm 2) is an algorithm that maps data of arbitrary size to a bit string of fixed size. It is practically infeasible to invert, meaning that with only the bit string and the hash algorithm, one is not able to access the original data. As we include the hash of all information in the previous block when creating a new block, we will be able to quickly identify any altered earlier block just by looking at the hash included in the last block.
1
2
3
4
5
Your blockchain:
block #0 -> block #1 -> block #2 {'my.info': '123'} -> block #3 -> block #4 ('hash': 000)

Attacker's blockchain:
block #0 -> block #1 -> block #2 {'my.info': '234'} -> block #3 -> block #4 ('hash': abc)


  • A consensus mechanism ensures that, at all times, there is only one blockchain that is considered legitimate by consensus. The PoW, or Proof of Work, is a typical consensus mechanism that requires CPU power to expand the blockchain and therefore acknowledge the longest blockchain (most CPU work) as legitimate. This ensures that any attempt to alter historical information becomes costly in CPU power and therefore infeasible.
1
2
3
4
5
Your blockchain:
block #0 -> block #1 -> block #2 {'my.info': '123'} -> block #3 -> block #4 ... -> block 900

Attacker's blockchain: (shorter chain, because changing block #2 delays its block building)
block #0 -> block #1 -> block #2 {'my.info': '234'} -> block #3 -> block #4 ... -> block 300

Bitcoin uses the HashCash PoW algorithm.

We now incorporate the hash function in our blockchain. The @staticmethod decorater does not implicitly pass self or cls as the first argument, and behave like plain functions except that you can call them from an instance of the class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import hashlib
import json

class Blockchain(object):
...
def new_block(self, previous_hash=None):
block = {
...
'previous_hash': previous_hash or self.hash(self.chain[-1]),
}
...

@staticmethod
def hash(block):
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()


Adding PoW. Adding a new block on the blockchain will require computation of a new hash repeatedly until it finds a number from which the first four digit of the new hash is ‘0000’:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Blockchain(object):
...
def proof_of_work(self, last_proof):
proof = 0
while self.valid_proof(last_proof, proof) is False:
proof += 1
return proof

@staticmethod
def valid_proof(last_proof, proof):
guess = f'{last_proof}{proof}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"


Adding a more granular data unit, transaction. Until a new block is added, all transactions will be appended to the same block chronologically.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Blockchain(object):
def __init__(self):
...
self.current_transactions = []
...

def new_block(self, previous_hash=None):
block = {
...
'transactions': self.current_transactions,
...
}

self.current_transactions = []
...

def new_transaction(self, sender, recipient, amount):
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1


Part II: Flask

We will use Flask, which is a web framework that allows easy mapping to Python functions, to host our blockchain interface.

1
2
3
4
5
from flask import Flask, jsonify, request
from uuid import uuid4

app = Flask(__name__)
node_identifier = str(uuid4()).replace('-', '')


Next, we create two GET end-point for us to create new blocks (a.k.a. mining new blocks) and view the current blockchain.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@app.route('/mine', methods=['GET'])
def mine():
last_block = blockchain.last_block
last_proof = last_block['proof']
proof = blockchain.proof_of_work(last_proof)

blockchain.new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)

block = blockchain.new_block(proof)
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('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200



Create a POST end-point to add new transaction to the current block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()

required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'Missing values', 400

index = blockchain.new_transaction(values['sender'],
values['recipient'],
values['amount'])

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

Run the script on main.

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

Open up http://0.0.0.0:5000/mine. You should see a new block mined everytime you refresh this page. (Note that the time required for mining is determined by the number of ‘0’ required in the PoW. Try changing ‘0000’ to ‘00000’ and observe that the PoW time increases astronomically)

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"index": 2,
"message": "New Block Forged",
"previous_hash": "c09cdcab291c295ee546026da0f38243334a887971b4b8b2733f09ad4db7b573",
"proof": 35293,
"transactions": [
{
"amount": 1,
"recipient": "46c3a90befc249199a1a0641172de705",
"sender": "0"
}
]
}

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"index": 3,
"message": "New Block Forged",
"previous_hash": "448670bc7414268d220585908d4f20463c13e1328a5629370ee7f018b1d0ae2b",
"proof": 35089,
"transactions": [
{
"amount": 1,
"recipient": "46c3a90befc249199a1a0641172de705",
"sender": "0"
}
]
}

Open up http://0.0.0.0:5000/chain to see the current blockchain.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"chain": [
{
"index": 1,
"previous_hash": 1,
"proof": 100,
"timestamp": 1569915417.781303,
"transactions": []
},
{
"index": 2,
"previous_hash": "c09cdcab291c295ee546026da0f38243334a887971b4b8b2733f09ad4db7b573",
"proof": 35293,
"timestamp": 1569915544.2279942,
"transactions": [
{
"amount": 1,
"recipient": "46c3a90befc249199a1a0641172de705",
"sender": "0"
}
]
},
{
"index": 3,
"previous_hash": "448670bc7414268d220585908d4f20463c13e1328a5629370ee7f018b1d0ae2b",
"proof": 35089,
"timestamp": 1569915726.590616,
"transactions": [
{
"amount": 1,
"recipient": "46c3a90befc249199a1a0641172de705",
"sender": "0"
}
]
}
],
"length": 3
}

Note that currently there is only 1 transaction each block. Use the following curl command in your terminal to add a second transaction in the incoming block #4.

1
2
3
4
5
$ curl -X POST -H "Content-Type: application/json" -d '{
"sender": "d4ee26eee15148ee92c6cd394edd974e",
"recipient": "someone-other-address",
"amount": 5
}' "http://localhost:5000/transactions/new"

1
2
3
{
"message": "Transaction will be added to Block 4"
}

Mining block #4 with http://0.0.0.0:5000/mine. You can now see the transaction just added with the curl command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"index": 4,
"message": "New Block Forged",
"previous_hash": "31309aade27e773b822a1454476455a4d5c538545a0e5bdc72fe0f4f229fdf33",
"proof": 119678,
"transactions": [
{
"amount": 5,
"recipient": "someone-other-address",
"sender": "d4ee26eee15148ee92c6cd394edd974e"
},
{
"amount": 1,
"recipient": "46c3a90befc249199a1a0641172de705",
"sender": "0"
}
]
}


Part III: Consensus Algorithm

We now create the consensus algorithm that acknowledge the longest blockchain among the all observed ones. First we need to create a registry for any new blockchain discovered by the server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from urllib.parse import urlparse

class Blockchain(object):
def __init__(self):
...
self.nodes = set()

def register_node(self, address):
parsed_url = urlparse(address)
self.nodes.add(parsed_url.netloc)


@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

Now run two blockchain instance through different ports http://0.0.0.0:5000 and http://0.0.0.0:5001. Enter the following terminal command to register the address ‘http://0.0.0.0:5001‘ with the first port-5000 blockchain.

1
2
3
curl -X POST -H "Content-Type: application/json" -d '{
"nodes": ["http://0.0.0.0:5001/"]
}' "http://0.0.0.0:5000/nodes/register"

1
2
3
4
5
6
{
"message": "New nodes have been added",
"total_nodes": [
"0.0.0.0:5001"
]
}

We then can add the consensus algorithm.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import requests
class Blockchain(object):
def valid_chain(self, chain):
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")
if block['previous_hash'] != self.hash(last_block):
return False

if not self.valid_proof(last_block['proof'], block['proof']):
return False

last_block = block
current_index += 1

return True

def resolve_conflicts(self):
neighbours = self.nodes
new_chain = None
max_length = len(self.chain)

for node in neighbours:
response = requests.get(f'http://{node}/chain')

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

if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain

if new_chain:
self.chain = new_chain
return True

return False

@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

Now fire up the blockchain on the two ports again, and mine a few blocks on the 5001 port. Register the 5001 port with the 5000 port and then open up http://0.0.0.0:5000/nodes/resolve. You should see that the 5000-chain is changed to the longer 5001 chain.






Reference