bitcoin-ruby 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. data/.gitignore +4 -1
  2. data/Gemfile +21 -0
  3. data/README.rdoc +85 -25
  4. data/Rakefile +7 -3
  5. data/bin/bitcoin_node +39 -42
  6. data/bin/bitcoin_shell +1 -0
  7. data/bin/bitcoin_wallet +129 -53
  8. data/bitcoin-ruby.gemspec +4 -7
  9. data/concept-examples/blockchain-pow.rb +1 -1
  10. data/doc/CONFIG.rdoc +5 -5
  11. data/doc/EXAMPLES.rdoc +9 -5
  12. data/doc/NAMECOIN.rdoc +34 -0
  13. data/doc/NODE.rdoc +147 -10
  14. data/examples/balance.rb +10 -4
  15. data/examples/bbe_verify_tx.rb +7 -2
  16. data/examples/forwarder.rb +73 -0
  17. data/examples/generate_tx.rb +34 -0
  18. data/examples/simple_network_monitor_and_util.rb +187 -0
  19. data/examples/verify_tx.rb +1 -1
  20. data/lib/bitcoin.rb +308 -18
  21. data/lib/bitcoin/builder.rb +62 -36
  22. data/lib/bitcoin/config.rb +2 -0
  23. data/lib/bitcoin/connection.rb +11 -8
  24. data/lib/bitcoin/electrum/mnemonic.rb +162 -0
  25. data/lib/bitcoin/ffi/openssl.rb +187 -21
  26. data/lib/bitcoin/gui/addr_view.rb +2 -0
  27. data/lib/bitcoin/gui/conn_view.rb +2 -0
  28. data/lib/bitcoin/gui/connection.rb +2 -0
  29. data/lib/bitcoin/gui/em_gtk.rb +2 -0
  30. data/lib/bitcoin/gui/gui.rb +2 -0
  31. data/lib/bitcoin/gui/helpers.rb +2 -0
  32. data/lib/bitcoin/gui/tree_view.rb +2 -0
  33. data/lib/bitcoin/gui/tx_view.rb +2 -0
  34. data/lib/bitcoin/key.rb +77 -11
  35. data/lib/bitcoin/litecoin.rb +81 -0
  36. data/lib/bitcoin/logger.rb +20 -1
  37. data/lib/bitcoin/namecoin.rb +279 -0
  38. data/lib/bitcoin/network/command_client.rb +7 -6
  39. data/lib/bitcoin/network/command_handler.rb +229 -43
  40. data/lib/bitcoin/network/connection_handler.rb +182 -70
  41. data/lib/bitcoin/network/node.rb +231 -106
  42. data/lib/bitcoin/protocol.rb +44 -23
  43. data/lib/bitcoin/protocol/address.rb +5 -3
  44. data/lib/bitcoin/protocol/alert.rb +3 -4
  45. data/lib/bitcoin/protocol/aux_pow.rb +123 -0
  46. data/lib/bitcoin/protocol/block.rb +98 -18
  47. data/lib/bitcoin/protocol/handler.rb +6 -5
  48. data/lib/bitcoin/protocol/parser.rb +44 -19
  49. data/lib/bitcoin/protocol/tx.rb +105 -52
  50. data/lib/bitcoin/protocol/txin.rb +39 -19
  51. data/lib/bitcoin/protocol/txout.rb +28 -13
  52. data/lib/bitcoin/protocol/version.rb +16 -7
  53. data/lib/bitcoin/script.rb +579 -122
  54. data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
  55. data/lib/bitcoin/storage/models.rb +20 -7
  56. data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
  57. data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
  58. data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
  59. data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
  60. data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
  61. data/lib/bitcoin/storage/storage.rb +233 -28
  62. data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
  63. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
  64. data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
  65. data/lib/bitcoin/validation.rb +369 -0
  66. data/lib/bitcoin/version.rb +1 -1
  67. data/lib/bitcoin/wallet/coinselector.rb +3 -0
  68. data/lib/bitcoin/wallet/keygenerator.rb +3 -1
  69. data/lib/bitcoin/wallet/keystore.rb +6 -2
  70. data/lib/bitcoin/wallet/txdp.rb +6 -4
  71. data/lib/bitcoin/wallet/wallet.rb +54 -16
  72. data/spec/bitcoin/bitcoin_spec.rb +48 -3
  73. data/spec/bitcoin/builder_spec.rb +40 -17
  74. data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
  75. data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
  76. data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
  77. data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
  78. data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
  79. data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
  80. data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
  81. data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
  82. data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
  83. data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
  84. data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
  85. data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
  86. data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
  87. data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
  88. data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
  89. data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
  90. data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
  91. data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
  92. data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
  93. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
  94. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
  95. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
  96. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
  97. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
  98. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
  99. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
  100. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
  101. data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
  102. data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
  103. data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
  104. data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
  105. data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
  106. data/spec/bitcoin/key_spec.rb +128 -3
  107. data/spec/bitcoin/namecoin_spec.rb +182 -0
  108. data/spec/bitcoin/network_spec.rb +5 -3
  109. data/spec/bitcoin/node/command_api_spec.rb +376 -0
  110. data/spec/bitcoin/protocol/addr_spec.rb +2 -0
  111. data/spec/bitcoin/protocol/alert_spec.rb +2 -0
  112. data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
  113. data/spec/bitcoin/protocol/block_spec.rb +134 -39
  114. data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
  115. data/spec/bitcoin/protocol/inv_spec.rb +10 -8
  116. data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
  117. data/spec/bitcoin/protocol/ping_spec.rb +2 -0
  118. data/spec/bitcoin/protocol/tx_spec.rb +83 -17
  119. data/spec/bitcoin/protocol/version_spec.rb +7 -5
  120. data/spec/bitcoin/script/opcodes_spec.rb +412 -133
  121. data/spec/bitcoin/script/script_spec.rb +112 -13
  122. data/spec/bitcoin/spec_helper.rb +68 -0
  123. data/spec/bitcoin/storage/reorg_spec.rb +199 -0
  124. data/spec/bitcoin/storage/storage_spec.rb +337 -0
  125. data/spec/bitcoin/storage/validation_spec.rb +261 -0
  126. data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
  127. data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
  128. data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
  129. data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
  130. data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
  131. metadata +105 -51
  132. data/lib/bitcoin/storage/sequel.rb +0 -335
  133. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
  134. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
  135. data/spec/bitcoin/reorg_spec.rb +0 -129
  136. data/spec/bitcoin/storage_spec.rb +0 -229
