『ゼロから創る暗号通貨』
第3章: Hello Blockchain ! : こんにちはブロックチェーン

濵津 誠/hamatz

第3章 Hello Blockchain ! : こんにちはブロックチェーン

本章ではブロックチェーンの仕組みについて学びます。まず、単純なブロックチェーンを作成してみることで、ブロックの連鎖が何を担保しているのか、その意味を確認します。その後、それをベースにして、第2章で作成したP2Pネットワークの機能へと繋ぎ込みます。さらに、Proof of Workやコンセンサスルールといった暗号通貨を成立させるためのアイデアについて学び、それらを取り込む形で暗号通貨の基盤となり得るシステムを準備していきます。ポイントとなるのは次のような内容です。

  1. ブロックチェーンが保証するデータの一貫性はどのようにして実現されているか?
  2. P2Pネットワーク上のシステムであることを前提としていることから、どのような点に注意が必要になるか?
  3. 利害関係が異なる不特定多数の人々に、1つのブロックチェーンを共有することを合意させるためのアイデアであるProof of Work(コンセンサス)とは、どういうものか?

本章が終わるころには、暗号通貨以外への応用も可能なブロックチェーンアプリケーションの基盤が完成することとなるでしょう。

[2pt_line]

「P2Pネットワークが完成したところで、いよいよブロックチェーンを作り込んでいこう。全体像と照らし合わせると図3.1で示す箇所になる」

今回作る機能ブロックについて

図3.1: 今回作る機能ブロックについて

「高まる!」

「……」

「え、ぼく何か変なこと言った?」

「実装して気分が高まるのはいいけれど、まずはブロックチェーンとは何かを再確認していくぞ。ブロックチェーンとは、文字どおりブロックの連鎖だ。じゃあ、そのブロックというのは、一体どんな構造をしていればいいと思う?」

「これから作るSimpleBitcoinでは、ポイントをやり取りするわけだから、そのポイントのデータだと思ってたけど」

「そこまでは合ってるよ。ブロックチェーンで内容を保証したいデータ、今の例であればポイントは、当然ブロックの一部でなければならない。だが、それだけではない。図3.2を見てほしい」

ブロックチェーンを構成するブロックとは何か?

図3.2: ブロックチェーンを構成するブロックとは何か?

図3.2のように、基本的には、『内容を保障したいデータ』と『前段のブロックからハッシュ値をとったもの』をセットにして1つのブロックが構成されるイメージになる」

「ハッシュ値っていうのは?」

「入力に対応した固定長の擬似的な乱数を返す、ハッシュ関数と呼ばれるものがある。ハッシュ関数に何らかの値を入力して得られる値がハッシュ値だ。ハッシュ関数では、同じ入力からは常に同じ値が得られる。そのため、ハッシュ値は、本来の意味での乱数ではない。しかし、出力値をみて入力値を特定することが非常に困難であるという性質がある。乱数と同様に意味を持たないデータというくらいに考えてほしい」

「で、その『前段のブロックのハッシュ値をとったもの』があると何が嬉しいの?」

「いま、図3.2におけるブロック1の内容を、ブロックチェーンを作った後で変更したとしよう。そうすると、ブロック2の中に格納されているハッシュ値と、実際のブロック1から計算したハッシュ値が、一致しなくなるだろう?この性質を利用することで、ブロックチェーンが一貫性のあるデータであることを保証できる」

「なるほど。どこか1か所でも改ざんされると、その改ざんされたブロック以降のすべてのブロックが信頼できないものになってしまうのか。そして、そのブロックチェーンが全体として意味をなさないものになってしまう、と」

「そのとおり。逆に、あるブロックのハッシュ値と次の段のブロックに含まれているハッシュ値が一致するような連鎖であれば、そのブロックチェーン全体の一貫性が保証される」

「そういうことかー。でも、改ざんした後で後続のブロックも含めて全部ハッシュ値を再計算したら、一貫性はあるように見えるのでは?」

「そういう問題はある。それをどう解決するかについては、もう少し話が進んでから考えよう」

「なるほど。ところで、連鎖というからには一番先頭のブロックがあると思うんだけど、そのブロックでは何のハッシュ値を使うの?」

「いい質問だね。P2Pネットワークにおいて始原となるCoreノードが存在していたように、ブロックチェーンにおいても始原となるブロックが存在することになっているのさ。この始原となるブロックは、一般にGenesisブロックと呼ばれている。そこで、SimpleBitcoinでもそう呼ぶことにしよう」

「Genesisブロックについては、例外的に前段のブロックを使わないということだね」

「そういうことだ。話を整理すると図3.3のようになる」

ブロックチェーンはGenesisブロックを始原として構成される

図3.3: ブロックチェーンはGenesisブロックを始原として構成される

「基本形を押さえたところで、いよいよコードに落とし込む作業に入ろうか」

