毕设教程python区块链实现 - proof of work工作量证明共识算法
Posted DanCheng-studio
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了毕设教程python区块链实现 - proof of work工作量证明共识算法相关的知识,希望对你有一定的参考价值。
文章目录
0 前言
Hi,大家好,这里是丹成学长,今天向同学们介绍如何构建一个区块链系统作为毕设,区块链的原理
🧿 选题指导, 项目分享:
1 区块链基础
学长以比特币的结构向大家详解区块链的组成部分
1.1 比特币内部结构
- previous hash(前一个区块的hash)
- merkle root(默克尔树根节点,内部存储交易数据)
- timestamp(当前区块生成的时间)
- nonce(旷工计算hash值次数)
1.2 实现的区块链数据结构
- index 当前第几个区块
- timestamp 该区块创建时的时间戳
- data 交易信息
- previousHash 前一个区块的hash
- hash 当前区块的hash
1.3 注意点
第一个区块叫做创世区块(genesis block),区块链创建的时候默认生产的这里用的是单纯的链表,不是用默克尔树存储
示例代码
from hashlib import sha256
//区块schema
class Block:
def __init__(self,index,timestamp,data,previousHash=""):
self.index = index
self.timestamp = timestamp
self.data = data
self.previousHash = previousHash
self.hash = self.calculateHash()
//计算当前区块的hash值
def calculateHash(self):
plainData = str(self.index)+str(self.timestamp)+str(self.data)
return sha256(plainData.encode('utf-8')).hexdigest()
def __str__(self):
return str(self.__dict__)
//区块链schema
class BlockChain:
//初始化的时候 创建 创世区块
def __init__(self):
self.chain = [self.createGenesisBlock()]
//构建创世区块
def createGenesisBlock(self):
return Block(0,"01/01/2018","genesis block","0")
//获取最后一个区块
def getLatestBlock(self):
return self.chain[len(self.chain)-1]
//往区块链里面添加区块
def addBlock(self,newBlock):
newBlock.previousHash = self.getLatestBlock().hash
newBlock.hash = newBlock.calculateHash()
self.chain.append(newBlock)
def __str__(self):
return str(self.__dict__)
//校验区块链是不是有效的 有没有人被篡改
def chainIsValid(self):
for index in range(1,len(self.chain)):
currentBlock = self.chain[index]
previousBlock = self.chain[index-1]
if (currentBlock.hash != currentBlock.calculateHash()):
return False
if previousBlock.hash != currentBlock.previousHash:
return False
return True
myCoin = BlockChain()
myCoin.addBlock(Block(1,"02/01/2018","amount:4"))
myCoin.addBlock(Block(2,"03/01/2018","amount:5"))
#print block info 打印区块链信息
print("print block info ####:")
for block in myCoin.chain:
print(block)
#check blockchain is valid 检查区块链是不是有效的
print("before tamper block,blockchain is valid ###")
print(myCoin.chainIsValid())
#tamper the blockinfo 篡改区块2的数据
myCoin.chain[1].data = "amount:1002"
print("after tamper block,blockchain is valid ###")
print(myCoin.chainIsValid())
输出结果
print block info ####:
'index': 0, 'timestamp': '01/01/2018', 'data': 'genesis block', 'previousHash': '0', 'hash': 'd8d21e5ba33780d5eb77d09d3b407ceb8ade4e5545ef951de1997b209d91e264'
'index': 1, 'timestamp': '02/01/2018', 'data': 'amount:4', 'previousHash': 'd8d21e5ba33780d5eb77d09d3b407ceb8ade4e5545ef951de1997b209d91e264', 'hash': '15426e32db30f4b26aa719ba5e573f372f41e27e4728eb9e9ab0bea8eae63a9d'
'index': 2, 'timestamp': '03/01/2018', 'data': 'amount:5', 'previousHash': '15426e32db30f4b26aa719ba5e573f372f41e27e4728eb9e9ab0bea8eae63a9d', 'hash': '75119e897f21c769acee6e32abcefc5e88e250a1f35cc95946379436050ac2f0'
before tamper block,blockchain is valid ###
True
after tamper block,blockchain is valid ###
False
1.4 区块链的核心-工作量证明算法
上面学长介绍了区块链的基本结构,我在之前的基础上来简单实现一下工作量证明算法(proof of work),在介绍pow之前先思考一下为什么要工作量证明算法,或者再往前想一步为什么比特币如何解决信任的问题?
1.4.1 拜占庭将军问题
比特币出现之前就有了拜占庭将军问题,主要思想是,如何在分布式系统环境里去相信其他人发给你的信息?
一组拜占庭将军分别各率领一支军队共同围困一座城市。为了简化问题,将各支军队的行动策略限定为进攻或撤离两种。因为部分军队进攻部分军队撤离可能会造成灾难性后果,因此各位将军必须通过投票来达成一致策略,即所有军队一起进攻或所有军队一起撤离。因为各位将军分处城市不同方向,他们只能通过信使互相联系。在投票过程中每位将军都将自己投票给进攻还是撤退的信息通过信使分别通知其他所有将军,这样一来每位将军根据自己的投票和其他所有将军送来的信息就可以知道共同的投票结果而决定行动策略
系统的问题在于,将军中可能出现叛徒,他们不仅可能向较为糟糕的策略投票,还可能选择性地发送投票信息。假设有9位将军投票,其中1名叛徒。8名忠诚的将军中出现了4人投进攻,4人投撤离的情况。这时候叛徒可能故意给4名投进攻的将领送信表示投票进攻,而给4名投撤离的将领送信表示投撤离。这样一来在4名投进攻的将领看来,投票结果是5人投进攻,从而发起进攻;而在4名投撤离的将军看来则是5人投撤离。这样各支军队的一致协同就遭到了破坏。
1.4.2 解决办法
拜占庭将军问题主要问题是,中间人可以拦截消息,进行修改;上述的那些士兵可以理解成比特币中的一些节点,不是所有节点拿到消息后都是可以直接处理的,先去解决一个数学问题,就是工作量证明,只有拥有特定的计算能力解决了问题之后才能去修改或者校验(验证,打包,上链)。
上图就是简单的工作量证明算法流程,一串数字后面有个x,x之前的数可以理解成交易数据,然后需要找到一个x,让整个数的hash值的开头有n个0,如果hash是很均匀的话,那么生成的hash值每一位为0或者1都是等可能的,所以前n个都为0的概率就是2的n次方/2的hash值位数,上图给出了如果hash值是5个bit的情况下的所有可能
1.4.3 代码实现
from hashlib import sha256
import time
class Block:
def __init__(self,index,timestamp,data,previousHash=""):
self.index = index
self.timestamp = timestamp
self.data = data
self.previousHash = previousHash
self.nonce = 0 //代表当前计算了多少次hash计算
self.hash = self.calculateHash()
def calculateHash(self):
plainData = str(self.index)+str(self.timestamp)+str(self.data)+str(self.nonce)
return sha256(plainData.encode('utf-8')).hexdigest()
#挖矿 difficulty代表复杂度 表示前difficulty位都为0才算成功
def minerBlock(self,difficulty):
while(self.hash[0:difficulty]!=str(0).zfill(difficulty)):
self.nonce+=1
self.hash = self.calculateHash()
def __str__(self):
return str(self.__dict__)
class BlockChain:
def __init__(self):
self.chain = [self.createGenesisBlock()]
self.difficulty = 5
def createGenesisBlock(self):
return Block(0,"01/01/2018","genesis block")
def getLatestBlock(self):
return self.chain[len(self.chain)-1]
#添加区块前需要 做一道计算题😶,坐完后才能把区块加入到链上
def addBlock(self,newBlock):
newBlock.previousHash = self.getLatestBlock().hash
newBlock.minerBlock(self.difficulty)
self.chain.append(newBlock)
def __str__(self):
return str(self.__dict__)
def chainIsValid(self):
for index in range(1,len(self.chain)):
currentBlock = self.chain[index]
previousBlock = self.chain[index-1]
if (currentBlock.hash != currentBlock.calculateHash()):
return False
if previousBlock.hash != currentBlock.previousHash:
return False
return True
myCoin = BlockChain()
# 下面打印了每个区块挖掘需要的时间 比特币通过一定的机制控制在10分钟出一个块
# 其实就是根据当前网络算力 调整我们上面difficulty值的大小,如果你在
# 本地把上面代码difficulty的值调很大你可以看到很久都不会出计算结果
startMinerFirstBlockTime = time.time()
print("start to miner first block time :"+str(startMinerFirstBlockTime))
myCoin.addBlock(Block(1,"02/01/2018","amount:4"))
print("miner first block time completed" + ",used " +str(time.time()-startMinerFirstBlockTime) +"s")
startMinerSecondBlockTime = time.time()
print("start to miner first block time :"+str(startMinerSecondBlockTime))
myCoin.addBlock(Block(2,"03/01/2018","amount:5"))
print("miner second block time completed" + ",used " +str(time.time()-startMinerSecondBlockTime) +"s\\n")
#print block info
print("print block info ####:\\n")
for block in myCoin.chain:
print("\\n")
print(block)
#check blockchain is valid
print("before tamper block,blockchain is valid ###")
print(myCoin.chainIsValid())
#tamper the blockinfo
myCoin.chain[1].data = "amount:1002"
print("after tamper block,blockchain is valid ###")
print(myCoin.chainIsValid())
输出
2 快速实现一个区块链
2.1 什么是区块链
区块链是一个不可变得,有序的被称之为块的记录链,它们可以包含交易、文件或者任何你喜欢的数据,但最重要的是,它们用hash连接在一起。
2.2 一个完整的快包含什么
一个索引,一个时间戳,一个事物列表,一个校验, 一个前快的散链表
2.3 什么是挖矿
挖矿其实非常简单就做了以下三件事:
1、计算工作量证明poW
2、通过新增一个交易赋予矿工(自已)一个币
3、构造新区块并将其添加到链中
2.4 工作量证明算法:
使用该算法来证明是如何在区块上创建和挖掘新的区块,pow的目标是计算出一个符合特定条件的数字,这个数字对于所有人而言必须在计算上非常困难,但易于验证,这就是工作证明背后的核心思想计算难度与目标字符串需要满足的特定字符串成正比。
2.5 实现代码
import hashlib
import json
import requests
from textwrap import dedent
from time import time
from uuid import uuid4
from urllib.parse import urlparse
from flask import Flask, jsonify, request
class Blockchain(object):
def __init__(self):
...
self.nodes = set()
# 用 set 来储存节点,避免重复添加节点.
...
self.chain = []
self.current_transactions = []
#创建创世区块
self.new_block(previous_hash=1,proof=100)
def reister_node(self,address):
"""
在节点列表中添加一个新节点
:param address:
:return:
"""
prsed_url = urlparse(address)
self.nodes.add(prsed_url.netloc)
def valid_chain(self,chain):
"""
确定一个给定的区块链是否有效
:param chain:
:return:
"""
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")
# 检查block的散列是否正确
if block['previous_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 ressolve_conflicts(self):
"""
共识算法
:return:
"""
neighbours = self.nodes
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:
self.chain = new_chain
return True
return False
def new_block(self,proof,previous_hash=None):
"""
创建一个新的块并将其添加到链中
:param proof: 由工作证明算法生成证明
:param previous_hash: 前一个区块的hash值
:return: 新区块
"""
block =
'index':len(self.chain)+1,
'timestamp':time(),
'transactions':self.current_transactions,
'proof':proof,
'previous_hash':previous_hash or self.hash(self.chain[-1]),
# 重置当前交易记录
self.current_transactions = []
self.chain.append(block)
return block
def new_transaction(self,sender,recipient,amount):
# 将新事务添加到事务列表中
"""
Creates a new transaction to go into the next mined Block
:param sender:发送方的地址
:param recipient:收信人地址
:param amount:数量
:return:保存该事务的块的索引
"""
self.current_transactions.append(
'sender':sender,
'recipient':recipient,
'amount':amount,
)
return self.last_block['index'] + 1
@staticmethod
def hash(block):
"""
给一个区块生成 SHA-256 值
:param block:
:return:
"""
# 必须确保这个字典(区块)是经过排序的,否则将会得到不一致的散列
block_string = json.dumps(block,sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
@property
def last_block(self):
# 返回链中的最后一个块
return self.chain[-1]
def proof_of_work(self,last_proof):
# 工作算法的简单证明
proof = 0
while self.valid_proof(last_proof,proof)is False:
proof +=1
return proof
@staticmethod
def valid_proof(last_proof,proof):
# 验证证明
guess = f'last_proofproof'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] =="0000"
# 实例化节点
app = Flask(__name__)
# 为该节点生成一个全局惟一的地址
node_identifier = str(uuid4()).replace('-','')
# 实例化Blockchain类
blockchain = Blockchain()
# 进行挖矿请求
@app.route('/mine',methods=['GET'])
def mine():
# 运行工作算法的证明来获得下一个证明。
last_block = blockchain.last_block
last_proof = last_block['proof']
proof = blockchain.proof_of_work(last_proof)
# 必须得到一份寻找证据的奖赏。
blockchain.new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)
# 通过将其添加到链中来构建新的块
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(proof,previous_hash)
response =
'message': "New Block Forged",
难度的调整
是在每个完整节点中独立自动发生的。每2016个区块,所有节点都会按统的公式自动调整难度,这个公式是由最新2016个区块的花要时长与期望时长(期望时长为20160分钟,即两周,是按每10分钟一个区块的产生速率计算出的总时长 )比较得出的,根据实际时长与期望时长的比值, 进行相应调整(或变难或变易)。即如果区块产生的速率比10分钟快则增加难度,比10分钟慢则降低难度公式总结为:
■新难度值=当前难度值x ( 20160分钟/最近的2016个区块的实际出块时间)
■next difficulty = previous difficulty* (2 weeks) /(time to mine last 2016 blocks)
■注: 2016数字,既不是1024,也不是2048,也不是2100
不可能三角:
最多只能满足其中两项:
去中心化、安全和高性能构成了区块链的“不可能三角”,在设计中无法将三个特性同时达成,只能符合其中两个。
去中心化是拥有大量参与区块生产和验证的节点,一般节点的数量越多,去中心化程度越高。
安全性是获得网络控制权需要花费的成本,通常在共识机制的设计中锚定现实世界的资产,例如工作量证明机制(PoW)锚定的是算力。
性能就是每秒处理交易的笔数(TPS)。造成区块链性能低下的主要原因是每笔交易都要在所有节点上达成一致。
比特币的区块形成过程
在当前区块加入区块链后,所有矿工就立即开始下一个区块的生成工作:
1. 把在本地内存中的交易信息记录到区块主体中
2. 在区块主体中生成此区块中所有交易信息的Merkle树,把Merkle树根的值保存在区块头中
3. 把上一个刚刚生成的区块的区块头的数据通过SHA256算法生成一 个哈希值填入到当前区块的父哈希值中
4.把当前时间保存在时间截字段中(版上默时难随)
5. 难度值字段会根据之前一 段时间区块的平均生成时间进行调整以应对整个网络不断变化的整体计算总量,如果计算总量增长了,则系统会调高教学题的难度值 .使得预期完成下一个区块的时间依然在一定时间内
■当在挖矿过程中,发现别的节点新发布了 一个区块,那么应该停止挖矿 , 重新从UTXO中取出一系列合法交易组成候选区块,在刚发布的这个区块后面开始挖矿
UTXO:
UTXO(Unspent Transaction Outputs)是未花费的交易输出,它是比特币交易生成及验证的一个核心概念。交易构成了一组链式结构,所有合法的比特币交易都可以追溯到前向一个或多个交易的输出,这些链条的源头都是挖矿奖励,末尾则是当前未花费的交易输出。
■因为一方面这个区块中的交易可能和网刚在挖的那个区块有面复,另一个本质的原因就是修选区块的块头有指向前一个区块的哈希指针,因为最新的区块已经变了这个哈希指针也要跟着改变
基于此,提问:这样是不是之前的工作都白费了而很可惜?
答:不可惜,因为挖矿过程的无记忆性,无论是在刚刚的区块上继续挖,还是新组装一个区块继续挖,成功的概论是一样的。
比特币的区块体:
■这些交易以什么样的数据格式/数据结构存储呢?
■区块链中的Merkle树将会对海笔交易进行数字签名,可以保证每笔交易都不可伪造 ,且没有重贸交易。所有的交易将通过Markle树的Hash过程产生一 个唯的Merkle根值计入区块头
■Merkle根/Master Hash/Top Hash/RootHash
■Merklet将相邻的两个交易数据的两个哈希值组成一一个字符串,再对这个字符串进行哈希,得到上层父节点的哈希值
■这些数据放在 levelDB数据库中
Merkle树
■在一个Merkle树里, 数据块是两两组的,每个块的哈希值是储存在上一级节点的。而上一节点又两两一组,其哈希值储存在更上- -级的节点。这样一直持续到触及根节点
■特点:叶子节点上的值通常为交易数据块的哈希值,而非叶子节点上的值是该节点的所有子节点的组合结果的哈希值,这类非叶子节点的哈希被称为路径哈希值
■只需记住树开头的哈希指针,就有能力去管理列表中任意一点的哈希指针,能判断确保数据有没有被篡改过,因为如果有攻击者修改了树下面的一些数据块,那么这将导致高级的哈希指针不会再匹配。就算他继续修改更高级的块,但是数据的改变已经影响到了他无法修改的树的最顶端,因为我们已经保存好了顶端的数据。因此,任何试图修改任意数据的行为都将被检查到,而我们只需记住顶端的哈希指针
隶属证明(成员证明 Proof of membership)
■Merkle树的另外一个优点。允许简洁的成员证明
■即想要证明一个确切的数据块是否Merkle树中的一-员?
■通常,只记住了数根,之后需要向我们展示这个区块和通向数根沿途的所有数据块。就可以暂时忽略树的其他部分,沿途的数据块就已经足以让我们验证到数根
■如果在树上有n个节点,那么就只有log(0)个块需要被展示,因为每个步骤都只需要计算下一级块的哈希,所以这大概只需要log(n)次去证明它.即定位的时间复杂度为0log(m)
■所以即使这个Merkdle树包含了非常多的块依旧可以在个较短的时间内证明一个成员块
Merkle Proof默克尔证明
■对于 SPV(简化支付验证,Simplified Payment Verification )轻钱包而言,怎么知道一个交易是否真实的呢?
■SPV拿到一个交易信息之后(比如接收到一笔钱),并不能确认这个交易是否合法 ,因此要对这个交易的输入进行验证。但是它只拿到了单个交易的信息,而没有本地的完整区块链数据,因此,SPV要拿着这个交易的信息向网络发起查询请求,这个请求被称为merkle block message
■当其他有完整区块链数据的客户端收到这个请求之后,利用传过来的交易信息在自己的区块链数据库中进行查询,并把验证路径返回给请求源,SPV 拿到验证路径之后,再做-次merke校验.确认无误之后,就认为这个交易是可信的
区块链网络中,节点一般分为:
全节点
轻节点
SPV节点
SPV与轻节点
不同点:
SPV节点: 目的是验证一笔交易是否上链、是否得到多个确认。
轻节点:节点本地保存与其自身相关的交易数据,目的不仅仅是验证交易,还有管理节点自身的资产收入、支付等信息。
共同点:
SPV节点和轻节点都无需在本地保存全部数据