bitcoin-ruby 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +2 -2
  4. data/COPYING +1 -1
  5. data/Gemfile +5 -11
  6. data/README.rdoc +11 -5
  7. data/Rakefile +5 -0
  8. data/bin/bitcoin_node +11 -29
  9. data/bin/bitcoin_node_cli +81 -0
  10. data/bin/bitcoin_wallet +9 -6
  11. data/doc/NODE.rdoc +79 -26
  12. data/examples/bbe_verify_tx.rb +1 -1
  13. data/examples/index_nhash.rb +24 -0
  14. data/examples/reindex_p2sh_addrs.rb +44 -0
  15. data/lib/bitcoin.rb +135 -20
  16. data/lib/bitcoin/builder.rb +233 -63
  17. data/lib/bitcoin/key.rb +89 -16
  18. data/lib/bitcoin/litecoin.rb +13 -11
  19. data/lib/bitcoin/namecoin.rb +5 -4
  20. data/lib/bitcoin/network/command_client.rb +23 -13
  21. data/lib/bitcoin/network/command_handler.rb +336 -131
  22. data/lib/bitcoin/network/connection_handler.rb +14 -13
  23. data/lib/bitcoin/network/node.rb +61 -20
  24. data/lib/bitcoin/protocol.rb +5 -1
  25. data/lib/bitcoin/protocol/block.rb +15 -3
  26. data/lib/bitcoin/protocol/parser.rb +3 -3
  27. data/lib/bitcoin/protocol/tx.rb +82 -20
  28. data/lib/bitcoin/protocol/txin.rb +7 -0
  29. data/lib/bitcoin/protocol/txout.rb +12 -9
  30. data/lib/bitcoin/script.rb +329 -75
  31. data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
  32. data/lib/bitcoin/storage/models.rb +6 -11
  33. data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
  34. data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
  35. data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
  36. data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
  37. data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
  38. data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
  39. data/lib/bitcoin/storage/storage.rb +161 -97
  40. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
  41. data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
  42. data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
  43. data/lib/bitcoin/validation.rb +87 -56
  44. data/lib/bitcoin/version.rb +1 -1
  45. data/spec/bitcoin/bitcoin_spec.rb +38 -0
  46. data/spec/bitcoin/builder_spec.rb +177 -0
  47. data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
  48. data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
  49. data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
  50. data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
  51. data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
  52. data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
  53. data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
  54. data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
  55. data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
  56. data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
  57. data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
  58. data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
  59. data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
  60. data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
  61. data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
  62. data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
  63. data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
  64. data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
  65. data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
  66. data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
  67. data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
  68. data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
  69. data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
  70. data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
  71. data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
  72. data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
  73. data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
  74. data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
  75. data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
  76. data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
  77. data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
  78. data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
  79. data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
  80. data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
  81. data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
  82. data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
  83. data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
  84. data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
  85. data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
  86. data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
  87. data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
  88. data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
  89. data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
  90. data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
  91. data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
  92. data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
  93. data/spec/bitcoin/key_spec.rb +79 -8
  94. data/spec/bitcoin/namecoin_spec.rb +1 -1
  95. data/spec/bitcoin/node/command_api_spec.rb +373 -86
  96. data/spec/bitcoin/performance/storage_spec.rb +41 -0
  97. data/spec/bitcoin/protocol/addr_spec.rb +7 -5
  98. data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
  99. data/spec/bitcoin/protocol/block_spec.rb +6 -0
  100. data/spec/bitcoin/protocol/tx_spec.rb +184 -1
  101. data/spec/bitcoin/protocol/txin_spec.rb +27 -0
  102. data/spec/bitcoin/protocol/txout_spec.rb +27 -0
  103. data/spec/bitcoin/script/opcodes_spec.rb +74 -3
  104. data/spec/bitcoin/script/script_spec.rb +271 -0
  105. data/spec/bitcoin/spec_helper.rb +34 -6
  106. data/spec/bitcoin/storage/models_spec.rb +104 -0
  107. data/spec/bitcoin/storage/reorg_spec.rb +42 -11
  108. data/spec/bitcoin/storage/storage_spec.rb +58 -15
  109. data/spec/bitcoin/storage/validation_spec.rb +44 -14
  110. data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
  111. data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
  112. data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
  113. metadata +117 -11
