Python从零开始构建区块链之网络+共识(上)

上一篇:用Python从零开始构建区块链之数据层理解

(上)准备工作

前言

上篇文章用代码简单构造了区块链数据层各个数据类的实现,一个简陋的区块链基本实现。今天开始,在其基础上简单实现区块链的网络层和共识层。

本次系列文章将从实际代码出发,来加深你对区块链网络同步和区块共识的理解。

准备工作

由于用到了网络,我们需要借助一个网络框架flask实现网络节点,同时一致性共识的算法建立在挖矿的基础上,我们需要对挖矿的原理有所了解。

1.Flask网络框架

Flask是一个使用 Python 编写的轻量级 Web 应用框架。我们在这里使用flask框架模拟实现区块链网络中的各个节点,以实现节点同步和节点中的最长链选择。

打开Pycharm,新建一个flask项目。我们来初步了解一下,我们将怎么使用它来构造节点。

新建后,默认的flask项目代码是:

from flask import Flask

app = Flask(__name__)

@app.route('/')  #映射根目录
def hello_world():
    return 'Hello World!'  #返回网页信息

if __name__ == '__main__':
    app.run()

我们运行一下项目,将会在consle看到:

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

在浏览器输入网址:http://127.0.0.1:5000/:


Python从零开始构建区块链之网络+共识(上)_第1张图片
http://127.0.0.1:5000/

我们一看就明白了,flask通过app.route映射网页链接,然后返回网页内容。我们对上述代码稍作修改:

from flask import Flask

app = Flask(__name__)

from flask import Flask

app = Flask(__name__)

@app.route('/')  #映射根目录
def hello_world():
    return 'this is a chainNode!'  #返回网页信息

@app.route('/zhangsan')  
def node_zhangsan():
    return '张三的区块链节点'  

@app.route('/lisi')  
def node_lisi():
    return '李四的区块链节点'  

@app.route('/wangwu')  
def node_wangwu():
    return '王五的区块链节点'  

if __name__ == '__main__':
    app.run()

运行结果依然是:

* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

我们访问网址发现:


Python从零开始构建区块链之网络+共识(上)_第2张图片
节点根目录:http://127.0.0.1:5000/
Python从零开始构建区块链之网络+共识(上)_第3张图片
张三的节点:http://127.0.0.1:5000/zhangsan
Python从零开始构建区块链之网络+共识(上)_第4张图片
王五的节点: http://127.0.0.1:5000/wangwu

这样,我们就通过flask框架构造了网络中不同的网络节点:

http://127.0.0.1:5000/zhangsan    #张三的节点
http://127.0.0.1:5000/lisi        #李四的节点
http://127.0.0.1:5000/wangwu      #王五的节点
...

2.区块链中挖矿概念的简单理解

在区块链中,挖矿其实是一个数学问题。通俗地讲,就是在不断计算一个区块的哈希值,直到计算出的结果符合系统设定的条件,我们就说“成功地挖到一个区块”。

忽略其他因素,我们通过代码简单地模拟一下挖矿的过程:

#通过哈希来挖掘区块
from hashlib import sha256

if __name__ == '__main__':
    x = 11
    y = 0

    #f'{x*y}' 将x*y的结果转化为字符串 encode("utf-8") 默认utf-8,可以不写
    #当x*y的结果的前三位是=都是0的时候找到目标哈希值
    #hexdigest:哈希结果的二进制字符串
    while sha256(f'{x*y}'.encode("utf-8")).hexdigest()[-3:] != "000":
    # while sha256(f'{x*y}'.encode("utf-8")).hexdigest()[-5:] != "01010":
    # while sha256(f'{x*y}'.encode("utf-8")).hexdigest()[:8] != "00001111":
        y += 1  #不符合条件,计算次数统计加1
        print(y)

    print("y = {}".format(y))  #找到目标值时候的计算次数

我们运行程序,看一下结果如何:


Python从零开始构建区块链之网络+共识(上)_第5张图片
sha256(f'{x*y}'.encode("utf-8")).hexdigest()[-3:] == "000"
Python从零开始构建区块链之网络+共识(上)_第6张图片
sha256(f'{x*y}'.encode("utf-8")).hexdigest()[-5:] == "01010"
Python从零开始构建区块链之网络+共识(上)_第7张图片
sha256(f'{x*y}'.encode("utf-8")).hexdigest()[:8] == "00001111"

