DEV Community

Cover image for Learn Blockchain by creating one
Sampark Sharma
Sampark Sharma

Posted on • Updated on

Learn Blockchain by creating one

The best way to learn how Blockchain works is to create one

You are here because like me you have heard about Bitcoin and cryptocurrency, and want to learn how they work. After spending hours reading articles and watching YouTube videos, I thought of making a simple project so that people could understand Blockchain.

Before getting started...

Remember that a blockchain is an immutable, sequential chain of records called Blocks. They can contain transactions, files, or any data you like, really. But the important thing is that they are chained together using hashes.

If you aren't sure what a hash is, here's an explanation.

Who is this guide is for? You should be comfortable at reading and writing basic Python syntax, as well as have some understanding of how HTTP requests work, as we'll be talking to Blockchain over HTTP.

What do I need? We are going to use Flask here, so it would be good if you have knowledge about it. Even if you don't have working knowledge, it is fine as I would be going through the code step by step. Let's install our requirements:

pip install Flask==1.1.2 requests==2.24.0

Oh, you'll also need an HTTP Client, like Postman or cURL. But anything would do.

Where's the final code? The source code is available here.

Step 1: Building a Blockchain

Open up any text editor, I prefer VS Code. Let's create a file names blockchain.py. It will contain our Blockchain structure/class.

Representing a Blockchain

Here is how our Blockchain class will look like:

class Blockchain(object):
    def __init__(self):
        self.chain = []
        self.current_transactions = []

    def new_block(self):
        # Creates a new Block and adds it to the chain
        pass

    def new_transaction(self):
        # Adds a new transaction to the list of transactions
        pass

    @staticmethod
    def hash(block):
        # Hashes a Block
        pass

    @property
    def last_block(self):
        # Returns the last Block in the chain
        pass

Okay, now let us go function to function.
init:

  • chain Contains actual chain/sequence of blocks
  • current_transactions Contains the list of transactions.

Rest functions have appropriate comments to make you understand their functionality.

Our Blockchain class is responsible for managing the chain. It will store transactions and have few helper methods for adding new blocks to the chain.

What does a Block look like?

Each Block has an index, a timestamp(in Unix time), a list of transactions, a proof (later in article), and the hash of the previous Block.

block = {
    "index": 2,
    "timestamp": 1595318883.8897538,
    "transactions": [
        {
            "sender": "0",
            "recipient": "d48099450667467e97a6033ddde69e8a",
            "amount": 70
        }
    ],
    "proof": 35293,
    "previous_hash": "65679099343e4a8f9c8c30438b053db6558546d8cef9378ed8d3dd3c7e317af5"
}

(Example of a Block in our Blockchain)

Till this point, the idea of a chain should be apparent--each new block contains the hash of the previous block. Thus providing immutability to blockchains. If an attacker corrupts an earlier Block in the chain then all the subsequent blocks will contain incorrect hashes, basically will not match with the hashes stored at other nodes. P.S. for blockchain to work, there should be multiple nodes to store data and those nodes should be decentralised, i.e. not on same server/ip.

Does it make sense? If it doesn't, take some time to let it sink in, read it once more--it's the core idea behind blockchains.

Adding transaction to a Block

Transaction is record of transfer of data, or in our case fake money. We'll need a way to add transactions to a Block. Our new_transaction() method is responsible for this. Let's see how it works:

class Blockchain(object):
    ...

    def new_transaction(self, sender, recipient, amount):
        """
        Creates a new transaction to go into the next mined Block
        :param sender: <str> Address of the Sender
        :param recipient: <str> Address of the Recipient
        :param amount: <int> Amount
        :return: <int> The index of the Block that will hold this transaction
        """

        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })

        return self.last_block['index'] + 1

This function adds a dict to current_transactions with sender, recipient and amount of the transaction. After new_transaction() adds a transaction to the list, it returns the index of the block which the transaction will be added to—the next one to be mined. This will be useful later on, to the user submitting the transaction.

Creating new Blocks

When our Blockchain is instantiated we’ll need to seed it with a genesis block—a block with no predecessors. We’ll also need to add a proof to our genesis block which is the result of mining (or proof of work). We’ll talk more about mining later.

In addition to creating the genesis block in out constructor(init), we'll also write out few other methods.

import hashlib
import json
from time import time


