上一篇:用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/:
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)
我们访问网址发现:
节点根目录:http://127.0.0.1:5000/ 张三的节点:http://127.0.0.1:5000/zhangsan 王五的节点: 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)) #找到目标值时候的计算次数
我们运行程序,看一下结果如何:
sha256(f'{x*y}'.encode("utf-8")).hexdigest()[-3:] == "000" sha256(f'{x*y}'.encode("utf-8")).hexdigest()[-5:] == "01010" 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晨
网友评论