アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

bitcoin-rubyのBlockとTxをbitcoinのデータから生成する

前回の記事
openassets-rubyをrailsに組み込んでみる - アルゴリズムとかオーダーとかの続きです。

今回はbitcoin-rubyで定義されているBitcoin::Protocol::BlockとBitcoin::Protocol::Txクラスを生成してみます。


まずはBlockから。説明は後回しでまずはソースをぺたり。

# blockのcountを指定してblockを取得する
def getblockbycount(blockcount)
      blockhash = provider.getblockhash(blockcount)
      blockdata = provider.getblock(blockhash, false)
      Bitcoin::Protocol::Block.new(blockdata.htb)
end

上記のメソッドを前回作ったBitcoinUtilに追加して、rails consoleで実行してみます。
実行結果は以下のようになるはず。

irb(main):003:0* BitcoinUtil.getblockbycount(101)
=> #<Bitcoin::Protocol::Block:0x007fb01f21dac8 @tx=[#<Bitcoin::Protocol::Tx:0x007fb01eba9368 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007fb01f2f1788 @value=5000000000, @pk_script_length=35, @pk_script="!\x03\xF5\xB8\xC4\xE4\x19\x95\r\x10\xB1\x11;\xC7k\xE0vD\x18K/\x8BC7\xC6\x96\x01\xE9Oq\x99\x9F\xB0\x12\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007fb01f2e2b20 @prev_out_hash="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", @prev_out_index=4294967295, @script_sig_length=4, @script_sig="\x01e\x01\x01", @sequence="\xFF\xFF\xFF\xFF", @script_witness=#<Bitcoin::Protocol::ScriptWitness:0x007fb01f2e0578 @stack=[]>>], @lock_time=0, @ver=1, @enable_bitcoinconsensus=false, @hash="7e05fcff4af0a77ff0b8f265b2b9ca82def9c69a6801bc52b71178415c7736af", @payload="\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x04\x01e\x01\x01\xFF\xFF\xFF\xFF\x01\x00\xF2\x05*\x01\x00\x00\x00#!\x03\xF5\xB8\xC4\xE4\x19\x95\r\x10\xB1\x11;\xC7k\xE0vD\x18K/\x8BC7\xC6\x96\x01\xE9Oq\x99\x9F\xB0\x12\xAC\x00\x00\x00\x00">], @ver=536870912, @prev_block_hash="|\xE8\xF8\xC7c|c\x96\f{\xF8\xC0\xEB\"\x1C\xE1\x81\xBD\xCA\xD1\"\xBB\xC7\xCA\x9A\xAE\x10\a\r\ri\x04", @mrkl_root="\xAF6w\\Ax\x11\xB7R\xBC\x01h\x9A\xC6\xF9\xDE\x82\xCA\xB9\xB2e\xF2\xB8\xF0\x7F\xA7\xF0J\xFF\xFC\x05~", @time=1509588862, @bits=545259519, @nonce=1, @hash="27d6943a0532cf70654090a71ebbc0ed8d04ae738581182bc1bb57c3e1eeb59e", @tx_count=1, @payload="\x00\x00\x00 |\xE8\xF8\xC7c|c\x96\f{\xF8\xC0\xEB\"\x1C\xE1\x81\xBD\xCA\xD1\"\xBB\xC7\xCA\x9A\xAE\x10\a\r\ri\x04\xAF6w\\Ax\x11\xB7R\xBC\x01h\x9A\xC6\xF9\xDE\x82\xCA\xB9\xB2e\xF2\xB8\xF0\x7F\xA7\xF0J\xFF\xFC\x05~~\x7F\xFAY\xFF\xFF\x7F \x01\x00\x00\x00\x01\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x04\x01e\x01\x01\xFF\xFF\xFF\xFF\x01\x00\xF2\x05*\x01\x00\x00\x00#!\x03\xF5\xB8\xC4\xE4\x19\x95\r\x10\xB1\x11;\xC7k\xE0vD\x18K/\x8BC7\xC6\x96\x01\xE9Oq\x99\x9F\xB0\x12\xAC\x00\x00\x00\x00">

コードの説明をします。

blockhash = provider.getblockhash(blockcount)
blockdata = provider.getblock(blockhash, false)

の2行でbitcoindから指定されたblock countのblockデータを取得しています。注意点は2行目の第2引数でfalseを指定してるところです。
rpcコマンドのgetblock "hash" で第2引数を指定せずにblockデータを取得するとjson形式で取得できます。
Bitcoint::Protocol::Block#binary_from_hashメソッドにこのjsonを渡してクラスを生成しようとすると、jsonパラメータの名前が違っていてうまく生成できません。(※exampleみるとbitcoinexplorerから取得して生成してるっぽいのでbitcoinexplorerの形式に合わせてるのかな?)

なので、第2引数にfalseを指定してhex stringのままblockデータを取得しています。
最後の3行目でBitcoin::Protocol::Blockクラスを生成しています。

Bitcoin::Protocol::Block.new(blockdata.htb)

ここで説明するものとしてはhtbメソッドぐらいかな?
bitcoin-rubyを入れるとStringクラスが拡張されて、hex stringをbinary stringに変換するhtbとその逆のbthメソッドなどの便利メソッドが追加されます。
Bitcoin::Protocol::Block#newではバイナリーstringを要求されるのでここではrpcコマンドで受け取ったhex stringを上記の便利メソッドを使ったバイナリーStringに変換して渡してます。


続いてBitcoin::Protocol::Txの生成方法。内容がほぼBlockと同じなので説明を省いてソースと実行結果だけ載せておきます。

