前回ブロックチェーンの作り方 #5 ではブロックチェーンの中核を担うマイニング処理とチェーンの受信処理をを実装しました。今回 #6 ではコインを持っていないユーザーが送金できてしまうという問題の対策として各アカウントの残高チェックを実装していきます。
関連記事:開発環境の準備とファイル構成
各アカウントの残高の計算とチェック
BlockChain.py
送信者の残高チェックに必要な処理とメソッドを BlockChain
クラスに実装します。
class BlockChain: (略) # チェーンの正当性の検証 def verify_chain(self, chain): (略) # トランザクションが再利用されていないことをチェック if tx not in all_block_txs: all_block_txs.append(tx) else: print('トランザクションが再利用されている') return False (↓処理を追加↓) # 各アカウントの残高チェック if all_block_txs: # 各アカウントの残高を計算 accounts = self.calc_accounts_balance(all_block_txs) # 各アカウントの残高のみを取り出す accounts_values = accounts.values() # 残高がマイナスになるアカウントがある場合はそのチェーンは不正とする if min(accounts_values) < 0: print('残高がマイナスになるアカウントがある') return False (↑処理を追加ここまで↑) # 検証OK return True (略) (↓メソッドを追加↓) # 各アカウントの残高の計算 def calc_accounts_balance(self, txs): # 各カウントの残高の入れ物 accounts = {} # 初期化処理 accounts['reward'] = 0 for tx in txs: accounts[tx['sender']] = 0 accounts[tx['to']] = 0 # 残高の計算 for tx in txs: # 送信者の残高を減らす accounts[tx['sender']] -= int(tx['coin']) # 受信者の残高を増やす accounts[tx['to']] += int(tx['coin']) # 報酬の送金総額は削除する del accounts['reward'] # 各アカウントの残高を返す return accounts
【コードの要点】
verify_chain
メソッドに各アカウントの残高チェック処理を追加
チェーン上の全てのトランザクションから各アカウントの残高を計算して、残高がマイナスになるアカウントがある場合は不正なチェーンとして処理します。残高の計算は下の calc_accounts_balance
メソッドを使います。
calc_accounts_balance
メソッド
各アカウント(正確にはブロックチェーンアドレスとなる公開鍵)ごとに所有しているコインの枚数を計算するメソッドです。
node.py
確認用に node.py
に各アカウントの残高を返す関数を追加します。
(略) # ブロックチェーンインスタンス bc = BlockChain() (略) (↓関数を追加↓) # 各アカウントの残高を返す @app.get('/accounts') def get_accounts(): return bc.calc_accounts_balance(bc.all_block_txs)
動作確認
node.py
を再起動し、ブラウザで「http://127.0.0.1:8000/accounts」にアクセスして各アカウントの残高が表示されることを確認してください。(残高がマイナスのアカウントが表示される場合もあります)
これまでは各アカウントの残高をチェックせずにトランザクションをブロックチェーンに追加していたために残高がマイナスとなっているアカウントがあるかもしれませんので、tx_pool.pkl
と chain.pkl
を削除して node.py
を再起動してください。
rm blockchain/data/tx_pool.pkl rm blockchain/data/chain.pkl
ユーザーAからユーザーBに送金するとユーザーAの残高がマイナスになります。この状態でユーザーCでマイニングしてエラーになることを確認してください。
python blockchain/send_tx.py(ユーザーAからユーザーBに送金) python blockchain/mining.py(ユーザーCでマイニング) mining success. nonce: 26687 hash: 000030f255c11315afcc471265cad7d4d48e720052f5415837bc04e8440c5cad mining time: 0.24599695205688477 second "error"
ノード側では以下のログが表示されていると思います。
INFO: 127.0.0.1:62737 - "GET /tx-pool HTTP/1.1" 200 OK
残高がマイナスになるアカウントがある
マイニング前の検証処理を追加
最後にマイニング側にもチェーンの検証処理と各アカウントの残高チェックの処理を追加します。ノード側で検証処理を行っているので不要と思われるかもしれませんが、コインを不正に入手するためノード側がトランザクションを偽装している可能性もありますので、マイニング前に検証処理を追加してせっかくマイニングに成功したのに、正規ノードに弾かれてしまうことを防ぎます。
mining.py
ノードから取得したチェーンが不正な場合は処理を終了させますので冒頭で sys モジュールをインポートします。
from mod.BlockChain import BlockChain from mod.users import users (↓モジュールを追加↓) import time # ブロックチェーンインスタンス bc = BlockChain() # チェーンの取得 chain = bc.get_chain() (↓処理を追加↓) # チェーンを検証 bc.chain['blocks'] = [] # インスタンス内のチェーンを空にする if not bc.validate_chain(chain): sys.exit('取得したチェーンが不正なため処理を終了しました') (↑処理を追加ここまで↑) # 最後のブロックのハッシュ値を生成 last_block = chain['blocks'][-1] last_block_hash = bc.gen_hash(last_block) # ブロックに含めるトランザクション tx_pool = bc.get_tx_pool() target_txs = tx_pool['txs'] (↓処理を追加↓) # 全ブロックのトランザクションリストを作成(残高チェック用) bc.chain = chain bc.set_all_block_txs() all_block_txs = bc.all_block_txs.copy() # 残高がマイナスになるトランザクションは除外する for tx in target_txs.copy(): all_block_txs.append(tx) if min(bc.calc_accounts_balance(all_block_txs).values()) < 0: target_txs.remove(tx) all_block_txs.remove(tx) (↑処理を追加ここまで↑) # マイナーの公開鍵 miner_public_key = users['C']['public_key'] (略)
【コードの要点】
# チェーンを検証
BlockChain
クラスの validate_chain
メソッドでノードから取得したチェーンの正当性を検証するのですが、ノードから取得したチェーンにジェネシスブロックしか含まれていない場合、インスタンス内のチェーン bc.chain
と長さが同じになるため検証で弾かれてしまいます。そのため bc.chain['blocks'] = []
でインスタンス内のチェーンを空にしてからチェーンの検証を行います。
# 残高がマイナスになるトランザクションは除外する
ブロックに含めようとしているトランザクションの送信者の残高がマイナスになる場合は、そのトランザクションをブロックに含めないようにしてマイニングを行います。
今回のまとめ
今回は、各アカウントの残高の計算とチェックとマイニング側にも検証処理を追加しました。残る実装は各ノード間のデータ同期処理になるのですが、検証用のサーバーを何台か用意しておく必要がありますので、次回はノード用サーバーの準備を行います。
コメント