Help us understand the problem. What is going on with this article?

ビットコインの原論文を読んで俺のブロックチェーンをつくろう Part2

More than 3 years have passed since last update.

ビットコインの原論文を読んで俺のブロックチェーンをつくろう Part1の続きです。

  • 注意
    • 今回で終わる予定でしたが、Part 5くらいまでありそうです。
    • P2Pネットワーク、プルーフ・オブ・ワーク部分は次回か次々回くらい!
    • ソースコードに関してBitcoinの本当の実装は読んでません。自分が楽しいようにやりやすいように何も考えずに書いております。(使い捨てのソースコードになっとる汗)

Part 2の概要

  • 所持コインを見る
  • コインを誰かに送る(トランザクションの作成のみ)

所持コインを見る

少しおさらいをします。ブロックチェーンのブロックには取引(トランザクション)の記録が含まれています。
そのブロックがチェーンとなって、最初の取引から最新の取引までを記録しています。
それを各ノードが保持することで、皆で同じ取引履歴を共有し、コインのやり取りができる仕組みのようです。

自分の所持コインを見るのに、今回は以下のようにしました。
1. 自分宛てに送られたコインを集計
2. そのうち使用されたコインは省く

取引について

今回、取引はTransactionクラスで表していて、論文に習ってInputとOutputを複数保持できるようにしてあります。

自分宛てのコインというのは、Outputのtoが自分のidになっているものにしました。
(idはnode作成順に0,1,2..単純に割り振ってます。)

自分宛ての取引をInputにして、誰か宛へOutputした取引があれば、そのInputに指定した取引は使用済みとしています。
自分宛ての取引の指定ですが、その取引のデータをハッシュした値にをInputクラスが保持することによって指定しています。

transaction.rb
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?メソッドとしました。

node.rb
  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
blockchain.rb
  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
block.rb
  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
transaction.rb
  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

これで集計ができます。
(署名の確認などは後回し!)

コイン送信部分(トランザクションの作成のみ)

自分の所持コインの確認ができるようになったので、送信のためのトランザクションの作成まで書きます。

node.rb
  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
transaction.rb
  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で構築してみたいです。
  • あと署名部分。プルーフ・オブ・ワークまでは行けないかも。
gyasui
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away