bitcoin-ruby 0.0.5 → 0.0.6

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +2 -2
  4. data/COPYING +1 -1
  5. data/Gemfile +5 -11
  6. data/README.rdoc +11 -5
  7. data/Rakefile +5 -0
  8. data/bin/bitcoin_node +11 -29
  9. data/bin/bitcoin_node_cli +81 -0
  10. data/bin/bitcoin_wallet +9 -6
  11. data/doc/NODE.rdoc +79 -26
  12. data/examples/bbe_verify_tx.rb +1 -1
  13. data/examples/index_nhash.rb +24 -0
  14. data/examples/reindex_p2sh_addrs.rb +44 -0
  15. data/lib/bitcoin.rb +135 -20
  16. data/lib/bitcoin/builder.rb +233 -63
  17. data/lib/bitcoin/key.rb +89 -16
  18. data/lib/bitcoin/litecoin.rb +13 -11
  19. data/lib/bitcoin/namecoin.rb +5 -4
  20. data/lib/bitcoin/network/command_client.rb +23 -13
  21. data/lib/bitcoin/network/command_handler.rb +336 -131
  22. data/lib/bitcoin/network/connection_handler.rb +14 -13
  23. data/lib/bitcoin/network/node.rb +61 -20
  24. data/lib/bitcoin/protocol.rb +5 -1
  25. data/lib/bitcoin/protocol/block.rb +15 -3
  26. data/lib/bitcoin/protocol/parser.rb +3 -3
  27. data/lib/bitcoin/protocol/tx.rb +82 -20
  28. data/lib/bitcoin/protocol/txin.rb +7 -0
  29. data/lib/bitcoin/protocol/txout.rb +12 -9
  30. data/lib/bitcoin/script.rb +329 -75
  31. data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
  32. data/lib/bitcoin/storage/models.rb +6 -11
  33. data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
  34. data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
  35. data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
  36. data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
  37. data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
  38. data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
  39. data/lib/bitcoin/storage/storage.rb +161 -97
  40. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
  41. data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
  42. data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
  43. data/lib/bitcoin/validation.rb +87 -56
  44. data/lib/bitcoin/version.rb +1 -1
  45. data/spec/bitcoin/bitcoin_spec.rb +38 -0
  46. data/spec/bitcoin/builder_spec.rb +177 -0
  47. data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
  48. data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
  49. data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
  50. data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
  51. data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
  52. data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
  53. data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
  54. data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
  55. data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
  56. data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
  57. data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
  58. data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
  59. data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
  60. data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
  61. data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
  62. data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
  63. data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
  64. data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
  65. data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
  66. data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
  67. data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
  68. data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
  69. data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
  70. data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
  71. data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
  72. data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
  73. data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
  74. data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
  75. data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
  76. data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
  77. data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
  78. data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
  79. data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
  80. data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
  81. data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
  82. data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
  83. data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
  84. data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
  85. data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
  86. data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
  87. data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
  88. data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
  89. data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
  90. data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
  91. data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
  92. data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
  93. data/spec/bitcoin/key_spec.rb +79 -8
  94. data/spec/bitcoin/namecoin_spec.rb +1 -1
  95. data/spec/bitcoin/node/command_api_spec.rb +373 -86
  96. data/spec/bitcoin/performance/storage_spec.rb +41 -0
  97. data/spec/bitcoin/protocol/addr_spec.rb +7 -5
  98. data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
  99. data/spec/bitcoin/protocol/block_spec.rb +6 -0
  100. data/spec/bitcoin/protocol/tx_spec.rb +184 -1
  101. data/spec/bitcoin/protocol/txin_spec.rb +27 -0
  102. data/spec/bitcoin/protocol/txout_spec.rb +27 -0
  103. data/spec/bitcoin/script/opcodes_spec.rb +74 -3
  104. data/spec/bitcoin/script/script_spec.rb +271 -0
  105. data/spec/bitcoin/spec_helper.rb +34 -6
  106. data/spec/bitcoin/storage/models_spec.rb +104 -0
  107. data/spec/bitcoin/storage/reorg_spec.rb +42 -11
  108. data/spec/bitcoin/storage/storage_spec.rb +58 -15
  109. data/spec/bitcoin/storage/validation_spec.rb +44 -14
  110. data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
  111. data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
  112. data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
  113. metadata +117 -11
