各ノードのブロックチェーンは /block により別々にブロックが生成される.そこで同期をとる時点で一番長いブロックをもつブロックチェーンを「真の」ブロックチェーンをみなすことにする.その他のノードのブロックチェーンは一番長いブロックチェーンに置き換える.
import sys, hashlib, json import requests from time import time from flask import Flask, jsonify, request from urllib.parse import urlparse app = Flask(__name__) class Blockchain(): # 別のノードのアドレスのリスト nodes = [] # ハッシュ値の上4文字が0000となるノンスを見つける difficulty = 4 target = '0000' def __init__(self): # ブロックチェーン本体 self.blockchain = [] # 1ブロックのトランザクションリスト self.tx_list = [] # ジェネシスブロックの"前ブロック"のハッシュ値 self.prev_block_hash = hashlib.sha256("genesis".encode()).hexdigest() # ジェネシスブロック block = { 'index': 0, 'timestamp': time(), # コインベーストランザクション: ノード所有者に1000コインを送付 'tx_list': [{'sender': '0', 'recipent': node_owner, 'amount': 1000}], 'nonce': 0, 'prev_block_hash': self.prev_block_hash } # ジェネシスブロックのノンスを計算 block['nonce'] = self.proof_of_work(block) # ジェネシスブロックをブロックチェーンに追加 self.blockchain.append(block) # プルーフオブワーク # ターゲットの条件を満たすハッシュ値を与えるノンスを見つける def proof_of_work(self, block): while True: hash = hashlib.sha256(json.dumps(block, sort_keys=True).encode()).hexdigest() if hash[:self.difficulty] == self.target: # 見つけたブロックのハッシュ値(次のブロックに必要) self.prev_block_hash = hash break block['nonce'] += 1 return block['nonce'] # 他のノードとブロックチェーンを比較 # 最も長いブロックチェーンに合わせる def sync(self): new_blockchian = None max_length = len(self.blockchain) for node in self.nodes: k = requests.get(f'http://{node}/blockchain') if k.status_code == 200: chain_body = k.json()['blockchain'] length = len(chain_body) if length > max_length: max_length = length new_blockchian = chain_body if new_blockchian: self.blockchain = new_blockchian return True return False # ノードの所有者 node_owner = sys.argv[2] print("node owner=", node_owner) # ブロックチェーンインスタンス bc = Blockchain() # ブロックチェーンを返す @app.route("/blockchain", methods=['GET']) def get_blockchain(): responce = {'blockchain': bc.blockchain} return jsonify(responce), 200 # ノード間のブロックチェーンの同期 @app.route("/nodes/sync", methods=['GET']) def node_sync(): updated = bc.sync() if updated: responce = { 'message': 'updated', 'blockchain': bc.blockchain } else: responce = { 'message': 'already latest', 'blockchain': bc.blockchain } return jsonify(responce), 200 # マイニングによるブロック生成 @app.route("/mine", methods=["GET"]) def mine(): # マイナー報酬のコインベーストランザクションをトランザクションリスト # の先頭に追加 # 受領者はnode_owner global node_owner tx = { 'sender': '0', 'recipient': node_owner, 'amount': 1000 } bc.tx_list.insert(0, tx) # マイニング block = { 'index': len(bc.blockchain), 'tiemstamp': time(), 'tx_list': bc.tx_list, 'nonce': 0, 'prev_block_hash': bc.prev_block_hash } block['nonce'] = bc.proof_of_work(block) # 次のブロック用に現在のブロックのハッシュを計算しておく bc.prev_block_hash = hashlib.sha256(json.dumps(block, sort_keys=True).encode()).hexdigest() # トランザクションリストをブロックに入れたのでクリア bc.tx_list = [] # ブロックチェーンにブロックを追加 bc.blockchain.append(block) return jsonify(bc.blockchain), 200 # トランザクション生成 @app.route("/tx", methods=['POST']) def tx(): tx = request.get_json() fields = ['sender', 'recipient', 'amount'] if (not all(k in tx for k in fields)) or (tx['amount'] <= 0): message = 'Input Error' status_code = 400 else: bc.tx_list.append(tx) message ='Tx added' status_code = 201 print(bc.tx_list) responce = {'message': message} return jsonify(responce), status_code # 他のノードの存在を伝える @app.route('/nodes/add', methods=['POST']) def add_nodes(): values = request.get_json() address = values.get('nodes') if address is None: responce = { 'message': 'Missing node' } return jsonify(responce), 400 for k in address: url = urlparse(k) bc.nodes.append(url.netloc) responce = { 'message': 'New nodes added', 'nodes': bc.nodes } return jsonify(responce), 201 # host: localhost if __name__ == "__main__": app.run(port=int(sys.argv[1]))
複数ノードでプログラムを動かし,/node/add でそれぞれ別ノードの存在を伝えておく.同期を取りたいときは /node/sync を実行する.sync関数が各ブロックチェーンのブロックの長さを比較する関数で,一番長いものを当該ノードのブロックチェーンにコピーする.まるごとコピーするのでブロックチェーンが長くなると非効率になるが,とりあえずはよしとする.別ノードのブロックチェーンをもってくる,/blockchain も新たに設けた.
実行例として,2つのノードを立ち上げる.
> python hoge.py 5000 Sato > python hoge.py 5001 Yamada
ノードの存在を伝え,
> curl -X POST -H "Content-Type: application/json" -d "{\"nodes\": [\"http://localhost:5001\"]}" "http://localhost:5000/nodes/add" | jq > curl -X POST -H "Content-Type: application/json" -d "{\"nodes\": [\"http://localhost:5000\"]}" "http://localhost:5001/nodes/add" | jq
ポート5000のノードはトランザクションを1回行い,マイニングも1回行う.
> curl -X POST -H "Content-Type: application/json" -d "{\"sender\": \"Sato\", \"recipient\": \"Watanabe\", \"amount\": 123}" "http://localhost:5000/tx" | jq > curl http://localhost:5000/mine | jq
この時点は5000のブロックチェーンの長さは2,5001の長さは1である.
> curl http://localhost:5000/blockchain |jq { "blockchain": [ { "index": 0, "nonce": 74845, "prev_block_hash": "aeebad4a796fcc2e15dc4c6061b45ed9b373f26adfc798ca7d2d8cc58182718e", "timestamp": 1624965321.6862228, "tx_list": [ { "amount": 1000, "recipent": "Sato", "sender": "0" } ] }, { "index": 1, "nonce": 79312, "prev_block_hash": "00007b46484e80d57f9ad5b4850f2b836d9a3ea89ddf233cd119ed9f95c21d28", "tiemstamp": 1624965354.314841, "tx_list": [ { "amount": 1000, "recipient": "Sato", "sender": "0" }, { "amount": 123, "recipient": "Watanabe", "sender": "Sato" } ] } ] } > curl http://localhost:5001/blockchain |jq { "blockchain": [ { "index": 0, "nonce": 13833, "prev_block_hash": "aeebad4a796fcc2e15dc4c6061b45ed9b373f26adfc798ca7d2d8cc58182718e", "timestamp": 1624965323.9136956, "tx_list": [ { "amount": 1000, "recipent": "Yamada", "sender": "0" } ] } ] }
ここで5000の上で同期を取ると
> curl http://localhost:5000/nodes/sync |jq { "blockchain": [ { "index": 0, "nonce": 74845, "prev_block_hash": "aeebad4a796fcc2e15dc4c6061b45ed9b373f26adfc798ca7d2d8cc58182718e", "timestamp": 1624965321.6862228, "tx_list": [ { "amount": 1000, "recipent": "Sato", "sender": "0" } ] }, { "index": 1, "nonce": 79312, "prev_block_hash": "00007b46484e80d57f9ad5b4850f2b836d9a3ea89ddf233cd119ed9f95c21d28", "tiemstamp": 1624965354.314841, "tx_list": [ { "amount": 1000, "recipient": "Sato", "sender": "0" }, { "amount": 123, "recipient": "Watanabe", "sender": "Sato" } ] } ], "message": "already latest" }
となり,何も起こらない.5001の上で同期を取ると
> curl http://localhost:5001/nodes/sync |jq { "blockchain": [ { "index": 0, "nonce": 74845, "prev_block_hash": "aeebad4a796fcc2e15dc4c6061b45ed9b373f26adfc798ca7d2d8cc58182718e", "timestamp": 1624965321.6862228, "tx_list": [ { "amount": 1000, "recipent": "Sato", "sender": "0" } ] }, { "index": 1, "nonce": 79312, "prev_block_hash": "00007b46484e80d57f9ad5b4850f2b836d9a3ea89ddf233cd119ed9f95c21d28", "tiemstamp": 1624965354.314841, "tx_list": [ { "amount": 1000, "recipient": "Sato", "sender": "0" }, { "amount": 123, "recipient": "Watanabe", "sender": "Sato" } ] } ], "message": "updated" }
となり,ブロックチェーンが更新される.