「りょーかい!」

3.1 簡単なブロックチェーンを作ってみる

「暗号通貨として利用することを考えると、他のノードから通知されたTransactionを使ってブロックを生成し、自身が管理しているブロックチェーンを更新していく、という形が基本になるだろう。つまり、図3.4のような形だ」

ブロックが生成されてチェーンに登録されるまでの流れ

図3.4: ブロックが生成されてチェーンに登録されるまでの流れ

「他のノードから新しく作られたブロックが通知される場合があるから、ブロックを作る役割と、保存・管理する役割を分けておくわけだね」

「そうだ。とはいえ、いきなり既存のコードに追加すると動作確認が大変だし、最初はシンプルに作り始めるほうがいい。そこで、まずは図3.5のような形から始めることにしよう」

ブロックチェーン最初の構成

図3.5: ブロックチェーン最初の構成

「じゃあ、ブロックの生成を依頼するStep1から考えてみるかー。図3.4を思い返すと、ブロック内に含まれているべき情報って、基本的にはTransactionと、前のブロックのハッシュ値なんだよな」

「更新の差分だけ欲しい、という場合に対応できるように、タイムスタンプくらいは仕込んでおくほうがいいだろう」

「わかった。ということは、ブロックのクラスはこんな感じかな」

リスト3.1: Block.py

    from time import time

    class Block:
        def __init__(self, transaction, previous_block_hash):
            self.timestamp = time()
            self.transaction = transaction
            self.previous_block = previous_block_hash

「このクラスは、Genesisブロックでも共用できる?」

「前段のブロックが存在しないという特徴があるだけなので、そこをセットしなければいいだけだから、大丈夫じゃないかなぁ」

「それじゃあ、そのまま進めてみよう。次は、図3.5でいうところのBlockBuilderを考えてごらん」

「ブロックの生成を頼んで、できあがったものが返ってくる、Step1とStep2で実際に叩かれる部分だね」

リスト3.2: BlockBuilder.py

  from .block import Block

  class BlockBuilder:
      def __init__(self):
        print('Initializing BlockBuilder...')
        pass

      def generate_new_block(self, transaction, previous_block_hash):
          new_block = Block(transaction, previous_block_hash)
          return new_block

「いいだろう。では、できあがったブロックを保存するStep3にはどんな処理が必要になるだろう?」

「返ってきたブロックは、普通にどんどんリストに追加していけばいいと思うんだよね。つまりこんな感じで大丈夫なハズ……!」

リスト3.3: BlockchainManager.py

 import threading


  class BlockchainManager:
      def __init__(self):
          print('Initializing BlockchainManager...')
          self.chain = []
          self.lock = threading.Lock()

      def set_new_block(self, block):
          with self.lock:
              self.chain.append(block)

「ふむ。それじゃあ、実際に動かしてみようか。BlockBuilderBlockchainManagerをどういうふうに使えばいいかな?」

「今回は簡単……、あれ、簡単じゃない?あれれ?あ、ちょっと待って!」

「どうした?」

「作ったブロックのハッシュ値って、どうやって計算するんだろう?Genesisブロックはともかく、このままだと次のブロックを作る処理ができないや!」

「そうだねぇ。じゃあ、どうすればいいと思う?」

「ハッシュ値の計算機能は、BlockchainManagerに持たせればいいと思うんだけど……」

「どうしてそう思うんだい?」

「他のノードからブロックが送られてくることもあり得るわけだから、届いたブロックが正しいか検証するような機能がどのみち必要になると思うんだよね。すでに保存済みのブロックのうちで一番新しいやつを取り出し、そのハッシュ値を計算したあとで、新しいブロックの中に含まれるprevious_blockの中の値と比較する、といった処理が必要になると思うんだ」

「よくできました。じゃあ、それを組み込んでみよう」

「うーんと、ハッシュ値を計算するにあたって、入力データってどう扱うべきなんだろう?ブロックを表すオブジェクトBlockが返ってきてるけど、よく考えたら、これをそのままハッシュ値を求めるのに使うのって変だよね?」

「いいところに気づいたね。これは実際に作ろうとしてはじめて気がつくようなポイントの1つといえる」

「そうなのかー。図3.4を見てわかった気になってた。うぬぬ……」

「ちょっと見方を変えてみよう。Blockができあがったときに、他のノードに通信で投げる場合、どういう形式にするのが汎用性が高くなると思う?」

「Python以外でもノードを作りたい、みたいなところまで考えると、やっぱりJSONとかかなー」

「どうやってJSONに直せばいいと思う?」

「できあがったBlock自身が、JSONを作り出すメソッドを持ってればいいんじゃないかなぁ」

「でも、BlockchainManagerの中にテキスト化したJSONデータを保存するようにしちゃうと、取り出しが不便じゃないかな?」

