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:
- We calculate the nonce for our new block
- 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
- 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
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.