📜 ⬆️ ⬇️

We study the blockchain in practice

You are reading this article because, like me, you watch the increasing popularity of cryptocurrency with keen interest. And you want to understand how blockchain works - the technology that underlies it.

But to understand the blockchain is not so easy, at least in my experience. I pored over abstruse videos, pushed through tutorials, and with growing annoyance noted the lack of illustrative examples.

I prefer to study in the process. In this scenario, I have to work on the topic immediately at the code level, which helps to consolidate the skill. If you follow my example, by the end of the article you will have a functioning blockchain and a clear understanding of how this all works.
')


But for starters ...


Let me remind you that a blockchain is an unchangeable, consistent chain of entries called blocks. They can include transactions, files, and, in principle, any other kind of data. The main thing here is that they are connected to each other by means of hashes.

If you do not quite understand what a hash is, go here .

Who is this guide intended for? For those who can read and write simple Python code without any problems and in general terms how HTTP requests work - we will communicate with our blockchain via HTTP.

What will be needed for work? Make sure you have Python 3.6+ installed (along with pip). You will also need to install Flask and the excellent Requests library:

pip install Flask==0.12.2 requests==2.18.4 

Oh yeah, you also need an HTTP client, for example, Postman or cURL. Anybody here will do.

Where can I see what happens in the end? Source code is available here .

Step One: Making the Blockchain


Open your favorite text or graphics editor, for example I like PyCharm . Create a new file called blockchain.py. We will work only in this file, and if you get confused, you can always peek into the source code .

Blockchain view

First, we create a new class, the constructor of which will create the original empty list (where our blockchain will be stored) and one more for transactions. This is how the class structure looks like:

 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 


The Blockchain class is responsible for managing the chain. Transactions will be stored here, as well as some auxiliary methods for adding new blocks to the chain. Let's write out these methods.

What does a block look like?

Each block contains an index, a timestamp (in Unix), a list of transactions, a proof, and a hash of the previous block.

Here is an example of how a separate block can look like:

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

Now the idea of ​​the chain should be obvious - each block includes a preceding hash. This is very important: this is how the immutability of the chain is ensured: if a hacker damages a block, absolutely all subsequent ones will contain invalid hashes.

Clear? If not, stop and give yourself time to assimilate this information - it is the basic principle of the blockchain.

Add transactions to the block

We need to somehow add new transactions to the block. The new_transaction () method is responsible for this ; it works quite simply:

 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 

When new_transaction () adds a new transaction to the list, it returns the index of the block where it was recorded to the next one with which mining will be done. Later it will come in handy to the next user who adds the transaction.

In addition to creating a genesis block in the constructor, we will also write down the new_block () , new_transaction (), and hash () methods:

 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 code probably doesn’t need any explanations - I added comments and dokstrings here and there to make it clearer. With the introduction of the blockchain, we are almost done. But now you must be wondering how the process of creating, embedding and mining blocks takes place.

We deal with the proof of work

The proof of work algorithm is used to create new blocks in the blockchain (this process is also called mining). The goal of the proof of work is to calculate the desired value to solve the equation. This value should be difficult to calculate (from a mathematical point of view), but easy to check for any member of the system. This is the main idea of ​​the proof of work.

To make it clearer, let's consider a very simple example.

Suppose a hash of some number X multiplied by another Y should end with 0. Accordingly, hash (x * y) = ac23dc ... 0. For this simplified example, we set x = 5. We set all this 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 correct answer is here: y = 21; it is with this value that the hash with 0 at the end is obtained

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

In Bitcoin, the proof of work algorithm is called HashCash and is not particularly different from the simple example above. This is the equation that the miners are trying to solve in order to create a new block. In general, the complexity is determined by how many characters you need to calculate in a given sequence. For the correct answer miners receive remuneration in the form of one coin - during the transaction.

Check their solution for the system is not difficult.

We write a simple proof of work

Now let's write a similar algorithm for our blockchain. Conditions take in the spirit of the above example:

Find the number p, which, when hashed with the proof of the previous block, gives a hash with four zeros at the beginning.

 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" 

We can vary the complexity of this task by changing the number of zeros at the beginning. But four is enough. You can see for yourself that a single additional zero significantly slows down the process of finding a solution.

The work on the class is almost complete and now we are ready to begin interacting with it using HTTP requests.

Step Two: Blockchain as an API


Here we will use Python Flask, a microframe that facilitates the process of correlating end points with Python functions, which allows us to communicate with the blockchain over the Web using HTTP requests.

