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

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

上篇文章,我们做好了实现节点同步的准备工作。这次,用flask框架实现区块链的多节点,通过共识实现节点之间的数据同步。

引入区块链类

首先,新建一个Python文件ChaorsCoinBlockNode,引入上次的区块链类ChaorsCoinBlockChain。在这生成一个网络节点和节点的钱包地址。

#节点的数据更新和网络公示

from uuid import uuid4  #签名
import requests  #网络请求
from flask import Flask, jsonify, request  #flask网络框架

from ChaorsCoinBlockChain import ChaorsCoinBlockChain

chaorsCoin = ChaorsCoinBlockChain()  #创建一个网络节点
node_id = str(uuid4()).replace("-", "")  #生成节点秘钥,即钱包地址
print("当前节点钱包地址:", node_id)
初始化flask框架

前面已经基本了解了flask映射网页的基本使用,这里的主页打印一个信息,用来表示该网络运行正常。

app = Flask(__name__)  #初始化flask框架
@app.route("/")
def index_page():
    return "welcome to ChaorsCoin..."
查看区块链

新建一个网页用于查看当前节点区块链,构造一个response(其实就是一个字典,因为网络上传输数据的格式是Json)来显示区块链长度和所有区块

@app.route("/chain")  #查看所有区块链
def index_chain():
    response = {
        "chain":chaorsCoin.chain,  #区块链
        "length":len(chaorsCoin.chain)  #区块链长度
    }
    return jsonify(response), 200
挖矿

新区快的挖掘依赖于上一个合法区块,每当产生一个区块的时候,系统会产生一笔奖励。每一个区块的第一笔交易都是作为系统奖励矿工的一个交易,叫CoinBaseTransaction。
同样,我们将想要显示的block信息封装到一个json字典里,显示在网页。

@app.route("/mine")  #挖矿
def index_mine():
    last_block = chaorsCoin.last_block
    proof = chaorsCoin.proof_of_work(last_block)

    #系统奖励比特币
    chaorsCoin.new_transaction(
        sender="0",  #0代表系统奖励,即coinBaseTransaction
        recipient=node_id,
        amount=12.5
    )

    block = chaorsCoin.new_block(proof, chaorsCoin.hash(last_block))  #新增区块

    response = {
        "message":"new block created...",
        "index":block["index"],
        "transactions":block["transactions"],
        "proof":block["proof"],
        "hash":chaorsCoin.hash(block),
        "prev_hash":block["prev_hash"]
    }
    return jsonify(response), 200

每调用一次这个页面,就会产生一个新区快加入到当前区块链里。所以要注意,我们这里测试设置的工作量难度不要太大,不然等好久才会生成一个新新区块。

   @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[:3] == "000"  #计算难度,这里的计算难度不要设置的太高。不然等好久才会生成一个新区块
        # return guess_hash[-5:] == "24689"
创建交易

交易的创建需要传入参数,所以这里使用POST请求。

@app.route("/new_transcations", methods=["POST"])  # 创建一个新的交易
def index_new_transcations():
    values = request.get_json()  #抓取网络传输的信息
    required = ["sender", "recipient", "amount"]

    #判断提交的json数据key值是否合法
    if not all(key in values for key in required):
        return "数据不完整或格式错误", 400

    index = chaorsCoin.new_transaction(values["sender"],
                                       values["recipient"],
                                       values["amount"])  #新增交易

    response = {
        "message":f"交易加入到区块{index}"
    }

    return jsonify(response), 200
节点新增

在区块链上,每一个用户都是一个独立的节点。当全网数据发生改变,每一个节点都需要去连接周围的节点以同步最新数据。所以,这里需要将周围的节点增加到当前区块链的节点集合中。

@app.route("/new_node", methods=["POST"])  #新增节点
def index_new_node():
    values = request.get_json()
    nodes = values.get("nodes")  #获取所有节点

    if nodes is None:
        return "怎么是空节点"

    for node in nodes:
        chaorsCoin.register_node(node)

    response = {
        "message": "网络节点加入到区块",
        "nodes":list(chaorsCoin.nodes)
    }

    return jsonify(response), 200

我们这里只是通过flask网络请求,模拟区块链上的节点同步原理。至于真实的节点同步可能是很复杂的,我们只是为了更好地理解其原理。

节点更新

可能全网不同节点的区块链是不同的,张三的区块链Height可能是100,李四的可能是105,那么王五请求全网同步数据时到底该选择谁的区块链呢?我们都知道会选择长度较长且区块链合法的链作为最长链。