@@ -0,0 +1,369 @@
1
+ # encoding: ascii-8bit
2
+
3
+ module Bitcoin::Validation
4
+
5
+ # maximum size of a block (in bytes)
6
+ MAX_BLOCK_SIZE = 1_000_000
7
+
8
+ # maximum number of signature operations in a block
9
+ MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE / 50
10
+
11
+ # maximum integer value
12
+ INT_MAX = 0xffffffff
13
+
14
+ # number of confirmations required before coinbase tx can be spent
15
+ COINBASE_MATURITY = 100
16
+
17
+ # interval (in blocks) for difficulty retarget
18
+ RETARGET = 2016
19
+
20
+ # interval (in blocks) for mining reward reduction
21
+ REWARD_DROP = 210_000
22
+
23
+ class ValidationError < StandardError
24
+ end
25
+
26
+
27
+ class Block
28
+ attr_accessor :block, :store, :prev_block, :error
29
+
30
+ RULES = {
31
+ syntax: [:hash, :tx_list, :bits, :max_timestamp, :coinbase, :coinbase_scriptsig, :mrkl_root, :transactions_syntax],
32
+ context: [:prev_hash, :difficulty, :coinbase_value, :min_timestamp, :transactions_context]
33
+ }
34
+
35
+ # TODO merged mining validations
36
+ if Bitcoin.namecoin?
37
+ RULES[:syntax] -= [:bits, :coinbase, :coinbase_scriptsig, :mrkl_root]
38
+ RULES[:context] -= [:difficulty, :coinbase_value]
39
+ end
40
+
41
+ # validate block rules. +opts+ are:
42
+ # rules:: which rulesets to validate (default: [:syntax, :context])
43
+ # raise_errors:: whether to raise ValidationError on failure (default: false)
44
+ def validate(opts = {})
45
+ return true if KNOWN_EXCEPTIONS.include?(block.hash)
46
+ opts[:rules] ||= [:syntax, :context]
47
+ opts[:rules].each do |name|
48
+ store.log.debug { "validating block #{name} #{block.hash} (#{block.to_payload.bytesize} bytes)" }
49
+ RULES[name].each.with_index do |rule, i|
50
+ unless (res = send(rule)) && res == true
51
+ raise ValidationError, "block error: #{name} check #{i} - #{rule} failed" if opts[:raise_errors]
52
+ @error = [rule, res]
53
+ return false
54
+ end
55
+ end
56
+ end
57
+ true
58
+ end
59
+
60
+ # setup new validator for given +block+, validating context with +store+,
61
+ # optionally passing the +prev_block+ for optimization.
62
+ def initialize block, store, prev_block = nil
63
+ @block, @store, @error = block, store, nil
64
+ @prev_block = prev_block || store.get_block(block.prev_block.reverse_hth)
65
+ end
66
+
67
+ # check that block hash matches header
68
+ def hash
69
+ claimed = block.hash; real = block.recalc_block_hash
70
+ claimed == real || [claimed, real]
71
+ end
72
+
73
+ # check that block has at least one tx (the coinbase)
74
+ def tx_list
75
+ block.tx.any? || block.tx.size
76
+ end
77
+
78
+ # check that block hash matches claimed bits
79
+ def bits
80
+ actual = block.hash.to_i(16)
81
+ expected = Bitcoin.decode_compact_bits(block.bits).to_i(16)
82
+ actual <= expected || [actual, expected]
83
+ end
84
+
85
+ # check that block time is not greater than max
86
+ def max_timestamp
87
+ time, max = block.time, Time.now.to_i + 2*60*60
88
+ time < max || [time, max]
89
+ end
90
+
91
+ # check that coinbase is present
92
+ def coinbase
93
+ coinbase, *rest = block.tx.map{|t| t.inputs.size == 1 && t.inputs.first.coinbase? }
94
+ (coinbase && rest.none?) || [coinbase ? 1 : 0, rest.select{|r| r}.size]
95
+ end
96
+
97
+ # check that coinbase scriptsig is valid
98
+ def coinbase_scriptsig
99
+ size = block.tx.first.in.first.script_sig.bytesize
100
+ size.between?(2,100) || [size, 2, 100]
101
+ end
102
+
103
+ # check that coinbase value is valid; no more than reward + fees
104
+ def coinbase_value
105
+ reward = ((50.0 / (2 ** (store.get_depth / REWARD_DROP.to_f).floor)) * 1e8).to_i
106
+ fees = 0
107
+ block.tx[1..-1].map.with_index do |t, idx|
108
+ val = tx_validators[idx]
109
+ fees += t.in.map.with_index {|i, idx|
110
+ val.prev_txs[idx].out[i.prev_out_index].value rescue 0
111
+ }.inject(:+)
112
+ val.clear_cache # memory optimization on large coinbases, see testnet3 block 4110
113
+ end
114
+ coinbase_output = block.tx[0].out.map(&:value).inject(:+)
115
+ coinbase_output <= reward + fees || [coinbase_output, reward, fees]
116
+ end
117
+
118
+ # check that merkle root matches transaction hashes
119
+ def mrkl_root
120
+ actual, expected = block.mrkl_root.reverse_hth, Bitcoin.hash_mrkl_tree(block.tx.map(&:hash))[-1]
121
+ actual == expected || [actual, expected]
122
+ end
123
+
124
+ def prev_hash
125
+ @prev_block && @prev_block.hash == block.prev_block.reverse_hth
126
+ end
127
+
128
+ # check that bits satisfy required difficulty
129
+ def difficulty
130
+ return true if Bitcoin.network_name == :testnet3
131
+ block.bits == next_bits_required || [block.bits, next_bits_required]
132
+ end
133
+
134
+ # check that timestamp is newer than the median of the last 11 blocks
135
+ def min_timestamp
136
+ return true if store.get_depth <= 11
137
+ d = store.get_depth
138
+ first = store.db[:blk][hash: block.prev_block.reverse.blob]
139
+ times = [first[:time]]
140
+ (10).times { first = store.db[:blk][hash: first[:prev_hash].blob]
141
+ times << first[:time] }
142
+ times.sort!
143
+ mid, rem = times.size.divmod(2)
144
+ min_time = (rem == 0 ? times[mid-1, 2].inject(:+) / 2.0 : times[mid])
145
+
146
+ block.time > min_time || [block.time, min_time]
147
+ end
148
+
149
+ # check transactions
150
+ def transactions_syntax
151
+ tx_validators.all?{|v|
152
+ begin
153
+ v.validate(rules: [:syntax], raise_errors: true)
154
+ rescue ValidationError
155
+ store.log.info { $!.message }
156
+ return false
157
+ end
158
+ }
159
+ end
160
+
161
+ def transactions_context
162
+ tx_validators.all?{|v|
163
+ begin
164
+ v.validate(rules: [:context], raise_errors: true)
165
+ rescue ValidationError
166
+ store.log.info { $!.message }
167
+ return false
168
+ end
169
+ }
170
+ end
171
+
172
+ def tx_validators
173
+ @tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block) }
174
+ end
175
+
176
+ def next_bits_required
177
+ index = (prev_block.depth + 1) / RETARGET
178
+ max_target = Bitcoin.decode_compact_bits(Bitcoin.network[:proof_of_work_limit]).to_i(16)
179
+ return Bitcoin.network[:proof_of_work_limit] if index == 0
180
+ return prev_block.bits if (prev_block.depth + 1) % RETARGET != 0
181
+ last = store.db[:blk][hash: prev_block.hash.htb.blob]
182
+ first = store.db[:blk][hash: last[:prev_hash].blob]
183
+ (RETARGET-2).times { first = store.db[:blk][hash: first[:prev_hash].blob] }
184
+
185
+ nActualTimespan = last[:time] - first[:time]
186
+ nTargetTimespan = RETARGET * 600
187
+
188
+ nActualTimespan = [nActualTimespan, nTargetTimespan/4].max
189
+ nActualTimespan = [nActualTimespan, nTargetTimespan*4].min
190
+
191
+ target = Bitcoin.decode_compact_bits(last[:bits]).to_i(16)
192
+ new_target = [max_target, (target * nActualTimespan)/nTargetTimespan].min
193
+ Bitcoin.encode_compact_bits new_target.to_s(16)
194
+ end
195
+
196
+ KNOWN_EXCEPTIONS = [
197
+ Bitcoin.network[:genesis_hash], # genesis block
198
+ "00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec", # BIP30 exception
199
+ "00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721", # BIP30 exception
200
+ ]
201
+
202
+ end
203
+
204
+ class Tx
205
+ attr_accessor :tx, :store, :error
206
+
207
+ RULES = {
208
+ syntax: [:hash, :lists, :max_size, :output_values, :inputs, :lock_time, :standard],
209
+ context: [:prev_out, :signatures, :spent, :input_values, :output_sum]
210
+ }
211
+
212
+ # validate tx rules. +opts+ are:
213
+ # rules:: which rulesets to validate (default: [:syntax, :context])
214
+ # raise_errors:: whether to raise ValidationError on failure (default: false)
215
+ def validate(opts = {})
216
+ return true if KNOWN_EXCEPTIONS.include?(tx.hash)
217
+ opts[:rules] ||= [:syntax, :context]
218
+ opts[:rules].each do |name|
219
+ store.log.debug { "validating tx #{name} #{tx.hash} (#{tx.to_payload.bytesize} bytes)" } if store
220
+ RULES[name].each.with_index do |rule, i|
221
+ unless (res = send(rule)) && res == true
222
+ raise ValidationError, "tx error: #{name} check #{i} - #{rule} failed" if opts[:raise_errors]
223
+ @error = [rule, res]
224
+ return false
225
+ end
226
+ end
227
+ end
228
+ clear_cache # memory optimizatons
229
+ true
230
+ end
231
+
232
+ KNOWN_EXCEPTIONS = [
233
+ # p2sh with invalid inner script, accepted by old miner before 4-2012 switchover
234
+ "6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192",
235
+ # p2sh with invalid inner script, accepted by old miner before 4-2012 switchover (testnet)
236
+ "b3c19d78b4953b694717a47d9852f8ea1ccd4cf93a45ba2e43a0f97d7cdb2655"
237
+ ]
238
+
239
+ # setup new validator for given +tx+, validating context with +store+.
240
+ # also needs the +block+ to find prev_outs for chains of tx inside one block.
241
+ def initialize tx, store, block = nil
242
+ @tx, @store, @block, @errors = tx, store, block, []
243
+ end
244
+
245
+ # check that tx hash matches data
246
+ def hash
247
+ generated_hash = tx.generate_hash(tx.to_payload)
248
+ tx.hash == generated_hash || [tx.hash, generated_hash]
249
+ end
250
+
251
+ # check that tx has at least one input and one output
252
+ def lists
253
+ (tx.in.any? && tx.out.any?) || [tx.in.size, tx.out.size]
254
+ end
255
+
256
+ # check that tx size doesn't exceed MAX_BLOCK_SIZE.
257
+ def max_size
258
+ tx.to_payload.bytesize <= MAX_BLOCK_SIZE || [tx.to_payload.bytesize, MAX_BLOCK_SIZE]
259
+ end
260
+
261
+ # check that total output value doesn't exceed MAX_MONEY.
262
+ def output_values
263
+ total = tx.out.inject(0) {|e, out| e + out.value }
264
+ total <= Bitcoin::network[:max_money] || [total, Bitcoin::network[:max_money]]
265
+ end
266
+
267
+ # check that none of the inputs is coinbase
268
+ # (coinbase tx do not get validated)
269
+ def inputs
270
+ tx.inputs.none?(&:coinbase?) || [tx.inputs.index(tx.inputs.find(&:coinbase?))]
271
+ end
272
+
273
+ # check that lock_time doesn't exceed INT_MAX
274
+ def lock_time
275
+ tx.lock_time <= INT_MAX || [tx.lock_time, INT_MAX]
276
+ end
277
+
278
+ # check that min_size is at least 86 bytes
279
+ # (smaller tx can't be valid / do anything useful)
280
+ def min_size
281
+ tx.to_payload.bytesize >= 86 || [tx.to_payload.bytesize, 86]
282
+ end
283
+
284
+ # check that tx matches "standard" rules.
285
+ # this is currently disabled since not all miners enforce it.
286
+ def standard
287
+ return true # not enforced by all miners
288
+ return false unless min_size
289
+ tx.out.all? {|o| Bitcoin::Script.new(o.pk_script).is_standard? }
290
+ end
291
+
292
+ # check that all prev_outs exist
293
+ # (and are in a block in the main chain, or the current block; see #prev_txs)
294
+ def prev_out
295
+ missing = tx.in.reject.with_index {|txin, idx|
296
+ prev_txs[idx].out[txin.prev_out_index] rescue false }
297
+ return true if prev_txs.size == tx.in.size && missing.empty?
298
+
299
+ missing.each {|i| store.log.warn { "prev out #{i.prev_out.reverse_hth}:#{i.prev_out_index} missing" } }
300
+ missing.map {|i| [i.prev_out.reverse_hth, i.prev_out_index] }
301
+ end
302
+
303
+ # TODO: validate coinbase maturity
304
+
305
+ # check that all input signatures are valid
306
+ def signatures
307
+ sigs = tx.in.map.with_index {|txin, idx| tx.verify_input_signature(idx, prev_txs[idx], (@block ? @block.time : 0)) }
308
+ sigs.all? || sigs.map.with_index {|s, i| s ? nil : i }.compact
309
+ end
310
+
311
+ # check that none of the prev_outs are already spent in the main chain
312
+ def spent
313
+ spent = tx.in.map.with_index {|txin, idx|
314
+ next false if @block && @block.tx.include?(prev_txs[idx])
315
+ next false unless next_in = prev_txs[idx].out[txin.prev_out_index].get_next_in
316
+ next false unless next_tx = next_in.get_tx
317
+ next false unless next_block = next_tx.get_block
318
+ next_block.chain == Bitcoin::Storage::Backends::StoreBase::MAIN
319
+ }
320
+ spent.none? || spent.map.with_index {|s, i| s ? i : nil }
321
+ end
322
+
323
+ # check that the total input value doesn't exceed MAX_MONEY
324
+ def input_values
325
+ total_in < Bitcoin::network[:max_money] || [total_in, Bitcoin::network[:max_money]]
326
+ end
327
+
328
+ # check that the total output value doesn't exceed the total input value
329
+ def output_sum
330
+ total_in >= total_out || [total_out, total_in]
331
+ end
332
+
333
+ # empty prev txs cache
334
+ def clear_cache
335
+ @prev_txs = nil
336
+ @total_in = nil
337
+ @total_out = nil
338
+ end
339
+
340
+ # collect prev_txs needed to verify the inputs of this tx.
341
+ # only returns tx that are in a block in the main chain or the current block.
342
+ def prev_txs
343
+ @prev_txs ||= tx.in.map {|i|
344
+ prev_tx = store.get_tx(i.prev_out.reverse_hth)
345
+ next prev_tx if store.class.name =~ /UtxoStore/ && prev_tx
346
+ next nil if !prev_tx && !@block
347
+
348
+ if store.class.name =~ /SequelStore/
349
+ block = store.db[:blk][id: prev_tx.blk_id] if prev_tx
350
+ next prev_tx if block && block[:chain] == 0
351
+ else
352
+ next prev_tx if prev_tx && prev_tx.get_block && prev_tx.get_block.chain == 0
353
+ end
354
+ next nil if !@block
355
+ @block.tx.find {|t| t.binary_hash == i.prev_out }
356
+ }.compact
357
+ end
358
+
359
+
360
+ def total_in
361
+ @total_in ||= tx.in.each_with_index.inject(0){|acc,(input,idx)| acc + prev_txs[idx].out[input.prev_out_index].value }
362
+ end
363
+
364
+ def total_out
365
+ @total_out ||= tx.out.inject(0){|acc,output| acc + output.value }
366
+ end
367
+
368
+ end
369
+ end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,3 +1,5 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  module Bitcoin::Wallet
2
4
 
