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.
- 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) }
|