区块链的文章看了很多是不是还是有些云里雾里的感觉 ,在对区块链概念有了基本了解之后,笔者建议有基础的同学可以动手写一个区块链,这样才能更深刻的理解区块链的本质。本文是写给有相应python和区块链基础的同学教给大家如何通过python从零开始构建区块链。
前导知识储备
1. Python
2. 区块链基础
测试环境
#此为本机环境,代码在本机环境已调试 运行无误
1. python3.6.3
2. flask0.12.2
3. requests2.18.4
4. postman
代码结构
整个区块链主要包括两个方面,一块是区块链核心代码,另外一块是节点服务器相关的接口,接下来分别就这两部分进行介绍。
1. 区块链核心代码
2. 节点服务器接口
区块链核心代码
首先我们看下最核心的区块链核心代码结构,对整体框架有个感性认识,之后我会分块进行完善。方便展示我调整了下格式。
class BlockChain(object):
def __init__(self):pass
def new_block(self):pass
def new_transaction(self):pass
def proof_of_work(self):pass
def register_node(self):pass
def valid_chain(self, chain):pass
def resolve_conflicts(self):pass
def hash(block):pass
def valid_proof(last_proof, proof):pass
def last_block(self): pass
__init__: 初始化
def __init__(self):
self.chain = []
self.current_transactions = []
self.nodes = set()
#创世区块构建
self.new_block(previous_hash=1, proof=100)
初始化函数中,定义了两个列表,chain用来存储区块链,current_transactions用来存储交易信息。集合nodes用来存储节点信息。实例化类的时候,同时初始化产生第一个区块,即创世区块。
new_block: 生成新的区块
def new_block(self, proof, previous_has=None):
block = {
'index': len(self.chain),
'timestamp': time.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
new_block主要用来生成一个新快,并添加到区块链的尾部,这里我们可以看到一个区块的结构被定义为一个字典。区块结构包括5类关键信息
1. index:当前区块的编号,区块编号从创世区块开始递增
2. timestamp: 当前时间戳,生成该区块时候的时间
3. transactions: 未加到区块的交易信息列表
4. proof:工作量证明
5. previous_hash: 前一个区块的哈希值
这是简单的模拟一个区块的结构,与比特币区块链有些差别。更详细的区块结构信息请参考我之前写过的文章浅出区块链,需要 注意的是区块哈希值并不在区块的数据结构里
new_transaction: 产生新的交易
def new_transaction(self, sender, receiver, amount):
self.current_transactions.append({
'sender': sender,
'receiver': receiver,
'amount': amount
})
return self.last_block['index']+1
一个交易的数据结构包括三部分
1. sender:发送发地址
2. receiver:接收方地址
3. amount:数量
函数里将交易数据加入到交易列表里,并返回即将通过挖矿产生的记录当前交易的下一个区块的index。
proof_of_work: 工作量证明
def proof_of_work(self, last_proof):
proof = 0
while(self.valid_proof(last_proof,proof) is False):
proof += 1
return proof
工作量证明算法是为了找出一个符合特定条件的数字,作为节点获取当前区块记账权的工作量证明
valid_proof: 工作量有效证明
@staticmethod
def valid_proof(last_proof, proof):
guess = '{0}{1}'.format(last_proof, proof).encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == '0000'
当前规则是:基于上一个区块工作量和当前区块工作量所生成的哈希值前四位为'0000'即为有效工作量。通过改变这一条件,可以调整难度。在比特币区块链中有动态调节机制,保证平均10分钟左右生成一个区块。
register_node: 注册节点
def register_node(self, address):
parsed_url = urlparse(address)
return nodes.add(parsed_url.netloc)
urlparse函数将地址进行解析,结果示例如下
In [1]: urlparse('http://192.168.1.1:5000')
Out [1]: ParseResult(scheme='http', netloc='192.168.1.1:5000', path='', params='', query='', fragment='')
valid_chain: 区块有效性验证
def valid_chain(self, chain):
last_block = chain[0]
current_index = 1
while current_index < len(chain):
block = chain[current_index]
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
区块链的检查主要是针对‘previous_hash' 和 ’proof' 两个字段进行
创世区块的index为0,其previous_hash 为1,不需要 检查,针对其后的每个区块检查以下两点
1. 区块所存储'previous_hash'的字段的值和前一个区块hash出来的值是否一致;
2. 并检查基于该区块'proof'字段的值和上一个区块的'proof'值的哈希值是否满足条件。
resolve_conflicts: 冲突检查
def resolve_conflicts(self):
neighbours = self.nodes
new_chain = None
max_length = len(self.chain)
for node in neighbours:
response = requests.get('http://{0}/chain'.format(node))
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
return False
此函数会遍历其相邻节点寻找更长的区块链,如果 发现更长的区块链则取代当前节点的区块链
hash:生成区块哈希值
@staticmethod
def hash(block):
block_string = json.dumps(block,sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
调用hashlib相应函数生成区块的sha256哈希值
last_block: 返回最后一个区块
@property
def last_block(self):
return self.chain[-1]
恭喜你,看到这里区块链底层最核心的部分已经完成,休息 一会,我们继续完成后续关于节点服务器的代码
节点服务器接口
这部分的代码主要来模拟区块链挖矿、交易、注册新节点、冲突解决等行为。
节点实例化:
import uuid,time,hashlib,json,requests
from urllib.parse import urlparse
from flask import Flask,jsonify,request
app = Flask(__name__)
node_identifier = str(uuid.uuid4()).replace('-','')
blockchain = BlockChain()
if __name__ == '__main__':
import sys
try:
port = int(sys.argv[1])
except:
port = 5000
app.run(host='0.0.0.0',port=port)
UUID(Universally Unique Identifier)通用唯一标识符,对于 所有的UUID可以保证在空间上和时间上的唯一性。
服务器接口:
@app.route('/mine',methods=['GET'])
@app.route('/chain', methods=['GET'])
@app.route('/transactions/new', methods=['POST'])
@app.route('/nodes/register', methods=['POST'])
@app.route('/nodes/resolve', methods=['GET'])
后续我们将分别对接口做进一步的介绍
挖矿接口:
@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',
receiver= node_identifier,
amount = 1,
)
block = blockchain.new_block(proof)
response = {
'message': 'New block created',
'index': blick['index'],
'transactions': block['transactions'],
'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),
}
return jsonify(response),200
发送交易数据接口:
@app.route('/transactions/new', methods=['POST'])
def new_transactions():
values = request.get_json()
required = ['sender', 'receiver', 'amount']
if not all(k in values for k in required):
return('Missing values',400)
index = blockchain.new_transaction(values['sender'],values['receiver'],values['amount'])
response = {'message':f'Transaction will be added to block {index}'}
return jsonify(response),201
注册新节点接口:
@app.route('/nodes/register', methods=['POST'])
def register_nodes():
values = request.get_json()
nodes = values.get('nodes')
if(nodes is None):
return('Error: pls 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 consensus():
replaced = blockchain.resolve_conflicts()
if replaced:
response = {
'message': 'Our chain has been replaced',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'Our chain is authoritative',
'chain': blockchain.chain
}
return jsonify(response),200
到此代码层面已经全部完成,接下来我们将区块链部署,来实际的模拟挖矿,交易等操作。
测试区块链
启动server:
python blockchain.py 5000
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
开始挖矿:
启动第二个节点:
通过在一台机器上开启不同的网络端口模拟多节点网络
python blockchain.py 5001
启动了第二个节点,并且目前第二个节点挖了两次框,区块链的长度为3,两个节点产生冲突。这时可以通过共识机制去处理冲突
解决冲突:
1. 通过接口/nodes/register进行注册
2. 通过接口/nodes/resolve解决节点冲突
可以看到目前节点(5000端口)的区块链已被替换为节点(5001端口)的数据
至此一个简单的完整的区块链就构建完成,需要了解其他更深入的信息,可以参考我的另外一篇区块链系列文章,里面收集了一些我认为比较不错的资料。
参考
Learn Blockchain by Building One, Daniel van Flymen