@@ -43,97 +43,37 @@ module Bitcoin::Storage
43
43
  ORPHAN = 2
44
44
 
45
45
  # possible script types
46
- SCRIPT_TYPES = [:unknown, :pubkey, :hash160, :multisig, :p2sh]
46
+ SCRIPT_TYPES = [:unknown, :pubkey, :hash160, :multisig, :p2sh, :op_return]
47
47
  if Bitcoin.namecoin?
48
48
  [:name_new, :name_firstupdate, :name_update].each {|n| SCRIPT_TYPES << n }
49
49
  end
50
50
 
51
- DEFAULT_CONFIG = {
52
- sqlite_pragmas: {
53
- # journal_mode pragma
54
- journal_mode: false,
55
- # synchronous pragma
56
- synchronous: false,
57
- # cache_size pragma
58
- # positive specifies number of cache pages to use,
59
- # negative specifies cache size in kilobytes.
60
- cache_size: -200_000,
61
- }
62
- }
51
+ # possible address types
52
+ ADDRESS_TYPES = [:hash160, :p2sh]
63
53
 
64
- SEQUEL_ADAPTERS = { :sqlite => "sqlite3", :postgres => "pg", :mysql => "mysql" }
54
+ DEFAULT_CONFIG = {}
65
55
 
66
- attr_reader :log, :config
56
+ attr_reader :log
67
57
 
68
58
  attr_accessor :config
69
59
 
70
60
  def initialize(config = {}, getblocks_callback = nil)
71
- base = self.class.ancestors.select {|a| a.name =~ /StoreBase$/ }[0]::DEFAULT_CONFIG
61
+ # merge all the configuration defaults, keeping the most specific ones.
62
+ store_ancestors = self.class.ancestors.select {|a| a.name =~ /StoreBase$/ }.reverse
63
+ base = store_ancestors.reduce(store_ancestors[0]::DEFAULT_CONFIG) do |config, ancestor|
64
+ config.merge(ancestor::DEFAULT_CONFIG)
65
+ end
72
66
  @config = base.merge(self.class::DEFAULT_CONFIG).merge(config)
73
67
  @log = config[:log] || Bitcoin::Storage.log
74
68
  @log.level = @config[:log_level] if @config[:log_level]
75
- init_sequel_store
69
+ init_store_connection
76
70
  @getblocks_callback = getblocks_callback
77
71
  @checkpoints = Bitcoin.network[:checkpoints] || {}
78
72
  @watched_addrs = []
73
+ @notifiers = {}
79
74
  end
80
75
 
