🌏

ブロックチェーン(テスト)の実装(Python)

に公開

今回は、前回までに書いたブロックチェーンのテストを実装しよーと思います!

環境

Windows10, VSCode, Python
test_block_chain.py は、block_chain.py と同階層に作成します。

前回までの記事・コード

前々回の記事:
https://zenn.dev/animalz/articles/c82f20acccd30f
前回の記事:
https://zenn.dev/animalz/articles/928dd981c2d1a5
コード:
GitHub: https://github.com/Animalyzm/mikoto_project
前回のコードは、block_chain.py です。
今回のコードは、test_block_chain.py です。

今回やること

  1. pytest のインストール
  2. ライブラリのインポート
  3. テスト用のデータ作成
  4. 前々回実装した関数のテストを実装
  5. 前回実装した関数のテストを実装
  6. テスト実行

1. pytest のインストール

pytest: https://docs.pytest.org/en/stable/index.html

コマンド・プロンプト
pip install pytest

2. ライブラリのインポート

pytest は実行時に使うだけなので、インポートはしてません。

test_block_chain.py
import binascii
import datetime as dt
import json
from ecdsa import SECP256k1, SigningKey, VerifyingKey
import block_chain as bc

3. テスト用データの作成

コードが冗長になるのを防ぐため、テスト用データを作成します。(前回の実行コードになります)

test_block_chain.py
name_list = ['Dog', 'Cat', 'Lion']
user_info_dict = {}
for name in name_list:
    user_info_dict[name] = bc.make_key_data()
transaction_pool = []
transaction_pool.append(
    bc.make_mikoto_trasaction(user_info_dict['Dog']['public_key_str'], 100)
)
transaction_pool.append(
    bc.make_thanks_transaction(
        user_info_dict['Dog']['secret_key_str'],
        user_info_dict['Dog']['public_key_str'],
        user_info_dict['Cat']['public_key_str'],
        10
    )
)
transaction_pool.append(
    bc.make_thanks_transaction(
        user_info_dict['Dog']['secret_key_str'],
        user_info_dict['Dog']['public_key_str'],
        user_info_dict['Lion']['public_key_str'],
        20
    )
)
block_chain = bc.make_first_block_chain()
sorted_new_block = bc.mining(transaction_pool, block_chain, user_info_dict['Dog']['public_key_str'], 100)
block_chain.append(sorted_new_block)

4. 前々回実装した関数のテストを実装

型、鍵の復元をテストをします。

test_block_chain.py
def test_make_key_data():
    """ test: type, str_to_key """
    key_data = bc.make_key_data()
    assert isinstance(key_data, dict)
    assert SigningKey.from_string(
        binascii.unhexlify(key_data['secret_key_str']), curve=SECP256k1
    )
    assert VerifyingKey.from_string(
        binascii.unhexlify(key_data['public_key_str']), curve=SECP256k1
    )

型、キー、型変換、値をテストします。

test_block_chain.py
def test_make_mikoto_trasaction():
    """ test: type, keys, conversion, value """
    # テスト用データ
    receiver_public_key_str = user_info_dict['Dog']['public_key_str']
    MIK = 100
    mikoto_transaction = bc.make_mikoto_trasaction(receiver_public_key_str, MIK)
    assert isinstance(mikoto_transaction, dict)
    assert list(mikoto_transaction.keys()) == ['time', 'sender', 'receiver', 'MIK', 'signature']
    # datetime 型に変換
    assert dt.datetime.fromisoformat(mikoto_transaction['time'])
    assert mikoto_transaction['sender'] == 'mikoto_project'
    assert mikoto_transaction['receiver'] == receiver_public_key_str
    assert isinstance(mikoto_transaction['MIK'], int)
    assert mikoto_transaction['signature'] == 'mikoto_project'

型、キー、型変換、値、検証をテストします。

test_block_chain.py
def test_make_thanks_transaction():
    """ test: type, keys, conversion, value, verify """
    # テスト用データ
    sender_secret_key_str = user_info_dict['Dog']['secret_key_str']
    sender_public_key_str = user_info_dict['Dog']['public_key_str']
    receiver_public_key_str = user_info_dict['Cat']['public_key_str']
    MIK = 10
    thanks_transaction = bc.make_thanks_transaction(
        sender_secret_key_str, sender_public_key_str, 
        receiver_public_key_str, MIK
    )
    assert isinstance(thanks_transaction, dict)
    assert list(thanks_transaction.keys()) == ['time', 'sender', 'receiver', 'MIK', 'signature']
    # datetime 型に変換
    assert dt.datetime.fromisoformat(thanks_transaction['time'])
    assert thanks_transaction['sender'] == sender_public_key_str
    assert thanks_transaction['receiver'] == receiver_public_key_str
    assert isinstance(thanks_transaction['MIK'], int)
    # verify
    signature = binascii.unhexlify(thanks_transaction['signature'])
    thanks_transaction.pop('signature')
    sender_public_key = VerifyingKey.from_string(binascii.unhexlify(sender_public_key_str), curve=SECP256k1)
    assert sender_public_key.verify(signature, json.dumps(thanks_transaction).encode('utf-8'))

検証成功と失敗のケースをテストします。

test_block_chain.py
def test_verify_transaction():
    """ test: True case, False case"""
    # テスト用データ
    thanks_trasaction = transaction_pool[1]
    assert bc.verify_transaction(thanks_trasaction)
    # receiver と sender を逆にする
    sender = thanks_trasaction['receiver']
    receiver = thanks_trasaction['sender']
    thanks_trasaction['sender'] = sender
    thanks_trasaction['receiver'] = receiver
    assert bc.verify_transaction(thanks_trasaction) is False

