11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ateam Brides Inc.Advent Calendar 2017

Day 25

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

Posted at

ビットコインの原論文を読んで俺のブロックチェーンをつくろう 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で構築してみたいです。
  • あと署名部分。プルーフ・オブ・ワークまでは行けないかも。
11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?