「そうか。となると、辞書型にしておいて、送信前に文字列化するような処理を挟むのがベストな気がしてきた」

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

「まず、Blockのデータを辞書型に変換するメソッドを用意するね」

Blockのデータを辞書型に変換する機能をつける

  def to_dict(self):
      d = {
          "timestamp" : self.timestamp,
          "transaction": json.dumps(self.transaction),
          "previous_block": self.previous_block,
      }
      return d

「次に、辞書型に変換したBlockのデータを文字列に直してからハッシュ値を得るための処理を、BlockchainManagerにつける」

Blockのデータからハッシュ値を算出する機能をつける

  def _get_double_sha256(self, message):
    return hashlib.sha256(hashlib.sha256(message).digest()).digest()

  def get_hash(self,block):
      block_string = json.dumps(block, sort_keys=True)
      return binascii.hexlify(self._get_double_sha256((block_string)
                              .encode('utf-8'))).decode('ascii')

「文字列へと変換する前にソートをかけてるのは、どうしてだい?」

「ハッシュの計算結果がノードごとにズレる可能性があるなと思って」

「ハッシュ関数sha256を二重にかけてるのは?」

「ビットコインがそうしてるって聞いたことがあったから……」

「妙なとこだけ詳しいね」

「そこは普通に褒めてくれていいんじゃないのー?」

「まあまあ。それじゃあ動作確認といこうか。もう考慮漏れはないかな?」

「ないと思う。実際にBlockchainManagerBlockBuilderを使うようなコードを書いてみるね」

リスト3.4: Sample_Blockchain.py

  from blockchain.blockchain_manager import BlockchainManager
  from blockchain.block_builder import BlockBuilder


  def main():
      bb = BlockBuilder()
      my_genesis_block = bb.generate_genesis_block()
      bm = BlockchainManager(my_genesis_block.to_dict())

      prev_block_hash = bm.get_hash(my_genesis_block.to_dict())
      print('genesis_block_hash :' , prev_block_hash)

      transaction = {
          'sender': 'test1',
          'recipient': 'test2',
          'value' : 3
      }

      new_block = bb.generate_new_block(transaction, prev_block_hash)
      bm.set_new_block(new_block.to_dict())

      new_block_hash = bm.get_hash(new_block.to_dict())
      print('1st_block_hash :' , new_block_hash)

      transaction2 = {
          'sender': 'test1',
          'recipient': 'test3',
          'value' : 2
      }
      new_block2 = bb.generate_new_block(transaction2, new_block_hash)
      bm.set_new_block(new_block2.to_dict())

      print(bm.chain)
      chain = bm.chain
      print(bm.is_valid(chain))

  if __name__ == '__main__':
      main()

「それじゃあ実行してごらん」

「よろしくお願いしまぁぁぁす!」

Genesisブロック

  Initializing BlockBuilder...
  Initializing BlockchainManager...
  genesis_block_hash : d68ed31f229bac4312e0575842....
  1st_block_hash : a184d91adfe30edbbceb2e28ce230a....
  [{'timestamp': 1530994005.6692312,
  'transaction': '"this_is_simple_bitcoin_genesis_block"',
  'previous_block': None},{'timestamp': 1530994005.670075,
  'transaction': '{"sender": "test1", "recipient": "test2", "value": 3}',
  'previous_block': 'd68ed31f229bac4312e05758....'},
  {'timestamp': 1530994005.670194,
  'transaction': '{"sender": "test1", "recipient": "test3", "value": 2}',
  'previous_block': 'a184d91adfe30edbbceb2e28....'}]

「おお。なんかちゃんと動いてそう」

「そのようだね」

「ただなぁ……」

「どうした?」

「動かしてみてはじめて気づいたんだけど、Genesisブロックは特別なブロックとして、リストの先頭にわかりやすく存在していてほしいなって思った」

「いまのところ、他のブロックと同じクラスを共用することにしていたからね」

「それに、Genesisブロックに入れておく値は、SimpleBitcoinとして1つに定めておくべきという気がした。ユーザーのコードでセットできるのは、よろしくないと思う」

「なるほど」

「というわけで、Genesisブロックですよってフラグを立てられるようにした上で、新規にクラスを作ろうと思った。もちろん無駄な手間をかけないようにBlockクラスをベースにはするけども」

リスト3.5: GenesisBlock

  class GenesisBlock(Block):
      """
      前方にブロックを持たないブロックチェーンの始原となるブロック。
      transaction にセットしているのは
      「{"message":"this_is_simple_bitcoin_genesis_block"}」をSHA256で
      ハッシュしたもの。深い意味はない
      """
      def __init__(self):
          super().__init__(transaction="AD9B477B42B22CDF18B1335603D....",
                            previous_block_hash=None)

      def to_dict(self):
          d = {
              "transaction": self.transaction,
              "genesis_block": True,
          }
          return d

