簡単なブロックチェーンをつくる(その10 ブロックチェーンの検証)

 最後にブロックチェーンの検証のコードを加えて,一応の完成とする.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]))

出力は前回と同じなので省略.

 ビットコインブロックチェーンに近づけるには署名,アドレス,マークル木,スクリプト等,やるべきことは多いがここでとりあえず終了にしたい.