bitcoin-ruby 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +2 -2
- data/COPYING +1 -1
- data/Gemfile +5 -11
- data/README.rdoc +11 -5
- data/Rakefile +5 -0
- data/bin/bitcoin_node +11 -29
- data/bin/bitcoin_node_cli +81 -0
- data/bin/bitcoin_wallet +9 -6
- data/doc/NODE.rdoc +79 -26
- data/examples/bbe_verify_tx.rb +1 -1
- data/examples/index_nhash.rb +24 -0
- data/examples/reindex_p2sh_addrs.rb +44 -0
- data/lib/bitcoin.rb +135 -20
- data/lib/bitcoin/builder.rb +233 -63
- data/lib/bitcoin/key.rb +89 -16
- data/lib/bitcoin/litecoin.rb +13 -11
- data/lib/bitcoin/namecoin.rb +5 -4
- data/lib/bitcoin/network/command_client.rb +23 -13
- data/lib/bitcoin/network/command_handler.rb +336 -131
- data/lib/bitcoin/network/connection_handler.rb +14 -13
- data/lib/bitcoin/network/node.rb +61 -20
- data/lib/bitcoin/protocol.rb +5 -1
- data/lib/bitcoin/protocol/block.rb +15 -3
- data/lib/bitcoin/protocol/parser.rb +3 -3
- data/lib/bitcoin/protocol/tx.rb +82 -20
- data/lib/bitcoin/protocol/txin.rb +7 -0
- data/lib/bitcoin/protocol/txout.rb +12 -9
- data/lib/bitcoin/script.rb +329 -75
- data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
- data/lib/bitcoin/storage/models.rb +6 -11
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
- data/lib/bitcoin/storage/storage.rb +161 -97
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
- data/lib/bitcoin/validation.rb +87 -56
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +38 -0
- data/spec/bitcoin/builder_spec.rb +177 -0
- data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
- data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
- data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
- data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
- data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
- data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
- data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
- data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
- data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
- data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
- data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
- data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
- data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
- data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
- data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
- data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
- data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
- data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
- data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
- data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
- data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
- data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
- data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
- data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
- data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
- data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
- data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
- data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
- data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
- data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
- data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
- data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
- data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
- data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
- data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
- data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
- data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
- data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
- data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
- data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
- data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
- data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
- data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
- data/spec/bitcoin/key_spec.rb +79 -8
- data/spec/bitcoin/namecoin_spec.rb +1 -1
- data/spec/bitcoin/node/command_api_spec.rb +373 -86
- data/spec/bitcoin/performance/storage_spec.rb +41 -0
- data/spec/bitcoin/protocol/addr_spec.rb +7 -5
- data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
- data/spec/bitcoin/protocol/block_spec.rb +6 -0
- data/spec/bitcoin/protocol/tx_spec.rb +184 -1
- data/spec/bitcoin/protocol/txin_spec.rb +27 -0
- data/spec/bitcoin/protocol/txout_spec.rb +27 -0
- data/spec/bitcoin/script/opcodes_spec.rb +74 -3
- data/spec/bitcoin/script/script_spec.rb +271 -0
- data/spec/bitcoin/spec_helper.rb +34 -6
- data/spec/bitcoin/storage/models_spec.rb +104 -0
- data/spec/bitcoin/storage/reorg_spec.rb +42 -11
- data/spec/bitcoin/storage/storage_spec.rb +58 -15
- data/spec/bitcoin/storage/validation_spec.rb +44 -14
- data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
- data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
- data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
- metadata +117 -11
|
@@ -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
|