Commit 5d38bc911ed7c614aed0f4bb8ba58bdb3c22adcb

Authored by comboy
Committed by Julian Langschaedel
1 parent a1d302794d

optimize finding spent outputs

Showing 5 changed files with 34 additions and 13 deletions Side-by-side Diff

lib/bitcoin/storage/models.rb
... ... @@ -145,8 +145,6 @@
145 145 # get the next input that references this output
146 146 def get_next_in
147 147 @store.get_txin_for_txout(get_tx.hash, @tx_idx)
148   - rescue
149   - nil
150 148 end
151 149  
152 150 # get all addresses this txout corresponds to (if possible)
lib/bitcoin/storage/sequel/sequel_store.rb
... ... @@ -274,7 +274,7 @@
274 274 end
275 275  
276 276 # get array of txes with given +tx_hashes+
277   - def get_many_tx(tx_hashes)
  277 + def get_txs(tx_hashes)
278 278 txs = db[:tx].filter(hash: tx_hashes.map{|h| h.htb.blob})
279 279 txs_ids = txs.map {|tx| tx[:id]}
280 280 return [] if txs_ids.empty?
... ... @@ -297,6 +297,11 @@
297 297 def get_txin_for_txout(tx_hash, txout_idx)
298 298 tx_hash = tx_hash.htb_reverse.blob
299 299 wrap_txin(@db[:txin][:prev_out => tx_hash, :prev_out_index => txout_idx])
  300 + end
  301 +
  302 + # optimized version of Storage#get_txins_for_txouts
  303 + def get_txins_for_txouts(txouts)
  304 + @db[:txin].filter([:prev_out, :prev_out_index] => txouts.map{|tx_hash, tx_idx| [tx_hash.htb_reverse.blob, tx_idx]}).map{|i| wrap_txin(i)}
300 305 end
301 306  
302 307 def get_txout_by_id(txout_id)
lib/bitcoin/storage/storage.rb
... ... @@ -334,6 +334,13 @@
334 334 raise "Not implemented"
335 335 end
336 336  
  337 + # get an array of corresponding txins for provided +txouts+
  338 + # txouts = [tx_hash, tx_idx]
  339 + # can be overwritten by specific storage for opimization
  340 + def get_txins_for_txouts(txouts)
  341 + txouts.map{|tx_hash, tx_idx| get_txin_for_txout(tx_hash, tx_idx) }.compact
  342 + end
  343 +
337 344 # get tx with given +tx_hash+
338 345 def get_tx(tx_hash)
339 346 raise "Not implemented"
... ... @@ -341,7 +348,7 @@
341 348  
342 349 # get more than one tx by +tx_hashes+, returns an array
343 350 # can be reimplemented by specific storage for optimization
344   - def get_many_tx(tx_hashes)
  351 + def get_txs(tx_hashes)
345 352 tx_hashes.map {|h| get_tx(h)}.compact
346 353 end
347 354  
lib/bitcoin/storage/utxo/utxo_store.rb
... ... @@ -291,6 +291,12 @@
291 291 wrap_txout(@db[:utxo][tx_hash: txin.prev_out.reverse.hth.blob, tx_idx: txin.prev_out_index])
292 292 end
293 293  
  294 + # get the next input that references given output
  295 + # we only store unspent outputs, so it's always nil
  296 + def get_txin_for_txout(tx_hash, tx_idx)
  297 + nil
  298 + end
  299 +
294 300 # get all Models::TxOut matching given +script+
295 301 def get_txouts_for_pk_script(script)
296 302 utxos = @db[:utxo].filter(pk_script: script.blob).order(:blk_id)
lib/bitcoin/validation.rb
... ... @@ -160,7 +160,7 @@
160 160 def prev_txs_hash
161 161 @prev_tx_hash ||= (
162 162 inputs = block.tx.map {|tx| tx.in }.flatten
163   - txs = store.get_many_tx(inputs.map{|i| i.prev_out.reverse_hth })
  163 + txs = store.get_txs(inputs.map{|i| i.prev_out.reverse_hth })
164 164 Hash[*txs.map {|tx| [tx.hash, tx] }.flatten]
165 165 )
166 166 end
... ... @@ -305,14 +305,19 @@
305 305  
306 306 # check that none of the prev_outs are already spent in the main chain
307 307 def spent
308   - spent = tx.in.map.with_index {|txin, idx|
309   - next false if @block && @block.tx.include?(prev_txs[idx])
310   - next false unless next_in = prev_txs[idx].out[txin.prev_out_index].get_next_in
311   - next false unless next_tx = next_in.get_tx
312   - next false unless next_block = next_tx.get_block
313   - next_block.chain == Bitcoin::Storage::Backends::StoreBase::MAIN
314   - }
315   - spent.none? || spent.map.with_index {|s, i| s ? i : nil }
  308 + # find all spent txouts
  309 + # OPTIMIZE: these could be fetched in one query for all transactions and cached
  310 + next_ins = store.get_txins_for_txouts(tx.in.map.with_index {|txin, idx| [prev_txs[idx].hash, txin.prev_out_index] })
  311 +
  312 + # no txouts found spending these txins, we can safely return true
  313 + return true if next_ins.empty?
  314 +
  315 + # there were some txouts spending these txins, verify that they are not on the main chain
  316 + next_ins.select! {|i| i.get_tx.blk_id } # blk_id is only set for tx in the main chain
  317 + return true if next_ins.empty?
  318 +
  319 + # now we know some txouts are already spent, return tx_idxs for debugging purposes
  320 + return next_ins.map {|i| i.get_prev_out.tx_idx }
316 321 end
317 322  
318 323 # check that the total input value doesn't exceed MAX_MONEY