1、代码
文件名称:blockchain.py
"""
{
一个块有序号
有时间戳
交易记录
工作量证明
上一个区块的hash值
"index":0,
"timestampe":"",
"transactions":[
{
"sender":'',#发送者
"recipient":'',#接受者
"amount":5,#金额
}],
"proof":'',
"previous_hash":'',
}
"""
import hashlib
from argparse import ArgumentParser
from time import time
import json
from urllib.parse import urlparse
from uuid import uuid4
import requests
from flask import Flask, jsonify, request
class Blockchain:
def __init__(self):
self.chain=[]#每个元素就是一个区块
self.current_tansactions=[]#每个元素就是一个交易实体
#创世纪区块
self.new_block(proof=100,previous_hash=1)
#节点
self.nodes=set()#set是集合
def register_node(self,address:str):#注册节点
parsed_url=urlparse(address)#节点地址,URLPARse是地址解析
self.nodes.add(parsed_url.netloc)#添加到node,只取netloc
@property#属性
def last_bock(self):#获取最后一块
return self.chain[-1]
def new_block(self,proof,previous_hash=None):#新加一个快
block={
'index':len(self.chain)+1,
'timestamp':time(),
'transcations':self.current_tansactions,
'proof':proof,
'previous_hash':previous_hash or self.hash(self.chain[-1])
}
#因为交易已经打包成区块,所以,就要把交易值清空
self.current_tansactions=[]
self.chain.append(block)#在链条里面加入新区块
return block
def new_transaction(self,sender,recipient,amount)->int:#新交易
#->一般出现在定义函数后面,为函数增加元数据,描述函数的返回类型
#交易包含发送/接受/金额
self.current_tansactions.append(
{#获取一个json数据
'sender':sender,
'recipient':recipient,
'amount':amount
}
)
return self.last_bock['index']+1
@staticmethod#静态方法
def hash(block):
#block是一个json,我们需要对block进行改造
block_string=json.dumps(block,sort_keys=True).encode()#字符排序
return hashlib.sha256(block_string).hexdigest()
def proof_of_work(self,last_proof:int)->int:#这个last_proof需要传参,也就是
#挖矿时候的,可以尝试的随机值
#产生一个工作量证明的值
proof=0
while self.valid_proof(last_proof,proof) is False:#valid_proof是不断去验证是否满足
#是否满足四个0开头的工作量
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)
if guess_hash[0:4]=='0000':#直到这个hash值满足条件:四个0开头
return True
else:
return False
#共识机制,是解决冲突的方法
def resolve_conflicts(self)->bool:
neighbours=self.nodes#nodes information
max_length=len(self.chain)#自身的链条长度
new_chain=None
for node in neighbours:#遍历链条,看哪条链条最长
response=requests.get(f'http://{node}/chain')#在路由chain,就可以获取chain和length
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#如果链条更长,就选择
#还要判断链条是否是有效链条,在valid_chain
new_chain=chain#换成新链条
if new_chain:
self.chain=new_chain
return True
return False
def valid_chain(self,chain)->bool:
last_block=chain[0]
current_index=1#
while current_index<len(chain):#遍历链条,依次去计算hash
block=chain[current_index]
if block['previous_hash']!=self.hash(last_block):#hash验证
return False#验证失败,返回false
if not self.valid_proof(last_block['proof'],block['proof']):
return False
#工作量证明是四个0开头如果不满足,也是假的
last_block=block
current_index +=1
return True
# #检验hash
# test = Blockchain()
# test.proof_of_work(100)
#利用这个server,与其他用户节点进行交互,不然就是只做好了一个块
#用flask映射webserver
app=Flask(__name__)
blockchain=Blockchain()
#
node_identifier=str(uuid4()).replace('-','')
#实例化区块链
#映射到某个函数
@app.route('/index',methods=['GET'])
def index():
return "hello BlockChain"
#新交易,交易的地址/金额返回
@app.route('/transactions/new',methods=['POST'])
def new_transaction():
values=request.get_json()#需要获取一个客户传递过来的表单信息
if values==None:#如果用户没有传参
return 'missing values',400
required=["sender",'recipient','amount']#检查三个字段
if not all(k in values for k in required):#如果有字段没有在其中,就是错误的
return "missing values",400
#用传送过来的三个信息,建立一个新的交易
#把交易添加到当前交易的数组
#返回的是一个index
index =blockchain.new_transaction(values['sender'],
values['recipient'],
values['amount'])
#提示用户我们添加一个交易
# 交易完成之后,需要挖矿才能让链条变长
response={'message':f'transation will be added to block{index}'}
return jsonify(response),201
@app.route('/mine',methods=['GET'])
def mine():
last_block=blockchain.last_bock
last_proof=last_block['proof']
#计算工作量证明
proof=blockchain.proof_of_work(last_proof)
#新的交易,是给挖矿者的
blockchain.new_transaction(sender='0',
recipient=node_identifier,#这个节点地址是随机生成的
amount=1
)
block=blockchain.new_block(proof,None)#hash值输入none,它会自己去计算那个hash值
response={
'message':'new block forged',
'index':block['index'],
'time':time(),
'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)
}
#把json结构换成字符串
return jsonify(response),200
#节点注册
@app.route('/nodes/register',methods=['POST'])
def register_nodes():
#发送方需要发送一个信息,格式如下:
# {
# "nodes": ["http://127.0.0.2:5000"]
# }
values=request.get_json()
nodes=values.get('nodes')
if nodes is None:
return "error: please supply a valid list of nodes",400
for node in nodes:
blockchain.register_node(node)
response={
'message':'new nodes have been added',
'total_nodes':list(blockchain.nodes)
}
return jsonify(response),201
#调用解决冲突的方法
@app.route('/nodes/resolve',methods=['GET'])
def consenseus():
replaced=blockchain.resolve_conflicts()
if replaced:
response={
'message':'our chain is replaced',
'new_chain':blockchain.chain
}
else:
response = {
'message': 'our chain is authoritative',
'chain': blockchain.chain
}
return jsonify(response),200
if __name__ =='__main__':
#设置不同端口,到不同的节点,在终端利用代码运行:
##python blockchain.py -p 5001
#就可以换到所需端口的节点
parser=ArgumentParser()#解析命令行参数
#让端口成为参数
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)
2、运行环境
使用pycharm和anaconda进行开发
利用conda新建一个python3.6的虚拟环境
以下为需要特殊安装的包
conda create -n python36 python=3.6
pip install flask==0.12.2
pip install werkzeug ==0.16.0
pip install requests==2.18.4
3、测试工具
Postman,安装,直接下载安装包,解压缩即可
可以看其他安装教程,是一个tar.gz后缀的文件
直接点开postman:
点开workspace,如图,直接打开就行,在右边可以输入链接
4、运行python文件时,打开两个终端
5、进行测试
输入如图所示的链接,进行测试
可以参考代码进行分析
其中post传参值如下:
发送者,接受者的名字等可以自行更改