『ゼロから創る暗号通貨』
第5章: すべての機能を結合し、動かしてみよう

濵津 誠/hamatz

第5章 すべての機能を結合し、動かしてみよう

本章では、これまでに作ってきた部品をすべて繋ぎ合わせることで、SimpleBitcoinが暗号通貨として実際に動作することを確認します。これまでのおさらいを兼ねて、サーバーとWalletの要所を確認しながらSimpleBitcoinを完成させましょう。ポイントになるのは次のような内容です。

  1. Walletとはどのような役割を持ち、どのような構成になるか
  2. サーバーとはどのような役割を持ち、どのような構成になるか
  3. SimpleBitcoinネットワーク内のメッセージの流れを追う

本章はこれまでの努力が実を結ぶところです。ぜひ楽しんで読み進めてください!

[2pt_line]

「作業を始める前に、これから先のポイントを確認してみよう。これまで準備してきた部品のうち完成している部分がどこで、まだ手を加える必要がある部分はどこか、全体像から見直してみよう」

「どれくらいの作業が残ってるかを確認するってことだね」

「そんなところだ。図5.1を見てほしい」

全体像から見たこれから先のポイント

図5.1: 全体像から見たこれから先のポイント

「残るポイントは5つってこと?」

「そうだね。WalletでTransactionの生成ができるところまでは、もう完成しているので、あとは生成したTransactionをP2Pネットワークに送信する機能へ繋ぎ込む必要がある。これが1つめのポイントだ」

「その必要性は認識していた」

「次に、受け取ったTransactionをブロックチェーンに取り込んでよいかどうかをチェックする機能が必要になる。これが2つめのポイントになる」

「受け取ったTransactionを無条件に信頼するのはまずいものね。使用済みのTransactionとなっているものがUTXOとして再利用されていないことの確認とかが必要か」

「そのとおり。ユーザーの不正を排除するための機能ともいえる。それに、自分の手数料を計算する必要があるから、不正の心配がなくてもTransactionの中身のチェックは必要だ」

「そういえば、Coinbase Transactionは、本来はCoreノード側でブロックを生成するときにセットするものだったよね」

「そのとおり。まさに、その機能の作り込みが3つめのポイントになる」

図5.1の4つめのポイントは、他のCoreノードで生成されたブロックに不正がないかを確認する話?」

「そうだね。ブロックはP2Pネットワーク上のどのCoreノードで生成されるかわからないから、飛んできたものを無条件に信頼するのはよろしくない。たとえば、Coinbase Transactionに途方もない額が上乗せされているようなブロックは却下されるべきだ」

「なるほど。トラストレスだもんね。5つめのポイントは、更新されたブロックチェーンから自分に関連したUTXOが存在しているかどうかを確認して、場合によっては残高表示を更新する部分の作り込みだよね」

「Walletはさっき実装したばかりだから、よくわかってるようだな。整理の意味で、残る5つのポイントをもう一度まとめよう」

これからの残課題における5つのポイント

図: これからの残課題における5つのポイント

「うん。できそうな気がしてきた」

「それでは、まずはWallet側から完成させていこう」

「1つめと5つめのポイントから取り組んでいくってことだね」

5.1 Walletを完成させよう

「Walletから完成させていくということなので、まずは、ここまでにできあがっているWalletの機能を整理して図5.2のような図にしてみたよ」

これまで完成しているWallet

図5.2: これまで完成しているWallet

「いいね。では、そこにP2Pネットワークとの接続周りの機能を追加して、実際に生成したTransactionを送付できるようにするには、何が必要だろう?」

「えっと、ここまでにできあがってるEdgeノードの機能を同じように整理すると、図5.3のようになるよね」

これまで完成しているEdgeノードのイメージ

図5.3: これまで完成しているEdgeノードのイメージ

「そうだね。ここにうまくWalletを繋ぎ込むには、どうすればいいだろう?」

どうやって融合させよう?

図5.4: どうやって融合させよう?

「これまでの方針どおりに、ClientCoreが他の機能を抽象化してUI側から利用しやすくするというイメージで進めるとすると、図5.5のような形になるかなぁ……」

「あまり釈然としていないようだね?」

最終的なWalletの姿?

図5.5: 最終的なWalletの姿?

「正直なところ、今のWalletのコードをいじる必要性をあまり感じていないんだ。WalletにくっついてるKeyManagerUTXOManagerを引き剥がして、わざわざClientCoreに移動させる必要があるのかなぁって……」

「なるほど。現実のイメージに照らし合わせて考えても、Walletという名前のアプリケーションが鍵を管理していることに不自然さはないよね」

「その鍵に結び付いているTransactionだけを管理するUTXOManagerにしても、かなり特殊な機能を提供するものだから、必要になるのはWallet側だけだと思うんだ」

「そうだね。それらの機能をClientCore側に移動しても、それによる抽象化の恩恵は特になくて、今ある機能を間接的に呼び出すことになるだろう」

「だから、むしろ図5.6のような切り分け方がいいと思うんだよね。ダメかな?」

