Class: Bitcoin::Validation::Block

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

Constant Summary collapse

RULES =
{
  syntax: [:hash, :tx_list, :bits, :max_timestamp, :coinbase, :coinbase_scriptsig, :mrkl_root, :transactions_syntax],
  context: [:prev_hash, :difficulty, :coinbase_value, :min_timestamp, :transactions_context]
}
KNOWN_EXCEPTIONS =
[
  Bitcoin.network[:genesis_hash], # genesis block
  "00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec", # BIP30 exception
  "00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721", # BIP30 exception
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(block, store, prev_block = nil) ⇒ Block

setup new validator for given block, validating context with store, optionally passing the prev_block for optimization.



59
60
61
62
# File 'lib/bitcoin/validation.rb', line 59

def initialize block, store, prev_block = nil
  @block, @store, @error = block, store, nil
  @prev_block = prev_block || store.get_block(block.prev_block.reverse_hth)
end

Instance Attribute Details

#blockObject

Returns the value of attribute block.



20
21
22
# File 'lib/bitcoin/validation.rb', line 20

def block
  @block
end

#errorObject

Returns the value of attribute error.



20
21
22
# File 'lib/bitcoin/validation.rb', line 20

def error
  @error
end

#prev_blockObject

Returns the value of attribute prev_block.



20
21
22
# File 'lib/bitcoin/validation.rb', line 20

def prev_block
  @prev_block
end

#storeObject

Returns the value of attribute store.



20
21
22
# File 'lib/bitcoin/validation.rb', line 20

def store
  @store
end

Instance Method Details

#bitsObject

check that block hash matches claimed bits



76
77
78
79
80
# File 'lib/bitcoin/validation.rb', line 76

def bits
  actual = block.hash.to_i(16)
  expected = Bitcoin.decode_compact_bits(block.bits).to_i(16)
  actual <= expected || [actual, expected]
end

#coinbaseObject

check that coinbase is present



96
97
98
99
# File 'lib/bitcoin/validation.rb', line 96

def coinbase
  coinbase, *rest = block.tx.map{|t| t.inputs.size == 1 && t.inputs.first.coinbase? }
  (coinbase && rest.none?) || [coinbase ? 1 : 0, rest.select{|r| r}.size]
end

#coinbase_scriptsigObject

check that coinbase scriptsig is valid



102
103
104
105
# File 'lib/bitcoin/validation.rb', line 102

def coinbase_scriptsig
  size = block.tx.first.in.first.script_sig.bytesize
  size.between?(2,100) || [size, 2, 100]
end

#coinbase_valueObject

check that coinbase value is valid; no more than reward + fees



108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/bitcoin/validation.rb', line 108

def coinbase_value
  reward = ((50.0 / (2 ** (store.get_depth / Bitcoin::REWARD_DROP.to_f).floor)) * 1e8).to_i
  fees = 0
  block.tx[1..-1].map.with_index do |t, idx|
    val = tx_validators[idx]
    fees += t.in.map.with_index {|i, idx|
      val.prev_txs[idx].out[i.prev_out_index].value rescue 0
    }.inject(:+)
    val.clear_cache # memory optimization on large coinbases, see testnet3 block 4110
  end
  coinbase_output = block.tx[0].out.map(&:value).inject(:+)
  coinbase_output <= reward + fees || [coinbase_output, reward, fees]
end

#difficultyObject

check that bits satisfy required difficulty



133
134
135
136
# File 'lib/bitcoin/validation.rb', line 133

def difficulty
  return true  if Bitcoin.network[:no_difficulty] == true
  block.bits == next_bits_required || [block.bits, next_bits_required]
end

#hashObject

check that block hash matches header



65
66
67
68
# File 'lib/bitcoin/validation.rb', line 65

def hash
  claimed = block.hash; real = block.recalc_block_hash
  claimed == real || [claimed, real]
end

#max_timestampObject

check that block time is not greater than max



90
91
92
93
# File 'lib/bitcoin/validation.rb', line 90

def max_timestamp
  time, max = block.time, Time.now.to_i + 2*60*60
  time < max || [time, max]
end

#min_timestampObject

check that timestamp is newer than the median of the last 11 blocks



139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/bitcoin/validation.rb', line 139

def min_timestamp
  return true  if store.get_depth <= 11
  d = store.get_depth
  first = store.db[:blk][hash: block.prev_block.reverse.blob]
  times = [first[:time]]
  (10).times { first = store.db[:blk][hash: first[:prev_hash].blob]
    times << first[:time] }
  times.sort!
  mid, rem = times.size.divmod(2)
  min_time = (rem == 0 ? times[mid-1, 2].inject(:+) / 2.0 : times[mid])

  block.time > min_time || [block.time, min_time]
end

#mrkl_rootObject

check that merkle root matches transaction hashes



123
124
125
126
# File 'lib/bitcoin/validation.rb', line 123

def mrkl_root
  actual, expected = block.mrkl_root.reverse_hth, Bitcoin.hash_mrkl_tree(block.tx.map(&:hash))[-1]
  actual == expected || [actual, expected]
end

#next_bits_requiredObject



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/bitcoin/validation.rb', line 204

def next_bits_required
  retarget = (Bitcoin.network[:retarget_interval] || Bitcoin::RETARGET_INTERVAL)
  index = (prev_block.depth + 1) / retarget  
  max_target = Bitcoin.decode_compact_bits(Bitcoin.network[:proof_of_work_limit]).to_i(16)
  return Bitcoin.network[:proof_of_work_limit]  if index == 0
  return prev_block.bits  if (prev_block.depth + 1) % retarget != 0
  last = store.db[:blk][hash: prev_block.hash.htb.blob]
  first = store.db[:blk][hash: last[:prev_hash].blob]
  (retarget - 2).times { first = store.db[:blk][hash: first[:prev_hash].blob] }

  nActualTimespan = last[:time] - first[:time]
  nTargetTimespan = retarget * 600

  nActualTimespan = [nActualTimespan, nTargetTimespan/4].max
  nActualTimespan = [nActualTimespan, nTargetTimespan*4].min

  target = Bitcoin.decode_compact_bits(last[:bits]).to_i(16)
  new_target = [max_target, (target * nActualTimespan)/nTargetTimespan].min
  Bitcoin.encode_compact_bits new_target.to_s(16)
end

#prev_hashObject



128
129
130
# File 'lib/bitcoin/validation.rb', line 128

def prev_hash
  @prev_block && @prev_block.hash == block.prev_block.reverse_hth
end

#prev_txs_hashObject

Fetch all prev_txs that will be needed for validation Used for optimization in tx validators



187
188
189
190
191
192
193
# File 'lib/bitcoin/validation.rb', line 187

def prev_txs_hash
  @prev_tx_hash ||= (
    inputs = block.tx[1..-1].map {|tx| tx.in }.flatten
    txs = store.get_txs(inputs.map{|i| i.prev_out.reverse_hth })
    Hash[*txs.map {|tx| [tx.hash, tx] }.flatten]
  )
end

#scrypt_bitsObject

check that block hash matches claimed bits using Scrypt hash



83
84
85
86
87
# File 'lib/bitcoin/validation.rb', line 83

def scrypt_bits
  actual = block.recalc_block_scrypt_hash.to_i(16)
  expected = Bitcoin.decode_compact_bits(block.bits).to_i(16)
  actual <= expected || [actual, expected]
end

#spent_outs_txinsObject

Fetch all prev_outs that already have a next_in, i.e. are already spent.



196
197
198
199
200
201
202
# File 'lib/bitcoin/validation.rb', line 196

def spent_outs_txins
  @spent_outs_txins ||= (
    next_ins = store.get_txins_for_txouts(block.tx[1..-1].map(&:in).flatten.map.with_index {|txin, idx| [txin.prev_out.reverse_hth, txin.prev_out_index] })
    # Only returns next_ins that are in blocks in the main chain
    next_ins.select {|i| store.get_block_id_for_tx_id(i.tx_id) }
  )
end

#transactions_contextObject

Run all context checks on transactions



169
170
171
172
173
174
175
176
177
178
# File 'lib/bitcoin/validation.rb', line 169

def transactions_context
  tx_validators.all?{|v|
    begin
      v.validate(rules: [:context], raise_errors: true)
    rescue ValidationError
      store.log.info { $!.message }
      return false
    end
  }
end

#transactions_syntaxObject

Run all syntax checks on transactions



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/bitcoin/validation.rb', line 154

def transactions_syntax
  # check if there are no double spends within this block
  return false if block.tx.map(&:in).flatten.map {|i| [i.prev_out, i.prev_out_index] }.uniq! != nil

  tx_validators.all?{|v|
    begin
      v.validate(rules: [:syntax], raise_errors: true)
    rescue ValidationError
      store.log.info { $!.message }
      return false
    end
  }
end

#tx_listObject

check that block has at least one tx (the coinbase)



71
72
73
# File 'lib/bitcoin/validation.rb', line 71

def tx_list
  block.tx.any? || block.tx.size
end

#tx_validatorsObject

Get validators for all tx objects in the current block



181
182
183
# File 'lib/bitcoin/validation.rb', line 181

def tx_validators
  @tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block, self)}
end

#validate(opts = {}) ⇒ Object

validate block rules. opts are:

rules

which rulesets to validate (default: [:syntax, :context])

raise_errors

whether to raise ValidationError on failure (default: false)



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/bitcoin/validation.rb', line 41

def validate(opts = {})
  return true  if KNOWN_EXCEPTIONS.include?(block.hash)
  opts[:rules] ||= [:syntax, :context]
  opts[:rules].each do |name|
    store.log.debug { "validating block #{name} #{block.hash} (#{block.to_payload.bytesize} bytes)" }
    RULES[name].each.with_index do |rule, i|
      unless (res = send(rule)) && res == true
        raise ValidationError, "block error: #{name} check #{i} - #{rule} failed"  if opts[:raise_errors]
        @error = [rule, res]
        return false
      end
    end
  end
  true
end