bitcoin-ruby 0.0.6 → 0.0.7
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 +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +2 -7
- data/COPYING +1 -1
- data/Gemfile +2 -6
- data/Gemfile.lock +34 -0
- data/README.rdoc +16 -68
- data/Rakefile +3 -6
- data/bin/bitcoin_shell +0 -1
- data/{concept-examples/blockchain-pow.rb → examples/concept-blockchain-pow.rb} +0 -0
- data/lib/bitcoin.rb +350 -296
- data/lib/bitcoin/builder.rb +3 -1
- data/lib/bitcoin/connection.rb +2 -1
- data/lib/bitcoin/contracthash.rb +76 -0
- data/lib/bitcoin/dogecoin.rb +97 -0
- data/lib/bitcoin/ffi/bitcoinconsensus.rb +74 -0
- data/lib/bitcoin/ffi/openssl.rb +98 -2
- data/lib/bitcoin/ffi/secp256k1.rb +144 -0
- data/lib/bitcoin/key.rb +12 -2
- data/lib/bitcoin/logger.rb +3 -12
- data/lib/bitcoin/protocol/block.rb +3 -9
- data/lib/bitcoin/protocol/parser.rb +6 -2
- data/lib/bitcoin/protocol/tx.rb +44 -13
- data/lib/bitcoin/protocol/txin.rb +4 -2
- data/lib/bitcoin/protocol/txout.rb +2 -2
- data/lib/bitcoin/script.rb +212 -37
- data/lib/bitcoin/trezor/mnemonic.rb +130 -0
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +32 -3
- data/spec/bitcoin/builder_spec.rb +18 -0
- data/spec/bitcoin/contracthash_spec.rb +45 -0
- data/spec/bitcoin/dogecoin_spec.rb +176 -0
- data/spec/bitcoin/ffi_openssl.rb +45 -0
- data/spec/bitcoin/fixtures/156e6e1b84c5c3bd3a0927b25e4119fadce6e6d5186f363317511d1d680fae9a.json +24 -0
- data/spec/bitcoin/fixtures/8d0b238a06b5a70be75d543902d02d7a514d68d3252a949a513865ac3538874c.json +24 -0
- data/spec/bitcoin/fixtures/coinbase-toshi.json +33 -0
- data/spec/bitcoin/fixtures/coinbase.json +24 -0
- data/spec/bitcoin/fixtures/dogecoin-block-60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01-toshi.json +46 -0
- data/spec/bitcoin/fixtures/rawtx-02-toshi.json +46 -0
- data/spec/bitcoin/fixtures/rawtx-03-toshi.json +73 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-04fdc38d6722ab4b12d79113fc4b2896bdcc5169710690ee4e78541b98e467b4.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-0b294c7d11dd21bcccb8393e6744fed7d4d1981a08c00e3e88838cc421f33c9f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-3bc52ac063291ad92d95ddda5fd776a342083b95607ad32ed8bc6f8f7d30449e.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-6f0bbdd4e71a8af4305018d738184df32dbb6f27284fdebd5b56d16947f7c181.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a7c9b06e275e8674cc19a5f7d3e557c72c6d93576e635b33212dbe08ab7cdb60.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-f80acbd2f594d04ddb0e1cacba662132104909157dff526935a3c88abe9201a5.bin +0 -0
- data/spec/bitcoin/protocol/block_spec.rb +0 -22
- data/spec/bitcoin/protocol/tx_spec.rb +145 -2
- data/spec/bitcoin/script/script_spec.rb +282 -0
- data/spec/bitcoin/secp256k1_spec.rb +48 -0
- data/spec/bitcoin/spec_helper.rb +0 -51
- data/spec/bitcoin/trezor/mnemonic_spec.rb +161 -0
- metadata +48 -98
- data/bin/bitcoin_dns_seed +0 -130
- data/bin/bitcoin_gui +0 -80
- data/bin/bitcoin_node +0 -153
- data/bin/bitcoin_node_cli +0 -81
- data/bin/bitcoin_wallet +0 -402
- data/doc/CONFIG.rdoc +0 -66
- data/doc/EXAMPLES.rdoc +0 -13
- data/doc/NAMECOIN.rdoc +0 -34
- data/doc/NODE.rdoc +0 -225
- data/doc/STORAGE.rdoc +0 -33
- data/doc/WALLET.rdoc +0 -102
- data/examples/balance.rb +0 -66
- data/examples/forwarder.rb +0 -73
- data/examples/index_nhash.rb +0 -24
- data/examples/reindex_p2sh_addrs.rb +0 -44
- data/examples/relay_tx.rb +0 -22
- data/examples/verify_tx.rb +0 -57
- data/lib/bitcoin/config.rb +0 -58
- data/lib/bitcoin/gui/addr_view.rb +0 -44
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +0 -80
- data/lib/bitcoin/gui/conn_view.rb +0 -38
- data/lib/bitcoin/gui/connection.rb +0 -70
- data/lib/bitcoin/gui/em_gtk.rb +0 -30
- data/lib/bitcoin/gui/gui.builder +0 -1643
- data/lib/bitcoin/gui/gui.rb +0 -292
- data/lib/bitcoin/gui/helpers.rb +0 -115
- data/lib/bitcoin/gui/tree_view.rb +0 -84
- data/lib/bitcoin/gui/tx_view.rb +0 -69
- data/lib/bitcoin/namecoin.rb +0 -280
- data/lib/bitcoin/network/command_client.rb +0 -104
- data/lib/bitcoin/network/command_handler.rb +0 -570
- data/lib/bitcoin/network/connection_handler.rb +0 -387
- data/lib/bitcoin/network/node.rb +0 -565
- data/lib/bitcoin/storage/dummy/dummy_store.rb +0 -179
- data/lib/bitcoin/storage/models.rb +0 -171
- data/lib/bitcoin/storage/sequel/migrations.rb +0 -99
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +0 -52
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +0 -45
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +0 -18
- data/lib/bitcoin/storage/sequel/migrations/004_change_txin_prev_out_to_blob.rb +0 -18
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +0 -14
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +0 -31
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +0 -16
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +0 -31
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +0 -56
- data/lib/bitcoin/storage/sequel/sequel_store.rb +0 -551
- data/lib/bitcoin/storage/storage.rb +0 -517
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +0 -52
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +0 -18
- data/lib/bitcoin/storage/utxo/migrations/003_update_indices.rb +0 -14
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +0 -14
- data/lib/bitcoin/storage/utxo/utxo_store.rb +0 -374
- data/lib/bitcoin/validation.rb +0 -400
- data/lib/bitcoin/wallet/coinselector.rb +0 -33
- data/lib/bitcoin/wallet/keygenerator.rb +0 -77
- data/lib/bitcoin/wallet/keystore.rb +0 -207
- data/lib/bitcoin/wallet/txdp.rb +0 -118
- data/lib/bitcoin/wallet/wallet.rb +0 -281
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +0 -43
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +0 -67
- data/spec/bitcoin/namecoin_spec.rb +0 -182
- data/spec/bitcoin/node/command_api_spec.rb +0 -663
- data/spec/bitcoin/storage/models_spec.rb +0 -104
- data/spec/bitcoin/storage/reorg_spec.rb +0 -236
- data/spec/bitcoin/storage/storage_spec.rb +0 -387
- data/spec/bitcoin/storage/validation_spec.rb +0 -300
- data/spec/bitcoin/wallet/coinselector_spec.rb +0 -38
- data/spec/bitcoin/wallet/keygenerator_spec.rb +0 -69
- data/spec/bitcoin/wallet/keystore_spec.rb +0 -190
- data/spec/bitcoin/wallet/txdp_spec.rb +0 -76
- data/spec/bitcoin/wallet/wallet_spec.rb +0 -238
|
@@ -1,517 +0,0 @@
|
|
|
1
|
-
# encoding: ascii-8bit
|
|
2
|
-
|
|
3
|
-
# The storage implementation supports different backends, which inherit from
|
|
4
|
-
# Storage::StoreBase and implement the same interface.
|
|
5
|
-
# Each backend returns Storage::Models objects to easily access helper methods and metadata.
|
|
6
|
-
#
|
|
7
|
-
# The most stable backend is Backends::SequelStore, which uses sequel and can use all
|
|
8
|
-
# kinds of SQL database backends.
|
|
9
|
-
module Bitcoin::Storage
|
|
10
|
-
|
|
11
|
-
autoload :Models, 'bitcoin/storage/models'
|
|
12
|
-
|
|
13
|
-
@log = Bitcoin::Logger.create(:storage)
|
|
14
|
-
def self.log; @log; end
|
|
15
|
-
|
|
16
|
-
BACKENDS = [:dummy, :sequel, :utxo]
|
|
17
|
-
BACKENDS.each do |name|
|
|
18
|
-
module_eval <<-EOS
|
|
19
|
-
def self.#{name} config, *args
|
|
20
|
-
Backends.const_get("#{name.capitalize}Store").new(config, *args)
|
|
21
|
-
end
|
|
22
|
-
EOS
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
module Backends
|
|
26
|
-
|
|
27
|
-
BACKENDS.each {|b| autoload("#{b.to_s.capitalize}Store", "bitcoin/storage/#{b}/#{b}_store.rb") }
|
|
28
|
-
|
|
29
|
-
# Base class for storage backends.
|
|
30
|
-
# Every backend must overwrite the "Not implemented" methods
|
|
31
|
-
# and provide an implementation specific to the storage.
|
|
32
|
-
# Also, before returning the objects, they should be wrapped
|
|
33
|
-
# inside the appropriate Bitcoin::Storage::Models class.
|
|
34
|
-
class StoreBase
|
|
35
|
-
|
|
36
|
-
# main branch (longest valid chain)
|
|
37
|
-
MAIN = 0
|
|
38
|
-
|
|
39
|
-
# side branch (connected, valid, but too short)
|
|
40
|
-
SIDE = 1
|
|
41
|
-
|
|
42
|
-
# orphan branch (not connected to main branch / genesis block)
|
|
43
|
-
ORPHAN = 2
|
|
44
|
-
|
|
45
|
-
# possible script types
|
|
46
|
-
SCRIPT_TYPES = [:unknown, :pubkey, :hash160, :multisig, :p2sh, :op_return]
|
|
47
|
-
if Bitcoin.namecoin?
|
|
48
|
-
[:name_new, :name_firstupdate, :name_update].each {|n| SCRIPT_TYPES << n }
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# possible address types
|
|
52
|
-
ADDRESS_TYPES = [:hash160, :p2sh]
|
|
53
|
-
|
|
54
|
-
DEFAULT_CONFIG = {}
|
|
55
|
-
|
|
56
|
-
attr_reader :log
|
|
57
|
-
|
|
58
|
-
attr_accessor :config
|
|
59
|
-
|
|
60
|
-
def initialize(config = {}, getblocks_callback = nil)
|
|
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
|
|
66
|
-
@config = base.merge(self.class::DEFAULT_CONFIG).merge(config)
|
|
67
|
-
@log = config[:log] || Bitcoin::Storage.log
|
|
68
|
-
@log.level = @config[:log_level] if @config[:log_level]
|
|
69
|
-
init_store_connection
|
|
70
|
-
@getblocks_callback = getblocks_callback
|
|
71
|
-
@checkpoints = Bitcoin.network[:checkpoints] || {}
|
|
72
|
-
@watched_addrs = []
|
|
73
|
-
@notifiers = {}
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def init_store_connection
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# name of the storage backend currently in use ("sequel" or "utxo")
|
|
80
|
-
def backend_name
|
|
81
|
-
self.class.name.split("::")[-1].split("Store")[0].downcase
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# reset the store; delete all data
|
|
85
|
-
def reset
|
|
86
|
-
raise "Not implemented"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# check data consistency of the top +count+ blocks.
|
|
90
|
-
def check_consistency count
|
|
91
|
-
raise "Not implemented"
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# handle a new block incoming from the network
|
|
96
|
-
def new_block blk
|
|
97
|
-
time = Time.now
|
|
98
|
-
res = store_block(blk)
|
|
99
|
-
log.info { "block #{blk.hash} " +
|
|
100
|
-
"[#{res[0]}, #{['main', 'side', 'orphan'][res[1]]}] " +
|
|
101
|
-
"(#{"%.4fs, %3dtx, %.3fkb" % [(Time.now - time), blk.tx.size, blk.payload.bytesize.to_f/1000]})" } if res && res[1]
|
|
102
|
-
res
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# store given block +blk+.
|
|
106
|
-
# determine branch/chain and dept of block. trigger reorg if side branch becomes longer
|
|
107
|
-
# than current main chain and connect orpans.
|
|
108
|
-
def store_block blk
|
|
109
|
-
log.debug { "new block #{blk.hash}" }
|
|
110
|
-
|
|
111
|
-
existing = get_block(blk.hash)
|
|
112
|
-
if existing && existing.chain == MAIN
|
|
113
|
-
log.debug { "=> exists (#{existing.depth}, #{existing.chain})" }
|
|
114
|
-
return [existing.depth]
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
prev_block = get_block(blk.prev_block.reverse_hth)
|
|
118
|
-
unless @config[:skip_validation]
|
|
119
|
-
validator = blk.validator(self, prev_block)
|
|
120
|
-
validator.validate(rules: [:syntax], raise_errors: true)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
if !prev_block || prev_block.chain == ORPHAN
|
|
124
|
-
if blk.hash == Bitcoin.network[:genesis_hash]
|
|
125
|
-
log.debug { "=> genesis (0)" }
|
|
126
|
-
return persist_block(blk, MAIN, 0)
|
|
127
|
-
else
|
|
128
|
-
depth = prev_block ? prev_block.depth + 1 : 0
|
|
129
|
-
log.debug { "=> orphan (#{depth})" }
|
|
130
|
-
return [0, 2] unless (in_sync? || Bitcoin.network_name =~ /testnet/)
|
|
131
|
-
return persist_block(blk, ORPHAN, depth)
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
depth = prev_block.depth + 1
|
|
135
|
-
|
|
136
|
-
checkpoint = @checkpoints[depth]
|
|
137
|
-
if checkpoint && blk.hash != checkpoint
|
|
138
|
-
log.warn "Block #{depth} doesn't match checkpoint #{checkpoint}"
|
|
139
|
-
exit if depth > get_depth # TODO: handle checkpoint mismatch properly
|
|
140
|
-
end
|
|
141
|
-
if prev_block.chain == MAIN
|
|
142
|
-
if prev_block == get_head
|
|
143
|
-
log.debug { "=> main (#{depth})" }
|
|
144
|
-
if !@config[:skip_validation] && ( !@checkpoints.any? || depth > @checkpoints.keys.last )
|
|
145
|
-
if self.class.name =~ /UtxoStore/
|
|
146
|
-
@config[:utxo_cache] = 0
|
|
147
|
-
@config[:block_cache] = 120
|
|
148
|
-
end
|
|
149
|
-
validator.validate(rules: [:context], raise_errors: true)
|
|
150
|
-
end
|
|
151
|
-
res = persist_block(blk, MAIN, depth, prev_block.work)
|
|
152
|
-
push_notification(:block, [blk, *res])
|
|
153
|
-
return res
|
|
154
|
-
else
|
|
155
|
-
log.debug { "=> side (#{depth})" }
|
|
156
|
-
return persist_block(blk, SIDE, depth, prev_block.work)
|
|
157
|
-
end
|
|
158
|
-
else
|
|
159
|
-
head = get_head
|
|
160
|
-
if prev_block.work + blk.block_work <= head.work
|
|
161
|
-
log.debug { "=> side (#{depth})" }
|
|
162
|
-
return persist_block(blk, SIDE, depth, prev_block.work)
|
|
163
|
-
else
|
|
164
|
-
log.debug { "=> reorg" }
|
|
165
|
-
new_main, new_side = [], []
|
|
166
|
-
fork_block = prev_block
|
|
167
|
-
while fork_block.chain != MAIN
|
|
168
|
-
new_main << fork_block.hash
|
|
169
|
-
fork_block = fork_block.get_prev_block
|
|
170
|
-
end
|
|
171
|
-
b = fork_block
|
|
172
|
-
while b = b.get_next_block
|
|
173
|
-
new_side << b.hash
|
|
174
|
-
end
|
|
175
|
-
log.debug { "new main: #{new_main.inspect}" }
|
|
176
|
-
log.debug { "new side: #{new_side.inspect}" }
|
|
177
|
-
|
|
178
|
-
push_notification(:reorg, [ new_main, new_side ])
|
|
179
|
-
|
|
180
|
-
reorg(new_side.reverse, new_main.reverse)
|
|
181
|
-
return persist_block(blk, MAIN, depth, prev_block.work)
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# persist given block +blk+ to storage.
|
|
187
|
-
def persist_block(blk)
|
|
188
|
-
raise "Not implemented"
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# update +attrs+ for block with given +hash+.
|
|
192
|
-
# typically used to update the chain value during reorg.
|
|
193
|
-
def update_block(hash, attrs)
|
|
194
|
-
raise "Not implemented"
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def new_tx(tx)
|
|
198
|
-
store_tx(tx)
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# store given +tx+
|
|
202
|
-
def store_tx(tx, validate = true)
|
|
203
|
-
raise "Not implemented"
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# check if block with given +blk_hash+ is already stored
|
|
207
|
-
def has_block(blk_hash)
|
|
208
|
-
raise "Not implemented"
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# check if tx with given +tx_hash+ is already stored
|
|
212
|
-
def has_tx(tx_hash)
|
|
213
|
-
raise "Not implemented"
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
# get the hash of the leading block
|
|
217
|
-
def get_head
|
|
218
|
-
raise "Not implemented"
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
# return depth of the head block
|
|
222
|
-
def get_depth
|
|
223
|
-
raise "Not implemented"
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# compute blockchain locator
|
|
227
|
-
def get_locator pointer = get_head
|
|
228
|
-
if @locator
|
|
229
|
-
locator, head = @locator
|
|
230
|
-
if head == pointer
|
|
231
|
-
return locator
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
return [("\x00"*32).hth] if get_depth == -1
|
|
236
|
-
locator, step, orig_pointer = [], 1, pointer
|
|
237
|
-
while pointer && pointer.hash != Bitcoin::network[:genesis_hash]
|
|
238
|
-
locator << pointer.hash
|
|
239
|
-
depth = pointer.depth - step
|
|
240
|
-
break unless depth > 0
|
|
241
|
-
prev_block = get_block_by_depth(depth) # TODO
|
|
242
|
-
break unless prev_block
|
|
243
|
-
pointer = prev_block
|
|
244
|
-
step *= 2 if locator.size > 10
|
|
245
|
-
end
|
|
246
|
-
locator << Bitcoin::network[:genesis_hash]
|
|
247
|
-
@locator = [locator, orig_pointer]
|
|
248
|
-
locator
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
# get block with given +blk_hash+
|
|
252
|
-
def get_block(blk_hash)
|
|
253
|
-
raise "Not implemented"
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# get block with given +depth+ from main chain
|
|
257
|
-
def get_block_by_depth(depth)
|
|
258
|
-
raise "Not implemented"
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# get block with given +prev_hash+
|
|
262
|
-
def get_block_by_prev_hash(prev_hash)
|
|
263
|
-
raise "Not implemented"
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# get block that includes tx with given +tx_hash+
|
|
267
|
-
def get_block_by_tx(tx_hash)
|
|
268
|
-
raise "Not implemented"
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# get block by given +block_id+
|
|
272
|
-
def get_block_by_id(block_id)
|
|
273
|
-
raise "Not implemented"
|
|
274
|
-
end
|
|
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
|
-
|
|
281
|
-
# get corresponding txin for the txout in
|
|
282
|
-
# transaction +tx_hash+ with index +txout_idx+
|
|
283
|
-
def get_txin_for_txout(tx_hash, txout_idx)
|
|
284
|
-
raise "Not implemented"
|
|
285
|
-
end
|
|
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
|
-
|
|
294
|
-
# get tx with given +tx_hash+
|
|
295
|
-
def get_tx(tx_hash)
|
|
296
|
-
raise "Not implemented"
|
|
297
|
-
end
|
|
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
|
-
|
|
305
|
-
# get tx with given +tx_id+
|
|
306
|
-
def get_tx_by_id(tx_id)
|
|
307
|
-
raise "Not implemented"
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
# Grab the position of a tx in a given block
|
|
311
|
-
def get_idx_from_tx_hash(tx_hash)
|
|
312
|
-
raise "Not implemented"
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
# collect all txouts containing the
|
|
316
|
-
# given +script+
|
|
317
|
-
def get_txouts_for_pk_script(script)
|
|
318
|
-
raise "Not implemented"
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
# collect all txouts containing a
|
|
322
|
-
# standard tx to given +address+
|
|
323
|
-
def get_txouts_for_address(address, unconfirmed = false)
|
|
324
|
-
hash160 = Bitcoin.hash160_from_address(address)
|
|
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
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
# get balance for given +hash160+
|
|
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
|
|
346
|
-
unspent = txouts.select {|o| o.get_next_in.nil?}
|
|
347
|
-
unspent.map(&:value).inject {|a,b| a+=b; a} || 0
|
|
348
|
-
rescue
|
|
349
|
-
nil
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
# parse script and collect address/txout mappings to index
|
|
353
|
-
def parse_script txout, i, tx_hash = "", tx_idx
|
|
354
|
-
addrs, names = [], []
|
|
355
|
-
|
|
356
|
-
script = Bitcoin::Script.new(txout.pk_script) rescue nil
|
|
357
|
-
if script
|
|
358
|
-
if script.is_hash160? || script.is_pubkey? || script.is_p2sh?
|
|
359
|
-
addrs << [i, script.get_address]
|
|
360
|
-
elsif script.is_multisig?
|
|
361
|
-
script.get_multisig_addresses.map do |address|
|
|
362
|
-
addrs << [i, address]
|
|
363
|
-
end
|
|
364
|
-
elsif Bitcoin.namecoin? && script.is_namecoin?
|
|
365
|
-
addrs << [i, script.get_address]
|
|
366
|
-
names << [i, script]
|
|
367
|
-
elsif script.is_op_return?
|
|
368
|
-
log.info { "Ignoring OP_RETURN script: #{script.get_op_return_data}" }
|
|
369
|
-
else
|
|
370
|
-
log.info { "Unknown script type in txout #{tx_hash}:#{tx_idx}" }
|
|
371
|
-
log.debug { script.to_string }
|
|
372
|
-
end
|
|
373
|
-
script_type = SCRIPT_TYPES.index(script.type)
|
|
374
|
-
else
|
|
375
|
-
log.error { "Error parsing script #{tx_hash}:#{tx_idx}" }
|
|
376
|
-
script_type = SCRIPT_TYPES.index(:unknown)
|
|
377
|
-
end
|
|
378
|
-
[script_type, addrs, names]
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
def add_watched_address address
|
|
382
|
-
hash160 = Bitcoin.hash160_from_address(address)
|
|
383
|
-
@db[:addr].insert(hash160: hash160) unless @db[:addr][hash160: hash160]
|
|
384
|
-
@watched_addrs << hash160 unless @watched_addrs.include?(hash160)
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
def rescan
|
|
388
|
-
raise "Not implemented"
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
# import satoshi bitcoind blk0001.dat blockchain file
|
|
392
|
-
def import filename, max_depth = nil
|
|
393
|
-
if File.file?(filename)
|
|
394
|
-
log.info { "Importing #{filename}" }
|
|
395
|
-
File.open(filename) do |file|
|
|
396
|
-
until file.eof?
|
|
397
|
-
magic = file.read(4)
|
|
398
|
-
|
|
399
|
-
# bitcoind pads the ends of the block files so that it doesn't
|
|
400
|
-
# have to reallocate space on every new block.
|
|
401
|
-
break if magic == "\0\0\0\0"
|
|
402
|
-
raise "invalid network magic" unless Bitcoin.network[:magic_head] == magic
|
|
403
|
-
|
|
404
|
-
size = file.read(4).unpack("L")[0]
|
|
405
|
-
blk = Bitcoin::P::Block.new(file.read(size))
|
|
406
|
-
depth, chain = new_block(blk)
|
|
407
|
-
break if max_depth && depth >= max_depth
|
|
408
|
-
end
|
|
409
|
-
end
|
|
410
|
-
elsif File.directory?(filename)
|
|
411
|
-
Dir.entries(filename).sort.each do |file|
|
|
412
|
-
next unless file =~ /^blk.*?\.dat$/
|
|
413
|
-
import(File.join(filename, file), max_depth)
|
|
414
|
-
end
|
|
415
|
-
else
|
|
416
|
-
raise "Import dir/file #{filename} not found"
|
|
417
|
-
end
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
def in_sync?
|
|
421
|
-
(get_head && (Time.now - get_head.time).to_i < 3600) ? true : false
|
|
422
|
-
end
|
|
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
|
-
|
|
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
|
-
|
|
512
|
-
end
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
# TODO: someday sequel will support #blob directly and #to_sequel_blob will be gone
|
|
517
|
-
class String; def blob; to_sequel_blob; end; end
|