Commit a1d302794db1968de760f63146698b1afbfc8ab4

Authored by comboy
Committed by Julian Langschaedel
1 parent c238f64e5d

fetch all prev_txs needed for validation with a single query

Showing 4 changed files with 37 additions and 5 deletions Side-by-side Diff

lib/bitcoin/protocol/tx.rb
... ... @@ -224,8 +224,8 @@
224 224 # read json block from a file
225 225 def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
226 226  
227   - def validator(store, block = nil)
228   - @validator ||= Bitcoin::Validation::Tx.new(self, store, block)
  227 + def validator(store, block = nil, opts = {})
  228 + @validator ||= Bitcoin::Validation::Tx.new(self, store, block, opts)
229 229 end
230 230  
231 231 def size
lib/bitcoin/storage/sequel/sequel_store.rb
... ... @@ -273,6 +273,20 @@
273 273 wrap_tx(@db[:tx][:hash => tx_hash.htb.blob])
274 274 end
275 275  
  276 + # get array of txes with given +tx_hashes+
  277 + def get_many_tx(tx_hashes)
  278 + txs = db[:tx].filter(hash: tx_hashes.map{|h| h.htb.blob})
  279 + txs_ids = txs.map {|tx| tx[:id]}
  280 + return [] if txs_ids.empty?
  281 +
  282 + # we fetch all needed block ids, inputs and outputs to avoid doing number of queries propertional to number of transactions
  283 + block_ids = Hash[*db[:blk_tx].join(:blk, id: :blk_id).filter(tx_id: txs_ids, chain: 0).map {|b| [b[:tx_id], b[:blk_id]] }.flatten]
  284 + inputs = db[:txin].filter(:tx_id => txs_ids).order(:tx_idx).map.group_by{ |txin| txin[:tx_id] }
  285 + outputs = db[:txout].filter(:tx_id => txs_ids).order(:tx_idx).map.group_by{ |txout| txout[:tx_id] }
  286 +
  287 + txs.map {|tx| wrap_tx(tx, block_ids[tx[:id]], inputs: inputs[tx[:id]], outputs: outputs[tx[:id]]) }
  288 + end
  289 +
276 290 # get transaction by given +tx_id+
277 291 def get_tx_by_id(tx_id)
278 292 wrap_tx(@db[:tx][:id => tx_id])
lib/bitcoin/storage/storage.rb
... ... @@ -339,6 +339,12 @@
339 339 raise "Not implemented"
340 340 end
341 341  
  342 + # get more than one tx by +tx_hashes+, returns an array
  343 + # can be reimplemented by specific storage for optimization
  344 + def get_many_tx(tx_hashes)
  345 + tx_hashes.map {|h| get_tx(h)}.compact
  346 + end
  347 +
342 348 # get tx with given +tx_id+
343 349 def get_tx_by_id(tx_id)
344 350 raise "Not implemented"
lib/bitcoin/validation.rb
... ... @@ -152,9 +152,19 @@
152 152 end
153 153  
154 154 def tx_validators
155   - @tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block) }
  155 + @tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block, tx_cache: prev_txs_hash)}
156 156 end
157 157  
  158 + # Fetch all prev_txs that will be needed for validation
  159 + # Used for optimization in tx validators
  160 + def prev_txs_hash
  161 + @prev_tx_hash ||= (
  162 + inputs = block.tx.map {|tx| tx.in }.flatten
  163 + txs = store.get_many_tx(inputs.map{|i| i.prev_out.reverse_hth })
  164 + Hash[*txs.map {|tx| [tx.hash, tx] }.flatten]
  165 + )
  166 + end
  167 +
158 168 def next_bits_required
159 169 retarget = (Bitcoin.network[:retarget_interval] || Bitcoin::RETARGET_INTERVAL)
160 170 index = (prev_block.depth + 1) / retarget
161 171  
... ... @@ -221,8 +231,10 @@
221 231  
222 232 # setup new validator for given +tx+, validating context with +store+.
223 233 # also needs the +block+ to find prev_outs for chains of tx inside one block.
224   - def initialize tx, store, block = nil
  234 + # opts+ may include :tx_cache which should be hash with transactiotns including prev_txs
  235 + def initialize(tx, store, block = nil, opts = {})
225 236 @tx, @store, @block, @errors = tx, store, block, []
  237 + @tx_cache = opts[:tx_cache]
226 238 end
227 239  
228 240 # check that tx hash matches data
... ... @@ -324,7 +336,7 @@
324 336 # only returns tx that are in a block in the main chain or the current block.
325 337 def prev_txs
326 338 @prev_txs ||= tx.in.map {|i|
327   - prev_tx = store.get_tx(i.prev_out.reverse_hth)
  339 + prev_tx = @tx_cache ? @tx_cache[i.prev_out.reverse_hth] : store.get_tx(i.prev_out.reverse_hth)
328 340 next prev_tx if prev_tx && prev_tx.blk_id # blk_id is set only if it's in the main chain
329 341 @block.tx.find {|t| t.binary_hash == i.prev_out } if @block
330 342 }.compact