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.
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
@@ -1,25 +1,17 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
+ # Validates blocks and transactions before they are accepted into the local blockchain.
4
+ # There are two modes of validation, "syntax" and "context". "syntax" validates everything
5
+ # that can be validated without access to the rest of the blockchain, for example that the
6
+ # block hash matches the claimed difficulty, and the tx hashes add up to the given merkle
7
+ # root, etc. The "context" rules include the checks that need to cross-reference data
8
+ # against the local database, like comparing the difficulty target to the last blocks, or
9
+ # checking for doublespends. (Suggestions for better names for these modes are welcome!)
10
+ # Everything accepted into the local storage should at least be syntax-validated, but it
11
+ # should be possible to skip context-validation when the current block is already known,
12
+ # for example when checkpoints are used.
3
13
  module Bitcoin::Validation
4
-
5
- # maximum size of a block (in bytes)
6
- MAX_BLOCK_SIZE = 1_000_000
7
-
8
- # maximum number of signature operations in a block
9
- MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE / 50
10
-
11
- # maximum integer value
12
- INT_MAX = 0xffffffff
13
-
14
- # number of confirmations required before coinbase tx can be spent
15
- COINBASE_MATURITY = 100
16
-
17
- # interval (in blocks) for difficulty retarget
18
- RETARGET = 2016
19
-
20
- # interval (in blocks) for mining reward reduction
21
- REWARD_DROP = 210_000
22
-
14
+
23
15
  class ValidationError < StandardError
24
16
  end
25
17
 
@@ -38,6 +30,11 @@ module Bitcoin::Validation
38
30
  RULES[:context] -= [:difficulty, :coinbase_value]
39
31
  end
40
32
 
33
+ if Bitcoin.litecoin?
34
+ RULES[:syntax] -= [:bits]
35
+ RULES[:syntax] += [:scrypt_bits]
36
+ end
37
+
41
38
  # validate block rules. +opts+ are:
42
39
  # rules:: which rulesets to validate (default: [:syntax, :context])
43
40
  # raise_errors:: whether to raise ValidationError on failure (default: false)
@@ -82,6 +79,13 @@ module Bitcoin::Validation
82
79
  actual <= expected || [actual, expected]
83
80
  end
84
81
 
82
+ # check that block hash matches claimed bits using Scrypt hash
83
+ def scrypt_bits
84
+ actual = block.recalc_block_scrypt_hash.to_i(16)
85
+ expected = Bitcoin.decode_compact_bits(block.bits).to_i(16)
86
+ actual <= expected || [actual, expected]
87
+ end
88
+
85
89
  # check that block time is not greater than max
86
90
  def max_timestamp
87
91
  time, max = block.time, Time.now.to_i + 2*60*60
@@ -102,7 +106,7 @@ module Bitcoin::Validation
102
106
 
103
107
  # check that coinbase value is valid; no more than reward + fees
104
108
  def coinbase_value
105
- reward = ((50.0 / (2 ** (store.get_depth / REWARD_DROP.to_f).floor)) * 1e8).to_i
109
+ reward = ((50.0 / (2 ** (store.get_depth / Bitcoin::REWARD_DROP.to_f).floor)) * 1e8).to_i
106
110
  fees = 0
107
111
  block.tx[1..-1].map.with_index do |t, idx|
108
112
  val = tx_validators[idx]
@@ -127,7 +131,7 @@ module Bitcoin::Validation
127
131
 
128
132
  # check that bits satisfy required difficulty
129
133
  def difficulty
130
- return true if Bitcoin.network_name == :testnet3
134
+ return true if Bitcoin.network[:no_difficulty] == true
131
135
  block.bits == next_bits_required || [block.bits, next_bits_required]
132
136
  end
133
137
 
@@ -146,8 +150,11 @@ module Bitcoin::Validation
146
150
  block.time > min_time || [block.time, min_time]
147
151
  end
148
152
 
149
- # check transactions
153
+ # Run all syntax checks on transactions
150
154
  def transactions_syntax
155
+ # check if there are no double spends within this block
156
+ return false if block.tx.map(&:in).flatten.map {|i| [i.prev_out, i.prev_out_index] }.uniq! != nil
157
+
151
158
  tx_validators.all?{|v|
152
159
  begin
153
160
  v.validate(rules: [:syntax], raise_errors: true)
@@ -158,6 +165,7 @@ module Bitcoin::Validation
158
165
  }