# txidからtransactionを取得
def gettransaction(txid)
      txdata = provider.getrawtransaction(txid)
      Bitcoin::Protocol::Tx.new(txdata.htb)
end

上記のメソッドをBlockの時とおなじく、BitcoinUtilに追加してrails consoleで実行してみた結果が以下の通り

irb(main):003:0* block = BitcoinUtil.getblockbycount(101)
=> #<Bitcoin::Protocol::Block:0x007fb01f369170 @tx=[#<Bitcoin::Protocol::Tx:0x007fb01eb48630 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007fb01eb7d178 @value=5000000000, @pk_script_length=35, @pk_script="!\x03\xF5\xB8\xC4\xE4\x19\x95\r\x10\xB1\x11;\xC7k\xE0vD\x18K/\x8BC7\xC6\x96\x01\xE9Oq\x99\x9F\xB0\x12\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007fb01eb6e2b8 @prev_out_hash="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", @prev_out_index=4294967295, @script_sig_length=4, @script_sig="\x01e\x01\x01", @sequence="\xFF\xFF\xFF\xFF", @script_witness=#<Bitcoin::Protocol::ScriptWitness:0x007fb01bc42fc0 @stack=[]>>], @lock_time=0, @ver=1, @enable_bitcoinconsensus=false, @hash="7e05fcff4af0a77ff0b8f265b2b9ca82def9c69a6801bc52b71178415c7736af", @payload="\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x04\x01e\x01\x01\xFF\xFF\xFF\xFF\x01\x00\xF2\x05*\x01\x00\x00\x00#!\x03\xF5\xB8\xC4\xE4\x19\x95\r\x10\xB1\x11;\xC7k\xE0vD\x18K/\x8BC7\xC6\x96\x01\xE9Oq\x99\x9F\xB0\x12\xAC\x00\x00\x00\x00">], @ver=536870912, @prev_block_hash="|\xE8\xF8\xC7c|c\x96\f{\xF8\xC0\xEB\"\x1C\xE1\x81\xBD\xCA\xD1\"\xBB\xC7\xCA\x9A\xAE\x10\a\r\ri\x04", @mrkl_root="\xAF6w\\Ax\x11\xB7R\xBC\x01h\x9A\xC6\xF9\xDE\x82\xCA\xB9\xB2e\xF2\xB8\xF0\x7F\xA7\xF0J\xFF\xFC\x05~", @time=1509588862, @bits=545259519, @nonce=1, @hash="27d6943a0532cf70654090a71ebbc0ed8d04ae738581182bc1bb57c3e1eeb59e", @tx_count=1, @payload="\x00\x00\x00 |\xE8\xF8\xC7c|c\x96\f{\xF8\xC0\xEB\"\x1C\xE1\x81\xBD\xCA\xD1\"\xBB\xC7\xCA\x9A\xAE\x10\a\r\ri\x04\xAF6w\\Ax\x11\xB7R\xBC\x01h\x9A\xC6\xF9\xDE\x82\xCA\xB9\xB2e\xF2\xB8\xF0\x7F\xA7\xF0J\xFF\xFC\x05~~\x7F\xFAY\xFF\xFF\x7F \x01\x00\x00\x00\x01\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x04\x01e\x01\x01\xFF\xFF\xFF\xFF\x01\x00\xF2\x05*\x01\x00\x00\x00#!\x03\xF5\xB8\xC4\xE4\x19\x95\r\x10\xB1\x11;\xC7k\xE0vD\x18K/\x8BC7\xC6\x96\x01\xE9Oq\x99\x9F\xB0\x12\xAC\x00\x00\x00\x00">
irb(main):004:0> txid = block.tx.first.hash
=> "7e05fcff4af0a77ff0b8f265b2b9ca82def9c69a6801bc52b71178415c7736af"
irb(main):005:0> BitcoinUtil.gettransaction(txid)
=> #<Bitcoin::Protocol::Tx:0x007fb01eb565c8 @scripts=[], @out=[#<Bitcoin::Protocol::TxOut:0x007fb01eb560c8 @value=5000000000, @pk_script_length=35, @pk_script="!\x03\xF5\xB8\xC4\xE4\x19\x95\r\x10\xB1\x11;\xC7k\xE0vD\x18K/\x8BC7\xC6\x96\x01\xE9Oq\x99\x9F\xB0\x12\xAC">], @in=[#<Bitcoin::Protocol::TxIn:0x007fb01eb563c0 @prev_out_hash="\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", @prev_out_index=4294967295, @script_sig_length=4, @script_sig="\x01e\x01\x01", @sequence="\xFF\xFF\xFF\xFF", @script_witness=#<Bitcoin::Protocol::ScriptWitness:0x007fb01eb56320 @stack=[]>>], @lock_time=0, @ver=1, @enable_bitcoinconsensus=false, @hash="7e05fcff4af0a77ff0b8f265b2b9ca82def9c69a6801bc52b71178415c7736af", @payload="\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x04\x01e\x01\x01\xFF\xFF\xFF\xFF\x01\x00\xF2\x05*\x01\x00\x00\x00#!\x03\xF5\xB8\xC4\xE4\x19\x95\r\x10\xB1\x11;\xC7k\xE0vD\x18K/\x8BC7\xC6\x96\x01\xE9Oq\x99\x9F\xB0\x12\xAC\x00\x00\x00\x00">
irb(main):007:0> BitcoinUtil.gettransaction(txid).out.first.value
=> 5000000000

Blockを取得してBlockからtxidを取得して、txを取得してます。
最後にこのtxが送信したBTCが50BTC(表示はsatoshi単位になってるけど)かどうか確認してます。