class Blockchain(object):
    def __init__(self):
        self.current_transactions = []
        self.chain = []

        # Create the genesis block
        self.new_block(previous_hash=1, proof=100)
    def new_block(self, proof, previous_hash=None):
        """
        Create a new Block in the Blockchain
        :param proof: <int> The proof given by the Proof of Work algorithm
        :param previous_hash: (Optional) <str> Hash of previous Block
        :return: <dict> New Block
        """

        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,
            'proof': proof,
            'previous_hash': previous_hash or self.hash(self.chain[-1]),
        }

        # Reset the current list of transactions
        self.current_transactions = []

        self.chain.append(block)
        return block

    def new_transaction(self, sender, recipient, amount):
        """
        Creates a new transaction to go into the next mined Block
        :param sender: <str> Address of the Sender
        :param recipient: <str> Address of the Recipient
        :param amount: <int> Amount
        :return: <int> The index of the Block that will hold this transaction
        """
        self.current_transactions.append({
            'sender': sender,
            'recipient': recipient,
            'amount': amount,
        })

        return self.last_block['index'] + 1

    @staticmethod
    def hash(block):
        """
        Creates a SHA-256 hash of a Block
        :param block: <dict> Block
        :return: <str>
        """

        # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

    @property
    def last_block(self):
        # Returns the last block in the chain
        return self.chain[-1]

Okay, now let us look at the code.

  • new_block: Adds a Block to the chain
  • new_transaction: Stores a transfer of amount from sender to recipient.
  • hash: Returns a hex formatted hash of the block passed to it in SHA256.
  • last_block: Returns the last block of chain. I've also added some comments and docstrings to help make it clear. We’re almost done with representing our blockchain. But at this point, you must be wondering how new blocks are created, forged or mined.

Understanding Proof of Work

A Proof of Work algorithm (PoW) is how new Blocks are created or mined on the blockchain. The goal of PoW is to discover a number which solves a problem. The number must be difficult to find but easy to verify—computationally speaking—by anyone on the network. This is the core idea behind Proof of Work.

We'll look at a very simple example to help this sink in.

Let's decide that the hash of some number x multiplied by another number y must end in 0. So, hash(x * y) = ad534...0. And for this example, let's fix x = 9. Implementing this in Python:

from hashlib import sha256
x = 9
y = 0  # We don't know what y should be yet...
while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":
    y += 1
print(f'The solution is y = {y}')

The solution here is y = 16. Since, the produced hash ends in 0.

hash(9 * 16) = 5ec1a0c99d4............2cf5563d97ff0

In Bitcoin, the Proof of Work algorithm is called Hashcash. And it's not too different from our example above. It’s the algorithm that miners race to solve in order to create a new block. In general, the difficulty is determined by the number of characters searched for in a string. The miners are then rewarded for their solution by receiving a coin—in a transaction.

The network is able to easily verify their solution.

Implementing basic Proof of Work

Let's implement a similar algorithm for our blockchain. Our rule will be: Find a number that when hashed with the previous block's solution a hash with 4 leading zeros(0000) is produced.

import hashlib
import json

from time import time
from uuid import uuid4


class Blockchain(object):
    ...

    def proof_of_work(self, last_proof):
        """
        Simple Proof of Work Algorithm:
         - Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
         - p is the previous proof, and p' is the new proof
        :param last_proof: <int>
        :return: <int>
        """

        proof = 0
        while self.valid_proof(last_proof, proof) is False:
            proof += 1

        return proof

    @staticmethod
    def valid_proof(last_proof, proof):
        """
        Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes?
        :param last_proof: <int> Previous Proof
        :param proof: <int> Current Proof
        :return: <bool> True if correct, False if not.
        """

        guess = f'{last_proof}{proof}'.encode()
        guess_hash = hashlib.sha256(guess).hexdigest()
        return guess_hash[:4] == "0000"

To make it difficult to find the proof we could increase the number of leading zeros. But 4 is sufficient. You'll find out that the addition of a single leading zero makes a huge difference to time required. To better understand I would suggest to add zeros and check how much time does it take, you could use the simple example we used earlier.

Our class is almost complete. The whole class is in blockchain.py. In the next post I would start using HTTP requests to interact with our network.
I will post the link in the update section to subsequent posts. To get the source code go here.

Updates

The link to the following blog is here.

Credits

I can't take the credit for this article. This article is just an upgraded version of dvf's article.
Photo by JJ Ying on Unsplash

Top comments (0)