159
166
  end
160
167
 
168
+ # Run all context checks on transactions
161
169
  def transactions_context
162
170
  tx_validators.all?{|v|
163
171
  begin
@@ -169,21 +177,42 @@ module Bitcoin::Validation
169
177
  }
170
178
  end
171
179
 
180
+ # Get validators for all tx objects in the current block
172
181
  def tx_validators
173
- @tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block) }
182
+ @tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block, self)}
183
+ end
184
+
185
+ # Fetch all prev_txs that will be needed for validation
186
+ # Used for optimization in tx validators
187
+ def prev_txs_hash
188
+ @prev_tx_hash ||= (
189
+ inputs = block.tx[1..-1].map {|tx| tx.in }.flatten
190
+ txs = store.get_txs(inputs.map{|i| i.prev_out.reverse_hth })
191
+ Hash[*txs.map {|tx| [tx.hash, tx] }.flatten]
192
+ )
193
+ end
194
+
195
+ # Fetch all prev_outs that already have a next_in, i.e. are already spent.
196
+ def spent_outs_txins
197
+ @spent_outs_txins ||= (
198
+ next_ins = store.get_txins_for_txouts(block.tx[1..-1].map(&:in).flatten.map.with_index {|txin, idx| [txin.prev_out.reverse_hth, txin.prev_out_index] })
199
+ # Only returns next_ins that are in blocks in the main chain
200
+ next_ins.select {|i| store.get_block_id_for_tx_id(i.tx_id) }
201
+ )
174
202
  end
175
203
 
176
204
  def next_bits_required
177
- index = (prev_block.depth + 1) / RETARGET
205
+ retarget = (Bitcoin.network[:retarget_interval] || Bitcoin::RETARGET_INTERVAL)
206
+ index = (prev_block.depth + 1) / retarget
178
207
  max_target = Bitcoin.decode_compact_bits(Bitcoin.network[:proof_of_work_limit]).to_i(16)
179
208
  return Bitcoin.network[:proof_of_work_limit] if index == 0
180
- return prev_block.bits if (prev_block.depth + 1) % RETARGET != 0
209
+ return prev_block.bits if (prev_block.depth + 1) % retarget != 0
181
210
  last = store.db[:blk][hash: prev_block.hash.htb.blob]
182
211
  first = store.db[:blk][hash: last[:prev_hash].blob]
183
- (RETARGET-2).times { first = store.db[:blk][hash: first[:prev_hash].blob] }
212
+ (retarget - 2).times { first = store.db[:blk][hash: first[:prev_hash].blob] }
184
213
 
185
214
  nActualTimespan = last[:time] - first[:time]
186
- nTargetTimespan = RETARGET * 600
215
+ nTargetTimespan = retarget * 600
187
216
 
188
217
  nActualTimespan = [nActualTimespan, nTargetTimespan/4].max
189
218
  nActualTimespan = [nActualTimespan, nTargetTimespan*4].min
@@ -202,11 +231,11 @@ module Bitcoin::Validation
202
231
  end
203
232
 
204
233
  class Tx
205
- attr_accessor :tx, :store, :error
234
+ attr_accessor :tx, :store, :error, :block_validator
206
235
 
207
236
  RULES = {
208
237
  syntax: [:hash, :lists, :max_size, :output_values, :inputs, :lock_time, :standard],
209
- context: [:prev_out, :signatures, :spent, :input_values, :output_sum]
238
+ context: [:prev_out, :signatures, :not_spent, :input_values, :output_sum]
210
239
  }
211
240
 
212
241
  # validate tx rules. +opts+ are:
@@ -236,10 +265,14 @@ module Bitcoin::Validation
236
265
  "b3c19d78b4953b694717a47d9852f8ea1ccd4cf93a45ba2e43a0f97d7cdb2655"
237
266
  ]
238
267
 
