btcruby 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.travis.yml +7 -0
  4. data/FAQ.md +7 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +18 -0
  7. data/HOWTO.md +17 -0
  8. data/LICENSE +19 -0
  9. data/README.md +59 -0
  10. data/Rakefile +6 -0
  11. data/TODO.txt +40 -0
  12. data/bin/console +19 -0
  13. data/btcruby.gemspec +20 -0
  14. data/documentation/address.md +73 -0
  15. data/documentation/base58.md +52 -0
  16. data/documentation/block.md +127 -0
  17. data/documentation/block_header.md +120 -0
  18. data/documentation/constants.md +88 -0
  19. data/documentation/data.md +54 -0
  20. data/documentation/diagnostics.md +90 -0
  21. data/documentation/extensions.md +76 -0
  22. data/documentation/hash_functions.md +58 -0
  23. data/documentation/hash_id.md +22 -0
  24. data/documentation/index.md +230 -0
  25. data/documentation/key.md +177 -0
  26. data/documentation/keychain.md +180 -0
  27. data/documentation/network.md +75 -0
  28. data/documentation/opcode.md +220 -0
  29. data/documentation/openssl.md +7 -0
  30. data/documentation/p2pkh.md +71 -0
  31. data/documentation/p2sh.md +64 -0
  32. data/documentation/proof_of_work.md +84 -0
  33. data/documentation/script.md +280 -0
  34. data/documentation/signature.md +71 -0
  35. data/documentation/transaction.md +213 -0
  36. data/documentation/transaction_builder.md +188 -0
  37. data/documentation/transaction_input.md +133 -0
  38. data/documentation/transaction_output.md +130 -0
  39. data/documentation/wif.md +72 -0
  40. data/documentation/wire_format.md +70 -0
  41. data/lib/btcruby/address.rb +296 -0
  42. data/lib/btcruby/base58.rb +108 -0
  43. data/lib/btcruby/big_number.rb +47 -0
  44. data/lib/btcruby/block.rb +170 -0
  45. data/lib/btcruby/block_header.rb +231 -0
  46. data/lib/btcruby/constants.rb +59 -0
  47. data/lib/btcruby/currency_formatter.rb +64 -0
  48. data/lib/btcruby/data.rb +98 -0
  49. data/lib/btcruby/diagnostics.rb +92 -0
  50. data/lib/btcruby/errors.rb +8 -0
  51. data/lib/btcruby/extensions.rb +65 -0
  52. data/lib/btcruby/hash_functions.rb +54 -0
  53. data/lib/btcruby/hash_id.rb +18 -0
  54. data/lib/btcruby/key.rb +517 -0
  55. data/lib/btcruby/keychain.rb +464 -0
  56. data/lib/btcruby/network.rb +73 -0
  57. data/lib/btcruby/opcode.rb +197 -0
  58. data/lib/btcruby/open_assets/asset.rb +35 -0
  59. data/lib/btcruby/open_assets/asset_address.rb +49 -0
  60. data/lib/btcruby/open_assets/asset_definition.rb +75 -0
  61. data/lib/btcruby/open_assets/asset_id.rb +24 -0
  62. data/lib/btcruby/open_assets/asset_marker.rb +94 -0
  63. data/lib/btcruby/open_assets/asset_processor.rb +377 -0
  64. data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
  65. data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
  66. data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
  67. data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
  68. data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
  69. data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
  70. data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
  71. data/lib/btcruby/open_assets.rb +26 -0
  72. data/lib/btcruby/openssl.rb +536 -0
  73. data/lib/btcruby/proof_of_work.rb +110 -0
  74. data/lib/btcruby/safety.rb +26 -0
  75. data/lib/btcruby/script.rb +733 -0
  76. data/lib/btcruby/signature_hashtype.rb +37 -0
  77. data/lib/btcruby/transaction.rb +511 -0
  78. data/lib/btcruby/transaction_builder/errors.rb +15 -0
  79. data/lib/btcruby/transaction_builder/provider.rb +54 -0
  80. data/lib/btcruby/transaction_builder/result.rb +73 -0
  81. data/lib/btcruby/transaction_builder/signer.rb +28 -0
  82. data/lib/btcruby/transaction_builder.rb +520 -0
  83. data/lib/btcruby/transaction_input.rb +298 -0
  84. data/lib/btcruby/transaction_outpoint.rb +30 -0
  85. data/lib/btcruby/transaction_output.rb +315 -0
  86. data/lib/btcruby/version.rb +3 -0
  87. data/lib/btcruby/wif.rb +118 -0
  88. data/lib/btcruby/wire_format.rb +362 -0
  89. data/lib/btcruby.rb +44 -2
  90. data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
  91. data/sample_code/creating_a_transaction_manually.rb +44 -0
  92. data/sample_code/generating_an_address.rb +20 -0
  93. data/sample_code/using_transaction_builder.rb +49 -0
  94. data/spec/address_spec.rb +206 -0
  95. data/spec/all.rb +6 -0
  96. data/spec/base58_spec.rb +83 -0
  97. data/spec/block_header_spec.rb +18 -0
  98. data/spec/block_spec.rb +18 -0
  99. data/spec/currency_formatter_spec.rb +46 -0
  100. data/spec/data_spec.rb +50 -0
  101. data/spec/diagnostics_spec.rb +41 -0
  102. data/spec/key_spec.rb +205 -0
  103. data/spec/keychain_spec.rb +261 -0
  104. data/spec/network_spec.rb +48 -0
  105. data/spec/open_assets/asset_address_spec.rb +33 -0
  106. data/spec/open_assets/asset_id_spec.rb +15 -0
  107. data/spec/open_assets/asset_marker_spec.rb +47 -0
  108. data/spec/open_assets/asset_processor_spec.rb +567 -0
  109. data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
  110. data/spec/open_assets/asset_transaction_spec.rb +70 -0
  111. data/spec/proof_of_work_spec.rb +53 -0
  112. data/spec/script_spec.rb +66 -0
  113. data/spec/spec_helper.rb +8 -0
  114. data/spec/transaction_builder_spec.rb +338 -0
  115. data/spec/transaction_spec.rb +162 -0
  116. data/spec/wire_format_spec.rb +283 -0
  117. metadata +141 -7