@@ -3,14 +3,33 @@ require_relative '../spec_helper.rb'
3
3
  include Bitcoin
4
4
  include Builder
5
5
 
6
+ class Array
7
+ def stringify_keys
8
+ map do |e|
9
+ (e.is_a?(Array) || e.is_a?(Hash)) ? e.stringify_keys : e
10
+ end
11
+ end
12
+ end
13
+
14
+ class Hash
15
+ def stringify_keys
16
+ Hash[map do |k, v|
17
+ v = v.stringify_keys if v.is_a?(Hash) || v.is_a?(Array)
18
+ [k.to_s, v]
19
+ end]
20
+ end
21
+ end
22
+
6
23
  describe 'Node Command API' do
7
24
 
8
- def test_command command, params = [], response = nil, &block
25
+ TSLB_TIMEOUT = 3
26
+
27
+ def test_command command, params = nil, response = nil, &block
9
28
  $responses = {}
10
29
  EM.run do
11
30
  @client = Bitcoin::Network::CommandClient.connect(*@config[:command]) do
12
31
  on_connected do
13
- request(command, *params)
32
+ request(command, params)
14
33
  end
15
34
  on_response do |cmd, data|
16
35
  $responses[cmd] = data
@@ -26,7 +45,7 @@ describe 'Node Command API' do
26
45
  if block
27
46
  block.call(result)
28
47
  else
29
- result.should == response
48
+ raise "ERROR: #{result} != #{response}" unless result.should == response
30
49
  end
31
50
  end
32
51
 
@@ -49,7 +68,7 @@ describe 'Node Command API' do
49
68
 
50
69
  @node = Bitcoin::Network::Node.new(@config)
51
70
  @pid = fork do
52
- $stdout = StringIO.new
71
+ # $stdout = StringIO.new
53
72
  SimpleCov.running = false if defined?(SimpleCov)
54
73
  @node.run
55
74
  end
@@ -60,8 +79,10 @@ describe 'Node Command API' do
60
79
  @key = Bitcoin::Key.generate
61
80
  @block = create_block @genesis.hash, false, [], @key
62
81
 
63
- test_command "store_block", [@genesis.to_payload.hth]
82
+ test_command "store_block", hex: @genesis.to_payload.hth
64
83
  sleep 0.1
84
+
85
+ @id = 0
65
86
  end
66
87
 
67
88
  after do
@@ -69,27 +90,24 @@ describe 'Node Command API' do
69
90
  end
70
91
 
71
92
  it "should return error for unknown command" do
72
- test_command("foo").should == {"error" => "unknown command: foo. send 'help' for help."}
93
+ test_command("foo", nil, {"error" => "unknown command: foo. send 'help' for help."})
73
94
  end
74
95
 
75
- it "should return error for wrong parameters" do
76
- test_command("info", "foo").should == {"error" => "wrong number of arguments (1 for 0)"}
77
- end
96
+ # it "should return error for wrong parameters" do
97
+ # test_command("info", "foo", {"error" => "wrong number of arguments (1 for 0)"})
98
+ # end
78
99
 
79
100
  it "should query tslb" do
80
- test_command("tslb") do |res|
81
- res.keys.include?("tslb").should == true
82
- res["tslb"].should >= 0
83
- res["tslb"].should <= 1
84
- end
101
+ test_command("tslb") {|r| (0..TSLB_TIMEOUT).include?(r['tslb']).should == true }
85
102
  end
86
103
 
87
104
  it "should query info" do
88
105
  info = test_command "info"
89
106
  info.is_a?(Hash).should == true
90
- info["blocks"].should == "0 (?)"
91
- info["addrs"].should == "0 (0)"
92
- info["connections"].should == "0 established (0 out, 0 in), 0 connecting"
107
+ info["blocks"].should == { "depth" => 0, "peers" => "?", "sync" => false }
108
+ info["addrs"].should == { "alive" => 0, "total" => 0 }
109
+ info["connections"].should == {
110
+ "established" => 0, "outgoing" => 0, "incoming" => 0, "connecting" => 0 }
93
111
  info["queue"].should == 0
94
112
  info["inv_queue"].should == 0
95
113
  info["inv_cache"].should == 0
@@ -97,7 +115,7 @@ describe 'Node Command API' do
97
115
  info["storage"].should == "sequel::sqlite:/"
