lib/bitcoin/protocol/block.rb in bitcoin-ruby-0.0.1 vs lib/bitcoin/protocol/block.rb in bitcoin-ruby-0.0.2
- old
+ new
@@ -1,10 +1,17 @@
+# encoding: ascii-8bit
+
module Bitcoin
module Protocol
class Block
+ BLOCK_VERSION_DEFAULT = (1 << 0)
+ BLOCK_VERSION_AUXPOW = (1 << 8)
+ BLOCK_VERSION_CHAIN_START = (1 << 16)
+ BLOCK_VERSION_CHAIN_END = (1 << 30)
+
# block hash
attr_accessor :hash
# previous block hash
attr_accessor :prev_block
@@ -28,69 +35,109 @@
attr_accessor :ver
# raw protocol payload
attr_accessor :payload
+ # AuxPow linking the block to a merge-mined chain
+ attr_accessor :aux_pow
+
alias :transactions :tx
# compare to another block
def ==(other)
@hash == other.hash
end
+ def binary_hash
+ [@hash].pack("H*")
+ end
+
+ def prev_block_hex
+ @prev_block_hex ||= @prev_block.reverse.unpack("H*")[0]
+ end
+
# create block from raw binary +data+
def initialize(data)
@tx = []
- parse_data(data) if data
+ parse_data_from_io(data) if data
end
# parse raw binary data
def parse_data(data)
- @ver, @prev_block, @mrkl_root, @time, @bits, @nonce, payload = data.unpack("Ia32a32IIIa*")
+ buf = parse_data_from_io(data)
+ buf.eof? ? true : buf.read
+ end
+
+ # parse raw binary data
+ def parse_data_from_io(buf, header_only=false)
+ buf = buf.is_a?(String) ? StringIO.new(buf) : buf
+ @ver, @prev_block, @mrkl_root, @time, @bits, @nonce = buf.read(80).unpack("Va32a32VVV")
recalc_block_hash
- tx_size, payload = Protocol.unpack_var_int(payload)
- (0...tx_size).each{ break if payload == true
+ if (@ver & BLOCK_VERSION_AUXPOW) > 0
+ @aux_pow = AuxPow.new(nil)
+ @aux_pow.parse_data_from_io(buf)
+ end
+
+ return buf if buf.eof?
+
+ tx_size = Protocol.unpack_var_int_from_io(buf)
+ @tx_count = tx_size
+ return buf if header_only
+
+ tx_size.times{ break if payload == true
t = Tx.new(nil)
- payload = t.parse_data(payload)
+ payload = t.parse_data_from_io(buf)
@tx << t
}
@payload = to_payload
- payload
+ buf
end
# recalculate the block hash
def recalc_block_hash
- @hash = Bitcoin.block_hash(hth(@prev_block), hth(@mrkl_root), @time, @bits, @nonce, @ver)
+ @hash = Bitcoin.block_hash(@prev_block.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver)
end
+ def recalc_mrkl_root
+ @mrkl_root = Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last.htb_reverse
+ end
+
+ # verify mrkl tree
+ def verify_mrkl_root
+ @mrkl_root.reverse_hth == Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last
+ end
+
# get the block header info
# [<version>, <prev_block>, <merkle_root>, <time>, <bits>, <nonce>, <txcount>, <size>]
def header_info
- [@ver, hth(@prev_block), hth(@mrkl_root), Time.at(@time), @bits, @nonce, @tx.size, @payload.size]
+ [@ver, @prev_block.reverse_hth, @mrkl_root.reverse_hth, Time.at(@time), @bits, @nonce, @tx.size, @payload.size]
end
- def hth(h); h.reverse.unpack("H*")[0]; end
- def htb(s); [s].pack('H*').reverse; end
-
# convert to raw binary format
def to_payload
- head = [@ver, @prev_block, @mrkl_root, @time, @bits, @nonce].pack("Ia32a32III")
- [head, Protocol.pack_var_int(@tx.size), @tx.map(&:to_payload).join].join
+ head = [@ver, @prev_block, @mrkl_root, @time, @bits, @nonce].pack("Va32a32VVV")
+ head << @aux_pow.to_payload if @aux_pow
+ return head if @tx.size == 0
+ head << Protocol.pack_var_int(@tx.size)
+ @tx.each{|tx| head << tx.to_payload }
+ head
end
# convert to ruby hash (see also #from_hash)
def to_hash
- {
+ h = {
'hash' => @hash, 'ver' => @ver,
- 'prev_block' => hth(@prev_block), 'mrkl_root' => hth(@mrkl_root),
+ 'prev_block' => @prev_block.reverse_hth, 'mrkl_root' => @mrkl_root.reverse_hth,
'time' => @time, 'bits' => @bits, 'nonce' => @nonce,
'n_tx' => @tx.size, 'size' => (@payload||to_payload).bytesize,
'tx' => @tx.map{|i| i.to_hash },
'mrkl_tree' => Bitcoin.hash_mrkl_tree( @tx.map{|i| i.hash } )
}
+ h['aux_pow'] = @aux_pow.to_hash if @aux_pow
+ h
end
def hextarget
Bitcoin.decode_compact_bits(@bits)
end
@@ -101,10 +148,26 @@
def difficulty
Bitcoin.block_difficulty(@bits)
end
+ # introduced in block version 2 by BIP_0034
+ # blockchain height as seen by the block itself.
+ # do not trust this value, instead verify with chain storage.
+ def bip34_block_height(height=nil)
+ return nil unless @ver >= 2
+ if height # generate height binary
+ buf = [height].pack("V").gsub(/\x00+$/,"")
+ [buf.bytesize, buf].pack("Ca*")
+ else
+ coinbase = @tx.first.inputs.first.script_sig
+ coinbase[1..coinbase[0].ord].ljust(4, "\x00").unpack("V").first
+ end
+ rescue
+ nil
+ end
+
# convert to json representation as seen in the block explorer.
# (see also #from_json)
def to_json(options = {:space => ''}, *a)
JSON.pretty_generate( to_hash, options )
end
@@ -114,17 +177,23 @@
def to_json_file(path)
File.open(path, 'wb'){|f| f.print to_json; }
end
# parse ruby hash (see also #to_hash)
- def self.from_hash(h)
+ def self.from_hash(h, do_raise=true)
blk = new(nil)
blk.instance_eval{
@ver, @time, @bits, @nonce = h.values_at('ver', 'time', 'bits', 'nonce')
- @prev_block, @mrkl_root = h.values_at('prev_block', 'mrkl_root').map{|i| htb(i) }
- recalc_block_hash
+ @prev_block, @mrkl_root = h.values_at('prev_block', 'mrkl_root').map{|i| i.htb_reverse }
+ unless h['hash'] == recalc_block_hash
+ raise "Block hash mismatch! Claimed: #{h['hash']}, Actual: #{@hash}" if do_raise
+ end
+ @aux_pow = AuxPow.from_hash(h['aux_pow']) if h['aux_pow']
h['tx'].each{|tx| @tx << Tx.from_hash(tx) }
+ if h['tx'].any? && !Bitcoin.freicoin?
+ (raise "Block merkle root mismatch! Block: #{h['hash']}" unless verify_mrkl_root) if do_raise
+ end
}
blk
end
# convert ruby hash to raw binary
@@ -146,9 +215,20 @@
# read binary block from a file
def self.from_file(path); new( Bitcoin::Protocol.read_binary_file(path) ); end
# read json block from a file
def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
+
+ def validator(store, prev_block = nil)
+ @validator ||= Bitcoin::Validation::Block.new(self, store, prev_block)
+ end
+
+ # get the (statistical) amount of work that was needed to generate this block.
+ def block_work
+ target = Bitcoin.decode_compact_bits(@bits)
+ (2**256) / (target.to_i(16) + 1)
+ end
+
end
end
end