「Genesisブロックではtimestampはセットしないことにしたんだね」

「すべてのCoreノードで共通のブロックチェーンを作っていくためには、全部のノードで同じGenesisブロックを共有しないといけないから」

「うん、いい決定だと思う」

「あと、実行してみて気づいたんだけど、コンソールの字を目で見て確認するのがめんどくさいなって思った」

「たしかに、いまの出力だと、正しいブロックチェーンかどうか確かめるのがつらいよね」

「そこで、ついでと言っちゃアレだけど、どうせ後でブロックチェーンの正しさを検証する必要が出てくるし、BlockchainManagerにそのための機能もつけちゃうことにした」

ブロックチェーンの正当性を検証する

  def is_valid(self,chain):

      last_block = chain[0]
      current_index = 1

      while current_index < len(chain):
          block = self.chain[current_index]
          if block['previous_block'] != self.get_hash(last_block):
              return False

          last_block = block
          current_index += 1

      return True

「このブロックチェーンの検証処理を、さっき実行したコードの最後に入れるね」

ブロックチェーンの正当性を検証するために追加したコード

  chain = bm.chain
  print(bm.is_valid(chain))

「ふむ。いいね。それじゃあ、これで動作確認してみようか」

変更したコードの実行結果

Initializing BlockBuilder...
Initializing BlockchainManager...
genesis_block_hash : 9eb495059d43529161a1c9b9aead63fd....
1st_block_hash : 144eedb744dfed3fdcddbda67207be527d2f....
[{'timestamp': 1530995289.335698,
'transaction': 'AD9B477B42B22CDF18B1335603D07378ACE8....',
'genesis_block': True}, {'timestamp': 1530995289.336798,
'transaction': '{"sender": "test1", "recipient": "test2", "value": 3}',
'previous_block': '9eb495059d43529161a1c9b9aead63fdf7....'},
{'timestamp': 1530995289.336915,
'transaction': '{"sender": "test1", "recipient": "test3", "value": 2}',
'previous_block': '144eedb744dfed3fdcddbda67207be527d....'}]
True

「うん。大丈夫そう」

「そのようだね。ベースができあがったところで次に進むとしようか!」

3.2 P2Pネットワーク機能に繋ぎ込もう

「さて、ブロックチェーンの基本形はできあがったわけだが、今のままだと非常に効率が悪い」

「効率?どういうこと?」

「ブロックの定義が『内容を保障したいデータ』と『前段のブロックからハッシュ値をとったもの』のセットであるなら、『内容を保証したいデータ』の中身は自由であっていいはずだ。ところが、今の実装だとTransactionが1つ追加されるたびにブロックを生成することになる」

「そうか、別に、いくつかのTransactionをまとめて1つのブロックを作ってもいいよね」

「そう。今のままだと、ブロックチェーンが無駄に長くなる。ある程度までTransactionが溜まるのを待って、1つにまとめてからブロックを作ったところで、それほど大きな問題はなさそうだろう?」

「うん」

「ブロックチェーンが頻繁に更新されることが前提になっていると、EdgeノードからCoreノードへのブロックチェーンの更新問い合わせ頻度が上がるから、Edgeノードが増えれば増えるほど通信機会が加速度的に増えていってしまう。これは問題が多そうだ」

「たしかに。更新間隔を延ばすことで問い合わせ回数も減らせるわけかー」

「更新間隔を延ばす工夫はEdgeノードからの問い合わせタイミングを削減するためだけではないのだけど、その辺の話はまた後ですることにして、とりあえず今は、定期的にまとまったTransactionを使ってブロックを生成するためのメカニズムについて考えてみよう」

「普通に考えると、ConnectionManagerを経由して通知されるTransactionをためておく場所を作って、そこから定期的にデータを取り出すような形にすればいいような気がするから、図3.6のようにすればいいと思う」

Transactionをためておく場所を作るには

図3.6: Transactionをためておく場所を作るには

「なるほど。じゃあ、それをコードにしてみようか」

「単にServerCoreの中にTransactionをためておくリストを用意するだけでいいかな、と思ったけど、どうせ先々でTransaction自身のフォーマットをチェックするための機能とかも作っていかないといけないだろうし、基本形はこんな感じでどうかな」

リスト3.6: TransactionPool.py

  import threading

  class TransactionPool:
      def __init__(self):
          print('Initializing TransactionPool...')
          self.transactions = []
          self.lock = threading.Lock()

      def set_new_transaction(self, transaction):
          with self.lock:
              print('set_new_transaction is called', transaction)
              self.transactions.append(transaction)

      def clear_my_transactions(self):
          with self.lock:
              print('transaction is now refreshed ... ')
              self.transactions = []

      def get_stored_transactions(self):
          if len(self.transactions) > 0:
              return self.transactions
          else:
              print("Currently, it seems transaction pool is empty...")
              return []

