搭建基于PoW共识算法的区块链网络

一.练习目标:

构建包含A、B、C、D、E五个节点在内的区块链网络,在区块链网络中基于Pow算法共识机制实现A-E五个节点记账。编写相应的HTTP接口实现对记账信息的查询和区块记录的查询。

二. 任务内容

(1)构建包括区块(Block)、区块链(Blockchain)、网络(Network)、节点(Peer)模型

(2)实现PoW算法及目标值转换功能

(3)使用Flask-APScheduler启动A-E五个节点实现以“间隔”的方式进行PoW共识记账。
 (4)基于Flask服务端架构,实现包括节点和区块数据查询接山和区块数据查询接口

三.具体实现
 1.创建项目的实体模型

创建consensus_app项目,在项目中创建models.py文件模块,在其中创建区块(Block).区块链(Blockchain)、网络(Netvork)、模型(peer)。具体代码如下:

(1).创建区块(Block)模型
import hashlib
from datetime import datetime
import networkx as nx
import matplotlib.pyplot as plt
#1.定义区块难度
DIFFICULT_BITS = 0x1e11ffff

class Block(object):
    def __init__(self,index,prev_hash,data,timestamp,bits):
        """
        区块的初始化方法,在创建一个区块需传入包括索引号等相关信息
        :param index: 区块索引号
        :param prev_hash: 前一区块的哈希值
        :param data: 区块中需保存的记录
        :param timestamp: 区块生成的时间戳
        :param bits: 区块需传入的比特值(预留)
        """
        self.index = index
        self.prev_hash = prev_hash
        self.data = data
        self.timestamp = timestamp
        self.difficult_bits= bits
        self.nonce = 0
        # 计算新区块的默克根
        self.merkle_root = self.calc_merkle_root()
        # 计算区块的哈希值
        self.block_hash = self.calc_block_hash()

    def to_json(self):
        """
        将区块内容以JSON的形式输出
        :return:
        """
        return {
            "index": self.index,
            "prev_hash": self.prev_hash,
            "merkle_root": self.merkle_root,
            "data": self.data,
            "timestamp": self.timestamp.strftime('%Y/%m/%d %H:%M:%S'),
            'bits': hex(self.difficult_bits)[2:].rjust(8, "0"),
            'nonce': hex(self.nonce)[2:].rjust(8, "0"),
            'block_hash': self.block_hash
        }

    def calc_merkle_root(self):
        """
        计算默克树的根(Merkle Root)
        :return:
        """
        calc_txs = [tx for tx in self.data]
        if len(calc_txs) == 1:
            return calc_txs[0]
        while len(calc_txs) > 1:
            if len(calc_txs) % 2 == 1:
                calc_txs.append(calc_txs[-1])
            sub_hash_roots = []
            for i in range(0, len(calc_txs), 2):
                join_str = "".join(calc_txs[i:i+2])
                sub_hash_roots.append(hashlib.sha256(join_str.encode()).hexdigest())
            calc_txs = sub_hash_roots
        return calc_txs[0]
    def calc_block_hash(self):
        """
        生成区块对应的哈希值
        :return:
        """
        blockheader = str(self.index) + str(self.prev_hash) \
                      + str(self.data) + str(self.timestamp) + \
                      hex(self.difficult_bits)[2:] + str(self.nonce)
        h = hashlib.sha256(blockheader.encode()).hexdigest()
        self.block_hash = h
        return h

需要注意的是,数据参数data中保存的区块数据不再使用交易,而是仅使用简单字符串数据,降低代码复杂度。另外,在区块中加入了“随机数"nonce和“区块难度"ificlt bits,分别为:

1. nonce:主要用于实现PoW算法:

2. dficult _bits: 不由外界传入,由上一区块参数传入。 

(2).创建区块链(Blockchain)模型
from datetime import datetime
#1.定义区块难度
DIFFICULT_BITS = 0x1e11ffff
#区块链对象
class Blockchain(object):
    def __init__(self):
        self.chain = []
        #用于记录不同节点按index顺序获取区块记账权的信息
        self.peer_block = {}
        self.create_genesis_block()

    def add_block(self, block):
        self.chain.append(block)

    def add_peer_block(self,name,block_index):
        """
        向peer_block中添加peer->block_index记录
        :param name:节点名称
        :param block_index区块索引值
        """

        if name in self.peer_block:
            self.peer_block[name].append(block_index)
        else:
            self.peer_block[name]= [block_index]
    
    
    def query_peer_block(self,name):
        """
        查询peer_block内容
        :param name:节点名称
        """
        return self.peer_block[name]
    
    def query_block_info(self, index=0):
        """
        通过索引值查询区块链chain中的区块信息
        """
        int_index = int(index)
        block_json = self.chain[int_index].to_json()
        return block_json

    def create_genesis_block(self):
        """
        创建创世区块,在这里定义区块难度,这将被之后所有的区块引用
        """
        genesis_block = Block(0,
                              "0" * 64,
                              ["hello world"],
                              datetime.now(),
                              DIFFICULT_BITS)
        self.add_block(genesis_block)

