Class: Bitcoin::Storage::Backends::SequelStore

Inherits:
SequelStoreBase show all
Defined in:
lib/bitcoin/storage/sequel/sequel_store.rb

Overview

Storage backend using Sequel to connect to arbitrary SQL databases. Inherits from StoreBase and implements its interface.

Constant Summary collapse

DEFAULT_CONFIG =
{
  # TODO
  mode: :full,

  # cache head block. only the instance that is updating the head should do this.
  cache_head: false,

  # store an index of tx.nhash values
  index_nhash: false
}

Constants inherited from SequelStoreBase

Bitcoin::Storage::Backends::SequelStoreBase::SEQUEL_ADAPTERS

Constants inherited from StoreBase

Bitcoin::Storage::Backends::StoreBase::ADDRESS_TYPES, Bitcoin::Storage::Backends::StoreBase::MAIN, Bitcoin::Storage::Backends::StoreBase::ORPHAN, Bitcoin::Storage::Backends::StoreBase::SCRIPT_TYPES, Bitcoin::Storage::Backends::StoreBase::SIDE

Instance Attribute Summary collapse

Attributes inherited from StoreBase

#config, #log

Instance Method Summary collapse

Methods inherited from SequelStoreBase

#check_metadata, #init_store_connection, #migrate, #sqlite_pragmas

Methods inherited from StoreBase

#add_watched_address, #backend_name, #get_balance, #get_locator, #get_txouts_for_address, #get_unspent_txouts_for_address, #import, #in_sync?, #init_store_connection, #new_block, #new_tx, #parse_script, #push_notification, #rescan, #store_block, #subscribe, #update_block

Constructor Details

#initialize(config, *args) ⇒ SequelStore

create sequel store with given config



27
28
29
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 27

def initialize config, *args
  super config, *args
end

Instance Attribute Details

#dbObject

sequel database connection



13
14
15
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 13

def db
  @db
end

Instance Method Details

#check_consistency(count = 1000) ⇒ Object

check data consistency of the top count blocks. validates that

  • the block hash computed from the stored data is the same

  • the prev_hash is the same as the previous blocks’ hash

  • the merkle root computed from all transactions is correct



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 471

def check_consistency count = 1000
  return  if get_depth < 1 || count <= 0
  depth = get_depth
  count = depth - 1  if count == -1
  count = depth - 1  if count >= depth
  log.info { "Checking consistency of last #{count} blocks..." }
  prev_blk = get_block_by_depth(depth - count - 1)
  (depth - count).upto(depth).each do |depth|
    blk = get_block_by_depth(depth)
    raise "Block hash #{blk.depth} invalid!"  unless blk.hash == blk.recalc_block_hash
    raise "Prev hash #{blk.depth} invalid!"  unless blk.prev_block.reverse.hth == prev_blk.hash
    raise "Merkle root #{blk.depth} invalid!"  unless blk.verify_mrkl_root
    print "#{blk.hash} #{blk.depth} OK\r"
    prev_blk = blk
  end
  log.info { "Last #{count} blocks are consistent." }
end

#connectObject

connect to database



32
33
34
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 32

def connect
  super
end

#delete_tx(hash) ⇒ Object

delete transaction TODO: also delete blk_tx mapping



234
235
236
237
238
239
240
241
242
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 234

def delete_tx(hash)
  log.debug { "Deleting tx #{hash} since all its outputs are spent" }
  @db.transaction do
    tx = get_tx(hash)
    tx.in.each {|i| @db[:txin].where(:id => i.id).delete }
    tx.out.each {|o| @db[:txout].where(:id => o.id).delete }
    @db[:tx].where(:id => tx.id).delete
  end
end

#get_block(blk_hash) ⇒ Object

get block for given blk_hash



275
276
277
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 275

def get_block(blk_hash)
  wrap_block(@db[:blk][:hash => blk_hash.htb.blob])
end

#get_block_by_depth(depth) ⇒ Object

get block by given depth



280
281
282
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 280

def get_block_by_depth(depth)
  wrap_block(@db[:blk][:depth => depth, :chain => MAIN])
end