我们依次增大目标条件的难度值(根据改变匹配的位数),发现计算的次数越来越大。甚至第三种条件我的Mac本跑了五分钟都没有出结果。。。

 #计算了10383次得到目标值
sha256(f'{x*y}'.encode("utf-8")).hexdigest()[-3:] == "000"
 #计算了874020次得到目标值
sha256(f'{x*y}'.encode("utf-8")).hexdigest()[-5:] == "01010"
 #Mac本计算了5分钟,91517163次仍然没有得到目标值!!!这个好难哦...
sha256(f'{x*y}'.encode("utf-8")).hexdigest()[:8] == "00001111"

这就是区块链“挖矿”的一个基本原理,挖矿难度值也在于上述代码中的目标条件设定。因此,我们可以通过这个条件设置来更改挖矿难度。

3.区块链架构整合与代码完善

前面,为了更直观清楚地理解区块链数据层的结构,我们构造了交易类,交易记录类,区块类,区块链类等来分层分模块详细理解各项功能。

现在,我们将主要的区块链功能整合到一个区块链类里,然后加上必要的工作量证明,区块校验,节点同步,最长链一致性算法等核心代码。并且对之前的Python代码做一个规范化,以后我们谈到的区块链代码框架也一般用今天写的这个。

引入必要模块
import hashlib  #信息安全加密
import json  #网络数据传递格式
import datetime  #时间
from typing import Optional, Dict, Any, List  #必要数据类型
from urllib.parse import urlparse  #url编码解码
from uuid import uuid4  #签名
import requests  #网络请求
from flask import Flask, jsonify, request  #flask网络框架
构造方法

创建一个区块链类ChaorsCoinBlockChain,首先我们需要实现init方法,init方法创造一个区块链时需要有一个创世区块,需要包含一个区块列表和当前交易列表,同时一个区块链必须还有节点信息。

    def __init__(self):
        self.chain = []  #区块列表
        self.current_transactions = []  #交易列表
        self.nodes = set()  #结点
        self.new_block(proof=100, prev_hash=1)  #创建创世区块
新增区块

上篇文章我们已经知道,每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明以及前一个区块的Hash值。

