ビットコインの原論文を読んで俺のブロックチェーンをつくろう Part1の続きです。
- 注意
- 今回で終わる予定でしたが、Part 5くらいまでありそうです。
- P2Pネットワーク、プルーフ・オブ・ワーク部分は次回か次々回くらい!
- ソースコードに関してBitcoinの本当の実装は読んでません。自分が楽しいようにやりやすいように何も考えずに書いております。(使い捨てのソースコードになっとる汗)
Part 2の概要
- 所持コインを見る
- コインを誰かに送る(トランザクションの作成のみ)
所持コインを見る
少しおさらいをします。ブロックチェーンのブロックには取引(トランザクション)の記録が含まれています。
そのブロックがチェーンとなって、最初の取引から最新の取引までを記録しています。
それを各ノードが保持することで、皆で同じ取引履歴を共有し、コインのやり取りができる仕組みのようです。
自分の所持コインを見るのに、今回は以下のようにしました。
- 自分宛てに送られたコインを集計
- そのうち使用されたコインは省く
取引について
今回、取引はTransactionクラスで表していて、論文に習ってInputとOutputを複数保持できるようにしてあります。
自分宛てのコインというのは、Outputのtoが自分のidになっているものにしました。
(idはnode作成順に0,1,2..単純に割り振ってます。)
自分宛ての取引をInputにして、誰か宛へOutputした取引があれば、そのInputに指定した取引は使用済みとしています。
自分宛ての取引の指定ですが、その取引のデータをハッシュした値にをInputクラスが保持することによって指定しています。
class Transaction
attr_reader :hash
def self.create_genesis_transaction
inputs = [Input.new(coinbase: "my blockchain")]
outputs = [Output.new(to: "0", coin_value: "1000")]
new(inputs: inputs, outputs: outputs)
end
def initialize(args)
@inputs = args[:inputs]
@outputs = args[:outputs]
digest = OpenSSL::Digest.new('sha256')
@inputs.each do |input|
digest.update(input.to_s)
end
@outputs.each do |output|
digest.update(output.to_s)
end
@hash = digest.hexdigest()
end
def transaction_to?(id)
@outputs.each do |output|
return true if output.to == id
end
return false
end
def sum_of_coin_to(id)
sum = 0
@outputs.each do |output|
sum += output.coin_value.to_i if output.to == id
end
sum
end
def used_as_input?(hash)
@inputs.each do |input|
return true if input.hash == hash
end
false
end
def to_s
<<~EOS
inputs : #{@inputs.to_s}
outputs : #{@outputs.to_s}
EOS
end
private
class Input
def initialize(args)
@coinbase = args[:coinbase]
@hash = args[:hash]
end
def to_s
string = ""
string += @coinbase unless @coinbase.nil?
string += @hash unless @hash.nil?
string
end
end
class Output
attr_reader :to, :coin_value
def initialize(args)
@to = args[:to]
@coin_value = args[:coin_value]
end
def to_s
string = ""
string += @to unless @to.nil?
string += @coin_value unless @coin_value.nil?
string
end
end
end
## 集計
集計部分はwalletメソッドとして、idを引数に自分宛て全てのトランザクションを検索できるようにしています。
また、使用済みかどうかはused_as_input?メソッドとしました。
def wallet(id)
txs = @blockchain.get_all_transaction_to(id)
txs.reject do |tx|
@blockchain.used_as_input?(tx.hash)
end
coins = 0
txs.each do |tx|
coins += tx.sum_of_coin_to(@id)
end
coins
end
def get_all_transaction_to(id)
txs = Array.new
@blockchain.each do |block|
tx = block.get_all_transaction_to id
txs << tx unless txs.nil?
end
txs.flatten
end
def used_as_input?(hash)
@blockchain.each do |block|
return true if block.used_as_input?(hash) == true
end
false
end
def get_all_transaction_to(id)
@transactions.select do |tx|
tx.transaction_to? id
end
end
def used_as_input?(hash)
@transactions.each do |tx|
return true if tx.used_as_input?(hash) == true
end
false
end
def sum_of_coin_to(id)
sum = 0
@outputs.each do |output|
sum += output.coin_value.to_i if output.to == id
end
sum
end
これで集計ができます。
(署名の確認などは後回し!)
コイン送信部分(トランザクションの作成のみ)
自分の所持コインの確認ができるようになったので、送信のためのトランザクションの作成まで書きます。
def send(target, coin)
txs = @blockchain.get_all_transaction_to(@id)
txs.reject do |tx|
@blockchain.used_as_input?(tx.hash)
end
input_coins = 0
hashs = Array.new
txs.each do |tx|
input_coins += tx.sum_of_coin_to(@id)
hashs << tx.hash
break if input_coins > coin.to_i
end
return if input_coins < coin.to_i
to_and_coin_values = [{to: target, coin_value: coin}]
if (input_coins - coin.to_i) > 0
to_and_coin_values << {to: @id, coin_value: (input_coins - coin.to_i).to_s}
end
tx = Transaction.create_transaction(hashs, to_and_coin_values)
end
def self.create_transaction(hashs, to_and_coin_values)
outputs = Array.new
to_and_coin_values.each do |to_and_cv|
outputs << Output.new(to: to_and_cv[:to], coin_value: to_and_cv[:coin_value])
end
inputs = Array.new
hashs.each do |hash|
inputs << Input.new(hash: hash)
end
new(inputs: inputs, outputs: outputs)
end
次回
- 次回こそネットワークの部分を書いていきたい!
- 実際のBitcoinがどうなっているのかわからないですが、とりあえずSkipGraphで構築してみたいです。
- あと署名部分。プルーフ・オブ・ワークまでは行けないかも。