单节点 = 数据库。多节点 + 交叉验证 = 区块链。本节用 HTTP API 模拟节点间通信。
16.5.1 节点架构
graph TD
N1[节点 1:3001] --> |广播新交易| N2[节点 2:3002]
N1 --> |广播新块| N3[节点 3:3003]
N2 --> |/resolve| N1
N3 --> |/resolve| N2
style N1 fill:#e3f2fd
style N2 fill:#e8f5e9
style N3 fill:#fff3e0
消息类型
| 消息 | 方法 | 用途 |
|---|---|---|
| 新区块 | POST /blocks/broadcast | 挖矿后广播 |
| 新交易 | POST /transactions/new | 接受广播/用户提交 |
| 请求链 | GET /chain | 获取完整链 |
| 共识 | GET /nodes/resolve | 最长链规则 |
16.5.2 Flask 节点实现
python
# mini_node.py — 极简节点
from flask import Flask, request, jsonify
import requests, time
app = Flask(__name__)
# 每个节点运行独立的区块链实例
blockchain = Blockchain(difficulty=4)
# 已知的对等节点列表
peers = set() # { "http://localhost:3002", ... }
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
data = request.get_json()
tx = blockchain.create_transaction(
data['sender'], data['recipient'], data['amount']
)
# 广播给所有同伴
broadcast_transaction(data)
return jsonify({"message": "交易已添加至 pending", "nonce": tx.nonce}), 201
def broadcast_transaction(tx_data):
for peer in peers:
try:
requests.post(f"{peer}/transactions/new", json=tx_data, timeout=1)
except: pass # 节点离线 — 不影响本次提交
@app.route('/mine', methods=['GET'])
def mine():
miner = request.args.get('address', '0')
block = blockchain.mine_pending_transactions(miner)
broadcast_block(block)
return jsonify({
"message": "已挖出新区块",
"index": block.index,
"hash": block.hash,
"nonce": block.nonce,
})
def broadcast_block(block):
data = {
"index": block.index,
"timestamp": block.timestamp,
"transactions": [tx.to_dict() for tx in block.transactions],
"previous_hash": block.previous_hash,
"nonce": block.nonce,
"hash": block.hash,
"difficulty": block.difficulty,
}
for peer in peers:
try:
requests.post(f"{peer}/blocks/broadcast", json=data, timeout=1)
except: pass
@app.route('/blocks/broadcast', methods=['POST'])
def receive_block():
"""其他节点挖出的新块"""
data = request.get_json()
# 验证后追加
# 简化:信任网络,仅检查 prevHash
new_block = Block(**data)
if new_block.previous_hash == blockchain.chain[-1].hash:
blockchain.chain.append(new_block)
return jsonify({"message": "区块已接收"}), 200
@app.route('/chain', methods=['GET'])
def full_chain():
return jsonify({
"chain": [asdict(b) for b in blockchain.chain],
"length": len(blockchain.chain),
})
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
"""最长链规则:发现更长的有效链时替换"""
replaced = False
for peer in peers:
try:
res = requests.get(f"{peer}/chain", timeout=2).json()
other_chain = res['chain']
if len(other_chain) > len(blockchain.chain):
# 验证整条链
if is_valid_chain(external_chain_to_blocks(other_chain)):
# 回滚本地状态
rebuild_from_chain(other_chain)
replaced = True
except: pass
return jsonify({"replaced": replaced, "length": len(blockchain.chain)})
@app.route('/register', methods=['POST'])
def register_node():
node = request.get_json()['node']
peers.add(node)
return jsonify({"message": f"节点 {node} 已注册", "peers": list(peers)})
# 启动
if __name__ == '__main__':
import sys
port = int(sys.argv[1]) if len(sys.argv) > 1 else 3001
app.run(host='0.0.0.0', port=port, debug=False)16.5.3 三重启动与测试
bash
# 启动 3 个节点
cd chapter16 && python mini_node.py 3001 &
cd chapter16 && python mini_node.py 3002 &
cd chapter16 && python mini_node.py 3003 &
# 互相注册
curl -X POST http://localhost:3001/register -H 'Content-Type: application/json' -d '{"node":"http://localhost:3002"}'
curl -X POST http://localhost:3001/register -H 'Content-Type: application/json' -d '{"node":"http://localhost:3003"}'
# 节点1 挖矿
curl http://localhost:3001/mine?address=Alice
# 节点2 发起交易
curl -X POST http://localhost:3002/transactions/new -H 'Content-Type: application/json' -d '{"sender":"Alice","recipient":"Bob","amount":20}'
# 节点3 发起交易后挖矿
curl -X POST http://localhost:3003/transactions/new ...
curl http://localhost:3003/mine?address=Bob
# 检查所有节点是否同步
curl http://localhost:3001/chain | jq'.length'
curl http://localhost:3002/chain | jq'.length'
curl http://localhost:3003/chain | jq'.length'
# 输出应相同!16.5.4 共识演示:分叉与恢复
sequenceDiagram
N1[节点1] ->> N2: 开始挖矿
N3[节点3] ->> N2: 开始挖矿
N1 ->> N1: 挖出块 #2A
N3 ->> N3: 挖出块 #2B(同时)
N1 ->> N2: 广播 #2A
N3 ->> N2: 广播 #2B
N2 ->> N2: 收到两个 #2,先到达者采纳
N1 ->> N2: 挖出 #3A(基于 #2A)
Note right of N2: #3A 更长,N2 回滚到 #2A
style N1 fill:#e3f2fd
style N3 fill:#fff3e0
, 前往 → 16.6 HTTP API 与区块链浏览器 |*
评论
0评论加载中…