[キャッチアップ]ブロックチェーンを動かす3(Python)

株式会社プライムストラクチャーの若手エンジニアのSayaです。
今回も前回同様、キャッチアップを兼ねてこちらを参考にブロックチェーン技術を検証していきたいと思います。

ブロックチェーンとのやりとりを行う

今回はPostmanを利用してローカルでやりとりをします。

`$ python blockchain.py`

を入力してblockchain.pyを起動します。
するとRunning onとなります。

次にPostmanを開き、下記の画像で表す赤枠内に

http://localhost:5000/mine
を入力してGETリクエストをSendします。

するとブロックのマイニングが行われ、下記の画像のように表示されます。

そして、このtransactionsに入っている3つの値をコピーして、
POSTリクエストでhttp://localhost:5000/transactions/newを指定し、
Body、raw、JSONを選択して貼り付けてSendします。

すると下記のようなメッセージが表示されます。

次にhttp://localhost:5000/chainをGETリクエストすると、下記のようにマイニングされたブロックの数が表示されます。

    {
        "chain": [
            {
                "index": 1,
                "previous_hash": 1,
                "proof": 100,
                "timestamp": 1539242723.078951,
                "transactions": []
            },
            {
                "index": 2,
                "previous_hash": "7d51744b1c5682903c4e4f822b398e3971649132846c43892b470b8cea5831e6",
                "proof": 35293,
                "timestamp": 1539242727.203414,
                "transactions": [
                    {
                        "amount": 1,
                        "recipient": "62845defce9145869ed7f5be37b11d37",
                        "sender": "0"
                    }
                ]
            },
            {
                "index": 3,
                "previous_hash": "383cb3846a28ab3754cc68749ac301230ed8e43d15d0a8aede26ae19def07178",
                "proof": 35089,
                "timestamp": 1539242739.303896,
                "transactions": [
                    {
                        "amount": 1,
                        "recipient": "62845defce9145869ed7f5be37b11d37",
                        "sender": "0"
                    }
                ]
            }
        ],
        "length": 3
    }

コンセンサスアルゴリズム(合意形成)

コンセンサスアルゴリズムは改ざんを防止する為に分権化する重要な技術で、仮想通貨によって取り入れられている技術が異なります。
プルーフオブワークもその一つです。

コンセンサスアルゴリズムを実装する前に、まずネットワーク上の隣接するノード同士を知らせるようにします。
ネットワーク上の各ノードは、ネットワーク上の他のノードのレジストリを保持する必要があります。
したがって、より多くのエンドポイントが必要になります。

  1. /nodes/register URL形式で新しいノードのリストを受け入れる。
  2. /nodes/resolve コンセンサスアルゴリズムを実装して、ノードが正しいチェーンを持つことを保証する。

ブロックチェーンのコンストラクタを変更し、ノードを登録するメソッドを用意します。
下記のコードを該当する箇所に追加します。

    ...
    from urllib.parse import urlparse
    ...


    class Blockchain(object):
        def __init__(self):
            ...
            self.nodes = set()
            ...

        def register_node(self, address):
            """
            Add a new node to the list of nodes

            :param address: <str> Address of node. Eg. 'http://192.168.0.5:5000'
            :return: None
            """

            parsed_url = urlparse(address)
            self.nodes.add(parsed_url.netloc)

コンセンサスアルゴリズムの実装

あるノードが別のノードと異なるチェーンを持つ場合に競合します。
これを解決するために、一番長い有効なチェーンが信頼できるものとしてルールを作成します。
このアルゴリズムを使用して、ネットワーク内のノード間の合意を得ます。

