[Python Block Chain] Part 1 : The Basic Blockchain

Photo by Ilya Pavlov on Unsplash

[Python Block Chain] Part 1 : The Basic Blockchain

Introduction

The basic idea of blockchain is very simple: a distributed immutable ledger that maintains a continuously growing list of records. the ledger is distributed among multiple peers

Distributed Consensus

Now, some of these peers might be evil and intentionally report a tampered version of blockchain data. The entire network uses democracy to come to a consensus on the current state of data and any non-conformant or outlier peers are ignored/blocked.

This means that in order for a blockchain network to be fair and valid, most of the nodes have to be agree on the same version of data. If 51% of the nodes are compromised the network is hacked. Since the networks should be globally distributed, this is not a possibility.

In this chapter we will implement a basic version of such blockchain in python. At the end of the chapter we will have the following basic functionalities of blockchain:

  1. A defined block and blockchain structure
  2. Methods to add new blocks to the blockchain with arbitrary data (strings for now)

The full code that will be implemented in this chapter can be found here. version-1 only contains the empty project structure.

The Blockchain

The blockchain in simple words is a chain of linked blocks containing data, each node in the chain contains a set of records and the hash (signature) of the previous node. Keeping track of the previous node guarantees that you can never change any of the nodes data because if there is a change in any node's data will change its hash and so the next node which keeps track of the (previous hash) will be invalid. and so the only way to edit a record in the middle is to modify every consecutive node.

image.png

this guarantees that the blockchain is immutable, you can only add new nodes to the chain but you cannot edit any of the middle node's data.

The Block

We will start by defining the block structure. Only the most essential properties are included at the block at this point.

  1. index : The block index blockchain (0 is first block)
  2. data : Any data that is included in the block.
  3. timestamp : A timestamp where this block was written
  4. hash : A sha256 hash taken from the content of the block (index, data, timestamp, previoushash)
  5. previousHash : A reference to the hash of the previous block. This value explicitly defines the previous block.

Genesis Block

The first block in the block chain is special because there it doesn't have a previous hash this is why it is special.

In Python3 we can implement the Block class as following

import hashlib
from dataclasses import dataclass

@dataclass
class Block:
    index: int
    hash: str
    prev_hash: str
    timestamp: int
    data: str

    def calculate_block_hash(self) -> str:
        return calculate_block_hash(
            self.index,
            self.prev_hash,
            self.timestamp,
            self.data
        )

def calculate_block_hash(index: int, prev_hash: str, timestamp: int, data: str) -> str:
    m = hashlib.sha256()
    payload = "%s%s%s%s" % (
        index,
        prev_hash,
        timestamp,
        data
    )
    m.update(payload.encode("utf8")) 
    return m.hexdigest()

It should be noted that the block hash has not yet nothing to do with mining, as there is no proof-of-work problem to solve. We use block hashes to preserve integrity of the block and to explicitly reference the previous block.

The Blockchain

A block chain simply contains list of blocks, it contains function to add blocks to the chain and verify chain status

from dataclasses import dataclass, field
from datetime import datetime
from . import Block, calculate_block_hash

@dataclass
class BlockChain:
    blocks: list[Block] = field(default_factory=list)

to support block addition we can add the following function to the Blockchain class definition

def add_block(self, block: Block):
        # Allow adding block only if Genesis Block or valid block
        if len(self.blocks) == 0 or self.is_valid_block(block, self.get_latest_block()):
            self.blocks.append(block)

Validating the integrity of blocks

At any given time we must be able to validate if a block or a chain of blocks are valid in terms of integrity. This is true especially when we receive new blocks from other nodes and must decide whether to accept them or not.

For a block to be valid the following must apply:

1.The index of the block must be one number larger than the previous 2.The prev_hash of the block match the hash of the previous block 3.The hash of the block itself must be valid

This can be done with the following code:

def is_valid_block(self, block: Block, previous_block: Block):
        # Check block index
        if previous_block.index + 1 != block.index:
            return False

        # Check previous block hash equls block hash
        if previous_block.hash != block.prev_hash:
            return False

        # Check block hash
        if block.calculate_block_hash() != block.hash:
            return False
        return True

to validate our chain we can always iterate over the nodes and confirm that each node's stored previous hash is equal to the the hash of the contents of the previous node. we can do this by validating each node with its previous one.


