区块链技术核心原理实现——Tiny熊老师区块链课堂的笔记
3-1 python 实现区块链环境准备
1、HTTP 超文本传输协议——Postman/curl
2、Python——PyCharm
3、工具:pip、pipenv、Flask/requests
3-2 建立项目,确定区块结构
cmd中:
C:\Users\Tai Park>D:
D:\>mkdir lesson1
D:\>cd lesson1
D:\lesson1>pip install pipenv
D:\lesson1>where python
D:\新建文件夹\python.exe
D:\lesson1>pipenv --python=D:\新建文件夹\python.exe
Creating a virtualenv for this project...
Pipfile: D:\lesson1\Pipfile
Using D:\新建文件夹\python.exe (3.7.0) to create virtualenv...
D:\lesson1>pipenv install requests==2.18.4
D:\lesson1>pipenv install flask==0.12.2
3-3 实现区块类结构-添加交易
整理区块链所需要的信息:
# {
# "index":0, #块的索引
# "timestamp":"", #时间戳
# "transactions":[ #交易信息
# {
# "sender":"", #交易的发送者
# "recipient":"", #交易的接收者
# "amount":5, #交易的金额
# }
# ],
# "proof":"", #工作量证明
# "previous_hash":"", #上一个区块的哈希值
#
# }
PyCharm:blockchain.py
大致函数定义:
class Blockchain:
def __init__(self):
self.chain = [] #一个元素对应一个块
self.current_transactions = [] #保存当前的交易信息,一个元素对应一个交易实体
def new_block(self): #新的区块
pass
def new_transaction(self,sender,recipient,amount)->int: #新的交易(发送者,接受者,金额)
#每个交易信息都是一个json,把它添加到交易信息的最后
self.current_transactions.append(
{
'sender':sender,
'recipient':recipient,
'amount':amount
}
)
#返回索引:原索引加一
return self.last_block['index'] + 1
@staticmethod
def hash(block): #静态方法,计算区块的哈希值
pass
@property
def last_block(self): #属性,获取到区块链最后一个区块
pass
3-4 实现创建区块
注:在win的PyCharm中,alt+Enter 可以实现快速import
# {
# "index":0, #块的索引
# "timestamp":"", #时间戳
# "transactions":[ #交易信息
# {
# "sender":"", #交易的发送者
# "recipient":"", #交易的接收者
# "amount":5, #交易的金额
# }
# ],
# "proof":"", #工作量证明
# "previous_hash":"", #上一个区块的哈希值
#
# }
import hashlib
import json
from time import time
class Blockchain:
#构造函数
def __init__(self):
# 一个元素对应一个块
self.chain = []
# 保存当前的交易信息,一个元素对应一个交易实体
self.current_transactions = []
# 创建创世区块,不用计算,没有内容
self.new_block(proof = 100,previous_hash = 1)
# 新的区块
def new_block(self,proof,previous_hash = None):
#构造json对象
block = {
'index':len(self.chain) + 1,
'timestamp':time(),
'transcations':self.current_transactions,
'proof':proof,
# 可以是传过来的hash值或者是调用hash()计算出来的hash值
'previous_hash':previous_hash or self.hash(self.last_block)
}
#把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了
self.current_transactions = []
#把生成的区块加入到链条中
self.chain.append(block)
return block
# 新的交易(发送者,接受者,金额)
def new_transaction(self,sender,recipient,amount)->int:
#每个交易信息都是一个json,把它添加到交易信息的最后
self.current_transactions.append(
{
'sender':sender,
'recipient':recipient,
'amount':amount
}
)
#返回索引:原索引加一
return self.last_block['index'] + 1
# 静态方法,计算区块的哈希值
@staticmethod
def hash(block):
#block是json对象,先把json转化为string并对字符排序并编码
block_string = json.dumps(block,sort_keys = True).encode()
#传入字符串编码后的字节数组,返回hash的摘要信息
return hashlib.sha256(block_string).hexdigest()
# 属性,获取到区块链最后一个区块
@property
def last_block(self):
#-1表示是数组最后一个元素
return self.chain(-1)
3-5 实现工作量证明
# {
# "index":0, #块的索引
# "timestamp":"", #时间戳
# "transactions":[ #交易信息
# {
# "sender":"", #交易的发送者
# "recipient":"", #交易的接收者
# "amount":5, #交易的金额
# }
# ],
# "proof":"", #工作量证明
# "previous_hash":"", #上一个区块的哈希值
#
# }
import hashlib
import json
from time import time
class Blockchain:
#构造函数
def __init__(self):
# 一个元素对应一个块
self.chain = []
# 保存当前的交易信息,一个元素对应一个交易实体
self.current_transactions = []
# 创建创世区块,不用计算,没有内容
self.new_block(proof = 100,previous_hash = 1)
# 新的区块
def new_block(self,proof,previous_hash = None):
#构造json对象
block = {
'index':len(self.chain) + 1,
'timestamp':time(),
'transcations':self.current_transactions,
'proof':proof,
# 可以是传过来的hash值或者是调用hash()计算出来的hash值
'previous_hash':previous_hash or self.hash(self.last_block)
}
#把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了
self.current_transactions = []
#把生成的区块加入到链条中
self.chain.append(block)
return block
# 新的交易(发送者,接受者,金额)
def new_transaction(self,sender,recipient,amount)->int:
#每个交易信息都是一个json,把它添加到交易信息的最后
self.current_transactions.append(
{
'sender':sender,
'recipient':recipient,
'amount':amount
}
)
#返回索引:原索引加一
return self.last_block['index'] + 1
# 静态方法,计算区块的哈希值
@staticmethod
def hash(block):
#block是json对象,先把json转化为string并对字符排序并编码
block_string = json.dumps(block,sort_keys = True).encode()
#传入字符串编码后的字节数组,返回hash的摘要信息
return hashlib.sha256(block_string).hexdigest()
# 属性,获取到区块链最后一个区块
@property
def last_block(self):
#-1表示是数组最后一个元素
return self.chain[-1]
#工作量证明
def proof_of_work(self,last_proof:int)->int:
proof = 0
#不停尝试proof的值,验证proof是否满足条件
while self.valid_proof(last_proof,proof) is False:
proof += 1
print(proof)
return proof
#验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件
def valid_proof(self,last_proof:int,proof:int)->bool:
#先把两个值转化成一个字符串并编码
guess = f'{last_proof}{proof}'.encode()
#用同样的方法拿到hash摘要
guess_hash = hashlib.sha256(guess).hexdigest()
print(guess_hash)
#如果满足前面四位是以0开头则返回True
return guess_hash[0:4] == '0000'
#验证工作量证明
testPow = Blockchain()
testPow.proof_of_work(100)
3-6 添加节点通信功能
Flask官网:http://flask.pocoo.org/
#初始化Flask类
app = Flask(__name__)
#启动Flask,提供运行入口
if __name__ == '__main__':
app.run(host = '0.0.0.0',port = 5000)
服务器已经运行了但是没有任何接受URL的接口
添加测试路由:
#测试路由
@app.route('/index',methods = ['GET'])
def index():
return 'Hello BlockChain'
打开Postman,输入本机地址 http://127.0.0.1:5000/index:
定义一个接受请求的API:
#定义一个接受请求的API
@app.route('/transactions/new',methods = ['POST'])
def new_transaction():
return "We'll add a new transaction"
此时用Postman会报405错误是因为没有将其改成POST:
继续添加:
#定义一个用来挖矿(交易打包)的API:
@app.route('/mine',methods = ['GET'])
def mine():
return "We'll mine a new block"
#定义返回整个区块链信息的路由:
@app.route('/chain',methods = ['GET'])
def full_chain():
return "return full chain"
目前为止的代码:
# {
# "index":0, #块的索引
# "timestamp":"", #时间戳
# "transactions":[ #交易信息
# {
# "sender":"", #交易的发送者
# "recipient":"", #交易的接收者
# "amount":5, #交易的金额
# }
# ],
# "proof":"", #工作量证明
# "previous_hash":"", #上一个区块的哈希值
#
# }
import hashlib
import json
from time import time
from flask import Flask
class Blockchain:
#构造函数
def __init__(self):
# 一个元素对应一个块
self.chain = []
# 保存当前的交易信息,一个元素对应一个交易实体
self.current_transactions = []
# 创建创世区块,不用计算,没有内容
self.new_block(proof = 100,previous_hash = 1)
# 新的区块
def new_block(self,proof,previous_hash = None):
#构造json对象
block = {
'index':len(self.chain) + 1,
'timestamp':time(),
'transcations':self.current_transactions,
'proof':proof,
# 可以是传过来的hash值或者是调用hash()计算出来的hash值
'previous_hash':previous_hash or self.hash(self.last_block)
}
#把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了
self.current_transactions = []
#把生成的区块加入到链条中
self.chain.append(block)
return block
# 新的交易(发送者,接受者,金额)
def new_transaction(self,sender,recipient,amount)->int:
#每个交易信息都是一个json,把它添加到交易信息的最后
self.current_transactions.append(
{
'sender':sender,
'recipient':recipient,
'amount':amount
}
)
#返回索引:原索引加一
return self.last_block['index'] + 1
# 静态方法,计算区块的哈希值
@staticmethod
def hash(block):
#block是json对象,先把json转化为string并对字符排序并编码
block_string = json.dumps(block,sort_keys = True).encode()
#传入字符串编码后的字节数组,返回hash的摘要信息
return hashlib.sha256(block_string).hexdigest()
# 属性,获取到区块链最后一个区块
@property
def last_block(self):
#-1表示是数组最后一个元素
return self.chain[-1]
#工作量证明
def proof_of_work(self,last_proof:int)->int:
proof = 0
#不停尝试proof的值,验证proof是否满足条件
while self.valid_proof(last_proof,proof) is False:
proof += 1
print(proof)
return proof
#验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件
def valid_proof(self,last_proof:int,proof:int)->bool:
#先把两个值转化成一个字符串并编码
guess = f'{last_proof}{proof}'.encode()
#用同样的方法拿到hash摘要
guess_hash = hashlib.sha256(guess).hexdigest()
print(guess_hash)
#如果满足前面四位是以0开头则返回True
return guess_hash[0:4] == '0000'
#初始化Flask类
app = Flask(__name__)
#定义一个接受请求的API
@app.route('/transactions/new',methods = ['POST'])
def new_transaction():
return "We'll add a new transaction"
#定义一个用来挖矿(交易打包)的API:
@app.route('/mine',methods = ['GET'])
def mine():
return "We'll mine a new block"
#定义返回整个区块链信息的路由:
@app.route('/chain',methods = ['GET'])
def full_chain():
return "return full chain"
#启动Flask,提供运行入口
if __name__ == '__main__':
app.run(host = '0.0.0.0',port = 5000)
3-7 交易接口实现
实例化一个blockchain:
blockchain = Blockchain()
实现chain接口:
#定义返回整个区块链信息的路由
@app.route('/chain',methods = ['GET'])
def full_chain():
response = {
#块的信息
'chain':blockchain.chain,
#链条数组的长度
'length':len(blockchain.chain)
}
#将dict类型转换为json串
return jsonify(response),200
在Postman中查看:
实现添加交易接口:
@app.route('/transactions/new',methods = ['POST'])
def new_transaction():
#拿到客户POST过来的内容
values = request.get_json()
#如果没有values的情况
if values is None:
return 'Missing values',400
#对三个字段进行检查,看请求时是否带上了这三个参数
required = ['sender','recipient','amount']
#如果k中内容有一个不在required中的话返回错误,返回400
if not all(k in values for k in required):
return 'Missing values',400
#如果满足格式则新建一个交易,把交易添加到当前的数组,返回的是其即将所在的区块索引
index = blockchain.new_transaction(values['sender'],
values['recipient'],
values['amount'])
#把新添加的交易的信息返回给用户
response = {'message':f'Transaction will be added to block:{index}'}
#通常post请求去添加一条记录都是返回201
return jsonify(response),201
Postman中:
3-8 挖矿接口实现
#利用uuid随机生成节点的ID,替换掉横杠
node_identifier = str(uuid4()).replace('-','')
#定义一个用来挖矿(交易打包)的API
@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)
#用proof新建一个块,传None的话会自己计算上一个区块的hash值
block = blockchain.new_block(proof,None)
#把包好的信息返回给用户
response = {
"message":"New Block Forged",
"index":block['index'],
"transactions":block['transcations'],
"proof":block['proof'],
"previous_hash":block['previous_hash']
}
return jsonify(response),200
Postman中先Post再Get
到目前为止的代码:
# {
# "index":0, #块的索引
# "timestamp":"", #时间戳
# "transactions":[ #交易信息
# {
# "sender":"", #交易的发送者
# "recipient":"", #交易的接收者
# "amount":5, #交易的金额
# }
# ],
# "proof":"", #工作量证明
# "previous_hash":"", #上一个区块的哈希值
#
# }
import hashlib
import json
from uuid import uuid4
from time import time
from flask import Flask, jsonify, request
class Blockchain:
#构造函数
def __init__(self):
# 一个元素对应一个块
self.chain = []
# 保存当前的交易信息,一个元素对应一个交易实体
self.current_transactions = []
# 创建创世区块,不用计算,没有内容
self.new_block(proof = 100,previous_hash = 1)
# 新的区块
def new_block(self,proof,previous_hash = None):
#构造json对象
block = {
'index':len(self.chain) + 1,
'timestamp':time(),
'transcations':self.current_transactions,
'proof':proof,
# 可以是传过来的hash值或者是调用hash()计算出来的hash值
'previous_hash':previous_hash or self.hash(self.last_block)
}
#把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了
self.current_transactions = []
#把生成的区块加入到链条中
self.chain.append(block)
return block
# 新的交易(发送者,接受者,金额)
def new_transaction(self,sender,recipient,amount)->int:
#每个交易信息都是一个json,把它添加到交易信息的最后
self.current_transactions.append(
{
'sender':sender,
'recipient':recipient,
'amount':amount
}
)
#返回索引:原索引加一
return self.last_block['index'] + 1
# 静态方法,计算区块的哈希值
@staticmethod
def hash(block):
#block是json对象,先把json转化为string并对字符排序并编码
block_string = json.dumps(block,sort_keys = True).encode()
#传入字符串编码后的字节数组,返回hash的摘要信息
return hashlib.sha256(block_string).hexdigest()
# 属性,获取到区块链最后一个区块
@property
def last_block(self):
#-1表示是数组最后一个元素
return self.chain[-1]
#工作量证明
def proof_of_work(self,last_proof:int)->int:
proof = 0
#不停尝试proof的值,验证proof是否满足条件
while self.valid_proof(last_proof,proof) is False:
proof += 1
print(proof)
return proof
#验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件
def valid_proof(self,last_proof:int,proof:int)->bool:
#先把两个值转化成一个字符串并编码
guess = f'{last_proof}{proof}'.encode()
#用同样的方法拿到hash摘要
guess_hash = hashlib.sha256(guess).hexdigest()
print(guess_hash)
#如果满足前面四位是以0开头则返回True
return guess_hash[0:4] == '0000'
#初始化Flask类
app = Flask(__name__)
#实例化blockchain
blockchain = Blockchain()
#利用uuid随机生成节点的ID,替换掉横杠
node_identifier = str(uuid4()).replace('-','')
#定义一个新添加交易的路由,用POST发送到服务器
@app.route('/transactions/new',methods = ['POST'])
def new_transaction():
#拿到客户POST过来的内容
values = request.get_json()
#如果没有values的情况
if values is None:
return 'Missing values',400
#对三个字段进行检查,看请求时是否带上了这三个参数
required = ['sender','recipient','amount']
#如果k中内容有一个不在required中的话返回错误,返回400
if not all(k in values for k in required):
return 'Missing values',400
#如果满足格式则新建一个交易,把交易添加到当前的数组,返回的是其即将所在的区块索引
index = blockchain.new_transaction(values['sender'],
values['recipient'],
values['amount'])
#把新添加的交易的信息返回给用户
response = {'message':f'Transaction will be added to block:{index}'}
#通常post请求去添加一条记录都是返回201
return jsonify(response),201
#定义一个用来挖矿(交易打包)的API
@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)
#用proof新建一个块,传None的话会自己计算上一个区块的hash值
block = blockchain.new_block(proof,None)
#把包好的信息返回给用户
response = {
"message":"New Block Forged",
"index":block['index'],
"transactions":block['transcations'],
"proof":block['proof'],
"previous_hash":block['previous_hash']
}
return jsonify(response),200
#定义返回整个区块链信息的路由
@app.route('/chain',methods = ['GET'])
def full_chain():
response = {
#块的信息
'chain':blockchain.chain,
#链条数组的长度
'length':len(blockchain.chain)
}
#将dict类型转换为json串
return jsonify(response),200
#启动Flask,提供运行入口
if __name__ == '__main__':
app.run(host = '0.0.0.0',port = 5000)
3-9 实现注册节点
#注册节点
def register_node(self,address:str):
#地址格式:http://127.0.0.1:5001
#通过urlparse解析地址
parsed_url = urlparse(address)
#将地址中的netloc部分提取出来
self.nodes.add(parsed_url.netloc)
#节点注册路由
#{"nodes":["http://127.0.0.2:5000"]}
@app.route('/nodes/register',methods = ['POST'])
def register_nodes():
#接受传过来的数据
values = request.get_json()
#接受节点信息
nodes = values.get("nodes")
#信息为空判断
if nodes is None:
return "Error:please supply a valid list of node",400
#多node注册
for node in nodes:
blockchain.register_node(node)
#信息返回给用户
response = {
"message":"New nodes have been added",
#原来定义的是个set集合,这里转化成list
"total_node":list(blockchain.nodes)
}
return jsonify(response),201
Postman:
到目前为止的代码:
# {
# "index":0, #块的索引
# "timestamp":"", #时间戳
# "transactions":[ #交易信息
# {
# "sender":"", #交易的发送者
# "recipient":"", #交易的接收者
# "amount":5, #交易的金额
# }
# ],
# "proof":"", #工作量证明
# "previous_hash":"", #上一个区块的哈希值
#
# }
import hashlib
import json
from uuid import uuid4
from time import time
from flask import Flask, jsonify, request
from urllib.parse import urlparse
class Blockchain:
#构造函数
def __init__(self):
# 一个元素对应一个块
self.chain = []
# 保存当前的交易信息,一个元素对应一个交易实体
self.current_transactions = []
# 保存节点信息,set()中每个值都是独一无二的
self.nodes = set()
# 创建创世区块,不用计算,没有内容
self.new_block(proof = 100,previous_hash = 1)
#注册节点
def register_node(self,address:str):
#地址格式:http://127.0.0.1:5001
#通过urlparse解析地址
parsed_url = urlparse(address)
#将地址中的netloc部分提取出来
self.nodes.add(parsed_url.netloc)
# 新的区块
def new_block(self,proof,previous_hash = None):
#构造json对象
block = {
'index':len(self.chain) + 1,
'timestamp':time(),
'transcations':self.current_transactions,
'proof':proof,
# 可以是传过来的hash值或者是调用hash()计算出来的hash值
'previous_hash':previous_hash or self.hash(self.last_block)
}
#把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了
self.current_transactions = []
#把生成的区块加入到链条中
self.chain.append(block)
return block
# 新的交易(发送者,接受者,金额)
def new_transaction(self,sender,recipient,amount)->int:
#每个交易信息都是一个json,把它添加到交易信息的最后
self.current_transactions.append(
{
'sender':sender,
'recipient':recipient,
'amount':amount
}
)
#返回索引:原索引加一
return self.last_block['index'] + 1
# 静态方法,计算区块的哈希值
@staticmethod
def hash(block):
#block是json对象,先把json转化为string并对字符排序并编码
block_string = json.dumps(block,sort_keys = True).encode()
#传入字符串编码后的字节数组,返回hash的摘要信息
return hashlib.sha256(block_string).hexdigest()
# 属性,获取到区块链最后一个区块
@property
def last_block(self):
#-1表示是数组最后一个元素
return self.chain[-1]
#工作量证明
def proof_of_work(self,last_proof:int)->int:
proof = 0
#不停尝试proof的值,验证proof是否满足条件
while self.valid_proof(last_proof,proof) is False:
proof += 1
print(proof)
return proof
#验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件
def valid_proof(self,last_proof:int,proof:int)->bool:
#先把两个值转化成一个字符串并编码
guess = f'{last_proof}{proof}'.encode()
#用同样的方法拿到hash摘要
guess_hash = hashlib.sha256(guess).hexdigest()
print(guess_hash)
#如果满足前面四位是以0开头则返回True
return guess_hash[0:4] == '0000'
#初始化Flask类
app = Flask(__name__)
#实例化blockchain
blockchain = Blockchain()
#利用uuid随机生成节点的ID,替换掉横杠
node_identifier = str(uuid4()).replace('-','')
#定义一个新添加交易的路由,用POST发送到服务器
@app.route('/transactions/new',methods = ['POST'])
def new_transaction():
#拿到客户POST过来的内容
values = request.get_json()
#如果没有values的情况
if values is None:
return 'Missing values',400
#对三个字段进行检查,看请求时是否带上了这三个参数
required = ['sender','recipient','amount']
#如果k中内容有一个不在required中的话返回错误,返回400
if not all(k in values for k in required):
return 'Missing values',400
#如果满足格式则新建一个交易,把交易添加到当前的数组,返回的是其即将所在的区块索引
index = blockchain.new_transaction(values['sender'],
values['recipient'],
values['amount'])
#把新添加的交易的信息返回给用户
response = {'message':f'Transaction will be added to block:{index}'}
#通常post请求去添加一条记录都是返回201
return jsonify(response),201
#定义一个用来挖矿(交易打包)的API
@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)
#用proof新建一个块,传None的话会自己计算上一个区块的hash值
block = blockchain.new_block(proof,None)
#把包好的信息返回给用户
response = {
"message":"New Block Forged",
"index":block['index'],
"transactions":block['transcations'],
"proof":block['proof'],
"previous_hash":block['previous_hash']
}
return jsonify(response),200
#定义返回整个区块链信息的路由
@app.route('/chain',methods = ['GET'])
def full_chain():
response = {
#块的信息
'chain':blockchain.chain,
#链条数组的长度
'length':len(blockchain.chain)
}
#将dict类型转换为json串
return jsonify(response),200
#节点注册路由
#{"nodes":["http://127.0.0.2:5000"]}
@app.route('/nodes/register',methods = ['POST'])
def register_nodes():
#接受传过来的数据
values = request.get_json()
#接受节点信息
nodes = values.get("nodes")
#信息为空判断
if nodes is None:
return "Error:please supply a valid list of node",400
#多node注册
for node in nodes:
blockchain.register_node(node)
#信息返回给用户
response = {
"message":"New nodes have been added",
#原来定义的是个set集合,这里转化成list
"total_node":list(blockchain.nodes)
}
return jsonify(response),201
#启动Flask,提供运行入口
if __name__ == '__main__':
app.run(host = '0.0.0.0',port = 5000)
3-10 实现共识机制-1
#验证hash值,看是否是有效链
def valid_chain(self,chain)->bool:
#取首块
last_block = chain[0]
#当前索引(第一个块索引是0不用计算,则从第二个块——索引是1的开始计算)
current_index = 1
#遍历这个链
while current_index block = chain[current_index] #如果当前块的前一个哈希值属性值不等于我们计算出来的上一个块的哈希值,说明链虚假,验证不通过 if block['previous_hash'] != self.hash(last_block): return False #工作量证明可能不满足规定(这里是四个0开头) 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 #自身链表长度 max_length = len(self.chain) #暂存链条 new_chain = None #遍历邻居的数据,用最长的链条取代该链 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 #如果new_chain存在的话,则说明它是新的最长链 if new_chain: self.chain = new_chain return True return False 3-11 实现共识机制-2 #可以调用解决冲突的路由 @app.route('/nodes/resolve',methods = ['GET']) def consensus(): #调用函数并查看链条是否被取代了 replaced = blockchain.resolve_conflicts() #如果被取代了要告诉一下用户 if replaced: response = { "message":"Our chain was replaced", "new_chain":blockchain.chain } else: response = { "message": "Our chain is authoritative", "chain":blockchain.chain } return jsonify(response),200 Postman测试: 为了使每次运行可以选择不同的端口,修改之前的代码: #启动Flask,提供运行入口 if __name__ == '__main__': #每次运行可以跑在不同的端口上,不是默认一个,而是通过参数传过来的 #初始化一个parser用来解析命令行参数 parser = ArgumentParser() #加上端口命令,举例:-p 5001 或者--port 5001 parser.add_argument('-p','--port',default = 5000,type = int,help = 'port to listen to') #对其解析 args = parser.parse_args() port = args.port app.run(host = '0.0.0.0',port = port) 在终端运行测试更改利用-p指令选择了5001端口: (venv) D:\lesson1>python blockchain.py -p 5001 * Serving Flask app "blockchain" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:5001/ (Press CTRL+C to quit) Postman: 最终代码: # { # "index":0, #块的索引 # "timestamp":"", #时间戳 # "transactions":[ #交易信息 # { # "sender":"", #交易的发送者 # "recipient":"", #交易的接收者 # "amount":5, #交易的金额 # } # ], # "proof":"", #工作量证明 # "previous_hash":"", #上一个区块的哈希值 # # } import hashlib import json import requests from uuid import uuid4 from time import time from flask import Flask, jsonify, request from urllib.parse import urlparse from argparse import ArgumentParser class Blockchain: #构造函数 def __init__(self): # 一个元素对应一个块 self.chain = [] # 保存当前的交易信息,一个元素对应一个交易实体 self.current_transactions = [] # 保存节点信息,set()中每个值都是独一无二的 self.nodes = set() # 创建创世区块,不用计算,没有内容 self.new_block(proof = 100,previous_hash = 1) #注册节点 def register_node(self,address:str): #地址格式:http://127.0.0.1:5001 #通过urlparse解析地址 parsed_url = urlparse(address) #将地址中的netloc部分提取出来 self.nodes.add(parsed_url.netloc) #验证hash值,看是否是有效链 def valid_chain(self,chain)->bool: #取首块 last_block = chain[0] #当前索引(第一个块索引是0不用计算,则从第二个块——索引是1的开始计算) current_index = 1 #遍历这个链 while current_index block = chain[current_index] #如果当前块的前一个哈希值属性值不等于我们计算出来的上一个块的哈希值,说明链虚假,验证不通过 if block['previous_hash'] != self.hash(last_block): return False #工作量证明可能不满足规定(这里是四个0开头) 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 #自身链表长度 max_length = len(self.chain) #暂存链条 new_chain = None #遍历邻居的数据,用最长的链条取代该链 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 #如果new_chain存在的话,则说明它是新的最长链 if new_chain: self.chain = new_chain return True return False # 新的区块 def new_block(self,proof,previous_hash = None): #构造json对象 block = { 'index':len(self.chain) + 1, 'timestamp':time(), 'transcations':self.current_transactions, 'proof':proof, # 可以是传过来的hash值或者是调用hash()计算出来的hash值 'previous_hash':previous_hash or self.hash(self.last_block) } #把交易信息清空,因为交易已经打包成区块了,那么当前的交易就已经没有了 self.current_transactions = [] #把生成的区块加入到链条中 self.chain.append(block) return block # 新的交易(发送者,接受者,金额) def new_transaction(self,sender,recipient,amount)->int: #每个交易信息都是一个json,把它添加到交易信息的最后 self.current_transactions.append( { 'sender':sender, 'recipient':recipient, 'amount':amount } ) #返回索引:原索引加一 return self.last_block['index'] + 1 # 静态方法,计算区块的哈希值 @staticmethod def hash(block): #block是json对象,先把json转化为string并对字符排序并编码 block_string = json.dumps(block,sort_keys = True).encode() #传入字符串编码后的字节数组,返回hash的摘要信息 return hashlib.sha256(block_string).hexdigest() # 属性,获取到区块链最后一个区块 @property def last_block(self): #-1表示是数组最后一个元素 return self.chain[-1] #工作量证明 def proof_of_work(self,last_proof:int)->int: proof = 0 #不停尝试proof的值,验证proof是否满足条件 while self.valid_proof(last_proof,proof) is False: proof += 1 print(proof) return proof #验证上一个区块的工作量证明和当前需要验证的工作量证明是否满足条件 def valid_proof(self,last_proof:int,proof:int)->bool: #先把两个值转化成一个字符串并编码 guess = f'{last_proof}{proof}'.encode() #用同样的方法拿到hash摘要 guess_hash = hashlib.sha256(guess).hexdigest() print(guess_hash) #如果满足前面四位是以0开头则返回True return guess_hash[0:4] == '0000' #初始化Flask类 app = Flask(__name__) #实例化blockchain blockchain = Blockchain() #利用uuid随机生成节点的ID,替换掉横杠 node_identifier = str(uuid4()).replace('-','') #定义一个新添加交易的路由,用POST发送到服务器 @app.route('/transactions/new',methods = ['POST']) def new_transaction(): #拿到客户POST过来的内容 values = request.get_json() #如果没有values的情况 if values is None: return 'Missing values',400 #对三个字段进行检查,看请求时是否带上了这三个参数 required = ['sender','recipient','amount'] #如果k中内容有一个不在required中的话返回错误,返回400 if not all(k in values for k in required): return 'Missing values',400 #如果满足格式则新建一个交易,把交易添加到当前的数组,返回的是其即将所在的区块索引 index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount']) #把新添加的交易的信息返回给用户 response = {'message':f'Transaction will be added to block:{index}'} #通常post请求去添加一条记录都是返回201 return jsonify(response),201 #定义一个用来挖矿(交易打包)的API @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) #用proof新建一个块,传None的话会自己计算上一个区块的hash值 block = blockchain.new_block(proof,None) #把包好的信息返回给用户 response = { "message":"New Block Forged", "index":block['index'], "transactions":block['transcations'], "proof":block['proof'], "previous_hash":block['previous_hash'] } return jsonify(response),200 #定义返回整个区块链信息的路由 @app.route('/chain',methods = ['GET']) def full_chain(): response = { #块的信息 'chain':blockchain.chain, #链条数组的长度 'length':len(blockchain.chain) } #将dict类型转换为json串 return jsonify(response),200 #节点注册路由 #{"nodes":["http://127.0.0.2:5000"]} @app.route('/nodes/register',methods = ['POST']) def register_nodes(): #接受传过来的数据 values = request.get_json() #接受节点信息 nodes = values.get("nodes") #信息为空判断 if nodes is None: return "Error:please supply a valid list of node",400 #多node注册 for node in nodes: blockchain.register_node(node) #信息返回给用户 response = { "message":"New nodes have been added", #原来定义的是个set集合,这里转化成list "total_node":list(blockchain.nodes) } return jsonify(response),201 #可以调用解决冲突的路由 @app.route('/nodes/resolve',methods = ['GET']) def consensus(): #调用函数并查看链条是否被取代了 replaced = blockchain.resolve_conflicts() #如果被取代了要告诉一下用户 if replaced: response = { "message":"Our chain was replaced", "new_chain":blockchain.chain } else: response = { "message": "Our chain is authoritative", "chain":blockchain.chain } return jsonify(response),200 #启动Flask,提供运行入口 if __name__ == '__main__': #每次运行可以跑在不同的端口上,不是默认一个,而是通过参数传过来的 #初始化一个parser用来解析命令行参数 parser = ArgumentParser() #加上端口命令,举例:-p 5001 或者--port 5001 parser.add_argument('-p','--port',default = 5000,type = int,help = 'port to listen to') #对其解析 args = parser.parse_args() port = args.port app.run(host = '0.0.0.0',port = port) 个人博客:www.unconstraint.cn