5. 前回実装した関数のテストを実装

型、文字列長、ハッシュ値の変化をテストします。

test_block_chain.py
def test_make_hash_str():
    """ test: type, len, change """
    list_data = list(range(5))
    list_hash_str = bc.make_hash_str(list_data)
    assert isinstance(list_hash_str, str)
    assert len(list_hash_str) == 64
    dict_data1 = {'a': 1, 'b': 2}
    dict_data2 = {'a': 1, 'b': 3}
    dict_hash_str1 = bc.make_hash_str(dict_data1)
    dict_hash_str2 = bc.make_hash_str(dict_data2)
    assert isinstance(dict_hash_str1, str)
    assert len(dict_hash_str1) == 64
    assert dict_hash_str1 != dict_hash_str2

型、キー、値をテストします。

test_block_chain.py
def test_make_first_block_chain():
    """ test: type, keys, value """
    first_block_chain = bc.make_first_block_chain()
    keys = ['time', 'transactions', 'hash', 'nonce']
    assert isinstance(first_block_chain, list)
    assert list(first_block_chain[0].keys()) == keys
    assert dt.datetime.fromisoformat(first_block_chain[0]['time'])
    assert first_block_chain[0]['transactions'] == []
    assert first_block_chain[0]['hash'] == 'mikoto_project'
    assert first_block_chain[0]['nonce'] == 0

型、文字列長、先頭からのゼロの数をテストします。

test_block_chain.py
def test_proof_of_work():
    """ test: type, len, zeros """
    POW_ZEROS = 3
    block = bc.make_first_block_chain()[0]
    block = bc.proof_of_work(block, POW_ZEROS)
    hash_value = bc.make_hash_str(block)
    assert isinstance(hash_value, str)
    assert len(hash_value) == 64
    assert hash_value.startswith('0'*POW_ZEROS)

型、キーをテストします。

test_block_chain.py
def test_mining():
    """ test: type, keys """
    block_chain = bc.make_first_block_chain()
    # テスト用データ
    miner_public_key_str = user_info_dict['Dog']['public_key_str']
    MIK = 100
    # テスト用データ:transaction_pool
    sorted_new_block = bc.mining(transaction_pool, block_chain, miner_public_key_str, MIK)
    keys = ['time', 'transactions', 'hash', 'nonce']
    assert isinstance(sorted_new_block, dict)
    assert list(sorted_new_block.keys()) == keys
    assert dt.datetime.fromisoformat(sorted_new_block['time'])
    assert isinstance(sorted_new_block['transactions'], list)
    assert isinstance(sorted_new_block['hash'], str)
    assert isinstance(sorted_new_block['nonce'], int)

True ケース、False ケースをテストします。

test_block_chain.py
def test_verify_block_chain():
    """ test: True case, False case """
    # テスト用データ:block_chain
    # True case
    assert bc.verify_block_chain(block_chain)
    # False case: fake_hash
    fake_hash_block_chain = copy.deepcopy(block_chain)
    fake_hash_block_chain[-1]['hash'] = ''
    assert bc.verify_block_chain(fake_hash_block_chain) is False
    # False case: fake_transaction
    fake_transaction_block_chain = copy.deepcopy(block_chain)
    fake_signature = fake_transaction_block_chain[-1]['transactions'][-2]['signature']
    fake_transaction_block_chain[-1]['transactions'][-3]['signature'] = fake_signature
    assert bc.verify_block_chain(fake_transaction_block_chain) is False
    # Flase case: dupulicates_transaction
    dupulicates_transaction_bc = copy.deepcopy(block_chain)
    dupulicate_transaction = dupulicates_transaction_bc[-1]['transactions'][-2]
    dupulicates_transaction_bc[-2]['transactions'].append(dupulicate_transaction)
    dupulicates_transaction_bc[-1]['hash'] = bc.make_hash_str(dupulicates_transaction_bc[-2])
    def re_proof_of_work(block):
        """ nonceを計算し直す """
        nonce_dict = {k: v for k, v in block.items() if k != 'time'}
        nonce_dict['nonce'] = 0
        nonce_dict = bc.proof_of_work(nonce_dict)
        block['nonce'] = nonce_dict['nonce']
        return block
    dupulicates_transaction_bc[-1] = re_proof_of_work(dupulicates_transaction_bc[-1])
    assert bc.verify_block_chain(dupulicates_transaction_bc) is False
    # False case: new_block_dupulicates_transaction
    new_block_dupulicates_transaction_bc = copy.deepcopy(block_chain)
    dupulicate_transaction = new_block_dupulicates_transaction_bc[-1]['transactions'][-2]
    new_block_dupulicates_transaction_bc[-1]['transactions'].append(dupulicate_transaction)
    new_block_dupulicates_transaction_bc[-1] = re_proof_of_work(new_block_dupulicates_transaction_bc[-1])
    assert bc.verify_block_chain(new_block_dupulicates_transaction_bc) is False
    # False case: fake_pow
    fake_pow_block_chain = copy.deepcopy(block_chain)
    if fake_pow_block_chain[-1]['nonce'] != 0:
        fake_pow_block_chain[-1]['nonce'] = 0
    else:
        fake_pow_block_chain[-1]['nonce'] = 1
    assert bc.verify_block_chain(fake_pow_block_chain) is False

6. テスト実行

コマンド・プロンプト
pytest test_block_chain.py

結果

以上になります、ありがとうございましたー♪

Discussion