3
5
  # select unspent txouts to be used by the Wallet when creating a new transaction
@@ -14,6 +16,7 @@ module Bitcoin::Wallet
14
16
  @txouts.each do |txout|
15
17
  begin
16
18
  next if txout.get_next_in
19
+ next if Bitcoin.namecoin? && txout.type.to_s =~ /^name_/
17
20
  next unless txout.get_address
18
21
  next unless txout.get_tx.get_block
19
22
  txouts << txout
@@ -1,8 +1,10 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  module Bitcoin::Wallet
2
4
 
3
5
  # Deterministic key generator as described in
4
6
  # https://bitcointalk.org/index.php?topic=11665.0.
5
- #
7
+ #
6
8
  # Takes a seed and generates an arbitrary amount of keys.
7
9
  # Protects against brute-force attacks by requiring the
8
10
  # key hash to fit a difficulty target, much like the block chain.
@@ -1,3 +1,5 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  require 'json'
2
4
  require 'stringio'
3
5
 
@@ -12,6 +14,7 @@ module Bitcoin::Wallet
12
14
  # [config] Hash of settings ({:file => "/foo/bar.json"})
13
15
  def initialize config
14
16
  @config = Hash[config.map{|k,v|[k.to_sym,v]}]
17
+ @config[:file].sub!("~", ENV["HOME"]) if @config[:file].is_a?(String)
15
18
  @keys = []