@@ -0,0 +1,567 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe "Verifying transfer outputs" do
4
+
5
+ def build_asset_transaction(inputs: [], issues: [], transfers: [])
6
+ tx = Transaction.new
7
+ script = PublicKeyAddress.new(hash: "some address".hash160).script
8
+ inputs.each do
9
+ tx.add_input(TransactionInput.new(previous_hash: "".sha256, previous_index: 0))
10
+ end
11
+ issues.each do |tuple|
12
+ tx.add_output(TransactionOutput.new(value: 1, script: script))
13
+ end
14
+ qtys = issues.map{|tuple| tuple.first} + transfers.map{|tuple| tuple.first}
15
+ tx.add_output(AssetMarker.new(quantities: qtys).output)
16
+ transfers.each do |tuple|
17
+ tx.add_output(TransactionOutput.new(value: 1, script: script))
18
+ end
19
+
20
+ atx = AssetTransaction.new(transaction: tx)
21
+ atx.inputs.each_with_index do |ain, i|
22
+ amount, name = inputs[i]
23
+ if amount
24
+ ain.asset_id = asset_id(name)
25
+ ain.value = amount
26
+ ain.verified = true
27
+ else
28
+ ain.asset_id = nil
29
+ ain.value = nil
30
+ ain.verified = true
31
+ end
32
+ end
33
+
34
+ atx
35
+ end
36
+
37
+ def asset_id(name)
38
+ name ? AssetID.new(hash: name.hash160) : nil
39
+ end
40
+
41
+ def asset_transfer_must_be_verified(inputs: [], issues: [], transfers: [])
42
+ atx = build_asset_transaction(inputs: inputs, issues: issues, transfers: transfers)
43
+ result = @processor.verify_transfers(atx)
44
+ if !result
45
+ # Note: when tests are executed in random order, this may contain some leftover messages from other tests.
46
+ #$stderr << Diagnostics.current.last_message
47
+ end
48
+ result.must_equal true
49
+ atx.outputs.map {|aout|
50
+ [aout.verified?, aout.value, aout.asset_id]
51
+ }.must_equal((issues + [nil] + transfers).map {|tuple| # [nil] for marker
52
+ amount, name = tuple
53
+ [true, amount, name ? asset_id(name) : nil]
54
+ })
55
+ end
56
+
57
+ def asset_transfer_must_not_be_verified(inputs: [], issues: [], transfers: [])
58
+ atx = build_asset_transaction(inputs: inputs, issues: issues, transfers: transfers)
59
+ result = @processor.verify_transfers(atx)
60
+ result.must_equal false
61
+ atx.outputs.map {|aout|
62
+ aout.verified?
63
+ }.must_equal((issues + [nil] + transfers).map {|tuple| # [nil] for marker
64
+ amount, name, failed = tuple
65
+ !failed
66
+ })
67
+ end
68
+
69
+ before do
70
+ @processor = AssetProcessor.new(source: :NOT_USED)
71
+ end
72
+
73
+ it "should support transferring asset with overlapping and underlapping" do
74
+ Diagnostics.current.trace do
75
+ asset_transfer_must_be_verified(
76
+ inputs: [ [100, "A"], [50, "A"], [10, "A"], [30, "B"], [14, "B"] ],
77
+ issues: [ ],
78
+ transfers: [ [125, "A"], [35, "A"], [44, "B"] ]
79
+ )
80
+ end
81
+ end
82
+
83
+ it "should support leftover whole assets" do
84
+ asset_transfer_must_be_verified(
85
+ inputs: [ [100, "A"], [50, "A"] ],
86
+ issues: [ ],
87
+ transfers: [ [100, "A"] ]
88
+ )
89
+ end
90
+
91
+ it "should support leftover partial assets" do
92
+ asset_transfer_must_be_verified(
93
+ inputs: [ [100, "A"], [50, "A"] ],
94
+ issues: [ ],
95
+ transfers: [ [125, "A"] ]
96
+ )
97
+ end
98
+
99
+ it "should fail when not enough units" do
100
+ asset_transfer_must_not_be_verified(
101
+ inputs: [ [100, "A"], [100, "A"] ],
102
+ issues: [ ],
103
+ transfers: [ [150, "A"], [150, "A", :fail], [50, "B", :fail] ]
104
+ )
105
+ end
106
+
107
+ it "should fail when assets are mixed" do
108
+ asset_transfer_must_not_be_verified(
109
+ inputs: [ [100, "A"], [100, "B"] ],
110
+ issues: [ ],
111
+ transfers: [ [50, "A"], [150, "A", :fail] ]
112
+ )
113
+ end
114
+
115
+ end
116
+
117
+
118
+ describe "Verifying a chain of transactions" do
119
+
120
+ class InMemoryTxSource
121
+ include AssetProcessorSource
122
+ def initialize
123
+ @txs = {}
124
+ end
125
+ def add_transaction(tx)
126
+ @txs[tx.transaction_hash] = tx
127
+ end
128
+ def transaction_for_hash(hash)
129
+ @txs[hash]
130
+ end
131
+ def inspect
132
+ "#<InMemoryTxSource:#{@txs.size} txs:\n#{@txs.map{|h,t| h.to_hex + ": #{t.inspect}" }.join("\n")}\n>"
133
+ end
134
+ end
135
+
136
+ def build_asset_transaction(inputs: [], issues: [], transfers: [])
137
+ tx = Transaction.new
138
+ script = PublicKeyAddress.new(hash: "some address".hash160).script
139
+ inputs.each do
140
+ tx.add_input(TransactionInput.new(previous_hash: "".sha256, previous_index: 0))
141
+ end
142
+ issues.each do |tuple|
143
+ tx.add_output(TransactionOutput.new(value: 1, script: script))
144
+ end
145
+ qtys = issues.map{|tuple| tuple.first} + transfers.map{|tuple| tuple.first}
146
+ tx.add_output(AssetMarker.new(quantities: qtys).output)
147
+ transfers.each do |tuple|
148
+ tx.add_output(TransactionOutput.new(value: 1, script: script))
149
+ end
150
+
151
+ atx = AssetTransaction.new(transaction: tx)
152
+ atx.inputs.each_with_index do |ain, i|
153
+ amount, name = inputs[i]
154
+ if amount
155
+ ain.asset_id = asset_id(name)
156
+ ain.value = amount
157
+ ain.verified = true
158
+ else
159
+ ain.asset_id = nil
160
+ ain.value = nil
161
+ ain.verified = true
162
+ end
163
+ end
164
+
165
+ atx
166
+ end
167
+
168
+ def asset_id(name)
169
+ name ? AssetID.new(hash: name.hash160) : nil
170
+ end
171
+
172
+ def make_transaction(inputs: [], outputs: [])
173
+ tx = Transaction.new
174
+ inputs.each do |inp|
175
+ txout = inp
176
+ tx.add_input(TransactionInput.new(previous_hash: txout.transaction_hash,
177
+ previous_index: txout.index))
178
+ end
179
+ payment_outputs = []
180
+ issue_outputs = []
181
+ transfer_outputs = []
182
+ qtys = []
183
+ outputs.each do |out|
184
+ if out[:issue]
185
+ issue_outputs << out
186
+ qtys << out[:issue]
187
+ elsif out[:transfer]
188
+ transfer_outputs << out
189
+ qtys << out[:transfer]
190
+ else
191
+ payment_outputs << out
192
+ end
193
+ end
194
+ issue_outputs.each do |out|
195
+ tx.add_output(TransactionOutput.new(value: out[:btc], script: Address.parse(out[:address]).script))
196
+ end
197
+ if qtys.size > 0
198
+ tx.add_output(AssetMarker.new(quantities: qtys).output)
199
+ end
200
+ (transfer_outputs + payment_outputs).each do |out|
201
+ tx.add_output(TransactionOutput.new(value: out[:btc], script: Address.parse(out[:address]).script))
202
+ end
203
+ tx
204
+ end
205
+
206
+ before do
207
+ @source = InMemoryTxSource.new
208
+ @processor = AssetProcessor.new(source: @source)
209
+ end
210
+
211
+ it "should verify a simple issuance chain (parent + child transaction)" do
212
+ issue_address = Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX")
213
+ asset_id = AssetID.new(script: issue_address.script)
214
+ tx1 = make_transaction(outputs: [
215
+ {btc: 10_000, address: "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"}
216
+ ])
217
+ tx2 = make_transaction(
218
+ inputs: [
219
+ tx1.outputs[0]
220
+ ],
221
+ outputs: [
222
+ {btc: 3000, issue: 42_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
223
+ {btc: 7000, address: "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"},
224
+ ]
225
+ )
226
+ @source.add_transaction(tx1)
227
+ atx = AssetTransaction.new(transaction: tx2)
228
+
229
+ Diagnostics.current.trace do
230
+ @processor.verify_asset_transaction(atx).must_equal(true)
231
+ atx.outputs.map {|aout|
232
+ [aout.verified?, aout.value, aout.asset_id]
233
+ }.must_equal([
234
+ [true, 42_000, asset_id],
235
+ [true, nil, nil],
236
+ [true, nil, nil],
237
+ ])
238
+ end
239
+ end
240
+
241
+
242
+ it "should verify a transfer chain" do
243
+ issue_address = Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX")
244
+ asset_id = AssetID.new(script: issue_address.script)
245
+ tx1 = make_transaction(outputs: [
246
+ {btc: 10_000, address: "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"}
247
+ ])
248
+ tx2 = make_transaction(
249
+ inputs: [
250
+ tx1.outputs[0]
251
+ ],
252
+ outputs: [
253
+ {btc: 3000, issue: 42_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
254
+ {btc: 7000, address: "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"},
255
+ ]
256
+ )
257
+ tx3 = make_transaction(
258
+ inputs: [
259
+ tx2.outputs[0]
260
+ ],
261
+ outputs: [
262
+ {btc: 3000, transfer: 10_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
263
+ {btc: 3000, transfer: 32_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
264
+ ]
265
+ )
266
+ tx4 = make_transaction(
267
+ inputs: [
268
+ # marker is output 0
269
+ tx3.outputs[1],
270
+ tx3.outputs[2],
271
+ ],
272
+ outputs: [
273
+ {btc: 3000, transfer: 40_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
274
+ {btc: 3000, transfer: 2_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
275
+ ]
276
+ )
277
+ @source.add_transaction(tx1)
278
+ @source.add_transaction(tx2)
279
+ @source.add_transaction(tx3)
280
+ atx = AssetTransaction.new(transaction: tx4)
281
+
282
+ Diagnostics.current.trace do
283
+ result = @processor.verify_asset_transaction(atx)
284
+ result.must_equal(true)
285
+ atx.outputs.map {|aout|
286
+ [aout.verified?, aout.value, aout.asset_id]
287
+ }.must_equal([
288
+ [true, nil, nil],
289
+ [true, 40_000, asset_id],
290
+ [true, 2_000, asset_id],
291
+ ])
292
+ end
293
+ end
294
+
295
+ it "should fail to verify an incorrect transfer chain" do
296
+ issue_address = Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX")
297
+ asset_id = AssetID.new(script: issue_address.script)
298
+ tx1 = make_transaction(outputs: [
299
+ {btc: 10_000, address: "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"}
300
+ ])
301
+ tx2 = make_transaction(
302
+ inputs: [
303
+ tx1.outputs[0]
304
+ ],
305
+ outputs: [
306
+ # HERE WE HAVE 12_000 issued instead of 42_000.
307
+ {btc: 3000, issue: 12_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
308
+ {btc: 7000, address: "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX"},
309
+ ]
310
+ )
311
+ tx3 = make_transaction(
312
+ inputs: [
313
+ tx2.outputs[0] # issue output
314
+ ],
315
+ outputs: [
316
+ {btc: 3000, transfer: 10_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
317
+ {btc: 3000, transfer: 32_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
318
+ ]
319
+ )
320
+ tx4 = make_transaction(
321
+ inputs: [
322
+ # marker is 0
323
+ tx3.outputs[1],
324
+ tx3.outputs[2],
325
+ ],
326
+ outputs: [
327
+ {btc: 3000, transfer: 40_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
328
+ {btc: 3000, transfer: 2_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
329
+ ]
330
+ )
331
+ @source.add_transaction(tx1)
332
+ @source.add_transaction(tx2)
333
+ @source.add_transaction(tx3)
334
+ atx = AssetTransaction.new(transaction: tx4)
335
+
336
+ #Diagnostics.current.trace do
337
+ result = @processor.verify_asset_transaction(atx)
338
+ result.must_equal(false)
339
+ atx.outputs.map {|aout|
340
+ [aout.verified?, aout.value, aout.asset_id]
341
+ }.must_equal([
342
+ [true, nil, nil],
343
+ [false, 40_000, nil],
344
+ [false, 2_000, nil],
345
+ ])
346
+ #end
347
+ end
348
+
349
+ it "should verify a transfer chain with multiple assets" do
350
+ issue_address1 = Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX")
351
+ issue_address2 = Address.parse("3GkKDgJAWJnizg6Tz7DBM8uDtdHtrUkQ2X")
352
+ asset_id1 = AssetID.new(script: issue_address1.script)
353
+ asset_id2 = AssetID.new(script: issue_address2.script)
354
+
355
+ tx1 = make_transaction(outputs: [
356
+ {btc: 10_000, address: issue_address1}
357
+ ])
358
+ tx2 = make_transaction(outputs: [
359
+ {btc: 10_000, address: issue_address2}
360
+ ])
361
+ # Issue 42K of asset 1
362
+ tx3 = make_transaction(
363
+ inputs: [
364
+ tx1.outputs[0],
365
+ ],
366
+ outputs: [
367
+ {btc: 3000, issue: 20_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
368
+ {btc: 3000, issue: 12_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
369
+ {btc: 3000, issue: 10_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
370
+ ]
371
+ )
372
+ # Issue 500K of asset 2
373
+ tx4 = make_transaction(
374
+ inputs: [
375
+ tx2.outputs[0],
376
+ ],
377
+ outputs: [
378
+ {btc: 3000, issue: 200_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
379
+ {btc: 3000, issue: 300_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
380
+ ]
381
+ )
382
+ # This transfers both assets
383
+ tx5 = make_transaction(
384
+ inputs: [
385
+ tx3.outputs[0],
386
+ tx3.outputs[1],
387
+ tx3.outputs[2],
388
+ tx4.outputs[0],
389
+ tx4.outputs[1],
390
+ ],
391
+ outputs: [
392
+ {btc: 3000, transfer: 30_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
393
+ {btc: 3000, transfer: 12_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
394
+ {btc: 3000, transfer: 500_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
395
+ ]
396
+ )
397
+ tx61 = make_transaction(
398
+ inputs: [
399
+ tx5.outputs[1],
400
+ ],
401
+ outputs: [
402
+ {btc: 3000, transfer: 10_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
403
+ {btc: 3000, transfer: 20_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
404
+ ]
405
+ )
406
+ tx6 = (1..100).inject(tx61) do |a, _|
407
+ b = make_transaction(
408
+ inputs: [
409
+ # marker is 0
410
+ a.outputs[1],
411
+ a.outputs[2],
412
+ ],
413
+ outputs: [
414
+ {btc: 3000, transfer: 10_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
415
+ {btc: 3000, transfer: 20_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
416
+ ]
417
+ )
418
+ @source.add_transaction(a)
419
+ @source.add_transaction(b)
420
+ b
421
+ end
422
+
423
+ # Issue 50K of asset 2
424
+ tx7 = make_transaction(outputs: [
425
+ {btc: 10_000, address: issue_address2}
426
+ ])
427
+ tx8 = make_transaction(
428
+ inputs: [
429
+ tx7.outputs[0],
430
+ ],
431
+ outputs: [
432
+ {btc: 3000, issue: 1, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
433
+ {btc: 3000, issue: 50_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
434
+ ]
435
+ )
436
+ # Create a long chain spending from tx21
437
+ tx9 = (1..100).inject(tx8) do |a, _|
438
+ b = make_transaction(
439
+ inputs: [
440
+ # marker is 0 and for tx8 it'll use second issue output so we don't have to have special case
441
+ a.outputs[1],
442
+ ],
443
+ outputs: [
444
+ {btc: 3000, transfer: 50_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
445
+ ]
446
+ )
447
+ @source.add_transaction(b)
448
+ b
449
+ end
450
+
451
+ # Final transaction
452
+ tx_final = make_transaction(
453
+ inputs: [
454
+ tx5.outputs[3],
455
+ tx5.outputs[2],
456
+ tx6.outputs[2],
457
+ tx9.outputs[1],
458
+ ],
459
+ outputs: [
460
+ {btc: 3000, transfer: 500_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
461
+ {btc: 3000, transfer: 30_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
462
+ {btc: 3000, transfer: 2_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
463
+ {btc: 3000, transfer: 50_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
464
+ ]
465
+ )
466
+ @source.add_transaction(tx1)
467
+ @source.add_transaction(tx2)
468
+ @source.add_transaction(tx3)
469
+ @source.add_transaction(tx4)
470
+ @source.add_transaction(tx5)
471
+ @source.add_transaction(tx6)
472
+ @source.add_transaction(tx7)
473
+ @source.add_transaction(tx8)
474
+ @source.add_transaction(tx9)
475
+ atx = AssetTransaction.new(transaction: tx_final)
476
+
477
+ Diagnostics.current.trace do
478
+ result = @processor.verify_asset_transaction(atx)
479
+ result.must_equal(true)
480
+
481
+ atx.outputs[0].value.must_equal nil
482
+ atx.outputs[1].value.must_equal 500_000
483
+ atx.outputs[2].value.must_equal 30_000
484
+ atx.outputs[3].value.must_equal 2_000
485
+
486
+ atx.outputs.map {|aout|
487
+ [aout.verified?, aout.value, aout.asset_id]
488
+ }.must_equal([
489
+ [true, nil, nil],
490
+ [true, 500_000, asset_id2],
491
+ [true, 30_000, asset_id1],
492
+ [true, 2_000, asset_id1],
493
+ [true, 50_000, asset_id2],
494
+ ])
495
+ end
496
+ end
497
+
498
+ it "should fail to verify a mix of assets in one output" do
499
+ issue_address1 = Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX")
500
+ issue_address2 = Address.parse("3GkKDgJAWJnizg6Tz7DBM8uDtdHtrUkQ2X")
501
+ asset_id1 = AssetID.new(script: issue_address1.script)
502
+ asset_id2 = AssetID.new(script: issue_address2.script)
503
+
504
+ tx1 = make_transaction(outputs: [
505
+ {btc: 10_000, address: issue_address1}
506
+ ])
507
+ tx2 = make_transaction(outputs: [
508
+ {btc: 10_000, address: issue_address2}
509
+ ])
510
+ # Issue 42K of asset 1
511
+ tx3 = make_transaction(
512
+ inputs: [
513
+ tx1.outputs[0],
514
+ ],
515
+ outputs: [
516
+ {btc: 3000, issue: 20_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
517
+ {btc: 3000, issue: 12_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
518
+ {btc: 3000, issue: 10_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
519
+ ]
520
+ )
521
+ # Issue 500K of asset 2
522
+ tx4 = make_transaction(
523
+ inputs: [
524
+ tx2.outputs[0],
525
+ ],
526
+ outputs: [
527
+ {btc: 3000, issue: 200_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
528
+ {btc: 3000, issue: 300_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
529
+ ]
530
+ )
531
+ # This transfers both assets
532
+ tx5 = make_transaction(
533
+ inputs: [
534
+ tx3.outputs[0],
535
+ tx3.outputs[1],
536
+ tx3.outputs[2],
537
+ tx4.outputs[0],
538
+ tx4.outputs[1],
539
+ ],
540
+ outputs: [
541
+ {btc: 3000, transfer: 30_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
542
+
543
+ # This one tries to get two kinds of assets in the same output.
544
+ {btc: 3000, transfer: 112_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
545
+ {btc: 3000, transfer: 400_000, address: "akB4NBW9UuCmHuepksob6yfZs6naHtRCPNy"},
546
+ ]
547
+ )
548
+ @source.add_transaction(tx1)
549
+ @source.add_transaction(tx2)
550
+ @source.add_transaction(tx3)
551
+ @source.add_transaction(tx4)
552
+ atx = AssetTransaction.new(transaction: tx5)
553
+
554
+ #Diagnostics.current.trace do
555
+ result = @processor.verify_asset_transaction(atx)
556
+ result.must_equal(false)
557
+ atx.outputs.map {|aout|
558
+ [aout.verified?, aout.value, aout.asset_id]
559
+ }.must_equal([
560
+ [true, nil, nil],
561
+ [true, 30_000, asset_id1],
562
+ [false, 112_000, asset_id1],
563
+ [false, 400_000, nil],
564
+ ])
565
+ #end
566
+ end
567
+ end