Class: Bitcoin::Validation::Block
- Inherits:
-
Object
- Object
- Bitcoin::Validation::Block
- 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
-
#block ⇒ Object
Returns the value of attribute block.
-
#error ⇒ Object
Returns the value of attribute error.
-
#prev_block ⇒ Object
Returns the value of attribute prev_block.
-
#store ⇒ Object
Returns the value of attribute store.
Instance Method Summary collapse
-
#bits ⇒ Object
check that block hash matches claimed bits.
-
#coinbase ⇒ Object
check that coinbase is present.
-
#coinbase_scriptsig ⇒ Object
check that coinbase scriptsig is valid.
-
#coinbase_value ⇒ Object
check that coinbase value is valid; no more than reward + fees.
-
#difficulty ⇒ Object
check that bits satisfy required difficulty.
-
#hash ⇒ Object
check that block hash matches header.
-
#initialize(block, store, prev_block = nil) ⇒ Block
constructor
setup new validator for given
block, validating context withstore, optionally passing theprev_blockfor optimization. -
#max_timestamp ⇒ Object
check that block time is not greater than max.
-
#min_timestamp ⇒ Object
check that timestamp is newer than the median of the last 11 blocks.
-
#mrkl_root ⇒ Object
check that merkle root matches transaction hashes.
- #next_bits_required ⇒ Object
- #prev_hash ⇒ Object
-
#prev_txs_hash ⇒ Object
Fetch all prev_txs that will be needed for validation Used for optimization in tx validators.
-
#scrypt_bits ⇒ Object
check that block hash matches claimed bits using Scrypt hash.
-
#spent_outs_txins ⇒ Object
Fetch all prev_outs that already have a next_in, i.e.
-
#transactions_context ⇒ Object
Run all context checks on transactions.
-
#transactions_syntax ⇒ Object
Run all syntax checks on transactions.
-
#tx_list ⇒ Object
check that block has at least one tx (the coinbase).
-
#tx_validators ⇒ Object
Get validators for all tx objects in the current block.
-
#validate(opts = {}) ⇒ Object
validate block rules.
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
#block ⇒ Object
Returns the value of attribute block.
20 21 22 |
# File 'lib/bitcoin/validation.rb', line 20 def block @block end |
#error ⇒ Object
Returns the value of attribute error.
20 21 22 |
# File 'lib/bitcoin/validation.rb', line 20 def error @error end |
#prev_block ⇒ Object
Returns the value of attribute prev_block.
20 21 22 |
# File 'lib/bitcoin/validation.rb', line 20 def prev_block @prev_block end |
#store ⇒ Object
Returns the value of attribute store.
20 21 22 |
# File 'lib/bitcoin/validation.rb', line 20 def store @store end |
Instance Method Details
#bits ⇒ Object
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 |
#coinbase ⇒ Object
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_scriptsig ⇒ Object
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_value ⇒ Object
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 |
#difficulty ⇒ Object
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 |
#hash ⇒ Object
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_timestamp ⇒ Object
check that block time is not greater than max
90 91 92 93 |
# File 'lib/bitcoin/validation.rb', line 90 def time, max = block.time, Time.now.to_i + 2*60*60 time < max || [time, max] end |
#min_timestamp ⇒ Object
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 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_root ⇒ Object
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_required ⇒ Object
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_hash ⇒ Object
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_hash ⇒ Object
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_bits ⇒ Object
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_txins ⇒ Object
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_context ⇒ Object
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 { $!. } return false end } end |
#transactions_syntax ⇒ Object
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 { $!. } return false end } end |
#tx_list ⇒ Object
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_validators ⇒ Object
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 |