98
116
  info["version"].should == 70001
99
117
  info["external_ip"].should == "127.0.0.1"
100
- info["uptime"].should =~ /00:00:00:0[0|1]/
118
+ info["uptime"].between?(0, 1).should == true
101
119
  end
102
120
 
103
121
  it "should query config" do
@@ -111,45 +129,60 @@ describe 'Node Command API' do
111
129
 
112
130
  # TODO
113
131
  it "should connect" do
114
- test_command("connect", ["127.0.0.1:1234"])["state"].should == "Connecting..."
132
+ test_command("connect", {host: "127.0.0.1", port: 1234})["state"].should == "connecting"
115
133
  end
116
134
 
117
135
  # TODO
118
136
  it "should disconnect" do
119
- test_command("disconnect", ["127.0.0.1:1234"])["state"].should == "Disconnected"
137
+ test_command("disconnect", ["127.0.0.1:1234"])["state"].should == "disconnected"
120
138
  end
121
139
 
122
140
  it "should store block" do
123
- test_command("info")["blocks"].should == "0 (?)"
124
- res = test_command "store_block", [ @block.to_payload.hth ]
125
- res.should == { "queued" => [ "block", @block.hash ] }
141
+ test_command("info")["blocks"].should == {"depth" => 0, "peers" => "?", "sync" => false}
142
+ res = test_command("store_block", { hex: @block.to_payload.hth })
143
+ res.should == { "queued" => @block.hash }
126
144
  sleep 0.1
127
- test_command("info")["blocks"].should == "1 (?) sync"
145
+ test_command("info")["blocks"]["depth"].should == 1
146
+ test_command("info")["blocks"]["sync"].should == true
128
147
  end
129
148
 
149
+ # TODO
150
+ # it "should store tx" do
151
+ # @tx = @block.tx[1]
152
+ # res = test_command("store_tx", { hex: @tx.to_payload.htb })
153
+ # res.should == { "queued" => @tx.hash }
154
+ # end
155
+
130
156
  describe :create_tx do
131
157
 
132
158
  before do
133
159
  @key2 = Key.generate
134
- test_command("store_block", [@block.to_payload.hth])
160
+ test_command("store_block", hex: @block.to_payload.hth)
135
161
  sleep 0.1
136
162
  end
137
163
 
138
164
  it "should create transaction from given private keys" do
