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.
- data/.gitignore +4 -1
- data/Gemfile +21 -0
- data/README.rdoc +85 -25
- data/Rakefile +7 -3
- data/bin/bitcoin_node +39 -42
- data/bin/bitcoin_shell +1 -0
- data/bin/bitcoin_wallet +129 -53
- data/bitcoin-ruby.gemspec +4 -7
- data/concept-examples/blockchain-pow.rb +1 -1
- data/doc/CONFIG.rdoc +5 -5
- data/doc/EXAMPLES.rdoc +9 -5
- data/doc/NAMECOIN.rdoc +34 -0
- data/doc/NODE.rdoc +147 -10
- data/examples/balance.rb +10 -4
- data/examples/bbe_verify_tx.rb +7 -2
- data/examples/forwarder.rb +73 -0
- data/examples/generate_tx.rb +34 -0
- data/examples/simple_network_monitor_and_util.rb +187 -0
- data/examples/verify_tx.rb +1 -1
- data/lib/bitcoin.rb +308 -18
- data/lib/bitcoin/builder.rb +62 -36
- data/lib/bitcoin/config.rb +2 -0
- data/lib/bitcoin/connection.rb +11 -8
- data/lib/bitcoin/electrum/mnemonic.rb +162 -0
- data/lib/bitcoin/ffi/openssl.rb +187 -21
- data/lib/bitcoin/gui/addr_view.rb +2 -0
- data/lib/bitcoin/gui/conn_view.rb +2 -0
- data/lib/bitcoin/gui/connection.rb +2 -0
- data/lib/bitcoin/gui/em_gtk.rb +2 -0
- data/lib/bitcoin/gui/gui.rb +2 -0
- data/lib/bitcoin/gui/helpers.rb +2 -0
- data/lib/bitcoin/gui/tree_view.rb +2 -0
- data/lib/bitcoin/gui/tx_view.rb +2 -0
- data/lib/bitcoin/key.rb +77 -11
- data/lib/bitcoin/litecoin.rb +81 -0
- data/lib/bitcoin/logger.rb +20 -1
- data/lib/bitcoin/namecoin.rb +279 -0
- data/lib/bitcoin/network/command_client.rb +7 -6
- data/lib/bitcoin/network/command_handler.rb +229 -43
- data/lib/bitcoin/network/connection_handler.rb +182 -70
- data/lib/bitcoin/network/node.rb +231 -106
- data/lib/bitcoin/protocol.rb +44 -23
- data/lib/bitcoin/protocol/address.rb +5 -3
- data/lib/bitcoin/protocol/alert.rb +3 -4
- data/lib/bitcoin/protocol/aux_pow.rb +123 -0
- data/lib/bitcoin/protocol/block.rb +98 -18
- data/lib/bitcoin/protocol/handler.rb +6 -5
- data/lib/bitcoin/protocol/parser.rb +44 -19
- data/lib/bitcoin/protocol/tx.rb +105 -52
- data/lib/bitcoin/protocol/txin.rb +39 -19
- data/lib/bitcoin/protocol/txout.rb +28 -13
- data/lib/bitcoin/protocol/version.rb +16 -7
- data/lib/bitcoin/script.rb +579 -122
- data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
- data/lib/bitcoin/storage/models.rb +20 -7
- data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
- data/lib/bitcoin/storage/storage.rb +233 -28
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
- data/lib/bitcoin/validation.rb +369 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/coinselector.rb +3 -0
- data/lib/bitcoin/wallet/keygenerator.rb +3 -1
- data/lib/bitcoin/wallet/keystore.rb +6 -2
- data/lib/bitcoin/wallet/txdp.rb +6 -4
- data/lib/bitcoin/wallet/wallet.rb +54 -16
- data/spec/bitcoin/bitcoin_spec.rb +48 -3
- data/spec/bitcoin/builder_spec.rb +40 -17
- data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
- data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
- data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
- data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
- data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
- data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
- data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
- data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
- data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
- data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
- data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
- data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
- data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
- data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
- data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
- data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
- data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
- data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
- data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
- data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
- data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
- data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
- data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
- data/spec/bitcoin/key_spec.rb +128 -3
- data/spec/bitcoin/namecoin_spec.rb +182 -0
- data/spec/bitcoin/network_spec.rb +5 -3
- data/spec/bitcoin/node/command_api_spec.rb +376 -0
- data/spec/bitcoin/protocol/addr_spec.rb +2 -0
- data/spec/bitcoin/protocol/alert_spec.rb +2 -0
- data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
- data/spec/bitcoin/protocol/block_spec.rb +134 -39
- data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
- data/spec/bitcoin/protocol/inv_spec.rb +10 -8
- data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
- data/spec/bitcoin/protocol/ping_spec.rb +2 -0
- data/spec/bitcoin/protocol/tx_spec.rb +83 -17
- data/spec/bitcoin/protocol/version_spec.rb +7 -5
- data/spec/bitcoin/script/opcodes_spec.rb +412 -133
- data/spec/bitcoin/script/script_spec.rb +112 -13
- data/spec/bitcoin/spec_helper.rb +68 -0
- data/spec/bitcoin/storage/reorg_spec.rb +199 -0
- data/spec/bitcoin/storage/storage_spec.rb +337 -0
- data/spec/bitcoin/storage/validation_spec.rb +261 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
- data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
- metadata +105 -51
- data/lib/bitcoin/storage/sequel.rb +0 -335
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
- data/spec/bitcoin/reorg_spec.rb +0 -129
- 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
|