Walletに特化した部分はClientCoreと切り離す

図5.6: Walletに特化した部分はClientCoreと切り離す

「これは君のプロジェクトなんだから、いいも悪いもない。君が好きなように設計して作ればいいんだよ。私見を言わせてもらえば、私でも図5.6のように作ろうとするだろうね」

「よし、じゃあその方針でやっていくよ!」

5.1.1 Wallet_GUIとClientCoreを接続しよう

「まずはWallet_GUIClientCoreを接続する部分から見ていこうか」

「実現したいことは、大きく分けると次の2つだったよね」

  1. Wallet_GUI上の入力で生成したTransactionメッセージを、ClientCoreを経由してP2Pネットワークに送信する
  2. Coreノードから最新のブロックチェーンを取得して、Wallet_GUI上で利用可能なUTXOを取り出す

「そのとおり」

「1について図にすると、図5.7のような役割分担の形になると思う」

Walletで生成されたTransactionが送信されるまで

図5.7: Walletで生成されたTransactionが送信されるまで

「ふむ。では、この手順に沿って、送信できるまでの処理をコードを追って見ていこうか」

「うん。まずはinitApp()の処理の中でClientCoreを登録しておく必要があるね」

Wallet_App.py initApp内に追加する処理

  def initApp(self, my_port, c_host, c_port):
      """
      ClientCoreとの接続含めて必要な初期化処理はここで実行する
      """
      print('SimpleBitcoin client is now activating ...: ')

      self.c_core = ClientCore(my_port, c_host, c_port)
      self.c_core.start()
      <以下略>

「あとは、この登録したClientCoreを使って、Coreノードへのメッセージ送信を依頼するだけ」

Wallet_App.py sendCoins内に追加する処理

  # TransactionをP2P Networkに送信
  tx_strings = json.dumps(new_tx)
  self.c_core.send_message_to_my_core_node(MSG_NEW_TRANSACTION, tx_strings)
  print('signed new_tx:', tx_strings)

「拍子抜けするくらい簡単だね。じゃあ、勢いに乗ってCoreノードから最新のブロックチェーンを取ってくる部分を見ていこうか」

「りょうかい」

5.1.2 ブロックチェーンの更新と利用可能残高の確認処理を追加しよう

「少しややこしいので、ブロックチェーンを取ってくるまでの段階と、取ってきたブロックチェーンから有効なUTXOを保存して残高表示を書き換えるとこまでの段階を、2つのステップに分けて図にしておいた」

Step1: 最新ブロックチェーンの取得

図5.8: Step1: 最新ブロックチェーンの取得

Step2: ブロックチェーンからのUTXOの取り出し

図5.9: Step2: ブロックチェーンからのUTXOの取り出し

「なるほど。P2Pネットワーク経由で最新のブロックチェーンを取得したことをClientCoreからWallet_GUIに通知する手段がないと、ブロックチェーンからどのタイミングでUTXOを取り出せばいいかわからないか」

「だから、ClientCoreには、図5.9の処理をするためのCallbackを登録するような口が必要になる」

「そうかー。とりあえずClientCoreの初期化時に登録できるように口だけ用意しておくことにして、まずは図5.8のステップについてコードにしてみるよ」

Wallet_App.py initApp内にCallbackを登録できるよう口だけ開けておく

    def initApp(self, my_port, c_host, c_port):
        """
        ClientCoreとの接続含めて必要な初期化処理はここで実行する
        """
        print('SimpleBitcoin client is now activating ...: ')

        self.c_core = ClientCore(my_port, c_host, c_port, self.update_callback)
        self.c_core.start()
        <以下略>

「それがいいだろうね」

「せっかく抽象化層としてClientCoreがあるんだから、最新のブロックチェーンの取得には、こんなふうに専用の関数を用意しちゃっていいと思うんだよね」

ClientCore内に追加する処理

  def send_req_full_chain_to_my_core_node(self):
      print('send_req_full_chain_to_my_core_node called')
      new_message = self.cm.get_message_text(MSG_REQUEST_FULL_CHAIN)
      self.cm.send_msg((self.my_central_host, self.my_central_port),
                        new_message)

「どうしてそう考えたんだい?」

「こうしておけば、UI側のメニューで『Update Blockchain』を選んだときに呼び出される関数で、この専用の関数を呼び出すだけで済むでしょ」

Wallet_App内に追加する処理

  def update_block_chain(self):
      self.c_core.send_req_full_chain_to_my_core_node()

「なるほど、考えたね」

「やった」

「ここまでで、図5.8のうち、①から④までの処理が実現できたことになる」

「⑤についても、チェーンの取り込み処理については、ブロックチェーンを勉強したときにServerCoreで一度やってるよね」

「そうだね。Coreノード用のBlockchainManagerをそのまま使えばいい。ただし、EdgeノードにはTransactionPoolがない、といった違いには注意が必要だ」

「それなら簡単にできそう」

