bitcoin-ruby 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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) }