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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +2 -2
- data/COPYING +1 -1
- data/Gemfile +5 -11
- data/README.rdoc +11 -5
- data/Rakefile +5 -0
- data/bin/bitcoin_node +11 -29
- data/bin/bitcoin_node_cli +81 -0
- data/bin/bitcoin_wallet +9 -6
- data/doc/NODE.rdoc +79 -26
- data/examples/bbe_verify_tx.rb +1 -1
- data/examples/index_nhash.rb +24 -0
- data/examples/reindex_p2sh_addrs.rb +44 -0
- data/lib/bitcoin.rb +135 -20
- data/lib/bitcoin/builder.rb +233 -63
- data/lib/bitcoin/key.rb +89 -16
- data/lib/bitcoin/litecoin.rb +13 -11
- data/lib/bitcoin/namecoin.rb +5 -4
- data/lib/bitcoin/network/command_client.rb +23 -13
- data/lib/bitcoin/network/command_handler.rb +336 -131
- data/lib/bitcoin/network/connection_handler.rb +14 -13
- data/lib/bitcoin/network/node.rb +61 -20
- data/lib/bitcoin/protocol.rb +5 -1
- data/lib/bitcoin/protocol/block.rb +15 -3
- data/lib/bitcoin/protocol/parser.rb +3 -3
- data/lib/bitcoin/protocol/tx.rb +82 -20
- data/lib/bitcoin/protocol/txin.rb +7 -0
- data/lib/bitcoin/protocol/txout.rb +12 -9
- data/lib/bitcoin/script.rb +329 -75
- data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
- data/lib/bitcoin/storage/models.rb +6 -11
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
- data/lib/bitcoin/storage/storage.rb +161 -97
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
- data/lib/bitcoin/validation.rb +87 -56
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +38 -0
- data/spec/bitcoin/builder_spec.rb +177 -0
- data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
- data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
- data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
- data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
- data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
- data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
- data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
- data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
- data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
- data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
- data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
- data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
- data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
- data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
- data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
- data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
- data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
- data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
- data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
- data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
- data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
- data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
- data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
- data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
- data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
- data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
- data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
- data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
- data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
- data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
- data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
- data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
- data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
- data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
- data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
- data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
- data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
- data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
- data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
- data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
- data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
- data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
- data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
- data/spec/bitcoin/key_spec.rb +79 -8
- data/spec/bitcoin/namecoin_spec.rb +1 -1
- data/spec/bitcoin/node/command_api_spec.rb +373 -86
- data/spec/bitcoin/performance/storage_spec.rb +41 -0
- data/spec/bitcoin/protocol/addr_spec.rb +7 -5
- data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
- data/spec/bitcoin/protocol/block_spec.rb +6 -0
- data/spec/bitcoin/protocol/tx_spec.rb +184 -1
- data/spec/bitcoin/protocol/txin_spec.rb +27 -0
- data/spec/bitcoin/protocol/txout_spec.rb +27 -0
- data/spec/bitcoin/script/opcodes_spec.rb +74 -3
- data/spec/bitcoin/script/script_spec.rb +271 -0
- data/spec/bitcoin/spec_helper.rb +34 -6
- data/spec/bitcoin/storage/models_spec.rb +104 -0
- data/spec/bitcoin/storage/reorg_spec.rb +42 -11
- data/spec/bitcoin/storage/storage_spec.rb +58 -15
- data/spec/bitcoin/storage/validation_spec.rb +44 -14
- data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
- data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
- data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
- 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
|
-
|
52
|
-
|
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
|
-
|
54
|
+
DEFAULT_CONFIG = {}
|
65
55
|
|
66
|
-
attr_reader :log
|
56
|
+
attr_reader :log
|
67
57
|
|
68
58
|
attr_accessor :config
|
69
59
|
|
70
60
|
def initialize(config = {}, getblocks_callback = nil)
|
71
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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(
|
362
|
-
|
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
|
-
|
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.
|
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.
|
389
|
-
addrs << [i,
|
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.
|
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.
|
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
|
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
|
-
|
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 <
|
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) }
|