block = {
    'index': 1,
    'timestamp': 1506057125.900785,
    'transactions': [
        ...
    ],
    'proof': 324984774000,
    'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}

因此,新增一个区块需要以上各项信息

   #新增区块
    def new_block(self,
                  proof:int,  #指定工作量类型
                  prev_hash:Optional[str]  #默认是字符串
                  )->Dict[str, Any]:  #指定返回值类型为字典

        block = {
            "index":len(self.chain) + 1,  #新增区块,原有区块索引加1
            "timestamp":datetime.datetime.now(),  #时间戳
            "transactions":self.current_transactions.clear(),  #交易
            "proof":proof,  #工作量证明
            "prev_hash":prev_hash or self.hash(self.chain[-1]) #上个区块的哈希
        }

        self.current_transactions = []  #开辟新的区块,即交易记录加入区块,当前交易需要被清空!!!
        self.chain.append(block)  #将区块追加到区块链表中

        return block
新增交易

一个交易信息包括发起者,接受者和交易数额。

#交易的数据结构
[
  {
            'sender': "8527147fe1f5426f9dd545de4b27ee00",
            'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
            'amount': 5,
   }
   ...
]
    #新增交易
    def new_transaction(self, sender:str, recipient:str, amount:int)->int:
        #生成交易信息
        self.current_transactions.append({
            "sender":sender,  #付款方
            "recipient":recipient,  #收款方
            "amount":amount  #交易数额
        })

        return self.last_block["index"] + 1  #索引标记交易的数量

求哈希值

接下来,我们需要定义一个静态方法来求区块的哈希值。

@staticmethod
    def hash(block:Dict[str, Any])->str:  #传入一个字典类型,返回一个字符串
        #对模块进行哈希处理 json.dumps:将区块处理为字符串
        block_str = json.dumps(block, sort_keys=True).encode("utf-8")

        return hashlib.sha256(block_str).hexdigest()  #返回哈希值
上个区块

我们还需要一个属性用来访问区块链上一个区块

@property
    def last_block(self)->Dict[str, Any]:

        return self.chain[-1]
工作量证明

这里就需要用到,我们上面讲到的挖矿原理(回顾请看上述准备工作2)。由于区块链挖矿需要依赖于上一个区块,所以这里需要传入上个区块作为参数。

    #挖矿依赖于上一个模块
    def proof_of_work(self, last_block)->int:  #挖矿获取工作量证明
        last_proof = last_block["proof"]  #取出算力证明
        last_hash = self.hash(last_block)

        proof = 0  #循环求解符合条件的合法哈希
        #valid_proof:用于验证工作量证明是否符合条件
        while self.valid_proof(last_proof, proof) is False:
            proof += 1

        return proof
工作量证明验证

这个方法用于验证矿工的工作量证明是否满足系统条件,挖矿的难度也是在这里设置的。

    @staticmethod
    def valid_proof(last_proof:int, proof:int)->bool:  #验证工作量证明
        guess = f'{last_proof}{proof}'.encode("utf-8")
        guess_hash = hashlib.sha256(guess).hexdigest()

        return guess_hash[:6] == "000000"  #计算难度
        # return guess_hash[-5:] == "24689"  #这里的条件更改可以改变工作量证明计算的难度(即挖矿难度)
节点注册

区块链网络中的各个节点需要加入到区块链中,才能完成全网的共识同步。

   def register_node(self, addr:str)->None:  #加入网络中的其他节点,用于更新
        parsed_url = urlparse(addr)  #url解析
        if parsed_url.netloc:  #可以连接网络
            self.nodes.add(parsed_url.netloc)  # 增加网络节点
        elif parsed_url.path:
            self.nodes.add(parsed_url.path)
        else:
            raise ValueError("url无效")
区块链合法验证

区块链合法性校验需要满足两个条件:1.区块哈希合法性;2.区块工作量证明合法性。二者缺一不可,必须同时满足才能说明该区块合法。而链上所有区块都合法,才能说明区块链合法。

    def valid_chain(self, chain:List[Dict[str, Any]])->bool:  #区块链校验
        last_block = chain[0]  #从第一块开始校验
        current_index = 1

        while current_index < len(chain):  #循环校验
            block = chain[current_index]
            if block["prev_hash"] != self.hash(last_block):  #区块校哈希验
                return False

            if not self.valid_proof(last_block["proof"],
                                    block["proof"]):  #工作量校验
                return False

            last_block = block
            current_index += 1

        return True
一致性共识算法

我们知道,有时候由于有不同矿工同时“挖矿”成功,区块链会出现短暂分叉。但是一段时间后,区块链会重新成为一条单独的主链,这个过程依赖于共识算法,而共识又必要依赖于节点同步,这里简单地实现下通过节点同步帮助主链选择最长链的方法。

    def resolve_conflicts(self)->bool:  #冲突,一致性算法的一种
        #取得互联网中最长的链来替换当前的链
        neighbours = self.nodes  #备份节点  eg:127.0.0.1是一个节点,另一个不同的节点192.168.1.
        new_chain = None  #更新后的最长区块链
        max_length = len(self.chain)  #先保存当前节点的长度

        for node in neighbours:  #刷新每个网络节点,获取最长跟新
            response = requests.get(f'http://{node}/chain')  #取得其他节点的区块链
            if response.status_code == 200:  #网络请求成功
                length = response.json()["length"]  #取得邻节点区块链长度
                chain = response.json()["chain"]    #取得邻节点区块链

                #如果找到区块链长度大于当前节点区块链长度并且区块链校验合法,刷新并保存最长区块链
                if length > max_length and self.valid_chain(chain):
                    max_length = length
                    new_chain = chain


        if new_chain:  #判断是否更新成功
            return True
        else:
            return  False

这样,我们就简单搭建了一个简陋但是功能还比较齐全的区块链架构。

网络共识实现代码的准备工作到此就告一段落,由于实现网络共识涉及的内容比较多,这一篇暂且讲到这。下一篇文章开始正式解析网络与共识的具体实现与调试。

下一篇:用Python从零开始构建区块链之网络+共识(下)

互联网颠覆世界,区块链颠覆互联网!

---------------------------------------------------------20180406晨

你可能感兴趣的:(Python从零开始构建区块链之网络+共识(上))