最後にブロックチェーンの検証のコードを加えて,一応の完成とする.verify_blockchain関数は各ブロックのハッシュ値を再計算し,ブロックに記録されいているものと一致するか調べる.この関数はsync関数のなかでブロックチェーンをコピーするときに使っている.
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 and verify_blockchain(chain_body): 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() # 各ブロックのハッシュを再計算して改ざんされていないかをチェック def verify_blockchain(blockchain): last_block = blockchain[0] # ジェネシスブロック index = 1 # 次のブロック高 while index < len(blockchain): block = blockchain[index] # ブロックのハッシュを再計算し,prev_hash_blockと一致しているかチェック last_block_hash = hashlib.sha256(json.dumps(last_block, sort_keys=True).encode()).hexdigest() if block['prev_hash_block'] != last_block_hash: return False last_block = block index += 1 return True # ブロックチェーンを返す @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]))
出力は前回と同じなので省略.
ビットコインのブロックチェーンに近づけるには署名,アドレス,マークル木,スクリプト等,やるべきことは多いがここでとりあえず終了にしたい.