Blockchain

Python - creating a blockchain

By lingy | Lingy | 28 Nov 2021


Also follow me on the Odysee.


You've probably come across the term  blockchain "the technology behind cryptocurrencies" by now. But what is a blockchain? And how to build one?

What is a blockchain?

A blockchain is, in short, a linked list of blocks, where each block added attests to the veracity of the block immediately before it, guaranteeing the legitimacy of the data. Despite being a simplistic explanation, in short, this is how a blockchain works.

As the concept is a bit confusing, I think it's better to see how a blockchain works in practice, that is, creating one from scratch.

Starting development

First let's add the following dependencies to the file:

import re
import json
from datetime import datetime as dt
from hashlib import sha256

Each of these dependencies will be explained later in the code, but add them because they will be needed. Now, let's get to the code itself.

The block

A block is, in short, an object that has:

  • an ID
  • an identification hash
  • The hash of the previous block (to ensure integrity)
  • Content (block data)
  • Transaction date and time

Of course a block can have more or less attributes, but this is the most common structure of a blockchain we have. The blocks, as they are chained together, end up behaving like a "chain of blocks", where you are able to pull data from all previous blocks starting with the last one recorded.

Now, let's create a class  Block with this data:

class Block:
    def __init__(self, index, previous_hash, data):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = dt.now().timestamp()

Did you notice something strange? Exactly, we are not generating the current block hash:

class Block:
    # código do construtor
    
    def generate_hash(self):
        return sha256(str( \
                          str(self.index) + \
                          str(self.previous_hash) + \
                          json.dumps(self.data) + \
                          str(self.timestamp)).encode('utf-8') \
                     ).hexdigest()

