The author believes that the fastest way to learn about blockchains is to create one yourself, and in this article, we'll follow the author as he creates a blockchain in Python.
We're new to the rise of digital currencies and are wondering how the technology behind them - the blockchain - makes it possible.
But fully understanding blockchain is not an easy task, I like to learn by doing, and learning the technology by writing code leads to a stronger grasp. You can deepen your understanding of blockchain by building a blockchain.
preliminary
This article requires the reader to have a basic understanding of Python, be able to read and write basic Python, and needs a basic understanding of HTTP requests.
We know that a blockchain is an immutable, ordered chain structure made up of records of blocks, which can be transactions, files, or any data you want, and the important thing is that they are linked by hashes (hashes).
If you don't know much about hashing yet, check out thethis article
environmental preparation
Environment preparation, make sure you have installed Python 3.6+, pip , Flask, requests
Installation method:
pip install Flask==0.12.2 requests==2.18.4
An HTTP client such as Postman, cURL or another client is also required.
Reference source code (the original code was not working when I translated it, I forked a copy, fixed the bugs in it and added the translation, thanks to star)
Start creating a Blockchain
Create a new file , all the code in this article is written in this one file, you can always refer to thesource code (computing)
Blockchain Classes
First a Blockchain class is created and in the constructor two lists are created, one for storing the blockchain and one for storing transactions.
Here is the framework for the Blockchain class:
class Blockchain(object): def __init__(self): = [] 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 used to manage the chain, it stores transactions, adds new blocks, etc. Let's refine these methods further below.
block structure
Each block contains attributes: index, Unix timestamp, list of transactions, proof of workload (explained later), and the hash value of the previous block.
Here is the structure of a block:
block = { 'index': 1, 'timestamp': 1506057125.900785, 'transactions': [ { 'sender': "8527147fe1f5426f9dd545de4b27ee00", 'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f", 'amount': 5, } ], 'proof': 324984774000, 'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" }
By this point, the concept of blockchain is clear, each new block contains the Hash of the previous block, which is the key point that guarantees blockchain immutability. If an attacker corrupts one of the previous blocks, then the Hash of all the subsequent blocks becomes incorrect. If you don't understand it, take your time to digest it and refer to {% post_link whatbc blockchain technology principles %}
Join the deal
Next we need to add a transaction to refine the new_transaction method.
class Blockchain(object): ... def new_transaction(self, sender, recipient, amount): """ Generate new transaction information,Information will be added to the next block to be mined :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
method adds a transaction record to the list and returns the index of the block (the next block to be mined) to which the record will be added, which will be useful later when the user submits a transaction.
Creating a new block
When Blockchain is instantiated, we need to construct a genesis block (the first block with no predecessors) and add a proof of workload to it.
Each block needs to go through proof of workload, commonly known as mining, which will be continued later.
In order to construct the genesis block, we also need to refine 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 = [] = [] # Create the genesis block self.new_block(previous_hash=1, proof=100) def new_block(self, proof, previous_hash=None): """ Generate new blocks :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() + 1, 'timestamp': time(), 'transactions': self.current_transactions, 'proof': proof, 'previous_hash': previous_hash or ([-1]), } # Reset the current list of transactions self.current_transactions = [] (block) return block def new_transaction(self, sender, recipient, amount): """ Generate new transaction information,Information will be added to the next block to be mined :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 [-1] @staticmethod def hash(block): """ Generate the SHA-256 hash value of the block :param block: <dict> Block :return: <str> """ # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes block_string = (block, sort_keys=True).encode() return hashlib.sha256(block_string).hexdigest()
With the code and comments above you can get an intuitive understanding of the blockchain, next we look at how the blocks are mined.
Understanding proofs of workload
The new block relies on a proof-of-work algorithm (PoW) to construct it.The goal of PoW is to find a number that meets a specific condition that is hard to calculate but easy to verify. This is the core idea of proof of workload.
To make it easier to understand, here's an example:
Assume that the hash value of the product of an integer x multiplied by another integer y must end in 0, i.e., hash(x * y) = ac23dc...0. Given variable x = 5, what is the value of y?
Implemented in Python as follows:
from hashlib import sha256 x = 5 y = 0 # y unknown while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0": y += 1 print(f'The solution is y = {y}')
The result is y = 21. because:
hash(5 * 21) = 1253e9373e...5e3600155e860
In Bitcoin, a proof-of-work algorithm called Hashcash is used, which is very similar to the problem above. Miners compete for the right to create a block and calculate the result. Typically, the difficulty of the calculation is proportional to the number of specific characters that need to be satisfied in the target string, and miners are rewarded with bitcoins for calculating the result.
Of course, it is very easy to verify this result on the web.
Realization of proof of workload
Let us implement a similar PoW algorithm with the rule: find a number p such that the hash value of the string it is stitched together with the proof of the previous block begins with 4 zeros.
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 workload: - Find a p' feasible hash(pp') starts with 4 zeros - p is the proof of the previous block, p' It's current 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): """ verification certificate: is or isn'thash(last_proof, proof)in order to4classifier for individual things or people, general, catch-all classifier0beginning? :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"
A measure of the complexity of the algorithm is to modify the number of zeros at the beginning. Use 4 for demonstration purposes, and you'll see that each additional zero greatly increases the time it takes to compute the result.
Now that the Blockchain class is basically complete, the next step is to interact with it using HTTP requests.
Blockchain as an API interface
We'll be using the Python Flask framework, a lightweight web application framework that facilitates mapping web requests to Python functions, and now let's get Blockchain running on a Flask-based web.
We will create three interfaces:
- /transactions/new creates a transaction and adds it to the block
- /mine tells the server to mine a new block.
- /chain Returns the entire blockchain
Create a node
Our "Flask server" will act as a node in the blockchain network. Let's start by adding some framework 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() @('/mine', methods=['GET']) def mine(): return "We'll mine a new Block" @('/transactions/new', methods=['POST']) def new_transaction(): return "We'll add a new transaction" @('/chain', methods=['GET']) def full_chain(): response = { 'chain': , 'length': len(), } return jsonify(response), 200 if __name__ == '__main__': (host='0.0.0.0', port=5000)
A brief explanation of the above code:
Line 15: Create a node.
Line 18: Create a random name for the node.
Line 21: Instance Blockchain class.
Lines 24-26: create/mine GET interface.
Lines 28-30: Create the /transactions/new POST interface, which can be used to send transaction data to the interface.
Lines 32-38: create the /chain interface, which returns the entire blockchain.
Lines 40-41: The service is running on port 5000.
Send Transaction
The structure of the transaction data sent to the node is as follows:
{ "sender": "my address", "recipient": "someone else's address", "amount": 5 }
There are already methods for adding transactions, and it's easy to add transactions based on the interface
import hashlib import json from textwrap import dedent from time import time from uuid import uuid4 from flask import Flask, jsonify, request ... @('/transactions/new', methods=['POST']) def new_transaction(): values = request.get_json() # Chek 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
dig for coal or minerals
Mining is where the magic lies, it's simple and does a little three things:
- Compute proof of workload PoW
- Grant the miner (yourself) a coin by adding a new transaction
- Constructing new blocks and adding them to the chain
import hashlib import json from time import time from uuid import uuid4 from flask import Flask, jsonify, request ... @('/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) # Provide rewards to nodes for proof-of-work. # A sender of "0" indicates a newly mined 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
Note that the recipient of the transaction is our own server node, and most of the work we do is just interacting around the Blockchain class methods. At this point, our blockchain is complete, so let's run it in practice
Running the blockchain
You can use cURL or Postman to interact with the API.
Start server.
$ python * Runing on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Let's go through the process of requestinghttp://localhost:5000/mine to mine
Add a new transaction via post request
If you're not using Postman, the cURL statement in a bit is the same:
$ curl -X POST -H "Content-Type: application/json" -d '{
"sender": "d4ee26eee15148ee92c6cd394edd974e",
"recipient": "someone-other-address",
"amount": 5
}' http://localhost:5000/transactions/new
After mining twice, there are 3 blocks, by requesting thehttp://localhost:5000/chain You can get all the block information.
{ "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 }
Coherence (consensus)
We already have a basic blockchain that can accept transactions and mine. But blockchain systems are supposed to be distributed. Since it is distributed, what exactly do we take to ensure that all nodes have the same chain? This is the consistency problem, and we have to implement a consistent algorithm if we want to have multiple nodes on the network.
Registered Nodes
Before implementing the consistency algorithm, we need to find a way for a node to know its neighboring nodes. Each node needs to keep a record containing other nodes in the network. So let's add a couple of new interfaces:
- /nodes/register Receive a list of new nodes in the form of URLs.
- /nodes/resolve performs a consistency algorithm that resolves any conflicts and ensures that the nodes have the correct chains
Let's modify Blockchain's init function and provide a register node method:
... from import urlparse ... class Blockchain(object): def __init__(self): ... = 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) (parsed_url.netloc)
We use set to store nodes, which is an easy way to avoid adding nodes repeatedly.
Implementing consensus algorithms
As mentioned earlier, conflict is when different nodes have different chains, and to solve this problem, it is stipulated that the longest, valid chain is the final chain, in other words, the longest valid chain in the network is the actual chain.
We use the following algorithm to achieve consensus in the network
... 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'] != (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): """ Consensus Algorithms to Resolve Conflicts Use the longest chain in the network. :return: <bool> True if chain is replaced, False otherwise """ neighbours = new_chain = None # We're only looking for chains longer than ours max_length = len() # Grab and verify the chains from all the nodes in our network for node in neighbours: response = (f'http://{node}/chain') if response.status_code == 200: length = ()['length'] chain = ()['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: = new_chain return True return False
The first method, valid_chain(), is used to check if the chain is a valid chain, iterating through each block to verify the hash and proof.
The second method, resolve_conflicts(), is used to resolve conflicts by iterating over all neighboring nodes and checking the validity of the chain with the previous method, replacing its own chain if it finds a longer valid chain.
Let's add two routes, one for registering nodes and one for resolving conflicts.
@('/nodes/register', methods=['POST']) def register_nodes(): values = request.get_json() nodes = ('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(), } return jsonify(response), 201 @('/nodes/resolve', methods=['GET']) def consensus(): replaced = blockchain.resolve_conflicts() if replaced: response = { 'message': 'Our chain was replaced', 'new_chain': } else: response = { 'message': 'Our chain is authoritative', 'chain': } return jsonify(response), 200
You can run nodes on different machines, or open different network ports on a machine machine to simulate a multi-node network. Here, different ports are opened on the same machine for demonstration purposes, and two nodes are started by running a single command in a different terminal:http://localhost:5000 cap (a poem)http://localhost:5001
pipenv run python pipenv run python -p 5001
Then two blocks are mined on node 2 to make sure it's a longer chain, and then the interface /nodes/resolve is accessed on node 1 ,at which point node 1's chain is replaced by node 2's chain via a consensus algorithm.
summarize
The above is a small introduction to the use of Python to jack a blockchain from scratch, I hope to help you, if you have any questions please leave me a message, I will reply to you in time. I would also like to thank you very much for your support of my website!