HomeBlockchainBlockchain DIYDeveloping Blockchain in Python

Developing Blockchain in Python

Welcome to the series “From Zero to Blockchain in Python” where we will build an implementation of a blockchain application, specifically a cryptocurrency from scratch. Throughout the series, we will build and improve the functionalities until we have a fully functional demo.

Disclaimer: Please note that this is by no means intended to be used in a real scenario, the code used here is for educational purposes only.

In this part we will build the following basic blockchain functionality:

  • Possibility to add blocks to the
  • Simple Proof of Work (PoW) algorithm
  • Possibility to add transactions
  • Possibility to mine new blocks
  • Possibility to replace the chain with a new one

And we will keep the following for future posts:

  • Wallet management
  • Sign transactions
  • Peer to Peer communication

Let the fun begin

We are now ready to start developing our blockchain module by module, In this article I’ll guide you step by step with code samples and explanations, going through my thought process and the code implementation. To stay focus on the main concepts, I’ll not cover here how I implemented swagger documentation or tests for code, but a copy of the full working example can be found here:

https://github.com/bajcmartinez/blockchainpy/tree/part_1

Basic Structures

The first things we need to discuss for our blockchain are blocks and the data we want to store in our blocks. For our purposes, we will be storing transaction information, so data like sender, recipient and amount. For our purposes we will use classes, it will be easier to understand and follow the code.

import time


class Transaction:
    def __init__(self, sender, recipient, amount):
        """
        Creates a new transaction

        :param sender: <str> sender account
        :param recipient: <str> recipient account
        :param amount: <float> amount to be transferred
        """
        self.sender = sender
        self.recipient = recipient
        self.timestamp = time.time()
        self.amount = amount

    def validate(self):
        """
        Checks if a transaction is valid

        :return: <bool> True if it is valid, False if not.
        """

        # Prevent stealing by creating negative transactions
        if self.amount < 0:
            return False

        return True

This is extremely simple, and we already created a validate function we will use later on, for now, we just won’t allow any transactions with negative amounts to prevent stealing coins. Next our Block definition:

Block = {
    index: number
    timestamp: number
    transactions: [Transactions]
    nonce: string
    previous_hash: string
    hash: string
}

Our block is also simple, it contains an index, a timestamp, a list of transactions that belong to the block, a nonce which we will use as the proof of work , the hash to the previous block and the hash for our block.

import hashlib
from api.schema.block import BlockSchema
from time import time


class Block:
    def __init__(self, index, transactions, nonce, previous_hash):
        """
        Constructs a new block

        :param index:
        :param transactions:
        :param previous_hash:
        """
        self.index = index
        self.timestamp = time()
        self.transactions = transactions
        self.nonce = nonce
        self.previous_hash = previous_hash
        self.hash = self.hash_block()

    def serialize(self, ignore=None):
        """
        Serializes a block into a string

        :return:
        """
        if ignore is None:
            ignore = []
        block_params = {x: self.__dict__[x] for x in self.__dict__ if x not in ignore}

        return BlockSchema().dumps(block_params)

    def hash_block(self):
        """
        Calculates the hash of the block

        :return:
        """
        sha = hashlib.sha256()
        sha.update(self.serialize(['hash']).encode('utf-8'))
        return sha.hexdigest()

With the blocks, things look a bit weird, so let’s go step by step. The important function in this class is the method hash_block which serializes the block information (minus the hash attributes) and returns a sha256 representation of the object. This representation is then used as the hash parameter of the block and will later be used as cryptographic proof that the block hasn’t been changed.

Blockchain

Now we need to glue it together with all the blockchain logic. Our blockchain will also be a class, where we are going to store all the information we need to monitor and function the blockchain.

Properties

self.__chain = []
self.__current_transactions = []

@property
def last_block(self):
    return self.__chain[-1]

@property
def last_transaction(self):
    return self.__current_transactions[-1]

