-->

Block Chain - What is it? How can we make one? Can we make a block chain in python?

By: Varghese Chacko 3 years, 5 months ago

Block chain is the buzzword in the world now. This is a demonstration of what block chain is, in Python. There is nothing fancy or the code we write can not be used as is, in any production requirement. But I am trying to explain the concept.

If you know linked lists, you know block chanin, but there is a very minor difference in structure and added security. A block in blockchain is similar to a node in a linked list. Let us start with our first block chain. I am going to call this block chain as AtemonBlockChain as to implement AtemonCoin

We create a class for the block and save the data using the __init__ magic function. The previous hash for initial block is blank or None as there no prvious block.

class AtemonBlock(object):
    """The block for ATEMON Coin."""

    def __init__(self, index, timestamp, data, previous_hash=None):
        """Constructor for Block."""
        self.index = index
        self.timestamp = timestamp
        self.data = data
        self.previous_hash = previous_hash

The block may have an index, needs a timestamp, some data, and has of the previous block. Yes, the block chains are linked to previous blocks usually, than next node in case of linked list. The chain is build up on hash for its integrity. So we create a function to generate hash of the block and we save it when we create the block. There are many algorithms and libraries available for generating the hash. sha256 is readily available in hashlib and we are using it.

def generate_hash(self):
        """Calculate hash of the block."""
        m = hashlib.sha256(
            "{index}{previous_hash}{timestamp}{data}".format(
                index=self.index,
                previous_hash=self.previous_hash,
                timestamp=self.timestamp,
                data=json.dumps(self.data)
            ).encode('utf-8')
        )
        return m.hexdigest()

We now edit the __init__ to include the hash of the current block

        self.hash = self.generate_hash()

Now our block is ready. Those who are familiar with linked lists see this very similar to node in the linked list.

Let us create a Block Chain named AtemonBlockChain. In this implementation, we keep the chain as simple array in the class/object.

class AtemonBlockChain(object):
    """The block chain for ATEMON coin."""

    def __init__(self):
        """Cunstructor for blockchain."""
        self.chain = []

Each block in the block chain points to the previous block by its hash. To start a block chain, we need to start with a block. The very first block in a chain is called Genesis Block. As the name suggests, its the beginning of the story. So we define a function to create a genicis block and attach to the chain. Since the genesis block has no previous hash, we will keep the previous hash as None.  Our function to create genisis block may look like:

    def create_genesis_block(self):
        """Create first block of the chain."""
        return AtemonBlock(0, "01/01/2018", "Genesis block for ATEMON Coin Chain", None)

Now let us add the genesis block when the chain is created by using magic function __init__

    def __init__(self):
        """Cunstructor for blockchain."""
        self.chain = [self.create_genesis_block()]

Let us add a function to get the last node of the chain and another function to add a block.

    def get_last_block(self):
        """Get last block of the chain."""
        return self.chain[-1]

    def add_block(self, new_block):
        """Add new block to chain."""
        new_block.previous_hash = self.get_last_block().hash
        new_block.hash = new_block.generate_hash()
        self.chain.append(new_block)

Let us add __repr__ for our block for printing purpose.

    def __repr__(self):
        """Stringify for pretty printing."""
        return json.dumps({
            "index": self.index,
            "timestamp": self.timestamp,
            "data": self.data,
            "previous_hash": self.previous_hash,
            "hash": self.hash,
        })

 

Now our block chain is ready. Let us add boiler plate to test the code and us use pprint to have a clean output.

if __name__ == '__main__':
    import pprint  # Here because it is used only for boiler plate
    atemon_coin = AtemonBlockChain()
    atemon_coin.add_block(AtemonBlock(1, "02/01/2018", {"amount": 10}))
    atemon_coin.add_block(AtemonBlock(2, "04/01/2018", {"amount": 25}))

    pp = pprint.PrettyPrinter(indent=4)
    pp.pprint(atemon_coin.chain)

The output I got is:

[
  {
    "timestamp": "01/01/2018",
    "data": "Genesis block for ATEMON Coin Chain",
    "previous_hash": None,
    "hash": "7123c7b553205c977aae3c6f6ef62fafdd3982268cb6b66a2fc1ca61cb5688dd",
    "index": 0
  },
  {
    "timestamp": "02/01/2018",
    "data": {
      "amount": 10
    },
    "previous_hash": "7123c7b553205c977aae3c6f6ef62fafdd3982268cb6b66a2fc1ca61cb5688dd",
    "hash": "f93e7a3f608a1c64ebb8bba2edd426bb437aa500937c9ad07915bcdaacaa800d",
    "index": 1
  },
  {
    "timestamp": "04/01/2018",
    "data": {
      "amount": 25
    },
    "previous_hash": "f93e7a3f608a1c64ebb8bba2edd426bb437aa500937c9ad07915bcdaacaa800d",
    "hash": "544eec5ddba68a25964167d1f771edd9f4ea42112a3dffddbd78102cda5b1c4b",
    "index": 2
  }
]

You may notice that the previous_hash of given node is the hash of its previous node and Genesis has previous hash as None. The whole program is

"""Block chain example."""
# Block Chain

import hashlib
import json


class AtemonBlock(object):
    """The block for ATEMON Coin."""

    def __init__(self, index, timestamp, data, previous_hash=None):
        """Constructor for Block."""
        self.index = index
        self.timestamp = timestamp
        self.data = data
        self.previous_hash = previous_hash
        self.hash = self.generate_hash()

    def generate_hash(self):
        """Calculate hash of the block."""
        m = hashlib.sha256(
            "{index}{previous_hash}{timestamp}{data}".format(
                index=self.index,
                previous_hash=self.previous_hash,
                timestamp=self.timestamp,
                data=json.dumps(self.data)
            ).encode('utf-8')
        )
        return m.hexdigest()

    def __repr__(self):
        """Stringify for pretty printing."""
        return json.dumps({
            "index": self.index,
            "timestamp": self.timestamp,
            "data": self.data,
            "previous_hash": self.previous_hash,
            "hash": self.hash,
        })