16
19
  load_keys
17
20
  end
@@ -92,9 +95,10 @@ module Bitcoin::Wallet
92
95
  def import(base58, label = nil)
93
96
  raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
94
97
  key = Bitcoin::Key.from_base58(base58)
98
+ raise ArgumentError, "Address #{key.addr} already in use" if label && find_key(key.addr)
95
99
  @keys << {:label => label, :addr => key.addr, :key => key}
96
100
  save_keys
97
- key.addr
101
+ key
98
102
  end
99
103
 
100
104
  # Load keys from file.
@@ -139,7 +143,7 @@ module Bitcoin::Wallet
139
143
  @config[:file].reopen
140
144
  dumper.call(@config[:file])
141
145
  @config[:file].rewind
142
- elsif File.exists?(@config[:file])
146
+ else
143
147
  File.open(@config[:file], 'w'){|file| dumper.call(file) }
144
148
  end
145
149
  end
@@ -1,3 +1,5 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  class Bitcoin::Wallet::TxDP
2
4
 
3
5
  attr_accessor :id, :tx, :inputs
@@ -7,7 +9,7 @@ class Bitcoin::Wallet::TxDP
7
9
  @inputs = []
8
10
  return unless tx.any?
9
11
  @tx[0].in.each_with_index do |input, i|