「悪くない。それじゃあ、このTransactionPoolから定期的にTransactionを取り出してBlockBuilderにデータを引き渡すようにするためには、どうすればいいだろう?」

「P2Pネットワークを実装するとき、定期的に生存確認する機能を作ったけど、基本的にはその応用で、こんな感じに書けばいいと思う」

リスト3.7: SampleBlockchain2.py

  import threading
  import time

  from blockchain.blockchain_manager import BlockchainManager
  from blockchain.block_builder import BlockBuilder
  from transaction.transaction_pool import TransactionPool


  # TransactionPoolの確認頻度
  CHECK_INTERVAL = 10
  block_timer = None

  def generate_block_with_tp(tp, bb, bm, prev_block_hash):

      result = tp.get_stored_transactions()
      if len(result) == 0:
          print('Transaction Pool is empty ...')

      new_block = bb.generate_new_block(result, prev_block_hash)
      bm.set_new_block(new_block.to_dict())
      prev_block_hash = bm.get_hash(new_block.to_dict())
      # ブロック生成に成功したらTransaction Poolはクリアする
      tp.clear_my_transactions()

      print('Current Blockchain is ... ', bm.chain)
      print('Current prev_block_hash is ... ', prev_block_hash)

      block_timer = threading.Timer(CHECK_INTERVAL,
                                generate_block_with_tp,
                                args=(tp, bb, bm, prev_block_hash))
      block_timer.start()

  def main():

      bb = BlockBuilder()
      my_genesis_block = bb.generate_genesis_block()
      bm = BlockchainManager(my_genesis_block.to_dict())

      tp = TransactionPool()

      prev_block_hash = bm.get_hash(my_genesis_block.to_dict())
      print('genesis_block_hash :' , prev_block_hash)

      transaction = {
          'sender': 'test1',
          'recipient': 'test2',
          'value' : 3
      }

      tp.set_new_transaction(transaction)

      transaction2 = {
          'sender': 'test1',
          'recipient': 'test3',
          'value' : 2
      }

      tp.set_new_transaction(transaction2)

      block_timer = threading.Timer(CHECK_INTERVAL,
                              generate_block_with_tp,
                              args=(tp, bb, bm, prev_block_hash))
      block_timer.start()


  if __name__ == '__main__':
      main()

「これをServerCoreに持っていくわけだね」

「Transactionが追加されるタイミングを考えないといけなくなるから、クリア周りの処理とかもうちょっと丁寧にしないと、ブロックに取り込んでないものまで消されちゃいそうだけどね……」

「まぁ、まずはここまでで動作確認をしてみようか?動きとしては図3.7のような流れになるはずだよね」

SampleBlockchain2.pyで行っている処理の流れ

図3.7: SampleBlockchain2.pyで行っている処理の流れ

「うん。それじゃあ実行するね」

  Initializing BlockBuilder...
  Initializing BlockchainManager...
  Initializing TransactionPool...
  genesis_block_hash : 77ec78bcf2da41e3c0ecb38a14622836350b67....
  Current Blockchain is ...  [{'timestamp': 1531051227.699093,
  'transactions': 'AD9B477B42B22CDF18B1335603D073....', 'genesis_block': True},
  {'timestamp': 1531051237.700393,
  'transactions': ['{"sender": "test1", "recipient": "test2", "value": 3}',
  '{"sender":"test1", "recipient": "test3", "value": 2}'],
  'previous_block': '77ec78bcf2da41e3c0ecb38a14622836....'}] ←※① 
  Current prev_block_hash is ...  3417364c641af82c018d1d4d1b2a....
  Currently, it seems transaction pool is empty...
  Transaction Pool is empty ...

「①のところでちゃんと複数のTransactionが1つのブロックの中に格納されてるよ、兄さん」

「じゃあ次は、ブロックに取り込めてないTransactionまで一緒に消してしまわないための工夫について考えてみようか」

「シンプルに考えると、ブロックを作るためにTransactionを取り出したタイミングでリストの長さを覚えておいて、それ以降に追加されたものは含めないようにしつつ、リストの頭から必要なだけ消す処理を入れるのがいいんじゃないかなぁ……」

TransactionPool.py の削除処理を変更

def clear_my_transactions(self, index):
  with self.lock:
      if index <= len(self.transactions):
          new_transactions = self.transactions
          del new_transactions[0:index]
          print('transaction is now refreshed ... ', new_transactions)
          self.transactions = new_transactions

「あとは、これまでの削除処理から呼び出し方を変えるね」

SampleBlockchain2のgenerate_block_with_tp()に追記

  index = len(result)
  tp.clear_my_transactions(index)

「更新されたTransactionを取得できているかどうかを確認する意味で、ブロックをチェーンに追加した後のタイミングでさらにTransactionを送り、TransactionPoolも更新するようにしておこう」