ClientCoreの__handle_message()に追加する処理

  if msg[2] == RSP_FULL_CHAIN:
      # ブロックチェーン送信要求に応じて返却されたブロックチェーンを検証し、
      # 有効なものか検証した上で自分の持つチェーンと比較し
      # 優位な方を今後のブロックチェーンとして利用する
      new_block_chain = pickle.loads(msg[4].encode('utf8'))
      result, pool_4_orphan_blocks = self.bm.resolve_conflicts(new_block_chain)
      print('blockchain received form central', result)
      if result is not None:
          self.prev_block_hash = result
          print('callback called')
          self.callback() ・・・⑨の処理呼び出しはココ
      else:
          print('Received blockchain is useless...')

「これで、図5.8にある⑥から⑨の処理が実現できたわけだ」

「呼び出し口だけで、肝心の処理の中身ができてないけどね」

「引き続き図5.9を見ながら処理の中身を考えていこう。まず、①から④までの処理についてはどうだろう?」

ClientCoreを通じてBlockchainManagerの機能を呼び出すだけかな。格納されている全ブロックから個別にTransactionを取り出して、リストにして返せばいいと思う」

BlockchainManagerに追加する処理

  def get_stored_transactions_from_bc(self):
      print('get_stored_transactions_from_bc was called!')
      current_index = 1
      stored_transactions = []

      while current_index < len(self.chain):
          block = self.chain[current_index]
          transactions = block['transactions']

          for t in transactions:
              stored_transactions.append(json.loads(t))

          current_index += 1

      return stored_transactions

「返されたTransactionのリストをどうする?」

「取り出した全部のTransactionをUTXOManagerでチェックして、自分のアドレスである公開鍵に関連するものだけを抽出しないといけないよね。図5.9でいうと、⑤と⑥の部分。ただ、これは、前にWalletを作るときに書いた機能を呼び出すだけだね」

「となると、図5.8の最後で呼び出されるCallbackの中身はどうなるかな?」

「処理の手順としては、こういう流れになると思う」

Callbackの中で実行される処理の手順

図: Callbackの中で実行される処理の手順

「それじゃあ、その流れをコードにしてみよう」

Wallet_App.pyに追加する処理

  def update_callback(self):
      print('update_callback was called!')
      s_transactions
              = self.c_core.get_stored_transactions_from_bc() ・・・1に対応
      print(s_transactions)
      self.um.extract_utxos(s_transactions) ・・・ 2に対応
      self.update_balance() ・・・ 3に対応

「これでいけるはず。いやー、想像してたより、全然少ないコード量で済んじゃったな」

「いつもなら、ここでCoreノードとの接続を検証してみるところなんだけど、せっかくだからCoreノードまで仕上げてから最後に結合させよう」

「ブロックチェーンを作るときに、どんなTransactionでも取り込めるようにしたんだから、ここはまぁ普通に動いて当たり前だもんね」

5.1.3 ここまでのまとめ

「というわけで、ここまでの部分を図5.10に整理しておいた。①と⑤の部分について必要な処理を確認し、実際にコードを更新したわけだ」

現在の進捗状況

図5.10: 現在の進捗状況

「残りの②、③、④が片付けば、ひとまずは完成ってことだよね。いよいよ終わりが見えてきたぞー!」

5.2 サーバーを完成させよう

「それじゃあ、いよいよサーバーを完成させるぞ」

「確認だけど、サーバーっていうのは、Coreノードのことだよね?」

「そうだ。ここまではP2Pネットワークからの流れでCoreノードと呼んできていた。そろそろ機能も揃ってきたし、サーバーと呼ぶことにしよう。Walletと同様に、まずは今までで作ってきたサーバーの全体像から確認していこう。再確認の意味も含めて、自分で図を描いてごらん」

「りょうかい。Edgeノードから受け取ったTransactionからブロックを作って、それをサーバーで共有するところまで作ってきたから、図5.11のようになるね」

ここまでのサーバー全体像

図5.11: ここまでのサーバー全体像

「上出来だ。今回、ここから追加したい機能はなんだっけ?」

「うーんと、大きく分けて次の2つのタイミングで必要になる機能だと思う」

追加する機能

図: 追加する機能

「それぞれの要点を図にしてごらん」

「それぞれ、図5.12図5.13のような感じになるんじゃないかな」

Wallet側からTransactionを受信した後の処理

図5.12: Wallet側からTransactionを受信した後の処理

他のサーバーから新しいブロックを受信した場合の処理

図5.13: 他のサーバーから新しいブロックを受信した場合の処理

5.2.1 Transaction受信時の処理

図5.12図5.13にあげてもらったポイントの中で、これまで作っていなかった機能が追加で必要になる部分はどこかな?参考までに、これまでの実際の機能構成から、Transaction受信時の処理とデータの流れを図5.14に示しておこう」

これまでのTransaction受信時の処理の流れ

図5.14: これまでのTransaction受信時の処理の流れ

「まず、EdgeノードからTransactionを受信した後の処理、つまり図5.12で追加しないといけない処理は、大きくは次の3つだと思う」

EdgeノードからのTransactionを受信時に行うべき処理

図: EdgeノードからのTransactionを受信時に行うべき処理

