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,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