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, :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
-
#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) ⇒ 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).
-
#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.
-
#spent ⇒ Object
check that none of the prev_outs are already spent in the main chain.
-
#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) ⇒ 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
#error ⇒ Object
Returns the value of attribute error.
205 206 207 |
# File 'lib/bitcoin/validation.rb', line 205 def error @error end |
#store ⇒ Object
Returns the value of attribute store.
205 206 207 |
# File 'lib/bitcoin/validation.rb', line 205 def store @store end |
#tx ⇒ Object
Returns the value of attribute tx.
205 206 207 |
# File 'lib/bitcoin/validation.rb', line 205 def tx @tx end |
Instance Method Details
#clear_cache ⇒ Object
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 |
#hash ⇒ Object
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_values ⇒ Object
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 |
#inputs ⇒ Object
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 |
#lists ⇒ Object
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_time ⇒ Object
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_size ⇒ Object
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_size ⇒ Object
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_sum ⇒ Object
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_values ⇒ Object
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_out ⇒ Object
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_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.
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 |
#signatures ⇒ Object
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 |
#spent ⇒ Object
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 |
#standard ⇒ Object
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_in ⇒ Object
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_out ⇒ Object
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 |