Pychain: Blockchain in 200 lines of Python

Since in the introduction of Bitcoin and the explosion of crypto currencies has created quite a lot of attention around the concept of Blockchain. In this post I will try to explain the concept by implementing Pychain, a toy Blockchain, in about 200 lines of Python.

What is Blockchain?

Wikipedia defines Blockchain as

A blockchain, originally block chain, is a growing list of records, called blocks, which are linked using cryptography.

If we simplify this a little further for easier understanding, we can define Blockchain as a Linked List of Blocks. In a traditional Linked List each node points to its succeeding node. However in Blockchain, each Block points to its preceding Block.

Just another Block in the chain

Continuing with our Linked list analogy, each block each block must contain meta data to validate the integrity of the chain. In Pychain we include on the most necessary: index, nonce, timestamp, data, previous hash, and hash.

block-info

Most of the attributes are self explanatory, except of nonce. We will discuss about a little bit later.

class Block:
def __init__(self, index, nonce, prev_hash, timestamp, data, hash):
self.index = index
self.nonce = nonce
self.prev_hash = prev_hash
self.data = data
self.timestamp = timestamp
self.hash = hash
view raw block.py hosted with ❤ by GitHub

Hash

Each block being added into the chain is validated by calculating the hash. SHA-256 in Pychain. The way hash is calculated is take all the fields (index, nonce, timestamp, data, and previous hash) and calculate SHA-256 sum.

import hashlib
def calculate_hash(index, nonce, timestamp, data, prev_hash):
block_val = str(index) + str(nonce) + prev_hash + str(timestamp) + data
return hashlib.sha256(block_val.encode("utf-8")).hexdigest()
view raw hash.py hosted with ❤ by GitHub

The process of calculating the hash is defined as “Mining” in bitcoin and other crypto currencies. If it were this simple, then validity of the chain can compromised very easily. To make it difficult to compromise each miner has to spend some time and money “working”. This is called “Proof of Work”.

Proof of Work

SHA-256 is always 256 bits or 64 bytes long no matter how long the input is. For example hash for text, “Hello, Pychain”, it is 368fd26488e3fda58ec81fc5bdc0ea6b919526f765cfddc315fb1d81b9793649.


$ echo Hello, Pychain | sha256sum
368fd26488e3fda58ec81fc5bdc0ea6b919526f765cfddc315fb1d81b9793649 -

Computing this hash hardly takes any effort. To increase the effort needed to mine a block, Bitcoin arbitrarily choose certain number of digits in the hash to be zeros. At the time of writing this post Bitcoin expects the first 18 digits of the hash to be zeros.  For a given input, SHA-256 will always compute the same hash. This is where the field “nonce” defined in our block comes to picture.

mine-algo
Simple algorithm for Proof of Work

For our Blockchain, we will set the default “difficulty” to 4, but while running this value can be set to whatever we want.

import hashlib
difficulty = 4
pattern = '0000'
def calculate_hash(data_to_hash):
return hashlib.sha256(data_to_hash.encode("utf-8")).hexdigest()
def mine_block(index, prev_hash, timestamp, data):
for nonce in range(1, maxNonce):
blockVal = str(index) + str(nonce) + prev_hash + str(timestamp) + data
hash = calculate_hash(blockVal)
if hash[:difficulty] == pattern:
return Block(index, nonce, prev_hash, timestamp, data, hash)
view raw mine_pychain.py hosted with ❤ by GitHub

Adding a new Block

Now that we have some basic infrastructure in place we can start adding blocks to Pychain. To mine a new block we need a information about the previous block and we compute the rest of the fields, index, time stamp. Data is provided by the end user.

def add_block (data):
prev_block = get_last_block()
timestamp = time.time()
new_block = mine_block(prev_block.index + 1, prev_block.hash, timestamp, data)
return new_block
view raw add_block.py hosted with ❤ by GitHub

Genesis Block

If we need the previous block to add a new block, how do we kick start the chain? This chicken and egg problem is solved by what is called the “Genesis Block”. This is a special kind of block with previous hash set to all zeros. Pychain generates this block like this.

def mine_genesis_block ():
timestamp = time.time()
return mine_block(0, "0", timestamp, "Genisis Block")

In a real world Blockchain, genesis block is computed only once.

Storing the Blocks

As the number of blocks grow, we need a way to persist the historical block. Pychain does not have persistence, we will store them as a in-memory array.

def mine_genesis_block ():
timestamp = time.time()
return mine_block(0, "0", timestamp, "Genisis Block")
chain = [mine_genesis_block()]
view raw init_pychain.py hosted with ❤ by GitHub

Authenticity of a Block

So far Pychain has been single node Blockchain. Although it is a valid chain, it does not serve much of a purpose. When we have multiple peer nodes in the network we need to way to validate if the new Block being added to the chain is authentic. Each node upon receiving a new Block will try to validate the block. Only valid Blocks will be added to the chain.

def is_block_valid(next_block, last_block):
if last_block.index + 1 != int(new_block["index"]):
return False
if last_block.hash != new_block["prev_hash"]:
return False
block_val = str(new_block["index"]) + str(new_block["nonce"]) + new_block["prev_hash"] + \
str(new_block["timestamp"]) + new_block["data"]
if calculate_hash(block_val) != new_block["hash"]:
return False
return True

Longest chain wins

Real world Blockchain implementations will trust the chain with the maximum work done, or in other words the longest chain always wins. This way as the chain grows, it becomes harder and harder to manipulate the chain. For an attacker to take over the blockchain has to mine all the blocks since the “fraudulent” block and be faster than 51% of all the nodes in doing so. For the sake of simplicity, Pychain only verifies if the index of the new Block is greater than the previous known block and the new block passes the validation.

Peer to Peer Protocol

All Blockchain implementations have a protocol to communicate between peers. Pychain has a bare bones protocol to achieve this. Websockets are used to communicate with other peers in the network. Pychain supports only two “commands”, QUERY_ALL and NEW_BLOCK.

Whenever a new node joins the network, it will try to ask one of its peers for the current state of the Blockchain using the QUERY_ALL command. If a new block is mined by this node it will send out a broadcast message to all the peers it is aware of with a NEW_BLOCK command. When a node receives a NEW_BLOCK message it will follow the block authentication process and decide to add the block or discard the block.

Manage the node

Each Pychain node will also expose a HTTP endpoint to allow for end user interaction.

class HttpHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/blocks':
self.set_response_headers(200)
self.wfile.write(bytes(json.dumps(chain, default=jdefault), "utf8"))
elif self.path == '/peers':
self.set_response_headers(200)
self.wfile.write(bytes(json.dumps(peers), "utf8"))
else:
self.set_response_headers(400, "plain/text")
message = "Bad request"
self.wfile.write(bytes(message, "utf8"))
return
def do_POST(self):
length = int(self.headers['Content-Length'])
post_body = json.loads(self.rfile.read(length))
new_block = add_block(post_body["data"])
send_broadcast(new_block)
self.set_response_headers(201)
self.wfile.write(bytes(json.dumps(new_block, default=jdefault), "utf-8"))
def set_response_headers(self, code, content_type="application/json"):
self.send_response(code)
self.send_header("Content-type", content_type)
self.end_headers()
view raw pychain_http.py hosted with ❤ by GitHub


# get all blocks known to this peer
$ curl http://localhost:6001/blocks

Finally

Working code for Pychain can be found here. Pychain is a simplified implementation of Blockchain to help understand the concept, and it is not suited for anything other than that.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s