需要注意的是,在create_genesis_block 函数实现了初始区块(genesis_block)的创建,在其中加入了DIFFICULT_BITS表示区块难度,这将被之后新创建的区块沿用。 

(3).创建节点 
# 模拟的节点
class Peer:
    def __init__(self,name):
        self.name = name
        self.last_block = 1

其中last_block参数勇于表示记录当前节点记录的区块链最新区块索引值

(4).创建网络(Network)模型
#区块链网络
class Network(object):
    def __init__(self, name):
        """
        初始化区块链网络
        :paeam name:
        """
        self.peers = []  #网络存在节点
        self.name = name    #网络名称
        self.G = nx.Graph()   #网络中的定义的network网络拓扑

    def add_peer(self, peer):
        """
        在网络中新增节点
        """
        self.peers.append(peer)
        self.G.add_node(peer.name)

    def add_edge(self, s_peer, e_peer):
        """
        在网络中新增节点间的边
        """
        e = (s_peer, e_peer)
        self.G.add_edge(*e)

    def del_peer(self, peer_name):
        """
        删除指定名称的peer节点
        """
        for i, peer in enumerate(self.peers):
            if peer_name == peer.name:
                del self.peers[i]
        self.G.remove_node(peer_name)

    def draw_network(self):
        """
        绘制网络
        """
        pos = nx.spring_layout(self.G, iterations=100)
        nx.draw(self.G, pos, with_labels=True)
        plt.show()
 2.创建项目实体

在consensus _app 项目中创建entity.py文件模块,在其中加入peer0 至peer4节点的实体以及区块链blockchain的实体内容

import models

# 用于创建节点和网络实体
peer0 = models.Peer('peer0')
peer1 = models.Peer('peer1')
peer2 = models.Peer('peer2')
peer3 = models.Peer('peer3')
peer4 = models.Peer('peer4')
peer_list = [peer0, peer1, peer2, peer3, peer4]

#创建区块链
blockchain = models.Blockchain()
3.创建Pow算法实现模块

在consensus_app项目中创建pow.py文件模块,在其中加入目标值生成函数和pow算法函数

扫描二维码关注公众号,回复: 17327745 查看本文章
#3.定义目标值的生成函数
def generate_target(difficult_bits):
    """
    基于区块难度生成对应的目标值
    :param difficult_bits:区块难度
    :return:
    """
    #取区块难度(16进制)的前2位作为指数
    exponent = int(difficult_bits / 16 ** 6)
    #取区块难度(16进制)的前6位作为系数
    coefficient = int(difficult_bits % 16 ** 6)
    print(f'exponent is {hex(exponent)}')
    print(f'coefficient is {hex(coefficient)}')

    #按照共识计算目标值
    target = coefficient * 2 ** (8 * (exponent - 0x03))
    print(f'target is {target}')
    #将目标值转换成16进制表示的值
    target_hex = hex(target)[2:].zfill(64)
    print(f'target_hex is {target_hex}')
    return target

#4.定义pow算法
def pow_alg(block):
    #获取指定区块的难度
    difficult_bits = block.difficult_bits
    #生成目标值
    target = generate_target(difficult_bits)

    #通过循环的方式反复生成区块头的哈希值并与目标值比较
    #设置计算次数为2的32次方
    #每次循环将区块中的“随机值”累加1,并生成新的哈希值与目标值比较
    for n in range(2 ** 32):
        block.nonce = block.nonce + n
        block.calc_block_hash()
        # print(f'block_hash hex is {hex(int(block.block_hash, 16))}')
        if int(block.block_hash, 16) < target:
            print(f'{"*" * 20}完成计算!{"*" * 20}')
            print(f'总共计算了:{block.nonce}次')
            print(f'target hex值为{hex(target)[2:].zfill(64)}')
            print(f'区块哈希值为:{hex(int(block.block_hash, 16))[2:].zfill(64)}')
            return block
        
 4.生成services处理模块

在consensus_app项目中创建services.py 文件模块,在其中加入加入包括创建网络(generate_network)和PoW算法的执行函数(exe_ pow)

import models
import entity
import pow
from datetime import datetime


def generate_network(network_name, peer_list):
    """
    生成区块链网络,初始化Network实体,以及在Network实体中加入节点(node)和边(node)
    :param network_name:网络名称
    :param peer_list:已生成的节点列表
    :return:生成的区块链网络实体对象
    """
    g_network = models.Network(network_name)
    for index, peer in enumerate(peer_list):
        g_network.add_peer(peer)
        if index == len(peer_list)-1:
            #如果是最后一个节点,那就让最后一个节点和第一个节点首位相连
            g_network.add_edge(peer.name, peer_list[0].name)
        else:
            #否则,建立本节点与下一个节点的边
            g_network.add_edge(peer.name, peer_list[index+1].name)
    return g_network

