Creating a blockchain in 60 lines of Python

Jose Angel Munoz - Nov 5 '21 - - Dev Community

When I read the document written by Phu Minh, I was curious about learning different concepts about blockchain. Once I started to read the code, I wanted to match it with Python to understand also the differences with JavaScript.

The objective of this post is finding the differences in both languages and serve as the Python appendix of the original post.

Even though the original document comes from a Python example, I wanted to have an exact match with the JavaScript code to compare.

Let's also fit the python code in the promised 60 lines.

Blockchain

Although the idea is to mimic the entire post and use the same sections to follow the code,

For the Blockchain definition, I prefer the following:

Blockchain is a system of recording information in a way that makes it difficult or impossible to change, hack, or cheat.

Setup

We are using Python for this project, so be sure to install it if you haven't.

As I have said, a block is just an object that has some information on it, so we should have a Block class like this:

class Block:

    def __init__(self, timestamp=None, data=None):
        self.timestamp = timestamp or time()
        # this.data should contain information like transactions.
        self.data = [] if data is None else data
Enter fullscreen mode Exit fullscreen mode

The class definition is quite similar in both languages. In Python, we use self instead of this and init is a constructor method.

Comments are also similar in both languages. In Python, we use # to comment vs. // in javascript.

Fot the sha256 algorithn, I will use the hashlib library vs the crypto package in javascript.

from hashlib import sha256

class Block:

    def __init__(self, timestamp=None, data=None):
        self.timestamp = timestamp or time()
        self.data = [] if data is None else data
        self.hash = self.getHash()
        self.prevHash = None # previous block's hash

    def getHash(self):
        hash = sha256()
        hash.update(str(self.prevHash).encode('utf-8'))
        hash.update(str(self.timestamp).encode('utf-8'))
        hash.update(str(self.data).encode('utf-8'))
        return hash.hexdigest()
Enter fullscreen mode Exit fullscreen mode

In the getHash method, from an empty hash, we update it with the rest of components. The hash is the result of the concatenation of the previous hash, the timestamp and the data. All of then with the .encode('utf-8') to convert the string to bytes.

The blockchain

Let's move over to the blockchain class.

class Blockchain:
    def __init__(self):
        # This property will contain all the blocks.
        self.chain = []
Enter fullscreen mode Exit fullscreen mode

Again, the class definition is similar in both languages.

To create the genesis block, we just call the Block with the current timestamp using time. To do that, we need to import the time library.

The string conversion is done with str instead of toString.

from time import time

class Blockchain:
    def __init__(self):
        # Create our genesis block
        self.chain = [Block(str(int(time())))]
Enter fullscreen mode Exit fullscreen mode

And the method to get the latest block is similar. We use len to get the length of the chain instead of length in javascript.

    def getLastBlock(self):
        return self.chain[len(self.chain) - 1]
Enter fullscreen mode Exit fullscreen mode

To add the block to the blockchain, we just call the addBlock method. The code is almost the same except the append (push in javascript).

def addBlock(self, block):
        # Since we are adding a new block, prevHash will be the hash of the old latest block
        block.prevHash = self.getLastBlock().hash
        # Since now prevHash has a value, we must reset the block's hash
        block.hash = block.getHash()
        self.chain.append(block)
Enter fullscreen mode Exit fullscreen mode

Validation

In the validation method, we start using range as a big difference. Also, because we don't use constants in Python, we just use normal variables.

For the conditional, python uses or instead of || in javascript.

def isValid(self):
    # Iterate over the chain, we need to set i to 1 because there are nothing before the genesis block, so we start at the second block.
    for i in range(1, len(self.chain)):
        currentBlock = self.chain[i]
        prevBlock = self.chain[i - 1]

        # Check validation
        if (currentBlock.hash != currentBlock.getHash() or prevBlock hash != currentBlock.prevHash):
            return False

    return True
Enter fullscreen mode Exit fullscreen mode

Proof-of-work

We can implement this system by adding a mine method and a nonce property to our block. Be careful because nonce must be declared before calling the self.getHash() method. If not, you will get the error AttributeError: 'Block' object has no attribute 'nonce'.

class Block:

    def __init__(self, timestamp=None, data=None):
        self.timestamp = timestamp or time()
        self.data = [] if data is None else data
        self.prevHash = None # previous block's hash
        self.nonce = 0
        self.hash = self.getHash()

    # Our hash function.
    def getHash(self):

        hash = sha256()
        hash.update(str(self.prevHash).encode('utf-8'))
        hash.update(str(self.timestamp).encode('utf-8'))
        hash.update(str(self.data).encode('utf-8'))
        hash.update(str(self.nonce).encode('utf-8'))
        return hash.hexdigest()

    def mine(self, difficulty):
        # Basically, it loops until our hash starts with
        # the string 0...000 with length of <difficulty>.
        while self.hash[:difficulty] != '0' * difficulty:
            # We increases our nonce so that we can get a whole different hash.
            self.nonce += 1
            # Update our new hash with the new nonce value.
            self.hash = self.getHash()