SampleBlockchain2のmain()に追記

    sleep(10) # TransactionPoolの周期的なチェックからタイミングをずらして追加する

    transaction3 = {
        'sender': "test5",
        'recipient': "test6",
        'value' : 10
    }

    tp.set_new_transaction(transaction3)

「よさそうだね。実行してみよう」

「うん」

  Initializing BlockBuilder...
  Initializing BlockchainManager...
  Initializing TransactionPool...
  genesis_block_hash : b667ecd2f233da9fa260d114a7613bf904....
  transaction is now refleshed ...  [] ↓※①
  Current Blockchain is ...  [{'timestamp': 1531052917.961272,
  'transactions': 'AD9B477B42B22CDF18B1335603D07378ACE8356....',
  'genesis_block': True}, {'timestamp': 1531052927.9624279,
  'transactions': ['{"sender": "test1", "recipient": "test2", "value": 3}',
  '{"sender":"test1", "recipient": "test3", "value": 2}'],
  'previous_block': 'b667ecd2f233da9fa260d114a7613bf9....'}]
  Current prev_block_hash is ...  1888f4cf0ad2356e549b042....
  transaction is now refleshed ...  [] ↓※②
  Current Blockchain is ...  [{'timestamp': 1531052917.961272,
  'transactions': 'AD9B477B42B22CDF18....', 'genesis_block': True},
  {'timestamp': 1531052927.9624279,
  'transactions': ['{"sender": "test1", "recipient": "test2", "value": 3}',
  '{"sender":"test1", "recipient": "test3", "value": 2}'],
  'previous_block': 'b667ecd2f233da9fa260d114a7613bf9....'},
  {'timestamp': 1531052937.96268, 'transactions': ['{"sender":"test5",
  "recipient": "test6", "value": 10}'],
   'previous_block': '1888f4cf0ad2356e549b042c60e939....'}]
  Current prev_block_hash is ...  a23918df08bc916578b37ecd871f5e0a....
  • ①:最初のブロック生成
  • ②:二度目のブロック生成でタイミングを遅らせて送信したTransactionが格納されているのがわかる

「後で追加したTransactionが新規のブロックとしてさらに格納できてる!」

「うんうん。いいね。そろそろこの辺の機能をServerCoreに持っていけそうなところまでできあがってきた感じがするね」

「あー、なんかやり遂げた気になってたけど、いわれてみればその作業が本命だった……!」

「とはいえ、ほぼコピペだけでいけるハズだよ。新しい観点があるとすれば、P2Pネットワークと組み合わせて実際にTransactionをSimpleBitcoinのプロトコルメッセージとして受信できるようになるから、複数のEdgeノードからTransactionが同時に送信されてくる、みたいなケースも想定する必要があるってあたりだ。P2Pネットワークの部分がちゃんと動いていれば、ノードが複数になろうと、メッセージが入り乱れようと、何も問題はないんじゃない?」

「そういわれるとなんか不安だけど……。とりあえずServerCoreの手直しからやっていくよ。まずは初期化のところで追加したクラスたちを登録して、と」

ServerCoreの初期化に追加する処理

  self.bb = BlockBuilder()
  my_genesis_block = self.bb.generate_genesis_block()
  self.bm = BlockchainManager(my_genesis_block.to_dict())
  self.prev_block_hash = self.bm.get_hash(my_genesis_block.to_dict())
  self.tp = TransactionPool()

「そんでもって、ブロック生成処理をこんな感じで組み込む!」

ServerCoreの追加するブロック生成処理

  def __generate_block_with_tp(self):
      result = self.tp.get_stored_transactions()
      print('generate_block_with_tp called!')
      if len(result) == 0:
          print('Transaction Pool is empty ...')
      new_block = self.bb.generate_new_block(result, 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())
      index = len(result)
      self.tp.clear_my_transactions(index)

      print('Current Blockchain is ... ', self.bm.chain)
      print('Current prev_block_hash is ... ', self.prev_block_hash)

      self.bb_timer = threading.Timer(CHECK_INTERVAL,
                                      self.__generate_block_with_tp)
      self.bb_timer.start()

「あとはhandle_messageで新規Transactionを受信した場合の処理を追加すればオッケー、なはず……!」

ServerCoreのhandle_messageに追加する処理

  if msg[2] == MSG_NEW_TRANSACTION:
      # TODO: 送信されてくるデータは暫定的にバイナリ化したJSON文字列という前提にしておく
      self.tp.set_new_transaction(json.loads(msg[4]))
      pass

「そこまでは悪くないね。でも、Edgeノードから送信されたTransactionは、そのCoreノードが受信して終わりなんだっけ?」

「あ、そうだった! 1か所のCoreノードで受け付けたTransactionメッセージは、P2Pネットワーク全体で共有できるように、他のCoreノードに対してブロードキャストされる必要があるんだったね」

