Class: Bitcoin::Validation::Tx
- Inherits:
-
Object
- Object
- Bitcoin::Validation::Tx
- 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, :not_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
-
#block_validator ⇒ Object
Returns the value of attribute block_validator.
-
#error ⇒ Object
Returns the value of attribute error.
-
#store ⇒ Object
Returns the value of attribute store.
-
#tx ⇒ Object
Returns the value of attribute tx.
Instance Method Summary collapse
-
#clear_cache ⇒ Object
empty prev txs cache.
-
#hash ⇒ Object
check that tx hash matches data.
-
#initialize(tx, store, block = nil, block_validator = nil) ⇒ Tx
constructor
Setup new validator for given
tx, validating context withstore. -
#input_values ⇒ Object
check that the total input value doesn’t exceed MAX_MONEY.
-
#inputs ⇒ Object
check that none of the inputs is coinbase (coinbase tx do not get validated).
-
#lists ⇒ Object
check that tx has at least one input and one output.
-
#lock_time ⇒ Object
check that lock_time doesn’t exceed INT_MAX.
-
#max_size ⇒ Object
check that tx size doesn’t exceed MAX_BLOCK_SIZE.
-
#min_size ⇒ Object
check that min_size is at least 86 bytes (smaller tx can’t be valid / do anything useful).
-
#not_spent ⇒ Object
check that none of the prev_outs are already spent in the main chain or in the current block.
-
#output_sum ⇒ Object
check that the total output value doesn’t exceed the total input value.
-
#output_values ⇒ Object
check that total output value doesn’t exceed MAX_MONEY.
-
#prev_out ⇒ Object
check that all prev_outs exist (and are in a block in the main chain, or the current block; see #prev_txs).
-
#prev_txs ⇒ Object
collect prev_txs needed to verify the inputs of this tx.
-
#signatures ⇒ Object
check that all input signatures are valid.
-
#standard ⇒ Object
check that tx matches “standard” rules.
- #total_in ⇒ Object
- #total_out ⇒ Object
-
#validate(opts = {}) ⇒ Object
validate tx rules.
Constructor Details
#initialize(tx, store, block = nil, block_validator = nil) ⇒ Tx
Setup new validator for given tx, validating context with store. Also needs the block that includes the tx to be validated, to find prev_outs for chains of txs inside the block. Optionally accepts the validator object for the block, to optimize fetching prev_txs and checking for doublespends.
273 274 275 276 |
# File 'lib/bitcoin/validation.rb', line 273 def initialize(tx, store, block = nil, block_validator = nil) @tx, @store, @block, @errors = tx, store, block, [] @block_validator = block_validator end |
Instance Attribute Details
#block_validator ⇒ Object
Returns the value of attribute block_validator.
234 235 236 |
# File 'lib/bitcoin/validation.rb', line 234 def block_validator @block_validator end |
#error ⇒ Object
Returns the value of attribute error.
234 235 236 |
# File 'lib/bitcoin/validation.rb', line 234 def error @error end |
#store ⇒ Object
Returns the value of attribute store.
234 235 236 |
# File 'lib/bitcoin/validation.rb', line 234 def store @store end |
#tx ⇒ Object
Returns the value of attribute tx.
234 235 236 |
# File 'lib/bitcoin/validation.rb', line 234 def tx @tx end |
Instance Method Details
#clear_cache ⇒ Object
empty prev txs cache
374 375 376 377 378 |
# File 'lib/bitcoin/validation.rb', line 374 def clear_cache @prev_txs = nil @total_in = nil @total_out = nil end |
#hash ⇒ Object
check that tx hash matches data
279 280 281 282 |
# File 'lib/bitcoin/validation.rb', line 279 def hash generated_hash = tx.generate_hash(tx.to_payload) tx.hash == generated_hash || [tx.hash, generated_hash] end |
#input_values ⇒ Object
check that the total input value doesn’t exceed MAX_MONEY
364 365 366 |
# File 'lib/bitcoin/validation.rb', line 364 def input_values total_in < Bitcoin::network[:max_money] || [total_in, Bitcoin::network[:max_money]] end |
#inputs ⇒ Object
check that none of the inputs is coinbase (coinbase tx do not get validated)
302 303 304 |
# File 'lib/bitcoin/validation.rb', line 302 def inputs tx.inputs.none?(&:coinbase?) || [tx.inputs.index(tx.inputs.find(&:coinbase?))] end |
#lists ⇒ Object
check that tx has at least one input and one output
285 286 287 |
# File 'lib/bitcoin/validation.rb', line 285 def lists (tx.in.any? && tx.out.any?) || [tx.in.size, tx.out.size] end |
#lock_time ⇒ Object
check that lock_time doesn’t exceed INT_MAX
307 308 309 |
# File 'lib/bitcoin/validation.rb', line 307 def lock_time tx.lock_time <= Bitcoin::UINT32_MAX || [tx.lock_time, Bitcoin::UINT32_MAX] end |
#max_size ⇒ Object
check that tx size doesn’t exceed MAX_BLOCK_SIZE.
290 291 292 |
# File 'lib/bitcoin/validation.rb', line 290 def max_size tx.to_payload.bytesize <= Bitcoin::MAX_BLOCK_SIZE || [tx.to_payload.bytesize, Bitcoin::MAX_BLOCK_SIZE] end |
#min_size ⇒ Object
check that min_size is at least 86 bytes (smaller tx can’t be valid / do anything useful)
313 314 315 |
# File 'lib/bitcoin/validation.rb', line 313 def min_size tx.to_payload.bytesize >= 86 || [tx.to_payload.bytesize, 86] end |
#not_spent ⇒ Object
check that none of the prev_outs are already spent in the main chain or in the current block
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/bitcoin/validation.rb', line 345 def not_spent # if we received cached spents, use it return block_validator.spent_outs_txins.empty? if block_validator # find all spent txouts next_ins = store.get_txins_for_txouts(tx.in.map.with_index {|txin, idx| [txin.prev_out.reverse_hth, txin.prev_out_index] }) # no txouts found spending these txins, we can safely return true return true if next_ins.empty? # there were some txouts spending these txins, verify that they are not on the main chain next_ins.select! {|i| i.get_tx.blk_id } # blk_id is only set for tx in the main chain return true if next_ins.empty? # now we know some txouts are already spent, return tx_idxs for debugging purposes return next_ins.map {|i| i.get_prev_out.tx_idx } end |
#output_sum ⇒ Object
check that the total output value doesn’t exceed the total input value
369 370 371 |
# File 'lib/bitcoin/validation.rb', line 369 def output_sum total_in >= total_out || [total_out, total_in] end |
#output_values ⇒ Object
check that total output value doesn’t exceed MAX_MONEY.
295 296 297 298 |
# File 'lib/bitcoin/validation.rb', line 295 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_out ⇒ Object
check that all prev_outs exist (and are in a block in the main chain, or the current block; see #prev_txs)
327 328 329 330 331 332 333 334 |
# File 'lib/bitcoin/validation.rb', line 327 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_txs ⇒ Object
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.
382 383 384 385 386 387 388 |
# File 'lib/bitcoin/validation.rb', line 382 def prev_txs @prev_txs ||= tx.in.map {|i| prev_tx = block_validator ? block_validator.prev_txs_hash[i.prev_out.reverse_hth] : store.get_tx(i.prev_out.reverse_hth) next prev_tx if prev_tx && prev_tx.blk_id # blk_id is set only if it's in the main chain @block.tx.find {|t| t.binary_hash == i.prev_out } if @block }.compact end |
#signatures ⇒ Object
check that all input signatures are valid
339 340 341 342 |
# File 'lib/bitcoin/validation.rb', line 339 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 |
#standard ⇒ Object
check that tx matches “standard” rules. this is currently disabled since not all miners enforce it.
319 320 321 322 323 |
# File 'lib/bitcoin/validation.rb', line 319 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_in ⇒ Object
391 392 393 |
# File 'lib/bitcoin/validation.rb', line 391 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_out ⇒ Object
395 396 397 |
# File 'lib/bitcoin/validation.rb', line 395 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)
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/bitcoin/validation.rb', line 244 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 |