Class: Bitcoin::Validation::Tx

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

Constant Summary collapse

RULES =
{
  syntax: [:hash, :lists, :max_size, :output_values, :inputs, :lock_time, :standard],
  context: [:prev_out, :signatures, :spent, :input_values, :output_sum]
}
KNOWN_EXCEPTIONS =
[
  # p2sh with invalid inner script, accepted by old miner before 4-2012 switchover
  "6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192",
  # p2sh with invalid inner script, accepted by old miner before 4-2012 switchover (testnet)
  "b3c19d78b4953b694717a47d9852f8ea1ccd4cf93a45ba2e43a0f97d7cdb2655"
]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tx, store, block = nil) ⇒ Tx

setup new validator for given tx, validating context with store. also needs the block to find prev_outs for chains of tx inside one block.



241
242
243
# File 'lib/bitcoin/validation.rb', line 241

def initialize tx, store, block = nil
  @tx, @store, @block, @errors = tx, store, block, []
end

Instance Attribute Details

#errorObject

Returns the value of attribute error.



205
206
207
# File 'lib/bitcoin/validation.rb', line 205

def error
  @error
end

#storeObject

Returns the value of attribute store.



205
206
207
# File 'lib/bitcoin/validation.rb', line 205

def store
  @store
end

#txObject

Returns the value of attribute tx.



205
206
207
# File 'lib/bitcoin/validation.rb', line 205

def tx
  @tx
end

Instance Method Details

#clear_cacheObject

empty prev txs cache



334
335
336
337
338
# File 'lib/bitcoin/validation.rb', line 334

def clear_cache
  @prev_txs = nil
  @total_in = nil
  @total_out = nil
end

#hashObject

check that tx hash matches data



246
247
248
249
# File 'lib/bitcoin/validation.rb', line 246

def hash
  generated_hash = tx.generate_hash(tx.to_payload)
  tx.hash == generated_hash || [tx.hash, generated_hash]
end

#input_valuesObject

check that the total input value doesn’t exceed MAX_MONEY



324
325
326
# File 'lib/bitcoin/validation.rb', line 324

def input_values
  total_in < Bitcoin::network[:max_money] || [total_in, Bitcoin::network[:max_money]]
end

#inputsObject

check that none of the inputs is coinbase (coinbase tx do not get validated)



269
270
271
# File 'lib/bitcoin/validation.rb', line 269

def inputs
  tx.inputs.none?(&:coinbase?) || [tx.inputs.index(tx.inputs.find(&:coinbase?))]
end

#listsObject

check that tx has at least one input and one output



252
253
254
# File 'lib/bitcoin/validation.rb', line 252

def lists
  (tx.in.any? && tx.out.any?) || [tx.in.size, tx.out.size]
end

#lock_timeObject

check that lock_time doesn’t exceed INT_MAX



274
275
276
# File 'lib/bitcoin/validation.rb', line 274

def lock_time
  tx.lock_time <= INT_MAX || [tx.lock_time, INT_MAX]
end

#max_sizeObject

check that tx size doesn’t exceed MAX_BLOCK_SIZE.



257
258
259
# File 'lib/bitcoin/validation.rb', line 257

def max_size
  tx.to_payload.bytesize <= MAX_BLOCK_SIZE || [tx.to_payload.bytesize, MAX_BLOCK_SIZE]
end

#min_sizeObject

check that min_size is at least 86 bytes (smaller tx can’t be valid / do anything useful)



280
281
282
# File 'lib/bitcoin/validation.rb', line 280

def min_size
  tx.to_payload.bytesize >= 86 || [tx.to_payload.bytesize, 86]
end

#output_sumObject

check that the total output value doesn’t exceed the total input value



329
330
331
# File 'lib/bitcoin/validation.rb', line 329

def output_sum
  total_in >= total_out || [total_out, total_in]
end

#output_valuesObject

