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,377 @@
1
+ # Implementation of OpenAssets protocol.
2
+ # https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki
3
+ require_relative 'asset_transaction.rb'
4
+ module BTC
5
+ # AssetProcessor implements verification and discovery of assets.
6
+ # This assumes that underlying Bitcoin transactions are valid and not double-spent.
7
+ # Only OpenAssets validation is applied to determine which outputs are holding which assets and how much.
8
+ class AssetProcessor
9
+
10
+ # AssetProcessorSource instance that provides transactions.
11
+ attr_accessor :source
12
+
13
+ # Network to use for encoding AssetIDs. Default is `Network.default`.
14
+ attr_accessor :network
15
+
16
+ def initialize(source: nil, network: nil)
17
+ raise ArgumentError, "Source is missing." if !source
18
+ @source = source
19
+ @network = network || Network.default
20
+ end
21
+
22
+ # Scans backwards and validates every AssetTransaction on the way.
23
+ # Does not verify Bitcoin transactions (assumes amounts and scripts are already validated).
24
+ # Updates verified flags on the asset transaction.
25
+ # Returns `true` if asset transaction is verified succesfully.
26
+ # Returns `false` otherwise.
27
+ def verify_asset_transaction(asset_transaction)
28
+ raise ArgumentError, "Asset Transaction is missing" if !asset_transaction
29
+
30
+ # Perform a depth-first scanning.
31
+ # When completed, we'll only have transactions that have all previous txs fully verified.
32
+ atx_stack = [ asset_transaction ]
33
+ i = 0
34
+ max_size = atx_stack.size
35
+ while i < atx_stack.size # note: array dynamically changes as we scan it
36
+ atx = atx_stack[i]
37
+ BTC::Invariant(atx.is_a?(AssetTransaction), "Must be AssetTransaction instance")
38
+
39
+ more_atxs_to_verify = partial_verify_asset_transaction(atx)
40
+ if more_atxs_to_verify == nil # Validation failed - return immediately
41
+ return false
42
+ elsif more_atxs_to_verify.size == 0 # atx is fully verifiable (issue-only or we used cached parents), remove it from the list
43
+
44
+ # outputs may not be all verified because they can be transfers with cached verified inputs.
45
+ # so we need to verify local balance and apply asset ids from inputs to outputs
46
+ if !verify_transfers(atx)
47
+ return false
48
+ end
49
+
50
+ if i == 0 # that was the topmost transaction, we are done.
51
+ return true
52
+ end
53
+
54
+ @source.asset_transaction_was_verified(atx)
55
+ atx_stack.delete_at(i)
56
+ i -= 1
57
+ # If this is was the last parent to check, then the previous item would be :parents marker
58
+ # Once we validate the child behind that marker, we might have another child or the marker again.
59
+ # Unroll the stack until the previous item is not a child with all parents verified.
60
+ while (child_atx = atx_stack[i]) == :parents
61
+ BTC::Invariant(i >= 1, ":parents marker should be preceded by an asset transaction")
62
+ atx_stack.delete_at(i)
63
+ i -= 1
64
+ child_atx = atx_stack.delete_at(i)
65
+
66
+ # Now all inputs are verified, we only need to verify the transfer outputs against them.
67
+ # This will make outputs verified for the later transactions (earlier in the list).
68
+ if !verify_transfers(child_atx)
69
+ return false
70
+ end
71
+
72
+ # Now transaction is fully verified.
73
+ # Source can cache it if needed.
74
+ @source.asset_transaction_was_verified(child_atx)
75
+
76
+ if i == 0 # this was the topmost child, return
77
+ return true
78
+ end
79
+ i -= 1
80
+ end
81
+ else
82
+ # we have more things to verify - dig inside these transactions
83
+ # Start with the last one so once any tx is verifed, we can move back and color inputs of the child transaction.
84
+ atx_stack.insert(i+1, :parents, *more_atxs_to_verify)
85
+ max_size = atx_stack.size if atx_stack.size > max_size
86
+ j = i
87
+ i += more_atxs_to_verify.size + 1
88
+ end
89
+ end
90
+ return true
91
+ end
92
+
93
+ def color_transaction_inputs(atx)
94
+ atx.inputs.each do |ain|
95
+ if !ain.verified?
96
+ prev_atx = ain.previous_asset_transaction
97
+ BTC::Invariant(!!prev_atx, "Must have previous asset transaction")
98
+ if !prev_atx.verified?
99
+ #puts "\n Prev ATX not fully verified: #{prev_atx.inspect} -> input #{ain.index} of #{atx.inspect}"
100
+ end
101
+ BTC::Invariant(prev_atx.verified?, "Must have previous asset transaction outputs fully verified")
102
+ output = prev_atx.outputs[ain.transaction_input.previous_index]
103
+ BTC::Invariant(output && output.verified?, "Must have a valid reference to a previous verified output")
104
+ # Copy over color information. The output can be colored or uncolored.
105
+ ain.asset_id = output.asset_id
106
+ ain.value = output.value
107
+ ain.verified = true
108
+ end
109
+ end
110
+ end
111
+
112
+
113
+ # Returns a list of asset transactions remaining to verify.
114
+ # Returns an empty array if verification succeeded and there is nothing more to verify.
115
+ # Returns `nil` if verification failed.
116
+ def partial_verify_asset_transaction(asset_transaction)
117
+ raise ArgumentError, "Asset Transaction is missing" if !asset_transaction
118
+
119
+ # 1. Verify issuing transactions and collect transfer outputs
120
+ cache_transactions do
121
+ if !verify_issues(asset_transaction)
122
+ return nil
123
+ end
124
+
125
+ # No transfer outputs, this transaction is verified.
126
+ # If there are assets on some inputs, they are destroyed.
127
+ if asset_transaction.transfer_outputs.size == 0
128
+ # We keep inputs unverified to indicate that they were not even processed.
129
+ return []
130
+ end
131
+
132
+ # 2. Fetch parent transactions to verify.
133
+ # * Verify inputs from non-OpenAsset transactions.
134
+ # * Return OA transactions for verification.
135
+ # * Link each input to its OA transaction output.
136
+ prev_unverified_atxs_by_hash = {}
137
+ asset_transaction.inputs.each do |ain|
138
+ txin = ain.transaction_input
139
+ # Ask source if it has a cached verified transaction for this input.
140
+ prev_atx = @source.verified_asset_transaction_for_hash(txin.previous_hash)
141
+ if prev_atx
142
+ BTC::Invariant(prev_atx.verified?, "Cached verified tx must be fully verified")
143
+ end
144
+ prev_atx ||= prev_unverified_atxs_by_hash[txin.previous_hash]
145
+ if !prev_atx
146
+ prev_tx = transaction_for_input(txin)
147
+ if !prev_tx
148
+ Diagnostics.current.add_message("Failed to load previous transaction for input #{ain.index}: #{txin.previous_id}")
149
+ return nil
150
+ end
151
+ begin
152
+ prev_atx = AssetTransaction.new(transaction: prev_tx)
153
+ prev_unverified_atxs_by_hash[prev_atx.transaction_hash] = prev_atx
154
+ rescue FormatError => e
155
+ # Previous transaction is not a valid Open Assets transaction,
156
+ # so we mark the input as uncolored and verified as such.
157
+ ain.asset_id = nil
158
+ ain.value = nil
159
+ ain.verified = true
160
+ end
161
+ end
162
+ # Remember a reference to this transaction so we can validate the whole `asset_transaction` when all previous ones are set and verified.
163
+ ain.previous_asset_transaction = prev_atx
164
+ end # each input
165
+
166
+ # Return all unverified transactions.
167
+ # Note: this won't include the already verified one.
168
+ prev_unverified_atxs_by_hash.values
169
+ end
170
+ end
171
+
172
+ # Attempts to verify issues. Fetches parent transactions to determine AssetID.
173
+ # Returns `true` if verified all issue outputs.
174
+ # Returns `false` if previous tx defining AssetID is not found.
175
+ def verify_issues(asset_transaction)
176
+ previous_txout = nil # fetch only when we have > 0 issue outputs
177
+ asset_transaction.outputs.each do |aout|
178
+ if !aout.verified?
179
+ if aout.value && aout.value > 0
180
+ if aout.issue?
181
+ previous_txout ||= transaction_output_for_input(asset_transaction.inputs[0].transaction_input)
182
+ if !previous_txout
183
+ Diagnostics.current.add_message("Failed to assign AssetID to issue output #{aout.index}: can't find output for input #0")
184
+ return false
185
+ end
186
+ aout.asset_id = AssetID.new(script: previous_txout.script, network: self.network)
187
+ # Output issues some known asset and amount and therefore it is verified.
188
+ aout.verified = true
189
+ else
190
+ # Transfer outputs must be matched with known asset ids on the inputs.
191
+ end
192
+ else
193
+ # Output without a value is uncolored.
194
+ aout.asset_id = nil
195
+ aout.value = nil
196
+ aout.verified = true
197
+ end
198
+ end
199
+ end
200
+ true
201
+ end # verify_issues
202
+
203
+ # Attempts to verify transfer transactions assuming all inputs are verified.
204
+ # Returns `true` if all transfers are verified (also updates `verified` and `asset_id` on them).
205
+ # Returns `false` if any transfer is invalid or some inputs are not verified.
206
+ def verify_transfers(asset_transaction)
207
+ # Do not verify colors on inputs if no transfers occur.
208
+ # Typically it's an issuance tx. If there are assets on inputs, they are destroyed.
209
+ if asset_transaction.transfer_outputs.size == 0
210
+ return true
211
+ end
212
+
213
+ color_transaction_inputs(asset_transaction)
214
+
215
+ current_asset_id = nil
216
+
217
+ inputs = asset_transaction.inputs.dup
218
+ current_asset_id = nil
219
+ current_input = nil
220
+ current_input_remainder = 0
221
+
222
+ asset_transaction.outputs.each do |aout|
223
+ # Only check outputs that can be colored (value > 0) and are transfer outputs (after the marker)
224
+ if aout.has_value? && !aout.marker? && !aout.issue?
225
+
226
+ aout.asset_id = nil
227
+ remaining_value = aout.value
228
+
229
+ # Try to fill in the output with available inputs.
230
+ while remaining_value > 0
231
+
232
+ BTC::Invariant((current_input_remainder == 0) ? (current_input == nil) : true,
233
+ "Remainder must be == 0 only when current_input is nil")
234
+
235
+ BTC::Invariant((current_input_remainder > 0) ? (current_input && current_input.colored?) : true,
236
+ "Remainder must be > 0 only when transfer input is colored")
237
+
238
+ current_input ||= inputs.shift
239
+
240
+ # skip uncolored inputs
241
+ while current_input && !current_input.colored?
242
+ current_input = inputs.shift
243
+ end
244
+
245
+ if !current_input
246
+ Diagnostics.current.add_message("Failed to assign AssetID to transfer output #{aout.index}: not enough colored inputs (#{remaining_value} missing for output #{aout.index}).")
247
+ return false
248
+ end
249
+
250
+ # Need to consume aout.value units from inputs and extract the asset ID
251
+ if !current_input.verified?
252
+ Diagnostics.current.add_message("Failed to assign AssetID to transfer output #{aout.index}: input #{current_input.index} is not verified.")
253
+ return false
254
+ end
255
+
256
+ # Make sure asset ID matches.
257
+ # If output gets assigned units from 2 or more inputs, all asset ids must be the same.
258
+ if !aout.asset_id
259
+ aout.asset_id = current_input.asset_id
260
+ else
261
+ if aout.asset_id != current_input.asset_id
262
+ Diagnostics.current.add_message("Failed to assign AssetID to transfer output #{aout.index}: already assigned another AssetID.")
263
+ return false
264
+ end
265
+ end
266
+
267
+ # If we have remainder from the previous output, use it.
268
+ # Otherwise use the whole input's value.
269
+ qty = if current_input_remainder > 0
270
+ current_input_remainder
271
+ else
272
+ current_input.value
273
+ end
274
+
275
+ if qty <= remaining_value
276
+ remaining_value -= qty
277
+ # choose next input, clear remainder.
278
+ current_input_remainder = 0
279
+ current_input = nil
280
+ else
281
+ current_input_remainder = qty - remaining_value
282
+ remaining_value = 0
283
+ # keep the current input to use with `current_input_remainder` in the next output
284
+ end
285
+ end # filling in the output
286
+
287
+ aout.verified = true
288
+
289
+ BTC::Invariant(aout.verified && aout.asset_id && aout.value > 0, "Transfer output should be fully verified")
290
+ end # only transfer outputs with value > 0
291
+ end # each output
292
+
293
+ # Some inputs may have remained. If those have some assets, they'll be destroyed.
294
+ if current_input_remainder > 0
295
+ Diagnostics.current.add_message("Warning: #{current_input_remainder} units left over from input #{current_input.index} will be destroyed.")
296
+ end
297
+
298
+ while current_input = inputs.shift
299
+ if current_input.colored?
300
+ Diagnostics.current.add_message("Warning: #{current_input.value} units from input #{current_input.index} will be destroyed.")
301
+ end
302
+ end
303
+
304
+ return true
305
+ end # verify_transfers
306
+
307
+ # scans forward and validates every AssetTransaction on the way.
308
+ def discover_asset(asset_id: nil)
309
+ # TODO: ...
310
+ end
311
+
312
+
313
+ protected
314
+
315
+ def cache_transactions(&block)
316
+ begin
317
+ @cached_txs_depth ||= 0
318
+ @cached_txs_depth += 1
319
+ @cached_txs ||= {}
320
+ result = yield
321
+ ensure
322
+ @cached_txs_depth -= 1
323
+ @cached_txs = nil if @cached_txs_depth <= 0
324
+ end
325
+ result
326
+ end
327
+
328
+ def transaction_for_input(txin)
329
+ transaction_for_hash(txin.previous_hash)
330
+ end
331
+
332
+ def transaction_output_for_input(txin)
333
+ tx = transaction_for_input(txin)
334
+ if tx
335
+ tx.outputs[txin.previous_index]
336
+ else
337
+ nil
338
+ end
339
+ end
340
+
341
+ def transaction_for_hash(hash)
342
+ if @cached_txs && (tx = @cached_txs[hash])
343
+ return tx
344
+ end
345
+ tx = @source.transaction_for_hash(hash)
346
+ if @cached_txs && tx
347
+ @cached_txs[tx.transaction_hash] = tx
348
+ end
349
+ tx
350
+ end
351
+ end
352
+
353
+ ::BTC::AssetTransactionInput # make sure AssetTransaction is defined
354
+ class AssetTransactionInput
355
+ attr_accessor :previous_asset_transaction
356
+ end
357
+
358
+ module AssetProcessorSource
359
+
360
+ # Override this method if you can provide already verified transaction.
361
+ def verified_asset_transaction_for_hash(hash)
362
+ nil
363
+ end
364
+
365
+ # Override this to cache verified asset transaction.
366
+ def asset_transaction_was_verified(atx)
367
+ nil
368
+ end
369
+
370
+ # Override this to provide a transaction with a given hash.
371
+ # If transaction is not found or not available, return nil.
372
+ # This may cause asset transaction remain unverified.
373
+ def transaction_for_hash(hash)
374
+ raise "Not Implemented"
375
+ end
376
+ end
377
+ end
@@ -0,0 +1,184 @@
1
+ # Implementation of OpenAssets protocol.
2
+ # https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki
3
+ module BTC
4
+
5
+ # Wrapper around Transaction that stores info about assets on inputs and outputs.
6
+ class AssetTransaction
7
+
8
+ # Raw BTC::Transaction containing asset transfer
9
+ attr_reader :transaction
10
+
11
+ # List of AssetTransactionInput instances.
12
+ attr_reader :inputs
13
+
14
+ # List of AssetTransactionOutput instances.
15
+ attr_reader :outputs
16
+
17
+ # AssetMarker instance describing the transaction
18
+ attr_reader :marker
19
+
20
+ # Metadata stored in AssetMarker
21
+ attr_reader :metadata
22
+
23
+ # Identifier of the underlying Bitcoin transaction.
24
+ attr_reader :transaction_hash
25
+ attr_reader :transaction_id
26
+
27
+ def initialize(transaction: nil, data: nil)
28
+ if data && !transaction
29
+ txdata, len = WireFormat.read_string(data: data, offset: 0)
30
+ raise ArgumentError, "Invalid data: tx data" if !txdata
31
+ transaction = Transaction.new(data: txdata)
32
+ data = data[len..-1]
33
+ end
34
+ raise ArgumentError, "Missing transaction" if !transaction
35
+ raise FormatError, "Transaction does not contain an AssetMarker" if !transaction.open_assets_transaction?
36
+ raise FormatError, "Coinbase transactions cannot be AssetTransactions" if transaction.coinbase?
37
+ raise FormatError, "Transactions with no inputs cannot be AssetTransactions" if transaction.inputs.size == 0
38
+
39
+ @transaction = transaction
40
+
41
+ # Extract the first marker output
42
+ marker_output = transaction.outputs.find{|txout| txout.script.open_assets_marker? }
43
+ @marker = AssetMarker.new(output: marker_output) # raises if marker is malformed
44
+
45
+ # Create unverified inputs.
46
+ @inputs = transaction.inputs.map do |txin|
47
+ AssetTransactionInput.new(transaction_input: txin)
48
+ end
49
+
50
+ # Create unverified outputs for all outputs.
51
+ @outputs = transaction.outputs.inject([]) do |array, txout|
52
+ # note: other outputs looking like markers are allowed, so we check for the first one only.
53
+ aout = AssetTransactionOutput.new(transaction_output: txout)
54
+ if txout == marker_output
55
+ aout.marker = true
56
+ aout.verified = true
57
+ else
58
+ if (txout.index < marker_output.index)
59
+ aout.issue = true
60
+ else
61
+ aout.transfer = true
62
+ end
63
+ end
64
+ array << aout
65
+ end
66
+
67
+ # Check that the marker contains not more quantities than outputs available.
68
+ # Marker output does not count and is not included in quantities list.
69
+ if @marker.quantities.size > (@outputs.size - 1)
70
+ raise FormatError, "OpenAssets marker specifies more quantities than colorable outputs available: #{@marker.quantities.size} > #{@outputs.size-1}."
71
+ end
72
+
73
+ # Fill in assets amounts for outputs (but keep them unverified).
74
+ # Excessive outputs receive `nil` amount.
75
+ @outputs.find_all{|o|!o.marker?}.each_with_index do |aout, i|
76
+ aout.value = @marker.quantities[i]
77
+ end
78
+
79
+ # contains only color info about ins/outs
80
+ if data
81
+ offset = 0
82
+ @inputs.each do |ain|
83
+ offset = ain.parse_assets_data(data, offset: offset)
84
+ end
85
+ @outputs.each do |aout|
86
+ if !aout.marker?
87
+ offset = aout.parse_assets_data(data, offset: offset)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ # Serialized tx will have:
94
+ # - raw tx data
95
+ # - array of input colors (verified yes/no, asset id, units)
96
+ # - array of output colors (verified yes/no, asset id, units) - do not contain marker output
97
+ def data
98
+ data = "".b
99
+ txdata = @transaction.data
100
+ data << WireFormat.encode_string(txdata)
101
+ @inputs.each do |ain|
102
+ data << ain.assets_data
103
+ end
104
+ @outputs.each do |aout|
105
+ if !aout.marker?
106
+ data << aout.assets_data
107
+ end
108
+ end
109
+ data
110
+ end
111
+
112
+ def metadata
113
+ marker ? marker.metadata : nil
114
+ end
115
+
116
+ def transaction_hash
117
+ @transaction.transaction_hash
118
+ end
119
+
120
+ def transaction_id
121
+ @transaction.transaction_id
122
+ end
123
+
124
+ def issue_outputs
125
+ @outputs.find_all{|o| o.issue? }
126
+ end
127
+
128
+ def transfer_outputs
129
+ @outputs.find_all{|o| o.transfer? }
130
+ end
131
+
132
+ # AssetTransaction is considered verified when all outputs are verified.
133
+ # Inputs may remain unverified when their assets are destroyed.
134
+ def verified?
135
+ outputs_verified?
136
+ end
137
+
138
+ def inputs_verified?
139
+ @inputs.all?{|i|i.verified?}
140
+ end
141
+
142
+ def outputs_verified?
143
+ @outputs.all?{|o|o.verified?}
144
+ end
145
+
146
+ def output_at_raw_index(i) # deprecated
147
+ @outputs[i]
148
+ # @outputs.find do |out|
149
+ # raise ArgumentError, "Underlying BTC::TransactionOutput instance are expected to have `index` attribute." if !out.index
150
+ # out.index == i
151
+ # end
152
+ end
153
+
154
+ def dup
155
+ atx = AssetTransaction.new(transaction: self.transaction.dup)
156
+ self.inputs.each_with_index do |ain, i|
157
+ ain2 = atx.inputs[i]
158
+ ain2.asset_id = ain.asset_id
159
+ ain2.value = ain.value
160
+ ain2.verified = ain.verified
161
+ end
162
+ self.outputs.each_with_index do |aout, i|
163
+ aout2 = atx.outputs[i]
164
+ aout2.asset_id = aout.asset_id
165
+ aout2.value = aout.value
166
+ aout2.verified = aout.verified
167
+ aout2.kind = aout.kind
168
+ end
169
+ end
170
+
171
+ def inspect
172
+ issues = issue_outputs.map{|out| out.value.inspect }.join(",")
173
+ ins = inputs.map{|inp| inp.value.inspect }.join(",")
174
+ outs = transfer_outputs.map{|out| out.value.inspect }.join(",")
175
+ %{#<#{self.class}:#{self.transaction_id[0,8]} issues [#{issues}] transfers [#{ins}] => [#{outs}]>}
176
+ end
177
+
178
+ protected
179
+ attr_writer :transaction
180
+ attr_writer :inputs
181
+ attr_writer :outputs
182
+ attr_writer :marker
183
+ end
184
+ end
@@ -0,0 +1,15 @@
1
+ module BTC
2
+ AssetTransactionBuilder
3
+ class AssetTransactionBuilder
4
+ class Error < BTCError; end
5
+
6
+ # Change address is not specified
7
+ class MissingChangeAddressError < Error; end
8
+
9
+ # Unspent outputs are missing. Maybe because input_addresses are not specified.
10
+ class MissingUnspentOutputsError < Error; end
11
+
12
+ # Unspent outputs are not sufficient to build the transaction.
13
+ class InsufficientFundsError < Error; end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module BTC
2
+ AssetTransactionBuilder
3
+ class AssetTransactionBuilder
4
+
5
+ # Interface for providing unspent asset outputs to the asset transaction builder.
6
+ # You can use it instead of Enumerable attribute `asset_unspent_outputs` to customize which unspents to be used.
7
+ module Provider
8
+ # Returns an Enumerable object yielding unspent asset outputs for the given asset ID and amount.
9
+ # Each unspent output is a BTC::AssetTransactionOutput instance with non-nil `transaction_hash`, `index`, `asset_id`, `value` attributes.
10
+ # Additional information about outputs and fees may be used to optimize the set of unspents.
11
+ def asset_unspent_outputs(asset_id: nil, amount: nil)
12
+ []
13
+ end
14
+
15
+ # Creates a block-based provider:
16
+ # atxbuilder.provider = AssetTransactionBuilder::Provider.new {|builder| [...] }
17
+ def self.new(&block)
18
+ BlockProvider.new(&block)
19
+ end
20
+
21
+ class BlockProvider # private
22
+ include Provider
23
+ def initialize(&block)
24
+ @block = block
25
+ end
26
+ def asset_unspent_outputs(atxbuilder)
27
+ @block.call(atxbuilder)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ module BTC
2
+ AssetTransactionBuilder
3
+ class AssetTransactionBuilder
4
+
5
+ # Result object containing transaction itself and various info about it.
6
+ # You get this object from `AssetTransactionBuilder#build` method.
7
+ class Result
8
+ # Array of BTC::Transaction instances.
9
+ # These may have unsigned inputs and must be published in the order.
10
+ # The last transaction is wrapped by the `asset_transaction`.
11
+ # Typically, this array contains just one transaction. When issuing an asset,
12
+ # it may contain two transactions.
13
+ attr_reader :transactions
14
+
15
+ # Array of arrays. Each top-level array refers to a list of input indexes to be signed.
16
+ # Some inputs can be signed already.
17
+ attr_reader :unsigned_input_indexes
18
+
19
+ # AssetTransaction instance with full details about asset issuance and transfer.
20
+ attr_reader :asset_transaction
21
+
22
+ # Total mining fee for all composed transactions.
23
+ attr_reader :fee
24
+
25
+ # Total cost of all issues and transfers (not including the mining fees and asset change outputs)
26
+ # All of that amount is owned by the asset holders and can be extracted or returned during re-sell.
27
+ attr_reader :assets_cost
28
+
29
+ def initialize
30
+ self.transactions = []
31
+ self.unsigned_input_indexes = []
32
+ self.asset_transaction = nil
33
+ self.fee = 0
34
+ self.assets_cost = 0
35
+ end
36
+ end
37
+
38
+ # Internal-only setters.
39
+ class Result
40
+ attr_accessor :transactions
41
+ attr_accessor :unsigned_input_indexes
42
+ attr_accessor :asset_transaction
43
+ attr_accessor :fee
44
+ attr_accessor :assets_cost
45
+ end
46
+ end
47
+ end