#get_block_by_id(block_id) ⇒ Object

get block by given id



299
300
301
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 299

def get_block_by_id(block_id)
  wrap_block(@db[:blk][:id => block_id])
end

#get_block_by_prev_hash(prev_hash) ⇒ Object

get block by given prev_hash



285
286
287
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 285

def get_block_by_prev_hash(prev_hash)
  wrap_block(@db[:blk][:prev_hash => prev_hash.htb.blob, :chain => MAIN])
end

#get_block_by_tx(tx_hash) ⇒ Object

get block by given tx_hash



290
291
292
293
294
295
296
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 290

def get_block_by_tx(tx_hash)
  tx = @db[:tx][:hash => tx_hash.htb.blob]
  return nil  unless tx
  parent = @db[:blk_tx][:tx_id => tx[:id]]
  return nil  unless parent
  wrap_block(@db[:blk][:id => parent[:blk_id]])
end

#get_block_id_for_tx_id(tx_id) ⇒ Object

get block id in the main chain by given tx_id



304
305
306
307
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 304

def get_block_id_for_tx_id(tx_id)
  @db[:blk_tx].join(:blk, id: :blk_id)
    .where(tx_id: tx_id, chain: MAIN).first[:blk_id] rescue nil
end

#get_depthObject

get depth of MAIN chain



266
267
268
269
270
271
272
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 266

def get_depth
  depth = (@config[:cache_head] && @head) ? @head.depth :
    @depth = @db[:blk].filter(:chain => MAIN).order(:depth).last[:depth] rescue nil

  return -1  unless depth
  depth
end

#get_headObject

get head block (highest block from the MAIN chain)



255
256
257
258
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 255

def get_head
  (@config[:cache_head] && @head) ? @head :
    @head = wrap_block(@db[:blk].filter(:chain => MAIN).order(:depth).last)
end

#get_head_hashObject



260
261
262
263
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 260

def get_head_hash
  (@config[:cache_head] && @head) ? @head.hash :
    @head = @db[:blk].filter(:chain => MAIN).order(:depth).last[:hash].hth
end

#get_idx_from_tx_hash(tx_hash) ⇒ Object

Grab the position of a tx in a given block



385
386
387
388
389
390
391
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 385

def get_idx_from_tx_hash(tx_hash)
  tx = @db[:tx][:hash => tx_hash.htb.blob]
  return nil  unless tx
  parent = @db[:blk_tx][:tx_id => tx[:id]]
  return nil  unless parent
  return parent[:idx]
end

#get_received(address) ⇒ Object

get total received of address address



490
491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 490

def get_received(address)
  return 0 unless Bitcoin.valid_address?(address)

  txouts = get_txouts_for_address(address)
  return 0 unless txouts.any?

  txouts.inject(0){ |m, out| m + out.value }

  # total = 0
  # txouts.each do |txout|
  #   tx = txout.get_tx
  #   total += txout.value
  # end
end

#get_tx(tx_hash) ⇒ Object

get transaction for given tx_hash



310
311
312
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 310

def get_tx(tx_hash)
  wrap_tx(@db[:tx][:hash => tx_hash.htb.blob])
end

#get_tx_by_id(tx_id) ⇒ Object

get transaction by given tx_id



329
330
331
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 329

def get_tx_by_id(tx_id)
  wrap_tx(@db[:tx][:id => tx_id])
end

#get_txin_for_txout(tx_hash, txout_idx) ⇒ Object

get corresponding Models::TxIn for the txout in transaction tx_hash with index txout_idx



335
336
337
338
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 335

def get_txin_for_txout(tx_hash, txout_idx)
  tx_hash = tx_hash.htb_reverse.blob
  wrap_txin(@db[:txin][:prev_out => tx_hash, :prev_out_index => txout_idx])
end

#get_txins_for_txouts(txouts) ⇒ Object

optimized version of Storage#get_txins_for_txouts



341
342
343
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 341

def get_txins_for_txouts(txouts)
  @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)}
end

#get_txout_by_id(txout_id) ⇒ Object



345
346
347
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 345

