How to build a blockchain

This article is translated from Learn Blockchains by Building One by @dvf , original link: https://hackernoon.com/learn-blockchains-by-building-one-117428612f46

 

On October 24 last year, blockchain was promoted to the national strategic position, and companies including traditional large enterprises and central enterprises have laid out blockchain. There is a wave of learning blockchain in society, and blockchain talents have become "hot goods".

 

For our craftsmen who write programs, the fastest way to learn blockchain is to create a blockchain. In the following, we will lead you to build a simple blockchain and learn in practice, which can not only deepen your understanding of blockchain, but also acquire skills.

 

Blockchain is a chain connected by immutable and continuous blocks recording information. Blocks contain transaction information, files, or any data you want to record. Most importantly, the blocks are linked together by "hashes.".

 

Before you start, you need to install Python 3.6 + (and pip), Flask, and Requests library:

pip install Flask==0.12.2 requests==2.18.4

 

In addition, you need an HTTP client, either Postman or cURL.

 

Now we can start.

 

Step 1: create a blockchain

Open a text editor or IDE. Pycharms are recommended here.

 

Create a new file named blockchain.py. Only one file will be used in the whole process. If you lose it, you can find the source file here: https://github.com/dvf/blockchain

 

Present blockchain

Create a blockchain class. In this class, constructor will create an original blank list to store the blockchain and another to store the transaction. Here is the blueprint of class:

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

Blockchain class is responsible for managing the chain, storing transactions, and adding new blocks to the chain.

 

What is a block like?

Each block has an index, a timestamp, a transaction list, a proof, and a hash value for the previous block. Here is an example:

block = {
    'index': 1,
    'timestamp': 1506057125.900785,
    'transactions': [
        {
            'sender': "8527147fe1f5426f9dd545de4b27ee00",
            'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
            'amount': 5,
        }
    ],
    'proof': 324984774000,
    'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}

 

As you can see from this example, the idea of "chain" is very obvious -- each new block contains the hash value of the previous block. This is critical because it makes the blockchain "immutable": if an attacker destroys an earlier block on the chain, all subsequent blocks will contain incorrect hash values.

 

Add transaction to block

Use new transaction():

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

After new_transaction() adds a transaction to the list, it will return the index of the next block to be dug out. This is useful for users who submit transactions.

 

Create new block

When Blockchain is materialized, we need to create "creation block". First, we need to add a proof in the genesis Blockchain, which is the result of mining (proof of workload). We'll talk about mining later.

 

In addition to adding the creation block in the constructor, you need to fill in new block(), new transaction(), hash():

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

    @property
    def last_block(self):
        return self.chain[-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()

 

The above example should be clear. To help you understand, I also added some comments and docstrings. We have almost finished the demonstration of blockchain. Next, let's see how a new block was dug out.

 

Proof of Work

Workload proof algorithm is a method of how blocks are created or dug out. The goal of workload proof is to find a "number" to solve the puzzle, which is hard to find but easy to be verified by people in the network through calculation.

 

Let's take a very simple example to further understand the problem.

 

First definition: the hash value of an integer X times Y must end with 0, so hash (x*y) = ac23dc 0, where X=5. Implemented in Python:

from hashlib import sha256
x = 5
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=21, because the generated hash ends with 0:

hash(5 * 21) = 1253e9373e...5e3600155e860

 

In bitcoin blockchain, workload proof algorithm is called Hashcash, which is not much different from the above example. This is an algorithm that miners compete to solve in order to create a new block. The difficulty depends on the number of characters searched in the string. Then, miners who successfully dig up the block will receive bitcoin as a reward.

 

Networks can easily validate their solutions.

 

Proof of deployment basic workload

Next, deploy a similar algorithm for our blockchain, and the rules are similar to the above example.

 

When we use the hash value 4 of the previous block for hash operation, we find a number P and output "0s":

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"

 

In order to adjust the difficulty of the algorithm, we can modify the number of leading zeros. But 4 is enough, and you'll find that adding a leading zero makes a huge difference in the time it takes to find a solution.

 

Here, class is almost finished. Next, I'm ready to use HTTP request to interact with it.

 

Step 2: Blockchain as an API

Using the Python Flask Framework, create three paths:

/transactions/new add a new transaction to the block

/mine tells the server to dig up a new block

/chain returns the whole blockchain

 

Set Flask

Our server is a node in the blockchain network. Next, create some template Codes:

import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4

from flask import Flask


class Blockchain(object):
    ...


# Instantiate our Node
app = Flask(__name__)

# Generate a globally unique address for this node
node_identifier = str(uuid4()).replace('-', '')

# Instantiate the Blockchain
blockchain = Blockchain()


@app.route('/mine', methods=['GET'])
def mine():
    return "We'll mine a new Block"
  
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
    return "We'll add a new transaction"

@app.route('/chain', methods=['GET'])
def full_chain():
    response = {
        'chain': blockchain.chain,
        'length': len(blockchain.chain),
    }
    return jsonify(response), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

 

Make a simple explanation for the content added above:
Line15: materialized node

Line18: create a random name for the node

Line21: materialized Blockchain class

Line24-26: create the / mine endpoint, which is a GET request.

Line28-30: create the / transactions/new endpoint, which is a POST request because we want to send data to it.

Line32-38: create the / chain endpoint and return to the whole blockchain.

Line40-41: run the server on port 5000.

 

Trading endpoint

This is an example of a transaction request. Here is what the user sends to the server:

{
 "sender": "my address",
 "recipient": "someone else's address",
 "amount": 5
}

 

With the class path to add transactions to blocks, the rest is simple. Write a function to add a transaction:

import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4

from flask import Flask, jsonify, request

...

@app.route('/transactions/new', methods=['POST'])
def new_transaction():
    values = request.get_json()

    # Check that the required fields are in the POST'ed data
    required = ['sender', 'recipient', 'amount']
    if not all(k in values for k in required):
        return 'Missing values', 400

    # Create a new Transaction
    index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])

    response = {'message': f'Transaction will be added to Block {index}'}
    return jsonify(response), 201

 