139
- res = test_command("create_tx", [[@key.to_base58], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
140
- tx = P::Tx.new(res[0].htb)
141
- tx.is_a?(P::Tx).should == true
165
+ res = test_command("create_tx", {
166
+ keys: [ @key.to_base58 ],
167
+ recipients: [[@key2.addr, 10e8], [@key.addr, 40e8]]
168
+ })
169
+ tx = P::Tx.new(res["hex"].htb)
170
+ tx.hash.should == res["hash"]
142
171
  tx.verify_input_signature(0, @block.tx[0]).should == true
143
172
  end
144
173
 
145
- it "should create transaction from given addresses" do
146
- res = test_command("create_tx", [[@key.addr], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
147
- tx = P::Tx.new(res[0].htb)
148
- tx.is_a?(P::Tx).should == true
174
+ it "should create unsigned transaction from given addresses" do
175
+ res = test_command("create_tx", {
176
+ keys: [ @key.addr ],
177
+ recipients: [[@key2.addr, 10e8], [@key.addr, 40e8]]
178
+ })
179
+ tx = P::Tx.new(res["hex"].htb)
180
+ tx.hash.should == res["hash"]
149
181
  tx.in[0].script_sig.should == ""
150
- -> { tx.verify_input_signature(0, @block.tx[0]) }.should.raise(TypeError)
182
+ #-> { tx.verify_input_signature(0, @block.tx[0]) }.should.raise(TypeError)
183
+ tx.verify_input_signature(0, @block.tx[0]).should == false
151
184
 
152
- res[1].each.with_index do |sig_data, idx|
185
+ res["missing_sigs"].each.with_index do |sig_data, idx|
153
186
  sig_hash, sig_addr = *sig_data
154
187
  sig_addr.should == @key.addr
155
188
  sig = @key.sign(sig_hash.htb)
@@ -162,12 +195,16 @@ describe 'Node Command API' do
162
195
  end
163
196
 
164
197
  it "should create transaction from given pubkeys" do
165
- res = test_command("create_tx", [[@key.pub], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
166
- tx = P::Tx.new(res[0].htb)
167
- tx.is_a?(P::Tx).should == true
168
- -> { tx.verify_input_signature(0, @block.tx[0]) }.should.raise(TypeError)
169
-
170
- res[1].each.with_index do |sig_data, idx|
198
+ res = test_command("create_tx", {
199
+ keys: [@key.pub],
200
+ recipients: [[@key2.addr, 10e8], [@key.addr, 40e8]]
201
+ })
202
+ tx = P::Tx.new(res["hex"].htb)
203
+ tx.hash.should == res["hash"]
204
+ #-> { tx.verify_input_signature(0, @block.tx[0]) }.should.raise(TypeError)
205
+ tx.verify_input_signature(0, @block.tx[0]).should == false
206
+
207
+ res["missing_sigs"].each.with_index do |sig_data, idx|
171
208
  sig_hash, sig_addr = *sig_data
172
209
  sig_addr.should == @key.addr
173
210
  sig = @key.sign(sig_hash.htb)
@@ -192,10 +229,11 @@ describe 'Node Command API' do
192
229
  t.output {|o| o.value 50e8; o.script {|s| s.recipient @key.addr } }
193
230
  end
194
231
  sig = @key.sign(tx.in[0].sig_hash)
195
- test_command("store_block", [@block.to_payload.hth])
232
+ test_command("store_block", hex: @block.to_payload.hth)
196
233
  sleep 0.1
197
- res = test_command("assemble_tx", [tx.to_payload.hth, [[sig.hth, @key.pub]]])
198
- tx = Bitcoin::P::Tx.new(res.htb)
234
+ res = test_command("assemble_tx", {tx: tx.to_payload.hth, sig_pubs: [[sig.hth, @key.pub]]})
235
+ tx = Bitcoin::P::Tx.new(res["hex"].htb)
236
+ tx.hash.should == res["hash"]
199
237
  tx.verify_input_signature(0, @block.tx[0]).should == true
200
238
  end
201
239
 
@@ -204,7 +242,7 @@ describe 'Node Command API' do
204
242
  describe :relay_tx do
205
243
 
206
244
  it "should handle decoding error" do
207
- res = test_command("relay_tx", ["foobar"])
245
+ res = test_command("relay_tx", hex: "foobar")
208
246
  res["error"].should == "Error decoding transaction."
209
247
  end
210
248
 
@@ -214,7 +252,7 @@ describe 'Node Command API' do
214
252
  create_tx(t, @block.tx[0], 0, [[22e14, @key]]) }], @key)
215
253
  tx = block.tx[1]
216
254
 
217
- error = test_command("relay_tx", [tx.to_payload.hth])
255
+ error = test_command("relay_tx", hex: tx.to_payload.hth)
218
256
  error["error"].should == "Transaction syntax invalid."
219
257
  error["details"].should == ["output_values", [22e14, 21e14]]
220
258
  end
@@ -225,7 +263,7 @@ describe 'Node Command API' do
225
263
  create_tx(t, @block.tx[0], 0, [[25e8, @key]]) }], @key)
226
264
  tx = block.tx[1]
227
265
 
228
- error = test_command("relay_tx", [tx.to_payload.hth])
266
+ error = test_command("relay_tx", hex: tx.to_payload.hth)
229
267
  error["error"].should == "Transaction context invalid."
230
268
  error["details"].should == ["prev_out", [[@block.tx[0].hash, 0]]]
231
269
  end
@@ -235,9 +273,9 @@ describe 'Node Command API' do
235
273
  create_tx(t, @block.tx[0], 0, [[25e8, @key]]) }], @key)
236
274
  tx = block.tx[1]
237
275
 
238
- test_command("store_block", [@block.to_payload.hth])
276
+ test_command("store_block", hex: @block.to_payload.hth)
239
277
  sleep 0.1
240
- res = test_command("relay_tx", [tx.to_payload.hth, 1, 0])
278
+ res = test_command("relay_tx", hex: tx.to_payload.hth, send: 1, wait: 0)
241
279
  res["success"].should == true
242
280
  res["hash"].should == tx.hash
243
281
  res["propagation"].should == { "sent" => 1, "received" => 0, "percent" => 0.0 }
