简介
本系统在基本上参考了这篇博客,访问可能需要搭梯子;本文主要是进行了翻译和验证,同时提供了一个基本的思路,肯定有很多不足之处,希望dalao们积极指正,因为是刚开始学习区块链。。。需要少部分的Flask的知识,这是一个轻型的web框架,在这里我们用来模拟结点联网的操作。
本系统的可以实现的功能:
- 产生区块,把区块添加到区块链上。
- 根据加密算法,验证交易的合法性 。
- 验证区块的合法性。
- 验证区块链的合法性,选择最长的有效链作为主链。
- 进行简单的加密货币交易 。
本系统的不足:
1. 没有签名验证机制
2. 所有的数据都是存储在内存中的,暂时没有存储到硬盘中。
3. 一个区块中存储的交易是线性表存储的,没有使用Merkle树的数据结构,因此必须进行线性的计算验证,而不是对数级别的。
4. 交易机制有缺陷,无法验证交易金额的合理性。假设A可以向B支付任意数值的比特币。
5. 挖矿机制存在缺陷,仅仅是根据哈希数值计算出来的,只要是先计算出首位是4个0的,就可以获得记账权;但是比特币是每10分钟,看谁的计算力更强。
6. 全部都是零确认交易,很容易出现双重支付攻击。
7. 广播算法存在问题,可能会引起循环播放
总体说明:本系统主要是为了学习区块链基本思想的,仅仅是学习参考。后期我会逐步完善,整取做一个更加完善的系统,可以持续关注。
区块的结构
区块的数据用dict格式存放,正好方便与json格式进行交互。一个区块包含以下结构:
- 在区块链中的编号
- 时间戳,创建区块的时间
- 包含的交易,如果区块入链,那么交易加入区块链;否则是待验证的交易。(list)
- 用于证明工作量的数据。该数据随机生成,与前一个区块的proof进行哈希计算,之后得到算力证明的数据
之后,按照区块链的设计思路,一个区块分为区块头和区块体,区块体包含交易,区块头包含其余的信息。在这里单独介绍一下交易的数据结构。因为本系统仅仅是验证使用的,我们不检验交易的合理性,用户也没有自己的钱包,一笔交易的数据结构只有发送方、接收方和交易的比特币。
区块链的结构
一个区块链包含如下结构:
- 主链,包含了当前所有经过验证的区块(list)
- 缓冲链,包含了未经过验证的区块和有关的交易信息(list)
- 结点集合,因为一个结点包含一条区块链,因此把邻接点的集合也放在主链中。(set)
- 区块,包含了交易信息
加密算法检验有效性
真正的比特币的签名机制我在这篇博客中有详细介绍。但是,由于本文仅仅介绍基本工作流程,这里仅仅是验证区块的数据没有被更改过和工作量的有效性。本文的具体检验步骤如下:
- 工作量的证明。把前一个区块的proof和本区块的proof一起进行哈希,检验开头四个字符是否都是0
- 链的有效性证明。把一个区块链从前往后所有的区块执行1的操作,根据哈希值是否匹配来判断链的有效性。因为在创建新区块的时候,就把前一个区块的哈希值添加到了新区块的头部了。而创世区块的前一个区块的哈希值默认1,proof默认100。
工作量证明和挖矿机制
把前一个区块的proof值和当前区块的proof值进行哈希,如果前四个字符是0,表示工作量是有效的。寻找的过程如下:从0开始,逐渐累加,不断与前一个区块的proof进行hash,直到满足条件。挖矿的过程就是一个寻找proof的过程。
代码说明:
全部代码的github地址:https://github.com/StudentErick/BlockChains_in_python/tree/master
头文件
用到的头文件请自行安装,需要Flask框架。
import hashlib
import json
from time import time
from urllib.parse import urlparse
from uuid import uuid4
import requests
from flask import Flask, jsonify, request
区块的数据结构
初始化代码。self.current_transactions
是未加入区块链的交易列表;self.chain
是合法的区块链;self.node
作为邻接结点,使用集合维护;同时,包含一个创世区块。
class BlockChains(object):
def __init__(self):
self.current_transactions = [] # 当前区块的交易
self.chain = [] # 区块链
self.new_block(previous_hash=1, proof=100) # 产生创世区块
self.nodes = set() # 区块链中的结点,防止重复结点的出现
......
创建新节点和添加区块的代码。区块的结构使用字典维护,方便后期与json格式转化;区块中包含一个交易列表、时间戳、前一个区块的哈希值和工作量证明proof。其实这里还有完善的地方,实际情况中,由于网络延时等的原因,交易列表里面会有很多信息没有经过验证,直接清空会有问题;不过,在这里仅仅作为示范,我们假设所有的交易都经过了验证。
class BlockChains(object):
........
def register_node(self, address):
'''
在区块链中添加新的结点
:param address: <str>结点的地址,格式为'http://192.168.0.5:5000'
:return: None
'''
parsed_url = urlparse(address)
self.nodes.add(parsed_url.netloc) # 自动忽略已经存在的结点
def new_block(self, proof, previous_hash=None):
'''
生成新的区块
:param proof:
:param previous_hash: 前一个区块的哈希值
:return: <dict> 新的区块
'''
s = str(previous_hash).encode('utf-8')
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': s or self.hash(self.chain[-1])
}
self.current_transactions = []
self.chain.append(block)
return block
@property
def last_block(self):
return self.chain[-1] # 返回最后一个区块
...........
哈希以工作量证明部分的代码。正如之前提到的那样,我们把整个区块的内容进行哈希操作,得到一个256位哈希值的字符串作为标识。工作量证明也像之前提到的那样,前一个区块的proof与当前区块的proof进行哈希操作。区块链的有效性证明也像之前提到的那样。
class BlockChains(object):
..........
@staticmethod
def hash(block):
'''
生成区块的SHA-256哈希值
:param block: <Block>当前区块的哈希值
:return: <str>
'''
block_string = json.dumps(block, sort_keys=True)
return hashlib.sha256(block_string).hexdigest()
def proof_of_work(self, last_proof):
'''
寻找一个p,使得与前一个区块proof哈希后的数值有4个0开头
:param last_proof: <int>
:return: <int>
'''
proof = 0
while self.valid_proof(last_proof, proof) is False:
proof += 1
return proof
@staticmethod
def valid_proof(last_proof, proof):
'''
验证是否以4个0开头
:param last_proof: 上一个区块的proof
:param proof: 当前的proof
:return: <bool>
'''
guess = (str(last_proof) + str(proof)).encode() # 相当于一个字符串的拼接
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
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(str(last_block))
print(str(block))
print("\n-----------\n")
# 检查区块的哈希值是否正确
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
..........
解决区块链冲突的问题,系统中可能有多个不同的区块链,结点应该选择当前最长的有效链作为主链。代码如下:
class BlockChains(object):
..........
def resolve_conclicts(self):
'''
检查区块链的冲突,必须是选择网络中最长的区块链
:return:<bool> True如果当前链被取代,否则False
'''
neighbours = self.nodes # 获取所有的邻接结点的消息
new_chain = None
max_length = len(self.chain)
for node in neighbours:
s = str(node)
st = 'http://' + s + '/chain'
response = requests.get(st)
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
网络测试端
以下是网络端,我们在这里进行本地模拟即可。Flask部分可以查看有关文档,仅需要一些基本功能即可。需要postman或者curl进行查看。
app = Flask(__name__) # 初始化结点
node_identifier = str(uuid4()).replace('-', '') # 为每一个结点生成唯一的编号
blockchain = BlockChains() # 初始化区块链
@app.route('/mine', methods=['GET'])
def mine():
last_block = blockchain.last_block
last_proof = last_block['proof']
proof = blockchain.proof_of_work(last_proof)
# 给工作量证明的结点提供奖励,"0"表示新挖出的比特币
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
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'Missing values', 400
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
s = str(index)
response = {'message': 'Transaction will be added to Block' + s}
return jsonify(response), 201
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
测试结果
我是用了postman客户端,大家根据需要自己调整即可。
程序运行:
挖矿:
查看区块链:
进行交易: