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_block
for 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
- #transactions_context ⇒ Object
-
#transactions_syntax ⇒ Object
check transactions.
-
#tx_list ⇒ Object
check that block has at least one tx (the coinbase).
- #tx_validators ⇒ Object
-
#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.
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
#block ⇒ Object
Returns the value of attribute block.
28 29 30 |
# File 'lib/bitcoin/validation.rb', line 28 def block @block end |
#error ⇒ Object
Returns the value of attribute error.
28 29 30 |
# File 'lib/bitcoin/validation.rb', line 28 def error @error end |
#prev_block ⇒ Object
Returns the value of attribute prev_block.
28 29 30 |
# File 'lib/bitcoin/validation.rb', line 28 def prev_block @prev_block end |
#store ⇒ Object
Returns the value of attribute store.
28 29 30 |
# File 'lib/bitcoin/validation.rb', line 28 def store @store end |
Instance Method Details
#bits ⇒ Object
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 |
#coinbase ⇒ Object
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_scriptsig ⇒ Object
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_value ⇒ Object
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 |
#difficulty ⇒ Object
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 |
#hash ⇒ Object
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_timestamp ⇒ Object
check that block time is not greater than max
86 87 88 89 |
# File 'lib/bitcoin/validation.rb', line 86 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
135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/bitcoin/validation.rb', line 135 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
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_required ⇒ Object
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_hash ⇒ Object
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_context ⇒ Object
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 { $!. } return false end } end |
#transactions_syntax ⇒ Object
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 { $!. } return false end } end |
#tx_list ⇒ Object
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_validators ⇒ Object
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 |