@@ -250,23 +288,106 @@ describe 'Node Command API' do
250
288
  before do
251
289
  @client = TCPSocket.new(*@config[:command])
252
290
 
253
- def send data
254
- @client.write(data.to_json + "\x00")
291
+ def send method, params = nil, client = @client
292
+ request = { id: @id += 1, method: method, params: params }
293
+ client.write(request.to_json + "\x00")
294
+ request.stringify_keys
255
295
  end
256
296
 
257
- def should_receive expected
258
- buf = ""
259
- while b = @client.read(1)
260
- break if b == "\x00"
261
- buf << b
297
+ def should_receive request, expected, client = @client
298
+ expected = expected.stringify_keys if expected.is_a?(Hash)
299
+ begin
300
+ Timeout.timeout(100) do
301
+ buf = ""
302
+ while b = client.read(1)
303
+ break if b == "\x00"
304
+ buf << b
305
+ end
306
+ resp = JSON.load(buf)
307
+ expected = request.merge(result: expected).stringify_keys
308
+ expected.delete("params")
309
+ raise "ERROR: #{resp} != #{expected}" unless resp.should == expected
310
+ end
311
+ rescue Timeout::Error
312
+ print " [TIMEOUT]"
313
+ :timeout.should == nil
262
314
  end
263
- resp = JSON.load(buf)
264
- resp.should == expected
315
+ end
316
+
317
+ def should_receive_block request, block, depth, client = @client
318
+ expected = { hash: block.hash, hex: block.to_payload.hth, depth: depth }
319
+ should_receive(request, expected, client)
320
+ end
321
+
322
+ def should_receive_tx request, tx, conf, client = @client
323
+ expected = { hash: tx.hash, nhash: tx.nhash, hex: tx.to_payload.hth, conf: conf }
324
+ should_receive(request, expected, client)
325
+ end
326
+
327
+ def should_receive_output request, tx, idx, conf, client = @client
328
+ expected = { hash: tx.hash, nhash: tx.nhash, idx: idx,
329
+ address: tx.out[idx].parsed_script.get_address, value: tx.out[idx].value, conf: conf }
330
+ should_receive(request, expected, client)
265
331
  end
266
332
 
267
333
  def store_block block
268
- send ["store_block", [ block.to_payload.hth ]]
269
- should_receive ["store_block", {"queued" => [ "block", block.hash ]}]
334
+ request = send("store_block", hex: block.to_payload.hth)
335
+ should_receive(request, {"queued" => block.hash })
336
+ end
337
+
338
+ end
339
+
340
+ describe :channels do
341
+
342
+ it "should combine multiple channels" do
343
+ should_receive r1 = send("monitor", channel: "block"), id: 0
344
+ should_receive r2 = send("monitor", channel: "tx", conf: 1), id: 1
345
+ store_block @block
346
+ should_receive_block(r1, @block, 1)
347
+ should_receive_tx(r2, @block.tx[0], 1)
348
+ end
349
+
350
+ it "should handle multiple clients" do
351
+ @client2 = TCPSocket.new(*@config[:command])
352
+ should_receive r1_1 = send("monitor", channel: "tx", conf: 1), id: 0
353
+ r1_2 = send("monitor", { channel: "block" }, @client2)
354
+ should_receive r1_2, { id: 0 }, @client2
355
+
356
+ store_block @block
357
+
358
+ should_receive_block(r1_2, @block, 1, @client2)
359
+ should_receive_tx(r1_1, @block.tx[0], 1)
360
+
361
+ block = create_block @block.hash, false
362
+ store_block block
363
+
364
+ should_receive_block(r1_2, block, 2, @client2)
365
+ should_receive_tx(r1_1, block.tx[0], 1)
366
+
367
+ r2_2 = send "monitor", { channel: "tx", conf: 1 }, @client2
368
+ should_receive r2_2, { id: 1 }, @client2
369
+ should_receive r2_1 = send("monitor", channel: "block"), id: 1
370
+
371
+ block = create_block block.hash, false
372
+ store_block block
373
+
374
+ should_receive_block(r1_2, block, 3, @client2)
375
+ should_receive_tx(r2_2, block.tx[0], 1, @client2)
376
+
377
+ should_receive_tx(r1_1, block.tx[0], 1)
378
+
379
+ # if something was wrong, we would now receive the last tx again
380
+
381
+ should_receive_block(r2_1, block, 3)
382
+
383
+ block = create_block block.hash, false
384
+ store_block block
385
+
386
+ should_receive_tx(r1_1, block.tx[0], 1)
387
+
388
+ should_receive_block(r2_1, block, 4)
389
+ should_receive_block(r1_2, block, 4, @client2)
390
+ should_receive_tx(r2_2, block.tx[0], 1, @client2)
270
391
  end