def get_txout_by_id(txout_id)
  wrap_txout(@db[:txout][:id => txout_id])
end

#get_txout_for_txin(txin) ⇒ Object

get corresponding Models::TxOut for txin



350
351
352
353
354
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 350

def get_txout_for_txin(txin)
  tx = @db[:tx][:hash => txin.prev_out.reverse.blob]
  return nil  unless tx
  wrap_txout(@db[:txout][:tx_idx => txin.prev_out_index, :tx_id => tx[:id]])
end

#get_txouts_for_hash160(hash160, type = :hash160, unconfirmed = false) ⇒ Object

get all Models::TxOut matching given hash160



363
364
365
366
367
368
369
370
371
372
373
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 363

def get_txouts_for_hash160(hash160, type = :hash160, unconfirmed = false)
  addr = @db[:addr][hash160: hash160, type: ADDRESS_TYPES.index(type)]
  return []  unless addr
  txouts = @db[:addr_txout].where(addr_id: addr[:id])
    .map{|t| @db[:txout][id: t[:txout_id]] }
    .map{|o| wrap_txout(o) }
  unless unconfirmed
    txouts.select!{|o| @db[:blk][id: o.get_tx.blk_id][:chain] == MAIN rescue false }
  end
  txouts
end

#get_txouts_for_name_hash(hash) ⇒ Object



375
376
377
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 375

def get_txouts_for_name_hash(hash)
  @db[:names].filter(hash: hash).map {|n| get_txout_by_id(n[:txout_id]) }
end

#get_txouts_for_pk_script(script) ⇒ Object

get all Models::TxOut matching given script



357
358
359
360
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 357

def get_txouts_for_pk_script(script)
  txouts = @db[:txout].filter(:pk_script => script.blob).order(:id)
  txouts.map{|txout| wrap_txout(txout)}
end

#get_txs(tx_hashes) ⇒ Object

get array of txes with given tx_hashes



315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 315

def get_txs(tx_hashes)
  txs = db[:tx].filter(hash: tx_hashes.map{|h| h.htb.blob})
  txs_ids = txs.map {|tx| tx[:id]}
  return [] if txs_ids.empty?

  # we fetch all needed block ids, inputs and outputs to avoid doing number of queries propertional to number of transactions
  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]
  inputs = db[:txin].filter(:tx_id => txs_ids).order(:tx_idx).map.group_by{ |txin| txin[:tx_id] }
  outputs = db[:txout].filter(:tx_id => txs_ids).order(:tx_idx).map.group_by{ |txout| txout[:tx_id] }

  txs.map {|tx| wrap_tx(tx, block_ids[tx[:id]], inputs: inputs[tx[:id]], outputs: outputs[tx[:id]]) }
end

#get_unconfirmed_txObject

get all unconfirmed Models::TxOut



380
381
382
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 380

def get_unconfirmed_tx
  @db[:unconfirmed].map{|t| wrap_tx(t)}
end

#has_block(blk_hash) ⇒ Object

check if block blk_hash exists in the main chain



245
246
247
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 245

def has_block(blk_hash)
  !!@db[:blk].where(:hash => blk_hash.htb.blob, :chain => 0).get(1)
end

#has_tx(tx_hash) ⇒ Object

check if transaction tx_hash exists



250
251
252
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 250

def has_tx(tx_hash)
  !!@db[:tx].where(:hash => tx_hash.htb.blob).get(1)
end

#persist_addrs(addrs) ⇒ Object

bulk-store addresses and txout mappings



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 128