239
- # setup new validator for given +tx+, validating context with +store+.
240
- # also needs the +block+ to find prev_outs for chains of tx inside one block.
241
- def initialize tx, store, block = nil
268
+ # Setup new validator for given +tx+, validating context with +store+.
269
+ # Also needs the +block+ that includes the tx to be validated, to find
270
+ # prev_outs for chains of txs inside the block.
271
+ # Optionally accepts the validator object for the block, to optimize fetching
272
+ # prev_txs and checking for doublespends.
273
+ def initialize(tx, store, block = nil, block_validator = nil)
242
274
  @tx, @store, @block, @errors = tx, store, block, []
275
+ @block_validator = block_validator
243
276
  end
244
277
 
245
278
  # check that tx hash matches data
@@ -255,7 +288,7 @@ module Bitcoin::Validation
255
288
 
256
289
  # check that tx size doesn't exceed MAX_BLOCK_SIZE.
257
290
  def max_size
258
- tx.to_payload.bytesize <= MAX_BLOCK_SIZE || [tx.to_payload.bytesize, MAX_BLOCK_SIZE]
291
+ tx.to_payload.bytesize <= Bitcoin::MAX_BLOCK_SIZE || [tx.to_payload.bytesize, Bitcoin::MAX_BLOCK_SIZE]
259
292
  end
260
293
 
261
294
  # check that total output value doesn't exceed MAX_MONEY.
@@ -272,7 +305,7 @@ module Bitcoin::Validation
272
305
 
273
306
  # check that lock_time doesn't exceed INT_MAX
274
307
  def lock_time
275
- tx.lock_time <= INT_MAX || [tx.lock_time, INT_MAX]
308
+ tx.lock_time <= Bitcoin::UINT32_MAX || [tx.lock_time, Bitcoin::UINT32_MAX]
276
309
  end
277
310
 
278
311
  # check that min_size is at least 86 bytes
@@ -308,16 +341,23 @@ module Bitcoin::Validation
308
341
  sigs.all? || sigs.map.with_index {|s, i| s ? nil : i }.compact
309
342
  end
310
343
 
311
- # check that none of the prev_outs are already spent in the main chain
312
- def spent
313
- spent = tx.in.map.with_index {|txin, idx|
314
- next false if @block && @block.tx.include?(prev_txs[idx])
315
- next false unless next_in = prev_txs[idx].out[txin.prev_out_index].get_next_in
316
- next false unless next_tx = next_in.get_tx
317
- next false unless next_block = next_tx.get_block
318
- next_block.chain == Bitcoin::Storage::Backends::StoreBase::MAIN
319
- }
320
- spent.none? || spent.map.with_index {|s, i| s ? i : nil }
344
+ # check that none of the prev_outs are already spent in the main chain or in the current block
345
+ def not_spent
346
+ # if we received cached spents, use it
347
+ return block_validator.spent_outs_txins.empty? if block_validator
348
+
349
+ # find all spent txouts
350
+ next_ins = store.get_txins_for_txouts(tx.in.map.with_index {|txin, idx| [txin.prev_out.reverse_hth, txin.prev_out_index] })
351
+
352
+ # no txouts found spending these txins, we can safely return true
353
+ return true if next_ins.empty?
354
+
355
+ # there were some txouts spending these txins, verify that they are not on the main chain
356
+ next_ins.select! {|i| i.get_tx.blk_id } # blk_id is only set for tx in the main chain
357
+ return true if next_ins.empty?
358
+
359
+ # now we know some txouts are already spent, return tx_idxs for debugging purposes
360
+ return next_ins.map {|i| i.get_prev_out.tx_idx }
321
361
  end
322
362
 
323
363
  # check that the total input value doesn't exceed MAX_MONEY
@@ -341,18 +381,9 @@ module Bitcoin::Validation
341
381
  # only returns tx that are in a block in the main chain or the current block.
342
382
  def prev_txs
343
383
  @prev_txs ||= tx.in.map {|i|
344
- prev_tx = store.get_tx(i.prev_out.reverse_hth)
345
- next prev_tx if store.class.name =~ /UtxoStore/ && prev_tx
346
- next nil if !prev_tx && !@block
347
-
348
- if store.class.name =~ /SequelStore/
349
- block = store.db[:blk][id: prev_tx.blk_id] if prev_tx
350
- next prev_tx if block && block[:chain] == 0
351
- else
352
- next prev_tx if prev_tx && prev_tx.get_block && prev_tx.get_block.chain == 0
353
- end
354
- next nil if !@block
355
- @block.tx.find {|t| t.binary_hash == i.prev_out }
384
+ prev_tx = block_validator ? block_validator.prev_txs_hash[i.prev_out.reverse_hth] : store.get_tx(i.prev_out.reverse_hth)
385
+ next prev_tx if prev_tx && prev_tx.blk_id # blk_id is set only if it's in the main chain
386
+ @block.tx.find {|t| t.binary_hash == i.prev_out } if @block
356
387
  }.compact