下記のコードを該当する箇所に追加します。

    ...
    import requests


    class Blockchain(object)
        ...

        def valid_chain(self, chain):
            """
            Determine if a given blockchain is valid

            :param chain: <list> A blockchain
            :return: <bool> True if valid, False if not
            """

            last_block = chain[0]
            current_index = 1

            while current_index < len(chain):
                block = chain[current_index]
                print(f'{last_block}')
                print(f'{block}')
                print("\n-----------\n")
                # Check that the hash of the block is correct
                if block['previous_hash'] != self.hash(last_block):
                    return False

                # Check that the Proof of Work is correct
                if not self.valid_proof(last_block['proof'], block['proof']):
                    return False

                last_block = block
                current_index += 1

            return True

        def resolve_conflicts(self):
            """
            This is our Consensus Algorithm, it resolves conflicts
            by replacing our chain with the longest one in the network.

            :return: <bool> True if our chain was replaced, False if not
            """

            neighbours = self.nodes
            new_chain = None

            # We're only looking for chains longer than ours
            max_length = len(self.chain)

            # Grab and verify the chains from all the nodes in our network
            for node in neighbours:
                response = requests.get(f'http://{node}/chain')

                if response.status_code == 200:
                    length = response.json()['length']
                    chain = response.json()['chain']

                    # Check if the length is longer and the chain is valid
                    if length > max_length and self.valid_chain(chain):
                        max_length = length
                        new_chain = chain

            # Replace our chain if we discovered a new, valid chain longer than ours
            if new_chain:
                self.chain = new_chain
                return True

            return False

valid_chain()は各ブロックのハッシュとプルーフの両方を検証することによってチェーンが有効かどうかをチェックします。

resolve_conflicts()はすべての隣接ノードをループしてチェーンをダウンロードし、valid_chain()で検証します。
長さがより大きい有効なチェーンが見つかった場合は、それに置き換えます。

APIに2つのエンドポイントを登録します。
1つは隣接ノードを追加する為のもので、もう1つは競合を解決する為のものです。

下記のコードをif __name__ == '__main__':の上に貼り付けてください。

    @app.route('/nodes/register', methods=['POST'])
    def register_nodes():
        values = request.get_json()

        nodes = values.get('nodes')
        if nodes is None:
            return "Error: Please supply a valid list of nodes", 400

        for node in nodes:
            blockchain.register_node(node)

        response = {
            'message': 'New nodes have been added',
            'total_nodes': list(blockchain.nodes),
        }
        return jsonify(response), 201


    @app.route('/nodes/resolve', methods=['GET'])
    def consensus():
        replaced = blockchain.resolve_conflicts()

        if replaced:
            response = {
                'message': 'Our chain was replaced',
                'new_chain': blockchain.chain
            }
        else:
            response = {
                'message': 'Our chain is authoritative',
                'chain': blockchain.chain
            }

        return jsonify(response), 200

これにより、他のマシンからでもネットワーク上の別のノードを回すことができるようになります。
または、同じマシン上の異なるポートを使用してプロセスを回すこともできます。

今回は一つのマシンで2つのノードを動かしてみたいと思います。
blockchain.pyをコピーしてblockchain2.pyを作成します。
blockchain2.pyのportの部分を

     if __name__ == '__main__':
         app.run(host='0.0.0.0', port=5001)

とします。
blockchain.pyと新しいタブでblockchain2.pyを起動します。

以下の画像のようにPostmanで新しいノードを登録します。

このようにして新しいノードが登録されたことが分かります。

そうしたらノード1であるhttp://localhost:5000/とノード2であるhttp://localhost:5001/でmineします。

ノード1で3回mineしました。

http://localhost:5000/nodes/resolveで確認すると、

3回mineされ、かつメインのチェーンになっていることが分かります。

次にhttp://localhost:5001/で5回mineします。

その後またhttp://localhost:5000/nodes/resolveで確認します。

ノード2の方が最も長いチェーンである為、ノード1が置き換えられました。

この他にも色々な順番でやってみると面白いかもしれません。

まとめ

ブロックチェーンの基本的な機能を一通り見ていき、様々な工夫がなされていることやその動きが分かりました。
最近ではこの技術を様々な分野に応用していると聞くので、どのような分野で活躍するのか今後色々と調べてみたいと思います。

参考文献

Learn Blockchains by Building One


Githubに今回行なった内容が載せてあります。
https://github.com/Saya-hack/Learn_Blockchains