「いいだろう。さらにオマケ的な話だけど、もう1つ次の機能を無駄がないように入れておくべきかな」

EdgeノードからのTransaction受信時に行うべき処理への追加

図: EdgeノードからのTransaction受信時に行うべき処理への追加

「なるほどー。いかにも必要になりそうだね」

「4つのポイントに合わせて、どこに機能が必要なのか、まとめてごらん」

図5.15のような関係になるかなあ」

新しく処理が必要になる箇所

図5.15: 新しく処理が必要になる箇所

「では、もう少し具体的に、コードを書きながら追加されるべき処理の中身について順を追って考えていこう」

デジタル署名の検証について

「まず、デジタル署名の検証について考えるね。RSAの鍵を使えるのはKeyManagerだけど、自分の鍵を使うわけじゃない処理でKeyManagerを使うのもおかしいと思う。新しいクラスを作った方がいいんじゃないかな。たとえばRSAUtilみたいな」

「その場合にRSAUtil側で持つべき処理はどんなステップを踏むことになると思う?」

「うーん……。大きく分けると2つの処理になるかな」

RSAUtil側で持つべき処理

図: RSAUtil側で持つべき処理

「そうだね。あえて追加するならば、Transactionのうち実際にInputとして使われているものを、デジタル署名の検証後の処理で使えるようについでに抜き出しておこう。そうやってInputとなるTransactionを返しておくようにすれば、後で無駄がないんじゃないかな」

「おお!なるほどたしかに!」

「じゃあ、ここまでの処理をコードに直してごらん」

「えーと……、こんな感じかな」

rsa_util.py

  import Crypto
  from Crypto.PublicKey import RSA
  from Crypto.Signature import PKCS1_v1_5
  from Crypto.Hash import SHA256

  import copy
  import binascii
  import json

  class RSAUtil:
      def __init__(self):
          pass

      def verify_signature(self, message, signature, sender_public_key):
          print('verify_signature was called')
          hashed_message = SHA256.new(message.encode('utf8'))
          verifier = PKCS1_v1_5.new(sender_public_key)
          result = verifier.verify(hashed_message,
                                  binascii.unhexlify(signature))
          print(result)
          return result

      def verify_sbc_transaction_sig(self, transaction):
          """
          simple_bitcoin のTransactionの署名の正当性を検証する
          """
          print('verify_sbc_transaction_sig was called')
          sender_pubkey_text, used_outputs
                        = self._get_pubkey_from_sbc_transaction(transaction)
          signature = transaction['signature']
          c_transaction = copy.deepcopy(transaction)
          del c_transaction['signature']
          target_txt = json.dumps(c_transaction, sort_keys=True)
          sender_pubkey = RSA.importKey(binascii.unhexlify(sender_pubkey_text))
          result = self.verify_signature(target_txt, signature, sender_pubkey)
          return result, used_outputs

      def _get_pubkey_from_sbc_transaction(self, transaction):
          print('_get_pubkey_from_sbc_transaction was called')
          input_t_list = transaction['inputs']
          used_outputs = []
          sender_pubkey = ''
          for i in input_t_list:
              idx = i['output_index']
              tx = i['transaction']['outputs'][idx]
              used_outputs.append(tx)
              sender_pubkey = tx['recipient']

          return sender_pubkey, used_outputs

「データの構造からして、必要な公開鍵を取り出す部分が、けっこうややこしいね……」

「うん。思わずブロック内のテキストデータのままでデジタル署名の検証の鍵に使おうとしてしまって、ちょっと危なかったよ」

「何も意識しないで検証のためにsignatureを除去すると、もとにしたデータ自身からもその項目が消えてしまうとか、割とPythonのハマりどころみたいな処理が満載だから気をつけないとね」

「ブロックチェーンのところで痛い目を見たばかりだったから今回は大丈夫だったけど、時間を置いたら忘れそうで怖い……」

Transactionの正当性の確認

「さて、気持ちを切り替えて、次はTransactionの正当性に関するチェックについて考えていこう」

「保存されてるブロックチェーンの中を見て確認するのだから、BlockchainManagerに確認機能を持たせるのが妥当かなって思う」

「そうだね。具体的にはどんな手順でどんな処理をすればいいだろう?」

「これも大きくは2つの観点があると思う」

Transaction正当性確認における2つの観点

図: Transaction正当性確認における2つの観点

「なるほど。使用済みであるかどうか、という1つめの観点はわかりやすいけど、2つめはどういう観点だろうか?」

「Walletアプリで送金Transactionを実験的に作ってるときに思い出したんだけど、CoinbaseTransactionを誰でも無制限に作れてしまうと、暗号通貨として事実上なんの意味もなくなると思うんだよね」

「たしかにそうだね。実験では便利だけど、ブロック生成時以外にCoinbaseTransactionを誰でも作れてしまうのは、セキュリティ的にダメすぎる。この2つの処理をそれぞれコードにできるかい?」

「それぞれこんな感じの処理に分けるのがいいと思う」