Enter fullscreen mode Exit fullscreen mode

To create the difficulty property:

self.difficulty = 1
Enter fullscreen mode Exit fullscreen mode

And the addBlock method:

    def addBlock(self, block):
        block.prevHash = self.getLastBlock().hash
        block.hash = block.getHash()
        block.mine(self.difficulty)
        self.chain.append(block)
Enter fullscreen mode Exit fullscreen mode

Testing out the chain

First, import the module and use the Blockchain class the same way using JeChain object:

from blockchain import Block
from blockchain import Blockchain
from time import time

JeChain = Blockchain()

# Add a new block
JeChain.addBlock(Block(str(int(time())), ({"from": "John", "to": "Bob", "amount": 100})))
# (This is just a fun example, real cryptocurrencies often have some more steps to implement).

# Prints out the updated chain
print(JeChain)
Enter fullscreen mode Exit fullscreen mode

It should looks like this:

[
    {
        "data": [],
        "timestamp": "1636153236",
        "nonce": 0,
        "hash": "4caa5f684eb3871cb0eea217a6d043896b3775f047e699d92bd29d0285541678",
        "prevHash": null
    },
    {
        "data": {
            "from": "John",
            "to": "Bob",
            "amount": 100
        },
        "timestamp": "1636153236",
        "nonce": 14,
        "hash": "038f82c6e6605acfcad4ade04e454eaa1cfa3d17f8c2980f1ee474eefb9613e9",
        "prevHash": "4caa5f684eb3871cb0eea217a6d043896b3775f047e699d92bd29d0285541678"
    }
]
Enter fullscreen mode Exit fullscreen mode

but only after adding the __repr__ method to the Blockchain class:

import json

    def __repr__(self):
        return json.dumps([{'data': item.data, 'timestamp': item.timestamp, 'nonce': item.nonce, 'hash': item.hash, 'prevHash': item.prevHash} for item in self.chain], indent=4)
Enter fullscreen mode Exit fullscreen mode

Updated bonus: Difficulty and block time

For the blockTime just:

self.blockTime = 30000
Enter fullscreen mode Exit fullscreen mode

Have a look to the ternary used for the difficulty system. In Python, the ternary operator is (if_test_is_false, if_test_is_true)[test], resulting in:

    def addBlock(self, block):
        block.prevHash = self.getLastBlock().hash
        block.hash = block.getHash()
        block.mine(self.difficulty)
        self.chain.append(block)

        self.difficulty += (-1, 1)[int(time()) - int(self.getLastBlock().timestamp) < self.blockTime]
Enter fullscreen mode Exit fullscreen mode

The final python code (Without proper formatting) in 60 lines is:

# -*- coding: utf-8 -*-

from hashlib import sha256
import json
from time import time


class Block:

    def __init__(self, timestamp=None, data=None):
        self.timestamp = timestamp or time()
        self.data = [] if data is None else data
        self.prevHash = None
        self.nonce = 0
        self.hash = self.getHash()

    def getHash(self):

        hash = sha256()
        hash.update(str(self.prevHash).encode('utf-8'))
        hash.update(str(self.timestamp).encode('utf-8'))
        hash.update(str(self.data).encode('utf-8'))
        hash.update(str(self.nonce).encode('utf-8'))
        return hash.hexdigest()

    def mine(self, difficulty):
        while self.hash[:difficulty] != '0' * difficulty:
            self.nonce += 1
            self.hash = self.getHash()

class Blockchain:

    def __init__(self):
        self.chain = [Block(str(int(time())))]
        self.difficulty = 1
        self.blockTime = 30000

    def getLastBlock(self):
        return self.chain[len(self.chain) - 1]

    def addBlock(self, block):
        block.prevHash = self.getLastBlock().hash
        block.hash = block.getHash()
        block.mine(self.difficulty)
        self.chain.append(block)

        self.difficulty += (-1, 1)[int(time()) - int(self.getLastBlock().timestamp) < self.blockTime]

    def isValid(self):
        for i in range(1, len(self.chain)):
            currentBlock = self.chain[i]
            prevBlock = self.chain[i - 1]

            if (currentBlock.hash != currentBlock.getHash() or prevBlock.hash != currentBlock.prevHash):
                return False

        return True

    def __repr__(self):
        return json.dumps([{'data': item.data, 'timestamp': item.timestamp, 'nonce': item.nonce, 'hash': item.hash, 'prevHash': item.prevHash} for item in self.chain], indent=4)
Enter fullscreen mode Exit fullscreen mode

Hopefully you will enjoy and learn with both posts!

. . . . . . . . . .