
PoWを使った簡易ブロックチェーンをpythonで実装
前回は非常にシンプルな、2つのブロックをハッシュ値でつなぐブロックチェーンを作りました。
Pythonのクラスやインスタンス(オブジェクト指向)の復習にもなるので、ぜひ見てみてください。
(リンク)
最近ふと、ブロックチェーンへの興味がより一層深まってきて、自分でコードを書きたくなりました。 ブロックチェーンは何かを、文章としての説明では理解していたつもりで…pathlog.jp
ですが、さすがにこれだけではブロックチェーンとは呼べませんので、今回は Proof of Work(PoW)を実装し、なぜブロックの改ざんが困難になるのかをコードレベルで確認していきます。
また、それに伴って、マイニングやタイムスタンプ、その他もろもろ(ハッシュの難易度調整、個数調整)を追加しました。
完成品コード
以下が、今回の実装をすべて含んだ完成形のコードです。
だいぶ「ブロックチェーン」になってきたのではないでしょうか!
解説
このコードは、ざっくり見ると以下のような構成になっています。
それぞれ見ていきましょう。
とその前に、まずはハッシュ関数と、時間のライブラリをインポートします。
Blockクラス
このクラスで、一つのブロックのインスタンスを作成できるようにします。
基本的には、やっていることはシンプルで、情報の保存だけです。
インスタンス作成時に、引数としてデータや一つ前のブロックのハッシュ値、ナンス、自らのハッシュ値、タイムスタンプを受け取り、それらを保存します。
それに加えて、後で楽になるので、こいつ自身にもハッシュする関数を入れておきます。
これだけになります。
メインはすべてBlockchainクラスに入っています。
Blockchainクラス
まず、それぞれがどのような役割を分担しているのか確認しましょう。
このようになっております。
__init__とハッシュ関数
まず、Blockchainインスタンスが作成されたら、self.chainというリストを作ります。ここにブロックを入れていきます。
それから難易度(ハッシュ値の最初に0が何個並ぶか)と生成するブロックの個数をinputで取得します。
そのあとは、create_genesis_blockを呼び出して、genesis_blockを作成します。それを先程のリストに入れます。
これが最初のブロックです。
そのあとは、最初のinputの値に応じて、forループでブロックを追加していきます。
二回目以降は、データとしてf”block {i}:を使用しています。つまり3つめのブロックなら、”block 3″ですね。
hash_block関数は、データ、一つ前のハッシュ値、ナンス、タイムスタンプ(ブロック生成時のもの)を引数として受け取ります。
それらをstringとして足し合わせて、SHA-256でハッシュした値を返します。
ジェネシスブロック(create_genesis_block)
次に、ジェネシスブロックの生成の関数があります。
これは、まず呼び出されたときにデータを”genesis”、previous_hashを0としてself.mine_block関数に渡して、マイニングしてもらいます。
見つかった結果をgenesisとして保存します。
これは以下のような辞書形式で返ってきます。
上から見つかったナンス(調整値)、ハッシュ値、タイムスタンプ(エポック秒)、経過時間、試行回数です。
Blockインスタンスの作成にはdata, previous_hash, nonce, hash, timestampがそれぞれ必要ですから、
Block("genesis", "0", genesis["nonce"], genesis["hash"], genesis["timestamp"])
のようにして、ジェネシスブロックを作成します。
データは”genesis”、前のブロックのハッシュ値は0、ナンス、ハッシュ値、タイムスタンプを渡します。
そうして得られたBlockを返します。
こうして得られた値を、__init__で、リストに追加します。
self.chain.append(genesis_block)
これで、ジェネシスブロックの完成です!
マイニング(mine_block)
次に、今回追加した、Proof of Work(PoW)を使ったマイニングを行う関数です。
ここはとても楽しいところです。
そもそもPoWとは、データにナンスと呼ばれる任意の数字を加えて、それをハッシュした値の先頭に指定された個数の0が並べば、それが認められるというものです。
この関数は、その条件に合ったときのナンスやハッシュ値、時間などを辞書形式にし、返します。
ナンスはnonce、指定する先頭の0の個数がdifficultyです。
ナンスを0、試行回数を0に初期化します。
targetは、0をdifficulty個かけたものです。difficultyが5ならば”00000″です。
ナンスを加えてハッシュした結果が、targetで始まれば、そのナンスで決定です。
経過時間計測用のstart_timeと、timestampも定義します。
ここから、マイニングの作業を定義していきます。
whileループを用いて、ハッシュを何度も何度も試行します。
ナンス=0から始めて、だめなら+1して再挑戦、だめなら+1して再挑戦を繰り返します。(attemptsも、この回数を記録します)
そして、多くの試行を経て条件に合うものが見つかったら、breakします。
その後は以下のように処理されます。
終了時間を記録し、終了時間から開始時間を引くことで、処理にかかった時間を算出します。
resultという辞書にまとめてこれらの結果を入れ、それを返します。
今回は実際のBitcoinのブロックチェーンに基づいて、時間もハッシュの対象に含めています。
これにより(取引)時間も改ざん不可能となりました。
各ブロックは timestamp を含む完全再計算可能な hash を持つのです。
以上が簡易的ですが、マイニングの実装になります。
ブロックの追加(add_block)
ここでは、ジェネシスブロックを除いた、ブロックの追加作業を行っていきます。
具体的には、まずマイニングの指令を先程のマイニング関数に出して、必要な情報をかき集めて、それを使ってBlockのインスタンスを作成します。
完成形は以下のようになります。
マイニングに必要なのはデータと一つ前のブロックのハッシュ値ですから、
のようにすることで、マイニングの結果(辞書)を取得することができます。
それらをわかりやすくするために変数に代入し、↓
ブロックを作成してチェーンリストに追加します。
あとは結果をわかりやすく表示すれば完成です。
このような形式でターミナルに表示されます。
これで肝心な動く部分は完成です!
チェーン検証(is_chain_valid)
ここでは以下3つの観点から、ブロックチェーンの正しさをチェックしています。
- ブロック同士のハッシュの繋がり
- タイムスタンプの関係
- PoWにおけるナンスも含めたハッシュ値の整合
この3点です。
これを一段階具体的なロジックにすると、(4段階)
現在のブロックに保存されている「一つ前のブロックのハッシュ値」と、一つ前のブロックのハッシュ値が同値である
現在のブロックの作成された時間が、一つ前のブロックの作成された時間よりも遅い
かつ
現在のブロックの作成された時間が、一つ前のブロックが作成されてから〇〇秒以内になっている
保存された現在のブロックのハッシュ値が、計算した結果と同値である
現在のブロックのハッシュ値を計算し、それが指定された数の0で始まる
ということになります。
本実装では簡略化のため、ブロック生成間隔の上限を100秒とハードコードします。
擬似コードにすると、
となります。
これをブロックごとにループで回しますので、最終的にコードは以下のようになります。
ここで、Blockクラス自身のハッシュ関数を使っています。
チェーンがうまくいっていれば、Trueを返します。
実行
bcというBlockchainクラスのインスタンスを作成します。
is_chain_validの結果を表示するようにします。
実行結果は以下のようになります。
難易度6のブロック個数5と入力しました。
以上で完成です!お疲れ様でした。
おわりに
自分でブロックチェーンを書いて、着々と仕組みを深く理解していくのが、本当に楽しくなってきました。
ここまで自力で書ける自分にも驚いています。
時間を忘れてやってしまいます笑
次はこの愛着のある自作ブロックチェーンを使って、数値をいじくって観察したいと思います。
今後はこの実装を使って、以下のような実験を行う予定です。
difficulty と attempts の関係の統計分析
ブロック生成時間の分布の可視化
nonce をランダム初期化した場合の偏り検証
difficulty を途中で変更した場合の fork 実験
以上です!

Ryo
CS好きの高校一年生。現在は米大学のラボでエネルギー制約下の分散コンピューティング経済学の研究をしています。


