Pythonでブロックチェーンを学ぶ -2-

以下、前回の第1回目で書いた Proof of Work の実装を用いて、実際にTransaction処理を書いてみる。

ブロックに追加するデータは、仮想通貨であれば、送金元アドレス、振込先アドレス、コインの数などになるが、ここでは、実験として Virtual Machine3台に定期的にポーリングして CPU値、Memory値、時刻を取得して、ブロックチェーンに追加していく処理にしてみた。(これらを対象としたのに特に大きな意味はないですが。。)

※上記 Raspberry Pi上で グラフ(SenseHat) のコードはここでは割愛。

ブロックチェーンの処理

関数は以下の構成。
==========================================
- Class 定義
           - コンストラクタ  
           - 新しいブロックチェーン作成
           - 新しいトランザクションをリストに加える
           - プルーフ・オブ・ワークのTrue/False 判断
           - ノードリストに新ノードを追加
           - ブロックチェーンが正しいかを確認
           - ノード間でのチェーンの整合性確認
           - ブロックの SHA-256 ハッシュを作る
           - 最初の4つが0となるような ノンス値を探す
           - チェーンの最後のブロックをリターンする
==========================================

# coding: UTF-8

import hashlib
import json
from time import time
from uuid import uuid4
from flask import Flask, jsonify, request
from urllib.parse import urlparse
import requests

class Blockchain(object):
    def __init__(self):
        """
        コンストラクトの設定。ブロックを作る初期設定
        """
        self.chain = []
        self.current_transactions = []
        self.nodes = set()
        self.new_block(previous_hash=1, proof=100)

    def new_block(self, proof, previous_hash=None):
        """
        ブロックチェーンに新しいブロックを作る
        :param proof: <int> プルーフ・オブ・ワークアルゴリズムから得られるプルーフ
        :param previous_hash: (オプション) <str> 前のブロックのハッシュ
        """

        block = {
            'index': len(self.chain) + 1,
            'timestamp': time(),
            'transactions': self.current_transactions,
            'proof': proof,
            'previous_hash': previous_hash or self.hash(self.chain[-1]),
        }

        # 現在のトランザクションをリセット
        self.current_transactions = []
     
     # 新しいブロックを作り、チェーンに加える
        self.chain.append(block)
        return block

    def new_transaction(self, cpuPct, maxMemAlloc, memFree, modTs):
        
        """
     新しいトランザクションをリストに加える
        次に採掘されるブロックに加える新しいトランザクションを作る
        :param cpuPct: <int> CPU使用率
        :param maxMemAlloc: <int> メモリ割当量
        :param memFree: <int> Freeメモリ
        :param modT: <int> 上記パラメーターの最終更新時間
        """
        self.current_transactions.append({
            'cpuPct': cpuPct,
            'maxMemAlloc': maxMemAlloc,
            'memFree': memFree,
            'modTs': modTs
        })

        return self.last_block['index'] + 1

    def proof_of_work(self, last_proof):
        """
        プルーフ・オブ・ワークのアルゴリズム:
         - hash(xx') の最初の4つが0となるような x' を探す
         - x は1つ前のブロックのプルーフ、 x' は新しいブロックのプルーフ
        :param last_proof: <int>
        :return: <int>
        """

        proof = 0
        while self.valid_proof(last_proof, proof) is False:
            proof += 1

        return proof

    def register_node(self, address):
        """
        ノードリストに新しいノードを加える
        :param address: <str> ノードのアドレス 例: 'http://192.168.0.5:5000'
        :return: None
        """
        parsed_url = urlparse(address)
        self.nodes.add(parsed_url.netloc)

    def valid_chain(self, chain):
        """
        ブロックチェーンが正しいかを確認する

        :param chain: <list> ブロックチェーン
        :return: <bool> True であれば正しく、 False であればそうではない
        """

        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")

            #ブロックのハッシュが正しいかを確認
            if block['previous_hash'] != self.hash(last_block):
                return False

            # PoWが正しいかどうかを確認
            if not self.valid_proof(last_block['proof'], block['proof']):
                return False

            last_block = block
            current_index += 1
        return True

    def resolve_conflicts(self):
        """
        コンセンサスアルゴリズム
        ネットワーク上の最も長いチェーンで置き換える
     これによって複数ノードで同期した場合のコンフリクトを解消する
        :return: <bool> 自らのチェーンが置き換えられると True 、そうでなれけば False
        """

        neighbours = self.nodes
        new_chain = None

        """
        自らのチェーンより長いチェーンを探す
        """
        max_length = len(self.chain)

        """
        他のすべてのノードのチェーンを確認
        """
        for node in neighbours:
            response = requests.get(f'http://{node}/chain')
            print(response)

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

                """
                そのチェーンがより長いか、有効かを確認
                """
                if length > max_length and self.valid_chain(chain):
                    max_length = length
                    new_chain = chain

        """
        もし自らのチェーンより長く、かつ有効なチェーンを見つけた場合それで置き換える
        """
        if new_chain:
            self.chain = new_chain
            return True

        return False

    @staticmethod
    def hash(block):
        """
        ブロックの SHA-256 ハッシュを作る
        :param block: <dict> ブロック
        :return: <str>
        """

        #必ずdict objectがソートされている必要がある。
        block_string = json.dumps(block, sort_keys=True).encode()
        return hashlib.sha256(block_string).hexdigest()

    @staticmethod
    def valid_proof(last_proof, proof):
        """
        hash(xx') の最初の4つが0となるような x' を探す
        """
        guess = f'{last_proof}{proof}'.encode()
        # f文字列について-> http://atsuoishimoto.hatenablog.com/entry/2016/12/25/122220
        guess_hash = hashlib.sha256(guess).hexdigest()

        return guess_hash[:4] == "0000"

    @property
    def last_block(self):
        # チェーンの最後のブロックをリターンする
        return self.chain[-1]

次回は ノード上でリクエスト処理を行うための APIを実装してみます。

今回の"note"を気に入って頂けましたら、是非サポートをお願いいたします!