This function will convert the block data into a JSON, concatenate with all the other data and create a cryptographic hash  sha256 from that string. This is the hash of the block, which validates its integrity. O  sha256 is just one of the algorithms, but you can use several others, like o  md5 (although I don't recommend it) or other cryptographic hashing algorithms. You will assign the property to the class as  self.hash = self.generate_hash().

So we have this as our starting block:

class Block:
    def __init__(self, index, previous_hash, data):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = dt.now().timestamp()
        self.hash = self.generate_hash()
        
    def generate_hash(self):
        return sha256(str( \
                          str(self.index) + \
                          str(self.previous_hash) + \
                          json.dumps(self.data) + \
                          str(self.timestamp)).encode('utf-8') \
                     ).hexdigest()

the blockchain

A blockchain works as if it were a chained list of blocks, where the last attached block is related to the penultimate by its hash. This is what will guarantee its validation and ensure that each block is legitimate. The initial structure will look like this:

class Blockchain:
    def __init__(self):
        self.blocks = [Block(0, None, {'content': 'Bloco Inicial'})]
        self.index = 1

Of course, the user will not be manually entering the ID and hash of the blocks, he will just give you a JSON containing the block data and want it to be there the way it should. So let's add the corresponding block:

class Blockchain:
    # código do construtor
    
    def get_last_block(self):
        return self.blocks[-1]
    
    def add_new_block(self, data):
        previous_hash = self.get_last_block().hash

        block = Block(self.index, previous_hash, data)

        self.index += 1
        self.blocks.append(block)

We can test if everything is working correctly as follows:

chain = Blockchain()
chain.add_new_block({'content': {'transaction_01': 1, 'transaction_02': 2}})
print(chain.blocks[1].data)

This code will return a dictionary with the following structure:

{
    'content': {
        'transaction_01': 1, 'transaction_02': 2
    }
}

We can also see the block hash if we want:

print(chain.blocks[1].hash)

This will return a random string, just like the string below:

2d76eeac710e20749fb1d594b9caf552ec93733d1205593f4c27e1488509e7f4

Validating blocks

Of course, with the code we have, anyone can change some data in our chain and it would be difficult to validate. Note my change:

chain.blocks[1].data = {'content': {'transaction_01': 3000}}

This doesn't feel right, does it? After all, if I made a transaction, I want it to be unique, and if someone changes it, the entire chain becomes invalid. So let's add a validation function to the blockchain:

class Blockchain:
    # demais funções
    
    def is_valid(self):
        for i in range(len(self.blocks)):
            current_block = self.blocks[i + 1]
            previous_block = self.blocks[i]

            if current_block.hash != current_block.generate_hash():
                return False

            if current_block.index != (previous_block.index + 1):
                return False

            if current_block.previous_hash != previous_block.hash:
                return False

            return True

This function performs the following operations:

  • cycles through the chain
  • Check if the values ​​are in the correct order
  • Checks if the hash of the block matches the hash of its successor
  • Check if the hash is valid

We can now test our function:

chain = Blockchain()
chain.add_new_block({'content': {'transaction_01': 1, 'transaction_02': 2}})
print(chain.is_valid()) # True
chain.blocks[1].data = {'content': {'transaction_01': 3000}}
print(chain.is_valid()) # False

If you've followed all the steps so far, there's probably something like this:

class Blockchain:
    def __init__(self):
        self.blocks = [Block(0, None, {'content': 'Bloco Inicial'})]
        self.index = 1
    def get_last_block(self):
        return self.blocks[-1]
    
    def add_new_block(self, data):
        previous_hash = self.get_last_block().hash

        block = Block(self.index, previous_hash, data)

        self.index += 1
        self.blocks.append(block)

    def is_valid(self):
        for i in range(len(self.blocks)):
            current_block = self.blocks[i + 1]
            previous_block = self.blocks[i]

            if current_block.hash != current_block.generate_hash():
                return False

            if current_block.index != (previous_block.index + 1):
                return False

            if current_block.previous_hash != previous_block.hash:
                return False

            return True

Mining

Now we get one more problem: I can just loop and keep adding garbage to the chain. Of course we don't want that, because if we keep adding garbage to our blockchain, it will become gigantic and useless. Note below:

chain = Blockchain()
i = 0
while(True):
    i += 1
    chain.add_new_block({'content': i})

This will simply fill our blockchain with useless and meaningless blocks. We need to add a certain "work", an extra difficulty, to make sure the person adding that block REALLY wants to add it. One way we can do this is mining.

The hashes in  sha256 are generated from an algorithm, so they're not really random, but it takes some computational power to get a valid hash for a given block. Mining is exactly that. During the mining process, you have a difficulty that varies from 0 to the width of the algorithm's hash (in our case, 64). If the difficulty is 1, for example, the first character of the hash must be  0, if the difficulty is 2 the first and second character must be  0, if it is 3 the first, the second and third character must be zero, and so on. against. To obtain these hashes, the computer will keep creating tens, hundreds, thousands or even millions of hashes, until it finds one that matches the selected difficulty. So let's start by creating a function mine to our block:

class Block:
    def __init__(self, index, previous_hash, data, difficulty):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = dt.now().timestamp()
        self.difficulty = difficulty
        
        self.mine()
    
    def mine(self):
        self.hash = self.generate_hash()

        while re.match("^[0]+$", self.hash[:self.difficulty]) == None:
            self.nonce += 1
            self.hash = self.generate_hash()

And we'll add the difficulty to our blockchain:

class Blockchain:
    def __init__(self):
        self.blocks = [Block(0, None, {'content': 'Bloco Inicial'}), 1]
        self.index = 1
        self.difficulty
    
    def add_new_block(self, data):
        previous_hash = self.get_last_block().hash

        block = Block(self.index, previous_hash, data, self.difficulty)

        self.index += 1
        self.blocks.append(block)

It will now take some time for each block to be mined (random time, of course, but it will still be some time), which will prevent them from filling your chain with useless stuff.

The complete code can then be summarized as:

import re
import json
from datetime import datetime as dt
from hashlib import sha256

class Block:
    def __init__(self, index, previous_hash, data, difficulty):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = dt.now().timestamp()
        self.difficulty = difficulty
        
        self.mine()
    
    def mine(self):
        self.hash = self.generate_hash()

        while re.match("^[0]+$", self.hash[:self.difficulty]) == None:
            self.nonce += 1
            self.hash = self.generate_hash()
        
    def generate_hash(self):
        return sha256(str( \
                          str(self.index) + \
                          str(self.previous_hash) + \
                          json.dumps(self.data) + \
                          str(self.timestamp)).encode('utf-8') \
                     ).hexdigest()
        

class Blockchain:
    def __init__(self):
        self.blocks = [Block(0, None, {'content': 'Bloco Inicial'}), 1]
        self.index = 1
        self.difficulty
    
    def get_last_block(self):
        return self.blocks[-1]
    
    def add_new_block(self, data):
        previous_hash = self.get_last_block().hash

        block = Block(self.index, previous_hash, data, self.difficulty)

        self.index += 1
        self.blocks.append(block)
        
    def is_valid(self):
        for i in range(len(self.blocks)):
            current_block = self.blocks[i + 1]
            previous_block = self.blocks[i]

            if current_block.hash != current_block.generate_hash():
                return False

            if current_block.index != (previous_block.index + 1):
                return False

            if current_block.previous_hash != previous_block.hash:
                return False

            return True

And presto, you have your first blockchain developed in Python.

How do you rate this article?


13

0


Lingy
Lingy

My personal blog about technology

Send a $0.01 microtip in crypto to the author, and earn yourself as you read!

20% to author / 80% to me.
We pay the tips from our rewards pool.