Class: Bitcoin::Protocol::Block

Inherits:
Object
  • Object
show all
Defined in:
lib/bitcoin/protocol/block.rb

Overview

Constant Summary collapse

BLOCK_VERSION_DEFAULT =
(1 << 0)
BLOCK_VERSION_AUXPOW =
(1 << 8)
BLOCK_VERSION_CHAIN_START =
(1 << 16)
BLOCK_VERSION_CHAIN_END =
(1 << 30)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = nil) ⇒ Block

create block from raw binary data


64
65
66
67
68
# File 'lib/bitcoin/protocol/block.rb', line 64

def initialize(data = nil)
  @tx = []
  @payload = nil
  parse_data_from_io(data) if data
end

Instance Attribute Details

#aux_powObject

AuxPow linking the block to a merge-mined chain


44
45
46
# File 'lib/bitcoin/protocol/block.rb', line 44

def aux_pow
  @aux_pow
end

#bitsObject

difficulty target bits


32
33
34
# File 'lib/bitcoin/protocol/block.rb', line 32

def bits
  @bits
end

#hashObject

block hash


13
14
15
# File 'lib/bitcoin/protocol/block.rb', line 13

def hash
  @hash
end

#mrkl_rootObject

merkle root


26
27
28
# File 'lib/bitcoin/protocol/block.rb', line 26

def mrkl_root
  @mrkl_root
end

#nonceObject

nonce (number counted when searching for block hash matching target)


35
36
37
# File 'lib/bitcoin/protocol/block.rb', line 35

def nonce
  @nonce
end

#partial_merkle_treeObject (readonly)

Returns the value of attribute partial_merkle_tree


46
47
48
# File 'lib/bitcoin/protocol/block.rb', line 46

def partial_merkle_tree
  @partial_merkle_tree
end

#payloadObject

raw protocol payload


41
42
43
# File 'lib/bitcoin/protocol/block.rb', line 41

def payload
  @payload
end

#prev_block_hashObject Also known as: prev_block

previous block hash


16
17
18
# File 'lib/bitcoin/protocol/block.rb', line 16

def prev_block_hash
  @prev_block_hash
end

#timeObject

block generation time


29
30
31
# File 'lib/bitcoin/protocol/block.rb', line 29

def time
  @time
end

#txObject Also known as: transactions

transactions (Array of Tx)


23
24
25
# File 'lib/bitcoin/protocol/block.rb', line 23

def tx
  @tx
end

#verObject

version (usually 1)


38
39
40
# File 'lib/bitcoin/protocol/block.rb', line 38

def ver
  @ver
end

Class Method Details

.binary_from_hash(h) ⇒ Object

convert ruby hash to raw binary


265
266
267
# File 'lib/bitcoin/protocol/block.rb', line 265

def self.binary_from_hash(h)
  from_hash(h).to_payload
end

.binary_from_json(json_string) ⇒ Object

convert json representation to raw binary


275
276
277
# File 'lib/bitcoin/protocol/block.rb', line 275

def self.binary_from_json(json_string)
  from_json(json_string).to_payload
end

.from_file(path) ⇒ Object

read binary block from a file


294
295
296
# File 'lib/bitcoin/protocol/block.rb', line 294

def self.from_file(path)
  new(Bitcoin::Protocol.read_binary_file(path))
end

.from_hash(h, do_raise = true) ⇒ Object

