bitcoin-ruby 0.0.1 → 0.0.2
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.
- data/.gitignore +4 -1
- data/Gemfile +21 -0
- data/README.rdoc +85 -25
- data/Rakefile +7 -3
- data/bin/bitcoin_node +39 -42
- data/bin/bitcoin_shell +1 -0
- data/bin/bitcoin_wallet +129 -53
- data/bitcoin-ruby.gemspec +4 -7
- data/concept-examples/blockchain-pow.rb +1 -1
- data/doc/CONFIG.rdoc +5 -5
- data/doc/EXAMPLES.rdoc +9 -5
- data/doc/NAMECOIN.rdoc +34 -0
- data/doc/NODE.rdoc +147 -10
- data/examples/balance.rb +10 -4
- data/examples/bbe_verify_tx.rb +7 -2
- data/examples/forwarder.rb +73 -0
- data/examples/generate_tx.rb +34 -0
- data/examples/simple_network_monitor_and_util.rb +187 -0
- data/examples/verify_tx.rb +1 -1
- data/lib/bitcoin.rb +308 -18
- data/lib/bitcoin/builder.rb +62 -36
- data/lib/bitcoin/config.rb +2 -0
- data/lib/bitcoin/connection.rb +11 -8
- data/lib/bitcoin/electrum/mnemonic.rb +162 -0
- data/lib/bitcoin/ffi/openssl.rb +187 -21
- data/lib/bitcoin/gui/addr_view.rb +2 -0
- data/lib/bitcoin/gui/conn_view.rb +2 -0
- data/lib/bitcoin/gui/connection.rb +2 -0
- data/lib/bitcoin/gui/em_gtk.rb +2 -0
- data/lib/bitcoin/gui/gui.rb +2 -0
- data/lib/bitcoin/gui/helpers.rb +2 -0
- data/lib/bitcoin/gui/tree_view.rb +2 -0
- data/lib/bitcoin/gui/tx_view.rb +2 -0
- data/lib/bitcoin/key.rb +77 -11
- data/lib/bitcoin/litecoin.rb +81 -0
- data/lib/bitcoin/logger.rb +20 -1
- data/lib/bitcoin/namecoin.rb +279 -0
- data/lib/bitcoin/network/command_client.rb +7 -6
- data/lib/bitcoin/network/command_handler.rb +229 -43
- data/lib/bitcoin/network/connection_handler.rb +182 -70
- data/lib/bitcoin/network/node.rb +231 -106
- data/lib/bitcoin/protocol.rb +44 -23
- data/lib/bitcoin/protocol/address.rb +5 -3
- data/lib/bitcoin/protocol/alert.rb +3 -4
- data/lib/bitcoin/protocol/aux_pow.rb +123 -0
- data/lib/bitcoin/protocol/block.rb +98 -18
- data/lib/bitcoin/protocol/handler.rb +6 -5
- data/lib/bitcoin/protocol/parser.rb +44 -19
- data/lib/bitcoin/protocol/tx.rb +105 -52
- data/lib/bitcoin/protocol/txin.rb +39 -19
- data/lib/bitcoin/protocol/txout.rb +28 -13
- data/lib/bitcoin/protocol/version.rb +16 -7
- data/lib/bitcoin/script.rb +579 -122
- data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
- data/lib/bitcoin/storage/models.rb +20 -7
- data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
- data/lib/bitcoin/storage/storage.rb +233 -28
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
- data/lib/bitcoin/validation.rb +369 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/coinselector.rb +3 -0
- data/lib/bitcoin/wallet/keygenerator.rb +3 -1
- data/lib/bitcoin/wallet/keystore.rb +6 -2
- data/lib/bitcoin/wallet/txdp.rb +6 -4
- data/lib/bitcoin/wallet/wallet.rb +54 -16
- data/spec/bitcoin/bitcoin_spec.rb +48 -3
- data/spec/bitcoin/builder_spec.rb +40 -17
- data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
- data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
- data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
- data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
- data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
- data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
- data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
- data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
- data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
- data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
- data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
- data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
- data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
- data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
- data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
- data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
- data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
- data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
- data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
- data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
- data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
- data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
- data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
- data/spec/bitcoin/key_spec.rb +128 -3
- data/spec/bitcoin/namecoin_spec.rb +182 -0
- data/spec/bitcoin/network_spec.rb +5 -3
- data/spec/bitcoin/node/command_api_spec.rb +376 -0
- data/spec/bitcoin/protocol/addr_spec.rb +2 -0
- data/spec/bitcoin/protocol/alert_spec.rb +2 -0
- data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
- data/spec/bitcoin/protocol/block_spec.rb +134 -39
- data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
- data/spec/bitcoin/protocol/inv_spec.rb +10 -8
- data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
- data/spec/bitcoin/protocol/ping_spec.rb +2 -0
- data/spec/bitcoin/protocol/tx_spec.rb +83 -17
- data/spec/bitcoin/protocol/version_spec.rb +7 -5
- data/spec/bitcoin/script/opcodes_spec.rb +412 -133
- data/spec/bitcoin/script/script_spec.rb +112 -13
- data/spec/bitcoin/spec_helper.rb +68 -0
- data/spec/bitcoin/storage/reorg_spec.rb +199 -0
- data/spec/bitcoin/storage/storage_spec.rb +337 -0
- data/spec/bitcoin/storage/validation_spec.rb +261 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
- data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
- metadata +105 -51
- data/lib/bitcoin/storage/sequel.rb +0 -335
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
- data/spec/bitcoin/reorg_spec.rb +0 -129
- data/spec/bitcoin/storage_spec.rb +0 -229
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
# The storage implementation supports different backends, which inherit from
|
|
2
4
|
# Storage::StoreBase and implement the same interface.
|
|
3
5
|
# Each backend returns Storage::Models objects to easily access helper methods and metadata.
|
|
@@ -11,7 +13,7 @@ module Bitcoin::Storage
|
|
|
11
13
|
@log = Bitcoin::Logger.create(:storage)
|
|
12
14
|
def self.log; @log; end
|
|
13
15
|
|
|
14
|
-
BACKENDS = [:dummy, :sequel]
|
|
16
|
+
BACKENDS = [:dummy, :sequel, :utxo]
|
|
15
17
|
BACKENDS.each do |name|
|
|
16
18
|
module_eval <<-EOS
|
|
17
19
|
def self.#{name} config, *args
|
|
@@ -22,7 +24,7 @@ module Bitcoin::Storage
|
|
|
22
24
|
|
|
23
25
|
module Backends
|
|
24
26
|
|
|
25
|
-
BACKENDS.each {|b| autoload("#{b.to_s.capitalize}Store", "bitcoin/storage/#{b}") }
|
|
27
|
+
BACKENDS.each {|b| autoload("#{b.to_s.capitalize}Store", "bitcoin/storage/#{b}/#{b}_store.rb") }
|
|
26
28
|
|
|
27
29
|
# Base class for storage backends.
|
|
28
30
|
# Every backend must overwrite the "Not implemented" methods
|
|
@@ -40,12 +42,102 @@ module Bitcoin::Storage
|
|
|
40
42
|
# orphan branch (not connected to main branch / genesis block)
|
|
41
43
|
ORPHAN = 2
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
# possible script types
|
|
46
|
+
SCRIPT_TYPES = [:unknown, :pubkey, :hash160, :multisig, :p2sh]
|
|
47
|
+
if Bitcoin.namecoin?
|
|
48
|
+
[:name_new, :name_firstupdate, :name_update].each {|n| SCRIPT_TYPES << n }
|
|
49
|
+
end
|
|
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
|
+
}
|
|
63
|
+
|
|
64
|
+
SEQUEL_ADAPTERS = { :sqlite => "sqlite3", :postgres => "pg", :mysql => "mysql" }
|
|
65
|
+
|
|
66
|
+
attr_reader :log, :config
|
|
67
|
+
|
|
68
|
+
attr_accessor :config
|
|
44
69
|
|
|
45
70
|
def initialize(config = {}, getblocks_callback = nil)
|
|
46
|
-
|
|
47
|
-
@
|
|
71
|
+
base = self.class.ancestors.select {|a| a.name =~ /StoreBase$/ }[0]::DEFAULT_CONFIG
|
|
72
|
+
@config = base.merge(self.class::DEFAULT_CONFIG).merge(config)
|
|
48
73
|
@log = config[:log] || Bitcoin::Storage.log
|
|
74
|
+
@log.level = @config[:log_level] if @config[:log_level]
|
|
75
|
+
init_sequel_store
|
|
76
|
+
@getblocks_callback = getblocks_callback
|
|
77
|
+
@checkpoints = Bitcoin.network[:checkpoints] || {}
|
|
78
|
+
@watched_addrs = []
|
|
79
|
+
end
|
|
80
|
+
|
|
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
|
+
Sequel::Migrator.run(@db, migrations_path)
|
|
105
|
+
unless (v = @db[:schema_info].first) && v[:magic] && v[:backend]
|
|
106
|
+
@db[:schema_info].update(
|
|
107
|
+
magic: Bitcoin.network[:magic_head].hth, backend: backend_name)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# check that database network magic and backend match the ones we are using
|
|
113
|
+
def check_metadata
|
|
114
|
+
version = @db[:schema_info].first
|
|
115
|
+
unless version[:magic] == Bitcoin.network[:magic_head].hth
|
|
116
|
+
name = Bitcoin::NETWORKS.find{|n,d| d[:magic_head].hth == version[:magic]}[0]
|
|
117
|
+
raise "Error: DB #{@db.url} was created for '#{name}' network!"
|
|
118
|
+
end
|
|
119
|
+
unless version[:backend] == backend_name
|
|
120
|
+
if version[:backend] == "sequel" && backend_name == "utxo"
|
|
121
|
+
log.warn { "Note: The 'utxo' store is now the default backend.
|
|
122
|
+
To keep using the full storage, change the configuration to use storage: 'sequel::#{@db.url}'.
|
|
123
|
+
To use the new storage backend, delete or move #{@db.url}, or specify a different database path in the config." }
|
|
124
|
+
end
|
|
125
|
+
raise "Error: DB #{@db.url} was created for '#{version[:backend]}' backend!"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# set pragma options for sqlite (if it is sqlite)
|
|
130
|
+
def sqlite_pragmas
|
|
131
|
+
return unless (@db.is_a?(Sequel::SQLite::Database) rescue false)
|
|
132
|
+
@config[:sqlite_pragmas].each do |name, value|
|
|
133
|
+
@db.pragma_set name, value
|
|
134
|
+
log.debug { "set sqlite pragma #{name} to #{value}" }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# name of the storage backend currently in use ("sequel" or "utxo")
|
|
139
|
+
def backend_name
|
|
140
|
+
self.class.name.split("::")[-1].split("Store")[0].downcase
|
|
49
141
|
end
|
|
50
142
|
|
|
51
143
|
# reset the store; delete all data
|
|
@@ -53,6 +145,16 @@ module Bitcoin::Storage
|
|
|
53
145
|
raise "Not implemented"
|
|
54
146
|
end
|
|
55
147
|
|
|
148
|
+
# handle a new block incoming from the network
|
|
149
|
+
def new_block blk
|
|
150
|
+
time = Time.now
|
|
151
|
+
res = store_block(blk)
|
|
152
|
+
log.info { "block #{blk.hash} " +
|
|
153
|
+
"[#{res[0]}, #{['main', 'side', 'orphan'][res[1]]}] " +
|
|
154
|
+
"(#{"%.4fs, %3dtx, %.3fkb" % [(Time.now - time), blk.tx.size, blk.payload.bytesize.to_f/1000]})" } if res && res[1]
|
|
155
|
+
res
|
|
156
|
+
end
|
|
157
|
+
|
|
56
158
|
# store given block +blk+.
|
|
57
159
|
# determine branch/chain and dept of block. trigger reorg if side branch becomes longer
|
|
58
160
|
# than current main chain and connect orpans.
|
|
@@ -60,9 +162,17 @@ module Bitcoin::Storage
|
|
|
60
162
|
log.debug { "new block #{blk.hash}" }
|
|
61
163
|
|
|
62
164
|
existing = get_block(blk.hash)
|
|
63
|
-
|
|
165
|
+
if existing && existing.chain == MAIN
|
|
166
|
+
log.debug { "=> exists (#{existing.depth}, #{existing.chain})" }
|
|
167
|
+
return [existing.depth]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
prev_block = get_block(blk.prev_block.reverse_hth)
|
|
171
|
+
unless @config[:skip_validation]
|
|
172
|
+
validator = blk.validator(self, prev_block)
|
|
173
|
+
validator.validate(rules: [:syntax], raise_errors: true)
|
|
174
|
+
end
|
|
64
175
|
|
|
65
|
-
prev_block = get_block(hth(blk.prev_block.reverse))
|
|
66
176
|
if !prev_block || prev_block.chain == ORPHAN
|
|
67
177
|
if blk.hash == Bitcoin.network[:genesis_hash]
|
|
68
178
|
log.debug { "=> genesis (0)" }
|
|
@@ -70,24 +180,38 @@ module Bitcoin::Storage
|
|
|
70
180
|
else
|
|
71
181
|
depth = prev_block ? prev_block.depth + 1 : 0
|
|
72
182
|
log.debug { "=> orphan (#{depth})" }
|
|
183
|
+
return [0, 2] unless in_sync?
|
|
73
184
|
return persist_block(blk, ORPHAN, depth)
|
|
74
185
|
end
|
|
75
186
|
end
|
|
76
187
|
depth = prev_block.depth + 1
|
|
188
|
+
|
|
189
|
+
checkpoint = @checkpoints[depth]
|
|
190
|
+
if checkpoint && blk.hash != checkpoint
|
|
191
|
+
log.warn "Block #{depth} doesn't match checkpoint #{checkpoint}"
|
|
192
|
+
exit if depth > get_depth # TODO: handle checkpoint mismatch properly
|
|
193
|
+
end
|
|
77
194
|
if prev_block.chain == MAIN
|
|
78
|
-
|
|
79
|
-
if next_block && next_block.chain == MAIN
|
|
80
|
-
log.debug { "=> side (#{depth})" }
|
|
81
|
-
return persist_block(blk, SIDE, depth)
|
|
82
|
-
else
|
|
195
|
+
if prev_block == get_head
|
|
83
196
|
log.debug { "=> main (#{depth})" }
|
|
84
|
-
|
|
197
|
+
if !@config[:skip_validation] && ( !@checkpoints.any? || depth > @checkpoints.keys.last )
|
|
198
|
+
if self.class.name =~ /UtxoStore/
|
|
199
|
+
@config[:utxo_cache] = 0
|
|
200
|
+
@config[:block_cache] = 120
|
|
201
|
+
end
|
|
202
|
+
validator.validate(rules: [:context], raise_errors: true)
|
|
203
|
+
end
|
|
204
|
+
return persist_block(blk, MAIN, depth, prev_block.work)
|
|
205
|
+
else
|
|
206
|
+
log.debug { "=> side (#{depth})" }
|
|
207
|
+
return persist_block(blk, SIDE, depth, prev_block.work)
|
|
85
208
|
end
|
|
86
209
|
else
|
|
87
210
|
head = get_head
|
|
88
|
-
if prev_block.
|
|
211
|
+
if prev_block.work + blk.block_work <= head.work
|
|
89
212
|
log.debug { "=> side (#{depth})" }
|
|
90
|
-
|
|
213
|
+
validator.validate(rules: [:context], raise_errors: true) unless @config[:skip_validation]
|
|
214
|
+
return persist_block(blk, SIDE, depth, prev_block.work)
|
|
91
215
|
else
|
|
92
216
|
log.debug { "=> reorg" }
|
|
93
217
|
new_main, new_side = [], []
|
|
@@ -102,8 +226,8 @@ module Bitcoin::Storage
|
|
|
102
226
|
end
|
|
103
227
|
log.debug { "new main: #{new_main.inspect}" }
|
|
104
228
|
log.debug { "new side: #{new_side.inspect}" }
|
|
105
|
-
|
|
106
|
-
return persist_block(blk, MAIN, depth)
|
|
229
|
+
reorg(new_side.reverse, new_main.reverse)
|
|
230
|
+
return persist_block(blk, MAIN, depth, prev_block.work)
|
|
107
231
|
end
|
|
108
232
|
end
|
|
109
233
|
end
|
|
@@ -114,13 +238,17 @@ module Bitcoin::Storage
|
|
|
114
238
|
end
|
|
115
239
|
|
|
116
240
|
# update +attrs+ for block with given +hash+.
|
|
117
|
-
# typically used to update chain.
|
|
241
|
+
# typically used to update the chain value during reorg.
|
|
118
242
|
def update_block(hash, attrs)
|
|
119
243
|
raise "Not implemented"
|
|
120
244
|
end
|
|
121
245
|
|
|
246
|
+
def new_tx(tx)
|
|
247
|
+
store_tx(tx)
|
|
248
|
+
end
|
|
249
|
+
|
|
122
250
|
# store given +tx+
|
|
123
|
-
def store_tx(tx)
|
|
251
|
+
def store_tx(tx, validate = true)
|
|
124
252
|
raise "Not implemented"
|
|
125
253
|
end
|
|
126
254
|
|
|
@@ -146,7 +274,14 @@ module Bitcoin::Storage
|
|
|
146
274
|
|
|
147
275
|
# compute blockchain locator
|
|
148
276
|
def get_locator pointer = get_head
|
|
149
|
-
|
|
277
|
+
if @locator
|
|
278
|
+
locator, head = @locator
|
|
279
|
+
if head == get_head
|
|
280
|
+
return locator
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
return [("\x00"*32).hth] if get_depth == -1
|
|
150
285
|
locator = []
|
|
151
286
|
step = 1
|
|
152
287
|
while pointer && pointer.hash != Bitcoin::network[:genesis_hash]
|
|
@@ -159,6 +294,7 @@ module Bitcoin::Storage
|
|
|
159
294
|
step *= 2 if locator.size > 10
|
|
160
295
|
end
|
|
161
296
|
locator << Bitcoin::network[:genesis_hash]
|
|
297
|
+
@locator = [locator, get_head]
|
|
162
298
|
locator
|
|
163
299
|
end
|
|
164
300
|
|
|
@@ -203,6 +339,11 @@ module Bitcoin::Storage
|
|
|
203
339
|
raise "Not implemented"
|
|
204
340
|
end
|
|
205
341
|
|
|
342
|
+
# Grab the position of a tx in a given block
|
|
343
|
+
def get_idx_from_tx_hash(tx_hash)
|
|
344
|
+
raise "Not implemented"
|
|
345
|
+
end
|
|
346
|
+
|
|
206
347
|
# collect all txouts containing the
|
|
207
348
|
# given +script+
|
|
208
349
|
def get_txouts_for_pk_script(script)
|
|
@@ -225,19 +366,83 @@ module Bitcoin::Storage
|
|
|
225
366
|
nil
|
|
226
367
|
end
|
|
227
368
|
|
|
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
|
+
# parse script and collect address/txout mappings to index
|
|
379
|
+
def parse_script txout, i
|
|
380
|
+
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
|
|
383
|
+
script = Bitcoin::Script.new(txout.pk_script) rescue nil
|
|
384
|
+
if script
|
|
385
|
+
if script.is_hash160? || script.is_pubkey?
|
|
386
|
+
addrs << [i, script.get_hash160]
|
|
387
|
+
elsif script.is_multisig?
|
|
388
|
+
script.get_multisig_pubkeys.map do |pubkey|
|
|
389
|
+
addrs << [i, Bitcoin.hash160(pubkey.unpack("H*")[0])]
|
|
390
|
+
end
|
|
391
|
+
elsif Bitcoin.namecoin? && script.is_namecoin?
|
|
392
|
+
addrs << [i, script.get_hash160]
|
|
393
|
+
names << [i, script]
|
|
394
|
+
else
|
|
395
|
+
log.debug { "Unknown script type"}# #{tx.hash}:#{txout_idx}" }
|
|
396
|
+
end
|
|
397
|
+
script_type = SCRIPT_TYPES.index(script.type)
|
|
398
|
+
else
|
|
399
|
+
log.error { "Error parsing script"}# #{tx.hash}:#{txout_idx}" }
|
|
400
|
+
script_type = SCRIPT_TYPES.index(:unknown)
|
|
401
|
+
end
|
|
402
|
+
[script_type, addrs, names]
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def add_watched_address address
|
|
406
|
+
hash160 = Bitcoin.hash160_from_address(address)
|
|
407
|
+
@db[:addr].insert(hash160: hash160) unless @db[:addr][hash160: hash160]
|
|
408
|
+
@watched_addrs << hash160 unless @watched_addrs.include?(hash160)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def rescan
|
|
412
|
+
raise "Not implemented"
|
|
413
|
+
end
|
|
414
|
+
|
|
228
415
|
# import satoshi bitcoind blk0001.dat blockchain file
|
|
229
416
|
def import filename, max_depth = nil
|
|
230
|
-
File.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
417
|
+
if File.file?(filename)
|
|
418
|
+
log.info { "Importing #{filename}" }
|
|
419
|
+
File.open(filename) do |file|
|
|
420
|
+
until file.eof?
|
|
421
|
+
magic = file.read(4)
|
|
422
|
+
raise "invalid network magic" unless Bitcoin.network[:magic_head] == magic
|
|
423
|
+
size = file.read(4).unpack("L")[0]
|
|
424
|
+
blk = Bitcoin::P::Block.new(file.read(size))
|
|
425
|
+
depth, chain = new_block(blk)
|
|
426
|
+
break if max_depth && depth >= max_depth
|
|
427
|
+
end
|
|
238
428
|
end
|
|
429
|
+
elsif File.directory?(filename)
|
|
430
|
+
Dir.entries(filename).sort.each do |file|
|
|
431
|
+
next unless file =~ /^blk.*?\.dat$/
|
|
432
|
+
import(File.join(filename, file), max_depth)
|
|
433
|
+
end
|
|
434
|
+
else
|
|
435
|
+
raise "Import dir/file #{filename} not found"
|
|
239
436
|
end
|
|
240
437
|
end
|
|
438
|
+
|
|
439
|
+
def in_sync?
|
|
440
|
+
(get_head && (Time.now - get_head.time).to_i < 3600) ? true : false
|
|
441
|
+
end
|
|
442
|
+
|
|
241
443
|
end
|
|
242
444
|
end
|
|
243
445
|
end
|
|
446
|
+
|
|
447
|
+
# TODO: someday sequel will support #blob directly and #to_sequel_blob will be gone
|
|
448
|
+
class String; def blob; to_sequel_blob; end; end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
up do
|
|
5
|
+
|
|
6
|
+
$stdout.puts "Running migration #{__FILE__}"
|
|
7
|
+
|
|
8
|
+
binary = adapter_scheme == :postgres ? :bytea : :varchar
|
|
9
|
+
|
|
10
|
+
alter_table :schema_info do
|
|
11
|
+
add_column :magic, :varchar # network magic-head
|
|
12
|
+
add_column :backend, :varchar # storage backend
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
next if tables.include?(:blk)
|
|
16
|
+
|
|
17
|
+
create_table :blk do
|
|
18
|
+
primary_key :id
|
|
19
|
+
column :hash, binary, :null => false, :unique => true, :index => true
|
|
20
|
+
column :depth, :int, :null => false, :index => true
|
|
21
|
+
column :version, :bigint, :null => false
|
|
22
|
+
column :prev_hash, binary, :null => false, :index => true
|
|
23
|
+
column :mrkl_root, binary, :null => false
|
|
24
|
+
column :time, :bigint, :null => false
|
|
25
|
+
column :bits, :bigint, :null => false
|
|
26
|
+
column :nonce, :bigint, :null => false
|
|
27
|
+
column :blk_size, :int, :null => false
|
|
28
|
+
column :chain, :int, :null => false
|
|
29
|
+
column :work, binary, :index => true
|
|
30
|
+
column :aux_pow, binary
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
create_table :addr do
|
|
34
|
+
primary_key :id
|
|
35
|
+
column :hash160, String, :null => false, :index => true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
create_table :addr_txout do
|
|
39
|
+
column :addr_id, :int, :null => false, :index => true
|
|
40
|
+
column :txout_id, :int, :null => false, :index => true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
create_table :names do
|
|
44
|
+
column :txout_id, :int, :null => false, :index => true
|
|
45
|
+
column :hash, binary, :index => true
|
|
46
|
+
column :name, binary, :index => true
|
|
47
|
+
column :value, binary
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
|
|
3
|
+
up do
|
|
4
|
+
|
|
5
|
+
$stdout.puts "Running migration #{__FILE__}"
|
|
6
|
+
|
|
7
|
+
create_table :utxo do
|
|
8
|
+
primary_key :id
|
|
9
|
+
column :tx_hash, String, null: false, index: true
|
|
10
|
+
column :tx_idx, :int, null: false, index: true
|
|
11
|
+
column :blk_id, :int, null: false, index: true
|
|
12
|
+
column :pk_script, (@db.adapter_scheme == :postgres ? :bytea : :blob), null: false
|
|
13
|
+
column :value, :bigint, null: false, index: true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
Bitcoin.require_dependency :sequel, message:
|
|
2
|
+
"Note: You will also need an adapter for your database like sqlite3, mysql2, postgresql"
|
|
3
|
+
|
|
4
|
+
module Bitcoin::Storage::Backends
|
|
5
|
+
|
|
6
|
+
# Storage backend using Sequel to connect to arbitrary SQL databases.
|
|
7
|
+
# Inherits from StoreBase and implements its interface.
|
|
8
|
+
class UtxoStore < StoreBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# possible script types
|
|
12
|
+
SCRIPT_TYPES = [:unknown, :pubkey, :hash160, :multisig, :p2sh]
|
|
13
|
+
if Bitcoin.namecoin?
|
|
14
|
+
[:name_new, :name_firstupdate, :name_update].each {|n| SCRIPT_TYPES << n }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# sequel database connection
|
|
18
|
+
attr_accessor :db
|
|
19
|
+
|
|
20
|
+
DEFAULT_CONFIG = {
|
|
21
|
+
# cache head block; it is only updated when new block comes in,
|
|
22
|
+
# so this should only be used by the store receiving new blocks.
|
|
23
|
+
cache_head: false,
|
|
24
|
+
# cache this many utxo records before syncing to disk.
|
|
25
|
+
# this should only be enabled during initial sync, because
|
|
26
|
+
# with it the store cannot reorg properly.
|
|
27
|
+
utxo_cache: 250,
|
|
28
|
+
# cache this many blocks.
|
|
29
|
+
# NOTE: this is also the maximum number of blocks the store can reorg.
|
|
30
|
+
block_cache: 120,
|
|
31
|
+
# keep an index of utxos for all addresses, not just the ones
|
|
32
|
+
# we are explicitly told about.
|
|
33
|
+
index_all_addrs: false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# create sequel store with given +config+
|
|
37
|
+
def initialize config, *args
|
|
38
|
+
super config, *args
|
|
39
|
+
@spent_outs, @new_outs, @watched_addrs = [], [], []
|
|
40
|
+
@deleted_utxos, @tx_cache, @block_cache = {}, {}, {}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# connect to database
|
|
44
|
+
def connect
|
|
45
|
+
super
|
|
46
|
+
load_watched_addrs
|
|
47
|
+
# rescan
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# reset database; delete all data
|
|
52
|
+
def reset
|
|
53
|
+
[:blk, :utxo, :addr, :addr_txout].each {|table| @db[table].delete }
|
|
54
|
+
@head = nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# persist given block +blk+ to storage.
|
|
58
|
+
def persist_block blk, chain, depth, prev_work = 0
|
|
59
|
+
load_watched_addrs
|
|
60
|
+
@db.transaction do
|
|
61
|
+
attrs = {
|
|
62
|
+
:hash => blk.hash.htb.blob,
|
|
63
|
+
:depth => depth,
|
|
64
|
+
:chain => chain,
|
|
65
|
+
:version => blk.ver,
|
|
66
|
+
:prev_hash => blk.prev_block.reverse.blob,
|
|
67
|
+
:mrkl_root => blk.mrkl_root.reverse.blob,
|
|
68
|
+
:time => blk.time,
|
|
69
|
+
:bits => blk.bits,
|
|
70
|
+
:nonce => blk.nonce,
|
|
71
|
+
:blk_size => blk.payload.bytesize,
|
|
72
|
+
:work => (prev_work + blk.block_work).to_s
|
|
73
|
+
}
|
|
74
|
+
existing = @db[:blk].filter(:hash => blk.hash.htb.blob)
|
|
75
|
+
if existing.any?
|
|
76
|
+
existing.update attrs
|
|
77
|
+
block_id = existing.first[:id]
|
|
78
|
+
else
|
|
79
|
+
block_id = @db[:blk].insert(attrs)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if @config[:block_cache] > 0
|
|
83
|
+
@block_cache.shift if @block_cache.size > @config[:block_cache]
|
|
84
|
+
@deleted_utxos.shift if @deleted_utxos.size > @config[:block_cache]
|
|
85
|
+
@block_cache[blk.hash] = blk
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if chain == MAIN
|
|
89
|
+
persist_transactions(blk.tx, block_id, depth)
|
|
90
|
+
@tx_cache = {}
|
|
91
|
+
@head = wrap_block(attrs.merge(id: block_id)) if chain == MAIN
|
|
92
|
+
end
|
|
93
|
+
return depth, chain
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def persist_transactions txs, block_id, depth
|
|
98
|
+
txs.each.with_index do |tx, tx_blk_idx|
|
|
99
|
+
tx.in.each.with_index do |txin, txin_tx_idx|
|
|
100
|
+
next if txin.coinbase?
|
|
101
|
+
size = @new_outs.size
|
|
102
|
+
@new_outs.delete_if {|o| o[0][:tx_hash] == txin.prev_out.reverse.hth &&
|
|
103
|
+
o[0][:tx_idx] == txin.prev_out_index }
|
|
104
|
+
@spent_outs << {
|
|
105
|
+
tx_hash: txin.prev_out.reverse.hth.to_sequel_blob,
|
|
106
|
+
tx_idx: txin.prev_out_index } if @new_outs.size == size
|
|
107
|
+
end
|
|
108
|
+
tx.out.each.with_index do |txout, txout_tx_idx|
|
|
109
|
+
_, a, n = *parse_script(txout, txout_tx_idx)
|
|
110
|
+
@new_outs << [{
|
|
111
|
+
:tx_hash => tx.hash.blob,
|
|
112
|
+
:tx_idx => txout_tx_idx,
|
|
113
|
+
:blk_id => block_id,
|
|
114
|
+
:pk_script => txout.pk_script.blob,
|
|
115
|
+
:value => txout.value },
|
|
116
|
+
@config[:index_all_addrs] ? a : a.select {|a| @watched_addrs.include?(a[1]) },
|
|
117
|
+
Bitcoin.namecoin? ? n : [] ]
|
|
118
|
+
end
|
|
119
|
+
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
|
+
end
|
|
123
|
+
|
|
124
|
+
def reorg new_side, new_main
|
|
125
|
+
new_side.each do |block_hash|
|
|
126
|
+
raise "trying to remove non-head block!" unless get_head.hash == block_hash
|
|
127
|
+
depth = get_depth
|
|
128
|
+
blk = @db[:blk][hash: block_hash.htb.blob]
|
|
129
|
+
delete_utxos = @db[:utxo].where(blk_id: blk[:id])
|
|
130
|
+
@db[:addr_txout].where("txout_id IN ?", delete_utxos.map{|o|o[:id]}).delete
|
|
131
|
+
|
|
132
|
+
delete_utxos.delete
|
|
133
|
+
(@deleted_utxos[depth] || []).each do |utxo|
|
|
134
|
+
utxo[:pk_script] = utxo[:pk_script].to_sequel_blob
|
|
135
|
+
utxo_id = @db[:utxo].insert(utxo)
|
|
136
|
+
addrs = Bitcoin::Script.new(utxo[:pk_script]).get_addresses
|
|
137
|
+
addrs.each do |addr|
|
|
138
|
+
hash160 = Bitcoin.hash160_from_address(addr)
|
|
139
|
+
store_addr(utxo_id, hash160)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
@db[:blk].where(id: blk[:id]).update(chain: SIDE)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
new_main.each do |block_hash|
|
|
147
|
+
block = @db[:blk][hash: block_hash.htb.blob]
|
|
148
|
+
blk = @block_cache[block_hash]
|
|
149
|
+
persist_transactions(blk.tx, block[:id], block[:depth])
|
|
150
|
+
@db[:blk].where(id: block[:id]).update(chain: MAIN)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def flush_spent_outs depth
|
|
155
|
+
log.time "flushed #{@spent_outs.size} spent txouts in %.4fs" do
|
|
156
|
+
if @spent_outs.any?
|
|
157
|
+
@spent_outs.each_slice(250) do |slice|
|
|
158
|
+
if @db.adapter_scheme == :postgres
|
|
159
|
+
condition = slice.map {|o| "(tx_hash = '#{o[:tx_hash]}' AND tx_idx = #{o[:tx_idx]})" }.join(" OR ")
|
|
160
|
+
else
|
|
161
|
+
condition = slice.map {|o| "(tx_hash = X'#{o[:tx_hash].hth}' AND tx_idx = #{o[:tx_idx]})" }.join(" OR ")
|
|
162
|
+
end
|
|
163
|
+
@db["DELETE FROM addr_txout WHERE EXISTS
|
|
164
|
+
(SELECT 1 FROM utxo WHERE
|
|
165
|
+
utxo.id = addr_txout.txout_id AND (#{condition}));"].all
|
|
166
|
+
@db["DELETE FROM utxo WHERE #{condition};"].first
|
|
167
|
+
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
@spent_outs = []
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def flush_new_outs depth
|
|
175
|
+
log.time "flushed #{@new_outs.size} new txouts in %.4fs" do
|
|
176
|
+
new_utxo_ids = @db[:utxo].insert_multiple(@new_outs.map{|o|o[0]})
|
|
177
|
+
@new_outs.each.with_index do |d, idx|
|
|
178
|
+
d[1].each do |i, hash160|
|
|
179
|
+
next unless i && hash160
|
|
180
|
+
store_addr(new_utxo_ids[idx], hash160)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
@new_outs.each.with_index do |d, idx|
|
|
185
|
+
d[2].each do |i, script|
|
|
186
|
+
next unless i && script
|
|
187
|
+
store_name(script, new_utxo_ids[idx])
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
@new_outs = []
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def add_watched_address address
|
|
195
|
+
hash160 = Bitcoin.hash160_from_address(address)
|
|
196
|
+
@db[:addr].insert(hash160: hash160) unless @db[:addr][hash160: hash160]
|
|
197
|
+
@watched_addrs << hash160 unless @watched_addrs.include?(hash160)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def load_watched_addrs
|
|
201
|
+
@watched_addrs = @db[:addr].all.map{|a| a[:hash160] } unless @config[:index_all_addrs]
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def rescan
|
|
205
|
+
load_watched_addrs
|
|
206
|
+
@rescan_lock ||= Monitor.new
|
|
207
|
+
@rescan_lock.synchronize do
|
|
208
|
+
log.info { "Rescanning #{@db[:utxo].count} utxos for #{@watched_addrs.size} addrs" }
|
|
209
|
+
count = @db[:utxo].count; n = 100_000
|
|
210
|
+
@db[:utxo].order(:id).each_slice(n).with_index do |slice, index|
|
|
211
|
+
log.debug { "rescan progress: %.2f%" % (100.0 / count * (index*n)) }
|
|
212
|
+
slice.each do |utxo|
|
|
213
|
+
next if utxo[:pk_script].bytesize >= 10_000
|
|
214
|
+
hash160 = Bitcoin::Script.new(utxo[:pk_script]).get_hash160
|
|
215
|
+
if @config[:index_all_addrs] || @watched_addrs.include?(hash160)
|
|
216
|
+
log.info { "Found utxo for address #{Bitcoin.hash160_to_address(hash160)}: " +
|
|
217
|
+
"#{utxo[:tx_hash][0..8]}:#{utxo[:tx_idx]} (#{utxo[:value]})" }
|
|
218
|
+
addr = @db[:addr][hash160: hash160]
|
|
219
|
+
addr_utxo = {addr_id: addr[:id], txout_id: utxo[:id]}
|
|
220
|
+
@db[:addr_txout].insert(addr_utxo) unless @db[:addr_txout][addr_utxo]
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# check if block +blk_hash+ exists
|
|
228
|
+
def has_block(blk_hash)
|
|
229
|
+
!!@db[:blk].where(:hash => blk_hash.htb.blob).get(1)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# check if transaction +tx_hash+ exists
|
|
233
|
+
def has_tx(tx_hash)
|
|
234
|
+
!!@db[:utxo].where(:tx_hash => tx_hash.blob).get(1)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# get head block (highest block from the MAIN chain)
|
|
238
|
+
def get_head
|
|
239
|
+
(@config[:cache_head] && @head) ? @head :
|
|
240
|
+
@head = wrap_block(@db[:blk].filter(:chain => MAIN).order(:depth).last)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# get depth of MAIN chain
|
|
244
|
+
def get_depth
|
|
245
|
+
return -1 unless get_head
|
|
246
|
+
get_head.depth
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# get block for given +blk_hash+
|
|
250
|
+
def get_block(blk_hash)
|
|
251
|
+
wrap_block(@db[:blk][:hash => blk_hash.htb.blob])
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# get block by given +depth+
|
|
255
|
+
def get_block_by_depth(depth)
|
|
256
|
+
wrap_block(@db[:blk][:depth => depth, :chain => MAIN])
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# get block by given +prev_hash+
|
|
260
|
+
def get_block_by_prev_hash(prev_hash)
|
|
261
|
+
wrap_block(@db[:blk][:prev_hash => prev_hash.htb.blob, :chain => MAIN])
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# get block by given +tx_hash+
|
|
265
|
+
def get_block_by_tx(tx_hash)
|
|
266
|
+
block_id = @db[:utxo][tx_hash: tx_hash.blob][:blk_id]
|
|
267
|
+
get_block_by_id(block_id)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# get block by given +id+
|
|
271
|
+
def get_block_by_id(block_id)
|
|
272
|
+
wrap_block(@db[:blk][:id => block_id])
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# get transaction for given +tx_hash+
|
|
276
|
+
def get_tx(tx_hash)
|
|
277
|
+
@tx_cache[tx_hash] ||= wrap_tx(tx_hash)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# get transaction by given +tx_id+
|
|
281
|
+
def get_tx_by_id(tx_id)
|
|
282
|
+
get_tx(tx_id)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def get_txout_by_id(id)
|
|
286
|
+
wrap_txout(@db[:utxo][id: id])
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# get corresponding Models::TxOut for +txin+
|
|
290
|
+
def get_txout_for_txin(txin)
|
|
291
|
+
wrap_txout(@db[:utxo][tx_hash: txin.prev_out.reverse.hth.blob, tx_idx: txin.prev_out_index])
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# get all Models::TxOut matching given +script+
|
|
295
|
+
def get_txouts_for_pk_script(script)
|
|
296
|
+
utxos = @db[:utxo].filter(pk_script: script.blob).order(:blk_id)
|
|
297
|
+
utxos.map {|utxo| wrap_txout(utxo) }
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# get all Models::TxOut matching given +hash160+
|
|
301
|
+
def get_txouts_for_hash160(hash160, unconfirmed = false)
|
|
302
|
+
addr = @db[:addr][hash160: hash160]
|
|
303
|
+
return [] unless addr
|
|
304
|
+
@db[:addr_txout].where(addr_id: addr[:id]).map {|ao| wrap_txout(@db[:utxo][id: ao[:txout_id]]) }.compact
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def get_balance hash160
|
|
308
|
+
get_txouts_for_hash160(hash160).map(&:value).inject(:+) || 0
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# wrap given +block+ into Models::Block
|
|
312
|
+
def wrap_block(block)
|
|
313
|
+
return nil unless block
|
|
314
|
+
|
|
315
|
+
data = {:id => block[:id], :depth => block[:depth], :chain => block[:chain],
|
|
316
|
+
:work => block[:work].to_i, :hash => block[:hash].hth}
|
|
317
|
+
blk = Bitcoin::Storage::Models::Block.new(self, data)
|
|
318
|
+
|
|
319
|
+
blk.ver = block[:version]
|
|
320
|
+
blk.prev_block = block[:prev_hash].reverse
|
|
321
|
+
blk.mrkl_root = block[:mrkl_root].reverse
|
|
322
|
+
blk.time = block[:time].to_i
|
|
323
|
+
blk.bits = block[:bits]
|
|
324
|
+
blk.nonce = block[:nonce]
|
|
325
|
+
|
|
326
|
+
if cached = @block_cache[block[:hash].hth]
|
|
327
|
+
blk.tx = cached.tx
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
blk.recalc_block_hash
|
|
331
|
+
blk
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# wrap given +transaction+ into Models::Transaction
|
|
335
|
+
def wrap_tx(tx_hash)
|
|
336
|
+
utxos = @db[:utxo].where(tx_hash: tx_hash.blob)
|
|
337
|
+
return nil unless utxos.any?
|
|
338
|
+
data = { blk_id: utxos.first[:blk_id] }
|
|
339
|
+
tx = Bitcoin::Storage::Models::Tx.new(self, data)
|
|
340
|
+
tx.hash = tx_hash # utxos.first[:tx_hash].hth
|
|
341
|
+
utxos.each {|u| tx.out[u[:tx_idx]] = wrap_txout(u) }
|
|
342
|
+
return tx
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# wrap given +output+ into Models::TxOut
|
|
346
|
+
def wrap_txout(utxo)
|
|
347
|
+
return nil unless utxo
|
|
348
|
+
data = {id: utxo[:id], tx_id: utxo[:tx_hash], tx_idx: utxo[:tx_idx]}
|
|
349
|
+
txout = Bitcoin::Storage::Models::TxOut.new(self, data)
|
|
350
|
+
txout.value = utxo[:value]
|
|
351
|
+
txout.pk_script = utxo[:pk_script]
|
|
352
|
+
txout
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def check_consistency(*args)
|
|
356
|
+
log.warn { "Utxo store doesn't support consistency check" }
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
end
|