A Step-by-Step Guide to Building a Blockchain in Python

Today I’ll demonstrate how to make a simple proof of work blockchain in Python in less than 100 lines of code.

Setting the initial conditions

First, let’s set the network difficulty. This will adjust how hard it is to mine a block. In real life, the network adjusts the difficulty dynamically to force the blocks to be discovered at a fixed, predictable rate.

difficulty="0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
Tick-Tick - d:amtwotp

Next, let’s draft a transaction to put in the first block. I’m using email addresses instead of network addresses in this example. I’m calling the coinbase by it’s namesake. I’ve also included a version stamp, which I am calling “1”.

transactions = [
        {
            "version":1,
            "inputs":{"coinbase":50},
            "outputs":{"my_email@yahoo.com":50}
        }
    ]

Next, let’s define the previous block. This block contains basically nothing, since it should not exist.

The hash of the block is defined as being the zero hash, and it has a timestamp of “right now”. I’ll encode the difficulty in the block too.

block = Block(
    prev_block="0x"+"0"*64,
    timestamp=int(time.time()),
    transactions=transactions,
    difficulty=difficulty
)

Now, let’s try mining a block. The magic comes from the “Block” class at the end of this article. I’ll use the simple version for now.

Coal Miner GIFs - Get the best GIF on GIPHY

First, let’s set the initial conditions. The nonce is the number we’ll use to affect the output such that it falls within the network difficulty.

nonce = 0
is_valid = False
print("\nMining...")
begin = time.time()

Let’s iterate through different nonces until we find a block.

while not is_valid:
    block.set_nonce(nonce)
    is_valid = block.is_valid()
    nonce += 1
    if not nonce % 100000:
        block.set_timestamp(int(time.time()))

Once this loop completes, we will have found our block!

print("Found valid block in {0:0.1f} minutes: {1}".format((time.time()-begin)/60, block.get_hash()))
        print(block)
ruinawish: Immortal - “Grim and Frostbitten Porn Photo Pics

Next, we repeat the process by creating another candidate block.

block = Block(
    prev_block=block.get_hash(),
    timestamp=int(time.time()),
    transactions=transactions,
    difficulty=difficulty
)

That’s it! There are a lot of things that were not implemented, but it gives you a basic idea of different data structures within a blockchain.

If you want to use a blockchain, chances are it would be better to use a database instead.

Here is the full code:

import hashlib
import json
import time

class Block:

    def __init__(self, prev_block, timestamp, transactions, difficulty):
    
        self.version = 1
        self.prev_block = prev_block
        self.timestamp = timestamp
        self.difficulty = difficulty
        self.nonce = 0
        
        self.transactions = transactions
        self.merkle_root = self.get_merkle_root()

    def __str__(self):
        data = {}
        for attr in ["version", "prev_block","timestamp","difficulty","nonce","transactions","merkle_root"]:
            data[attr] = getattr(self, attr)
        return json.dumps(data, indent=2)

    def get_hash(self):
        return hashlib.sha256(str.encode(str(self))).hexdigest()
    
    def get_merkle_root(self):
        hashes = []
        for transaction in self.transactions:
            transaction_str = json.dumps(transaction)
            transaction_hash = hashlib.sha256(str.encode(transaction_str)).hexdigest()
            hashes.append(transaction_hash)
        while len(hashes) > 1:
            if len(hashes) % 2:
                hashes.append(hashes[-1])
            new_hashes = []
            for i in range(0, len(hashes)-2+1, 2):
                new_hash = hashlib.sha256(str.encode(str(hashes[i:i+2]))).hexdigest()
                new_hashes.append(new_hash)
            hashes = new_hashes
        return hashes[0]
    
    def is_valid(self):
        difficulty_test_pass = int(self.get_hash(),16) < int(self.difficulty,16)
        return difficulty_test_pass

    def set_nonce(self, nonce):
        self.nonce = nonce
    
    def set_timestamp(self, timestamp):
        self.timestamp = timestamp

def main():

    version=1
    difficulty="0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"

    transactions = [
        {
            "version":1,
            "inputs":{"coinbase":50},
            "outputs":{"my_email@yahoo.com":50}
        }
    ]
    block = Block(
        prev_block="0x"+"0"*64,
        timestamp=int(time.time()),
        transactions=transactions,
        difficulty=difficulty
    )
    while True:
        nonce = 0
        is_valid = False
        print("\nMining...")
        begin = time.time()
        while not is_valid:
            block.set_nonce(nonce)
            is_valid = block.is_valid()
            nonce += 1
            if not nonce % 100000:
                block.set_timestamp(int(time.time()))
        print("Found valid block in {0:0.1f} minutes: {1}".format((time.time()-begin)/60, block.get_hash()))
        print(block)
        
        block = Block(
            prev_block=block.get_hash(),
            timestamp=int(time.time()),
            transactions=transactions,
            difficulty=difficulty
        )

if __name__ == "__main__":
    main()

About the author



Hi, I'm Nathan. Thanks for reading! Keep an eye out for more content being posted soon.


Leave a Reply

Your email address will not be published. Required fields are marked *