<jupyter_start><jupyter_text>Blockchain functionality Block linking process will have few tasks such as clubbing all the information to create a structure, calculating the hash of the block and appending it to the blockchain. Let's break down each of these functionalities into blockchain methods.<jupyter_code># -*- coding: utf-8 -*-
import json

from Crypto.Hash import SHA256
from datetime import datetime


class Block(object):
    """A class representing the block for the blockchain"""

    def __init__(self, index, previous_hash, timestamp, data, hash):
        self.index = index
        self.previous_hash = previous_hash
        self.timestamp = timestamp
        self.data = data
        self.hash = hash<jupyter_output><empty_output><jupyter_text>Above code snippet has python class called Block which has all the basic attributes to represent a block. Usually a block will contain both header and a body where header part will have metadata about the block. But, above example doesn’t distinguish between the header and body. A typical blockchain application such as bitcoin will have a huge set of data such as transactions, but we will consider data as just string type.<jupyter_code>class Blockchain(object):
    """A class representing list of blocks"""

    def __init__(self):

        self._chain = [self.get_genesis_block()]
        self.timestamp = datetime.now().strftime("%s")
        
    def get_genesis_block(self):
        """creates first block of the chain"""

        return Block(0, "0", 1465154705, "my genesis block!!",
                     "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7")

    def calculate_hash(self, index, previous_hash, timestamp, data):
        """calculates SHA256 hash value"""

        hash_object = SHA256.new(data=(str(index) + previous_hash + str(timestamp) + data).encode())
        return hash_object.hexdigest()

    def get_latest_block(self):
        """gets the last block from the blockchain"""

        try:
            return self._chain[-1]
        except IndexError as e:
            return None
    
    def create_block(self, block_data):
        """creates a new block with the given block data"""

        previous_block = self.get_latest_block()
        next_index = previous_block.index + 1
        next_timestamp = self.timestamp
        next_hash = self.calculate_hash(next_index, previous_block.hash, next_timestamp, block_data)
        return Block(next_index, previous_block.hash, next_timestamp, block_data, next_hash)

    @property
    def chain(self):
        """created a dict containing list of block objects to view"""

        return self.dict(self._chain)

    def dict(self, chain):
        """converts list of block objects to dictionary"""

        return json.loads(json.dumps(chain, default=lambda o: o.__dict__))

    def reset(self):
        """resets the blockchain blocks except genesis block"""

        self._chain = [self._chain[0]]

    def add_block(self, data):
        """appends a new block to the blockchain"""

        self._chain.append(self.create_block(data))<jupyter_output><empty_output><jupyter_text>Above class is a collection of class methods to create a valid blockchain by using hash function. Constructor of the Blockchain will initialize a chain by appending genesis block, which is the first block of the blockchain which doesn't have any reference to previous block.   get_genesis_block(self)A genesis block is hardcoded block which will be appended to the beginning of the blockchain. It is created with all static contents. Above genesis block has a hardcoded hash value which is created using SHA256 as follows.SHA256.new(data=(str(0) + "0"+ str(1465154705) +"my genesis block!!").encode()).hexdigest()   calculate_hash(self, index, previous_hash, timestamp, data):calculate_hash is a crucial method of the blockchain as this method creates a hash value which will bind all the blocks together. SHA256 hash value is created by using PyCryptodome package as shown in the chapter 2. This method will concatenate block index, hash value of previous block, timestamp and data to create a string to be hashed. SHA256 hash function will generate a digest which will be the hash value of that block.   get_latest_block(self)This function identifies the last block which is appended to the chain which is required while creating each block to find the hash value of previous block.   create_block(self, block_data):This function will build a block by constructing all the required attributes to create a Block object. It will also calculate the hash value for the current block. A new Block object consisting of the block structure will be finally created.   chain(self), dict(self, chain), reset(self), add_block(self, data) All the above functions are used to add blocks, reset and read the blocks of the blockchain. Method add_block and the attribute chain are the only required class members that needs to be exposed to the user.   Creating a blockchain  Now that we have defined all the required functionalities of a simple blockchain linker, let's emulate a blockchain linker by creating few blocks and adding them to the blockchain. The below code snippet creates a Blockchain object and adds three blocks to the blockchain along with an existing genesis block. This operation is performed again after resetting the blockchain. The important observation here is that both the output of new_chain.chain would produce same list of blocks containing same block hashes as below. This is due to the fact that all the attributes contributing to the hash value creation are same during both the execution, and hash function always produces the same hash value if the fed input is the same. The time stamp is intentionally kept constant for all the blocks to showcase the feature of the hash function.<jupyter_code>new_chain = Blockchain()
new_chain.add_block(data="modified first block data")
new_chain.add_block(data="second block data")
new_chain.add_block(data="third block data")

