300行代码实现一个简化版比特币系统(二)

这篇接着上一篇,在构建好区块链结构之后,怎么建立一个真正运行起来的区块节点和网络。
我们使用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


1. 好了,现在可以自己运行区块链试试了。比如先启用节点1。七、运行区块链

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])


可以下载下来,自己修改代码,完善代码,实现属于自己的区块链~

免责声明:信息仅供参考,不构成投资及交易建议。投资者据此操作,风险自担。
如果觉得文章对你有用,请随意赞赏收藏
其大力 1人赞赏收藏
相关推荐
相关下载
登录后评论
Copyright © 2019 宽客在线