271
392
 
272
393
  end
@@ -274,18 +395,26 @@ describe 'Node Command API' do
274
395
  describe :block do
275
396
 
276
397
  before do
277
- send ["monitor", ["block"]]
278
- should_receive ["monitor", ["block", [ @genesis.to_hash, 0 ]]]
398
+ @request = send "monitor", channel: "block"
399
+
400
+ should_receive(@request, id: 0)
279
401
  store_block @block
280
- should_receive ["monitor", ["block", [ @block.to_hash, 1 ]]]
402
+ should_receive_block(@request, @block, 1)
281
403
  end
282
404
 
283
405
  it "should monitor block" do
284
406
  @block = create_block @block.hash, false
285
407
  store_block @block
286
- should_receive ["monitor", ["block", [ @block.to_hash, 2 ]]]
408
+ should_receive_block(@request, @block, 2)
287
409
  end
288
410
 
411
+ it "should unmonitor block" do
412
+ @request = send "unmonitor", id: 0
413
+ should_receive @request, id: 0
414
+ store_block create_block(@block.hash, false)
415
+
416
+ test_command("tslb") {|r| (0..TSLB_TIMEOUT).include?(r['tslb']).should == true }
417
+ end
289
418
 
290
419
  it "should not monitor side or orphan blocks" do
291
420
  @side = create_block @genesis.hash, false
@@ -297,37 +426,138 @@ describe 'Node Command API' do
297
426
  # should not send side or orphan block only the next main block
298
427
  @block = create_block @block.hash, false
299
428
  store_block @block
300
- should_receive ["monitor", ["block", [ @block.to_hash, 2 ]]]
429
+
430
+ should_receive_block(@request, @block, 2)
431
+ end
432
+
433
+ it "should received missed blocks when last block hash is given" do
434
+ @client = TCPSocket.new(*@config[:command])
435
+ blocks = [@block]
436
+ 3.times do
437
+ blocks << create_block(blocks.last.hash, false)
438
+ store_block blocks.last
439
+ end
440
+ sleep 0.1
441
+
442
+ r = send "monitor", channel: "block", last: blocks[1].hash
443
+
444
+ should_receive_block(r, blocks[1], 2)
445
+ should_receive_block(r, blocks[2], 3)
446
+ should_receive_block(r, blocks[3], 4)
447
+ end
448
+
449
+ end
450
+
451
+ describe :reorg do
452
+
453
+ before do
454
+ @request = send "monitor", channel: "reorg"
455
+ should_receive @request, id: 0
456
+ store_block @block
457
+ end
458
+
459
+ it "should monitor reorg" do
460
+ @block1 = create_block @genesis.hash, false
461
+ store_block @block1
462
+ @block2 = create_block @block1.hash, false
463
+ store_block @block2
464
+ should_receive @request, { new_main: [ @block1.hash ], new_side: [ @block.hash ] }
465
+ end
466
+
467
+ it "should unmonitor reorg" do
468
+ r = send "unmonitor", id: 0
469
+ should_receive r, id: 0
470
+ @block1 = create_block @genesis.hash, false
471
+ store_block @block1
472
+ @block2 = create_block @block1.hash, false
473
+ store_block @block2
474
+
475
+ test_command("tslb") {|r| (0..TSLB_TIMEOUT).include?(r['tslb']).should == true }
301
476
  end
302
477
 
303
478
  end
304
479
 
305
480
  describe :tx do
306
481
 
482
+
307
483
  it "should monitor unconfirmed tx" do
308
- send ["monitor", ["tx"]]
484
+ r1 = send "monitor", channel: "tx"
485
+ should_receive r1, id: 0
309
486
  tx = @block.tx[0]