357
388
  end
358
389
 
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -24,6 +24,44 @@ describe 'Bitcoin Address/Hash160/PubKey' do
24
24
  .should == "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
25
25
  end
26
26
 
27
+ it 'bitcoin-address from pubkey' do
28
+ Bitcoin::network = :testnet
29
+ Bitcoin.pubkey_to_address("029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4")
30
+ .should == "mu6vSuyvpVxvDAJyZczjxaU56pXLNBSf9C"
31
+ Bitcoin.pubkey_to_address("049e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4b82314814017e4e9b06a0fd8e01772bb410cb1c36cfc2d03079c315bc7494b86")
32
+ .should == "n4bZ82i9SdLj6YauPn3PPKFRhQHMZrdaPq"
33
+
34
+ Bitcoin::network = :bitcoin
35
+ Bitcoin.pubkey_to_address("029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4")
36
+ .should == "1Eay9rtx1UXfS3qMr42N8fFkEpvdR2euvg"
37
+ Bitcoin.pubkey_to_address("049e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4b82314814017e4e9b06a0fd8e01772bb410cb1c36cfc2d03079c315bc7494b86")
38
+ .should == "1Q5bpydAdbuUKS7HgD51ZQ36qQgeiN8cBE"
39
+ end
40
+
41
+ it 'bitcoin p2sh multisig address from pubkeys' do
42
+ Bitcoin::network = :testnet
43
+ address, redeem_script = Bitcoin.pubkeys_to_p2sh_multisig_address(2, "029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4",
44
+ "0299acf23a65c31fe02052d7474769529c21612b1afa56cc149747fe63867592ec")
45
+ address.should == "2NGaiH7MNYWhsWPQKudZEvy8KnoWPfuGPg1"
46
+ redeem_script.hth.should == "52" + # OP_2
47
+ "21" + "029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4" + # pubkey.bytesize + pubkey
48
+ "21" + "0299acf23a65c31fe02052d7474769529c21612b1afa56cc149747fe63867592ec" + # pubkey.bytesize + pubkey
49
+ "52" + # OP_2
50
+ "ae" # OP_CHECKMULTISIG
51
+
52
+ Bitcoin::network = :bitcoin
53
+ address, redeem_script = Bitcoin.pubkeys_to_p2sh_multisig_address(2, "029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4",
54
+ "0299acf23a65c31fe02052d7474769529c21612b1afa56cc149747fe63867592ec",
55
+ "020b16a7227f873ac68cf3140f1101d2eda5acb28bf3e7d546409139caf25142e4")
56
+ address.should == "38eiL6Jac27TVAu83wj81Miso9rXiVqgcP"
57
+ redeem_script.hth.should == "52" + # OP_2
58
+ "21" + "029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4" + # pubkey.bytesize + pubkey
59
+ "21" + "0299acf23a65c31fe02052d7474769529c21612b1afa56cc149747fe63867592ec" + # pubkey.bytesize + pubkey
60
+ "21" + "020b16a7227f873ac68cf3140f1101d2eda5acb28bf3e7d546409139caf25142e4" + # pubkey.bytesize + pubkey
61
+ "53" + # OP_3
62
+ "ae" # OP_CHECKMULTISIG
63
+ end
64
+
27
65
  it 'bitcoin p2sh address from bitcoin-hash160' do
28
66
  Bitcoin::network = :testnet
29
67
  Bitcoin.hash160_to_p2sh_address("d11e2f2f385efeecd30f867f1d55c0bc8a27f29e")
@@ -85,6 +85,29 @@ describe "Bitcoin::Builder" do
85
85
  tx2.out[0].pk_script.should == tx.out[0].pk_script
86
86
  end
87
87
 