We create three methods:


Customize Flask

Our "server" will generate a single network node in the blockchain system. Let's write some sample code:

 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) 

A brief explanation of what we added:

Line 15: Instantiate a node. Read more about Flask here .
Line 18: Create an arbitrary name for the node.
Line 21: Instantiates the Blockchain class.
Lines 24-26: Creates a / mine endpoint, i.e. a GET request.
Lines 28-30: Creates an endpoint for / transactions / new , that is, a POST request, since that is where we will send the data.
Lines 32-38: Creates an endpoint / chain , which returns the whole blockchain.
Lines 40-41: Starts the server on port 5000.

End point for transactions

Here's what the transaction request will look like. This is what the user sends to the server:

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

We already have a class method for adding a transaction to the block, so everything is easy further. Let's 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 destination

It is at this end point that all the magic is created, but there is nothing particularly difficult in it. She should do three things:

  1. Calculate proof of work
  2. To issue a miner (that is, us) a reward, adding a transaction, during which we receive one coin
  3. Embed a new block in 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 block = blockchain.new_block(proof) response = { 'message': "New Block Forged", 'index': block['index'], 'transactions': block['transactions'], 'proof': block['proof'], 'previous_hash': block['previous_hash'], } return jsonify(response), 200 

Please note that the node address is specified as the recipient of the created block. Most of what we do here comes down to interacting with the methods of our Blockchain class. Upon completion of this step, the main work is completed, you can start a dialogue.

Step Three: Dialogue with the blockchain


To interact with the API within the system, you can use the good old cURL or Postman.

We start the server:

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

Let's try to create a block by sending a GET request to localhost: 5000 / mine:


Now we create a new transaction by sending a POST request containing its structure to the address localhost : 5000 / transactions / new:


If you're not working with Postman, here's how to formulate a similar query in cURL:

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

I restarted the server and created two more blocks in order to get three in the end. Let's examine the resulting chain through a query localhost: 5000 / 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 Four: Consensus


All this is very cool. We have a simple blockchain, which allows you to make transactions and create new blocks. But the blockchain only makes sense if it is decentralized. And if we make it decentralized, how can we guarantee that the same chain will be displayed everywhere? This is called the problem of consensus. If we want the system to have more than one node, we will have to introduce a consensus algorithm.

Recognize new nodes

Before implementing the algorithm of consensus, we need to do something so that each node in the system knows about the existence of its neighbors. Each node in the system must have a registry of all other nodes. So you need additional endpoints:

  1. / nodes / register , which will accept the list of new nodes in the URL format
  2. / nodes / resolve to implement a consensus algorithm that will resolve conflicts and keep track of the correct chain in the node.

We need to correct the blockchain constructor and provide a method for registering nodes:

 ... 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) 

Notice: we used set () to store the list of nodes. This is a simple way to ensure that adding new nodes will be respected indempotency - that is, no matter how many times we add any particular node, it will be counted only once.

Implement a consensus algorithm

As I already mentioned, a conflict occurs when the chain of one node is different from the chain of another. To eliminate it, we introduce the following rule: the prerogative is always for the chain that is longer. In other words, the longest chain in the system is considered as actual. Using this algorithm, we reach a consensus among all the nodes of the system:

 ... 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 method valid_chain () is responsible for checking the chains for validity, passing each block and verifying both the hash and the proof.

resolve_conflicts () is a method that processes all neighboring nodes: downloads their chains and checks them in the manner described above. If a valid chain is found longer than ours, a replacement is made.

Let's enter into our API two endpoints, one to add neighbors, and the other to resolve 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 stage, if you want, you can attract other machines and build different nodes for your system. Or achieve the same using different ports on the same machine. I created a new node on another port on the same machine, and allowed the original node to recognize it. Thus, it turned out two sites: localhost: 5000 and localhost: 5001.


In node number two, I added more blocks so that the chain turned out to be definitely longer. Then I called GET / nodes / resolve in the first node - and the consensus algorithm replaced its chain with the chain of the second.


Well that's all. Now gather your friends and test your blockchain together.

I hope this material will inspire you with new ideas. Personally, I watch the development of cryptocurrency with great enthusiasm: I am sure that the blockchain will turn our ideas about the economy, government and storage of information.

In the future, I plan to release the second part of the article, where we will add the transaction validation mechanism to the blockchain and talk about how all this can be used in products.

Source: https://habr.com/ru/post/339280/


All Articles