Mining endpoint

The end point of mining is a place to show miracles, mainly doing three things:

Proof of calculation workload

One coin for successful absenteeism

Add a new block to the chain

 

import hashlib
import json

from time import time
from uuid import uuid4

from flask import Flask, jsonify, request

...

@app.route('/mine', methods=['GET'])
def mine():
    # We run the proof of work algorithm to get the next proof...
    last_block = blockchain.last_block
    last_proof = last_block['proof']
    proof = blockchain.proof_of_work(last_proof)

    # We must receive a reward for finding the proof.
    # The sender is "0" to signify that this node has mined a new coin.
    blockchain.new_transaction(
        sender="0",
        recipient=node_identifier,
        amount=1,
    )

    # Forge the new Block by adding it to the chain
    previous_hash = blockchain.hash(last_block)
    block = blockchain.new_block(proof, previous_hash)

    response = {
        'message': "New Block Forged",
        'index': block['index'],
        'transactions': block['transactions'],
        'proof': block['proof'],
        'previous_hash': block['previous_hash'],
    }
    return jsonify(response), 200

 

Note that the recipient of the excavated block is the address of our node. All we have done is to interact with the path in Blockchain class. Now we can interact with blockchain.

 

Step3: interact with Blockchain

You can use regular cURL or Postman to interact with the API over the network.

 

Start server:

$ python blockchain.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

Let's try to dig a block through GET request to http://localhost:5000/mine:.

(create GET request with Postman)

 

Create a transaction by POST request to http://localhost:5000/transactions/new.

(POST request created with Postman)

 

If you don't use Postman, you can use cURL:

$ curl -X POST -H "Content-Type: application/json" -d '{
 "sender": "d4ee26eee15148ee92c6cd394edd974e",
 "recipient": "someone-other-address",
 "amount": 5
}' "http://localhost:5000/transactions/new"

 

Restart the server and dig out two blocks. Now there are three blocks in total. Next, through the request http://localhost:5000/chain View the entire chain.

 

{
  "chain": [
    {
      "index": 1,
      "previous_hash": 1,
      "proof": 100,
      "timestamp": 1506280650.770839,
      "transactions": []
    },
    {
      "index": 2,
      "previous_hash": "c099bc...bfb7",
      "proof": 35293,
      "timestamp": 1506280664.717925,
      "transactions": [
        {
          "amount": 1,
          "recipient": "8bbcb347e0634905b0cac7955bae152b",
          "sender": "0"
        }
      ]
    },
    {
      "index": 3,
      "previous_hash": "eff91a...10f2",
      "proof": 35089,
      "timestamp": 1506280666.1086972,
      "transactions": [
        {
          "amount": 1,
          "recipient": "8bbcb347e0634905b0cac7955bae152b",
          "sender": "0"
        }
      ]
    }
  ],
  "length": 3
}

 

Step 4: consensus

Now, we have a basic blockchain, which can receive transactions and dig out blocks. But the core point of blockchain is "decentralization". In addition, if they are decentralized, how do you ensure that they map the entire chain? This involves the issue of consensus. If we want to achieve decentralization, we need to ensure that there are multiple nodes in the network. If there are multiple nodes, we need a consensus algorithm.

 

Register a new node