parse ruby hash (see also #to_hash)


244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/bitcoin/protocol/block.rb', line 244

def self.from_hash(h, do_raise = true)
  blk = new(nil)
  blk.instance_eval do
    @ver, @time, @bits, @nonce = h.values_at('ver', 'time', 'bits', 'nonce')
    @prev_block_hash, @mrkl_root = h.values_at('prev_block', 'mrkl_root').map(&:htb_reverse)

    if do_raise && h['hash'] != recalc_block_hash
      raise "Block hash mismatch! Claimed: #{h['hash']}, Actual: #{@hash}"
    end

    @aux_pow = AuxPow.from_hash(h['aux_pow']) if h['aux_pow']

    h['tx'].each { |tx| @tx << Tx.from_hash(tx, do_raise) }
    if h['tx'].any? && do_raise && !verify_mrkl_root
      raise "Block merkle root mismatch! Block: #{h['hash']}"
    end
  end
  blk
end

.from_json(json_string) ⇒ Object

parse json representation (see also #to_json)


270
271
272
# File 'lib/bitcoin/protocol/block.rb', line 270

def self.from_json(json_string)
  from_hash(JSON.parse(json_string))
end

.from_json_file(path) ⇒ Object

read json block from a file


299
300
301
# File 'lib/bitcoin/protocol/block.rb', line 299

def self.from_json_file(path)
  from_json(Bitcoin::Protocol.read_binary_file(path))
end

Instance Method Details

#==(other) ⇒ Object

compare to another block


51
52
53
# File 'lib/bitcoin/protocol/block.rb', line 51

def ==(other)
  @hash == other.hash
end

#binary_hashObject


55
56
57
# File 'lib/bitcoin/protocol/block.rb', line 55

def binary_hash
  [@hash].pack('H*')
end

#bip34_block_height(height = nil) ⇒ Object

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.


218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/bitcoin/protocol/block.rb', line 218

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 StandardError
  nil
end

#block_headerObject

block header binary output


287
288
289
290
291
# File 'lib/bitcoin/protocol/block.rb', line 287

def block_header
  [
    @ver, @prev_block_hash, @mrkl_root, @time, @bits, @nonce, Protocol.pack_var_int(0)
  ].pack('Va32a32VVVa*')
end

#block_workObject

get the (statistical) amount of work that was needed to generate this block.


304
305
306
307
308
# File 'lib/bitcoin/protocol/block.rb', line 304

def block_work
  target = Bitcoin.decode_compact_bits(@bits).to_i(16)
  return 0 if target <= 0
  (2**256) / (target + 1)
end

#decimaltargetObject


207
208
209
# File 'lib/bitcoin/protocol/block.rb', line 207

def decimaltarget
  Bitcoin.decode_compact_bits(@bits).to_i(16)
end

#difficultyObject


211
212
213
# File 'lib/bitcoin/protocol/block.rb', line 211

def difficulty
  Bitcoin.block_difficulty(@bits)
end

#header_infoObject

get the block header info

<version>, <prev_block>, <merkle_root>, <time>, <bits>, <nonce>, <txcount>, <size>

166
167
168
169
170
171
# File 'lib/bitcoin/protocol/block.rb', line 166

def header_info
  [
    @ver, @prev_block_hash.reverse_hth, @mrkl_root.reverse_hth,
    Time.at(@time), @bits, @nonce, @tx.size, @payload.size
  ]
end

#header_to_json(options = { space: '' }) ⇒ Object

convert header to json representation.


280
281
282
283
284
# File 'lib/bitcoin/protocol/block.rb', line 280

def header_to_json(options = { space: '' })
  h = to_hash
  %w[tx mrkl_tree].each { |k| h.delete(k) }
  JSON.pretty_generate(h, options)
end

#hextargetObject


203
204
205
# File 'lib/bitcoin/protocol/block.rb', line 203

def hextarget
  Bitcoin.decode_compact_bits(@bits)
end

#parse_data(data) ⇒ Object

parse raw binary data


71
72
73
74
# File 'lib/bitcoin/protocol/block.rb', line 71

def parse_data(data)
  buf = parse_data_from_io(data)
  buf.eof? ? true : buf.read
end

#parse_data_from_io(buf, header_only = false) ⇒ Object

parse raw binary data


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/bitcoin/protocol/block.rb', line 77

def parse_data_from_io(buf, header_only = false)
  buf = buf.is_a?(String) ? StringIO.new(buf) : buf
  @ver, @prev_block_hash, @mrkl_root, @time, @bits, @nonce = buf.read(80).unpack('Va32a32VVV')
  recalc_block_hash

  if !Bitcoin.network[:auxpow_chain_id].nil? && (@ver & BLOCK_VERSION_AUXPOW) > 0
    @aux_pow = AuxPow.new(nil)
    @aux_pow.parse_data_from_io(buf)
  end

  return buf if buf.eof?

  return parse_filtered_header(buf) if header_only == :filtered

  tx_size = Protocol.unpack_var_int_from_io(buf)
  @tx_count = tx_size
  return buf if header_only

  tx_size.times do
    break if payload == true
    return buf if buf.eof?

    t = Tx.new(nil)
    t.parse_data_from_io(buf)
    @tx << t
  end

  @payload = to_payload
  buf
end

#parse_filtered_header(buf) ⇒ Object


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/bitcoin/protocol/block.rb', line 108

def parse_filtered_header(buf)
  @tx_count = buf.read(4).unpack('V')[0]

  nhashes = Protocol.unpack_var_int_from_io(buf)
  hashes = []
  nhashes.times do
    hashes << buf.read(256 / 8)
  end

  nflags = Protocol.unpack_var_int_from_io(buf)
  flags = buf.read(nflags)

  @partial_merkle_tree = PartialMerkleTree.new(@tx_count, hashes, flags)
  @partial_merkle_tree.assign_value

  buf
end

#prev_block=(hash) ⇒ Object


18
19
20
# File 'lib/bitcoin/protocol/block.rb', line 18

def prev_block=(hash)
  @prev_block_hash = hash
end

#prev_block_hexObject


59
60
61
# File 'lib/bitcoin/protocol/block.rb', line 59

def prev_block_hex
  @prev_block_hex ||= @prev_block_hash.reverse.unpack('H*')[0]
end

#recalc_block_hashObject

recalculate the block hash


127
128
129
130
131
# File 'lib/bitcoin/protocol/block.rb', line 127

def recalc_block_hash
  @hash = Bitcoin.block_hash(
    @prev_block_hash.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver
  )
end

#recalc_block_scrypt_hashObject


133
134
135
136
137
# File 'lib/bitcoin/protocol/block.rb', line 133

def recalc_block_scrypt_hash
  @scrypt_hash = Bitcoin.block_scrypt_hash(
    @prev_block_hash.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver
  )
end

#recalc_mrkl_rootObject


139
140
141
142
143
144
145
# File 'lib/bitcoin/protocol/block.rb', line 139

def recalc_mrkl_root
  @mrkl_root = if partial_merkle_tree
                 partial_merkle_tree.root.value.htb_reverse
               else
                 Bitcoin.hash_mrkl_tree(@tx.map(&:hash)).last.htb_reverse
               end
end

#sizeObject


199
200
201
# File 'lib/bitcoin/protocol/block.rb', line 199

def size
  payload.bytesize
end

#to_hash(options = {}) ⇒ Object

convert to ruby hash (see also #from_hash)


186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/bitcoin/protocol/block.rb', line 186

def to_hash(options = {})
  h = {
    'hash' => @hash, 'ver' => @ver,
    'prev_block' => @prev_block_hash.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(options) },
    'mrkl_tree' => Bitcoin.hash_mrkl_tree(@tx.map(&:hash))
  }
  h['aux_pow'] = @aux_pow.to_hash if @aux_pow
  h
end

#to_json(options = { space: '' }, *_) ⇒ Object

convert to json representation as seen in the block explorer. (see also #from_json)


233
234
235
# File 'lib/bitcoin/protocol/block.rb', line 233

def to_json(options = { space: '' }, *_)
  JSON.pretty_generate(to_hash(options), options)
end

#to_json_file(path) ⇒ Object

write json representation to a file (see also #to_json)


239
240
241
# File 'lib/bitcoin/protocol/block.rb', line 239

def to_json_file(path)
  File.open(path, 'wb') { |f| f.print to_json; }
end

#to_payloadObject

convert to raw binary format


174
175
176
177
178
179
180
181
182
183
# File 'lib/bitcoin/protocol/block.rb', line 174

def to_payload
  @aux_pow ||= nil
  head = [@ver, @prev_block_hash, @mrkl_root, @time, @bits, @nonce].pack('Va32a32VVV')
  head << @aux_pow.to_payload if @aux_pow
  return head if @tx.empty?

  head << Protocol.pack_var_int(@tx.size)
  @tx.each { |tx| head << tx.to_payload }
  head
end

#tx_hashesObject


156
157
158
159
160
161
162
# File 'lib/bitcoin/protocol/block.rb', line 156

def tx_hashes
  if partial_merkle_tree
    partial_merkle_tree.tx_hashes
  else
    @tx.map(&:hash)
  end
end

#verify_mrkl_rootObject

verify mrkl tree


148
149
150
151
152
153
154
# File 'lib/bitcoin/protocol/block.rb', line 148

def verify_mrkl_root
  if partial_merkle_tree
    partial_merkle_tree.valid_tree?(@mrkl_root.reverse_hth)
  else
    @mrkl_root.reverse_hth == Bitcoin.hash_mrkl_tree(@tx.map(&:hash)).last
  end
end