def exe_pow(data,peer_name):
    """
    供节点循环执行pow算法
    :param data:区块数据
    ;param peer_name:节点名称
    """

    #1.首先获得区块链中最新区块
    last_block = entity.blockchain.chain[-1]
    #2.获取last_block区块的索引值
    index = last_block.index + 1
    #3.生成新的区块
    g_block = models.Block(last_block.index + 1, last_block.prev_hash,data,datetime.now(),last_block.difficult_bits)
    #4.将区块扔进pow算法中进行计算,在得到区块头后返回区块信息
    c_block = pow.pow_alg(g_block)
    #判断区块链内的内容,如果当前区块链中没有新数据产生,则将产生的区块加入区块链中。
    if len (entity.blockchain.chain) <= index:
        entity.blockchain.add_block(c_block)
        entity.blockchain.add_peer_block(peer_name,index)
    else:
        #如果计算的区块索引已存在,则说明其他节点已抢先计算完成,则计算失败不能将结果保存至区块链
        print(f'区块索引<{index}>,已存在!{peer_name}节点计算失败')
    
5. 创建程序执行模块

在consensus_app 项目中创建app.py文件执行模块,在其中定义区块链网络(network),创建Flask执行进程app以及定时任务执行scheduler,并设置定时执行间隔为20秒。在模块中加入HTTP相应接口。

接口名 接口描述 输入参数 返回值
peer_block _query 获取所有节点及其记录区块的索引值
键值对形式。所有节
键值对形式。所有节
block_query 查询指定区块索引值(高度) id: 区块索引值 区块详细内容
from flask import Flask, request
from flask_apscheduler import APScheduler
import services
from datetime import datetime
import entity

app = Flask(__name__)

network = services.generate_network('test',entity.peer_list)
#创建定时任务
scheduler = APScheduler()
class Config(object):
    SCHEDULER_API_ENABLED = True
app.config.from_object(Config())
scheduler.init_app(app)

#设置统一的间隔执行任务时间
interval_seconds = 20

#创建节点0(peer0),进行pow计算任务
@scheduler.task('interval', 
                id='peer0-calc', 
                seconds=interval_seconds, 
                misfire_grace_time=900)
def send_message():
    #设置统一的区块数据
    data = f'{entity.peer0.name}-{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} '
    services.exe_pow(data=data,peer_name=entity.peer0.name)

#创建节点1(peer1),进行pow计算任务
@scheduler.task('interval', 
                id='peer1-calc', 
                seconds=interval_seconds, 
                misfire_grace_time=900)

def send_message():
    data = f'{entity.peer1.name}-{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} '
    services.exe_pow(data=data,peer_name=entity.peer1.name)

#创建节点2(peer2),进行pow计算任务
@scheduler.task('interval', 
                id='peer2-calc', 
                seconds=interval_seconds, 
                misfire_grace_time=900)

def send_message():
    data = f'{entity.peer2.name}-{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} '
    services.exe_pow(data=data,peer_name=entity.peer2.name)

#创建节点3(peer4),进行pow计算任务
@scheduler.task('interval', 
                id='peer3-calc', 
                seconds=interval_seconds, 
                misfire_grace_time=900)

def send_message():
    data = f'{entity.peer3.name}-{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} '
    services.exe_pow(data=data,peer_name=entity.peer3.name)

#创建节点4(peer4),进行pow计算任务
@scheduler.task('interval', 
                id='peer2-calc', 
                seconds=interval_seconds, 
                misfire_grace_time=900)

def send_message():
    data = f'{entity.peer4.name}-{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} '
    services.exe_pow(data=data,peer_name=entity.peer4.name)

@app.route('/peer_block_query',methods = ['GET'])
def peer_block_query():
    """
    获取所有节点及记录区块的索引值
    """
    return entity.blockchain.peer_block

@app.route('/block_query',methods = ['GET'])
def block_query():
    """
    查询指定区块索引值(高度)
    """

    index = request.args['id']
    return entity.blockchain.query_block_info(index)

if __name__ == '__main__':
    scheduler.start()
    app.run()
6.控制台输出结果
 7.验证项目启动正确性

使用Postman以GET的方式访问http://127.0.0.1:5000/peer_block _query,将会获取节点与区块记录区块的信息

在区块链中以“键值对”的形式存储了节点与区块索引的信息,以此方式就可以判定不同区块的记录节点。
接着可以通过索引的方式查询不同区块的详细内容,使用Postman 以GET的形式访问http://127.0.0.1:5000/block _query?id=3 可以访问区块索引值(高度)为3的区块内容 

猜你喜欢

转载自blog.csdn.net/2201_76041915/article/details/134606184