def is_valid_blockchain(self):
        print("Checking integrity of Blockchain with length:", len(self.blocks))
        # Blockchain is invalid if it doesn't have a Genesis block.
        if len(self.blocks)  == 0:
            return False

        # Validate chain
        for idx in range(1, len(self.blocks)):
            print("Checking integrity of Block Idx:", self.blocks[idx].index)
            if not self.is_valid_block(self.blocks[idx], self.blocks[idx - 1]):
                return False

        # Blockchain is valid
        return True

Choosing the longest chain

There should always be only one explicit set of blocks in the chain at a given time. In case of conflicts (e.g. two nodes both generate a block number) we choose the chain that has the longest number of blocks. In the below example, the data introduced in block 72: a350235b00 will not be included in the blockchain, since it will be overridden by the longer chain.

image.png

We implement this simple resolve implementation using the following function

 def replace_chain(self, other_chain):
        if self.get_genesis_block() != other_chain.get_genesis_block():
            logging.error("Received blockchain with invalid Genesis block: %s", other_chain)
        elif other_chain.is_valid_blockchain() and other_chain.length() > self.length():
            logging.error("Received blockchain: %s is valid. Replacing current blockchain with received blockchain", other_chain)
            self.blocks = other_chain.blocks
        else:
            logging.error("Received invalid blockchain: %s", other_chain)

Testing our simple Blockchain

We then a simple main function in a run.py file to test our blockchain

from blockchain import Block, calculate_block_hash
from blockchain.blockchain import BlockChain

def main():
    #genesis_block = create_genesis_block("Test Block Data 1")

    genesis_block = Block(
        0,
        'bc48c2c971c48f6b767b059885a51b4e753ae0928dd69b0ec0ce3ae4b6311d56',
        None,
        1638999821.017757,
        "My Genesis Block!!"
    )

    blockchain = BlockChain(genesis_block)
    blockchain.add_block(blockchain.generate_next_block("Test Block Data 1"))
    blockchain.add_block(blockchain.generate_next_block("Test Block Data 2"))
    blockchain.add_block(blockchain.generate_next_block("Test Block Data 3"))


    o_blockchain = BlockChain(genesis_block)
    o_blockchain.add_block(o_blockchain.generate_next_block("Test Block Data 1"))
    o_blockchain.add_block(o_blockchain.generate_next_block("Test Block Data 2"))
    o_blockchain.add_block(o_blockchain.generate_next_block("Test Block Data 3"))
    o_blockchain.add_block(o_blockchain.generate_next_block("Test Block Data 4"))


    blockchain.replace_chain(o_blockchain)

    logging.info("Is Valid Blockchain: %s", blockchain.is_valid_blockchain())

we get the following output:

py-blockchain meally$ python3 run.py 
INFO:root:Checking integrity of Blockchain: Blockchain[genesis[:8]=bc48c2c9, size=5]
INFO:root:Checking integrity of Block: Block[idx=1, contentSize=17, hash[:8]=bc5d6be6]
INFO:root:Checking integrity of Block: Block[idx=2, contentSize=17, hash[:8]=faf9af6d]
INFO:root:Checking integrity of Block: Block[idx=3, contentSize=17, hash[:8]=f4a5d425]
INFO:root:Checking integrity of Block: Block[idx=4, contentSize=17, hash[:8]=4c08e577]
INFO:root:Blockchain: Blockchain[genesis[:8]=bc48c2c9, size=5] is valid!
ERROR:root:Received blockchain: Blockchain[genesis[:8]=bc48c2c9, size=5] is valid. Replacing current blockchain with received blockchain
INFO:root:Checking integrity of Blockchain: Blockchain[genesis[:8]=bc48c2c9, size=5]
INFO:root:Checking integrity of Block: Block[idx=1, contentSize=17, hash[:8]=bc5d6be6]
INFO:root:Checking integrity of Block: Block[idx=2, contentSize=17, hash[:8]=faf9af6d]
INFO:root:Checking integrity of Block: Block[idx=3, contentSize=17, hash[:8]=f4a5d425]
INFO:root:Checking integrity of Block: Block[idx=4, contentSize=17, hash[:8]=4c08e577]
INFO:root:Blockchain: Blockchain[genesis[:8]=bc48c2c9, size=5] is valid!
INFO:root:Is Valid Blockchain: True

The full code that will be implemented in this chapter can be found here. version-1 only contains the empty project structure.

In the next blog post we will create a simple web server for our block chain and begin creating the peer to peer communication (distributed ledger) by using a mix between http server and web-sockets.