print(json.dumps(new_chain.chain))<jupyter_output>[{"index": 0, "previous_hash": "0", "data": "my genesis block!!", "hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", "timestamp": 1465154705}, {"index": 1, "previous_hash": "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7", "data": "modified first block data", "hash": "0237cae5709bd1fbf991f7352741fc794606e6886cd33b1e21d604c62879d4f0", "timestamp": "1521878822"}, {"index": 2, "previous_hash": "0237cae5709bd1fbf991f7352741fc794606e6886cd33b1e21d604c62879d4f0", "data": "second block data", "hash": "78e42d25ecd9aca0bb9b82b6d333c58319b5dc74611c4444017da39a494505a3", "timestamp": "1521878822"}, {"index": 3, "previous_hash": "78e42d25ecd9aca0bb9b82b6d333c58319b5dc74611c4444017da39a494505a3", "data": "third block data", "hash": "91e81e5582c3e7af2de4a9c8a2db793be0e6859981a082b17cd36c2ecf86c1aa", "timestamp": "1521878822"}]<jupyter_text>An example implementation of proof-of-work.  Example for brute forcing with nonce Below code snippet is a simple example for generating hashes to solve the proof-of-work puzzle. The nonce is created in an incremental fashion and appended to the input data. Hash value is computed using SHA256 algorithm and this is repeated for all the nonce values. The program will generate below hashes for each of the nonce appended data.<jupyter_code>from __future__ import print_function
from Crypto.Hash import SHA256

text = "I am <NAME>"

# iterate nonce from 0 to 19
for nonce in range(20):

    # add the nonce to the end of the text
    input_data = text + str(nonce)

    # calculate the SHA-256 hash of the input (text+nonce)
    hash_data = SHA256.new(input_data.encode()).hexdigest()

    # show the input and hash result
    print((input_data + '=>' + hash_data)[:64] + "...")<jupyter_output>I am Satoshi Nakamoto0=>a80a81401765c8eddee25df36728d732acb6d135...
I am Satoshi Nakamoto1=>f7bc9a6304a4647bb41241a677b5345fe3cd30db...
I am Satoshi Nakamoto2=>ea758a8134b115298a1583ffb80ae62939a2d086...
I am Satoshi Nakamoto3=>bfa9779618ff072c903d773de30c99bd6e2fd70b...
I am Satoshi Nakamoto4=>bce8564de9a83c18c31944a66bde992ff1a77513...
I am Satoshi Nakamoto5=>eb362c3cf3479be0a97a20163589038e4dbead49...
I am Satoshi Nakamoto6=>4a2fd48e3be420d0d28e202360cfbaba410bedde...
I am Satoshi Nakamoto7=>790b5a1349a5f2b909bf74d0d166b17a333c7fd8...
I am Satoshi Nakamoto8=>702c45e5b15aa54b625d68dd947f1597b1fa571d...
I am Satoshi Nakamoto9=>7007cf7dd40f5e933cd89fff5b791ff0614d9c60...
I am Satoshi Nakamoto10=>c2f38c81992f4614206a21537bd634af7178964...
I am Satoshi Nakamoto11=>7045da6ed8a914690f087690e1e8d662cf9e56f...
I am Satoshi Nakamoto12=>60f01db30c1a0d4cbce2b4b22e88b9b93f58f10...
I am Satoshi Nakamoto13=>0ebc56d59a34f5082aaef3d66b37a661696c2b6...
I am Satoshi Nakamoto14=>27ead1ca85da66981fd9da0[...]<jupyter_text>Example for finding a nonce to solve proof-of-work This example will illustrate on finding a nonce by brute forcing with the help of SHA256 to find a hash value that will satisfy the target hash. Target hash value is determined by setting the difficulty bits in the proof-of-work algorithm. We will use the same blockchain linker created in the earlier section and will modify that example to include proof-of-work algorithm while creating new block. Let’s modify few functions of the blockchain example to include the consensus algorithm.<jupyter_code>import json

from Crypto.Hash import SHA256
from datetime import datetime

max_nonce = 2 ** 32  # 4 billion


class Block(object):
    """A class representing the block for the blockchain"""

    def __init__(self, index, previous_hash, timestamp, data,
                 difficulty_bits, nonce, hash):
        self.index = index
        self.previous_hash = previous_hash
        self.timestamp = timestamp
        self.data = data
        self.difficulty_bits = difficulty_bits
        self.nonce = nonce
        self.hash = hash