class AtemonBlockChain(object):
    """The block chain for ATEMON coin."""

    def __init__(self):
        """Cunstructor for blockchain."""
        self.chain = [self.create_genesis_block()]

    def create_genesis_block(self):
        """Create first block of the chain."""
        return AtemonBlock(0, "01/01/2018", "Genesis block for ATEMON Coin Chain", None)

    def get_last_block(self):
        """Get last block of the chain."""
        return self.chain[-1]

    def add_block(self, new_block):
        """Add new block to chain."""
        new_block.previous_hash = self.get_last_block().hash
        new_block.hash = new_block.generate_hash()
        self.chain.append(new_block)


if __name__ == '__main__':
    import pprint  # Here because it is used only for boiler plate
    atemon_coin = AtemonBlockChain()
    atemon_coin.add_block(AtemonBlock(1, "02/01/2018", {"amount": 10}))
    atemon_coin.add_block(AtemonBlock(2, "04/01/2018", {"amount": 25}))

    pp = pprint.PrettyPrinter(indent=4)
    pp.pprint(atemon_coin.chain)

The main issue with the above implementation is that we didnt add any security checks. two primary checks are 

  1. Check if the hash of every node is same as the hash stored in it
  2. Hash of the attribute previous_hash is same as hash of the previous node. 

A simple function to check validity of chain is

    def is_valid(self):
        """Check the chain is valid."""
        for index, current_block in enumerate(self.chain[1:]):  # Skip the Genesis block
            previous_block = self.chain[index]  # because we skipped one block and the index is 1 less than actual index
            if current_block.hash != current_block.generate_hash(): # Check if the hash of current block is same as what is saved in it
                return False
            if current_block.previous_hash != previous_block.hash:  # Check if the hash of previous block is same as value stored in previous_hash
                return False
        return True

Now let us check if out chain is valid.

    print("Is valid", atemon_coin.is_valid())

It prints True. Now let us manipulate the blockchain and change the amount and generate the hash as follows:

    atemon_coin.chain[1].data = {"amount": 50}
    atemon_coin.chain[1].hash = atemon_coin.chain[1].generate_hash()

This will fail for obvious reasons. There are two major issues with this implementation. First is, anyone can create any number of block and attach to the block chain and flood our chain with blocks. Second is, anyone can use computers with super computing power can tamper the block and regenerate the hash for all blocks of the chain and manipulate it. To overcome this chance of hacking, the block chains use "Proof of work (PoW)" or "Proof of Stake". We will discuss them in detail in another bg post.

Full code with valiator is

"""Block chain example."""
# Block Chain

import hashlib
import json


class AtemonBlock(object):
    """The block for ATEMON Coin."""

    def __init__(self, index, timestamp, data, previous_hash=None):
        """Constructor for Block."""
        self.index = index
        self.timestamp = timestamp
        self.data = data
        self.previous_hash = previous_hash
        self.hash = self.generate_hash()

    def generate_hash(self):
        """Calculate hash of the block."""
        m = hashlib.sha256(
            "{index}{previous_hash}{timestamp}{data}".format(
                index=self.index,
                previous_hash=self.previous_hash,
                timestamp=self.timestamp,
                data=json.dumps(self.data)
            ).encode('utf-8')
        )
        return m.hexdigest()

    def __repr__(self):
        """Stringify for pretty printing."""
        return json.dumps({
            "index": self.index,
            "timestamp": self.timestamp,
            "data": self.data,
            "previous_hash": self.previous_hash,
            "hash": self.hash,
        })


class AtemonBlockChain(object):
    """The block chain for ATEMON coin."""

    def __init__(self):
        """Cunstructor for blockchain."""
        self.chain = [self.create_genesis_block()]

    def create_genesis_block(self):
        """Create first block of the chain."""
        return AtemonBlock(0, "01/01/2018", "Genesis block for ATEMON Coin Chain", None)

    def get_last_block(self):
        """Get last block of the chain."""
        return self.chain[-1]

    def add_block(self, new_block):
        """Add new block to chain."""
        new_block.previous_hash = self.get_last_block().hash
        new_block.hash = new_block.generate_hash()
        self.chain.append(new_block)

    def is_valid(self):
        """Check the chain is valid."""
        for index, current_block in enumerate(self.chain[1:]):  # Skip the Genesis block
            previous_block = self.chain[index]  # because we skipped one block and the index is 1 less than actual index
            if current_block.hash != current_block.generate_hash():
                return False
            if current_block.previous_hash != previous_block.hash:
                return False
        return True

if __name__ == '__main__':
    import pprint  # Here because it is used only for boiler plate
    atemon_coin = AtemonBlockChain()
    atemon_coin.add_block(AtemonBlock(1, "02/01/2018", {"amount": 10}))
    atemon_coin.add_block(AtemonBlock(2, "04/01/2018", {"amount": 25}))

    print("Is valid", atemon_coin.is_valid())
    atemon_coin.chain[1].data = {"amount": 50}
    atemon_coin.chain[1].hash = atemon_coin.chain[1].generate_hash()

    print("Is valid", atemon_coin.is_valid())
    
    pp = pprint.PrettyPrinter(indent=4)
    pp.pprint(atemon_coin.chain)

I will be back soon with PoW for this example.

Let us talk!

We take the vision which comes from dreams and apply the magic of science and mathematics, adding the heritage of our profession and our knowledge to create a design.