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