81
- def init_sequel_store
82
- return unless (self.is_a?(SequelStore) || self.is_a?(UtxoStore)) && @config[:db]
83
- @config[:db].sub!("~", ENV["HOME"])
84
- @config[:db].sub!("<network>", Bitcoin.network_name.to_s)
85
- adapter = SEQUEL_ADAPTERS[@config[:db].split(":").first] rescue nil
86
- Bitcoin.require_dependency(adapter, gem: adapter) if adapter
87
- connect
88
- end
89
-
90
- # connect to database
91
- def connect
92
- Sequel.extension(:core_extensions, :sequel_3_dataset_methods)
93
- @db = Sequel.connect(@config[:db].sub("~", ENV["HOME"]))
94
- @db.extend_datasets(Sequel::Sequel3DatasetMethods)
95
- sqlite_pragmas; migrate; check_metadata
96
- log.info { "opened #{backend_name} store #{@db.uri}" }
97
- end
98
-
99
- # check if schema is up to date and migrate to current version if necessary
100
- def migrate
101
- migrations_path = File.join(File.dirname(__FILE__), "#{backend_name}/migrations")
102
- Sequel.extension :migration
103
- unless Sequel::Migrator.is_current?(@db, migrations_path)
104
- log = @log; @db.instance_eval { @log = log }
105
- Sequel::Migrator.run(@db, migrations_path)
106
- unless (v = @db[:schema_info].first) && v[:magic] && v[:backend]
107
- @db[:schema_info].update(
108
- magic: Bitcoin.network[:magic_head].hth, backend: backend_name)
109
- end
110
- end
111
- end
112
-
113
- # check that database network magic and backend match the ones we are using
114
- def check_metadata
115
- version = @db[:schema_info].first
116
- unless version[:magic] == Bitcoin.network[:magic_head].hth
117
- name = Bitcoin::NETWORKS.find{|n,d| d[:magic_head].hth == version[:magic]}[0]
118
- raise "Error: DB #{@db.url} was created for '#{name}' network!"
119
- end
120
- unless version[:backend] == backend_name
121
- if version[:backend] == "sequel" && backend_name == "utxo"
122
- log.warn { "Note: The 'utxo' store is now the default backend.
123
- To keep using the full storage, change the configuration to use storage: 'sequel::#{@db.url}'.
124
- To use the new storage backend, delete or move #{@db.url}, or specify a different database path in the config." }
125
- end
126
- raise "Error: DB #{@db.url} was created for '#{version[:backend]}' backend!"
127
- end
128
- end
129
-
130
- # set pragma options for sqlite (if it is sqlite)
131
- def sqlite_pragmas
132
- return unless (@db.is_a?(Sequel::SQLite::Database) rescue false)
133
- @config[:sqlite_pragmas].each do |name, value|
134
- @db.pragma_set name, value
135
- log.debug { "set sqlite pragma #{name} to #{value}" }
136
- end
76
+ def init_store_connection
137
77
  end
138
78
 
139
79
  # name of the storage backend currently in use ("sequel" or "utxo")
@@ -146,6 +86,12 @@ module Bitcoin::Storage
146
86
  raise "Not implemented"
147
87
  end
148
88
 
89
+ # check data consistency of the top +count+ blocks.
90
+ def check_consistency count
91
+ raise "Not implemented"
92
+ end
93
+
94
+
149
95
  # handle a new block incoming from the network
150
96
  def new_block blk
151
97
  time = Time.now
@@ -202,7 +148,9 @@ module Bitcoin::Storage
202
148
  end
203
149
  validator.validate(rules: [:context], raise_errors: true)
204
150
  end
205
- return persist_block(blk, MAIN, depth, prev_block.work)
151
+ res = persist_block(blk, MAIN, depth, prev_block.work)
152
+ push_notification(:block, [blk, *res])
153
+ return res
206
154
  else
207
155
  log.debug { "=> side (#{depth})" }
208
156
  return persist_block(blk, SIDE, depth, prev_block.work)
@@ -211,7 +159,6 @@ module Bitcoin::Storage
211
159
  head = get_head
212
160
  if prev_block.work + blk.block_work <= head.work
213
161
  log.debug { "=> side (#{depth})" }
214
- validator.validate(rules: [:context], raise_errors: true) unless @config[:skip_validation]
215
162
  return persist_block(blk, SIDE, depth, prev_block.work)
216
163
  else
217
164
  log.debug { "=> reorg" }
@@ -227,6 +174,9 @@ module Bitcoin::Storage
227
174
  end
228
175
  log.debug { "new main: #{new_main.inspect}" }
229
176
  log.debug { "new side: #{new_side.inspect}" }
177
+
178
+ push_notification(:reorg, [ new_main, new_side ])
179
+
230
180
  reorg(new_side.reverse, new_main.reverse)
231
181
  return persist_block(blk, MAIN, depth, prev_block.work)
232
182
  end
@@ -323,17 +273,35 @@ module Bitcoin::Storage
323
273
  raise "Not implemented"
324
274
  end
325
275
 
276
+ # get block id in main chain by given +tx_id+
277
+ def get_block_id_for_tx_id(tx_id)
278
+ get_tx_by_id(tx_id).blk_id rescue nil # tx.blk_id is always in main chain
279
+ end
280
+
326
281
  # get corresponding txin for the txout in
327
282
  # transaction +tx_hash+ with index +txout_idx+
328
283
  def get_txin_for_txout(tx_hash, txout_idx)
329
284
  raise "Not implemented"
330
285
  end
331
286
 
287
+ # get an array of corresponding txins for provided +txouts+
288
+ # txouts = [tx_hash, tx_idx]
289
+ # can be overwritten by specific storage for opimization
290
+ def get_txins_for_txouts(txouts)
291
+ txouts.map{|tx_hash, tx_idx| get_txin_for_txout(tx_hash, tx_idx) }.compact
292
+ end
293
+
332
294
  # get tx with given +tx_hash+
333
295
  def get_tx(tx_hash)
334
296
  raise "Not implemented"
335
297
  end
336
298
 
299
+ # get more than one tx by +tx_hashes+, returns an array
300
+ # can be reimplemented by specific storage for optimization
301
+ def get_txs(tx_hashes)
302
+ tx_hashes.map {|h| get_tx(h)}.compact
303
+ end
304
+
337
305
  # get tx with given +tx_id+
338
306
  def get_tx_by_id(tx_id)
339
307
  raise "Not implemented"
@@ -354,49 +322,57 @@ module Bitcoin::Storage
354
322
  # standard tx to given +address+
355
323
  def get_txouts_for_address(address, unconfirmed = false)
356
324
  hash160 = Bitcoin.hash160_from_address(address)
357
- get_txouts_for_hash160(hash160, unconfirmed)
325
+ type = Bitcoin.address_type(address)
326
+ get_txouts_for_hash160(hash160, type, unconfirmed)
327
+ end
328
+
329
+ # collect all unspent txouts containing a
330
+ # standard tx to given +address+
331
+ def get_unspent_txouts_for_address(address, unconfirmed = false)
332
+ txouts = self.get_txouts_for_address(address, unconfirmed)
333
+ txouts.select! do |t|
334
+ not t.get_next_in
335
+ end
336
+ txouts
358
337
  end
359
338
 
360
339
  # get balance for given +hash160+
361
- def get_balance(hash160, unconfirmed = false)
362
- txouts = get_txouts_for_hash160(hash160, unconfirmed)
340
+ def get_balance(hash160_or_addr, unconfirmed = false)
341
+ if Bitcoin.valid_address?(hash160_or_addr)
342
+ txouts = get_txouts_for_address(hash160_or_addr)
343
+ else
344
+ txouts = get_txouts_for_hash160(hash160_or_addr, :hash160, unconfirmed)
345
+ end
363
346
  unspent = txouts.select {|o| o.get_next_in.nil?}
364
347
  unspent.map(&:value).inject {|a,b| a+=b; a} || 0
365
348
  rescue
366
349
  nil
367
350
  end
368
351
 
369
-
370
- # store address +hash160+
371
- def store_addr(txout_id, hash160)
372
- addr = @db[:addr][:hash160 => hash160]
373
- addr_id = addr[:id] if addr
374
- addr_id ||= @db[:addr].insert({:hash160 => hash160})
375
- @db[:addr_txout].insert({:addr_id => addr_id, :txout_id => txout_id})
376
- end
377
-
378
352
  # parse script and collect address/txout mappings to index
379
- def parse_script txout, i
353
+ def parse_script txout, i, tx_hash = "", tx_idx
380
354
  addrs, names = [], []
381
- # skip huge script in testnet3 block 54507 (998000 bytes)
382
- return [SCRIPT_TYPES.index(:unknown), [], []] if txout.pk_script.bytesize > 10_000
355
+
383
356
  script = Bitcoin::Script.new(txout.pk_script) rescue nil
384
357
  if script
385
- if script.is_hash160? || script.is_pubkey?
386
- addrs << [i, script.get_hash160]
358
+ if script.is_hash160? || script.is_pubkey? || script.is_p2sh?
359
+ addrs << [i, script.get_address]
387
360
  elsif script.is_multisig?
388
- script.get_multisig_pubkeys.map do |pubkey|
389
- addrs << [i, Bitcoin.hash160(pubkey.unpack("H*")[0])]
361
+ script.get_multisig_addresses.map do |address|
362
+ addrs << [i, address]
390
363
  end
391
364
  elsif Bitcoin.namecoin? && script.is_namecoin?
392
- addrs << [i, script.get_hash160]
365
+ addrs << [i, script.get_address]
393
366
  names << [i, script]
367
+ elsif script.is_op_return?
368
+ log.info { "Ignoring OP_RETURN script: #{script.get_op_return_data}" }
394
369
  else
395
- log.debug { "Unknown script type"}# #{tx.hash}:#{txout_idx}" }
370
+ log.info { "Unknown script type in txout #{tx_hash}:#{tx_idx}" }
371
+ log.debug { script.to_string }
396
372
  end
397
373
  script_type = SCRIPT_TYPES.index(script.type)
398
374
  else
399
- log.error { "Error parsing script"}# #{tx.hash}:#{txout_idx}" }
375
+ log.error { "Error parsing script #{tx_hash}:#{tx_idx}" }
400
376
  script_type = SCRIPT_TYPES.index(:unknown)
401
377
  end
402
378
  [script_type, addrs, names]
@@ -445,9 +421,97 @@ module Bitcoin::Storage
445
421
  (get_head && (Time.now - get_head.time).to_i < 3600) ? true : false
446
422
  end
447
423
 
424
+ def push_notification channel, message
425
+ @notifiers[channel.to_sym].push(message) if @notifiers[channel.to_sym]
426
+ end
427
+
428
+ def subscribe channel
429
+ @notifiers[channel.to_sym] ||= EM::Channel.new
430
+ @notifiers[channel.to_sym].subscribe {|*data| yield(*data) }
431
+ end
432
+
448
433
  end
434
+
435
+ class SequelStoreBase < StoreBase
436
+
437
+ DEFAULT_CONFIG = {
438
+ sqlite_pragmas: {
439
+ # journal_mode pragma
440
+ journal_mode: false,
441
+ # synchronous pragma
442
+ synchronous: false,
443
+ # cache_size pragma
444
+ # positive specifies number of cache pages to use,
445
+ # negative specifies cache size in kilobytes.
446
+ cache_size: -200_000,
447
+ }
448
+ }
449
+
450
+ SEQUEL_ADAPTERS = { :sqlite => "sqlite3", :postgres => "pg", :mysql => "mysql" }
451
+
452
+ #set the connection
453
+ def init_store_connection
454
+ return unless (self.is_a?(SequelStore) || self.is_a?(UtxoStore)) && @config[:db]
455
+ @config[:db].sub!("~", ENV["HOME"])
456
+ @config[:db].sub!("<network>", Bitcoin.network_name.to_s)
457
+ adapter = SEQUEL_ADAPTERS[@config[:db].split(":").first] rescue nil
458
+ Bitcoin.require_dependency(adapter, gem: adapter) if adapter
459
+ connect
460
+ end
461
+
462
+ # connect to database
463
+ def connect
464
+ Sequel.extension(:core_extensions, :sequel_3_dataset_methods)
465
+ @db = Sequel.connect(@config[:db].sub("~", ENV["HOME"]))
466
+ @db.extend_datasets(Sequel::Sequel3DatasetMethods)
467
+ sqlite_pragmas; migrate; check_metadata
468
+ log.info { "opened #{backend_name} store #{@db.uri}" }
469
+ end
470
+
471
+ # check if schema is up to date and migrate to current version if necessary
472
+ def migrate
473
+ migrations_path = File.join(File.dirname(__FILE__), "#{backend_name}/migrations")
474
+ Sequel.extension :migration
475
+ unless Sequel::Migrator.is_current?(@db, migrations_path)
476
+ store = self; log = @log; @db.instance_eval { @log = log; @store = store }
477
+ Sequel::Migrator.run(@db, migrations_path)
478
+ unless (v = @db[:schema_info].first) && v[:magic] && v[:backend]
479
+ @db[:schema_info].update(
480
+ magic: Bitcoin.network[:magic_head].hth, backend: backend_name)
481
+ end
482
+ end
483
+ end
484
+
485
+ # check that database network magic and backend match the ones we are using
486
+ def check_metadata
487
+ version = @db[:schema_info].first
488
+ unless version[:magic] == Bitcoin.network[:magic_head].hth
489
+ name = Bitcoin::NETWORKS.find{|n,d| d[:magic_head].hth == version[:magic]}[0]
490
+ raise "Error: DB #{@db.url} was created for '#{name}' network!"
491
+ end
492
+ unless version[:backend] == backend_name
493
+ if version[:backend] == "sequel" && backend_name == "utxo"
494
+ log.warn { "Note: The 'utxo' store is now the default backend.
495
+ To keep using the full storage, change the configuration to use storage: 'sequel::#{@db.url}'.
496
+ To use the new storage backend, delete or move #{@db.url}, or specify a different database path in the config." }
497
+ end
498
+ raise "Error: DB #{@db.url} was created for '#{version[:backend]}' backend!"
499
+ end
500
+ end
501
+
502
+ # set pragma options for sqlite (if it is sqlite)
503
+ def sqlite_pragmas
504
+ return unless (@db.is_a?(Sequel::SQLite::Database) rescue false)
505
+ @config[:sqlite_pragmas].each do |name, value|
506
+ @db.pragma_set name, value
507
+ log.debug { "set sqlite pragma #{name} to #{value}" }
508
+ end
509
+ end
510
+ end
511
+
449
512
  end
450
513
  end
451
514
 
515
+
452
516
  # TODO: someday sequel will support #blob directly and #to_sequel_blob will be gone
453
517
  class String; def blob; to_sequel_blob; end; end
@@ -9,7 +9,7 @@ Sequel.migration do
9
9
  column :tx_hash, String, null: false, index: true
10
10
  column :tx_idx, :int, null: false, index: true
11
11
  column :blk_id, :int, null: false, index: true
12
- column :pk_script, (@db.adapter_scheme == :postgres ? :bytea : :blob), null: false
12
+ column :pk_script, (@db.adapter_scheme == :postgres ? :bytea : :blob), null: false
13
13
  column :value, :bigint, null: false, index: true
14
14
  end
15
15
 
@@ -0,0 +1,14 @@
1
+ # Add column addr.type and correct the type for all p2sh addresses
2
+
3
+ Sequel.migration do
4
+
5
+ up do
6
+
7
+ @log.info { "Running migration #{__FILE__}" }
8
+
9
+ add_column :addr, :type, :int, default: 0, null: false
10
+ add_index :addr, [:hash160, :type]
11
+
12
+ end
13
+
14
+ end
@@ -5,8 +5,7 @@ module Bitcoin::Storage::Backends
5
5
 
6
6
  # Storage backend using Sequel to connect to arbitrary SQL databases.
7
7
  # Inherits from StoreBase and implements its interface.
8
- class UtxoStore < StoreBase
9
-
8
+ class UtxoStore < SequelStoreBase
10
9
 
11
10
  # possible script types
12
11
  SCRIPT_TYPES = [:unknown, :pubkey, :hash160, :multisig, :p2sh]
@@ -106,7 +105,7 @@ module Bitcoin::Storage::Backends
106
105
  tx_idx: txin.prev_out_index } if @new_outs.size == size
107
106
  end
108
107
  tx.out.each.with_index do |txout, txout_tx_idx|
109
- _, a, n = *parse_script(txout, txout_tx_idx)
108
+ _, a, n = *parse_script(txout, txout_tx_idx, tx.hash, txout_tx_idx)
110
109
  @new_outs << [{
111
110
  :tx_hash => tx.hash.blob,
112
111
  :tx_idx => txout_tx_idx,
@@ -116,9 +115,9 @@ module Bitcoin::Storage::Backends
116
115
  @config[:index_all_addrs] ? a : a.select {|a| @watched_addrs.include?(a[1]) },
117
116
  Bitcoin.namecoin? ? n : [] ]
118
117
  end
118
+ flush_spent_outs(depth) if @spent_outs.size > @config[:utxo_cache]
119
+ flush_new_outs(depth) if @new_outs.size > @config[:utxo_cache]
119
120
  end
120
- flush_spent_outs(depth) if @spent_outs.size > @config[:utxo_cache]
121
- flush_new_outs(depth) if @new_outs.size > @config[:utxo_cache]
122
121
  end
123
122
 
124
123
  def reorg new_side, new_main
@@ -191,6 +190,18 @@ module Bitcoin::Storage::Backends
191
190
  end
192
191
  end
193
192
 
193
+ # store hash160 and type of +addr+
194
+ def store_addr(txout_id, addr)
195
+ hash160 = Bitcoin.hash160_from_address(addr)
196
+ type = ADDRESS_TYPES.index(Bitcoin.address_type(addr))
197
+
198
+ addr = @db[:addr][hash160: hash160, type: type]
199
+ addr_id = addr[:id] if addr
200
+ addr_id ||= @db[:addr].insert(hash160: hash160, type: type)
201
+
202
+ @db[:addr_txout].insert(addr_id: addr_id, txout_id: txout_id)
203
+ end
204
+
194
205
  def add_watched_address address
195
206
  hash160 = Bitcoin.hash160_from_address(address)
196
207
  @db[:addr].insert(hash160: hash160) unless @db[:addr][hash160: hash160]
@@ -291,6 +302,12 @@ module Bitcoin::Storage::Backends
291
302
  wrap_txout(@db[:utxo][tx_hash: txin.prev_out.reverse.hth.blob, tx_idx: txin.prev_out_index])
292
303
  end
293
304
 
305
+ # get the next input that references given output
306
+ # we only store unspent outputs, so it's always nil
307
+ def get_txin_for_txout(tx_hash, tx_idx)
308
+ nil
309
+ end
310
+
294
311
  # get all Models::TxOut matching given +script+
295
312
  def get_txouts_for_pk_script(script)
296
313
  utxos = @db[:utxo].filter(pk_script: script.blob).order(:blk_id)
@@ -298,16 +315,12 @@ module Bitcoin::Storage::Backends
298
315
  end
299
316
 
300
317
  # get all Models::TxOut matching given +hash160+
301
- def get_txouts_for_hash160(hash160, unconfirmed = false)
302
- addr = @db[:addr][hash160: hash160]
318
+ def get_txouts_for_hash160(hash160, type = :hash160, unconfirmed = false)
319
+ addr = @db[:addr][hash160: hash160, type: ADDRESS_TYPES.index(type)]
303
320
  return [] unless addr
304
321
  @db[:addr_txout].where(addr_id: addr[:id]).map {|ao| wrap_txout(@db[:utxo][id: ao[:txout_id]]) }.compact
305
322
  end
306
323
 
307
- def get_balance hash160
308
- get_txouts_for_hash160(hash160).map(&:value).inject(:+) || 0
309
- end
310
-
311
324
  # wrap given +block+ into Models::Block
312
325
  def wrap_block(block)
313
326
  return nil unless block
@@ -335,7 +348,7 @@ module Bitcoin::Storage::Backends
335
348
  def wrap_tx(tx_hash)
336
349
  utxos = @db[:utxo].where(tx_hash: tx_hash.blob)
337
350
  return nil unless utxos.any?
338
- data = { blk_id: utxos.first[:blk_id] }
351
+ data = { blk_id: utxos.first[:blk_id], id: tx_hash }
339
352
  tx = Bitcoin::Storage::Models::Tx.new(self, data)
340
353
  tx.hash = tx_hash # utxos.first[:tx_hash].hth
341
354
  utxos.each {|u| tx.out[u[:tx_idx]] = wrap_txout(u) }