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,338 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
describe BTC::TransactionBuilder do
|
3
|
+
|
4
|
+
class SignerByKey
|
5
|
+
include TransactionBuilder::Signer
|
6
|
+
def initialize(&block)
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
def signing_key_for_output(output: nil, address: nil)
|
10
|
+
@block.call(output, address)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class SignerBySignatureScript
|
15
|
+
include TransactionBuilder::Signer
|
16
|
+
def initialize(&block)
|
17
|
+
@block = block
|
18
|
+
end
|
19
|
+
def signature_script_for_input(input: nil, output: nil)
|
20
|
+
@block.call(input, output)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "TransactionBuilder with no outputs" do
|
25
|
+
before do
|
26
|
+
@builder = TransactionBuilder.new
|
27
|
+
@all_utxos = self.mock_utxos
|
28
|
+
|
29
|
+
@builder.input_addresses = self.mock_addresses
|
30
|
+
@builder.provider = TransactionBuilder::Provider.new do |txb|
|
31
|
+
addrs = txb.public_addresses
|
32
|
+
addrs.must_equal self.mock_addresses
|
33
|
+
scripts = addrs.map{|a| a.script }.uniq
|
34
|
+
@all_utxos.find_all{|utxo| scripts.include?(utxo.script) }
|
35
|
+
end
|
36
|
+
@builder.change_address = Address.parse("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should fill unspent_outputs using Provider" do
|
40
|
+
@builder.unspent_outputs.must_equal @all_utxos
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should have a default fee rate" do
|
44
|
+
@builder.fee_rate.must_equal Transaction::DEFAULT_FEE_RATE
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should return a result" do
|
48
|
+
result = @builder.build
|
49
|
+
result.class.must_equal TransactionBuilder::Result
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should compose a fully-spending transaction when no outputs are given" do
|
53
|
+
result = @builder.build
|
54
|
+
tx = result.transaction
|
55
|
+
tx.inputs.size.must_equal mock_utxos.size
|
56
|
+
tx.outputs.size.must_equal 1 # only one change address
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should have a list of unsigned indexes" do
|
60
|
+
result = @builder.build
|
61
|
+
result.unsigned_input_indexes.must_equal (0...(mock_utxos.size)).to_a
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should have a reasonable fee" do
|
65
|
+
result = @builder.build
|
66
|
+
size = result.transaction.data.bytesize # size of unsigned transaction
|
67
|
+
result.fee.must_be :>=, (size / 1000)*Transaction::DEFAULT_FEE_RATE
|
68
|
+
result.fee.must_be :<, 0.01 * BTC::COIN
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should have valid amounts" do
|
72
|
+
result = @builder.build
|
73
|
+
result.inputs_amount.must_equal mock_utxos.inject(0){|sum, out| sum + out.value}
|
74
|
+
result.outputs_amount.must_be :<=, result.inputs_amount
|
75
|
+
# Balance should be correct
|
76
|
+
result.inputs_amount.must_equal (result.outputs_amount + result.fee)
|
77
|
+
result.transaction.inputs.each do |txin|
|
78
|
+
# Unsigned txins contain respective output scripts.
|
79
|
+
# Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
|
80
|
+
txin.signature_script.data_only?.must_equal false
|
81
|
+
txin.signature_script.p2pkh?.must_equal true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should sign if WIFs are provided" do
|
86
|
+
@builder.input_addresses = mock_wifs
|
87
|
+
result = @builder.build
|
88
|
+
result.unsigned_input_indexes.must_equal []
|
89
|
+
result.transaction.inputs.each do |txin|
|
90
|
+
# Unsigned txins contain respective output scripts.
|
91
|
+
# Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
|
92
|
+
txin.signature_script.data_only?.must_equal true
|
93
|
+
txin.signature_script.p2pkh?.must_equal false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should sign if signer provides a key" do
|
98
|
+
@builder.signer = SignerByKey.new do |txout, addr|
|
99
|
+
wif = mock_wifs.find{|wif| wif.public_address == addr}
|
100
|
+
wif.key
|
101
|
+
end
|
102
|
+
result = @builder.build
|
103
|
+
result.unsigned_input_indexes.must_equal []
|
104
|
+
result.transaction.inputs.each do |txin|
|
105
|
+
# Unsigned txins contain respective output scripts.
|
106
|
+
# Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
|
107
|
+
txin.signature_script.data_only?.must_equal true
|
108
|
+
txin.signature_script.p2pkh?.must_equal false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should sign if signer provides a signature script" do
|
113
|
+
@builder.signer = SignerBySignatureScript.new do |txin, txout|
|
114
|
+
Script.new << "signature" << "pubkey"
|
115
|
+
end
|
116
|
+
result = @builder.build
|
117
|
+
result.unsigned_input_indexes.must_equal []
|
118
|
+
result.transaction.inputs.each do |txin|
|
119
|
+
# Unsigned txins contain respective output scripts.
|
120
|
+
# Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
|
121
|
+
txin.signature_script.data_only?.must_equal true
|
122
|
+
txin.signature_script.p2pkh?.must_equal false
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should include all prepended unspents AND all normal unspents" do
|
127
|
+
@builder.prepended_unspent_outputs = [ BTC::TransactionOutput.new(
|
128
|
+
value: 6_666_666,
|
129
|
+
script: Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX").script,
|
130
|
+
index: 0,
|
131
|
+
transaction_hash: "some mock tx".hash256
|
132
|
+
) ]
|
133
|
+
result = @builder.build
|
134
|
+
result.inputs_amount.must_equal mock_utxos.inject(0){|sum, out| sum + out.value} + 6_666_666
|
135
|
+
result.transaction.inputs.size.must_equal mock_utxos.size + 1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
describe "TransactionBuilder with some outputs" do
|
143
|
+
before do
|
144
|
+
@builder = TransactionBuilder.new
|
145
|
+
@all_utxos = self.mock_utxos
|
146
|
+
@builder.input_addresses = self.mock_addresses
|
147
|
+
@builder.provider = TransactionBuilder::Provider.new do |txb|
|
148
|
+
addrs = txb.public_addresses
|
149
|
+
addrs.must_equal self.mock_addresses
|
150
|
+
scripts = addrs.map{|a| a.script }.uniq
|
151
|
+
@all_utxos.find_all{|utxo| scripts.include?(utxo.script) }
|
152
|
+
end
|
153
|
+
@builder.change_address = Address.parse("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")
|
154
|
+
@builder.outputs = [ TransactionOutput.new(value: 1000_500,
|
155
|
+
script: Address.parse("1TipsuQ7CSqfQsjA9KU5jarSB1AnrVLLo").script) ]
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should fill unspent_outputs using provider" do
|
159
|
+
@builder.unspent_outputs.must_equal @all_utxos
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should have a default fee rate" do
|
163
|
+
@builder.fee_rate.must_equal Transaction::DEFAULT_FEE_RATE
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should compose a minimal transaction to pay necessary amount" do
|
167
|
+
result = @builder.build
|
168
|
+
tx = result.transaction
|
169
|
+
tx.inputs.size.must_equal 11
|
170
|
+
tx.outputs.size.must_equal 2 # one change address and one output address
|
171
|
+
|
172
|
+
# Payment address
|
173
|
+
tx.outputs.first.value.must_equal 1000_500
|
174
|
+
tx.outputs.first.script.standard_address.to_s.must_equal "1TipsuQ7CSqfQsjA9KU5jarSB1AnrVLLo"
|
175
|
+
|
176
|
+
# Change address
|
177
|
+
tx.outputs.last.value.must_equal (1000_00*11 - 1000_500 - result.fee)
|
178
|
+
tx.outputs.last.script.standard_address.to_s.must_equal "1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG"
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should have a list of unsigned indexes" do
|
182
|
+
result = @builder.build
|
183
|
+
result.unsigned_input_indexes.must_equal (0...(result.transaction.inputs.size)).to_a
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should have a reasonable fee" do
|
187
|
+
result = @builder.build
|
188
|
+
size = result.transaction.data.bytesize # size of unsigned transaction
|
189
|
+
result.fee.must_be :>=, (size / 1000)*Transaction::DEFAULT_FEE_RATE
|
190
|
+
result.fee.must_be :<, 0.01 * BTC::COIN
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should have valid amounts" do
|
194
|
+
result = @builder.build
|
195
|
+
result.inputs_amount.must_equal 11*1000_00
|
196
|
+
result.outputs_amount.must_be :<=, result.inputs_amount
|
197
|
+
# Balance should be correct
|
198
|
+
result.inputs_amount.must_equal (result.outputs_amount + result.fee)
|
199
|
+
result.transaction.inputs.each do |txin|
|
200
|
+
# Unsigned txins contain respective output scripts.
|
201
|
+
# Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
|
202
|
+
txin.signature_script.data_only?.must_equal false
|
203
|
+
txin.signature_script.p2pkh?.must_equal true
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
it "should sign if WIFs are provided" do
|
208
|
+
@builder.input_addresses = mock_wifs
|
209
|
+
result = @builder.build
|
210
|
+
result.unsigned_input_indexes.must_equal []
|
211
|
+
result.transaction.inputs.each do |txin|
|
212
|
+
# Unsigned txins contain respective output scripts.
|
213
|
+
# Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
|
214
|
+
txin.signature_script.data_only?.must_equal true
|
215
|
+
txin.signature_script.p2pkh?.must_equal false
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should include all prepended unspents and none of normal unspents if amount is covered" do
|
220
|
+
@builder.prepended_unspent_outputs = [ BTC::TransactionOutput.new(
|
221
|
+
value: 6_666_666,
|
222
|
+
script: Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX").script,
|
223
|
+
index: 0,
|
224
|
+
transaction_hash: "some mock tx".hash256
|
225
|
+
) ]
|
226
|
+
result = @builder.build
|
227
|
+
result.inputs_amount.must_equal 6_666_666
|
228
|
+
result.transaction.inputs.size.must_equal 1
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should include all prepended unspents and just enough of normal unspents" do
|
232
|
+
@builder.prepended_unspent_outputs = [ BTC::TransactionOutput.new(
|
233
|
+
value: @builder.outputs.first.value - (self.mock_utxos.first.value / 2),
|
234
|
+
script: Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX").script,
|
235
|
+
index: 0,
|
236
|
+
transaction_hash: "some mock tx".hash256
|
237
|
+
) ]
|
238
|
+
result = @builder.build
|
239
|
+
result.inputs_amount.must_equal(@builder.outputs.first.value + (self.mock_utxos.first.value / 2))
|
240
|
+
result.transaction.inputs.size.must_equal 2
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe "TransactionBuilder edge cases" do
|
245
|
+
before do
|
246
|
+
@builder = TransactionBuilder.new
|
247
|
+
@all_utxos = self.mock_utxos
|
248
|
+
@builder.input_addresses = self.mock_addresses
|
249
|
+
@builder.provider = TransactionBuilder::Provider.new do |txb|
|
250
|
+
addrs = txb.public_addresses
|
251
|
+
addrs.must_equal self.mock_addresses
|
252
|
+
scripts = addrs.map{|a| a.script }.uniq
|
253
|
+
@all_utxos.find_all{|utxo| scripts.include?(utxo.script) }
|
254
|
+
end
|
255
|
+
@builder.change_address = Address.parse("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should detect missing change address" do
|
259
|
+
@builder.change_address = nil
|
260
|
+
lambda do
|
261
|
+
result = @builder.build
|
262
|
+
end.must_raise TransactionBuilder::MissingChangeAddressError
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should detect missing unspents" do
|
266
|
+
@builder.provider = nil
|
267
|
+
lambda do
|
268
|
+
result = @builder.build
|
269
|
+
end.must_raise TransactionBuilder::MissingUnspentOutputsError
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should detect missing unspents" do
|
273
|
+
@builder.unspent_outputs = [ ]
|
274
|
+
lambda do
|
275
|
+
result = @builder.build
|
276
|
+
end.must_raise TransactionBuilder::MissingUnspentOutputsError
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should detect not enough unspents" do
|
280
|
+
@builder.outputs = [ TransactionOutput.new(value: 100*COIN, script: @builder.change_address.script) ]
|
281
|
+
lambda do
|
282
|
+
result = @builder.build
|
283
|
+
end.must_raise TransactionBuilder::InsufficientFundsError
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should detect not enough unspents because of change constraints" do
|
287
|
+
@builder.dust_change = 0 # no coins are allowed to be lost
|
288
|
+
@builder.minimum_change = 10000
|
289
|
+
@builder.unspent_outputs = mock_utxos[0, 1]
|
290
|
+
@builder.outputs = [ TransactionOutput.new(value: 1000_00 - @builder.fee_rate - 10, script: @builder.change_address.script) ]
|
291
|
+
lambda do
|
292
|
+
result = @builder.build
|
293
|
+
end.must_raise TransactionBuilder::InsufficientFundsError
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should forgo change if it's below dust level" do
|
297
|
+
@builder.dust_change = 42
|
298
|
+
@builder.minimum_change = 1000
|
299
|
+
@builder.unspent_outputs = mock_utxos[0, 1]
|
300
|
+
assumed_fee = 2590 #@builder.fee_rate # we assume we'll have sub-1K transaction
|
301
|
+
amount = 1000_00 - assumed_fee - @builder.dust_change
|
302
|
+
@builder.outputs = [ TransactionOutput.new(value: amount, script: @builder.change_address.script) ]
|
303
|
+
|
304
|
+
result = @builder.build
|
305
|
+
result.fee.must_equal assumed_fee
|
306
|
+
result.transaction.wont_equal nil
|
307
|
+
result.transaction.outputs.size.must_equal 1
|
308
|
+
result.transaction.outputs.first.value.must_equal amount
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
def mock_keys
|
314
|
+
@mock_keys ||= [
|
315
|
+
Key.new(private_key: "Wallet1".sha256),
|
316
|
+
Key.new(private_key: "Wallet2".sha256)
|
317
|
+
]
|
318
|
+
end
|
319
|
+
|
320
|
+
def mock_addresses
|
321
|
+
mock_keys.map{|k| k.address }
|
322
|
+
end
|
323
|
+
|
324
|
+
def mock_wifs
|
325
|
+
mock_keys.map{|k| k.to_wif_object }
|
326
|
+
end
|
327
|
+
|
328
|
+
def mock_utxos
|
329
|
+
scripts = mock_addresses.map{|a| a.script }
|
330
|
+
(0...32).map do |i|
|
331
|
+
TransactionOutput.new(value: 100_000,
|
332
|
+
script: scripts[i % scripts.size],
|
333
|
+
transaction_hash: ((16+i).to_s(16)*32).from_hex,
|
334
|
+
index: i)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
describe BTC::Transaction do
|
3
|
+
|
4
|
+
it "should have core attributes" do
|
5
|
+
|
6
|
+
tx = BTC::Transaction.new
|
7
|
+
|
8
|
+
tx.data.to_hex.must_equal("01000000" + "0000" + "00000000")
|
9
|
+
|
10
|
+
tx.to_h.must_equal({
|
11
|
+
"hash"=>"d21633ba23f70118185227be58a63527675641ad37967e2aa461559f577aec43",
|
12
|
+
"ver"=>1,
|
13
|
+
"vin_sz"=>0,
|
14
|
+
"vout_sz"=>0,
|
15
|
+
"lock_time"=>0,
|
16
|
+
"size"=>10,
|
17
|
+
"in"=>[],
|
18
|
+
"out"=>[]
|
19
|
+
})
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "Hash <-> ID conversion" do
|
24
|
+
before do
|
25
|
+
@txid = "43ec7a579f5561a42a7e9637ad4156672735a658be2752181801f723ba3316d2"
|
26
|
+
@txhash = @txid.from_hex.reverse
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should convert tx ID to binary hash" do
|
30
|
+
BTC.hash_from_id(nil).must_equal nil
|
31
|
+
BTC.hash_from_id(@txid).must_equal @txhash
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should convert binary hash to tx ID" do
|
35
|
+
BTC.id_from_hash(nil).must_equal nil
|
36
|
+
BTC.id_from_hash(@txhash).must_equal @txid
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should convert hash to/from id for TransactionOutput" do
|
40
|
+
txout = TransactionOutput.new
|
41
|
+
txout.transaction_hash = @txhash
|
42
|
+
txout.transaction_id.must_equal @txid
|
43
|
+
txout.transaction_id = "deadbeef"
|
44
|
+
txout.transaction_hash.to_hex.must_equal "efbeadde"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
describe "Amounts calculation" do
|
50
|
+
before do
|
51
|
+
@tx = Transaction.new
|
52
|
+
@tx.add_input(TransactionInput.new)
|
53
|
+
@tx.add_input(TransactionInput.new)
|
54
|
+
@tx.add_output(TransactionOutput.new(value: 123))
|
55
|
+
@tx.add_output(TransactionOutput.new(value: 50_000))
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have good defaults" do
|
59
|
+
@tx.inputs_amount.must_equal nil
|
60
|
+
@tx.fee.must_equal nil
|
61
|
+
@tx.outputs_amount.must_equal 50_123
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should derive inputs_amount from fee" do
|
65
|
+
@tx.fee = 10_000
|
66
|
+
@tx.fee.must_equal 10_000
|
67
|
+
@tx.inputs_amount.must_equal 60_123
|
68
|
+
@tx.outputs_amount.must_equal 50_123
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should derive fee from inputs_amount" do
|
72
|
+
@tx.inputs_amount = 55_123
|
73
|
+
@tx.fee.must_equal 5_000
|
74
|
+
@tx.inputs_amount.must_equal 55_123
|
75
|
+
@tx.outputs_amount.must_equal 50_123
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should derive inputs_amount from input values if present" do
|
79
|
+
@tx.inputs[0].value = 50_523
|
80
|
+
@tx.inputs[1].value = 100
|
81
|
+
@tx.fee.must_equal 500
|
82
|
+
@tx.inputs_amount.must_equal 50_623
|
83
|
+
@tx.outputs_amount.must_equal 50_123
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not derive inputs_amount from input values if some value is nil" do
|
87
|
+
@tx.inputs[0].value = 50_523
|
88
|
+
@tx.inputs[1].value = nil
|
89
|
+
@tx.fee.must_equal nil
|
90
|
+
@tx.inputs_amount.must_equal nil
|
91
|
+
@tx.outputs_amount.must_equal 50_123
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
describe "Certain transaction" do
|
97
|
+
|
98
|
+
before do
|
99
|
+
@txdata =("0100000001dfb32e172d6cdc51215c28b83415f977fc6ce281e057f7cf40c700" +
|
100
|
+
"8003f7230f000000008a47304402207f5561ac3cfb05743cab6ca914f7eb93c4" +
|
101
|
+
"89f276f10cdf4549e7f0b0ef4e85cd02200191c0c2fd10f10158973a0344fdaf" +
|
102
|
+
"2438390e083a509d2870bcf2b05445612b0141043304596050ca119efccada1d" +
|
103
|
+
"d7ca8e511a76d8e1ddb7ad050298d208455b8bcd09593d823ca252355bf0b41c" +
|
104
|
+
"2ac0ba2afa7ada4660bd38e27585aac7d4e6e435ffffffff02c0791817000000" +
|
105
|
+
"0017a914bd224370f93a2b0435ded92c7f609e71992008fc87ac7b4d1d000000" +
|
106
|
+
"001976a914450c22770eebb00d376edabe7bb548aa64aa235688ac00000000").from_hex
|
107
|
+
@tx = Transaction.new(hex: @txdata.to_hex)
|
108
|
+
@tx = Transaction.new(data: @txdata)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should decode inputs and outputs correctly" do
|
112
|
+
@tx.version.must_equal 1
|
113
|
+
@tx.lock_time.must_equal 0
|
114
|
+
@tx.inputs.size.must_equal 1
|
115
|
+
@tx.outputs.size.must_equal 2
|
116
|
+
@tx.transaction_id.must_equal "f2d0daf07409e44216fe71075df88f3c8c0c5f8e313582ab256e7af2765dd14e"
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should support dup" do
|
120
|
+
@tx2 = @tx.dup
|
121
|
+
@tx2.data.must_equal @txdata
|
122
|
+
@tx2.must_equal @tx
|
123
|
+
@tx2.object_id.wont_equal @tx.object_id
|
124
|
+
end
|
125
|
+
|
126
|
+
it "detect script kinds" do
|
127
|
+
@tx.outputs[0].script.standard?.must_equal true
|
128
|
+
@tx.outputs[0].script.script_hash_script?.must_equal true
|
129
|
+
|
130
|
+
@tx.outputs[1].script.standard?.must_equal true
|
131
|
+
@tx.outputs[1].script.public_key_hash_script?.must_equal true
|
132
|
+
end
|
133
|
+
|
134
|
+
it "input script should have a valid signature" do
|
135
|
+
|
136
|
+
@tx.inputs.first.signature_script.to_a.map{|c|c.to_hex}.must_equal [
|
137
|
+
"304402207f5561ac3cfb05743cab6ca914f7eb93c489f276f10cdf4549e7f0b0ef4e85cd02200191c0c2fd10f10158973a0344fdaf2438390e083a509d2870bcf2b05445612b01",
|
138
|
+
"043304596050ca119efccada1dd7ca8e511a76d8e1ddb7ad050298d208455b8bcd09593d823ca252355bf0b41c2ac0ba2afa7ada4660bd38e27585aac7d4e6e435"
|
139
|
+
]
|
140
|
+
|
141
|
+
Diagnostics.current.trace do
|
142
|
+
BTC::Key.validate_script_signature(@tx.inputs.first.signature_script.to_a[0], verify_lower_s: true).must_equal true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "Coinbase Transaction" do
|
149
|
+
before do
|
150
|
+
@txdata =("0100000001000000000000000000000000000000000000000000000000000000" +
|
151
|
+
"0000000000ffffffff130301e6040654188d181202119700de00000fccffffff" +
|
152
|
+
"ff0108230595000000001976a914ca6ecc7d4d671d8c5c964a48dbb0bc194407" +
|
153
|
+
"a30688ac00000000").from_hex
|
154
|
+
@tx = Transaction.new(data: @txdata)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should encode coinbase inputs correctly" do
|
158
|
+
@tx.data.must_equal @txdata
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|