def persist_addrs addrs
  addr_txouts, new_addrs = [], []

  # find addresses that are already there
  existing_addr = {}
  addrs.each do |i, addr|
    hash160 = Bitcoin.hash160_from_address(addr)
    type = Bitcoin.address_type(addr)
    if existing = @db[:addr][hash160: hash160, type: ADDRESS_TYPES.index(type)]
      existing_addr[[hash160, type]] = existing[:id]
    end
  end

  # iterate over all txouts, grouped by hash160
  addrs.group_by {|_, a| a }.each do |addr, txouts|
    hash160 = Bitcoin.hash160_from_address(addr)
    type = Bitcoin.address_type(addr)

    if existing_id = existing_addr[[hash160, type]]
      # link each txout to existing address
      txouts.each {|id, _| addr_txouts << [existing_id, id] }
    else
      # collect new address/txout mapping
      new_addrs << [[hash160, type], txouts.map {|id, _| id }]
    end
  end

  # insert all new addresses
  new_addr_ids = fast_insert(:addr, new_addrs.map {|hash160_and_type, txout_id|
      hash160, type = *hash160_and_type
      { hash160: hash160, type: ADDRESS_TYPES.index(type) }
    }, return_ids: true)


  # link each new txout to the new addresses
  new_addr_ids.each.with_index do |addr_id, idx|
    new_addrs[idx][1].each do |txout_id|
      addr_txouts << [addr_id, txout_id]
    end
  end

  # insert addr/txout links
  fast_insert(:addr_txout, addr_txouts.map {|addr_id, txout_id| { addr_id: addr_id, txout_id: txout_id }})
end

#persist_block(blk, chain, depth, prev_work = 0) ⇒ Object

persist given block blk to storage.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 43

def persist_block blk, chain, depth, prev_work = 0
  @db.transaction do
    attrs = {
      :hash => blk.hash.htb.blob,
      :depth => depth,
      :chain => chain,
      :version => blk.ver,
      :prev_hash => blk.prev_block.reverse.blob,
      :mrkl_root => blk.mrkl_root.reverse.blob,
      :time => blk.time,
      :bits => blk.bits,
      :nonce => blk.nonce,
      :blk_size => blk.to_payload.bytesize,
      :work => (prev_work + blk.block_work).to_s
    }
    attrs[:aux_pow] = blk.aux_pow.to_payload.blob  if blk.aux_pow
    existing = @db[:blk].filter(:hash => blk.hash.htb.blob)
    if existing.any?
      existing.update attrs
      block_id = existing.first[:id]
    else
      block_id = @db[:blk].insert(attrs)
      blk_tx, new_tx, addrs, names = [], [], [], []

      # store tx
      existing_tx = Hash[*@db[:tx].filter(hash: blk.tx.map {|tx| tx.hash.htb.blob }).map { |tx| [tx[:hash].hth, tx[:id]] }.flatten]
      blk.tx.each.with_index do |tx, idx|
        existing = existing_tx[tx.hash]
        existing ? blk_tx[idx] = existing : new_tx << [tx, idx]
      end

      new_tx_ids = fast_insert(:tx, new_tx.map {|tx, _| tx_data(tx) }, return_ids: true)
      new_tx_ids.each.with_index {|tx_id, idx| blk_tx[new_tx[idx][1]] = tx_id }

      fast_insert(:blk_tx, blk_tx.map.with_index {|id, idx| { blk_id: block_id, tx_id: id, idx: idx } })

      # store txins
      fast_insert(:txin, new_tx.map.with_index {|tx, tx_idx|
        tx, _ = *tx
        tx.in.map.with_index {|txin, txin_idx|
          p2sh_type = nil
          if @config[:index_p2sh_type] && !txin.coinbase? && (script = tx.scripts[txin_idx]) && script.is_p2sh?
            p2sh_type = Bitcoin::Script.new(script.inner_p2sh_script).type
          end
          txin_data(new_tx_ids[tx_idx], txin, txin_idx, p2sh_type) } }.flatten)

      # store txouts
      txout_i = 0
      txout_ids = fast_insert(:txout, new_tx.map.with_index {|tx, tx_idx|
        tx, _ = *tx
        tx.out.map.with_index {|txout, txout_idx|
          script_type, a, n = *parse_script(txout, txout_i, tx.hash, txout_idx)
          addrs += a; names += n; txout_i += 1
          txout_data(new_tx_ids[tx_idx], txout, txout_idx, script_type) } }.flatten, return_ids: true)

      # store addrs
      persist_addrs addrs.map {|i, addr| [txout_ids[i], addr]}
      names.each {|i, script| store_name(script, txout_ids[i]) }
    end
    @head = wrap_block(attrs.merge(id: block_id))  if chain == MAIN
    @db[:blk].where(:prev_hash => blk.hash.htb.blob, :chain => ORPHAN).each do |b|
      log.debug { "connecting orphan #{b[:hash].hth}" }
      begin
        store_block(get_block(b[:hash].hth))
      rescue SystemStackError
        EM.defer { store_block(get_block(b[:hash].hth)) }  if EM.reactor_running?
      end
    end
    return depth, chain
  end