「だとすると、どういう処理が必要になると思う?」

「Coreノードのリストに格納されている全ノードに対して、同じメッセージを伝える必要があるよね。図3.8のような具合だと思う」

SampleBlockchain2.pyで行っている処理の流れ

図3.8: SampleBlockchain2.pyで行っている処理の流れ

「あれ、でも、これだけだとダメだな」

「なぜだい?」

「何も考えずにその処理を追加すると、そのブロードキャストを受けたCoreノードがまたそれを再拡散しようとするから、永久に同じメッセージを共有しあう変なループができてしまいそう……」

「そうだね。すでに知ってるTransactionをまた受け取らないようにする必要があるね。ただ、そもそもSimpleBitcoinのネットワーク構成だと、Coreノードから受信したメッセージをさらに別のCoreノードに送信する必要性がないよね?」

「たしかに、自分にメッセージを送ってきた相手が他のCoreノードだったら、自分に送るタイミングですでに他の全ノードにも同じメッセージを送信しちゃってるのか……」

「重複チェックもあっていいけど、そもそも捨てられるとわかっているメッセージを送る必要はないよね?」

「それはそうだね……。受信したメッセージの送信元をチェックして振る舞いを変えるようにしてみよう」

「それにはどこを変えればいいだろう?」

ConnectionManager側であらかじめ判定しておいて、その判定結果をコールバックを通じて受け取ればいいと思う。実質的にそのチェックはCoreNodeListの方でやることになるから変更箇所は2つだね」

ConnectionManagerにメッセージ送信元を判定する機能をつける

  def __is_in_core_set(self, peer):
      """
      与えられたnodeがCoreノードのリストに含まれているか?をチェックする
      """
      return self.core_node_set.has_this_peer(peer)

CoreNodeListに含まれているpeerかどうかを判定する機能をつける

  def has_this_peer(self, peer):
      """
      与えられたpeerがリストに含まれているか?をチェックする
      """
      return peer in self.list

「そうだね。P2Pネットワークのところで出てきたMSG_ENHANCEDタイプのメッセージ(2.4節参照)もそうだけど、Coreノードは相互に接続されているのが前提になってるから、これで無駄な通信を避けられそうだね」

「同じEdgeノードが二重で送信してくることなんかはあるかもしれないし、重複チェック自体はあってもいいと思うから、こんな感じの処理にすべきかな」

新規Transactionを受け取った場合の処理

  if msg[2] == MSG_NEW_TRANSACTION:
      # 新規transactionを登録する処理を呼び出す
      new_transaction = json.loads(msg[4])
      print("received new_transaction", new_transaction)
      current_transactions = self.tp.get_stored_transactions()
      if new_transaction in current_transactions:
          print("this is already pooled transaction:", t)
          return
      if not is_core:
          self.tp.set_new_transaction(new_transaction)
          new_message = self.cm.get_message_text(MSG_NEW_BLOCK,
                                      json.dumps(new_block.to_dict()))
          self.cm.send_msg_to_all_peer(new_message)
      else:
          self.tp.set_new_transaction(new_transaction)

「それでは、ClientCore側には何か変更が必要だろうか?」

「うーん……。少なくともEdgeノード側にはブロック生成の機能は求められていないし、単にTransactionを送る側という位置付けだから、特に変更は必要ないと思う」

「それじゃあ、これで接続確認をしてみよう。今回は図3.9のような流れでメッセージを送ってみてごらん。それぞれのタイミングで生成されるブロックの構成を確認するところがポイントだよ」

複数のEdgeノードから入り混じったメッセージを送信してみる

図3.9: 複数のEdgeノードから入り混じったメッセージを送信してみる