BlockchainManagerに追加する処理

  def has_this_output_in_my_chain(self, transaction_output):
      """
      保存されているブロックチェーン内ですでにこの
      TransactionOutputがInputとして使われていないか?の確認

      返り値はTrueがすでに存在しているということだから、
      Transactionの有効性としてNGを意味していることに注意
      """
      print('has_this_output_in_my_chain was called!')
      current_index = 1

      if len(self.chain) == 1:
          print('only the genesis block is in my chain')
          return False

      while current_index < len(self.chain):
          block = self.chain[current_index]
          transactions = block['transactions']

          for t in transactions:
              t = json.loads(t)
              if t['t_type'] == 'basic' or t['t_type']
                                              == 'coinbase_transaction':
                  if t['inputs'] != []:
                      inputs_t = t['inputs']
                      for it in inputs_t:
                          print(it['transaction']['outputs'][it['output_index']])
                          if it['transaction']['outputs'][it['output_index']]
                                                == transaction_output:
                              print('This TransactionOutput was already used',
                                    transaction_output)
                              return True

          current_index += 1

      return False


  def is_valid_output_in_my_chain(self, transaction_output):
      """
      チェーン内で認知されていない不正なTransactionを使ってないか確認
      テスト用に気軽にCoinbaseTransaction使えなくなるので有効化させるときは注意
      """
      print('is_valid_output_in_my_chain was called!')
      current_index = 1

      while current_index < len(self.chain):
          block = self.chain[current_index]
          transactions = block['transactions']

          for t in transactions:
              t = json.loads(t)
              if t['t_type'] == 'basic' or t['t_type']
                                              == 'coinbase_transaction':
                  outputs_t = t['outputs']
                  for ot in outputs_t:
                      if ot == transaction_output:
                          return True

          current_index += 1

      return False

is_valid_output_in_my_chainのほうは、テストのときは動作確認が異様に辛くなる厄介な機能になりそうだから、気をつけて有効化をコントロールしないとね」

「たしかにそうかも」

二重支払い予防は念入りに ―― TransactionPoolの中もチェックしておこう

「それじゃあ、ブロックチェーンに続いて、TransactionPoolの中の重複チェックを考えていこう」

TransactionPoolの中にあるときは、すでに辞書型になってるから、比較の際に文字列化は不要だよね。それ以外、中の処理としては、ブロックチェーン内のTransactionとの比較とほとんど同じだと思う」

TransactionPoolに追加する処理

  def has_this_output_in_my_tp(self, transaction_output):
      """
      TransactionPool内ですでにこのTransactionOutputが
      Inputとして使われていないか?の確認
      """
      print('has_this_output_in_my_tp is called')
      transactions = self.transactions
      for t in transactions:
          inputs_t = t['inputs']
          for it in inputs_t:
              if it == transaction_output:
                  return True

      return False

「そうだね。ここにテクニカルな悩みどころはないだろう」

オマケ:暗号通貨用のTransactionかどうかの判定

「ブロックチェーンを探る処理には時間がかかる。だから、そもそも暗号通貨用途以外のTransactionが混じっていたら、その前の時点で検証から除外したほうがいい」

「うん。なくてもいい機能だけど、便利なので、このタイミングでつけておくことにするんだよね」

「そうだ。Transaction群の中に暗号通貨以外のものが混じってくるような状況が将来的に出てくる可能性もあるからな」

「だとしたら、この機能はUTXOManagerに持たせておこうかな」

UTXOManagerに追加する処理

  def is_sbc_transaction(self, tx):
      """
      暗号通貨用のTransactionかそれ以外かを判定する。
      便利なので種別も一緒に返す
      """
      print(tx['t_type'])
      tx_t = tx['t_type']

      t_basic = 'basic'
      t_coinbase = 'coinbase_transaction'
      unknown = 'unknown'

      if tx_t != t_basic:
          if tx_t != t_coinbase:
              return False, unknown
          else:
              return True, t_coinbase
      else:
          return True, t_basic

「便利そうだから、CoinbaseTransactionか、それとも通常のTransactionか、その判定もつけといたよ」

新しく用意した機能をServerCoreから呼び出すために

「ここまでは機能単位で作り込んできたわけだが、そろそろ実際に利用できる形で組み込んでいこう」

「Transaction自身は、Callbackを通じてConnectionManagerからServerCoreに届くようになってるから、そこからデータの有効性チェックをまとめて呼び出せるようになるのが一番効率が良さそうかな」

「ふむ。つまり、ServerCore__handle_message()の中だね」

「そう。こんな感じ」

