You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
7.7 KiB
Python
280 lines
7.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
import hashlib
|
|
import json
|
|
from argparse import ArgumentParser
|
|
from time import time
|
|
from urllib.parse import urlparse
|
|
from uuid import uuid4
|
|
|
|
import requests
|
|
from flask import Flask, jsonify, request
|
|
|
|
|
|
# 区块的结构
|
|
# {
|
|
# # 序号
|
|
# "index": 0,
|
|
# # 时间戳
|
|
# "timestamp": "",
|
|
# # 交易信息
|
|
# "transactions": [
|
|
# {
|
|
# # 交易的发送者
|
|
# "sender": "",
|
|
# # 交易的接收者
|
|
# "recipient": "",
|
|
# # 交易的金额
|
|
# "amount": 5
|
|
# }
|
|
#
|
|
# ],
|
|
# # 工作量证明
|
|
# "proof": "",
|
|
# # 上一个区块的 Hash值
|
|
# "previous_hash": ""
|
|
# }
|
|
|
|
class Blockchain:
|
|
|
|
# 初始化
|
|
def __init__(self):
|
|
self.chain = []
|
|
self.current_transactions = []
|
|
# 保存节点的信息
|
|
self.nodes = set()
|
|
|
|
# 创建一个创世纪的区块定义
|
|
self.new_block(proof=100, previous_hash=1)
|
|
|
|
# 验证链为有效链 -> Hash值和工作量证明是否有效
|
|
def valid_chain(self, chain) -> bool:
|
|
last_block = chain[0]
|
|
current_index = 1
|
|
|
|
while current_index < len(chain):
|
|
block = chain[current_index]
|
|
|
|
# Hash 不一致, 则校验不通过, 是虚假的信息
|
|
if block['previous_hash'] != self.hash(last_block):
|
|
return False
|
|
|
|
# 工作量证明可能不满足, 不满足 "0000"开头的
|
|
if not self.valid_proof(last_block['proof'], block['proof']):
|
|
return False
|
|
|
|
last_block = block
|
|
current_index += 1
|
|
return True
|
|
|
|
# 共识机制 解决多个节点的冲突问题
|
|
def resolve_conflicts(self) -> bool:
|
|
# 使用当前链的长度和附近节点的链的长度进行对比, 选择最长的链进行继续工作
|
|
neighbours = self.nodes
|
|
max_length = len(self.chain)
|
|
new_chain = None
|
|
|
|
for node in neighbours:
|
|
response = requests.get(f'http://{node}/chain')
|
|
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
|
|
|
|
# 注册一个节点
|
|
def register_node(self, address: str):
|
|
# 解析节点信息 127.0.0.1:5001, 保存到 nodes 里面
|
|
parsed_url = urlparse(address)
|
|
self.nodes.add(parsed_url.netloc)
|
|
|
|
# 创建新的区块
|
|
def new_block(self, proof, previous_hash=None):
|
|
# 创建区块, 把交易信息加入区块
|
|
block = {
|
|
'index': len(self.chain) + 1,
|
|
'timestamp': time(),
|
|
'transactions': self.current_transactions,
|
|
'proof': proof,
|
|
'previous_hash': previous_hash or self.hash(self.last_block)
|
|
}
|
|
|
|
# 把交易信息清空
|
|
self.current_transactions = []
|
|
# 把区块加入到链中
|
|
self.chain.append(block)
|
|
|
|
return block
|
|
|
|
# 新添加一个交易
|
|
def new_transaction(self, sender, recipient, amount) -> int:
|
|
# 把创建的交易信息作为一个交易记录[json], 放在交易列表里
|
|
self.current_transactions.append(
|
|
{
|
|
'sender': sender,
|
|
'recipient': recipient,
|
|
'amount': amount
|
|
}
|
|
)
|
|
|
|
# 返回区块的索引
|
|
return self.last_block['index'] + 1
|
|
|
|
# (静态) 计算 Hash
|
|
@staticmethod
|
|
def hash(block):
|
|
# json 排序转成 字符串, 然后进行编码
|
|
block_str = json.dumps(block, sort_keys=True).encode()
|
|
|
|
# 使用 hash 函数进行 hash, 并返回摘要信息
|
|
return hashlib.sha256(block_str).hexdigest()
|
|
|
|
# (属性) 获取最后一个区块
|
|
@property
|
|
def last_block(self):
|
|
return self.chain[-1]
|
|
|
|
# 工作量证明
|
|
def proof_of_work(self, last_proof: int) -> int:
|
|
proof = 0
|
|
while self.valid_proof(last_proof, proof) is False:
|
|
proof += 1
|
|
|
|
# print(proof)
|
|
return proof
|
|
|
|
# 验证合规的工作量 比如以几个 0 开头, 这里先定义 4个0
|
|
@staticmethod
|
|
def valid_proof(last_proof: int, proof: int) -> bool:
|
|
# 字符串拼接并进行编码 -> 构建 guess
|
|
guess = f'{last_proof}{proof}'.encode()
|
|
# 获取 guess 的 hash 值
|
|
guess_hash = hashlib.sha256(guess).hexdigest()
|
|
# print(guess_hash)
|
|
|
|
return guess_hash[0:4] == "0000"
|
|
|
|
|
|
app = Flask(__name__)
|
|
block_chain = Blockchain()
|
|
|
|
# UUID 来声明当前节点的ID
|
|
node_identifier = str(uuid4()).replace('-', '')
|
|
|
|
|
|
# 交易接口
|
|
@app.route('/transactions/new', methods=['POST'])
|
|
def new_transaction():
|
|
# 拿到请求内容
|
|
values = request.get_json()
|
|
|
|
# 检查请求字段是否完整
|
|
required = ["sender", "recipient", "amount"]
|
|
if values is None:
|
|
return "Missing values", 400
|
|
if not all(k in values for k in required):
|
|
return "Missing values", 400
|
|
|
|
index = block_chain.new_transaction(values['sender'], values['recipient'], values['amount'])
|
|
|
|
response = {"message": f'Transcation will be added to Block {index}'}
|
|
return jsonify(response), 201
|
|
|
|
|
|
# 挖矿接口
|
|
@app.route('/mine', methods=['GET'])
|
|
def mine():
|
|
# 传入上一个工作量证明, 来计算当前的工作量证明
|
|
last_block = block_chain.last_block
|
|
last_proof = last_block['proof']
|
|
proof = block_chain.proof_of_work(last_proof)
|
|
|
|
# 新添加一个交易, 这个交易就是给自己的奖励
|
|
block_chain.new_transaction(sender="0", recipient=node_identifier, amount=1)
|
|
|
|
# 新建一个区块
|
|
block = block_chain.new_block(proof, None)
|
|
|
|
# 封装返回信息
|
|
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('/chain', methods=['GET'])
|
|
def full_chain():
|
|
response = {
|
|
'chain': block_chain.chain,
|
|
'length': len(block_chain.chain)
|
|
}
|
|
return jsonify(response), 200
|
|
|
|
|
|
# 添加一个节点
|
|
@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
|
|
|
|
for node in nodes:
|
|
block_chain.register_node(node)
|
|
|
|
response = {
|
|
"message": "New nodes have bean added",
|
|
"total_nodes": list(block_chain.nodes)
|
|
}
|
|
|
|
return jsonify(response), 201
|
|
|
|
|
|
# 共识机制, 解决冲突的接口
|
|
@app.route('/nodes/resolve', methods=['GET'])
|
|
def consensus():
|
|
replaced = block_chain.resolve_conflicts()
|
|
|
|
if replaced:
|
|
response = {
|
|
'message': 'Our chain was replaced',
|
|
'new_chain': block_chain.chain
|
|
}
|
|
else:
|
|
response = {
|
|
'message': 'Our chain is authoritative',
|
|
'chain': block_chain.chain
|
|
}
|
|
return jsonify(response), 200
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# 测试一下 工作量证明
|
|
# test_Pow = Blockchain()
|
|
# test_Pow.proof_of_work(100)
|
|
|
|
# 单机模式
|
|
# app.run(host='0.0.0.0', port=5000)
|
|
|
|
# 多节点模式 , 通过命令行参数进行实现
|
|
# -p -port
|
|
parser = ArgumentParser()
|
|
parser.add_argument('-p', '-port', default=5000, type=int, help='port to listen to')
|
|
args = parser.parse_args()
|
|
port = args.port
|
|
|
|
app.run(host='0.0.0.0', port=port)
|