310
- send ["store_tx", [ tx.to_payload.hth ] ]
311
- should_receive ["store_tx", { "queued" => [ "tx", tx.hash ]}]
312
- should_receive ["monitor", ["tx", [ tx.to_hash, 0 ]]]
487
+ r2 = send "store_tx", hex: tx.to_payload.hth
488
+ should_receive r2, { "queued" => tx.hash }
489
+
490
+ should_receive_tx(r1, tx, 0)
491
+ end
492
+
493
+ it "should unmonitor tx" do
494
+ r1 = send "monitor", channel: "tx"
495
+ should_receive r1, id: 0
496
+
497
+ r2 = send "unmonitor", id: 0
498
+ should_receive r2, id: 0
499
+
500
+ tx = @block.tx[0]
501
+ r3 = send "store_tx", hex: tx.to_payload.hth
502
+ should_receive r3, { "queued" => tx.hash }
503
+
504
+ test_command("tslb") {|r| (0..TSLB_TIMEOUT).include?(r['tslb']).should == true }
313
505
  end
314
506
 
315
507
  it "should monitor confirmed tx" do
316
- send ["monitor", ["tx_1"]]
508
+ r = send "monitor", channel: "tx", conf: 1
509
+ should_receive r, id: 0
317
510
  store_block @block
318
- should_receive ["monitor", ["tx_1", [ @block.tx[0].to_hash, 1 ]]]
511
+
512
+ should_receive_tx(r, @block.tx[0], 1)
319
513
  end
320
514
 
321
515
  it "should monitor tx for given confirmation level" do
322
- send ["monitor", ["tx_3"]]
516
+ r = send "monitor", channel: "tx", conf: 3
517
+ should_receive r, id: 0
518
+
323
519
  @tx = @block.tx[0]
324
520
  store_block @block
325
521
  @block = create_block @block.hash, false
326
522
  store_block @block
327
- should_receive ["monitor", ["tx_3", [ @genesis.tx[0].to_hash, 3 ]]]
523
+
524
+ should_receive_tx(r, @genesis.tx[0], 3)
525
+
328
526
  @block = create_block @block.hash, false
329
527
  store_block @block
330
- should_receive ["monitor", ["tx_3", [ @tx.to_hash, 3 ]]]
528
+
529
+ should_receive_tx(r, @tx, 3)
530
+ end
531
+
532
+ it "should receive missed txs when last txhash is given" do
533
+ @client = TCPSocket.new(*@config[:command])
534
+ blocks = [@block]; store_block @block
535
+ 3.times do
536
+ blocks << create_block(blocks.last.hash, false)
537
+ store_block blocks.last
538
+ end
539
+ sleep 0.1
540
+
541
+ r = send "monitor", channel: "tx", conf: 1, last: blocks[0].tx[0].hash
542
+
543
+ should_receive_tx(r, blocks[1].tx[0], 3)
544
+ should_receive_tx(r, blocks[2].tx[0], 2)
545
+ should_receive_tx(r, blocks[3].tx[0], 1)
546
+
547
+ should_receive r, id: 0
548
+ end
549
+
550
+
551
+ it "should filter txs for given addresses" do
552
+ @key2 = Bitcoin::Key.generate
553
+ block = create_block(@block.hash, false, [->(t) {
554
+ create_tx(t, @block.tx[0], 0, [[50e8, @key2]]) }], @key)
555
+ @addr = @block.tx[0].out[0].parsed_script.get_address
556
+ r = send "monitor", channel: "tx", conf: 1, addresses: [ @key2.addr ]
557
+ should_receive r, id: 0
558
+ store_block @block
559
+ store_block block
560
+ should_receive_tx(r, block.tx[1], 1)
331
561
  end
332
562
 
333
563
  end
@@ -336,37 +566,94 @@ describe 'Node Command API' do
336
566
 
337
567
  before do
338
568
  @tx = @block.tx[0]; @out = @tx.out[0]
339
- @addr = Bitcoin::Script.new(@out.pk_script).get_address
340
569
  end
341
570
 
342
571
  it "should monitor unconfirmed outputs" do
343
- send ["monitor", ["output"]]
572
+ r1 = send "monitor", channel: "output"
573
+ should_receive r1, id: 0
574
+ tx = @block.tx[0]
575
+ r2 = send "store_tx", hex: tx.to_payload.hth
576
+ should_receive r2, { "queued" => tx.hash }
577
+ should_receive_output(r1, tx, 0, 0)
578
+ end
579
+
580
+ it "should unmonitor outputs" do
581
+ should_receive send("monitor", channel: "output"), id: 0
582
+ should_receive send("unmonitor", id: 0), id: 0
583
+
344
584
  tx = @block.tx[0]
