Create a blockchain from scratch with Python

The author believes that the fastest way to learn blockchain is to create one yourself. This article follows the author to create a blockchain with Python.

We are new to the rise of digital currency and want to know how the technology behind it, the blockchain, is realized.

But fully understanding the blockchain is not easy, I like to learn by doing, and learning the technology by writing code will make it more solid. The understanding of blockchain can be deepened by building a blockchain.

Preparation This article requires readers to have a basic understanding of Python, be able to read and write basic Python, and need a basic understanding of HTTP requests.

We know that the blockchain is an immutable, ordered chain structure composed of records of blocks, records can be transactions, files or any data you want, the important thing is that they are linked by hashes (hashes) of.

If you are not familiar with hashing, you can check this article

Environment preparation Environment preparation, make sure that Python3.6+, pip , Flask, requests installation method have been installed:

 
   
pip install Flask==0.12.2 requests==2.18.4

Also requires an HTTP client, such as Postman, cURL or others.

Refer to the source code (the original code could not run when I translated it, I forked a copy, fixed the errors, and added the translation, thanks to star)

Start creating Blockchain and create a new file blockchain.py. All the code in this article is written in this file, you can refer to the source code at any time

The Blockchain class first creates a Blockchain class and creates two lists in the constructor, one for storing blockchains and one for storing transactions.

Here is the skeleton of the Blockchain 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

The Blockchain class is used to manage the chain, it can store transactions, add new blocks, etc. Let's further improve these methods.

Block Structure Each block contains attributes: index, Unix timestamp, transaction list (transactions), proof of work (explained later), and the hash value of the previous block.

The following is the structure of a block:

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

At this point, the concept of blockchain is clear, each new block contains the Hash of the previous block, which is the key point, it guarantees the immutability of the blockchain. want to

If you want to learn, you can add QUN491308659, code: Cauchy, code: Cauchy If an attacker destroys a block in front, the hash of all the blocks in the back will become incorrect. If you don't understand, digest it slowly, you can refer to the blockchain bookkeeping source

Join the transaction Next we need to add a transaction to improve the new_transaction method

 
   
class Blockchain(object):
...
def new_transaction(self, sender, recipient, amount):
"""
生成新交易信息,信息将加入到下一个待挖的区块中 :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

The method adds a transaction record to the list and returns the index of the block to which the record will be added (the next block to be mined), which will be useful later when the user submits the transaction.

Creating a new block When the Blockchain is instantiated, we need to construct a genesis block (the first block without the previous block) and add a proof-of-work to it. Each block needs to go through proof-of-work, commonly known as mining, which will be explained later.

In order to construct the genesis block, we also need to improve the newblock(), newtransaction() and hash() methods:

 
   
import hashlibimport jsonfrom time import timeclass 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):
"""
生成新块 :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):
"""
生成新交易信息,信息将加入到下一个待挖的区块中 :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):
"""
生成块的 SHA-256 hash值 :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()

Through the above code and comments, we can have an intuitive understanding of the blockchain. Next, let's see how the blocks are mined.

Understanding Proof of Work New blocks rely on the Proof of Work algorithm (PoW) to construct. The goal of PoW is to find a number that meets certain conditions, which is difficult to calculate but easy to verify. This is the core idea of ​​Proof of Work.

For ease of understanding, here is an example:

Suppose the hash value of the product of an integer x times another integer y must end in 0, ie hash(x * y) = ac23dc…0. Let the variable x = 5, find the value of y?

Implemented in Python as follows:

 
   