@property
def pending_transactions(self):
    return self.__current_transactions

@property
def full_chain(self):
    return self.__chain

This is all we need to host our blockchain, at least for now. Let’s review what each one is all about:

  • __chain: A list of Blocks representing the blockchain [private]
  • __current_transactions: The list of transactions which are not yet part of a block [private]
  • last_block: The last block added to the chain
  • last_transaction: The last transaction added to the chain
  • pending_transactions: returns the __current_transactions attribute
  • full_chain: returns the __chain attribute

Constructor

def __init__(self):
    self.__current_transactions = []
    self.__chain = []
    # Create genesis block
    self.create_genesis()

def create_genesis(self):
    """
    Creates the Genesis block and passes it to the chain

    :return: None
    """
    genesis_block = Block(0, self.__current_transactions, 0, '00')
    self.__chain.append(genesis_block)

In our constructor, we will simple initialize the 2 lists and create our first block, which is called the genesis block. it will have no transactions attached to it, no nonce, and it’s previous hash will be simple 00 to represent that there is no previous hash.

Creating Transactions

def create_transaction(self, sender, recipient, amount):
    """
    Creates a new transaction to go into the next block

    :param sender: <str> sender address
    :param recipient: <str> recipient address
    :param amount: <float> amount
    :return: <Transaction> generated transaction
    """
    transaction = Transaction(sender, recipient, amount)

    if transaction.validate():
        self.__current_transactions.append(transaction)

        return transaction, True

    return None, False

Simply as generating the transaction object, validating if it’s valid and attaching it to the chain.

Proof of Work

Now we start with the heavy-duty of blockchains, the proof of work. The proof of work consists of 2 functions, one proof of work generator, and another validator. The way our implementation works is by taking the latest block and add a nonce such to satisfy that:

f`{last_nonce}{last_hash}{nonce}`

hashed with sha256 will result in 4 leading zeroes.

@staticmethod
def validate_proof_of_work(last_nonce, last_hash, nonce):
    """
    Validates the nonce

    :param last_nonce: <int> Nonce of the last block
    :param nonce: <int> Current nonce to be validated
    :param last_hash: <str> Hash of the last block
    :return: <bool> True if correct, False if not.
    """
    sha = hashlib.sha256(f'{last_nonce}{last_hash}{nonce}'.encode())
    return sha.hexdigest()[:4] == '0000'

def generate_proof_of_work(self, block):
    """
    Very simple proof of work algorithm:

    - Find a number 'p' such that hash(pp') contains 4 leading zeroes
    - Where p is the previous proof, and p' is the new proof

    :param block: <Block> reference to the last block object
    :return: <int> generated nonce
    """
    last_nonce = block.nonce
    last_hash = block.hash

    nonce = 0
    while not self.validate_proof_of_work(last_nonce, last_hash, nonce):
        nonce += 1

    return nonce

The only way to find our nonce value is by try an error, we will start with the value 0, and adding 1 at a time until the validation function gives a positive. This is intended to be a process intensive calculation to prevent agents from inserting or updating corrupted blocks.

Mine

We are now ready to mine our first block, let’s see how to do that by examine the process:

  1. We calculate the nonce for our new block
  2. We create a new transaction to give the miner a reward, this transaction will have 0 as the sender and will generate 1 coin to the reward address
  3. We will create the block and add it to the chain
def mine(self, reward_address):
    """
    Mines a new block into the chain

    :param reward_address: <str> address where the reward coin will be transferred to
    :return: result of the mining attempt and the new block
    """
    last_block = self.last_block
    index = last_block.index + 1
    previous_hash = last_block.hash

    # Let's start with the heavy duty, generating the proof of work
    nonce = self.generate_proof_of_work(last_block)

    # In the next step we will create a new transaction to reward the miner
    # In this particular case, the miner will receive coins that are just "created", so there is no sender
    self.create_transaction(
        sender="0",
        recipient=reward_address,
        amount=1,
    )

    # Add the block to the new chain
    block = Block(index, self.__current_transactions, nonce, previous_hash)

    if self.add_block(block):
        return block

    return None