345
- send ["store_tx", [ tx.to_payload.hth ]]
346
- should_receive ["store_tx", { "queued" => [ "tx", tx.hash ]}]
347
- addr = Bitcoin::Script.new(tx.out[0].pk_script).get_address
348
- should_receive ["monitor", ["output", [ tx.hash, addr, tx.out[0].value, 0]]]
585
+ r2 = send "store_tx", hex: tx.to_payload.hth
586
+ should_receive r2, { "queued" => tx.hash }
587
+
588
+ test_command("tslb") {|r| (0..TSLB_TIMEOUT).include?(r['tslb']).should == true }
349
589
  end
350
590
 
351
591
  it "should monitor confirmed output" do
352
- send ["monitor", ["output_1"]]
592
+ r = send "monitor", channel: "output", conf: 1
593
+ should_receive r, id: 0
353
594
  store_block @block
354
- should_receive ["monitor", ["output_1", [ @tx.hash, @addr, @out.value, 1 ]]]
595
+ should_receive_output(r, @tx, 0, 1)
355
596
  end
356
597
 
357
598
  it "should monitor output for given confirmation level" do
358
- send ["monitor", ["output_3"]]
599
+ r = send "monitor", channel: "output", conf: 3
600
+ should_receive r, id: 0
359
601
  store_block @block
360
602
  @block = create_block @block.hash, false
361
603
  store_block @block
362
604
  tx = @genesis.tx[0]; out = tx.out[0]
363
- addr = Bitcoin::Script.new(out.pk_script).get_address
364
- should_receive ["monitor", ["output_3", [ tx.hash, addr, out.value, 3 ]]]
605
+ should_receive_output(r, tx, 0, 3)
365
606
 
366
607
  @block = create_block @block.hash, false
367
608
  store_block @block
368
- should_receive ["monitor", ["output_3", [ @tx.hash, @addr, @out.value, 3 ]]]
369
-
609
+ should_receive_output(r, @tx, 0, 3)
610
+ end
611
+
612
+ it "should receive missed outputs when last txhash:idx is given" do
613
+ @key = Bitcoin::Key.generate
614
+ @client = TCPSocket.new(*@config[:command])
615
+ blocks = [@block]; store_block @block
616
+ 3.times do
617
+ blocks << create_block(blocks.last.hash, false, [], @key)
618
+ store_block blocks.last
619
+ end
620
+ sleep 0.1
621
+
622
+ r = send "monitor", channel: "output", conf: 1, last: "#{blocks[0].tx[0].hash}:0"
623
+
624
+ should_receive_output(r, blocks[1].tx[0], 0, 3)
625
+ should_receive_output(r, blocks[2].tx[0], 0, 2)
626
+ should_receive_output(r, blocks[3].tx[0], 0, 1)
627
+
628
+ should_receive r, id: 0
629
+ end
630
+
631
+ it "should filter outputs for given addresses" do
632
+ @key2 = Bitcoin::Key.generate
633
+ block = create_block(@block.hash, false, [->(t) {
634
+ create_tx(t, @block.tx[0], 0, [[50e8, @key2]]) }], @key)
635
+
636
+ r = send "monitor", channel: "output", conf: 1, addresses: [ @key2.addr ]
637
+ should_receive r, id: 0
638
+ store_block @block
639
+ store_block block
640
+ should_receive_output(r, block.tx[1], 0, 1)
641
+ end
642
+
643
+ it "should add filter address to output monitor params" do
644
+ @key2 = Bitcoin::Key.generate
645
+ block = create_block(@block.hash, false, [->(t) {
646
+ create_tx(t, @block.tx[0], 0, [[50e8, @key2]]) }], @key)
647
+
648
+ r1 = send "monitor", channel: "output", conf: 1, addresses: [ ]
649
+ should_receive r1, id: 0
650
+
651
+ r2 = send "filter_monitor_output", id: 0, address: @key2.addr
652
+ should_receive r2, id: 0
653
+
654
+ store_block @block
655
+ store_block block
656
+ should_receive_output(r1, block.tx[1], 0, 1)
370
657
  end
371
658
 
372
659
  end