本帖最后由 regan 于 2018-4-16 11:52 编辑
1.区块链技术最近非常的火,到处都在热议区块链,去中心化,挖矿等等各种概念层出不穷。各大公司也在大力部署区块链。也许你跟我一样刚听别人说区块链的时候,只是懵懵懂懂的只是知道个概念,但作为一名技术人员,用手里的技术是实现一个自己的区块链,不仅可以深入了解区块链的运行机制,还有一个重要的原因是乐在其中,因为马上自己就要用Python代码实现自己的区块链了!
2.确保安装了pip Flask request库
pip install Flask==0.12.2 requests==2.18.4
3.接下来,我们要考虑如何去实现区块链了,区块链需要得到记录并且需要记录交易的信息。因此需要使用两个列表来保存这些节点信息。当然要让新的区块加入进来,需要实现一个new_block的添加方法,添加的新的区块中需要记录交易信息,因此还要实现一个new_transaction方法用于将交易信息添加到交易列表中。每一个区块都是不重复且唯一的,因此需要为每个区块定义一个唯一的标志,这里借助Hash模块计算Hash值,定义一个hash方法,当然也可以使用别的算法为每个区块生成一个唯一的值。当我们添加新的区块的时候,需要把区块添加到区块链的最后,因此要实现一个方法,这个方法用于返回区块链最后的记录,这个方法取名叫last_block.这里先定义出BlockChain类的框架
class Blockchain(object):
def __init__(self):
#定义两个列表,用于记录区块链及交易信息
self.chain = []
self.current_transactions = []
def new_block(self):
# 创建一个新的Block区块,并添加到区块链中
pass
def new_transaction(self):
# 在交易列表中添加一个交易信息
pass
@staticmethod
def hash(block):
# 通过Hash算法返回区块的Hash值
pass
@property
def last_block(self):
# 返回区块链中最后一个区块
pass
4.接下来我们考虑下区块的结构,区块链最主要的作用是去中心化,并且要记录区块与前面区块的关联,交易及交易发生的时间,因此Block这个数据结构至少要有timestamp、transactions、工作量证明【也就是所说的通过挖矿【劳动】来获取的一个值,这个地方只要是通过一个算法,用计算机的算力去碰撞这个值,最先算出这个值的将会得到挖矿的奖励】、以及形成链条的用于记录前一个区块的previous_hash,这样在整个链条上只要有一个Block被攻击破坏了,后面所有区块的Hash都是错误的,这保证了区块链的不变性。当然每个区块还得有自己的索引编号,表示是第几个Block,transactions里要记录交易双方的信息及交易的数量。Block数据结构如下:
block = {
'index': 1,
'timestamp': 1506057125.900785,
'transactions': [
{
'sender': "8527147fe1f5426f9dd545de4b27ee00",
'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
'amount': 5,
}
],
'proof': 324984774000,
'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
5.理解了上面的基本的概念,那我们接下来实现BlockChain中的new_transaction方法,一个交易的完成少不了交易双方及交易的量这几个重要的值。因此这几个值做成参数传递给new_transaction方法
def new_transaction(self, sender, recipient, amount):
"""
生成新交易信息,信息将加入到下一个待挖的区块中
:param sender: <str> 发送者的地址
:param recipient: <str> 接受者的地址
:param amount: <int> 交易额度
:return: <int> 返回新的Block的Id值,新产生的交易将会被记录在新的Block中
"""
#实现很简单,向交易列表中添加一个字典,这个字典中记录交易双发的信息
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
#返回最后一个区块的index加上1,即对应到新的区块上
return self.last_block['index'] + 1
6.接下来我们完善创建区块的方法,想一下在区块链“创世之初”还没有任何区块的时候,这个时候区块链刚刚被创建,因此我们需要在创建区块链的时候,指定一个创世块,并给它添加一个工作量证明。先来理一下创建新块需要做哪些事情,需要记录前面一个区块的hash地址吧!需要记录工作量的证明吧,没有工作量,谁给你发工资,类似于公司里的考核。接下来我们就来实现new_block方法
def new_block(self, proof, previous_hash=None):
"""
生成新块
:param proof: <int> 工作量证明,它是一个工作算法对应的一个值
:param previous_hash: (Optional) <str> 前一个区块的hash值
:return: <dict> 返回一个新的块,这个块block是一个字典结构
"""
block = {
#新block对应的index
'index': len(self.chain) + 1,
#时间戳,记录区块创建的时间
'timestamp': time(),
#记录当前的交易记录,即通过new_transactions创建的交易,记录在这个新的block里
'transactions': self.current_transactions,
#工作量证明
'proof': proof,
#前一个block对应的hash值
'previous_hash': previous_hash or self.hash(self.chain[-1]),
}
# 重置当前的交易,用于记录下一次交易
self.current_transactions = []
#将新生成的block添加到block列表中
self.chain.append(block)
#返回新创建的blcok
return block
7.前面说了在区块链创建之初,需要先创建创世块,这个创世块应该在哪个位置被创建呢?没错,就是和区块链一起创建,自然是在__init__方法中啦。接下来我们看下__init__方法。
def __init__(self):
self.current_transactions = []
self.chain = []
# 创建“创世块”
self.new_block(previous_hash=1, proof=100)
8.在new_transaction中调用了last_block这个方法,其实这个方法很简单,返回区块链最后一个区块。
@property
def last_block(self):
return self.chain[-1]
9.还有一个方法没有实现,那就是hash方法,在这个方法内部需要为当前的block计算得出一个hash值,最简单的方法就是使用“摘要函数”,python工具箱中已经实现了很多摘要函数例如md5,sha256等。
@staticmethod
def hash(block):
"""
生成块的 SHA-256 hash值
:param block: <dict> Block
:return: <str>
"""
# 首先将block字典结构转换成json字符串,通过sort_keys指定按key拍好序。
block_string = json.dumps(block, sort_keys=True).encode()
#调用sha256函数求取摘要
return hashlib.sha256(block_string).hexdigest()
10.上面实现了区块链的基本结构及方法,那区块是如何挖出来的呢?如何实现挖矿呢?还记得上面定义的proof工作量证明这个变量吗?它是如何计算出来的呢?它和挖矿又有何联系呢?
工作量实际上是需要使用工作量证明方法PoW来构造!它的目标就是找出符合特定条件的数字,而这个数字往往很难计算出来,因此要计算它需要耗费大量的算力,即工作量!这个数据虽然难计算,很很容易验证的。
为了便于理解,假设有两个数字x,y.这里定义一个规则x乘以y的hash值必须以'a'结尾,设x变量为math.e,求y?
from hashlib import sha256
import math
x = math.e
y = 0 # y未知
while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "a":
y += math.pi
print(f'The solution is y = {y}')
最后打印出一个结果,这个结果。
在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。
证明很困难,相反验证就很容易了,
x = math.e
y = 9.42477796076938
sha256(f'{x*y}'.encode()).hexdigest()
11.理解了工作量,接下来我们就来实现一个工作量证明的PoW算法,规则为:寻找一个数p,使得他和前面一个区块的proof拼接成的字符串的Hash值以‘abc'符开头。定义两个方法proff_of_work和valid_proff方法。
def proof_of_work(self, last_proof):
"""
简单的工作量证明:
- 查找一个数 p 使得 hash(p+last_proof) 以'abc'开头
- last_proof 是上一个块的证明, p是当前的证明
:param last_proof: <int>
:return: <int>
"""
proof = 0
#定义一个死循环,直到valid_proof验证通过
while self.valid_proof(last_proof, proof) is False:
proof += math.pi
return proof
@staticmethod
def valid_proof(last_proof, proof):
"""
验证证明: 是否hash(last_proof, proof)以'abc'开头?
:param last_proof: <int> 前一个证明
:param proof: <int> 当前证明
:return: <bool>
"""
guess = f'{last_proof}{proof}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:3] == "abc"
PoW工作量的证明算法和验证算法就写完了,是不是很简单!当然对于算法的复杂度来说,字符串的个数越长,验证的复杂度就越高,例如验证以’abcd‘四个字符开头,每增加以为都将大大增加验证所需要的时间。
12.BlockChain类已经基本完成,是不是非常简单呢?接下来,我们需要制作一个Restful接口,以供区块链在网络上调用。
我们将使用Python Flask框架,这是一个轻量Web应用框架,它方便将网络请求映射到 Python函数,现在我们来让Blockchain运行在基于Flask web上。
我们将创建三个接口:
- /transactions/new 创建一个交易并添加到区块
- /mine 告诉服务器去挖掘新的区块
- /chain 返回整个区块链
Flask服务器将会扮演一个“节点”,先来书写Flask的整体框架,而后再实现。
import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4
from flask import Flask
#实例化一个Flask节点
app = Flask(__name__)
# 为当前节点生成一个全局唯一的地址,使用uuid4方法
node_identifier = str(uuid4()).replace('-', '')
#初始化区块链
blockchain = Blockchain()
# 告诉服务器去挖掘新的区块
@app.route('/mine', methods=['GET'])
def mine():
return "We'll mine a new Block"
# 创建一个交易并添加到区块,POST接口可以给接口发送交易数据
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
return "We'll add a new transaction"
#返回整个区块链,GET接口
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200
if __name__ == '__main__':
#服务器运行在5000端口上
app.run(host='0.0.0.0', port=5000)
13.有了整体的框架,那我们来实现吧!
首先来实现发送交易的方法new_transaction方法,发送交易的数据格式如下:
{
"sender": "sender address",
"recipient": "other address",
"amount": 10
}
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
#获取请求的参数,得到参数的json格式数据
values = request.get_json()
print('request parameters:%s'%(values))
#检查请求的参数是否合法,包含sender,recipient,amount几个字段
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'Missing values', 400
# 使用blockchain的new_transaction方法创建新的交易
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
#构建response信息
response = {'message': f'Transaction will be added to Block {index}'}
#返回响应信息
return jsonify(response), 201
13.接下来就实现最“神奇”的地方——挖矿!挖矿要做三件事情。
- 计算工作量证明PoW
- 通过新增一个交易授予矿工(自己)一个币
- 构造新区块并将其添加到链中
@app.route('/mine', methods=['GET'])
def mine():
#获取区块链最后一个block
last_block = blockchain.last_block
#取出最后一个block的proof工作量证明
last_proof = last_block['proof']
# 运行工作量的证明和验证算法,得到proof。
proof = blockchain.proof_of_work(last_proof)
# 给工作量证明的节点提供奖励.
# 发送者为 "0" 表明是新挖出的币
# 接收者是我们自己的节点,即上面生成的node_identifier。实际中这个值可以用用户的账号。
blockchain.new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)
# 产生一个新的区块,并添加到区块链中
block = blockchain.new_block(proof)
#构造返回响应信息
response = {
'message': "New Block Forged",
'index': block['index'],
'transactions': block['transactions'],
'proof': block['proof'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 200
14.至此,全部代码就开发完成了!接下来运行区块链程序吧!
运行 python Blockchain.py
在浏览器中输入localhost:5000/mine进行挖矿
请求chain获取整个区块链
localhost:5000/chain
可以看到整个区块链的数据了。
使用curl 命令模拟post请求转账
curl -X POST -H "Content-Type: application/json" -d '{
"sender": "3acfc29432d6463187def555ea24c856",
"recipient": "mymymyacount",
"amount": 5
}' "http://localhost:5000/transactions/new"
15.惊不惊喜,刺不刺激!接下来我们玩点更刺激的,将我们的区块链分布式化!借助Docker容器技术,快速实现分布式的区块链。现在要想,既然是分布式的,那我们怎样来保证所有的节点拥有相同的链喃?这是一个一致性的问题,要实现分布式的多节点,必须实现一致性算法!
在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口:
- /nodes/register 接收URL形式的新节点列表
- /nodes/resolve 执行一致性算法,解决任何冲突,确保节点拥有正确的链
接下来,我们修改单节点版本的算法,在init方法中增加一个set集合,用于记录网络中所有的节点。set集合可以避免重复添加。
def __init__(self):
self.current_transactions = []
self.chain = []
self.nodes = set()
# 创建“创世块”
self.new_block(previous_hash=1, proof=100)
接下来我们要实现一个注册方法,让每个节点都知道它相邻的节点,把相邻节点的信息注册到自己上,实际上就是记录下相邻节点的url地址到set集合中。
def register_node(self, address):
"""
增加一个新的网络节点到set集合
:param address: <str>网络地址 'http://172.16.0.50:5000'
:return: None
"""
parsed_url = urlparse(address)
self.nodes.add(parsed_url.netloc)
16.接下来实现一致性共识算法。前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。基于这个假设,我们实现一致性算法。这个算法要做两件事
- 检验链是否有效
- 找到网络中链最长且有效的链,若最长的链不是自己的,则替换掉自己的链。
实现两个方法valid_chain和resove_confilicts方法,如下
def valid_chain(self, chain):
"""
检查给定的链是否是有效的
:param chain: <list> 区块链
:return: <bool>
"""
last_block = chain[0]
current_index = 1
while current_index < len(chain):
block = chain[current_index]
print(f'{last_block}')
print(f'{block}')
# 检验当前block的previous_hash值和前面block的hash值是否相等
if block['previous_hash'] != self.hash(last_block):
return False
# 验证前面block的工作量证明和当前block的工作量证明拼接起来的字符串的hash是否以'abc'为开头
if not self.valid_proof(last_block['proof'], block['proof']):
return False
last_block = block
current_index += 1
#验证通过,返回True
return True
def resolve_conflicts(self):
"""
共识算法解决冲突
使用网络中最长的链.
:return: <bool> True 如果链被取代, 否则为False
"""
#所有的邻居节点
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']
# 检查链是否更长,且有效。更新new_chain,并更新max_length。
if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain
# 如果new_chain是有定义的,则说明在邻居中找到了链更长的,用new_chain替换掉自己的链
if new_chain:
self.chain = new_chain
return True
return False
17.核心的方法定义好了,别急!还需要定义两个Restful接口,来接收注册和一致性。
@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 nodes", 400
#注册节点到blockchain中
for node in nodes:
blockchain.register_node(node)
#构造一个响应
response = {
'message': 'New nodes have been added',
'total_nodes': list(blockchain.nodes),
}
#201:提示知道新文件的URL
return jsonify(response), 201
#解决一致性问题的API
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
#调用resolve_conficts()方法,让网络中的chain协调一致
replaced = blockchain.resolve_conflicts()
#如果当前节点的chain被替换掉,返回替换掉的信息;否则返回当前节点的chain是有权威的!
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
18.接下来编写程序的入口
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
args = parser.parse_args()
port = args.port
app.run(host='127.0.0.1', port=port)
19.进入到BlockChain_distribution.py所在目录,运行
a. python BlockChain_distribution.py
b. python BlockChain_distribution.py --port 5001
c.启动另一个console窗口,使用curl命令模拟请求,/nodes/register注册。
curl -X POST -H "Content-Type: application/json" -d '{
"nodes": ["http://127.0.0.1:5001"]
}' "http://127.0.0.1:5000/nodes/register"
curl -X POST -H "Content-Type: application/json" -d '{
"nodes": ["http://127.0.0.1:5000"]
}' "http://127.0.0.1:5001/nodes/register"
在端口为5001的上面挖矿。运行:localhost:5001/mine
查看blockchain
localhost:5001/chain
然后在端口为5000的机器上运行/nodes/resolve接口,解决一致性问题
localhost:5000/nodes/resolve
看到同步成功!再在5000上运行localhost:5000/chain
可以看到两个网络节点的blockchain得到了同步!分布式的区块链开发完成!,实际上这里可以把区块部署在不同的网络节点,启动不同的端口,只要一个网络节点知道其他的网络节点就可以,因为他在内部可以通过请求邻居网络节点来获取区块链的最新情况,从而做一致性决策。
20.为了便于大家一键部署把玩,这里带大家把刚才我们开发的分布式的区块链部署到Docker容器中。在linux服务器上安装Docker ,centos使用yum install docker,ubuntu/debian使用apt-get install docker安装即可。然后启动docker服务器。
【Docker相关的内容可以查考课程:http://edu.51cto.com/course/12675.html】
选择docker.io/avnergoncalves/ubuntu-python3.5作为基镜像
#指定基镜像
FROM docker.io/avnergoncalves/ubuntu-python3.5
#设置工作目录
WORKDIR /app
#更新
RUN apt-get update -qqy
#安装必要的软件和工具
RUN apt-get -qqy install vim wget net-tools iputils-ping openssh-server python python3-dev python3-pip
#使用pip命令安装Flask,jsonify,request库
RUN pip3 install --upgrade pip
RUN pip3 install flask jsonify request
#开放5000端口
EXPOSE 5000
ENV BUILD_ON 2018-03-03
#拷贝py文件到app目录
COPY docker_blockchain.py /app
#指定容器启动后执行的命令
#CMD ['/bin/bash']
ENTRYPOINT ["python3", "/a
新建blockchain目录,将上面的Dockerfile,docker_blockchain.py文件拷贝到这个目录中,新建build.sh文件和build_network.sh文件
build.sh文件内容如下:
build_network.sh文件内容如下:
运行sh build.sh等待镜像的构建完成,完成后使用docker iamges查看生成的blockchain镜像。
运行sh build_network.sh构建网络。使用docker network ls 查看网络情况
接下来就可以运行镜像生成容器了。
测试脚本如下:
#注意:运行的时候将192.168.0.4改成你自己虚拟机的ip即可!其他不用变。
#启动node1节点
docker run --rm -p 12381:5000 --net blockchain --name node1 --ip 172.19.0.2 blockchain
#启动node1节点
docker run --rm -p 12382:5000 --net blockchain --name node2 --ip 172.19.0.3 blockchain
#模拟挖矿
curl -X GET -H "Content-Type: application/json" "http://192.168.0.4:12381/mine"
#获取整个区块链信息
curl -X GET -H "Content-Type: application/json" "http://192.168.0.4:12381/chain"
#模拟转账
curl -X POST -H "Content-Type: application/json" -d '{
"sender": "891a9c1f3d1a4575871eaa2f2e44b85e",
"recipient": "mymymyacount",
"amount": 5
}' "http://192.168.0.4:12381/transactions/new"
#模拟注册
curl -X POST -H "Content-Type: application/json" -d '{
"nodes": ["http://192.168.0.4:12381"]
}' "http://192.168.0.4:12382/nodes/register"
curl -X POST -H "Content-Type: application/json" -d '{
"nodes": ["http://192.168.0.4:12382"]
}' "http://192.168.0.4:12381/nodes/register"
#在node2上挖矿
curl -X GET -H "Content-Type: application/json" "http://192.168.0.4:12382/mine"
#在node1上挖矿,先调用一致性同步算法。在node2上挖矿没挖一次都要调用一下同步算法
curl -X GET -H "Content-Type: application/json" "http://192.168.0.4:12381/nodes/resolve"
curl -X GET -H "Content-Type: application/json" "http://192.168.0.4:12382/nodes/resolve"
如果你先制作镜像麻烦,可以直接pull我已经制作好的镜像,docker search reganzm/blockchain,docker pull reganzm/blockchain即可。然后运行上面的测试脚本!
|
|