end

#reorg(new_side, new_main) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 115

def reorg new_side, new_main
  @db.transaction do
    @db[:blk].where(hash: new_side.map {|h| h.htb.blob }).update(chain: SIDE)
    new_main.each do |block_hash|
      unless @config[:skip_validation]
        get_block(block_hash).validator(self).validate(raise_errors: true)
      end
      @db[:blk].where(hash: block_hash.htb.blob).update(chain: MAIN)
    end
  end
end

#resetObject

reset database; delete all data



37
38
39
40
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 37

def reset
  [:blk, :blk_tx, :tx, :txin, :txout, :addr, :addr_txout, :names].each {|table| @db[table].delete }
  @head = nil
end

#store_tx(tx, validate = true) ⇒ Object

store transaction tx



185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 185

def store_tx(tx, validate = true)
  @log.debug { "Storing tx #{tx.hash} (#{tx.to_payload.bytesize} bytes)" }
  tx.validator(self).validate(raise_errors: true)  if validate
  @db.transaction do
    transaction = @db[:tx][:hash => tx.hash.htb.blob]
    return transaction[:id]  if transaction
    tx_id = @db[:tx].insert(tx_data(tx))
    tx.in.each_with_index {|i, idx| store_txin(tx_id, i, idx)}
    tx.out.each_with_index {|o, idx| store_txout(tx_id, o, idx, tx.hash)}
    tx_id
  end
end

#store_txin(tx_id, txin, idx, p2sh_type = nil) ⇒ Object

store input txin



212
213
214
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 212

def store_txin(tx_id, txin, idx, p2sh_type = nil)
  @db[:txin].insert(txin_data(tx_id, txin, idx, p2sh_type))
end

#store_txout(tx_id, txout, idx, tx_hash = "") ⇒ Object

store output txout



224
225
226
227
228
229
230
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 224

def store_txout(tx_id, txout, idx, tx_hash = "")
  script_type, addrs, names = *parse_script(txout, idx, tx_hash, idx)
  txout_id = @db[:txout].insert(txout_data(tx_id, txout, idx, script_type))
  persist_addrs addrs.map {|i, h| [txout_id, h] }
  names.each {|i, script| store_name(script, txout_id) }
  txout_id
end

#tx_data(tx) ⇒ Object

prepare transaction data for storage



174
175
176
177
178
179
180
181
182
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 174

def tx_data tx
  data = {
    hash: tx.hash.htb.blob,
    version: tx.ver, lock_time: tx.lock_time,
    coinbase: tx.in.size == 1 && tx.in[0].coinbase?,
    tx_size: tx.payload.bytesize }
  data[:nhash] = tx.nhash.htb.blob  if @config[:index_nhash]
  data
end

#txin_data(tx_id, txin, idx, p2sh_type = nil) ⇒ Object

prepare txin data for storage



199
200
201
202
203
204
205
206
207
208
209
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 199

def txin_data tx_id, txin, idx, p2sh_type = nil
  data = {
    tx_id: tx_id, tx_idx: idx,
    script_sig: txin.script_sig.blob,
    prev_out: txin.prev_out.blob,
    prev_out_index: txin.prev_out_index,
    sequence: txin.sequence.unpack("V")[0],
  }
  data[:p2sh_type] = SCRIPT_TYPES.index(p2sh_type)  if @config[:index_p2sh_type]
  data
end

#txout_data(tx_id, txout, idx, script_type) ⇒ Object

prepare txout data for storage



217
218
219
220
221
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 217

def txout_data tx_id, txout, idx, script_type
  { tx_id: tx_id, tx_idx: idx,
    pk_script: txout.pk_script.blob,
    value: txout.value, type: script_type }
