参考链接:https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
参考链接:https://xiaozhuanlan.com/topic/1547608293
一个区块主要结构如下:
block = {
'index' : len(self.chain) + 1,
'timestamp' : time(),
'transactions' : self.currentTransaction,
'proof' : proof,
'previousHash' : previousHash or self.hash(self.chain[-1])
}
具体说也就是当前区块的索引。
创建区块时间戳
区块里面包含的交易
知识证明
前一个区块的hash值
def __init__(self):
self.chain = []
self.currentTransaction = []
#Create the genesis block
self.newBlock(proof=100, previousHash=1)
有一个区块链的数组
以及需要区块链里面的交易的数组
一个一个创世区块
def newBlock(self, proof, previousHash = None):
# Creates a new Block and adds it to the chain
"""
生成新块
:param proof: The proof given by the Proof of Work algorithm
:param previous_hash: (Optional) Hash of previous Block
:return: 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
前面的交易需要加入到新生成的区块中
因此新生成的区块包含前面的交易,生成之后,当前交易数组清空,最后返回当前区块
def newTransaction(self, sender, recipient, amount):
# Adds a new transaction to the list of transactions
"""
生成新交易信息,信息将加入到下一个待挖的区块中
:param sender: Address of the Sender
:param recipient: Address of the Recipient
:param amount: Amount
:return: The index of the Block that will hold this transaction
"""
self.currentTransaction.append({
'sender' : sender,
'recipient' : recipient,
'amount' : amount,
})
#下一个待挖的区块中
return self.lastBlock['index'] + 1
交易有三个属性
返回值的意思是,需要将交易记录在下一个区块中
@staticmethod
def hash(block):
# Hashes a Block
"""
生成块的 SHA-256 hash值
:param block: Block
:return:
转化为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进制的数字返回
@property
def lastBlock(self):
# Returns the last Block in the chain
return self.chain[-1]
新的区块依赖工作量证明算法(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: Previous Proof
:param proof: Current Proof
:return: 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:
:return:
"""
proof = 0
while self.validProof(lastProof, proof) is False:
proof += 1
return proof
逻辑是:当一台机器算出新的proof值之后,才能够创建新的区块
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()
#创建/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(lastProof)
# 给工作量证明的节点提供奖励.
# 发送者为 "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
交易是放在区块上的,并且会放在下一个区块上,随着下一个区块的生成而被记录。
#创建/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,表示客户端错误
之后产生新的交易,返回新的交易将要放在的区块的索引。
#创建/chain接口, 返回整个区块链。
@app.route('/chain', methods=['GET'])
def fullChain():
response = {
'chain' : blockchain.chain,
'length' : len(blockchain.chain),
}
return jsonify(response), 200
if __name__ == '__main__':
#服务运行在端口5000上.
app.run(host='127.0.0.1', port=5000)
使用本地的IDE可以直接运行
需要注意的是,当更改代码之后,需要重新运行
运行之后,使用get请求: http://localhost:5000/chain
可以看到:
只有一个区块(创世区块),里面是没有交易的。
使用post请求:http://localhost:5000/transactions/new
post请求json格式有很多种方法
一种是直接使用代码的形式:
表明交易的发送方,接收方,交易的金额
需要注意的是:一定要转化为json格式
还可以在headers里面自行添加:
可以看到交易成功,返回201,表示更新了服务器数据
并且说这个交易即将被更新到区块2上面
发送get请求到:http://localhost:5000/mine
可以看到,区块的第一个交易是创建这个区块的时候挖矿得到的金钱(比特币)
第二个交易就是刚我们使用post请求产生的交易
这个时候我们再查看整个区块:
可以看到所有的交易都被保存下来了。
当2号区块创建之后,之后所有的交易就会保存在将来被挖出来的第三个区块上面啦
为了验证区块链的有效性,从两个方面:
def validChain(self, chain):
"""
Determine if a given blockchain is valid
:param chain: A blockchain
:return: True if valid, False if not
"""
previousBlock = chain[0]
currentIndex = 1
while currentIndex < len(chain):
block = chain[currentIndex]
print(f'{previousBlock}')
print(f'{block}')
print("\n-----------\n")
# Check that the hash of the block is correct
if block['previousHash'] != self.hash(previousBlock):
return False
# Check that the Proof of Work is correct
if not self.validProof(previousBlock['proof'], block['proof']):
return False
previousBlock = block
currentIndex += 1
return True
冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。
def resolveConflicts(self):
"""
共识算法解决冲突
使用网络中最长的链.
:return: True 如果链被取代, 否则为False
"""
neighbours = self.nodes
newChain = None
# We're only looking for chains longer than ours
maxLength = len(self.chain)
# Grab and verify the chains from all the nodes in our network
for node in neighbours:
response = requests.get(f'http://{node}/chain')
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
# Check if the length is longer and the chain is valid
if length > maxLength and self.validChain(chain):
maxLength = length
newChain = chain
if newChain:
self.chain = newChain
return True
return False
添加节点解决冲突的路由:
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
replaced = blockchain.resolveConflicts()
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), 2
首先对节点进行注册。注册的代码如下:
def __init__(self):
self.chain = []
self.currentTransaction = []
self.nodes = set()
#Create the genesis block
self.newBlock(proof=100, previousHash=1)
def registerNode(self, address):
"""
Add a new node to the list of nodes
:param address: Address of node. Eg. 'http://192.168.0.5:5000'
:return: None
"""
parsedUrl = urlparse(address)
self.nodes.add(parsedUrl.netloc)
添加节点注册的路由:
@app.route('/nodes/register', methods=['POST'])
def registerNodes():
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.registerNode(node)
response = {
'message': 'New nodes have been added',
'totalNodes': list(blockchain.nodes),
}
return jsonify(response), 201
使用不同的端口号运行不同的区块链
有一个长链5001
有一个短链5000
短链注册:
解决冲突:
长链没有变化:
短链被替换: