python从零开始实现区块链(区块链数据结构、挖矿、交易、分布性一致性解决方案源码)
参考链接:https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
参考链接:https://xiaozhuanlan.com/topic/1547608293
1区块链数据结构(BlockChain.py)
一个区块主要结构如下:
block = {
'index' : len(self.chain) + 1,
'timestamp' : time(),
'transactions' : self.currentTransaction,
'proof' : proof,
'previousHash' : previousHash or self.hash(self.chain[-1])
}
具体说也就是当前区块的索引。
创建区块时间戳
区块里面包含的交易
知识证明
前一个区块的hash值
1.1 区块链初始化:
def __init__(self):
self.chain = []
self.currentTransaction = []
#Create the genesis block
self.newBlock(proof=100, previousHash=1)
有一个区块链的数组
以及需要区块链里面的交易的数组
一个一个创世区块
1.2. 创建新区块
def newBlock(self, proof, previousHash = None):
# Creates a new Block and adds it to the chain
"""
生成新块
:param proof: <int> The proof given by the Proof of Work algorithm
:param previous_hash: (Optional) <str> Hash of previous Block
:return: <dict> New Block
"""
block = {
'index' : len(self.chain) + 1,
'timestamp' : time(),
'transactions' : self.currentTransaction,
'proof' : proof,
'previousHash' : previousHash or self.hash(self.chain[-1])
}
# Reset the current list of transactions
self.currentTransaction = []
self.chain.append(block)
return block
前面的交易需要加入到新生成的区块中
因此新生成的区块包含前面的交易,生成之后,当前交易数组清空,最后返回当前区块
1.3. 创建新的交易
def newTransaction(self, sender, recipient, amount):
# Adds a new transaction to the list of transactions
"""
生成新交易信息,信息将加入到下一个待挖的区块中
:param sender: <str> Address of the Sender
:param recipient: <str> Address of the Recipient
:param amount: <int> Amount
:return: <int> The index of the Block that will hold this transaction
"""
self.currentTransaction.append({
'sender' : sender,
'recipient' : recipient,
'amount' : amount,
})
#下一个待挖的区块中
return self.lastBlock['index'] + 1
交易有三个属性
- 发送方
- 接收方
- 数量
返回值的意思是,需要将交易记录在下一个区块中
1.4. 区块hash
@staticmethod
def hash(block):
# Hashes a Block
"""
生成块的 SHA-256 hash值
:param block: <dict> Block
:return: <str>
转化为json编码格式之后hash,最后以16进制的形式输出
"""
# We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
blockString = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(blockString).hexdigest()
使用python装饰器语法糖将hash方法设置为静态方法
将block的字符串通过SHA-256生成hash值,然后转变为16进制的数字返回
1.5. get最后区块方法
@property
def lastBlock(self):
# Returns the last Block in the chain
return self.chain[-1]
1.6. 工作量证明
新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。
为了方便理解,举个例子:
假设一个整数 x 乘以另一个整数 y 的积的 Hash 值必须以 0 结尾,即 hash(x * y) = ac23dc…0。设变量 x = 5,求 y 的值?
让我们来实现一个相似PoW算法,规则是:寻找一个数 p,使得它与前一个区块的 proof 拼接成的字符串的 Hash 值以 4 个零开头。
@staticmethod
def validProof(lastProof, proof):
"""
验证证明: 是否hash(last_proof, proof)以4个0开头?
:param last_proof: <int> Previous Proof
:param proof: <int> Current Proof
:return: <bool> True if correct, False if not.
"""
guess = f'{lastProof}{proof}'.encode()
guessHash = hashlib.sha256(guess).hexdigest()
return guessHash[:4] == '0000'
def proofOfWork(self, lastProof):
"""
简单的工作量证明:
- 查找一个 p' 使得 hash(pp') 以4个0开头
- p 是上一个块的证明, p' 是当前的证明
:param last_proof: <int>
:return: <int>
"""
proof = 0
while self.validProof(lastProof, proof) is False:
proof += 1
return proof
逻辑是:当一台机器算出新的proof值之后,才能够创建新的区块
2.使用Flask框架来简历区块服务器(FlaskServer.py)
2.1. 准备工作
import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4
from flask import Flask, jsonify, request
from BlockChain import Blockchain
# Instantiate our Node
app = Flask(__name__)
# Generate a globally unique address for this node
# 基于随机数生成唯一的id
nodeIdentifier = str(uuid4()).replace('-', '')
# Instantiate the BlockChain
blockchain = Blockchain()
2.2. 挖矿实现
#创建/mine GET接口
@app.route('/mine', methods=['GET'])
def mine():
# 1.计算工作量证明PoW
# 2.通过新增一个交易授予矿工(自己)一个币
# 3.构造新区块并且添加到链中
# We run the proof of work algorithm to get the next proof...
lastBlock = blockchain.lastBlock
lastProof = lastBlock['proof']
proof = blockchain.proofOfWork(lastBlock)
# 给工作量证明的节点提供奖励.
# 发送者为 "0" 表明是新挖出的币
blockchain.newTransaction(
sender='0',
recipient=nodeIdentifier,
amount=1
)
#加入到链中
block = blockchain.newBlock(proof)
response = {
'message' : "New Block Forged",
'index' : block['index'],
'transactions' : block['transactions'],
'proof' : block['proof'],
'previousHash' : block['previousHash'],
}
return jsonify(response), 200
先获取到上一个区块的工作量证明,然后算出新的工作量证明
区块的第一个交易为:
自己收到的一个比特币的奖励,假设发送方为:‘0’
如果成功,那么返回200ok,并且返回新区块的json
2.3. 新的交易
交易是放在区块上的,并且会放在下一个区块上,随着下一个区块的生成而被记录。
#创建/transactions/new POST接口,可以给接口发送交易数据
@app.route('/transactions/new', methods=['POST'])
def newTransaction():
values = request.get_json()
# Check that the required fields are in the POST'ed data
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'Missing values', 400
# Create a new Transaction
index = blockchain.newTransaction(values['sender'], values['recipient'], values['amount'])
response = {'message': f'Transaction will be added to Block {index}'}
return jsonify(response), 201
首先获得post请求的json值
然后解析该json,如果没有sender…这些数据返回400,表示客户端错误
之后产生新的交易,返回新的交易将要放在的区块的索引。
2.4. 打印整个区块
#创建/chain接口, 返回整个区块链。
@app.route('/chain', methods=['GET'])
def fullChain():
response = {
'chain' : blockchain.chain,
'length' : len(blockchain.chain),
}
return jsonify(response), 200
2.5. 运行整个服务
if __name__ == '__main__':
#服务运行在端口5000上.
app.run(host='127.0.0.1', port=5000)
3.区块链测试
使用postman进行测试,可以自行下载
3.1 运行
使用本地的IDE可以直接运行
需要注意的是,当更改代码之后,需要重新运行
运行之后,使用get请求: http://localhost:5000/chain
可以看到:
只有一个区块(创世区块),里面是没有交易的。
3.2 创建交易
使用post请求:http://localhost:5000/transactions/new
post请求json格式有很多种方法
一种是直接使用代码的形式:
表明交易的发送方,接收方,交易的金额
需要注意的是:一定要转化为json格式
还可以在headers里面自行添加:
可以看到交易成功,返回201,表示更新了服务器数据
并且说这个交易即将被更新到区块2上面
3.3 创建区块
发送get请求到:http://localhost:5000/mine
可以看到,区块的第一个交易是创建这个区块的时候挖矿得到的金钱(比特币)
第二个交易就是刚我们使用post请求产生的交易
这个时候我们再查看整个区块:
可以看到所有的交易都被保存下来了。
当2号区块创建之后,之后所有的交易就会保存在将来被挖出来的第三个区块上面啦