end

#wrap_block(block) ⇒ Object

wrap given block into Models::Block



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 394

def wrap_block(block)
  return nil  unless block

  data = {:id => block[:id], :depth => block[:depth], :chain => block[:chain], :work => block[:work].to_i, :hash => block[:hash].hth, :size => block[:blk_size]}
  blk = Bitcoin::Storage::Models::Block.new(self, data)

  blk.ver = block[:version]
  blk.prev_block = block[:prev_hash].reverse
  blk.mrkl_root = block[:mrkl_root].reverse
  blk.time = block[:time].to_i
  blk.bits = block[:bits]
  blk.nonce = block[:nonce]

  blk.aux_pow = Bitcoin::P::AuxPow.new(block[:aux_pow])  if block[:aux_pow]

  blk_tx = db[:blk_tx].filter(blk_id: block[:id]).join(:tx, id: :tx_id).order(:idx)

  # fetch inputs and outputs for all transactions in the block to avoid additional queries for each transaction
  inputs = db[:txin].filter(:tx_id => blk_tx.map{ |tx| tx[:id] }).order(:tx_idx).map.group_by{ |txin| txin[:tx_id] }
  outputs = db[:txout].filter(:tx_id => blk_tx.map{ |tx| tx[:id] }).order(:tx_idx).map.group_by{ |txout| txout[:tx_id] }

  blk.tx = blk_tx.map { |tx| wrap_tx(tx, block[:id], inputs: inputs[tx[:id]], outputs: outputs[tx[:id]]) }

  blk.hash = block[:hash].hth
  blk
end

#wrap_tx(transaction, block_id = nil, prefetched = {}) ⇒ Object

wrap given transaction into Models::Transaction



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 422

def wrap_tx(transaction, block_id = nil, prefetched = {})
  return nil  unless transaction

  block_id ||= @db[:blk_tx].join(:blk, id: :blk_id)
    .where(tx_id: transaction[:id], chain: 0).first[:blk_id] rescue nil

  data = {id: transaction[:id], blk_id: block_id, size: transaction[:tx_size], idx: transaction[:idx]}
  tx = Bitcoin::Storage::Models::Tx.new(self, data)

  inputs = prefetched[:inputs] || db[:txin].filter(:tx_id => transaction[:id]).order(:tx_idx)
  inputs.each { |i| tx.add_in(wrap_txin(i)) }

  outputs = prefetched[:outputs] || db[:txout].filter(:tx_id => transaction[:id]).order(:tx_idx)
  outputs.each { |o| tx.add_out(wrap_txout(o)) }
  tx.ver = transaction[:version]
  tx.lock_time = transaction[:lock_time]
  tx.hash = transaction[:hash].hth
  tx
end

#wrap_txin(input) ⇒ Object

wrap given input into Models::TxIn



443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 443

def wrap_txin(input)
  return nil  unless input
  data = { :id => input[:id], :tx_id => input[:tx_id], :tx_idx => input[:tx_idx],
    :p2sh_type => input[:p2sh_type] ? SCRIPT_TYPES[input[:p2sh_type]] : nil }
  txin = Bitcoin::Storage::Models::TxIn.new(self, data)
  txin.prev_out = input[:prev_out]
  txin.prev_out_index = input[:prev_out_index]
  txin.script_sig_length = input[:script_sig].bytesize
  txin.script_sig = input[:script_sig]
  txin.sequence = [input[:sequence]].pack("V")
  txin
end

#wrap_txout(output) ⇒ Object

wrap given output into Models::TxOut



457
458
459
460
461
462
463
464
465
# File 'lib/bitcoin/storage/sequel/sequel_store.rb', line 457

def wrap_txout(output)
  return nil  unless output
  data = {:id => output[:id], :tx_id => output[:tx_id], :tx_idx => output[:tx_idx],
    :hash160 => output[:hash160], :type => SCRIPT_TYPES[output[:type]]}
  txout = Bitcoin::Storage::Models::TxOut.new(self, data)
  txout.value = output[:value]
  txout.pk_script = output[:pk_script]
  txout
end