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.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +2 -2
  4. data/COPYING +1 -1
  5. data/Gemfile +5 -11
  6. data/README.rdoc +11 -5
  7. data/Rakefile +5 -0
  8. data/bin/bitcoin_node +11 -29
  9. data/bin/bitcoin_node_cli +81 -0
  10. data/bin/bitcoin_wallet +9 -6
  11. data/doc/NODE.rdoc +79 -26
  12. data/examples/bbe_verify_tx.rb +1 -1
  13. data/examples/index_nhash.rb +24 -0
  14. data/examples/reindex_p2sh_addrs.rb +44 -0
  15. data/lib/bitcoin.rb +135 -20
  16. data/lib/bitcoin/builder.rb +233 -63
  17. data/lib/bitcoin/key.rb +89 -16
  18. data/lib/bitcoin/litecoin.rb +13 -11
  19. data/lib/bitcoin/namecoin.rb +5 -4
  20. data/lib/bitcoin/network/command_client.rb +23 -13
  21. data/lib/bitcoin/network/command_handler.rb +336 -131
  22. data/lib/bitcoin/network/connection_handler.rb +14 -13
  23. data/lib/bitcoin/network/node.rb +61 -20
  24. data/lib/bitcoin/protocol.rb +5 -1
  25. data/lib/bitcoin/protocol/block.rb +15 -3
  26. data/lib/bitcoin/protocol/parser.rb +3 -3
  27. data/lib/bitcoin/protocol/tx.rb +82 -20
  28. data/lib/bitcoin/protocol/txin.rb +7 -0
  29. data/lib/bitcoin/protocol/txout.rb +12 -9
  30. data/lib/bitcoin/script.rb +329 -75
  31. data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
  32. data/lib/bitcoin/storage/models.rb +6 -11
  33. data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
  34. data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
  35. data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
  36. data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
  37. data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
  38. data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
  39. data/lib/bitcoin/storage/storage.rb +161 -97
  40. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
  41. data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
  42. data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
  43. data/lib/bitcoin/validation.rb +87 -56
  44. data/lib/bitcoin/version.rb +1 -1
  45. data/spec/bitcoin/bitcoin_spec.rb +38 -0
  46. data/spec/bitcoin/builder_spec.rb +177 -0
  47. data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
  48. data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
  49. data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
  50. data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
  51. data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
  52. data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
  53. data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
  54. data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
  55. data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
  56. data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
  57. data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
  58. data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
  59. data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
  60. data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
  61. data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
  62. data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
  63. data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
  64. data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
  65. data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
  66. data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
  67. data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
  68. data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
  69. data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
  70. data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
  71. data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
  72. data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
  73. data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
  74. data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
  75. data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
  76. data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
  77. data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
  78. data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
  79. data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
  80. data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
  81. data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
  82. data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
  83. data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
  84. data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
  85. data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
  86. data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
  87. data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
  88. data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
  89. data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
  90. data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
  91. data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
  92. data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
  93. data/spec/bitcoin/key_spec.rb +79 -8
  94. data/spec/bitcoin/namecoin_spec.rb +1 -1
  95. data/spec/bitcoin/node/command_api_spec.rb +373 -86
  96. data/spec/bitcoin/performance/storage_spec.rb +41 -0
  97. data/spec/bitcoin/protocol/addr_spec.rb +7 -5
  98. data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
  99. data/spec/bitcoin/protocol/block_spec.rb +6 -0
  100. data/spec/bitcoin/protocol/tx_spec.rb +184 -1
  101. data/spec/bitcoin/protocol/txin_spec.rb +27 -0
  102. data/spec/bitcoin/protocol/txout_spec.rb +27 -0
  103. data/spec/bitcoin/script/opcodes_spec.rb +74 -3
  104. data/spec/bitcoin/script/script_spec.rb +271 -0
  105. data/spec/bitcoin/spec_helper.rb +34 -6
  106. data/spec/bitcoin/storage/models_spec.rb +104 -0
  107. data/spec/bitcoin/storage/reorg_spec.rb +42 -11
  108. data/spec/bitcoin/storage/storage_spec.rb +58 -15
  109. data/spec/bitcoin/storage/validation_spec.rb +44 -14
  110. data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
  111. data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
  112. data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
  113. 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.hash160 == hash160 ? o : nil
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
- script.get_hash160
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
- script.get_address
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
- script.get_addresses
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
- script.type
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 < StoreBase
10
+ class SequelStore < SequelStoreBase
11
11
 
12
12
  # sequel database connection
13
13
  attr_accessor :db
14
14
 
15
- DEFAULT_CONFIG = { mode: :full, cache_head: false }
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 = @db[:tx][hash: tx.hash.htb.blob]
61
- existing ? blk_tx[idx] = existing[:id] : new_tx << [tx, idx]
70
+ existing = existing_tx[tx.hash]
71
+ existing ? blk_tx[idx] = existing : new_tx << [tx, idx]
62
72
  end
63
- new_tx_ids = @db[:tx].insert_multiple(new_tx.map {|tx, _| tx_data(tx) })
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
- @db[:blk_tx].insert_multiple(blk_tx.map.with_index {|id, idx|
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
- txin_ids = @db[:txin].insert_multiple(new_tx.map.with_index {|tx, tx_idx|
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
- txin_data(new_tx_ids[tx_idx], txin, txin_idx) } }.flatten)
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 = @db[:txout].insert_multiple(new_tx.map.with_index {|tx, tx_idx|
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, h| [txout_ids[i], h]}
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 {|b| get_block(b).validator(self).validate(raise_errors: true) } unless @config[:skip_validation]
105
- @db[:blk].where(hash: new_main.map {|h| h.htb.blob }).update(chain: MAIN)
106
- end
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
- elsif Bitcoin.namecoin? && script.is_namecoin?
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
- addrs.group_by {|_, a| a }.each do |hash160, txouts|
140
- if existing = @db[:addr][:hash160 => hash160]
141
- txouts.each {|id, _| addr_txouts << [existing[:id], id] }
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
- new_addrs << [hash160, txouts.map {|id, _| id }]
150
+ # collect new address/txout mapping
151
+ new_addrs << [[hash160, type], txouts.map {|id, _| id }]
144
152
  end
145
153
  end
146
- new_addr_ids = @db[:addr].insert_multiple(new_addrs.map {|hash160, txout_id|
147
- { hash160: hash160 } })
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
- @db[:addr_txout].insert_multiple(addr_txouts.map {|addr_id, txout_id|
154
- { addr_id: addr_id, txout_id: txout_id }})
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
- { hash: tx.hash.htb.blob,
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
- { tx_id: tx_id, tx_idx: idx,
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 => 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(:addr_id => addr[:id])
319
- .map{|t| @db[:txout][:id => t[:txout_id]] }
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][:id => o.get_tx.blk_id][:chain] == MAIN rescue false }
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
- .order(:idx).each {|tx| blk.tx << wrap_tx(tx, block[:id]) }
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.recalc_block_hash
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 = tx.hash_from_payload(tx.to_payload)
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