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.



62
63
64
65
# File 'lib/bitcoin/validation.rb', line 62

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.



28
29
30
# File 'lib/bitcoin/validation.rb', line 28

def block
  @block
end

#errorObject

Returns the value of attribute error.



28
29
30
# File 'lib/bitcoin/validation.rb', line 28

def error
  @error
end

#prev_blockObject

Returns the value of attribute prev_block.



28
29
30
# File 'lib/bitcoin/validation.rb', line 28

def prev_block
  @prev_block
end

#storeObject

Returns the value of attribute store.



28
29
30
# File 'lib/bitcoin/validation.rb', line 28

def store
  @store
end

Instance Method Details

#bitsObject

check that block hash matches claimed bits



79
80
81
82
83
# File 'lib/bitcoin/validation.rb', line 79

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



92
93
94
95
# File 'lib/bitcoin/validation.rb', line 92

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



98
99
100
101
# File 'lib/bitcoin/validation.rb', line 98

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



104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/bitcoin/validation.rb', line 104

def coinbase_value
  reward = ((50.0 / (2 ** (store.get_depth / 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



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

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

#hashObject

check that block hash matches header



68
69
70
71
# File 'lib/bitcoin/validation.rb', line 68

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



86
87
88
89
# File 'lib/bitcoin/validation.rb', line 86

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



135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/bitcoin/validation.rb', line 135

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



119
120
121
122
# File 'lib/bitcoin/validation.rb', line 119

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



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/bitcoin/validation.rb', line 176

def next_bits_required
  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



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

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

#transactions_contextObject



161
162
163
164
165
166
167
168
169
170
# File 'lib/bitcoin/validation.rb', line 161

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

check transactions



150
151
152
153
154
155
156
157
158
159
# File 'lib/bitcoin/validation.rb', line 150

def transactions_syntax
  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)



74
75
76
# File 'lib/bitcoin/validation.rb', line 74

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

#tx_validatorsObject



172
173
174
# File 'lib/bitcoin/validation.rb', line 172

def tx_validators
  @tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block) }
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)



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/bitcoin/validation.rb', line 44

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