btcruby 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +18 -0
- data/.travis.yml +7 -0
- data/FAQ.md +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +18 -0
- data/HOWTO.md +17 -0
- data/LICENSE +19 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/TODO.txt +40 -0
- data/bin/console +19 -0
- data/btcruby.gemspec +20 -0
- data/documentation/address.md +73 -0
- data/documentation/base58.md +52 -0
- data/documentation/block.md +127 -0
- data/documentation/block_header.md +120 -0
- data/documentation/constants.md +88 -0
- data/documentation/data.md +54 -0
- data/documentation/diagnostics.md +90 -0
- data/documentation/extensions.md +76 -0
- data/documentation/hash_functions.md +58 -0
- data/documentation/hash_id.md +22 -0
- data/documentation/index.md +230 -0
- data/documentation/key.md +177 -0
- data/documentation/keychain.md +180 -0
- data/documentation/network.md +75 -0
- data/documentation/opcode.md +220 -0
- data/documentation/openssl.md +7 -0
- data/documentation/p2pkh.md +71 -0
- data/documentation/p2sh.md +64 -0
- data/documentation/proof_of_work.md +84 -0
- data/documentation/script.md +280 -0
- data/documentation/signature.md +71 -0
- data/documentation/transaction.md +213 -0
- data/documentation/transaction_builder.md +188 -0
- data/documentation/transaction_input.md +133 -0
- data/documentation/transaction_output.md +130 -0
- data/documentation/wif.md +72 -0
- data/documentation/wire_format.md +70 -0
- data/lib/btcruby/address.rb +296 -0
- data/lib/btcruby/base58.rb +108 -0
- data/lib/btcruby/big_number.rb +47 -0
- data/lib/btcruby/block.rb +170 -0
- data/lib/btcruby/block_header.rb +231 -0
- data/lib/btcruby/constants.rb +59 -0
- data/lib/btcruby/currency_formatter.rb +64 -0
- data/lib/btcruby/data.rb +98 -0
- data/lib/btcruby/diagnostics.rb +92 -0
- data/lib/btcruby/errors.rb +8 -0
- data/lib/btcruby/extensions.rb +65 -0
- data/lib/btcruby/hash_functions.rb +54 -0
- data/lib/btcruby/hash_id.rb +18 -0
- data/lib/btcruby/key.rb +517 -0
- data/lib/btcruby/keychain.rb +464 -0
- data/lib/btcruby/network.rb +73 -0
- data/lib/btcruby/opcode.rb +197 -0
- data/lib/btcruby/open_assets/asset.rb +35 -0
- data/lib/btcruby/open_assets/asset_address.rb +49 -0
- data/lib/btcruby/open_assets/asset_definition.rb +75 -0
- data/lib/btcruby/open_assets/asset_id.rb +24 -0
- data/lib/btcruby/open_assets/asset_marker.rb +94 -0
- data/lib/btcruby/open_assets/asset_processor.rb +377 -0
- data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
- data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
- data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
- data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
- data/lib/btcruby/open_assets.rb +26 -0
- data/lib/btcruby/openssl.rb +536 -0
- data/lib/btcruby/proof_of_work.rb +110 -0
- data/lib/btcruby/safety.rb +26 -0
- data/lib/btcruby/script.rb +733 -0
- data/lib/btcruby/signature_hashtype.rb +37 -0
- data/lib/btcruby/transaction.rb +511 -0
- data/lib/btcruby/transaction_builder/errors.rb +15 -0
- data/lib/btcruby/transaction_builder/provider.rb +54 -0
- data/lib/btcruby/transaction_builder/result.rb +73 -0
- data/lib/btcruby/transaction_builder/signer.rb +28 -0
- data/lib/btcruby/transaction_builder.rb +520 -0
- data/lib/btcruby/transaction_input.rb +298 -0
- data/lib/btcruby/transaction_outpoint.rb +30 -0
- data/lib/btcruby/transaction_output.rb +315 -0
- data/lib/btcruby/version.rb +3 -0
- data/lib/btcruby/wif.rb +118 -0
- data/lib/btcruby/wire_format.rb +362 -0
- data/lib/btcruby.rb +44 -2
- data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
- data/sample_code/creating_a_transaction_manually.rb +44 -0
- data/sample_code/generating_an_address.rb +20 -0
- data/sample_code/using_transaction_builder.rb +49 -0
- data/spec/address_spec.rb +206 -0
- data/spec/all.rb +6 -0
- data/spec/base58_spec.rb +83 -0
- data/spec/block_header_spec.rb +18 -0
- data/spec/block_spec.rb +18 -0
- data/spec/currency_formatter_spec.rb +46 -0
- data/spec/data_spec.rb +50 -0
- data/spec/diagnostics_spec.rb +41 -0
- data/spec/key_spec.rb +205 -0
- data/spec/keychain_spec.rb +261 -0
- data/spec/network_spec.rb +48 -0
- data/spec/open_assets/asset_address_spec.rb +33 -0
- data/spec/open_assets/asset_id_spec.rb +15 -0
- data/spec/open_assets/asset_marker_spec.rb +47 -0
- data/spec/open_assets/asset_processor_spec.rb +567 -0
- data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
- data/spec/open_assets/asset_transaction_spec.rb +70 -0
- data/spec/proof_of_work_spec.rb +53 -0
- data/spec/script_spec.rb +66 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/transaction_builder_spec.rb +338 -0
- data/spec/transaction_spec.rb +162 -0
- data/spec/wire_format_spec.rb +283 -0
- 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
|