bitcoin-ruby 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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,337 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ include Bitcoin
6
+ include Bitcoin::Storage
7
+ include Bitcoin::Storage::Backends
8
+ include Bitcoin::Builder
9
+ include Bitcoin::Validation
10
+
11
+ [ { :name => :dummy },
12
+ { :name => :utxo, :db => 'sqlite:/', :index_all_addrs => true },
13
+ { :name => :sequel, :db => 'sqlite:/' } ].each do |configuration|
14
+
15
+ describe "Bitcoin::Storage::Backends::#{configuration[:name].capitalize}Store" do
16
+
17
+ before do
18
+ class Bitcoin::Validation::Block; def difficulty; true; end; end
19
+ Bitcoin.network[:proof_of_work_limit] = Bitcoin.encode_compact_bits("ff"*32)
20
+
21
+ Bitcoin::network = :testnet
22
+ @store = Bitcoin::Storage.send(configuration[:name], configuration)
23
+ def @store.in_sync?; true; end
24
+ @store.reset
25
+ @store.log.level = 4
26
+
27
+ @store.store_block(P::Block.new(fixtures_file('testnet/block_0.bin')))
28
+ @store.store_block(P::Block.new(fixtures_file('testnet/block_1.bin')))
29
+ @store.store_block(P::Block.new(fixtures_file('testnet/block_2.bin')))
30
+ @store.store_block(P::Block.new(fixtures_file('testnet/block_3.bin')))
31
+
32
+ unless @store.class.name =~ /UtxoStore/
33
+ @store.store_tx(P::Tx.new(fixtures_file('rawtx-01.bin')), false)
34
+ @store.store_tx(P::Tx.new(fixtures_file('rawtx-02.bin')), false)
35
+ end
36
+
37
+ @blk = P::Block.new(fixtures_file('testnet/block_4.bin'))
38
+ @tx = P::Tx.new(fixtures_file('rawtx-03.bin'))
39
+ end
40
+
41
+ after do
42
+ class Bitcoin::Validation::Block
43
+ def difficulty
44
+ return true if Bitcoin.network_name == :testnet3
45
+ block.bits == next_bits_required || [block.bits, next_bits_required]
46
+ end
47
+ end
48
+ end
49
+
50
+ it "should get backend name" do
51
+ @store.backend_name.should == configuration[:name].to_s
52
+ end
53
+
54
+ it "should get depth" do
55
+ @store.get_depth.should == 3
56
+ end
57
+
58
+ it "should report depth as -1 if store is empty" do
59
+ @store.reset
60
+ @store.get_depth.should == -1
61
+ end
62
+
63
+ it "should get head" do
64
+ @store.get_head
65
+ .should == @store.get_block("0000000098932356a236718829dd9e3eb0f9143317ab921333b1a203de336de4")
66
+ end
67
+
68
+ it "should get locator" do
69
+ @store.get_locator.should == [
70
+ "0000000098932356a236718829dd9e3eb0f9143317ab921333b1a203de336de4",
71
+ "000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f",
72
+ "000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604",
73
+ "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"]
74
+ end
75
+
76
+ it "should not store if there is no prev block" do
77
+ @store.reset
78
+ @store.store_block(@blk).should == [0, 2]
79
+ @store.get_depth.should == -1
80
+ end
81
+
82
+ it "should check whether block is already stored" do
83
+ @store.has_block(@blk.hash).should == false
84
+ @store.store_block(@blk)
85
+ @store.has_block(@blk.hash).should == true
86
+ end
87
+
88
+ it "should get block by depth" do
89
+ @store.get_block_by_depth(0).hash.should ==
90
+ P::Block.new(fixtures_file('testnet/block_0.bin')).hash
91
+ @store.get_block_by_depth(1).hash.should ==
92
+ P::Block.new(fixtures_file('testnet/block_1.bin')).hash
93
+ @store.get_block_by_depth(2).hash.should ==
94
+ P::Block.new(fixtures_file('testnet/block_2.bin')).hash
95
+ end
96
+
97
+ it "should store and retrieve all relevant block data for hash/json serialization" do
98
+ (0..2).each do |i|
99
+ expected = P::Block.new(fixtures_file("testnet/block_#{i}.bin")).to_hash
100
+ @store.get_block_by_depth(i).to_hash.should == expected
101
+ end
102
+ end
103
+
104
+ it "should get block by hash" do
105
+ @store.get_block(
106
+ "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008").hash
107
+ .should == P::Block.new(fixtures_file('testnet/block_0.bin')).hash
108
+ @store.get_block(
109
+ "000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604").hash
110
+ .should == P::Block.new(fixtures_file('testnet/block_1.bin')).hash
111
+ @store.get_block(
112
+ "000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f").hash
113
+ .should == P::Block.new(fixtures_file('testnet/block_2.bin')).hash
114
+ end
115
+
116
+ it "should not get block" do
117
+ @store.get_block("nonexistant").should == nil
118
+ end
119
+
120
+ it "should get block depth" do
121
+ @store.get_block("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008")
122
+ .depth.should == 0
123
+ @store.get_block("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604")
124
+ .depth.should == 1
125
+ @store.get_block("000000037b21cac5d30fc6fda2581cf7b2612908aed2abbcc429c45b0557a15f")
126
+ .depth.should == 2
127
+ end
128
+
129
+ it "should get prev block" do
130
+ @store.get_block("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008")
131
+ .get_prev_block.should == nil
132
+ @store.get_block("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604")
133
+ .get_prev_block.should ==
134
+ @store.get_block("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008")
135
+ end
136
+
137
+ it "should get next block" do
138
+ @store.get_block("0000000098932356a236718829dd9e3eb0f9143317ab921333b1a203de336de4")
139
+ .get_next_block.should == nil
140
+ @store.get_block("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008")
141
+ .get_next_block.should ==
142
+ @store.get_block("000000033cc282bc1fa9dcae7a533263fd7fe66490f550d80076433340831604")
143
+ end
144
+
145
+ it "should get block for tx" do
146
+ @store.store_block(@blk)
147
+ @store.get_block_by_tx(@blk.tx[0].hash).should == @blk
148
+ end
149
+
150
+ # TODO calling .is_a? on @store breaks it (?!?)
151
+ if @store.class.name =~ /SequelStore/
152
+ describe :transactions do
153
+
154
+ it "should store tx" do
155
+ @store.store_tx(@tx, false).should != false
156
+ end
157
+
158
+ it "should not store tx if already stored and return existing id" do
159
+ id = @store.store_tx(@tx, false)
160
+ @store.store_tx(@tx, false).should == id
161
+ end
162
+
163
+ it "should check if tx is already stored" do
164
+ @store.has_tx(@tx.hash).should == false
165
+ @store.store_tx(@tx, false)
166
+ @store.has_tx(@tx.hash).should == true
167
+ end
168
+
169
+ it "should store hash160 for txout" do
170
+ @store.store_tx(@tx, false)
171
+ @store.get_tx(@tx.hash).out[0].hash160
172
+ .should == "3129d7051d509424d23d533fa2d5258977e822e3"
173
+ end
174
+
175
+ it "should get tx" do
176
+ @store.store_tx(@tx, false)
177
+ @store.get_tx(@tx.hash).should == @tx
178
+ end
179
+
180
+ it "should not get tx" do
181
+ @store.get_tx("nonexistant").should == nil
182
+ end
183
+
184
+ it "should get the position for a given tx" do
185
+ @store.store_block(@blk)
186
+ result = @store.get_idx_from_tx_hash(@blk.tx[0].hash)
187
+ result.should == 0
188
+ end
189
+
190
+ it "should get tx for txin" do
191
+ @store.store_tx(@tx, false)
192
+ @store.get_tx(@tx.hash).in[0].get_tx.should == @tx
193
+ end
194
+
195
+ it "should get prev out for txin" do
196
+ tx = P::Tx.new(fixtures_file('rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin'))
197
+ outpoint_tx = P::Tx.new(fixtures_file('rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin'))
198
+ @store.store_tx(outpoint_tx, false)
199
+ @store.store_tx(tx, false)
200
+ @store.get_tx(tx.hash).in[0].get_prev_out.should == outpoint_tx.out[0]
201
+ end
202
+
203
+ it "should get tx for txout" do
204
+ @store.store_tx(@tx, false)
205
+ @store.get_tx(@tx.hash).out[0].get_tx.should == @tx
206
+ end
207
+
208
+ it "should get next in for txin" do
209
+ tx = P::Tx.new(fixtures_file('rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin'))
210
+ outpoint_tx = P::Tx.new(fixtures_file('rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin'))
211
+ @store.store_tx(outpoint_tx, false)
212
+ @store.store_tx(tx, false)
213
+ @store.get_tx(outpoint_tx.hash).out[0].get_next_in.should == tx.in[0]
214
+ end
215
+
216
+ it "should store multisig tx and index hash160's" do
217
+ *keys = Bitcoin::Key.generate, Bitcoin::Key.generate
218
+ pk_script = Bitcoin::Script.to_multisig_script(1, keys[0].pub, keys[1].pub)
219
+ txout = P::TxOut.new(1000, pk_script)
220
+ @store.store_txout(0, txout, 0)
221
+ keys.each do |key|
222
+ hash160 = Bitcoin.hash160(key.pub)
223
+ txouts = @store.get_txouts_for_hash160(hash160, true)
224
+ txouts.size.should == 1
225
+ txouts[0].pk_script.should == txout.pk_script
226
+ end
227
+ end
228
+
229
+ it "should index output script type" do
230
+ @store.store_tx(@tx, false)
231
+ @store.get_tx(@tx.hash).out.first.type.should == :hash160
232
+ end
233
+
234
+ end
235
+ end
236
+
237
+
238
+ describe :txouts do
239
+
240
+ before do
241
+ @key = Key.generate
242
+ @key2 = Key.generate
243
+ @store.store_block(@blk)
244
+ blk = create_block @blk.hash, true, [], @key
245
+ @block = create_block blk.hash, true, [->(t) {
246
+ create_tx(t, blk.tx.first, 0, [[50, @key2]]) }], @key
247
+ end
248
+
249
+ it "should get block for tx" do
250
+ @store.get_tx(@block.tx[1].hash).get_block.hash.should == @block.hash
251
+ end
252
+
253
+ it "should get txouts for pk script" do
254
+ script = @blk.tx[0].out[0].pk_script
255
+ @store.get_txouts_for_pk_script(script)
256
+ .should == [@blk.tx[0].out[0]]
257
+ end
258
+
259
+ it "should get txouts for hash160" do
260
+ @store.get_txouts_for_hash160(@key2.hash160, true)
261
+ .should == [@block.tx[1].out[0]]
262
+ end
263
+
264
+ it "should get txouts for address" do
265
+ @store.get_txouts_for_address(@key2.addr, true)
266
+ .should == [@block.tx[1].out[0]]
267
+ end
268
+
269
+ it "should get balance for address" do
270
+ @store.get_balance(@key.addr).should == 0
271
+ @store.get_balance(@key2.hash160).should == 50
272
+ end
273
+
274
+ end
275
+
276
+ describe "validation" do
277
+
278
+ before do
279
+ @key = Bitcoin::Key.generate
280
+ @store.store_block @blk
281
+ @block = create_block @blk.hash, false, [], @key
282
+ @tx = build_tx {|t| create_tx(t, @block.tx.first, 0, [[50, @key]]) }
283
+ @tx.instance_eval { @in = [] }
284
+ end
285
+
286
+ it "should validate blocks" do
287
+ @block.tx << @tx
288
+ -> { @store.store_block(@block) }.should
289
+ .raise(ValidationError).message.should =~ /mrkl_root/
290
+ end
291
+
292
+ it "should validate transactions for blocks added to main chain" do
293
+ @store.store_block(@block)
294
+ block = create_block @block.hash, false, [->(tx) {
295
+ create_tx(tx, @block.tx.first, 0, [[50, @key]]) }], @key
296
+ block.tx.last.in[0].prev_out_index = 5
297
+ -> { @store.store_block(block) }.should
298
+ .raise(ValidationError).message.should =~ /transactions_syntax/
299
+ end
300
+
301
+ it "should not validate transactions for blocks added to a side or orphan chain" do
302
+ @store.store_block(@block)
303
+ block = create_block @blk.hash, false, [->(tx) {
304
+ create_tx(tx, @block.tx.first, 0, [[50, @key]]) }], @key
305
+ @store.store_block(block).should == [5, 1]
306
+ end
307
+
308
+ if @store.class.name =~ /Sequel/
309
+
310
+ it "should validate transactions" do
311
+ @store.store_block @block
312
+ -> { @store.store_tx(@tx, true) }.should.raise(ValidationError)
313
+ end
314
+
315
+
316
+ it "should validate transactions for new main blocks on reorg" do
317
+ @store.store_block(@block)
318
+ block = create_block @blk.hash, true, [->(tx) {
319
+ create_tx(tx, @block.tx.first, 0, [[50, @key]]) }], @key
320
+ block2 = create_block block.hash, false, [], @key
321
+ -> { @store.store_block(block2) }.should
322
+ .raise(ValidationError).message.should =~ /transactions_context/
323
+ end
324
+
325
+ end
326
+
327
+ it "should skip validation" do
328
+ @store.config[:skip_validation] = true
329
+ @block.tx << @tx
330
+ @store.store_block(@block).should == [5, 0]
331
+ end
332
+
333
+ end
334
+
335
+ end
336
+
337
+ end
@@ -0,0 +1,261 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ include Bitcoin::Builder
6
+ include Bitcoin::Storage
7
+ include Bitcoin::Validation
8
+
9
+ [ { :name => :utxo, :db => 'sqlite:/', :index_all_addrs => true },
10
+ { :name => :sequel, :db => 'sqlite:/' } ].each do |configuration|
11
+
12
+ describe "block rules (#{configuration[:name].capitalize}Store)" do
13
+
14
+ def balance addr
15
+ @store.get_balance(Bitcoin.hash160_from_address(addr))
16
+ end
17
+
18
+ before do
19
+ Bitcoin.network = :spec
20
+ @store = Bitcoin::Storage.send(configuration[:name], configuration)
21
+ @store.reset
22
+ @store.log.level = :warn
23
+ Bitcoin.network[:proof_of_work_limit] = Bitcoin.encode_compact_bits("f"*64)
24
+ @key = Bitcoin::Key.generate
25
+ @block0 = create_block "00"*32, false, [], @key
26
+ Bitcoin.network[:genesis_hash] = @block0.hash
27
+ @store.store_block(@block0)
28
+ @store.get_head.should == @block0
29
+ @block1 = create_block @block0.hash, true, [], @key
30
+ @store.get_head.should == @block1
31
+ @block = create_block @block1.hash, false
32
+ end
33
+
34
+ def check_block blk, error
35
+ b = blk.dup
36
+ if block_given?
37
+ yield(b); b.bits = Bitcoin.network[:proof_of_work_limit]; b.recalc_block_hash
38
+ end
39
+
40
+ validator = b.validator(@store)
41
+ validator.validate.should == false
42
+ validator.error.should == error
43
+ end
44
+
45
+ it "1. Check syntactic correctness" do
46
+ block = create_block @block1.hash, false
47
+ block.hash = "00" * 32
48
+ block.bits = Bitcoin.network[:proof_of_work_limit]
49
+ validator = block.validator(@store)
50
+ validator.validate.should == false
51
+ validator.error.should == [:hash, ["00"*32, block.hash]]
52
+ end
53
+
54
+ it "3. Transaction list must be non-empty" do
55
+ check_block(@block, [:tx_list, 0]) {|b| b.tx = [] }
56
+ end
57
+
58
+ it "4. Block hash must satisfy claimed nBits proof of work" do
59
+ @block.bits = Bitcoin.encode_compact_bits("0000#{"ff" * 30}")
60
+ @block.recalc_block_hash
61
+ target = Bitcoin.decode_compact_bits(block.bits).to_i(16)
62
+ check_block(@block, [:bits, [@block.hash.to_i(16), target]])
63
+ end
64
+
65
+ it "5. Block timestamp must not be more than two hours in the future" do
66
+ fake_time = (Time.now + 3 * 60 * 60).to_i
67
+ check_block(@block, [:max_timestamp, [fake_time, Time.now.to_i + 7200]]) {|b|
68
+ b.time = fake_time }
69
+ end
70
+
71
+ it "6. First transaction must be coinbase (i.e. only 1 input, with hash=0, n=-1), the rest must not be" do
72
+ block = create_block @block1.hash, false, [
73
+ ->(tx) { create_tx(tx, @block1.tx.first, 0, [[50, @key]]) } ], @key
74
+ check_block(block, [:coinbase, [0, 1]]) {|b| b.tx = b.tx.reverse }
75
+ check_block(block, [:coinbase, [1, 1]]) {|b| b.tx << b.tx[0] }
76
+ end
77
+
78
+ it "8. For the coinbase (first) transaction, scriptSig length must be 2-100" do
79
+ check_block(@block, [:coinbase_scriptsig, [1, 2, 100]]) {|b|
80
+ b.tx[0].in[0].script_sig = "\x01" }
81
+ check_block(@block, [:coinbase_scriptsig, [101, 2, 100]]) {|b|
82
+ b.tx[0].in[0].script_sig = "\x01" * 101 }
83
+ end
84
+
85
+ it "10. Verify Merkle hash" do
86
+ check_block(@block, [:mrkl_root, ["00"*32, @block.mrkl_root.reverse_hth]]) {|b|
87
+ b.mrkl_root = "\x00" * 32 }
88
+ end
89
+
90
+ it "12. Check that nBits value matches the difficulty rules" do
91
+ block = create_block @block1.hash, false, [], @key
92
+ Bitcoin.network[:proof_of_work_limit] = Bitcoin.encode_compact_bits("0000#{"ff"*30}")
93
+ validator = block.validator(@store)
94
+ validator.validate.should == false
95
+ validator.error.should == [:difficulty, [553713663, 520159231]]
96
+ end
97
+
98
+ it "13. Reject if timestamp is the median time of the last 11 blocks or before" do
99
+ prev_block = @block1
100
+ 12.times do |i|
101
+ prev_block = create_block(prev_block.hash, false, [])
102
+ prev_block.time = Time.now.to_i - (12-i)
103
+ prev_block.recalc_block_hash
104
+ @store.store_block(prev_block).should == [i+2, 0]
105
+ end
106
+ block = create_block(prev_block.hash, false, [], @key)
107
+
108
+ fake_time = Time.now.to_i - 100
109
+ times = @store.db[:blk].where("depth > 2").map{|b|b[:time]}.sort
110
+ m, r = times.size.divmod(2)
111
+ min_time = (r == 0 ? times[m-1, 2].inject(:+) / 2.0 : times[m])
112
+ check_block(block, [:min_timestamp, [fake_time, min_time]]) {|b| b.time = fake_time }
113
+
114
+ fake_time = @store.get_block_by_depth(8).time
115
+ check_block(block, [:min_timestamp, [fake_time, fake_time]]) {|b| b.time = fake_time }
116
+ @store.store_block(block).should == [14, 0]
117
+ end
118
+
119
+ it "should allow chains of unconfirmed transactions" do
120
+ tx1 = build_tx {|t| create_tx(t, @block1.tx.first, 0, [[50, @key]]) }
121
+ tx2 = build_tx {|t| create_tx(t, tx1, 0, [[50, @key]]) }
122
+ block = create_block(@block1.hash, false, [], @key)
123
+ block.tx << tx1; block.tx << tx2
124
+ block.bits = Bitcoin.encode_compact_bits("f"*64)
125
+ block.mrkl_root = [Bitcoin.hash_mrkl_tree(block.tx.map(&:hash)).last].pack("H*").reverse
126
+ block.recalc_block_hash
127
+ @store.store_block(block).should == [2, 0]
128
+ end
129
+
130
+ it "should check coinbase output value" do
131
+ block2 = create_block(@block1.hash, false, [
132
+ ->(tx) { create_tx(tx, @block1.tx.first, 0, [[40e8, @key]])}
133
+ ], @key, 60e8)
134
+ @store.store_block(block2).should == [2, 0]
135
+
136
+ block3 = create_block(block2.hash, false, [], @key, 60e8)
137
+ -> { @store.store_block(block3) }.should.raise(ValidationError)
138
+
139
+ Bitcoin::Validation::REWARD_DROP = 2
140
+ block4 = create_block(block2.hash, false, [], @key, 50e8)
141
+ -> { @store.store_block(block4) }.should.raise(ValidationError)
142
+
143
+ block5 = create_block(block2.hash, false, [], @key, 25e8)
144
+ @store.store_block(block5).should == [3, 0]
145
+ Bitcoin::Validation::REWARD_DROP = 210_000
146
+ end
147
+
148
+ end
149
+
150
+ describe "transaction rules (#{configuration[:name].capitalize}Store)" do
151
+
152
+ before do
153
+ Bitcoin.network = :spec
154
+ @store = Bitcoin::Storage.send(configuration[:name], configuration)
155
+ @store.reset
156
+ @store.log.level = :warn
157
+
158
+ Bitcoin.network[:proof_of_work_limit] = Bitcoin.encode_compact_bits("f"*64)
159
+ @key = Bitcoin::Key.generate
160
+ @block0 = create_block "00"*32, false, [], @key
161
+ Bitcoin.network[:genesis_hash] = @block0.hash
162
+ @store.store_block(@block0)
163
+ @store.get_head.should == @block0
164
+ @block1 = create_block @block0.hash, true, [], @key
165
+ @store.get_head.should == @block1
166
+ @tx = build_tx {|t| create_tx(t, @block1.tx.first, 0, [[50, @key]]) }
167
+ end
168
+
169
+ def check_tx tx, error
170
+ t = tx.dup
171
+ yield(t) && t.instance_eval { @hash = generate_hash(to_payload) } if block_given?
172
+ validator = t.validator(@store)
173
+ validator.validate.should == false
174
+ validator.error.should == error
175
+ end
176
+
177
+ it "should validate" do
178
+ validator = @tx.validator(@store)
179
+ validator.validate.should == true
180
+ validator.validate(raise_errors: true).should == true
181
+ hash = @tx.hash; @tx.instance_eval { @hash = "f"*64 }
182
+ validator = @tx.validator(@store)
183
+ validator.validate.should == false
184
+ validator.error.should == [:hash, ["f"*64, hash]]
185
+ -> { validator.validate(raise_errors: true) }.should.raise(ValidationError)
186
+ end
187
+
188
+ it "1. Check syntactic correctness" do
189
+ hash = @tx.hash; @tx.instance_eval { @hash = "ff"*32 }
190
+ validator = @tx.validator(@store)
191
+ validator.validate.should == false
192
+ validator.error.should == [:hash, ["ff"*32, hash]]
193
+ end
194
+
195
+ it "2. Make sure neither in or out lists are empty" do
196
+ check_tx(@tx, [:lists, [0, 1]]) {|tx| tx.instance_eval { @in = [] } }
197
+ check_tx(@tx, [:lists, [1, 0]]) {|tx| tx.instance_eval { @out = [] } }
198
+ end
199
+
200
+ it "3. Size in bytes < MAX_BLOCK_SIZE" do
201
+ max = Bitcoin::Validation::MAX_BLOCK_SIZE; Bitcoin::Validation::MAX_BLOCK_SIZE = 1000
202
+ check_tx(@tx, [:max_size, [@tx.payload.bytesize+978, 1000]]) {|tx|
203
+ tx.out[0].pk_script = "\x00" * 1001 }
204
+ Bitcoin::Validation::MAX_BLOCK_SIZE = max
205
+ end
206
+
207
+ it "4. Each output value, as well as the total, must be in legal money range" do
208
+ check_tx(@tx, [:output_values, [Bitcoin::network[:max_money] + 1, Bitcoin::network[:max_money]]]) {|tx|
209
+ tx.out[0].value = Bitcoin::network[:max_money] + 1 }
210
+ end
211
+
212
+ it "5. Make sure none of the inputs have hash=0, n=-1" do
213
+ check_tx(@tx, [:inputs, [0]]) do |tx|
214
+ tx.in.first.prev_out = "\x00"*32
215
+ tx.in.first.prev_out_index = 4294967295
216
+ end
217
+ end
218
+
219
+ it "6. Check that nLockTime <= INT_MAX, size in bytes >= 100, and sig opcount <= 2" do
220
+ check_tx(@tx, [:lock_time, [INT_MAX + 1, INT_MAX]]) {|tx| tx.lock_time = INT_MAX + 1 }
221
+ # TODO: validate sig opcount
222
+ end
223
+
224
+ # it "7. Reject 'nonstandard' transactions: scriptSig doing anything other than pushing numbers on the stack, or scriptPubkey not matching the two usual forms" do
225
+ # check_tx(@tx, /standard/) {|tx| tx.out[0].pk_script = Bitcoin::Script.from_string("OP_ADD OP_DUP OP_DROP").raw }
226
+ # end
227
+
228
+ # it "9. Reject if any other tx in the pool uses the same transaction output as one used by this tx." do
229
+ # end
230
+
231
+ it "11. For each input, if we are using the nth output of the earlier transaction, but it has fewer than n+1 outputs, reject this transaction" do
232
+ check_tx(@tx, [:prev_out, [[@tx.in[0].prev_out.reverse_hth, 2]]]) {|tx| tx.in[0].prev_out_index = 2 }
233
+ end
234
+
235
+ it "13. Verify crypto signatures for each input; reject if any are bad" do
236
+ check_tx(@tx, [:signatures, [0]]) {|tx| @tx.in[0].script_sig[-1] = "\x00" }
237
+ end
238
+
239
+ it "14. For each input, if the referenced output has already been spent by a transaction in the main branch, reject this transaction" do
240
+ block2 = create_block(@block1.hash, true, [
241
+ ->(tx) {create_tx(tx, @block1.tx.first, 0, [[50, @key]])}], @key)
242
+ if @store.class.name =~ /Utxo/
243
+ check_tx(@tx, [:prev_out, [[@tx.in[0].prev_out.reverse_hth, 0]]])
244
+ else
245
+ check_tx(@tx, [:spent, [0]])
246
+ end
247
+ end
248
+
249
+ it "15. Using the referenced output transactions to get input values, check that each input value, as well as the sum, are in legal money range" do
250
+ @store.db[@store.class.name =~ /Utxo/ ? :utxo : :txout].where(id: 2).update(value: 22e14)
251
+ check_tx(@tx, [:input_values, [22e14, 21e14]])
252
+ end
253
+
254
+ it "16. Reject if the sum of input values < sum of output values" do
255
+ tx = build_tx {|t| create_tx(t, @block1.tx.first, 0, [[100e8, @key]]) }
256
+ check_tx(tx, [:output_sum, [100e8, 50e8]])
257
+ end
258
+
259
+ end
260
+
261
+ end