「うん、わかった。大丈夫かなー……」

  Initializing server...
        <中略>
  ADD request for Edge node was received!!
  Adding edge:  ('10.1.1.27', 50095) ←※①
        <中略>
  Transaction Pool is empty ... ↓※②
  Current Blockchain is ...
  [{'timestamp': 1531064807.39126, 'transactions': 'AD9B477B42B22CDF....',
  'genesis_block': True}]
  Current prev_block_hash is ...  d2576ae02ea5ed290e63e3707ecc....
  <中略>
  ADD request for Edge node was received!!
  Adding edge:  ('10.1.1.27', 50088) ←※③
 <中略>
  Waiting for the connection ... ↓※④
  ('ok', 2, 7, 50095, '{"sender": "test4", "recipient": "test5", "value": 3}')
  Connected by ..  ('10.1.1.27', 57091) ↓※⑤
  ('ok', 2, 7, 50095, '{"sender": "test6", "recipient": "test7", "value": 2}')
 <中略>
  Current Blockchain is ... ↓※⑥
  [{'timestamp': 1531064807.39126,
  'transactions': 'AD9B477B42B22CDF18B1335603D07378ACE8....',
  'genesis_block': True}, {'timestamp': 1531064827.4299781,
  'transactions': ['{"sender": "test4", "recipient": "test5", "value": 3}',
  '{"sender": "test6", "recipient": "test7", "value": 2}'],
  'previous_block': 'd2576ae02ea5ed290e63e3707ecc2a9986....'}]
  <中略>   ↓※⑦
  ('ok', 2, 7, 50088, '{"sender": "test1", "recipient": "test2", "value": 3}')
  Waiting for the connection ...
  Connected by ..  ('10.1.1.27', 57096) ↓※⑧
  ('ok', 2, 7, 50088, '{"sender": "test1", "recipient": "test3", "value": 2}') 
 <中略>  ↓※⑨
  ('ok', 2, 7, 50095, '{"sender": "test8", "recipient": "test9", "value": 10}')
  Waiting for the connection ...
  check_peers_connection was called!
  transaction is now refreshed ...  [] ↓※⑩
  Current Blockchain is ...  [{'timestamp': 1531064807.39126,
  'transactions': 'AD9B477B42B22CDF18B1335603D07378ACE8....',
  'genesis_block': True}, {'timestamp': 1531064827.4299781,
  'transactions': ['{"sender": "test4", "recipient": "test5", "value": 3}',
  '{"sender": "test6", "recipient": "test7", "value": 2}'],
  'previous_block': 'd2576ae02ea5ed290e63e3707ecc2a9986ab8c....'},
  {'timestamp': 1531064837.4476311,
  'transactions': ['{"sender": "test1", "recipient": "test2", "value": 3}',
   '{"sender": "test1", "recipient": "test3", "value": 2}',
   '{"sender": "test8", "recipient": "test9","value": 10}'],
   'previous_block': 'd628e00d881e2b4e0db3184135a698092dae....'}]
  Current prev_block_hash is ...  260dc89d592aa590dd990bc03ed....
  • ①:1つめのEdgeノードが接続
  • ②:この時点ではまだ Genesisブロックだけしか存在していない
  • ③:接続されるEdgeノードが2つに
  • ④:1つめのEdgeノードから最初のTransactionが届く
  • ⑤:1つめのEdgeノードから追加Transactionが届く
  • ⑥:このタイミングまでに届いていた2つのTransactionを使ってブロックが生成されている
  • ⑦:2つめのEdgeノードから最初のTransactionが届く
  • ⑧:2つめのEdgeノードから追加Transactionが届く
  • ⑨:1つめのEdgeノードから追加Transactionが届く
  • ⑩:このタイミングまでに届いていたTransaction3つを使ってブロックが生成されている

「うん。⑩のタイミングで追加されたブロックに、2つのEdgeノードからのTransactionが混合しているのが見えるね。そのまま続けて見ていこう」

 <中略>
  ('ok', 2, 7, 50088, '{"sender": "test5", "recipient": "test6",
  "value": 10}') ←※① 
  Waiting for the connection ...
 <中略>
  transaction is now refleshed ...  [] ↓※②
  Current Blockchain is ...  [{'timestamp': 1531064807.39126,
  'transactions': 'AD9B477B42B22CDF18B133....', 'genesis_block': True},
  {'timestamp': 1531064827.4299781,
   'transactions': ['{"sender": "test4", "recipient": "test5", "value": 3}',
   '{"sender": "test6",
   "recipient": "test7", "value": 2}'],
   'previous_block': 'd2576ae02ea5ed290e63e3707ecc2....'},
   {'timestamp': 1531064837.4476311, 'transactions': ['{"sender": "test1",
   "recipient": "test2", "value": 3}', '{"sender": "test1",
   "recipient": "test3", "value": 2}',
   '{"sender": "test8", "recipient": "test9", "value": 10}'],
   'previous_block': 'd628e00d881e2b4e0db318....'},
   {'timestamp': 1531064847.44801, 'transactions':
   ['{"sender": "test5", "recipient": "test6", "value": 10}'],
   'previous_block': '260dc89d592aa590dd990....'}]
 <以下略>
  • ①:2つめのEdgeノードから追加Transactionが届く
  • ②:さらにチェーンが伸びる

「おおお!なんか動いてる。ちゃんと動いてるっぽく見えるよ、兄さん!」

「ふむ。ブロックを生成するところまでは、どうやら無事にたどり着いたみたいだね。次は、Transactionと同様に、この新規に生成したブロック自体が他のCoreノードと共有されるために全体にブロードキャストされるところの作り込みについて考えてみよう」

「これまでブロックチェーンを作る部分はあくまでも単一のCoreノードで動かしてきたけれど、いよいよブロックチェーンらしく、複数のCoreノードがそれぞれでブロックを生成する形になっていくわけだね!ブロックチェーンのクライマックス!高まる!」

「またそれか……」

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