这篇接着上一篇,在构建好区块链结构之后,怎么建立一个真正运行起来的区块节点和网络。
我们使用Python Flask框架,方便接收http请求,模拟不同节点之间的通信等。
我们将创建五个接口:
/transactions/new: 请求创建一个交易并添加到区块
/mine: 告诉服务器去挖掘新的区块,在实际的比特币系统中,挖矿节点时时刻刻都在挖矿,但是由于我们设置为没有难度,可以手工触发一下使其挖矿。
/chain: 返回整个区块链信息
/nodes/find_neighbors: 发现邻居节点
/nodes/resolve_conflicts: 使用最长链的共识解决冲突
这部分的代码在另一个python文件node.py中实现。
主要实现三个函数,框架如下:
# -*- coding: utf-8 -*-
import time
from uuid import uuid4
from flask import Flask, request, jsonify
import block
import sys
import requests
app = Flask(__name__)
node_id = str(uuid4()).replace('-', '')
need_genesis = True if sys.argv[1] == "True" else False
blockchain = block.Blockchain(node_id, need_genesis)
@app.route("/mine", methods=["GET"])
def mine():
pass
@app.route("/transactions/new", methods=["POST"])
def new_transaction():
pass
@app.route('/chain', methods=['GET'])
def full_chain():
pass
接收的参数是发送者地址、接受者地址和金额,代码为:
@app.route("/transactions/new", methods=["POST"])
def new_transaction():
values = request.values
required = ["sender", "recipient", "amount"]
if values is None or (not all(k in values for k in required)):
return "Missing values", 400
msg = blockchain.new_transaction(values["sender"], values["recipient"], values["amount"])
response = {"message": msg}
return jsonify(response), 201
这个很简单,主要是利用了上一篇中的交易函数。
@app.route("/mine", methods=["GET"])
def mine():
values = request.values
miner = values["miner"] if "miner" in values else node_id
blockchain.mine_block('f' * 64, miner)
response = {"message": "mine success"}
return jsonify(response), 200
这个也是直接调用了block里面的函数
这个是为了我们方便查看区块链的变化。
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
"chain": blockchain.chain,
"length": len(blockchain.chain),
"in_chain_transactions": blockchain.in_chain_transactions,
"not_in_chain_transactions": blockchain.not_in_chain_transactions,
"utxo": blockchain.utxo,
"neighbors": str(blockchain.neighbors),
}
return jsonify(response), 200
其中neighbors只是的邻居节点。接下来会讲。
@app.route('/nodes/find_neighbors', methods=['POST'])
def find_neighbors():
# there is a host list
host_list = [
"10.0.243.101:5000",
"10.0.243.101:5001",
"10.0.243.101:5002",
"10.0.243.101:5003",
]
for h in host_list:
if h == request.host:
continue
url = "http://%s/nodes/response_neighbors" % (h)
try:
r = requests.get(url, params={"node_id": node_id, "port": request.host.split(':')[1]})
if r.status_code != 200:
continue
r_json = r.json()
blockchain.neighbors.add((r_json["remote_host"], r_json["remote_node_id"]))
except:
pass
response = {
"neighbors": str(blockchain.neighbors),
}
return jsonify(response), 200
新节点加入到区块链网络中的时候,一般开始会有一些种子节点,连接到种子节点之后,种子节点再将这个节点广播出去。这里讲一个端口当做一个节点,所以将500X开头的端口视为一定会扫描的节点,这里只列出了4个端口。
相应的,节点本身肯定得提供响应新节点发现的请求。也就是/nodes/response_neighbors这部分。
@app.route('/nodes/response_neighbors', methods=['GET'])
def response_neighbors():
# client_port = str(request.environ.get("REMOTE_PORT"))
client_port = request.values["port"]
client_host = "%s:%s" % (request.remote_addr, client_port)
client_node_id = request.values["node_id"]
response = {
# "client_host": client_host,
# "client_node_id": client_node_id,
"remote_host": request.host,
"remote_node_id": node_id,
}
if client_port[0] == '5' and len(client_port) == 4:
blockchain.neighbors.add((client_host, client_node_id))
return jsonify(response), 200
比特币使用的是最长链原则,也就是说当前最长的链是正确的主链。发现主链之后,如果自己的链不是主链则需要替换更新,这里为了简化,就是直接将主链的所有信息(除了邻居节点)都同步过来,也就是全量更新,但是实际中是增量更新。
@app.route('/nodes/resolve_conflicts', methods=['GET'])
def resolve_conflicts():
replaced = blockchain.resolve_conflicts()
if replaced:
response = {
'message': 'Our chain was replaced',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'Our chain is main chain',
'chain': blockchain.chain
}
return jsonify(response), 200
python node.py True 5000
在节点1上运行 /chain。True代表的是第一个节点,会挖掘创世区块,5000是端口号。
现在使用restlet client可以查看目前区块里的结果
2. 接着,再起节点2。
python node.py False 5001
节点2上运行 /chain。信息如下:第二个节点里面的区块,是没有创世区块的。
接着,不管是在哪个节点上,发起寻找邻居节点的请求。比如在节点1运行:/nodes/find_neighbors。
这个时候,再次查看节点1的区块信息:
节点2的区块信息:
可以看到,两个节点相互都发现了对方。
3. 在节点1上发起一个交易,并挖矿,
发起的请求依次为:
/transactions/new?recipient=f6704ea73bf14fdcb5538a6c9322f0c0&sender=4297526e78074df3a784140e0651c125&amount=10
/mine
得到以下信息:
4. 目前节点2和节点1的区块链信息不同,进行共识,在节点2上运行 /nodes/resolve_conflicts
可以看到,节点2中的区块已经被替换了,这样就解决了冲突。
这部分的完整代码为:
# -*- coding: utf-8 -*-
import time
from uuid import uuid4
from flask import Flask, request, jsonify
import block
import sys
import requests
# Instantiate our Node
app = Flask(__name__)
# Generate a globally unique address for this node
node_id = str(uuid4()).replace('-', '')
# Instantiate the Blockchain
need_genesis = True if sys.argv[1] == "True" else False
blockchain = block.Blockchain(node_id, need_genesis)
@app.route("/mine", methods=["GET"])
def mine():
values = request.values
miner = values["miner"] if "miner" in values else node_id
blockchain.mine_block('f' * 64, miner)
response = {"message": "mine success"}
return jsonify(response), 200
@app.route("/transactions/new", methods=["POST"])
def new_transaction():
values = request.values
required = ["sender", "recipient", "amount"]
if values is None or (not all(k in values for k in required)):
return "Missing values", 400
msg = blockchain.new_transaction(values["sender"], values["recipient"], values["amount"])
response = {"message": msg}
return jsonify(response), 201
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
"chain": blockchain.chain,
"length": len(blockchain.chain),
"in_chain_transactions": blockchain.in_chain_transactions,
"not_in_chain_transactions": blockchain.not_in_chain_transactions,
"utxo": blockchain.utxo,
"neighbors": str(blockchain.neighbors),
}
return jsonify(response), 200
@app.route('/nodes/response_neighbors', methods=['GET'])
def response_neighbors():
# client_port = str(request.environ.get("REMOTE_PORT"))
client_port = request.values["port"]
client_host = "%s:%s" % (request.remote_addr, client_port)
client_node_id = request.values["node_id"]
response = {
# "client_host": client_host,
# "client_node_id": client_node_id,
"remote_host": request.host,
"remote_node_id": node_id,
}
if client_port[0] == '5' and len(client_port) == 4:
blockchain.neighbors.add((client_host, client_node_id))
return jsonify(response), 200
@app.route('/nodes/find_neighbors', methods=['POST'])
def find_neighbors():
# there is a host list
host_list = [
"10.0.243.101:5000",
"10.0.243.101:5001",
"10.0.243.101:5002",
"10.0.243.101:5003",
]
for h in host_list:
if h == request.host:
continue
url = "http://%s/nodes/response_neighbors" % (h)
try:
r = requests.get(url, params={"node_id": node_id, "port": request.host.split(':')[1]})
if r.status_code != 200:
continue
r_json = r.json()
blockchain.neighbors.add((r_json["remote_host"], r_json["remote_node_id"]))
except:
pass
response = {
"neighbors": str(blockchain.neighbors),
}
return jsonify(response), 200
@app.route('/nodes/resolve_conflicts', methods=['GET'])
def resolve_conflicts():
replaced = blockchain.resolve_conflicts()
if replaced:
response = {
'message': 'Our chain was replaced',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'Our chain is main chain',
'chain': blockchain.chain
}
return jsonify(response), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=sys.argv[2])
可以下载下来,自己修改代码,完善代码,实现属于自己的区块链~