check that total output value doesn’t exceed MAX_MONEY.



262
263
264
265
# File 'lib/bitcoin/validation.rb', line 262

def output_values
  total = tx.out.inject(0) {|e, out| e + out.value }
  total <= Bitcoin::network[:max_money] || [total, Bitcoin::network[:max_money]]
end

#prev_outObject

check that all prev_outs exist (and are in a block in the main chain, or the current block; see #prev_txs)



294
295
296
297
298
299
300
301
# File 'lib/bitcoin/validation.rb', line 294

def prev_out
  missing = tx.in.reject.with_index {|txin, idx|
    prev_txs[idx].out[txin.prev_out_index] rescue false }
  return true  if prev_txs.size == tx.in.size && missing.empty?

  missing.each {|i| store.log.warn { "prev out #{i.prev_out.reverse_hth}:#{i.prev_out_index} missing" } }
  missing.map {|i| [i.prev_out.reverse_hth, i.prev_out_index] }
end

#prev_txsObject

collect prev_txs needed to verify the inputs of this tx. only returns tx that are in a block in the main chain or the current block.



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/bitcoin/validation.rb', line 342

def prev_txs
  @prev_txs ||= tx.in.map {|i|
    prev_tx = store.get_tx(i.prev_out.reverse_hth)
    next prev_tx  if store.class.name =~ /UtxoStore/ && prev_tx
    next nil  if !prev_tx && !@block

    if store.class.name =~ /SequelStore/
      block = store.db[:blk][id: prev_tx.blk_id]  if prev_tx
      next prev_tx  if block && block[:chain] == 0
    else
      next prev_tx  if prev_tx && prev_tx.get_block && prev_tx.get_block.chain == 0
    end
    next  nil if !@block
    @block.tx.find {|t| t.binary_hash == i.prev_out }
  }.compact
end

#signaturesObject

check that all input signatures are valid



306
307
308
309
# File 'lib/bitcoin/validation.rb', line 306

def signatures
  sigs = tx.in.map.with_index {|txin, idx| tx.verify_input_signature(idx, prev_txs[idx], (@block ? @block.time : 0)) }
  sigs.all? || sigs.map.with_index {|s, i| s ? nil : i }.compact
end

#spentObject

check that none of the prev_outs are already spent in the main chain



312
313
314
315
316
317
318
319
320
321
# File 'lib/bitcoin/validation.rb', line 312

def spent
  spent = tx.in.map.with_index {|txin, idx|
    next false  if @block && @block.tx.include?(prev_txs[idx])
    next false  unless next_in = prev_txs[idx].out[txin.prev_out_index].get_next_in
    next false  unless next_tx = next_in.get_tx
    next false  unless next_block = next_tx.get_block
    next_block.chain == Bitcoin::Storage::Backends::StoreBase::MAIN
  }
  spent.none? || spent.map.with_index {|s, i| s ? i : nil }
end

#standardObject

check that tx matches “standard” rules. this is currently disabled since not all miners enforce it.



286
287
288
289
290
# File 'lib/bitcoin/validation.rb', line 286

def standard
  return true  # not enforced by all miners
  return false  unless min_size
  tx.out.all? {|o| Bitcoin::Script.new(o.pk_script).is_standard? }
end

#total_inObject



360
361
362
# File 'lib/bitcoin/validation.rb', line 360

def total_in
  @total_in ||= tx.in.each_with_index.inject(0){|acc,(input,idx)| acc + prev_txs[idx].out[input.prev_out_index].value }
end

#total_outObject



364
365
366
# File 'lib/bitcoin/validation.rb', line 364

def total_out
  @total_out ||= tx.out.inject(0){|acc,output| acc + output.value }
end

#validate(opts = {}) ⇒ Object

validate tx rules. opts are:

rules

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

raise_errors

whether to raise ValidationError on failure (default: false)



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/bitcoin/validation.rb', line 215

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