88
+ it "should allow txin.prev_out as tx or hash" do
89
+ prev_tx = @block.tx[0]
90
+ tx1 = build_tx do |t|
91
+ t.input {|i| i.prev_out prev_tx, 0 }
92
+ end
93
+ tx2 = build_tx do |t|
94
+ t.input {|i| i.prev_out prev_tx.hash, 0, prev_tx.out[0].pk_script }
95
+ end
96
+ tx1.in[0].should == tx2.in[0]
97
+ end
98
+
99
+
100
+ it "should provide txout#to shortcut" do
101
+ tx1 = build_tx do |t|
102
+ t.output {|o| o.value 123; o.to @keys[1].addr }
103
+ end
104
+ tx2 = build_tx do |t|
105
+ t.output {|o| o.value 123
106
+ o.script {|s| s.recipient @keys[1].addr } }
107
+ end
108
+ tx1.out[0].should == tx2.out[0]
109
+ end
110
+
88
111
  it "should build unsigned transactions and add the signature hash" do
89
112
  tx = build_tx do |t|
90
113
  t.input do |i|
@@ -102,6 +125,106 @@ describe "Bitcoin::Builder" do
102
125
  tx.in[0].sig_hash.should != nil
103
126
  end
104
127
 
128
+ it "should build unsigned multisig transactions and add the signature hash" do
129
+ tx1 = build_tx do |t|
130
+ t.input {|i| i.prev_out(@block.tx[0], 0); i.signature_key(@keys[0]) }
131
+ t.output do |o|
132
+ o.value 123
133
+ o.to [2, *@keys[0..2].map(&:pub)], :multisig
134
+ end
135
+ end
136
+
137
+ tx2 = build_tx do |t|
138
+ t.input do |i|
139
+ i.prev_out tx1, 0
140
+ i.signature_key @keys[0]
141
+ end
142
+ t.output {|o| o.value 123; o.to @keys[0].addr }
143
+ end
144
+
145
+ tx2.is_a?(Bitcoin::P::Tx).should == true
146
+ tx2.in[0].sig_hash.should != nil
147
+ end
148
+
149
+ it "should build unsigned p2sh multisig transactions and add the signature hash" do
150
+ tx1 = build_tx do |t|
151
+ t.input {|i| i.prev_out(@block.tx[0], 0); i.signature_key(@keys[0]) }
152
+ t.output do |o|
153
+ o.value 123
154
+ o.to [2, *@keys[0..2].map(&:pub)], :p2sh_multisig
155
+ end
156
+ end
157
+
158
+ tx2 = build_tx do |t|
159
+ t.input do |i|
160
+ i.prev_out tx1, 0
161
+ i.signature_key @keys[0]
162
+ i.redeem_script tx1.out[0].redeem_script
163
+ end
164
+ t.output {|o| o.value 123; o.to @keys[0].addr }
165
+ end
166
+
167
+ tx2.is_a?(Bitcoin::P::Tx).should == true
168
+ tx2.in[0].sig_hash.should != nil
169
+ end
170
+
171
+ it "should add change output" do
172
+ change_address = Bitcoin::Key.generate.addr
173
+ tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
174
+ change_address: change_address) do |t|
175
+ t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
176
+ t.output {|o| o.value 12345; o.script {|s| s.recipient @keys[1].addr } }
177
+ end
178
+ tx.out.count.should == 2
179
+ tx.out.last.value.should == 50e8 - 12345
180
+ Bitcoin::Script.new(tx.out.last.pk_script).get_address.should == change_address
181
+ end
182
+
183
+ it "should add change output and leave fee" do
184
+ change_address = Bitcoin::Key.generate.addr
185
+ tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
186
+ change_address: change_address, leave_fee: true) do |t|
187
+ t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
188
+ t.output {|o| o.value 12345; o.script {|s| s.recipient @keys[1].addr } }
189
+ end
190
+ tx.out.count.should == 2
191
+ tx.out.last.value.should == 50e8 - 12345 - Bitcoin.network[:min_tx_fee]
192
+ Bitcoin::Script.new(tx.out.last.pk_script).get_address.should == change_address
193
+
194
+ tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
195
+ change_address: change_address, leave_fee: true) do |t|
196
+ t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
197
+ 49.times { t.output {|o| o.value 1e8; o.script {|s| s.recipient @keys[1].addr } } }
198
+ t.output {|o| o.value(1e8 - 10000); o.script {|s| s.recipient @keys[1].addr } }
199
+ end
200
+ tx.out.size.should == 50
201
+ tx.out.map(&:value).inject(:+).should == 50e8 - 10000
202
+ end
203
+
204
+ it "randomize_outputs should not modify output values or fees" do
205
+ change_address = Bitcoin::Key.generate.addr
206
+ tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
207
+ change_address: change_address, leave_fee: true) do |t|
208
+ t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
209
+ t.output {|o| o.value 12345; o.script {|s| s.recipient @keys[1].addr } }
210
+ t.randomize_outputs
211
+ end
212
+
213
+ tx.out.count.should == 2
214
+ tx.out.last.value.should == 50e8 - 12345 - Bitcoin.network[:min_tx_fee]
215
+ Bitcoin::Script.new(tx.out.last.pk_script).get_address.should == change_address
216
+
217
+ tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
218
+ change_address: change_address, leave_fee: true) do |t|
219
+ t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
220
+ 49.times { t.output {|o| o.value 1e8; o.script {|s| s.recipient @keys[1].addr } } }
221
+ t.output {|o| o.value(1e8 - 10000); o.script {|s| s.recipient @keys[1].addr } }
222
+ t.randomize_outputs
223
+ end
224
+ tx.out.size.should == 50
225
+ tx.out.map(&:value).inject(:+).should == 50e8 - 10000
226
+ end
227
+
105
228
  it "should build address script" do
