ビットコインが世間を騒がせてます。
yahooのビットコインに関するニュースは、ここ最近爆発的に増えています。
年 | ニュース件数(2017年12月15日調べ) |
---|---|
2013 | 3 |
2014 | 6 |
2015 | 3 |
2016 | 0 |
2017 | 403 |
ここ4,5ヶ月くらいで爆発的に記事数が上がっています。
マウント・ゴックスが話題に上がったのが、2014年のはじめ
その時6件なので、今ビットコインがどれだけ注目されているか明らかですね
自分も関心があって検索とかしてたのですが、アドベントカレンダーもあるしこれを機会に調べてみようかな〜と思い立ったわけです。
ビットコインは、様々な技術を組み合わせることで成立してるということなので、今回はその中の一つ、ブロックチェーン技術についてフォーカスし、Rubyでブロックチェーンのシミュレーターを作ってみようと思いました。
この記事の実装部分は、mizchiさんの記事でjs(babel + flowtype)によって実装されたものをrubyでアレンジしたものになってます。
今回実装するブロックチェーンでベースを理解し、ビットコインのブロックチェーンとどのような差があるのか着目して調べてみました。
ブロックチェーンって何?
Japan Blockchain Association(JBA)によるとブロックチェーンは狭義と広義の2つの定義があります。
- ビザンチン障害1を含む不特定多数のノードを用い、時間の経過とともにその時点の合意が覆る確率が0へ収束するプロトコル、またはその実装をブロックチェーンと呼ぶ。
- 電子署名とハッシュポインタを使用し改竄検出が容易なデータ構造を持ち、且つ、当該データをネットワーク上に分散する多数のノードに保持させることで、高可用性及びデータ同一性等を実現する技術を広義のブロックチェーンと呼ぶ。
今回実装するのは1のパターンに近いものです。
聞きなれない単語を軽く説明すると
ビサンチン障害: 間違った答え/嘘のレスポンスをする障害
ノード: サーバー
ビサンチン障害対策がない、ノードではなくスレッドであることから、完全に1のパターンに当てはまらないので、今回実装するのはブロックチェーンっぽいものです。
ビサンチン障害対策として、ビットコインは正しい答えを返したら経済的インセンティブを与えるという方式をとっています。俗に言うマイニング(Proof of work/Proof of Stake)というやつです。
要件
- ブロックチェーンはブロックの連結リスト
- ブロックはいくつかのデータと自身のハッシュを持つ
- 前のブロックのハッシュもそのデータに含まれる
=> データ改竄すると後のブロックも全部影響でるので長くなるほど変更難度↑ - ノードは新しいブロックを自由に追加できる
- 検証結果が正しく、長いブロックチェーンが信頼される
- 更新を受け取った時、手元のブロックチェーンとそれを比較し、より長いものをブロックチェーンとして受け入れる
クラス設計
- ブロック
- メンバ: データ, インデックス, タイムスタンプ, 前のブロックのハッシュ, 自分のハッシュ(それ以外のメンバから生成)
- ブロックチェーン
- メンバ: 複数のブロック
- メソッド: 最後のブロックを返す
- メソッド: 次のブロックの生成
- メソッド: 新しいブロックが有効か判断する
- メソッド: 新しいブロックを追加する
- ノード
- メンバ: 名前、 ブロックチェーン
- メソッド: ブロックチェーンを受け入れる
- メソッド: 新しいブロックを追加する
実装
githubはこちら
ブロッククラスは、Block.newした時に自身のhashを計算してるところがポイントです。
あとblock_chainの最初のブロックは ジェネシスブロックと呼ばれバードコードされています。
require 'digest/sha2'
class Block
attr_reader :index, :previous_hash, :timestamp, :data, :hash
def initialize args
@index = args[:index]
@previous_hash = args[:previous_hash]
@timestamp = args[:timestamp]
@data = args[:data]
@hash = calculate_hash(args)
end
def ==(other)
hash == other.hash
end
private
def calculate_hash args
Digest::SHA256.hexdigest({
index: args[:index],
previous_hash: args[:previous_hash],
timestamp: args[:timestamp],
data: args[:data]
}.to_json)
end
class << self
def genesis_block
Block.new(
index: 0,
previous_hash: '0',
timestamp: 1512779478,
data: "genesis block!!"
)
end
end
end
ブロックチェーンクラスはブロックの関連検証の機能、追加の機能を担っています。
is_valid_new_block?
is_valid_chain?
が肝です。
新しいブロックの条件は
- 前のブロックのindexの次のindexと等しい
- 前のブロックのhashは新しいブロックのprevious_hashと等しい
- hashの計算方法が誤ってないか確認
ブロックチェーンとして正しいか - 最初のブロックから検証していき条件を満たすか確認していきます
require 'digest/sha2'
require 'json'
require_relative 'block'
class BlockChain
attr_reader :blocks
def initialize
@blocks = []
@blocks << BlockChain.get_genesis_block()
end
def get_latest_block
@blocks.last
end
def generate_next_block data
previous_block = get_latest_block
next_index = previous_block.index + 1
next_timestamp = Time.now.to_i
Block.new(
index: next_index,
previous_hash: previous_block.hash,
timestamp: next_timestamp,
data: data
)
end
def is_valid_new_block? new_block, previous_block
if previous_block.index + 1 != new_block.index
puts "invalid index"
return false
elsif previous_block.hash != new_block.previous_hash
puts "invalid hash: previous hash"
return false
elsif calculate_hash_for_block(new_block) != new_block.hash
puts "invalid hash: hash"
return false
end
true
end
def add_block new_block
if is_valid_new_block?(new_block, get_latest_block)
@blocks << new_block
end
end
def size
@blocks.size
end
private
def calculate_hash_for_block block
Digest::SHA256.hexdigest({
index: block.index,
previous_hash: block.previous_hash,
timestamp: block.timestamp,
data: block.data
}.to_json)
end
class << self
def is_valid_chain? block_chain_to_validate
return false if block_chain_to_validate.blocks[0] != BlockChain.get_genesis_block()
tmp_blocks = [block_chain_to_validate.blocks[0]]
block_chain_to_validate.blocks[1..-1].each.with_index(1) do |block, i|
if block_chain_to_validate.is_valid_new_block?(block, tmp_blocks[i - 1])
tmp_blocks << block
else
return false
end
end
end
def get_genesis_block
Block.genesis_block
end
end
end
ノード(マイナー)クラスは、ブロックチェーンにブロックを追加/ブロックチェーンを受け入れるか判断します。
なお今回はブロックの中のデータにminerの名前とタイムスタンプを入れるようにしてます。
こんな感じ
miner1: 2017-12-16 01:03:00 +0900
require_relative 'block_chain'
class Miner
attr_reader :name, :block_chain
def initialize args
@name = args[:name]
@block_chain = BlockChain.new
end
def accept receive_block_chain
puts "#{@name} checks received block chain. Size: #{@block_chain.size}"
if receive_block_chain.size > @block_chain.size
if BlockChain.is_valid_chain? receive_block_chain
puts "#{@name} accepted received blockchain"
@block_chain = receive_block_chain.clone
else
puts "Received blockchain invalid"
end
end
end
def add_new_block
next_block = @block_chain.generate_next_block "#{@name}: #{Time.now}"
@block_chain.add_block(next_block)
puts "#{@name} add new block: #{next_block.hash}"
end
end
シミュレーターを実行してみます。
マイナーというスレッドを3つ立てて検証します。
以下がメインプログラムです。
require_relative 'lib/block_chain'
require_relative 'lib/miner'
## ブロックチェーンシミュレーター
$receive_block_chain = BlockChain.new
def create_miner args
Thread.new {
miner = Miner.new args
3.times do
sleep [1, 2, 3].sample
miner.accept $receive_block_chain
[1, 2, 3].sample.times.each do |i|
miner.add_new_block
end
broadcast miner
end
}
end
def broadcast miner
puts "#{miner.name} broadcasted"
$receive_block_chain = miner.block_chain
end
puts "start"
th1 = create_miner name: "miner1"
th2 = create_miner name: "miner2"
th3 = create_miner name: "miner3"
[th1, th2, th3].each{|t| t.join}
puts "block chain result"
$receive_block_chain.blocks.each{|block| puts block.data}
実行結果
start
miner3 checks received block chain. Size: 1
miner3 add new block: 5383cca2dfd5530df9a9bdfec05590834166a9e5aa7371db389feb0d0aec9ab6
miner1 checks received block chain. Size: 1
miner3 add new block: b41dd46c244fa633f168a28aa69b28b8f9c37eb7f7c0f3c29999eabfa24886d3
miner1 add new block: 713a5534e4ec9eee88894f3a357b47bd06c236c077ef140b822c4fef03c0b033
miner3 broadcasted
miner1 add new block: 6baea7509d3e8ba65115618460cc8939353448a85045efb27abd25370711066b
miner1 add new block: 4dcf23972c6a40b3e91c16630c4afb55503231085d7ae99a1f66247008be789d
miner1 broadcasted
miner2 checks received block chain. Size: 1
miner3 checks received block chain. Size: 3
miner1 checks received block chain. Size: 4
miner3 accepted received blockchain
miner1 add new block: 67ff3bfa6c53707d225ac51a7788b60806dfc7b18e00fb8b6cfefb543276b880
miner3 add new block: e0a0046f83a57b20422d755edf7606144f9e5a5d1571800245cc91cc3f4cf339
miner1 add new block: 6b0bfecbd2acb9ca75a13d6afe8b6f64c43815862cd5b50388c5cc7e6c0aef8b
miner1 broadcasted
miner3 add new block: fcbe41cfb65f1f981aa9d1a3468727a689123c39c4b7859ec4edef58ffbdafcf
miner3 broadcasted
miner2 accepted received blockchain
miner2 add new block: d0b72c499b538fe7caf755c71a6c54c2d801c30eb9f6ebd21d9fc7256b99db8d
miner2 add new block: ea5479a23d280de80ce074a11a8de3a9654f3b4e642cb700d44b7e49ed45121b
miner2 add new block: cb0a6226f0a0a1e0314462c095ed354ddce85a945b4aec674103287c75c1b8dd
miner2 broadcasted
miner1 checks received block chain. Size: 11
miner1 add new block: 7f67554bd27279b1c52b8aad48ca2605c04f8c441011831d8ad7241d37c96e3f
miner1 broadcasted
miner3 checks received block chain. Size: 12
miner3 add new block: 2194a36fe2fe9d0c4961a17cad89097fbc4829ccf9af623b1f199f7910c2ff62
miner3 add new block: ad928d4d143cc9f7c0cd6360cd27c845bab2d86b2fcc0888262cacae61c22908
miner3 broadcasted
miner2 checks received block chain. Size: 14
miner2 add new block: aa52bbb540f6e892a198c3b58cc98fe9a0c78b695ce60b32a35e50a31b01bb63
miner2 broadcasted
miner2 checks received block chain. Size: 15
miner2 add new block: 914f63094953b62b51dfc43e3c6916b86b9a5bf4693f90722bca20ff35c20e95
miner2 add new block: 62c67487c930026ab9d4e4ba22847a6b3c2b27a26fcf2906bda780d22399c72f
miner2 broadcasted
block chain result
genesis block!!
miner1: 2017-12-16 01:03:00 +0900
miner1: 2017-12-16 01:03:00 +0900
miner1: 2017-12-16 01:03:00 +0900
miner1: 2017-12-16 01:03:01 +0900
miner3: 2017-12-16 01:03:01 +0900
miner1: 2017-12-16 01:03:01 +0900
miner3: 2017-12-16 01:03:01 +0900
miner2: 2017-12-16 01:03:01 +0900
miner2: 2017-12-16 01:03:01 +0900
miner2: 2017-12-16 01:03:01 +0900
miner1: 2017-12-16 01:03:02 +0900
miner3: 2017-12-16 01:03:04 +0900
miner3: 2017-12-16 01:03:04 +0900
miner2: 2017-12-16 01:03:04 +0900
miner2: 2017-12-16 01:03:07 +0900
miner2: 2017-12-16 01:03:07 +0900
実装は以上です
今回実装したものとビットコインブロックチェーンの違い
たくさんあると思うのですが、大きめの違いを
トランザクション
dataに入るものは今回は文字列でした。
ビットコインのブロックチェーンは、複数のトランザクションをデータに持ちます。
トランザクションは、送金元と送金先、いくらのコインを移動するかというようなデータです。
マイニング
今回作成したブロックチェーンはブロックを生成する際にコストがほとんどかからないです。
なので、改竄しようと思ったらマシンパワーの差が大きく効いてきます。
対してビットコインのブロックチェーン技術は、ブロック生成の処理に運を含む処理を入れています。
これが「マイニング/Proof of Work」です。
やることは、ノンス という適当な数を探すことです。
ブロックのハッシュを計算する際に所定の数の0を連続するハッシュを作成することが求められます。
そのため、ノンスを決めてブロックに入れてハッシュを生成...条件を満たすまで繰り返します。
実際のブロックのhashはこんな感じになってます。
ちなみにブロックの生成に成功すると、誰も所有したことのない新規のビットコイン(コインベース)と自分が取り込んだトランザクションの手数料の総額が入ります。
このインセンティブを得るためにマイナーは頑張って計算します。
(ちなみに新規ビットコインの数は徐々に減っていきます: 現在は12.5BTC/block + 手数料BTC => 日本円にしたら2500万円/block以上です 12/15日現在)
改竄していくほうが損というわけです。
結果としてビサンチン障害を含むノードがあっても大丈夫という感じです。
ビットコインのジェネシスブロック
1つのトランザクションを含むブロックです
引用
こんな感じで、誰でもトランザクションを見ることができます。
どのアドレスからどのアドレスに送られたかわかるようになってます。
まとめ
今回実際にブロックチェーンを実装、ビットコインのブロックチェーンを調べてみてわかったことは
・分散ノードを前提とした耐久性の高さ
・改竄可能性を限りなく0にしている
ということです。
サービスを考える際は、
- インセンティブをどのように設定してネットワークに参加するノードを集めるか
- ブロックチェーンに取り込まれると削除/更新ができない
ことに注目しないといけないのかなと思いました。
ビットコインウォレット/スマートコントラクト等理解できてないことも山のようにあるので、引き続き調べていこうと思います。
ありがとうございました〜
参考
- http://mizchi.hatenablog.com/entry/2017/11/30/002046 < この投稿はこの記事に大きく影響を受けています!!
- ブロックチェーンの定義
- いちばんやさしいブロックチェーンの教本
- ビットコインのジェネシスブロック
- ビットコインのブロックチェーンをいい感じに見れるサイト