bitcoin-ruby 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +2 -2
- data/COPYING +1 -1
- data/Gemfile +5 -11
- data/README.rdoc +11 -5
- data/Rakefile +5 -0
- data/bin/bitcoin_node +11 -29
- data/bin/bitcoin_node_cli +81 -0
- data/bin/bitcoin_wallet +9 -6
- data/doc/NODE.rdoc +79 -26
- data/examples/bbe_verify_tx.rb +1 -1
- data/examples/index_nhash.rb +24 -0
- data/examples/reindex_p2sh_addrs.rb +44 -0
- data/lib/bitcoin.rb +135 -20
- data/lib/bitcoin/builder.rb +233 -63
- data/lib/bitcoin/key.rb +89 -16
- data/lib/bitcoin/litecoin.rb +13 -11
- data/lib/bitcoin/namecoin.rb +5 -4
- data/lib/bitcoin/network/command_client.rb +23 -13
- data/lib/bitcoin/network/command_handler.rb +336 -131
- data/lib/bitcoin/network/connection_handler.rb +14 -13
- data/lib/bitcoin/network/node.rb +61 -20
- data/lib/bitcoin/protocol.rb +5 -1
- data/lib/bitcoin/protocol/block.rb +15 -3
- data/lib/bitcoin/protocol/parser.rb +3 -3
- data/lib/bitcoin/protocol/tx.rb +82 -20
- data/lib/bitcoin/protocol/txin.rb +7 -0
- data/lib/bitcoin/protocol/txout.rb +12 -9
- data/lib/bitcoin/script.rb +329 -75
- data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
- data/lib/bitcoin/storage/models.rb +6 -11
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
- data/lib/bitcoin/storage/storage.rb +161 -97
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
- data/lib/bitcoin/validation.rb +87 -56
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +38 -0
- data/spec/bitcoin/builder_spec.rb +177 -0
- data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
- data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
- data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
- data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
- data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
- data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
- data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
- data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
- data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
- data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
- data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
- data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
- data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
- data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
- data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
- data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
- data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
- data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
- data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
- data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
- data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
- data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
- data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
- data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
- data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
- data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
- data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
- data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
- data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
- data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
- data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
- data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
- data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
- data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
- data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
- data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
- data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
- data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
- data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
- data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
- data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
- data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
- data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
- data/spec/bitcoin/key_spec.rb +79 -8
- data/spec/bitcoin/namecoin_spec.rb +1 -1
- data/spec/bitcoin/node/command_api_spec.rb +373 -86
- data/spec/bitcoin/performance/storage_spec.rb +41 -0
- data/spec/bitcoin/protocol/addr_spec.rb +7 -5
- data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
- data/spec/bitcoin/protocol/block_spec.rb +6 -0
- data/spec/bitcoin/protocol/tx_spec.rb +184 -1
- data/spec/bitcoin/protocol/txin_spec.rb +27 -0
- data/spec/bitcoin/protocol/txout_spec.rb +27 -0
- data/spec/bitcoin/script/opcodes_spec.rb +74 -3
- data/spec/bitcoin/script/script_spec.rb +271 -0
- data/spec/bitcoin/spec_helper.rb +34 -6
- data/spec/bitcoin/storage/models_spec.rb +104 -0
- data/spec/bitcoin/storage/reorg_spec.rb +42 -11
- data/spec/bitcoin/storage/storage_spec.rb +58 -15
- data/spec/bitcoin/storage/validation_spec.rb +44 -14
- data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
- data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
- data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
- metadata +117 -11
@@ -72,6 +72,12 @@ module Bitcoin::Storage::Backends
|
|
72
72
|
wrap_block(@blk.find {|blk| blk.tx.map(&:hash).include?(tx_hash) })
|
73
73
|
end
|
74
74
|
|
75
|
+
def get_idx_from_tx_hash(tx_hash)
|
76
|
+
return nil unless tx = get_tx(tx_hash)
|
77
|
+
return nil unless blk = tx.get_block
|
78
|
+
blk.tx.index tx
|
79
|
+
end
|
80
|
+
|
75
81
|
def get_tx(tx_hash)
|
76
82
|
transaction = @tx[tx_hash]
|
77
83
|
return nil unless transaction
|
@@ -88,15 +94,24 @@ module Bitcoin::Storage::Backends
|
|
88
94
|
wrap_txin(txin)
|
89
95
|
end
|
90
96
|
|
97
|
+
def get_txout_for_txin(txin)
|
98
|
+
return nil unless tx = @tx[txin.prev_out.reverse_hth]
|
99
|
+
wrap_tx(tx).out[txin.prev_out_index]
|
100
|
+
end
|
101
|
+
|
91
102
|
def get_txouts_for_pk_script(script)
|
92
103
|
txouts = @tx.values.map(&:out).flatten.select {|o| o.pk_script == script}
|
93
104
|
txouts.map {|o| wrap_txout(o) }
|
94
105
|
end
|
95
106
|
|
96
|
-
def get_txouts_for_hash160(hash160, unconfirmed = false)
|
107
|
+
def get_txouts_for_hash160(hash160, type = :hash160, unconfirmed = false)
|
97
108
|
@tx.values.map(&:out).flatten.map {|o|
|
98
109
|
o = wrap_txout(o)
|
99
|
-
o.
|
110
|
+
if o.parsed_script.is_multisig?
|
111
|
+
o.parsed_script.get_multisig_pubkeys.map{|pk| Bitcoin.hash160(pk.unpack("H*")[0])}.include?(hash160) ? o : nil
|
112
|
+
else
|
113
|
+
o.hash160 == hash160 && o.type == type ? o : nil
|
114
|
+
end
|
100
115
|
}.compact
|
101
116
|
end
|
102
117
|
|
@@ -143,8 +158,7 @@ module Bitcoin::Storage::Backends
|
|
143
158
|
def wrap_txout(output)
|
144
159
|
return nil unless output
|
145
160
|
tx = @tx.values.find{|t| t.out.include?(output)}
|
146
|
-
data = {tx_id: tx.hash, tx_idx: tx.out.index(output),
|
147
|
-
hash160: Bitcoin::Script.new(output.pk_script).get_hash160 }
|
161
|
+
data = {tx_id: tx.hash, tx_idx: tx.out.index(output), hash160: output.parsed_script.get_hash160}
|
148
162
|
txout = Bitcoin::Storage::Models::TxOut.new(self, data)
|
149
163
|
[:value, :pk_script_length, :pk_script].each do |attr|
|
150
164
|
txout.send("#{attr}=", output.send(attr))
|
@@ -156,5 +170,10 @@ module Bitcoin::Storage::Backends
|
|
156
170
|
"DummyStore"
|
157
171
|
end
|
158
172
|
|
173
|
+
def check_consistency(*args)
|
174
|
+
log.warn { "Dummy store doesn't support consistency check" }
|
175
|
+
end
|
176
|
+
|
177
|
+
|
159
178
|
end
|
160
179
|
end
|
@@ -95,13 +95,14 @@ module Bitcoin::Storage::Models
|
|
95
95
|
# Transaction input retrieved from storage. (see Bitcoin::Protocol::TxIn
|
96
96
|
class TxIn < Bitcoin::Protocol::TxIn
|
97
97
|
|
98
|
-
attr_reader :store, :id, :tx_id, :tx_idx
|
98
|
+
attr_reader :store, :id, :tx_id, :tx_idx, :p2sh_type
|
99
99
|
|
100
100
|
def initialize store, data
|
101
101
|
@store = store
|
102
102
|
@id = data[:id]
|
103
103
|
@tx_id = data[:tx_id]
|
104
104
|
@tx_idx = data[:tx_idx]
|
105
|
+
@p2sh_type = data[:p2sh_type]
|
105
106
|
end
|
106
107
|
|
107
108
|
# get the transaction this input is in
|
@@ -134,7 +135,7 @@ module Bitcoin::Storage::Models
|
|
134
135
|
end
|
135
136
|
|
136
137
|
def hash160
|
137
|
-
|
138
|
+
parsed_script.get_hash160
|
138
139
|
end
|
139
140
|
|
140
141
|
# get the transaction this output is in
|
@@ -145,18 +146,16 @@ module Bitcoin::Storage::Models
|
|
145
146
|
# get the next input that references this output
|
146
147
|
def get_next_in
|
147
148
|
@store.get_txin_for_txout(get_tx.hash, @tx_idx)
|
148
|
-
rescue
|
149
|
-
nil
|
150
149
|
end
|
151
150
|
|
152
151
|
# get all addresses this txout corresponds to (if possible)
|
153
152
|
def get_address
|
154
|
-
|
153
|
+
parsed_script.get_address
|
155
154
|
end
|
156
155
|
|
157
156
|
# get the single address this txout corresponds to (first for multisig tx)
|
158
157
|
def get_addresses
|
159
|
-
|
158
|
+
parsed_script.get_addresses
|
160
159
|
end
|
161
160
|
|
162
161
|
def get_namecoin_name
|
@@ -164,11 +163,7 @@ module Bitcoin::Storage::Models
|
|
164
163
|
end
|
165
164
|
|
166
165
|
def type
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
def script
|
171
|
-
@_script = Bitcoin::Script.new(@pk_script)
|
166
|
+
parsed_script.type
|
172
167
|
end
|
173
168
|
|
174
169
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
|
3
|
+
up do
|
4
|
+
|
5
|
+
@log.info { "Running migration #{__FILE__}" }
|
6
|
+
|
7
|
+
if adapter_scheme == :postgres
|
8
|
+
execute "DROP VIEW unconfirmed" if self.views.include?(:unconfirmed)
|
9
|
+
execute "ALTER TABLE tx ALTER COLUMN hash TYPE bytea USING hash::bytea"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
|
3
|
+
up do
|
4
|
+
|
5
|
+
@log.info { "Running migration #{__FILE__}" }
|
6
|
+
|
7
|
+
def process_block blk
|
8
|
+
print "\r#{blk.hash} - #{blk.depth}"
|
9
|
+
blk.tx.each do |tx|
|
10
|
+
self[:tx].where(hash: tx.hash.htb.blob).update(nhash: tx.nhash.htb.blob)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
if @store.config[:index_nhash]
|
15
|
+
puts "Building normalized hash index..."
|
16
|
+
|
17
|
+
add_column :tx, :nhash, :bytea
|
18
|
+
|
19
|
+
if blk = @store.get_block_by_depth(0)
|
20
|
+
process_block(blk)
|
21
|
+
while blk = blk.get_next_block
|
22
|
+
process_block(blk)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
add_index :tx, :nhash
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
|
3
|
+
up do
|
4
|
+
|
5
|
+
@log.info { "Running migration #{__FILE__}" }
|
6
|
+
|
7
|
+
# Naming seems to be different on different adapters and sequel's
|
8
|
+
# "drop_index(:txin, :prev_out)" doesn't seem to be handling it correctly
|
9
|
+
execute "DROP INDEX IF EXISTS txin_prev_out_idx;"
|
10
|
+
execute "DROP INDEX IF EXISTS txin_prev_out_index;"
|
11
|
+
|
12
|
+
add_index :txin, [:prev_out, :prev_out_index]
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Add column txin.p2sh_type and index the type of the inner p2sh script of all inputs
|
2
|
+
|
3
|
+
Sequel.migration do
|
4
|
+
|
5
|
+
up do
|
6
|
+
|
7
|
+
@log.info { "Running migration #{__FILE__}" }
|
8
|
+
|
9
|
+
if @store.config[:index_p2sh_type]
|
10
|
+
puts "Building p2sh type index..."
|
11
|
+
|
12
|
+
add_column :txin, :p2sh_type, :int
|
13
|
+
|
14
|
+
self[:txout].where(type: 4).each do |txout|
|
15
|
+
tx = self[:tx][id: txout[:tx_id]]
|
16
|
+
next unless next_in = self[:txin][prev_out: tx[:hash].reverse, prev_out_index: txout[:tx_idx]]
|
17
|
+
script = Bitcoin::Script.new(next_in[:script_sig], txout[:pk_script])
|
18
|
+
if script.is_p2sh?
|
19
|
+
inner_script = Bitcoin::Script.new(script.inner_p2sh_script)
|
20
|
+
p2sh_type = @store.class::SCRIPT_TYPES.index(inner_script.type)
|
21
|
+
self[:txin].where(id: next_in[:id]).update(p2sh_type: p2sh_type)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
add_index :txin, [:id, :p2sh_type]
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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
|
+
puts "Fixing address types for #{self[:txout].where(type: 4).count} p2sh addresses..."
|
10
|
+
|
11
|
+
add_column :addr, :type, :int, default: 0, null: false
|
12
|
+
|
13
|
+
i = 0
|
14
|
+
# iterate over all txouts with p2sh type
|
15
|
+
self[:txout].where(type: 4).each do |txout|
|
16
|
+
# find addr_txout mapping
|
17
|
+
addr_txout = self[:addr_txout][txout_id: txout[:id]]
|
18
|
+
|
19
|
+
# find currently linked address
|
20
|
+
addr = self[:addr][id: addr_txout[:addr_id]]
|
21
|
+
|
22
|
+
# skip if address type is already p2sh
|
23
|
+
next i+=1 if addr[:type] == 1
|
24
|
+
|
25
|
+
# if address has other txouts, that are not p2sh-type, we need a different one
|
26
|
+
if self[:addr_txout].where(addr_id: addr[:id])
|
27
|
+
.join(:txout, id: :txout_id).where("type != 4").any?
|
28
|
+
|
29
|
+
# if there is already a corrected address
|
30
|
+
if a = self[:addr][hash160: addr[:hash160], type: 1]
|
31
|
+
# use the existing corrected address
|
32
|
+
addr_id = a[:id]
|
33
|
+
else
|
34
|
+
# create new address with correct p2sh type
|
35
|
+
addr_id = self[:addr].insert(hash160: addr[:hash160], type: 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
# change mapping to point to new address
|
39
|
+
self[:addr_txout].where(txout_id: txout[:id]).update(addr_id: addr_id)
|
40
|
+
|
41
|
+
# if address has only this txout
|
42
|
+
else
|
43
|
+
# change to correct type
|
44
|
+
self[:addr].where(id: addr[:id]).update(type: 1)
|
45
|
+
end
|
46
|
+
|
47
|
+
print "\r#{i}"; i+=1
|
48
|
+
|
49
|
+
end
|
50
|
+
puts
|
51
|
+
|
52
|
+
add_index :addr, [:hash160, :type]
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -7,12 +7,21 @@ module Bitcoin::Storage::Backends
|
|
7
7
|
|
8
8
|
# Storage backend using Sequel to connect to arbitrary SQL databases.
|
9
9
|
# Inherits from StoreBase and implements its interface.
|
10
|
-
class SequelStore <
|
10
|
+
class SequelStore < SequelStoreBase
|
11
11
|
|
12
12
|
# sequel database connection
|
13
13
|
attr_accessor :db
|
14
14
|
|
15
|
-
DEFAULT_CONFIG = {
|
15
|
+
DEFAULT_CONFIG = {
|
16
|
+
# TODO
|
17
|
+
mode: :full,
|
18
|
+
|
19
|
+
# cache head block. only the instance that is updating the head should do this.
|
20
|
+
cache_head: false,
|
21
|
+
|
22
|
+
# store an index of tx.nhash values
|
23
|
+
index_nhash: false
|
24
|
+
}
|
16
25
|
|
17
26
|
# create sequel store with given +config+
|
18
27
|
def initialize config, *args
|
@@ -56,33 +65,38 @@ module Bitcoin::Storage::Backends
|
|
56
65
|
blk_tx, new_tx, addrs, names = [], [], [], []
|
57
66
|
|
58
67
|
# store tx
|
68
|
+
existing_tx = Hash[*@db[:tx].filter(hash: blk.tx.map {|tx| tx.hash.htb.blob }).map { |tx| [tx[:hash].hth, tx[:id]] }.flatten]
|
59
69
|
blk.tx.each.with_index do |tx, idx|
|
60
|
-
existing =
|
61
|
-
existing ? blk_tx[idx] = existing
|
70
|
+
existing = existing_tx[tx.hash]
|
71
|
+
existing ? blk_tx[idx] = existing : new_tx << [tx, idx]
|
62
72
|
end
|
63
|
-
|
73
|
+
|
74
|
+
new_tx_ids = fast_insert(:tx, new_tx.map {|tx, _| tx_data(tx) }, return_ids: true)
|
64
75
|
new_tx_ids.each.with_index {|tx_id, idx| blk_tx[new_tx[idx][1]] = tx_id }
|
65
76
|
|
66
|
-
|
67
|
-
{ blk_id: block_id, tx_id: id, idx: idx } })
|
77
|
+
fast_insert(:blk_tx, blk_tx.map.with_index {|id, idx| { blk_id: block_id, tx_id: id, idx: idx } })
|
68
78
|
|
69
79
|
# store txins
|
70
|
-
|
80
|
+
fast_insert(:txin, new_tx.map.with_index {|tx, tx_idx|
|
71
81
|
tx, _ = *tx
|
72
82
|
tx.in.map.with_index {|txin, txin_idx|
|
73
|
-
|
83
|
+
p2sh_type = nil
|
84
|
+
if @config[:index_p2sh_type] && !txin.coinbase? && (script = tx.scripts[txin_idx]) && script.is_p2sh?
|
85
|
+
p2sh_type = Bitcoin::Script.new(script.inner_p2sh_script).type
|
86
|
+
end
|
87
|
+
txin_data(new_tx_ids[tx_idx], txin, txin_idx, p2sh_type) } }.flatten)
|
74
88
|
|
75
89
|
# store txouts
|
76
90
|
txout_i = 0
|
77
|
-
txout_ids =
|
91
|
+
txout_ids = fast_insert(:txout, new_tx.map.with_index {|tx, tx_idx|
|
78
92
|
tx, _ = *tx
|
79
93
|
tx.out.map.with_index {|txout, txout_idx|
|
80
|
-
script_type, a, n = *parse_script(txout, txout_i, tx.hash)
|
94
|
+
script_type, a, n = *parse_script(txout, txout_i, tx.hash, txout_idx)
|
81
95
|
addrs += a; names += n; txout_i += 1
|
82
|
-
txout_data(new_tx_ids[tx_idx], txout, txout_idx, script_type) } }.flatten)
|
96
|
+
txout_data(new_tx_ids[tx_idx], txout, txout_idx, script_type) } }.flatten, return_ids: true)
|
83
97
|
|
84
98
|
# store addrs
|
85
|
-
persist_addrs addrs.map {|i,
|
99
|
+
persist_addrs addrs.map {|i, addr| [txout_ids[i], addr]}
|
86
100
|
names.each {|i, script| store_name(script, txout_ids[i]) }
|
87
101
|
end
|
88
102
|
@head = wrap_block(attrs.merge(id: block_id)) if chain == MAIN
|
@@ -101,65 +115,70 @@ module Bitcoin::Storage::Backends
|
|
101
115
|
def reorg new_side, new_main
|
102
116
|
@db.transaction do
|
103
117
|
@db[:blk].where(hash: new_side.map {|h| h.htb.blob }).update(chain: SIDE)
|
104
|
-
new_main.each
|
105
|
-
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
# parse script and collect address/txout mappings to index
|
110
|
-
def parse_script txout, i, tx_hash = ""
|
111
|
-
addrs, names = [], []
|
112
|
-
|
113
|
-
script = Bitcoin::Script.new(txout.pk_script) rescue nil
|
114
|
-
if script
|
115
|
-
if script.is_hash160? || script.is_pubkey?
|
116
|
-
addrs << [i, script.get_hash160]
|
117
|
-
elsif script.is_multisig?
|
118
|
-
script.get_multisig_pubkeys.map do |pubkey|
|
119
|
-
addrs << [i, Bitcoin.hash160(pubkey.unpack("H*")[0])]
|
118
|
+
new_main.each do |block_hash|
|
119
|
+
unless @config[:skip_validation]
|
120
|
+
get_block(block_hash).validator(self).validate(raise_errors: true)
|
120
121
|
end
|
121
|
-
|
122
|
-
addrs << [i, script.get_hash160]
|
123
|
-
names << [i, script]
|
124
|
-
else
|
125
|
-
log.info { "Unknown script type in #{tx_hash}:#{i}" }
|
126
|
-
log.debug { script.to_string }
|
122
|
+
@db[:blk].where(hash: block_hash.htb.blob).update(chain: MAIN)
|
127
123
|
end
|
128
|
-
script_type = SCRIPT_TYPES.index(script.type)
|
129
|
-
else
|
130
|
-
log.error { "Error parsing script #{tx_hash}:#{i}" }
|
131
|
-
script_type = SCRIPT_TYPES.index(:unknown)
|
132
124
|
end
|
133
|
-
[script_type, addrs, names]
|
134
125
|
end
|
135
126
|
|
136
127
|
# bulk-store addresses and txout mappings
|
137
128
|
def persist_addrs addrs
|
138
129
|
addr_txouts, new_addrs = [], []
|
139
|
-
|
140
|
-
|
141
|
-
|
130
|
+
|
131
|
+
# find addresses that are already there
|
132
|
+
existing_addr = {}
|
133
|
+
addrs.each do |i, addr|
|
134
|
+
hash160 = Bitcoin.hash160_from_address(addr)
|
135
|
+
type = Bitcoin.address_type(addr)
|
136
|
+
if existing = @db[:addr][hash160: hash160, type: ADDRESS_TYPES.index(type)]
|
137
|
+
existing_addr[[hash160, type]] = existing[:id]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# iterate over all txouts, grouped by hash160
|
142
|
+
addrs.group_by {|_, a| a }.each do |addr, txouts|
|
143
|
+
hash160 = Bitcoin.hash160_from_address(addr)
|
144
|
+
type = Bitcoin.address_type(addr)
|
145
|
+
|
146
|
+
if existing_id = existing_addr[[hash160, type]]
|
147
|
+
# link each txout to existing address
|
148
|
+
txouts.each {|id, _| addr_txouts << [existing_id, id] }
|
142
149
|
else
|
143
|
-
|
150
|
+
# collect new address/txout mapping
|
151
|
+
new_addrs << [[hash160, type], txouts.map {|id, _| id }]
|
144
152
|
end
|
145
153
|
end
|
146
|
-
|
147
|
-
|
154
|
+
|
155
|
+
# insert all new addresses
|
156
|
+
new_addr_ids = fast_insert(:addr, new_addrs.map {|hash160_and_type, txout_id|
|
157
|
+
hash160, type = *hash160_and_type
|
158
|
+
{ hash160: hash160, type: ADDRESS_TYPES.index(type) }
|
159
|
+
}, return_ids: true)
|
160
|
+
|
161
|
+
|
162
|
+
# link each new txout to the new addresses
|
148
163
|
new_addr_ids.each.with_index do |addr_id, idx|
|
149
164
|
new_addrs[idx][1].each do |txout_id|
|
150
165
|
addr_txouts << [addr_id, txout_id]
|
151
166
|
end
|
152
167
|
end
|
153
|
-
|
154
|
-
|
168
|
+
|
169
|
+
# insert addr/txout links
|
170
|
+
fast_insert(:addr_txout, addr_txouts.map {|addr_id, txout_id| { addr_id: addr_id, txout_id: txout_id }})
|
155
171
|
end
|
156
172
|
|
157
173
|
# prepare transaction data for storage
|
158
174
|
def tx_data tx
|
159
|
-
|
175
|
+
data = {
|
176
|
+
hash: tx.hash.htb.blob,
|
160
177
|
version: tx.ver, lock_time: tx.lock_time,
|
161
178
|
coinbase: tx.in.size == 1 && tx.in[0].coinbase?,
|
162
179
|
tx_size: tx.payload.bytesize }
|
180
|
+
data[:nhash] = tx.nhash.htb.blob if @config[:index_nhash]
|
181
|
+
data
|
163
182
|
end
|
164
183
|
|
165
184
|
# store transaction +tx+
|
@@ -177,17 +196,21 @@ module Bitcoin::Storage::Backends
|
|
177
196
|
end
|
178
197
|
|
179
198
|
# prepare txin data for storage
|
180
|
-
def txin_data tx_id, txin, idx
|
181
|
-
|
199
|
+
def txin_data tx_id, txin, idx, p2sh_type = nil
|
200
|
+
data = {
|
201
|
+
tx_id: tx_id, tx_idx: idx,
|
182
202
|
script_sig: txin.script_sig.blob,
|
183
203
|
prev_out: txin.prev_out.blob,
|
184
204
|
prev_out_index: txin.prev_out_index,
|
185
|
-
sequence: txin.sequence.unpack("V")[0]
|
205
|
+
sequence: txin.sequence.unpack("V")[0],
|
206
|
+
}
|
207
|
+
data[:p2sh_type] = SCRIPT_TYPES.index(p2sh_type) if @config[:index_p2sh_type]
|
208
|
+
data
|
186
209
|
end
|
187
210
|
|
188
211
|
# store input +txin+
|
189
|
-
def store_txin(tx_id, txin, idx)
|
190
|
-
@db[:txin].insert(txin_data(tx_id, txin, idx))
|
212
|
+
def store_txin(tx_id, txin, idx, p2sh_type = nil)
|
213
|
+
@db[:txin].insert(txin_data(tx_id, txin, idx, p2sh_type))
|
191
214
|
end
|
192
215
|
|
193
216
|
# prepare txout data for storage
|
@@ -199,7 +222,7 @@ module Bitcoin::Storage::Backends
|
|
199
222
|
|
200
223
|
# store output +txout+
|
201
224
|
def store_txout(tx_id, txout, idx, tx_hash = "")
|
202
|
-
script_type, addrs, names = *parse_script(txout, idx, tx_hash)
|
225
|
+
script_type, addrs, names = *parse_script(txout, idx, tx_hash, idx)
|
203
226
|
txout_id = @db[:txout].insert(txout_data(tx_id, txout, idx, script_type))
|
204
227
|
persist_addrs addrs.map {|i, h| [txout_id, h] }
|
205
228
|
names.each {|i, script| store_name(script, txout_id) }
|
@@ -218,9 +241,9 @@ module Bitcoin::Storage::Backends
|
|
218
241
|
end
|
219
242
|
end
|
220
243
|
|
221
|
-
# check if block +blk_hash+ exists
|
244
|
+
# check if block +blk_hash+ exists in the main chain
|
222
245
|
def has_block(blk_hash)
|
223
|
-
!!@db[:blk].where(:hash => blk_hash.htb.blob).get(1)
|
246
|
+
!!@db[:blk].where(:hash => blk_hash.htb.blob, :chain => 0).get(1)
|
224
247
|
end
|
225
248
|
|
226
249
|
# check if transaction +tx_hash+ exists
|
@@ -277,11 +300,31 @@ module Bitcoin::Storage::Backends
|
|
277
300
|
wrap_block(@db[:blk][:id => block_id])
|
278
301
|
end
|
279
302
|
|
303
|
+
# get block id in the main chain by given +tx_id+
|
304
|
+
def get_block_id_for_tx_id(tx_id)
|
305
|
+
@db[:blk_tx].join(:blk, id: :blk_id)
|
306
|
+
.where(tx_id: tx_id, chain: MAIN).first[:blk_id] rescue nil
|
307
|
+
end
|
308
|
+
|
280
309
|
# get transaction for given +tx_hash+
|
281
310
|
def get_tx(tx_hash)
|
282
311
|
wrap_tx(@db[:tx][:hash => tx_hash.htb.blob])
|
283
312
|
end
|
284
313
|
|
314
|
+
# get array of txes with given +tx_hashes+
|
315
|
+
def get_txs(tx_hashes)
|
316
|
+
txs = db[:tx].filter(hash: tx_hashes.map{|h| h.htb.blob})
|
317
|
+
txs_ids = txs.map {|tx| tx[:id]}
|
318
|
+
return [] if txs_ids.empty?
|
319
|
+
|
320
|
+
# we fetch all needed block ids, inputs and outputs to avoid doing number of queries propertional to number of transactions
|
321
|
+
block_ids = Hash[*db[:blk_tx].join(:blk, id: :blk_id).filter(tx_id: txs_ids, chain: 0).map {|b| [b[:tx_id], b[:blk_id]] }.flatten]
|
322
|
+
inputs = db[:txin].filter(:tx_id => txs_ids).order(:tx_idx).map.group_by{ |txin| txin[:tx_id] }
|
323
|
+
outputs = db[:txout].filter(:tx_id => txs_ids).order(:tx_idx).map.group_by{ |txout| txout[:tx_id] }
|
324
|
+
|
325
|
+
txs.map {|tx| wrap_tx(tx, block_ids[tx[:id]], inputs: inputs[tx[:id]], outputs: outputs[tx[:id]]) }
|
326
|
+
end
|
327
|
+
|
285
328
|
# get transaction by given +tx_id+
|
286
329
|
def get_tx_by_id(tx_id)
|
287
330
|
wrap_tx(@db[:tx][:id => tx_id])
|
@@ -294,6 +337,11 @@ module Bitcoin::Storage::Backends
|
|
294
337
|
wrap_txin(@db[:txin][:prev_out => tx_hash, :prev_out_index => txout_idx])
|
295
338
|
end
|
296
339
|
|
340
|
+
# optimized version of Storage#get_txins_for_txouts
|
341
|
+
def get_txins_for_txouts(txouts)
|
342
|
+
@db[:txin].filter([:prev_out, :prev_out_index] => txouts.map{|tx_hash, tx_idx| [tx_hash.htb_reverse.blob, tx_idx]}).map{|i| wrap_txin(i)}
|
343
|
+
end
|
344
|
+
|
297
345
|
def get_txout_by_id(txout_id)
|
298
346
|
wrap_txout(@db[:txout][:id => txout_id])
|
299
347
|
end
|
@@ -312,14 +360,14 @@ module Bitcoin::Storage::Backends
|
|
312
360
|
end
|
313
361
|
|
314
362
|
# get all Models::TxOut matching given +hash160+
|
315
|
-
def get_txouts_for_hash160(hash160, unconfirmed = false)
|
316
|
-
addr = @db[:addr][:hash160
|
363
|
+
def get_txouts_for_hash160(hash160, type = :hash160, unconfirmed = false)
|
364
|
+
addr = @db[:addr][hash160: hash160, type: ADDRESS_TYPES.index(type)]
|
317
365
|
return [] unless addr
|
318
|
-
txouts = @db[:addr_txout].where(:
|
319
|
-
.map{|t| @db[:txout][:
|
366
|
+
txouts = @db[:addr_txout].where(addr_id: addr[:id])
|
367
|
+
.map{|t| @db[:txout][id: t[:txout_id]] }
|
320
368
|
.map{|o| wrap_txout(o) }
|
321
369
|
unless unconfirmed
|
322
|
-
txouts.select!{|o| @db[:blk][:
|
370
|
+
txouts.select!{|o| @db[:blk][id: o.get_tx.blk_id][:chain] == MAIN rescue false }
|
323
371
|
end
|
324
372
|
txouts
|
325
373
|
end
|
@@ -358,15 +406,20 @@ module Bitcoin::Storage::Backends
|
|
358
406
|
|
359
407
|
blk.aux_pow = Bitcoin::P::AuxPow.new(block[:aux_pow]) if block[:aux_pow]
|
360
408
|
|
361
|
-
db[:blk_tx].filter(blk_id: block[:id]).join(:tx, id: :tx_id)
|
362
|
-
|
409
|
+
blk_tx = db[:blk_tx].filter(blk_id: block[:id]).join(:tx, id: :tx_id).order(:idx)
|
410
|
+
|
411
|
+
# fetch inputs and outputs for all transactions in the block to avoid additional queries for each transaction
|
412
|
+
inputs = db[:txin].filter(:tx_id => blk_tx.map{ |tx| tx[:id] }).order(:tx_idx).map.group_by{ |txin| txin[:tx_id] }
|
413
|
+
outputs = db[:txout].filter(:tx_id => blk_tx.map{ |tx| tx[:id] }).order(:tx_idx).map.group_by{ |txout| txout[:tx_id] }
|
363
414
|
|
364
|
-
blk.
|
415
|
+
blk.tx = blk_tx.map { |tx| wrap_tx(tx, block[:id], inputs: inputs[tx[:id]], outputs: outputs[tx[:id]]) }
|
416
|
+
|
417
|
+
blk.hash = block[:hash].hth
|
365
418
|
blk
|
366
419
|
end
|
367
420
|
|
368
421
|
# wrap given +transaction+ into Models::Transaction
|
369
|
-
def wrap_tx(transaction, block_id = nil)
|
422
|
+
def wrap_tx(transaction, block_id = nil, prefetched = {})
|
370
423
|
return nil unless transaction
|
371
424
|
|
372
425
|
block_id ||= @db[:blk_tx].join(:blk, id: :blk_id)
|
@@ -375,21 +428,22 @@ module Bitcoin::Storage::Backends
|
|
375
428
|
data = {id: transaction[:id], blk_id: block_id, size: transaction[:tx_size], idx: transaction[:idx]}
|
376
429
|
tx = Bitcoin::Storage::Models::Tx.new(self, data)
|
377
430
|
|
378
|
-
inputs = db[:txin].filter(:tx_id => transaction[:id]).order(:tx_idx)
|
431
|
+
inputs = prefetched[:inputs] || db[:txin].filter(:tx_id => transaction[:id]).order(:tx_idx)
|
379
432
|
inputs.each { |i| tx.add_in(wrap_txin(i)) }
|
380
433
|
|
381
|
-
outputs = db[:txout].filter(:tx_id => transaction[:id]).order(:tx_idx)
|
434
|
+
outputs = prefetched[:outputs] || db[:txout].filter(:tx_id => transaction[:id]).order(:tx_idx)
|
382
435
|
outputs.each { |o| tx.add_out(wrap_txout(o)) }
|
383
436
|
tx.ver = transaction[:version]
|
384
437
|
tx.lock_time = transaction[:lock_time]
|
385
|
-
tx.hash =
|
438
|
+
tx.hash = transaction[:hash].hth
|
386
439
|
tx
|
387
440
|
end
|
388
441
|
|
389
442
|
# wrap given +input+ into Models::TxIn
|
390
443
|
def wrap_txin(input)
|
391
444
|
return nil unless input
|
392
|
-
data = {:id => input[:id], :tx_id => input[:tx_id], :tx_idx => input[:tx_idx]
|
445
|
+
data = { :id => input[:id], :tx_id => input[:tx_id], :tx_idx => input[:tx_idx],
|
446
|
+
:p2sh_type => input[:p2sh_type] ? SCRIPT_TYPES[input[:p2sh_type]] : nil }
|
393
447
|
txin = Bitcoin::Storage::Models::TxIn.new(self, data)
|
394
448
|
txin.prev_out = input[:prev_out]
|
395
449
|
txin.prev_out_index = input[:prev_out_index]
|
@@ -448,6 +502,50 @@ module Bitcoin::Storage::Backends
|
|
448
502
|
# end
|
449
503
|
end
|
450
504
|
|
505
|
+
protected
|
506
|
+
|
507
|
+
# Abstraction for doing many quick inserts.
|
508
|
+
#
|
509
|
+
# * +table+ - db table name
|
510
|
+
# * +data+ - a table of hashes with the same keys
|
511
|
+
# * +opts+
|
512
|
+
# ** return_ids - if true table of inserted rows ids will be returned
|
513
|
+
def fast_insert(table, data, opts={})
|
514
|
+
return [] if data.empty?
|
515
|
+
# For postgres we are using COPY which is much faster than separate INSERTs
|
516
|
+
if @db.adapter_scheme == :postgres
|
517
|
+
|
518
|
+
columns = data.first.keys
|
519
|
+
if opts[:return_ids]
|
520
|
+
ids = db.transaction do
|
521
|
+
# COPY does not return ids, so we set ids manually based on current sequence value
|
522
|
+
# We lock the table to avoid inserts that could happen in the middle of COPY
|
523
|
+
db.execute("LOCK TABLE #{table} IN SHARE UPDATE EXCLUSIVE MODE")
|
524
|
+
first_id = db.fetch("SELECT nextval('#{table}_id_seq') AS id").first[:id]
|
525
|
+
|
526
|
+
# Blobs need to be represented in the hex form (yes, we do hth on them earlier, could be improved
|
527
|
+
# \\x is the format of bytea as hex encoding in postgres
|
528
|
+
csv = data.map.with_index{|x,i| [first_id + i, columns.map{|c| x[c].kind_of?(Sequel::SQL::Blob) ? "\\x#{x[c].hth}" : x[c]}].join(',')}.join("\n")
|
529
|
+
db.copy_into(table, columns: [:id] + columns, format: :csv, data: csv)
|
530
|
+
last_id = first_id + data.size - 1
|
531
|
+
|
532
|
+
# Set sequence value to max id, last arg true means it will be incremented before next value
|
533
|
+
db.execute("SELECT setval('#{table}_id_seq', #{last_id}, true)")
|
534
|
+
(first_id..last_id).to_a # returned ids
|
535
|
+
end
|
536
|
+
else
|
537
|
+
csv = data.map{|x| columns.map{|c| x[c].kind_of?(Sequel::SQL::Blob) ? "\\x#{x[c].hth}" : x[c]}.join(',')}.join("\n")
|
538
|
+
@db.copy_into(table, format: :csv, columns: columns, data: csv)
|
539
|
+
end
|
540
|
+
|
541
|
+
else
|
542
|
+
|
543
|
+
# Life is simple when your are not optimizing ;)
|
544
|
+
@db[table].insert_multiple(data)
|
545
|
+
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
451
549
|
end
|
452
550
|
|
453
551
|
end
|