class Blockchain(object):
    """A class representing list of blocks"""

    def __init__(self):

        self._chain = [self.get_genesis_block()]
        self.timestamp = datetime.now().strftime("%s")
        self.difficulty_bits = 0

    @property
    def chain(self):
        """created a dict containing list of block objects to view"""

        return self.dict(self._chain)

    def dict(self, chain):
        """converts list of block objects to dictionary"""

        return json.loads(json.dumps(chain, default=lambda o: o.__dict__))

    def reset(self):
        """resets the blockchain blocks except genesis block"""

        self._chain = [self._chain[0]]

    def get_genesis_block(self):
        """creates first block of the chain"""

        # SHA256.new(data=(str(0) + "0"+ str(1465154705) +"my genesis block!!"+"0").encode()).hexdigest()
        return Block(0, "0", 1465154705, "my genesis block!!", 0, 0,
                     "f6b3fd6d417048423692c275deeaa010d4174bd680635d3e3cb0050aa46401cb")

    def add_block(self, data):
        """appends a new block to the blockchain"""

        self._chain.append(self.create_block(data))

    def create_block(self, block_data):
        """creates a new block with the given block data"""

        previous_block = self.get_latest_block()
        next_index = previous_block.index + 1
        next_timestamp = self.timestamp
        next_hash, next_nonce = self.calculate_hash(next_index, previous_block.hash, next_timestamp, block_data)
        return Block(next_index, previous_block.hash, next_timestamp, block_data, self.difficulty_bits, next_nonce, next_hash)

    def get_latest_block(self):
        """gets the last block from the blockchain"""

        try:
            return self._chain[-1]
        except IndexError as e:
            return None

    def calculate_hash(self, index, previous_hash, timestamp, data):
        """calculates SHA256 hash value by solving hash puzzle"""

        header = str(index) + previous_hash + str(timestamp) + data + str(self.difficulty_bits)

        hash_value, nonce = self.proof_of_work(header)
        return hash_value, nonce

    def proof_of_work(self, header):

        target = 2 ** (256 - difficulty_bits)

        for nonce in xrange(max_nonce):
            hash_result = SHA256.new(data=(str(header) + str(nonce)).encode()).hexdigest()

            if int(hash_result, 16) < target:
                print("Success with nonce %d" % nonce)
                print("Hash is %s" % hash_result)
                return (hash_result, nonce)

        print("Failed after %d (max_nonce) tries" % nonce)
        return nonce
if __name__ == '__main__':

    new_chain = Blockchain()

    for difficulty_bits in range(32):
        difficulty = 2 ** difficulty_bits
        new_chain.difficulty_bits = difficulty_bits
        print("Difficulty: %ld (%d bits)" % (difficulty, difficulty_bits))
        print("Starting search...")

        start_time = datetime.now()

        new_block_data = 'test block with transactions'
        new_chain.add_block(data=new_block_data)


        end_time = datetime.now()

        elapsed_time = (end_time - start_time).total_seconds()
        print("Elapsed Time: %.4f seconds" % elapsed_time)

        if elapsed_time > 0:

            hash_power = float(int(new_chain.chain[-1].get("nonce")) / elapsed_time)
            print("Hashing Power: %ld hashes per second" % hash_power)<jupyter_output>Difficulty: 1 (0 bits)
Starting search...
Success with nonce 0
Hash is 3668b619d449a007c5e8bb2f038edb80270665f0f7d1f821722d800c93a48354
Elapsed Time: 0.0004 seconds
Hashing Power: 0 hashes per second
Difficulty: 2 (1 bits)
Starting search...
Success with nonce 3
Hash is 1644c850e78d4f4bd08a41e0de0926798ca114018ff92d17a9dfaf5b2320af5b
Elapsed Time: 0.0021 seconds
Hashing Power: 1463 hashes per second
Difficulty: 4 (2 bits)
Starting search...
Success with nonce 0
Hash is 200dc175db7698acff9a4f9b4f116d2ca465b2d5ece577edb6616f3529d12d57
Elapsed Time: 0.0002 seconds
Hashing Power: 0 hashes per second
Difficulty: 8 (3 bits)
Starting search...
Success with nonce 3
Hash is 1a8df5bcefc9cbc670b5af27c614a3d0ecadddc0dffa64e9f1db11ea27fbc6a9
Elapsed Time: 0.0003 seconds
Hashing Power: 9118 hashes per second
Difficulty: 16 (4 bits)
Starting search...
Success with nonce 7
Hash is 08c3d5e18b138eab0f84084af9c1dd6827456ceb199a10916f5dba523c8e9b8b
Elapsed Time: 0.0005 seconds
Hashing Power: 14553 hashes [...]
{'fraction_non_alphanumeric': 0.07901680322177475, 'fraction_numerical': 0.08276628246076934, 'mean_word_length': 4.084009883515708, 'pattern_counts': {'":': 20, '<': 20, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '79305', 'license': 'MIT', 'n_tokens_mistral': 5099, 'n_tokens_neox': 4328, 'n_words': 1461, 'path': 'basics/chapter3.ipynb', 'repo_name': 'prasad03kp/blockchain101-scem', 'size': '14412'}