@app.route("/node_refresh")  #刷新节点
def index_node_refresh():
    replaced = chaorsCoin.resolve_conflicts()  #一致性算法进行最长链选择

    print(replaced)

    if replaced:
        response = {
        "message": "区块链被替换为最长有效链",
        "new chain": chaorsCoin.chain
        }
    else:
        response = {
            "message": "当前区块链为最长无需替换",
            "chain": chaorsCoin.chain
        }
    return jsonify(response), 200
一致性算法回顾

当区块中的节点进行数据同步时,当前节点会取得周围节点的一个列表。循环访问各节点保存的区块链,如果其他区块链高度大于自身区块链并且区块链验证合法,则将其当做最长链进行替换。通俗简单地讲,原理就跟一堆数组求最大值没啥区别。只是可能具体的实现细节有点不同且复杂,这里不属于今天的讨论范畴,我们只讲原理。

    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")  #访问网络节点
            print(response.status_code)
            if response.status_code == 200:
                length = response.json()["length"]  #取得邻节点长度
                chain = response.json()["chain"]    #取得邻节点区块链
                # print(max_length, length, self.valid_chain(chain))

                #刷新并保存最长区块链
                if length > max_length and self.valid_chain(chain):
                    max_length = length
                    new_chain = chain

        if new_chain:  #判断是否更新成功
            self.chain = new_chain  # 替换区块链
            return True

        return False

这样我们就用flask框架构建了一个具备主要功能的区块链节点,接下来运行并测试一下。

if __name__ == '__main__':
   app.run("127.0.0.1", port=5005)  #当提示address被占用的时候,更改一下port即可

单节点网络测试

程序运行结果:

当前节点钱包地址: 2a35a1b48e0843269ffc5d7cd6b684e1
 * Running on http://127.0.0.1:5005/ (Press CTRL+C to quit)

打开consle输出的网址,试着添加不同的后缀访问不同的功能。

ChaorsCoin主页

访问区块链页面,发现现在只有创世区块:

当前节点区块链

我们访问挖矿界面,就会产生一个新的区块。

挖矿产生新区快

我们多访问几次挖矿界面,然后再回到chain页面发现新挖的区块都追加的区块链里了。

多次挖矿后的chain页面

我们发现每次的区块里只有区块奖励的coinBaseTransaction,为什么呢?因为我们并没有产生其他的交易。接下来,模拟产生一个交易。这时候需要用到http工具来提交POST请求。

打开PostMan,按交易需要的格式提交一个POST请求:

提交POST请求

添加交易的请求提交后,我们看到提示将该笔交易加入到了区块5。我们知道,交易都是先被打包到区块内,然后当一个区块被挖出后区块里的交易才会生效。也就是,挖出第五区块后才能在区块链里看到这笔交易信息。

所以,这里需要再一次访问挖矿页面。然后在看区块界面:

第五区块被挖到
加入交易后再次挖矿成功后的区块链

多节点同步测试

单节点区块链的访问,挖矿,新增交易等功能没问题后接下来我们就可以测试多节点间的数据同步。

新建两个文件ChaorsCoinBlockNode_1和ChaorsCoinBlockNode_2,复制ChaorsCoinBlockNode的代码到这两个文件。修改节点地址,使得它们是三个不同的节点。

#ChaorsCoinBlockNode
app.run("127.0.0.1", port=5005)
#ChaorsCoinBlockNode_1
app.run("127.0.0.1", port=5006)
#ChaorsCoinBlockNode_2
app.run("127.0.0.1", port=5007)

分别运行三个节点,通过人为模拟节点数据的不同。我们通过访问各自的挖矿页面,使得他们的区块链长度不同。假设他们共同拥有的区块是相同的。

此时,三个节点的区块链为:

节点5005
节点5006
节点5007

我们以节点5007为例,节点5007向网络发起同步请求。这时,他需要知道自己周围的节点。因此,我们通过POST请求将5005,5006加入到5007的节点列表中。

#节点5005  区块长度4
#节点5006  区块长度8
#节点5007  区块长度3

#预计同步后的数据应该以节点5006为准
添加节点请求

这个时候,节点5007就可以同步区块链数据了。访问node_refresh页面,发现更新后的区块链果然为三个节点中的最长链:

节点更新数据

当然这个时候,再访问节点5007的chain页面,区块链数据也已经更新到最新区块链。

这样,我们就通过代码粗陋地实现了一个简单区块链的网络层和共识层。同时,也加深聊聊对区块链数据结构和网络同步原理的理解。

本文的flask框架并不复杂,关键的还是理解区块链整体的架构代码的实现(ChaorsCoinBlockChain)。



作者:chaors
链接:https://www.jianshu.com/p/1e247faf8a15
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页