Let’s now examine the add_block function and what it entails:

def add_block(self, block):
  """
  Creates a new block and passes it to the chain

  :param block: <Block> Block to add to the chain
  :return: <bool> True if the block is added to the chain, False if not.
  """
  if self.validate_block(block, self.last_block):
      self.__chain.append(block)

      # Remove transactions from the list
      self.__current_transactions = []

      return True

  return False

def validate_block(self, current_block, previous_block):
  """
  Validates a block with reference to its previous

  :param current_block:
  :param previous_block:
  :return:
  """
  # Check the block index
  if current_block.index != previous_block.index + 1:
      return False

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

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

  if not self.validate_proof_of_work(previous_block.nonce, previous_block.hash, current_block.nonce):
      return False

  return True

As we get now to this point things start looking a bit strange around the validate_block function. What are all those ifs? The main purpose of this function is to validate that the block belongs to the chain, that the previous_hash attributes matches with the previous block, that the hash stored in the block matches the actually calculated hash and that our proof of work is valid.

Replacing the Blockchain

This is a very important step, in case of a conflict on the blockchain among peers, we need a system to define which is the correct one, for that we will follow the bitcoin approach saying that the chain with the most work put into will be the right one.

The amount of work will be a very simple calculation for now, and it’s the number of blocks on the chain. I know there’s room for improvement, and we can work on that later on. Here is how the code looks like:

def validate_chain(self, chain_to_validate):
    """
    Verifies if a given chain is valid

    :param chain_to_validate: <[Block]>
    :return: <bool> True if the chain is valid
    """
    # First validate both genesis blocks
    if chain_to_validate[0].hash_block() != self.__chain[0].hash_block():
        return False

    # Then we compare each block with its previous one
    for x in range(1, len(chain_to_validate)):
        if not self.validate_block(chain_to_validate[x], chain_to_validate[x - 1]):
            return False

    return True

def replace_chain(self, new_chain):
    """
    Attempts to replace the chain for a new one

    :param new_chain:
    :return: <bool> True if the chain was replace, False if not.
    """
    # We only replace if the new chain is bigger than the current one
    if len(new_chain) <= len(self.__chain):
        return False

    # Validate the new chain
    if not self.validate_chain(new_chain):
        return False

    new_blocks = new_chain[len(self.__chain):]
    for block in new_blocks:
        self.add_block(block)

Simple, we compare the sizes of the chains, we make sure the new chain has all valid blocks and we simply insert into our blockchain all the missing blocks, wonderful, except for this:

# Then we compare each block with its previous one
for x in range(1, len(chain_to_validate)):
    if not self.validate_block(chain_to_validate[x], chain_to_validate[x - 1]):
        return False
Does it hurt your eyes? Every time we want to validate an incoming chain we need to validate all the blocks on the chain, there has to be a better approach especially if we consider many of those blocks we probably have on our own chain. Well… there is a better way, but it won’t be covered now, we will take a look into that in a future post, but if you are intrigued and want to do the research and create a PR you are welcome to do so :), just take a look at Merkle Trees .

Congrats!

That’s it… pretty much… you still need to expose all the methods we discussed on a server of some sort, in my demo project I’m using flask and Swagger so that you have a UI to test and see what’s going on. All the information to run the project is on the README file.

There is still a lot more work to be done, like creating node management and synchronization, signing transactions, adding merkle trees, etc. I’m going to continue working on the project and posting regularly here the updates, so stay tuned!

Thanks so much for staying with me so long, hope this project has been interesting. If you want to know more, or if you want me to work on a specific topic, please email me at [email protected], I’ll be happy to support.

Happy coding!

This article has been published from the source link without modifications to the text. Only the headline has been changed.

Source link

 

Most Popular