from hashlib import sha256
x = 5y = 0 # y未知while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":
y += 1print(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 to calculate results for the right to create blocks. Usually, the calculation difficulty is proportional to the number of specific characters that the target string needs to satisfy, and miners are rewarded with bitcoins after calculating the result. Of course, it is very easy to verify this result on the web.

Implementing Proof of Work Let's implement a similar PoW algorithm. The rule is: find a number p such that the hash value of the string concatenated with the proof of the previous block starts with 4 zeros.

 
   
import hashlibimport jsonfrom time import timefrom uuid import uuid4class Blockchain(object):
...
def proof_of_work(self, last_proof):
"""
简单的工作量证明:
- 查找一个 p' 使得 hash(pp') 以4个0开头 - p 是上一个块的证明, p' 是当前的证明 :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):
"""
验证证明: 是否hash(last_proof, proof)以4个0开头?
: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"

The way to measure the complexity of the algorithm is to modify the number of leading zeros. Use 4 for demonstration purposes, and you'll find that one more zero greatly increases the time it takes to compute the result.

Now that the Blockchain class is basically completed, the next step is to use HTTP requests to interact.

Blockchain as the API interface we will use the Python Flask framework, which is a lightweight web application framework that facilitates mapping network requests to Python functions, now let's make Blockchain run on the 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 new blocks

  • /chain returns the entire blockchain

Creating Nodes Our "Flask server" will act as a node in the blockchain network. Let's add some framework code first:

 
   
import hashlibimport jsonfrom textwrap import dedentfrom time import timefrom uuid import uuid4from flask import Flaskclass Blockchain(object): ...# Instantiate our Nodeapp = Flask(__name__)# Generate a globally unique address for this nodenode_identifier = str(uuid4()).replace('-', '')# Instantiate the Blockchainblockchain = 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), 200if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

Briefly explain the above code: Line 15: Create a node. Line 18: Create a random name for the node. Line 21: Instance the Blockchain class. Lines 24–26: Create the /mine GET interface. Lines 28–30: Create the /transactions/new POST interface, which can 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.

The transaction data structure sent to the node is as follows:

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

There has been a method for adding transactions before, and it is very simple to add transactions based on the interface

 
   
import hashlibimport jsonfrom textwrap import dedentfrom time import timefrom uuid import uuid4from flask import Flaskclass 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), 200if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

Mining is the magic, it is very simple, it does three things:

  1. Computational proof of work PoW

  2. Grant the miner (themselves) a coin by adding a transaction

  3. Construct a new block and add it to the chain

 
   
import hashlibimport jsonfrom textwrap import dedentfrom time import timefrom uuid import uuid4from flask import Flask, jsonify, [email protected]('/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

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, let's actually run it

To run the blockchain you can use cURL or Postman to interact with the API

Start the server:

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

Let's mine by requesting http://localhost:5000/mine

Request mining with Postman

Add a new transaction via post request

Request mining with Postman

If you are not using Postman, the same cURL statement is used:

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

After digging twice, there are 3 blocks. You can get all the block information by requesting http://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}

Consistency (Consensus) We already have a basic blockchain that accepts transactions and mining. But blockchain systems should be distributed. Since it is distributed, what do we do to ensure that all nodes have the same chain? This is the consistency problem. If we want to have multiple nodes on the network, we must implement a consistent algorithm.

Registering Nodes Before implementing the consensus algorithm, we need to find a way for a node to know its neighbors. Each node needs to keep a record of other nodes in the network. So let's add a few more interfaces:

  1. /nodes/register receives a list of new nodes as a URL

  2. /nodes/resolve executes the consensus algorithm, resolves any conflicts, and ensures that the nodes have the correct chain. We modify the Blockchain init function and provide a method to register 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)

We use a set to store nodes, which is an easy way to avoid adding nodes repeatedly.

Implementing the Consensus Algorithm As mentioned earlier, conflict means that different nodes have different chains. In order to solve this problem, it is stipulated that the longest and valid chain is the final chain. In other words, the longest valid chain in the network is the actual chain. chain.

We use the following algorithm to achieve consensus in the network

 
   
...import requestsclass 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):
"""
共识算法解决冲突
使用网络中最长的链.
:return: <bool> True 如果链被取代, 否则为False """
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 used to check whether it is a valid chain, traversing each block to verify the hash and proof.

第2个方法 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

你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:http://localhost:5000 和 http://localhost:5001想要学习的可以加QUN491308659,编码:柯西,编码:柯西

 
   
pipenv run python blockchain.pypipenv run python blockchain.py -p 5001

注册新节点

然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问接口/nodes/resolve ,这时节点1的链会通过共识算法被节点2的链取代。

共识算法解决冲突

好啦,你可以邀请朋友们一起来测试你的区块链



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326027671&siteId=291194637