10
- prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
12
+ prev_out_hash = input.prev_out.reverse_hth
11
13
  prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
12
14
  raise "prev tx #{prev_out_hash} not found" unless prev_tx
13
15
  prev_out = prev_tx.out[input.prev_out_index]
@@ -27,7 +29,7 @@ class Bitcoin::Wallet::TxDP
27
29
  def sign_inputs
28
30
  @inputs.each_with_index do |txin, i|
29
31
  input = @tx[0].in[i]
30
- prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
32
+ prev_out_hash = input.prev_out.reverse_hth
31
33
  prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
32
34
  raise "prev tx #{prev_out_hash} not found" unless prev_tx
33
35
  prev_out = prev_tx.out[input.prev_out_index]
@@ -47,7 +49,7 @@ class Bitcoin::Wallet::TxDP
47
49
  def serialize
48
50
  lines = []
49
51
  lines << "-----BEGIN-TRANSACTION-#{@id}".ljust(80, '-')
50
- size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
52
+ size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse_hth
51
53
  lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_#{size}"
52
54
  tx = @tx.map(&:to_payload).join.unpack("H*")[0]
53
55
  tx_str = ""; tx.split('').each_with_index{|c,i| tx_str << (i % 80 == 0 ? "\n#{c}" : c)}
@@ -57,7 +59,7 @@ class Bitcoin::Wallet::TxDP
57
59
  next unless input[1]
58
60
  input[1].each do |sig|
59
61
  size = [sig[1]].pack("H*").bytesize
60
- size = [size].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
62
+ size = [size].pack("C").ljust(2, "\x00").reverse_hth
61
63
  lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_#{size}"
62
64
  sig_str = ""; sig[1].split('').each_with_index{|c,i| sig_str << (i % 80 == 0 ? "\n#{c}" : c)}
63
65
  lines << sig_str.strip