Before deploying the consensus algorithm, we need to find a way to let the nodes know the neighboring nodes on the network. Each node on the network should save the registry of other nodes. So we need more endpoints:

/nodes/register receives a list of new nodes in the form of URL s

/nodes/resolve deploy consensus algorithm to resolve conflicts and ensure that all nodes are on the correct chain.

 

We need to modify the constructor of the blockchain and provide the method to register the node:

...
from urllib.parse import urlparse
...


class Blockchain(object):
    def __init__(self):
        ...
        self.nodes = set()
        ...

    def register_node(self, address):
        """
        Add a new node to the list of nodes
        :param address: <str> Address of node. Eg. 'http://192.168.0.5:5000'
        :return: None
        """

        parsed_url = urlparse(address)
        self.nodes.add(parsed_url.netloc)

 

Note that we used set () to save the node list. This ensures that the addition of new nodes is idempotent, that is, the same node appears only once no matter how many times it is added.

 

Deployment consensus algorithm

Node conflict refers to the difference between one node and another node, with different chains. To solve this problem, we make a rule that the longest chain is the right one. Using this algorithm, the nodes in the network reach a consensus.

 

...
import requests


class Blockchain(object)
    ...
    
    def valid_chain(self, chain):
        """
        Determine if a given blockchain is valid
        :param chain: <list> A blockchain
        :return: <bool> True if valid, False if not
        """

        last_block = chain[0]
        current_index = 1

        while current_index < len(chain):
            block = chain[current_index]
            print(f'{last_block}')
            print(f'{block}')
            print("\n-----------\n")
            # Check that the hash of the block is correct
            if block['previous_hash'] != self.hash(last_block):
                return False

            # Check that the Proof of Work is correct
            if not self.valid_proof(last_block['proof'], block['proof']):
                return False

            last_block = block
            current_index += 1

        return True

    def resolve_conflicts(self):
        """
        This is our Consensus Algorithm, it resolves conflicts
        by replacing our chain with the longest one in the network.
        :return: <bool> True if our chain was replaced, False if not
        """

        neighbours = self.nodes
        new_chain = None

        # We're only looking for chains longer than ours
        max_length = len(self.chain)

        # Grab and verify the chains from all the nodes in our network
        for node in neighbours:
            response = requests.get(f'http://{node}/chain')

            if response.status_code == 200:
                length = response.json()['length']
                chain = response.json()['chain']

                # Check if the length is longer and the chain is valid
                if length > max_length and self.valid_chain(chain):
                    max_length = length
                    new_chain = chain

        # Replace our chain if we discovered a new, valid chain longer than ours
        if new_chain:
            self.chain = new_chain
            return True

        return False

 

The first path, valid_chain(), checks whether the chain is valid by "patrolling" each block and verifying the hash value and proof of the block.

 

resolve_conflicts() is responsible for "patrolling" all neighboring nodes, downloading their chains, and verifying that they use the above paths. If we find a valid chain longer than ours, this chain will replace ours.

 

Let's register these two endpoints in our API, one for adding adjacent nodes and the other for resolving conflicts:

@app.route('/nodes/register', methods=['POST'])
def register_nodes():
    values = request.get_json()

    nodes = values.get('nodes')
    if nodes is None:
        return "Error: Please supply a valid list of nodes", 400

    for node in nodes:
        blockchain.register_node(node)

    response = {
        'message': 'New nodes have been added',
        'total_nodes': list(blockchain.nodes),
    }
    return jsonify(response), 201


@app.route('/nodes/resolve', methods=['GET'])
def consensus():
    replaced = blockchain.resolve_conflicts()

    if replaced:
        response = {
            'message': 'Our chain was replaced',
            'new_chain': blockchain.chain
        }
    else:
        response = {
            'message': 'Our chain is authoritative',
            'chain': blockchain.chain
        }

    return jsonify(response), 200

 

At this time, if you like, you can change to a different computer, or you can rotate different nodes in your network. Or start the process using different ports on the same computer. I created another node on another port on my computer and registered it with the current node. In this way, I have two nodes:

http://localhost:5000

http://localhost:5001

 

(register new node)

 

Then I dig out some new blocks on node 2 to make sure the chain is longer. After that, I call GET /nodes/resolve on node 1, where the chain is replaced by a consensus algorithm.

 

 

(consensus algorithm in work)

 

Here, a complete public blockchain is almost formed. You can find some friends to test it for you.

 

Wanxiang blockchain has been involved in blockchain since 2015. If you are interested in blockchain, welcome to join us!

Tags: Blockchain network JSON Python

Posted on Tue, 07 Apr 2020 05:57:14 -0400 by adams0423