ServerCoreの__handle_message()の中に追加する処理

  if msg[2] == MSG_NEW_TRANSACTION:
      new_transaction = json.loads(msg[4])
      print('received new_transaction', new_transaction)
      is_sbc_t, _ = self.um.is_sbc_transaction(new_transaction)
      current_transactions = self.tp.get_stored_transactions()
      if new_transaction in current_transactions:
          print('this is already pooled transaction: ', new_transaction)
          return

          if not is_sbc_t:
              print('this is not SimpleBitcoin transaction: ', new_transaction)
          else:
              # テスト用に最初のブロックだけ
              # 未知のCoinbaseTransactionを許すための暫定処置
              if self.bm.get_my_chain_length() != 1:
                  checked = self._check_availability_of_transaction(
                                                          new_transaction)
                  if not checked:
                      print('Transaction Verification Error')
                      return
          self.tp.set_new_transaction(new_transaction)

          if not is_core:
              new_message = self.cm.get_message_text(MSG_NEW_TRANSACTION,
                                                json.dumps(new_transaction))
              self.cm.send_msg_to_all_peer(new_message)
      else:
          if not is_sbc_t:
              print('this is not SimpleBitcoin transaction: ',
                    new_transaction)
          else:
              # テスト用に最初のブロックだけ
              # 未知のCoinbaseTransactionを許すための暫定処置
              if self.bm.get_my_chain_length() != 1:
                  checked = self._check_availability_of_transaction(
                                                            new_transaction)
                  if not checked:
                      print('Transaction Verification Error')
                      return
          self.tp.set_new_transaction(new_transaction)

          if not is_core:
              new_message = self.cm.get_message_text(MSG_NEW_TRANSACTION,
                                              json.dumps(new_transaction))
              self.cm.send_msg_to_all_peer(new_message)

「呼び出してる_check_availability_of_transactionはどうする?」

_check_availability_of_transaction自体は、こんなふうに書けばいいかなって」