106
229
  key = Bitcoin::Key.generate
107
230
  s = script {|s| s.type :address; s.recipient key.addr }
@@ -121,4 +244,58 @@ describe "Bitcoin::Builder" do
121
244
  Bitcoin::Script.new(s).to_string.should == "1 #{keys[0].pub} #{keys[1].pub} 2 OP_CHECKMULTISIG"
122
245
  end
123
246
 
247
+ it "should build and spend multisig output" do
248
+ tx1 = build_tx do |t|
249
+ t.input {|i| i.prev_out(@block.tx[0], 0); i.signature_key(@keys[0]) }
250
+ t.output do |o|
251
+ o.value 123
252
+ o.to [2, *@keys[0..2].map(&:pub)], :multisig
253
+ end
254
+ end
255
+
256
+ Bitcoin::Script.new(tx1.out[0].pk_script).to_string.should ==
257
+ "2 #{@keys[0..2].map(&:pub).join(' ')} 3 OP_CHECKMULTISIG"
258
+
259
+ tx2 = build_tx do |t|
260
+ t.input do |i|
261
+ i.prev_out tx1, 0
262
+ i.signature_key @keys[0..1]
263
+ end
264
+ t.output {|o| o.value 123; o.to @keys[0].addr }
265
+ end
266
+
267
+ tx2.verify_input_signature(0, tx1).should == true
268
+ end
269
+
270
+ it "should build and spend p2sh multisig output" do
271
+ tx1 = build_tx do |t|
272
+ t.input {|i| i.prev_out(@block.tx[0], 0); i.signature_key(@keys[0]) }
273
+ t.output do |o|
274
+ o.value 123
275
+ o.to [2, *@keys[0..2].map(&:pub)], :p2sh_multisig
276
+ end
277
+ end
278
+
279
+ Bitcoin::Script.new(tx1.out[0].pk_script).to_string.should ==
280
+ "OP_HASH160 #{Bitcoin.hash160(tx1.out[0].redeem_script.hth)} OP_EQUAL"
281
+
282
+ tx2 = build_tx do |t|
283
+ t.input do |i|
284
+ i.prev_out tx1, 0
285
+ # provide 2 required keys for signing
286
+ i.signature_key @keys[0..1]
287
+ # provide the redeem script from the previous output
288
+ i.redeem_script tx1.out[0].redeem_script
289
+ end
290
+
291
+ t.output {|o| o.value 123; o.to @keys[0].addr }
292
+ end
293
+
294
+ script = Bitcoin::Script.new(tx2.in[0].script_sig, tx1.out[0].pk_script)
295
+ # check script execution is valid
296
+ script.run { true }.should == true
297
+ # check signatures are valid
298
+ tx2.verify_input_signature(0, tx1).should == true
299
+ end
300
+
124
301
  end