『ゼロから創る暗号通貨』
第3章: Hello Blockchain ! : こんにちはブロックチェーン
濵津 誠/hamatz
本章ではブロックチェーンの仕組みについて学びます。まず、単純なブロックチェーンを作成してみることで、ブロックの連鎖が何を担保しているのか、その意味を確認します。その後、それをベースにして、第2章で作成したP2Pネットワークの機能へと繋ぎ込みます。さらに、Proof of Workやコンセンサスルールといった暗号通貨を成立させるためのアイデアについて学び、それらを取り込む形で暗号通貨の基盤となり得るシステムを準備していきます。ポイントとなるのは次のような内容です。
本章が終わるころには、暗号通貨以外への応用も可能なブロックチェーンアプリケーションの基盤が完成することとなるでしょう。
「P2Pネットワークが完成したところで、いよいよブロックチェーンを作り込んでいこう。全体像と照らし合わせると図3.1で示す箇所になる」
「高まる!」
「……」
「え、ぼく何か変なこと言った?」
「実装して気分が高まるのはいいけれど、まずはブロックチェーンとは何かを再確認していくぞ。ブロックチェーンとは、文字どおりブロックの連鎖だ。じゃあ、そのブロックというのは、一体どんな構造をしていればいいと思う?」
「これから作るSimpleBitcoinでは、ポイントをやり取りするわけだから、そのポイントのデータだと思ってたけど」
「そこまでは合ってるよ。ブロックチェーンで内容を保証したいデータ、今の例であればポイントは、当然ブロックの一部でなければならない。だが、それだけではない。図3.2を見てほしい」
「図3.2のように、基本的には、『内容を保障したいデータ』と『前段のブロックからハッシュ値をとったもの』をセットにして1つのブロックが構成されるイメージになる」
「ハッシュ値っていうのは?」
「入力に対応した固定長の擬似的な乱数を返す、ハッシュ関数と呼ばれるものがある。ハッシュ関数に何らかの値を入力して得られる値がハッシュ値だ。ハッシュ関数では、同じ入力からは常に同じ値が得られる。そのため、ハッシュ値は、本来の意味での乱数ではない。しかし、出力値をみて入力値を特定することが非常に困難であるという性質がある。乱数と同様に意味を持たないデータというくらいに考えてほしい」
「で、その『前段のブロックのハッシュ値をとったもの』があると何が嬉しいの?」
「いま、図3.2におけるブロック1の内容を、ブロックチェーンを作った後で変更したとしよう。そうすると、ブロック2の中に格納されているハッシュ値と、実際のブロック1から計算したハッシュ値が、一致しなくなるだろう?この性質を利用することで、ブロックチェーンが一貫性のあるデータであることを保証できる」
「なるほど。どこか1か所でも改ざんされると、その改ざんされたブロック以降のすべてのブロックが信頼できないものになってしまうのか。そして、そのブロックチェーンが全体として意味をなさないものになってしまう、と」
「そのとおり。逆に、あるブロックのハッシュ値と次の段のブロックに含まれているハッシュ値が一致するような連鎖であれば、そのブロックチェーン全体の一貫性が保証される」
「そういうことかー。でも、改ざんした後で後続のブロックも含めて全部ハッシュ値を再計算したら、一貫性はあるように見えるのでは?」
「そういう問題はある。それをどう解決するかについては、もう少し話が進んでから考えよう」
「なるほど。ところで、連鎖というからには一番先頭のブロックがあると思うんだけど、そのブロックでは何のハッシュ値を使うの?」
「いい質問だね。P2Pネットワークにおいて始原となるCoreノードが存在していたように、ブロックチェーンにおいても始原となるブロックが存在することになっているのさ。この始原となるブロックは、一般にGenesisブロックと呼ばれている。そこで、SimpleBitcoinでもそう呼ぶことにしよう」
「Genesisブロックについては、例外的に前段のブロックを使わないということだね」
「そういうことだ。話を整理すると図3.3のようになる」
「基本形を押さえたところで、いよいよコードに落とし込む作業に入ろうか」
「りょーかい!」
「暗号通貨として利用することを考えると、他のノードから通知されたTransactionを使ってブロックを生成し、自身が管理しているブロックチェーンを更新していく、という形が基本になるだろう。つまり、図3.4のような形だ」
「他のノードから新しく作られたブロックが通知される場合があるから、ブロックを作る役割と、保存・管理する役割を分けておくわけだね」
「そうだ。とはいえ、いきなり既存のコードに追加すると動作確認が大変だし、最初はシンプルに作り始めるほうがいい。そこで、まずは図3.5のような形から始めることにしよう」
「じゃあ、ブロックの生成を依頼するStep1から考えてみるかー。図3.4を思い返すと、ブロック内に含まれているべき情報って、基本的にはTransactionと、前のブロックのハッシュ値なんだよな」
「更新の差分だけ欲しい、という場合に対応できるように、タイムスタンプくらいは仕込んでおくほうがいいだろう」
「わかった。ということは、ブロックのクラスはこんな感じかな」
「このクラスは、Genesisブロックでも共用できる?」
「前段のブロックが存在しないという特徴があるだけなので、そこをセットしなければいいだけだから、大丈夫じゃないかなぁ」
「それじゃあ、そのまま進めてみよう。次は、図3.5でいうところのBlockBuilder
を考えてごらん」
「ブロックの生成を頼んで、できあがったものが返ってくる、Step1とStep2で実際に叩かれる部分だね」
「いいだろう。では、できあがったブロックを保存するStep3にはどんな処理が必要になるだろう?」
「返ってきたブロックは、普通にどんどんリストに追加していけばいいと思うんだよね。つまりこんな感じで大丈夫なハズ……!」
「ふむ。それじゃあ、実際に動かしてみようか。BlockBuilder
とBlockchainManager
をどういうふうに使えばいいかな?」
「今回は簡単……、あれ、簡単じゃない?あれれ?あ、ちょっと待って!」
「どうした?」
「作ったブロックのハッシュ値って、どうやって計算するんだろう?Genesisブロックはともかく、このままだと次のブロックを作る処理ができないや!」
「そうだねぇ。じゃあ、どうすればいいと思う?」
「ハッシュ値の計算機能は、BlockchainManager
に持たせればいいと思うんだけど……」
「どうしてそう思うんだい?」
「他のノードからブロックが送られてくることもあり得るわけだから、届いたブロックが正しいか検証するような機能がどのみち必要になると思うんだよね。すでに保存済みのブロックのうちで一番新しいやつを取り出し、そのハッシュ値を計算したあとで、新しいブロックの中に含まれるprevious_block
の中の値と比較する、といった処理が必要になると思うんだ」
「よくできました。じゃあ、それを組み込んでみよう」
「うーんと、ハッシュ値を計算するにあたって、入力データってどう扱うべきなんだろう?ブロックを表すオブジェクトBlock
が返ってきてるけど、よく考えたら、これをそのままハッシュ値を求めるのに使うのって変だよね?」
「いいところに気づいたね。これは実際に作ろうとしてはじめて気がつくようなポイントの1つといえる」
「そうなのかー。図3.4を見てわかった気になってた。うぬぬ……」
「ちょっと見方を変えてみよう。Block
ができあがったときに、他のノードに通信で投げる場合、どういう形式にするのが汎用性が高くなると思う?」
「Python以外でもノードを作りたい、みたいなところまで考えると、やっぱりJSONとかかなー」
「どうやってJSONに直せばいいと思う?」
「できあがったBlock
自身が、JSONを作り出すメソッドを持ってればいいんじゃないかなぁ」
「でも、BlockchainManager
の中にテキスト化したJSONデータを保存するようにしちゃうと、取り出しが不便じゃないかな?」
「そうか。となると、辞書型にしておいて、送信前に文字列化するような処理を挟むのがベストな気がしてきた」
「じゃあ、それをコードにしてみよう」
「まず、Block
のデータを辞書型に変換するメソッドを用意するね」
def to_dict(self): d = { "timestamp" : self.timestamp, "transaction": json.dumps(self.transaction), "previous_block": self.previous_block, } return d
「次に、辞書型に変換したBlock
のデータを文字列に直してからハッシュ値を得るための処理を、BlockchainManager
につける」
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
を二重にかけてるのは?」
「ビットコインがそうしてるって聞いたことがあったから……」
「妙なとこだけ詳しいね」
「そこは普通に褒めてくれていいんじゃないのー?」
「まあまあ。それじゃあ動作確認といこうか。もう考慮漏れはないかな?」
「ないと思う。実際にBlockchainManager
とBlockBuilder
を使うようなコードを書いてみるね」
「それじゃあ実行してごらん」
「よろしくお願いしまぁぁぁす!」
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クラスをベースにはするけども」
「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
「うん。大丈夫そう」
「そのようだね。ベースができあがったところで次に進むとしようか!」
「さて、ブロックチェーンの基本形はできあがったわけだが、今のままだと非常に効率が悪い」
「効率?どういうこと?」
「ブロックの定義が『内容を保障したいデータ』と『前段のブロックからハッシュ値をとったもの』のセットであるなら、『内容を保証したいデータ』の中身は自由であっていいはずだ。ところが、今の実装だとTransactionが1つ追加されるたびにブロックを生成することになる」
「そうか、別に、いくつかのTransactionをまとめて1つのブロックを作ってもいいよね」
「そう。今のままだと、ブロックチェーンが無駄に長くなる。ある程度までTransactionが溜まるのを待って、1つにまとめてからブロックを作ったところで、それほど大きな問題はなさそうだろう?」
「うん」
「ブロックチェーンが頻繁に更新されることが前提になっていると、EdgeノードからCoreノードへのブロックチェーンの更新問い合わせ頻度が上がるから、Edgeノードが増えれば増えるほど通信機会が加速度的に増えていってしまう。これは問題が多そうだ」
「たしかに。更新間隔を延ばすことで問い合わせ回数も減らせるわけかー」
「更新間隔を延ばす工夫はEdgeノードからの問い合わせタイミングを削減するためだけではないのだけど、その辺の話はまた後ですることにして、とりあえず今は、定期的にまとまったTransactionを使ってブロックを生成するためのメカニズムについて考えてみよう」
「普通に考えると、ConnectionManager
を経由して通知されるTransactionをためておく場所を作って、そこから定期的にデータを取り出すような形にすればいいような気がするから、図3.6のようにすればいいと思う」
「なるほど。じゃあ、それをコードにしてみようか」
「単にServerCore
の中にTransactionをためておくリストを用意するだけでいいかな、と思ったけど、どうせ先々でTransaction自身のフォーマットをチェックするための機能とかも作っていかないといけないだろうし、基本形はこんな感じでどうかな」
「悪くない。それじゃあ、このTransactionPool
から定期的にTransactionを取り出してBlockBuilder
にデータを引き渡すようにするためには、どうすればいいだろう?」
「P2Pネットワークを実装するとき、定期的に生存確認する機能を作ったけど、基本的にはその応用で、こんな感じに書けばいいと思う」
「これをServerCore
に持っていくわけだね」
「Transactionが追加されるタイミングを考えないといけなくなるから、クリア周りの処理とかもうちょっと丁寧にしないと、ブロックに取り込んでないものまで消されちゃいそうだけどね……」
「まぁ、まずはここまでで動作確認をしてみようか?動きとしては図3.7のような流れになるはずだよね」
「うん。それじゃあ実行するね」
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を取り出したタイミングでリストの長さを覚えておいて、それ以降に追加されたものは含めないようにしつつ、リストの頭から必要なだけ消す処理を入れるのがいいんじゃないかなぁ……」
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
「あとは、これまでの削除処理から呼び出し方を変えるね」
index = len(result) tp.clear_my_transactions(index)
「更新されたTransactionを取得できているかどうかを確認する意味で、ブロックをチェーンに追加した後のタイミングでさらにTransactionを送り、TransactionPool
も更新するようにしておこう」
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が新規のブロックとしてさらに格納できてる!」
「うんうん。いいね。そろそろこの辺の機能をServerCore
に持っていけそうなところまでできあがってきた感じがするね」
「あー、なんかやり遂げた気になってたけど、いわれてみればその作業が本命だった……!」
「とはいえ、ほぼコピペだけでいけるハズだよ。新しい観点があるとすれば、P2Pネットワークと組み合わせて実際にTransactionをSimpleBitcoinのプロトコルメッセージとして受信できるようになるから、複数のEdgeノードからTransactionが同時に送信されてくる、みたいなケースも想定する必要があるってあたりだ。P2Pネットワークの部分がちゃんと動いていれば、ノードが複数になろうと、メッセージが入り乱れようと、何も問題はないんじゃない?」
「そういわれるとなんか不安だけど……。とりあえず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()
「そんでもって、ブロック生成処理をこんな感じで組み込む!」
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を受信した場合の処理を追加すればオッケー、なはず……!」
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のような具合だと思う」
「あれ、でも、これだけだとダメだな」
「なぜだい?」
「何も考えずにその処理を追加すると、そのブロードキャストを受けたCoreノードがまたそれを再拡散しようとするから、永久に同じメッセージを共有しあう変なループができてしまいそう……」
「そうだね。すでに知ってるTransactionをまた受け取らないようにする必要があるね。ただ、そもそもSimpleBitcoinのネットワーク構成だと、Coreノードから受信したメッセージをさらに別のCoreノードに送信する必要性がないよね?」
「たしかに、自分にメッセージを送ってきた相手が他のCoreノードだったら、自分に送るタイミングですでに他の全ノードにも同じメッセージを送信しちゃってるのか……」
「重複チェックもあっていいけど、そもそも捨てられるとわかっているメッセージを送る必要はないよね?」
「それはそうだね……。受信したメッセージの送信元をチェックして振る舞いを変えるようにしてみよう」
「それにはどこを変えればいいだろう?」
「ConnectionManager
側であらかじめ判定しておいて、その判定結果をコールバックを通じて受け取ればいいと思う。実質的にそのチェックはCoreNodeListの方でやることになるから変更箇所は2つだね」
def __is_in_core_set(self, peer): """ 与えられたnodeがCoreノードのリストに含まれているか?をチェックする """ return self.core_node_set.has_this_peer(peer)
def has_this_peer(self, peer): """ 与えられたpeerがリストに含まれているか?をチェックする """ return peer in self.list
「そうだね。P2Pネットワークのところで出てきたMSG_ENHANCED
タイプのメッセージ(2.4節参照)もそうだけど、Coreノードは相互に接続されているのが前提になってるから、これで無駄な通信を避けられそうだね」
「同じEdgeノードが二重で送信してくることなんかはあるかもしれないし、重複チェック自体はあってもいいと思うから、こんな感じの処理にすべきかな」
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のような流れでメッセージを送ってみてごらん。それぞれのタイミングで生成されるブロックの構成を確認するところがポイントだよ」
「うん、わかった。大丈夫かなー……」
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....
「うん。⑩のタイミングで追加されたブロックに、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....'}] <以下略>
「おおお!なんか動いてる。ちゃんと動いてるっぽく見えるよ、兄さん!」
「ふむ。ブロックを生成するところまでは、どうやら無事にたどり着いたみたいだね。次は、Transactionと同様に、この新規に生成したブロック自体が他のCoreノードと共有されるために全体にブロードキャストされるところの作り込みについて考えてみよう」
「これまでブロックチェーンを作る部分はあくまでも単一のCoreノードで動かしてきたけれど、いよいよブロックチェーンらしく、複数のCoreノードがそれぞれでブロックを生成する形になっていくわけだね!ブロックチェーンのクライマックス!高まる!」
「またそれか……」