_check_availability_of_transaction()の中身

  def _check_availability_of_transaction(self, transaction):
      """
      Transactionに含まれているTransactionInputの
      有効性(二重使用)を検証する
      """
      v_result, used_outputs = self.rsa_util.verify_sbc_transaction_sig(
                                                              transaction)
      if v_result is not True:
          print('signature verification error on new transaction')
          return False

      for used_o in used_outputs:
          print('used_o', used_o)
          bm_v_result = self.bm.has_this_output_in_my_chain(used_o)
          tp_v_result = self.tp.has_this_output_in_my_tp(used_o)
          bm_v_result2 = self.bm.is_valid_output_in_my_chain(used_o)
          if bm_v_result:
              print('This TransactionOutput is already used', used_o)
              return False
          if tp_v_result:
              print('This TransactionOutput is already
                    stored in the TransactionPool', used_o)
              return False
          if bm_v_result2 is not True:
              print('This TransactionOutput is unknown', used_o)
              return False

      return True

「ふむ。良さそうだね。次はCoinbaseTransactionの生成について見ていこうか」

「おー」

5.2.2 CoinbaseTransactionの生成

CoinbaseTransactionが追加されるのは、ブロックの生成タイミングだけなので、処理を入れ込むタイミングはすでに自明と言っていいだろう」

「そうだね」

「あえて気をつけるところといえば、TransactionPool内にあるすべてのTransactionをチェックして、CoinbaseTransactionに追加で含められる手数料を計算する処理が必要ってことくらいかな」

「その機能はどこにつけるべき?」

TransactionPool内のTransactionが対象なんだから、TransactionPoolが持てばいいんじゃないかな」

「それじゃあ、こんな感じで。サポート外のTransactionははじくようにしておこう」

TransactionPoolに追加すべき手数料計算の処理

  def get_total_fee_from_tp(self):
      """
      TransactionPool内に格納されているTransactionすべての手数料の合計値を算出する
      """
      print('get_total_fee_from_tp is called')
      transactions = self.transactions
      result = 0
      for t in transactions:
          checked = self.check_type_of_transaction(t)
          if checked:
              total_in = sum(
                i['transaction']['outputs'][i['output_index']]['value']
                for i in t['inputs'])
              total_out = sum(o['value'] for o in t['outputs'])
              delta = total_in  - total_out
              result += delta

      return result

  def check_type_of_transaction(self, transaction):
      if transaction['t_type'] == 'basic' or transaction['t_type']
                                == 'coinbase_transaction':
          return True
      else:
          return False

「いいだろう。あとは、これと組み合わせて、ブロック生成の処理を改良するだけだね」

「全体の流れとしては図5.16のようになると思う」

CoinbaseTransactionの追加からブロック生成まで

図5.16: CoinbaseTransactionの追加からブロック生成まで

「つまり?」

「手順としては、こんな感じ。4と5の処理は図5.17にしておいたよ」

ブロック生成までの処理手順

図: ブロック生成までの処理手順

CoinbaseTransactionの扱い

図5.17: CoinbaseTransactionの扱い

「うん、いいと思う。それで、どんなコードになるだろう?」

「コードに直すとこんな感じかな」

ServerCoreのブロック生成処理への修正

  def __generate_block_with_tp(self):

      print('Thread for generate_block_with_tp started!')
      while not self.flag_stop_block_build:
          self.is_bb_running = True
          prev_hash = copy.copy(self.prev_block_hash)
          result = self.tp.get_stored_transactions()
          if len(result) == 0:
              print('Transaction Pool is empty ...')
              break
          new_tp = self.bm.remove_useless_transaction(result)
          self.tp.renew_my_transactions(new_tp)
          if len(new_tp) == 0:
              break
          # リワードとしてリストの先頭に自分宛のCoinbaseTransactionを追加する
          total_fee = self.tp.get_total_fee_from_tp()
          # TODO: インセンティブの値をここに直書きするのはイケてないので後で対処する
          total_fee += 30
          my_coinbase_t = CoinbaseTransaction(self.km.my_address(), total_fee)
          transactions_4_block = copy.deepcopy(new_tp)
          transactions_4_block.insert(0, my_coinbase_t.to_dict())
          new_block = self.bb.generate_new_block(transactions_4_block,
                                                prev_hash)
          # タイミングがどうしてもクロスするので念のため保存前に再度確認
          if new_block.to_dict()['previous_block'] == self.prev_block_hash:
              self.bm.set_new_block(new_block.to_dict())
              self.prev_block_hash = self.bm.get_hash(new_block.to_dict())
              msg_new_block = self.cm.get_message_text(MSG_NEW_BLOCK,
                                              json.dumps(new_block.to_dict()))
              self.cm.send_msg_to_all_peer(msg_new_block)
              # ブロック生成に成功したらTransaction Poolはクリアする
              index = len(new_tp)
              self.tp.clear_my_transactions(index)
              break
          else:
              print('Bad block. It seems someone already win the PoW.')
              break

      print('Current Blockchain is ... ', self.bm.chain)
      print('Current prev_block_hash is ... ', self.prev_block_hash)
      self.flag_stop_block_build = False
      self.is_bb_running = False
      self.bb_timer = threading.Timer(CHECK_INTERVAL,
                                      self.__generate_block_with_tp)
      self.bb_timer.start()

「せっかくCoinbaseTransactionを作り出すのだから、ここで作ったSimpleBitcoinを自分のWalletから使えるようにしておくといいんじゃないかな?」

「たしかに。Coreノードとしてサーバーを起動するときにパスフレーズを渡しておくと、Walletで読み込み可能なPEM形式のファイルを書き出す、というくらいの処理なら、割と少ない変更で書けそう」

KeyManagerの初期化プロセスへの修正

  def __init__(self, privatekey_text = None, pass_phrase= None):
      print('Initializing KeyManager...')
      if privatekey_text:
          self.import_key_pair(privatekey_text, pass_phrase)
      else:
          random_gen = Crypto.Random.new().read
          self._private_key = RSA.generate(2048, random_gen)
          self._public_key = self._private_key.publickey()
          self._signer = PKCS1_v1_5.new(self._private_key)
          if pass_phrase is not None:
              my_pem = self.export_key_pair(pass_phrase)
              my_pem_hex = binascii.hexlify(my_pem).decode('ascii')
              # とりあえずファイル名は固定
              path = 'my_server_key_pair.pem'
              f1 = open(path,'a')
              f1.write(my_pem_hex)
              f1.close()

「いいね。ServerCoreの初期化プロセスでKeyManagerを設定するところもパラメーターが増えているから、気をつけておこう」

「大丈夫。このくらいならなんとか!起動時にファイルを作るだけじゃなく、後で自分のWalletの鍵を使ってサーバーを運用したいみたいな話までカバーしようとすると、ちょっと面倒そうだけど……」

「よし。それでは次にいこう」

「次は、Transactionじゃなくてブロックの正当性確認か」

5.2.3 受信したブロックの正当性確認

「ということで、受信したブロックの正当性に関するチェックについて考えていこう」

「基本的には、受信したTransactionを検証するときに確認すべき項目と同じことを、ブロックから取り出した各Transactionに対して実行できればいいと思うんだよね」

「基本的には、そのとおりだ。しかし、完全に同じではない。では、追加で気をつける必要があるのは、どんな点だろう?」

CoinbaseTransactionに対する注意だと思う」

「そうだね。CoinbaseTransactionは、Transactionのチェック時には存在していなかった。だから、そこが重要なポイントになる。まずはそこから見ていこうか」

「うん」

「それに、ブロックの生成時は、一番悪いことができるタイミングでもある。どのような悪いことができそうかな?」

「パッと思いつくのは、この辺かな……」

ブロック生成の際に考えられる4つの悪用パターン

図: ブロック生成の際に考えられる4つの悪用パターン

「うん、そんなところだろう。悪用1と2は、ブロックにおける検証に特有の話だ。一方、悪用3と4は、Transactionの検証にも共通する話だ」

「そうかー」

「じゃあ、過剰な手数料やインセンティブが載っていないかのチェックから見てみよう。どんな処理になると思う?」

「処理の流れはこんな感じになると思う」

手数料チェックの流れ

図: 手数料チェックの流れ

「良さそうだね。2のインセンティブは、どうやって決める?」

「うーん。とりあえず、SimpleBitcoinでは、固定ということでいんじゃないかな。30くらいで」

「いいだろう。では、1からコードにしていこうか。手数料を計算する処理はどこで担当するのがいいだろう?」

「ブロックのTransaction群はCoinbaseTransactionも含んでるから、それをキレイに除外しようとすると、BlockchainManagerを使うよりもServerCoreでやるほうがいいかな。こんな感じで」

ServerCoreに追加する処理その1

  def get_total_fee_on_block(self, block):
      """
      ブロックに格納されているbasicなTransactionすべての手数料の合計値を算出する
      """
      print('get_total_fee_on_block is called')
      transactions = block['transactions']
      result = 0
      for t in transactions:
          t = json.loads(t)
          is_sbc_t, t_type = self.um.is_sbc_transaction(t)
          if t_type == 'basic':
              total_in = sum(
                i['transaction']['outputs'][i['output_index']]['value']
                for i in t['inputs'])
              total_out = sum(o['value'] for o in t['outputs'])
              delta = total_in  - total_out
              result += delta

      return result

「インセンティブの値は固定ということにしたから、2つめの処理はただの足し算だね。3つめはどういうふうに作ろうか?」

「ブロックの中のTransaction群からCoinbaseTransactionを取り出して、そこでvalueにセットされている値を取り出して比較するのが素直だと思う」

ServerCoreへ追加する処理その2

  def check_transactions_in_new_block(self, block):
      """
      ブロック内のTranactionに不正がないか確認する
      """
      fee_for_block = self.get_total_fee_on_block(block)
      fee_for_block += 30
      print("fee_for_block: ", fee_for_block)

      transactions = block['transactions']

      for t in transactions:
          t = json.loads(t)
          # basic, coinbase_transaction以外はスルーチェック
          is_sbc_t, t_type = self.um.is_sbc_transaction(t)
          if is_sbc_t:
              if t_type == 'basic':
                  if self._check_availability_of_transaction_in_block(t)
                      is not True:
                      print('Bad Block. Having invalid Transaction')
                      return False
              elif t_type == 'coinbase_transaction':
                  insentive = t['outputs'][0]['value']
                  print('insentive', insentive)
                  if insentive != fee_for_block:
                      print('Invalid value in fee for CoinbaseTransaction',
                            insentive)
                      return False

      print('ok. this block is acceptable.')
      return True

「いいね。ではここに、悪用ポイントの2つめ、つまり『2つ以上のCoinbaseTransactionがセット』されていないかのチェックも加えてみようか?」

「ループの最初以外のCoinbaseTransactionは認めないって形にすればいいだけだから、これは簡単かな」

ServerCoreへ追加する処理その2+

  def check_transactions_in_new_block(self, block):
   <中略>
      counter = 0

      for t in transactions:
          t = json.loads(t)
          # basic, coinbase_transaction以外はスルーチェック
          is_sbc_t, t_type = self.um.is_sbc_transaction(t)
          if is_sbc_t:
              if t_type == 'basic':
                  <中略>
              elif t_type == 'coinbase_transaction':
                  if counter != 0:
                      print('Coinbase Transaction is only for BlockBuilder')
                      return False
                  else:
                      insentive = t['outputs'][0]['value']
                    <以下略>

「うん。いいだろう。それじゃあ、通常のTransactionに対するチェック処理、つまり悪用ポイントの3と4への対処について考えていこうか」

「さっき書いたコードではTODOにしていたとこだね」

「ここはTransactionの検証処理とほぼ同じだ。ただし、唯一の違いとして、TransactionPoolの中をチェックする必要はない」

「ってことは、こんな感じでいいかな。共通な処理が多いから、本当はもう少しキレイに書けるのかもしれないけど……」

ServerCoreへ追加する処理その3

  def _check_availability_of_transaction_in_block(self, transaction):
      """
      Transactionの有効性を検証する(Block用)
      """
      v_result, used_outputs = self.km.verify_sbc_transaction_sig(transaction)
      if v_result is not True:
          print('signature verification error on new transaction')
          return False

      for used_o in used_outputs:
          print('used_o: ',used_o)
          bm_v_result = self.bm.has_this_output_in_my_chain(used_o)
          bm_v_result2 = self.bm.is_valid_output_in_my_chain(used_o)
          if bm_v_result2 is not True:
              print('This TransactionOutput is unknown', used_o)
              return False
          if bm_v_result:
              print('This TransactionOutput is already used', used_o)
              return False

      return True

「なるほど。これをcheck_transactions_in_new_blockの中で呼び出して、結果の真偽を確認しようってわけだね」

5.2.4 ここまでのまとめ(サーバー編)

「えーと、完成……ってことでいいかな、兄さん?」

「そうだね。ここまでのまとめをしてみよう。図5.18のとおり、②、③、④の部分について必要な処理を確認し、実際にコードを更新した」

現在の進捗状況

図5.18: 現在の進捗状況

「うん。次はいよいよ、Walletとサーバーを接続しての最終確認だ!」

  • この書籍は無料で各章の半分まで読めます。
  • クラウドファンディングでのご購入、電子版をご購入のお客様は、全文をお読みいただけます。
  • 閲覧にはご購入されたアカウントでのログイン が必要です。
  • 製本版をご購入の方は別途電子版のご購入 が必要となります。