bitcoin-ruby 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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
data/lib/bitcoin/key.rb CHANGED
@@ -7,8 +7,8 @@ module Bitcoin
7
7
 
8
8
  # Generate a new keypair.
9
9
  # Bitcoin::Key.generate
10
- def self.generate
11
- k = new; k.generate; k
10
+ def self.generate(opts={compressed: true})
11
+ k = new(nil, nil, opts); k.generate; k
12
12
  end
13
13
 
14
14
  # Import private key from base58 fromat as described in
@@ -24,7 +24,7 @@ module Bitcoin
24
24
  key = new(key, nil, compressed)
25
25
  end
26
26
 
27
- def == other
27
+ def ==(other)
28
28
  self.priv == other.priv
29
29
  end
30
30
 
@@ -32,11 +32,12 @@ module Bitcoin
32
32
  # Bitcoin::Key.new
33
33
  # Bitcoin::Key.new(privkey)
34
34
  # Bitcoin::Key.new(nil, pubkey)
35
- def initialize privkey = nil, pubkey = nil, compressed = false
35
+ def initialize(privkey = nil, pubkey = nil, opts={compressed: true})
36
+ compressed = opts.is_a?(Hash) ? opts.fetch(:compressed, true) : opts
36
37
  @key = Bitcoin.bitcoin_elliptic_curve
37
38
  @pubkey_compressed = pubkey ? self.class.is_compressed_pubkey?(pubkey) : compressed
38
39
  set_priv(privkey) if privkey
39
- set_pub(pubkey) if pubkey
40
+ set_pub(pubkey, @pubkey_compressed) if pubkey
40
41
  end
41
42
 
42
43
  # Generate new priv/pub key.
@@ -59,21 +60,17 @@ module Bitcoin
59
60
  # In case the key was initialized with only
60
61
  # a private key, the public key is regenerated.
61
62
  def pub
63
+ regenerate_pubkey unless @key.public_key
64
+ return nil unless @key.public_key
62
65
  @pubkey_compressed ? pub_compressed : pub_uncompressed
63
66
  end
64
67
 
65
68
  def pub_compressed
66
- regenerate_pubkey unless @key.public_key
67
- return nil unless @key.public_key
68
69
  @key.public_key.group.point_conversion_form = :compressed
69
- hex = @key.public_key.to_hex.rjust(66, '0')
70
- @key.public_key.group.point_conversion_form = :uncompressed
71
- hex
70
+ @key.public_key.to_hex.rjust(66, '0')
72
71
  end
73
72
 
74
73
  def pub_uncompressed
75
- regenerate_pubkey unless @key.public_key
76
- return nil unless @key.public_key
77
74
  @key.public_key.group.point_conversion_form = :uncompressed
78
75
  @key.public_key.to_hex.rjust(130, '0')
79
76
  end
@@ -99,7 +96,7 @@ module Bitcoin
99
96
 
100
97
  # Sign +data+ with the key.
101
98
  # key1 = Bitcoin::Key.generate
102
- # sig = key.sign("some data")
99
+ # sig = key1.sign("some data")
103
100
  def sign(data)
104
101
  @key.dsa_sign_asn1(data)
105
102
  end
@@ -108,6 +105,7 @@ module Bitcoin
108
105
  # key2 = Bitcoin::Key.new(nil, key1.pub)
109
106
  # key2.verify("some data", sig)
110
107
  def verify(data, sig)
108
+ regenerate_pubkey unless @key.public_key
111
109
  @key.dsa_verify_asn1(data, sig)
112
110
  end
113
111
 
@@ -162,12 +160,87 @@ module Bitcoin
162
160
  Bitcoin.int_to_base58( hex.to_i(16) )
163
161
  end
164
162
 
163
+
164
+ # Export private key to bip38 (non-ec-multiply) format as described in
165
+ # https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki
166
+ # See also Key.from_bip38
167
+ def to_bip38(passphrase)
168
+ flagbyte = compressed ? "\xe0" : "\xc0"
169
+ addresshash = Digest::SHA256.digest( Digest::SHA256.digest( self.addr ) )[0...4]
170
+
171
+ require 'scrypt' unless defined?(::SCrypt::Engine)
172
+ buf = SCrypt::Engine.__sc_crypt(passphrase, addresshash, 16384, 8, 8, 64)
173
+ derivedhalf1, derivedhalf2 = buf[0...32], buf[32..-1]
174
+
175
+ aes = proc{|k,a,b|
176
+ cipher = OpenSSL::Cipher::AES.new(256, :ECB); cipher.encrypt; cipher.padding = 0; cipher.key = k
177
+ cipher.update [ (a.to_i(16) ^ b.unpack("H*")[0].to_i(16)).to_s(16).rjust(32, '0') ].pack("H*")
178
+ }
179
+
180
+ encryptedhalf1 = aes.call(derivedhalf2, self.priv[0...32], derivedhalf1[0...16])
181
+ encryptedhalf2 = aes.call(derivedhalf2, self.priv[32..-1], derivedhalf1[16..-1])
182
+
183
+ encrypted_privkey = "\x01\x42" + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2
184
+ encrypted_privkey += Digest::SHA256.digest( Digest::SHA256.digest( encrypted_privkey ) )[0...4]
185
+
186
+ encrypted_privkey = Bitcoin.encode_base58( encrypted_privkey.unpack("H*")[0] )
187
+ end
188
+
189
+ # Import private key from bip38 (non-ec-multiply) fromat as described in
190
+ # https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki
191
+ # See also #to_bip38
192
+ def self.from_bip38(encrypted_privkey, passphrase)
193
+ version, flagbyte, addresshash, encryptedhalf1, encryptedhalf2, checksum =
194
+ [ Bitcoin.decode_base58(encrypted_privkey) ].pack("H*").unpack("a2aa4a16a16a4")
195
+ compressed = (flagbyte == "\xe0") ? true : false
196
+
197
+ raise "Invalid version" unless version == "\x01\x42"
198
+ raise "Invalid checksum" unless Digest::SHA256.digest(Digest::SHA256.digest(version + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2))[0...4] == checksum
199
+
200
+ require 'scrypt' unless defined?(::SCrypt::Engine)
201
+ buf = SCrypt::Engine.__sc_crypt(passphrase, addresshash, 16384, 8, 8, 64)
202
+ derivedhalf1, derivedhalf2 = buf[0...32], buf[32..-1]
203
+
204
+ aes = proc{|k,a|
205
+ cipher = OpenSSL::Cipher::AES.new(256, :ECB); cipher.decrypt; cipher.padding = 0; cipher.key = k
206
+ cipher.update(a)
207
+ }
208
+
209
+ decryptedhalf2 = aes.call(derivedhalf2, encryptedhalf2)
210
+ decryptedhalf1 = aes.call(derivedhalf2, encryptedhalf1)
211
+
212
+ priv = decryptedhalf1 + decryptedhalf2
213
+ priv = (priv.unpack("H*")[0].to_i(16) ^ derivedhalf1.unpack("H*")[0].to_i(16)).to_s(16).rjust(64, '0')
214
+ key = Bitcoin::Key.new(priv, nil, compressed)
215
+
216
+ if Digest::SHA256.digest( Digest::SHA256.digest( key.addr ) )[0...4] != addresshash
217
+ raise "Invalid addresshash! Password is likely incorrect."
218
+ end
219
+
220
+ key
221
+ end
222
+
223
+ # Import private key from warp fromat as described in
224
+ # https://github.com/keybase/warpwallet
225
+ # https://keybase.io/warp/
226
+ def self.from_warp(passphrase, salt="", compressed=false)
227
+ require 'scrypt' unless defined?(::SCrypt::Engine)
228
+ s1 = SCrypt::Engine.scrypt(passphrase+"\x01", salt+"\x01", 2**18, 8, 1, 32)
229
+ s2 = OpenSSL::PKCS5.pbkdf2_hmac(passphrase+"\x02", salt+"\x02", 2**16, 32, OpenSSL::Digest::SHA256.new)
230
+ s3 = s1.bytes.zip(s2.bytes).map{|a,b| a ^ b }.pack("C*")
231
+
232
+ key = Bitcoin::Key.new(s3.unpack("H*")[0], nil, compressed)
233
+ # [key.addr, key.to_base58, [s1,s2,s3].map{|i| i.unpack("H*")[0] }, compressed]
234
+ key
235
+ end
236
+
237
+
165
238
  protected
166
239
 
167
240
  # Regenerate public key from the private key.
168
241
  def regenerate_pubkey
169
242
  return nil unless @key.private_key
170
- set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1])
243
+ set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1], @pubkey_compressed)
171
244
  end
172
245
 
173
246
  # Set +priv+ as the new private key (converting from hex).
@@ -176,8 +249,8 @@ module Bitcoin
176
249
  end
177
250
 
178
251
  # Set +pub+ as the new public key (converting from hex).
179
- def set_pub(pub)
180
- @pubkey_compressed ||= self.class.is_compressed_pubkey?(pub)
252
+ def set_pub(pub, compressed = nil)
253
+ @pubkey_compressed = compressed == nil ? self.class.is_compressed_pubkey?(pub) : compressed
181
254
  @key.public_key = OpenSSL::PKey::EC::Point.from_hex(@key.group, pub)
182
255
  end
183
256
 
@@ -3,12 +3,6 @@ require 'openssl'
3
3
  module Litecoin
4
4
  module Scrypt
5
5
 
6
- def scrypt_1024_1_1_256(input)
7
- input = [input].pack("H*") if input.bytesize == 160
8
- #scrypt_1024_1_1_256_sp(input, scratchpad = Array.new(131072 + 63){ 0 })
9
- scrypt_1024_1_1_256_sp(input)
10
- end
11
-
12
6
  def scrypt_1024_1_1_256_sp(input, scratchpad=[])
13
7
  b = pbkdf2_sha256(input, input, 1, 128)
14
8
  x = b.unpack("V*")
@@ -27,7 +21,7 @@ module Litecoin
27
21
  xor_salsa8(x, x, 16, 0)
28
22
  }
29
23
 
30
- pbkdf2_sha256(input, x.pack("V*"), 1, 32).reverse.unpack("H*")[0]
24
+ pbkdf2_sha256(input, x.pack("V*"), 1, 32)
31
25
  end
32
26
 
33
27
  def pbkdf2_sha256(pass, salt, c=1, dk_len=128)
@@ -70,12 +64,20 @@ end
70
64
 
71
65
  if $0 == __FILE__
72
66
  secret_hex = "020000004c1271c211717198227392b029a64a7971931d351b387bb80db027f270411e398a07046f7d4a08dd815412a8712f874a7ebf0507e3878bd24e20a3b73fd750a667d2f451eac7471b00de6659"
73
- p Litecoin::Scrypt.scrypt_1024_1_1_256(secret_hex) == "00000000002bef4107f882f6115e0b01f348d21195dacd3582aa2dabd7985806"
67
+ secret_bytes = [secret_hex].pack("H*")
68
+
69
+ begin
70
+ require "scrypt"
71
+ hash = SCrypt::Engine.__sc_crypt(secret_bytes, secret_bytes, 1024, 1, 1, 32)
72
+ p hash.reverse.unpack("H*")[0] == "00000000002bef4107f882f6115e0b01f348d21195dacd3582aa2dabd7985806"
73
+ rescue LoadError
74
+ puts "scrypt gem not found, using native scrypt"
75
+ p Litecoin::Scrypt.scrypt_1024_1_1_256_sp(secret_bytes).reverse.unpack("H*")[0] == "00000000002bef4107f882f6115e0b01f348d21195dacd3582aa2dabd7985806"
76
+ end
74
77
 
75
78
  require 'benchmark'
76
- secret_bytes = [secret_hex].pack("H*")
77
79
  Benchmark.bmbm{|x|
78
- #x.report("v1"){ p Litecoin::Scrypt.scrypt_1024_1_1_256_sp_old(secret_bytes) == "00000000002bef4107f882f6115e0b01f348d21195dacd3582aa2dabd7985806" }
79
- x.report("v2"){ p Litecoin::Scrypt.scrypt_1024_1_1_256_sp(secret_bytes) == "00000000002bef4107f882f6115e0b01f348d21195dacd3582aa2dabd7985806" }
80
+ x.report("v1"){ SCrypt::Engine.__sc_crypt(secret_bytes, secret_bytes, 1024, 1, 1, 32).reverse.unpack("H*") rescue nil }
81
+ x.report("v2"){ Litecoin::Scrypt.scrypt_1024_1_1_256_sp(secret_bytes).reverse.unpack("H*")[0] }
80
82
  }
81
83
  end
@@ -32,6 +32,7 @@ module Bitcoin::Namecoin
32
32
  def get_hash160
33
33
  return @chunks[2..-3][0].unpack("H*")[0] if is_hash160?
34
34
  return @chunks[-3].unpack("H*")[0] if is_namecoin?
35
+ return @chunks[-2].unpack("H*")[0] if is_p2sh?
35
36
  return Bitcoin.hash160(get_pubkey) if is_pubkey?
36
37
  end
37
38
 
@@ -262,12 +263,12 @@ module Bitcoin::Namecoin
262
263
  end
263
264
 
264
265
  def expires_in
265
- Namecoin::EXPIRATION_DEPTH - (@store.get_depth - get_block.depth) rescue nil
266
+ Bitcoin::Namecoin::EXPIRATION_DEPTH - (@store.get_depth - get_block.depth) rescue nil
266
267
  end
267
268
 
268
- def to_json(opts = {})
269
- JSON.pretty_generate({ name: @name, value: @value, txid: get_tx.hash,
270
- address: get_address, expires_in: expires_in }, opts)
269
+ def as_json(opts = {})
270
+ { name: @name, value: @value, txid: get_tx.hash,
271
+ address: get_address, expires_in: expires_in }
271
272
  end
272
273
 
273
274
  end
@@ -19,6 +19,8 @@ class Bitcoin::Network::CommandClient < EM::Connection
19
19
  instance_eval &block if block
20
20
  @buffer = BufferedTokenizer.new("\x00")
21
21
  @connection_attempts = 0
22
+ @requests = {}
23
+ @i = 0
22
24
  end
23
25
 
24
26
  def log;
@@ -32,7 +34,7 @@ class Bitcoin::Network::CommandClient < EM::Connection
32
34
  # call +connected+ callback
33
35
  def post_init
34
36
  log.debug { "Connected" }
35
- callback :connected
37
+ request(:connected) { callback(:connected) }
36
38
  end
37
39
 
38
40
  # call +disconnected+ callback and try to reconnect
@@ -47,21 +49,29 @@ class Bitcoin::Network::CommandClient < EM::Connection
47
49
  end
48
50
 
49
51
  # request command +cmd+ with +args+ from the server
50
- def request cmd, *args
52
+ def request cmd, params = nil, &block
53
+ id = @i += 1
54
+ @requests[id] = block if block
51
55
  log.debug { "request: #{cmd} #{args.inspect}" }
52
- register_monitor_callbacks if cmd.to_sym == :monitor
53
- send_data([cmd, args].to_json + "\x00")
56
+ register_monitor_callbacks(params) if cmd.to_sym == :monitor
57
+ request = { id: id, method: cmd }
58
+ request[:params] = params if params
59
+ send_data(request.to_json + "\x00")
54
60
  end
55
61
 
56
62
  # receive response from server
57
63
  def receive_data data
58
64
  @connection_attempts = 0
59
65
  @buffer.extract(data).each do |packet|
60
- cmd, *data = *JSON.load(packet)
61
- log.debug { d = data.inspect
62
- "response: #{cmd} #{d[0...50]}#{d.size > 50 ? '...' : ''}" }
63
- callback(:response, cmd, *data)
64
- callback(cmd.to_sym, *data)
66
+ response = JSON.parse(packet)
67
+ log.debug { d = response['result'].inspect
68
+ "response: #{response['method']} #{d[0...50]}#{d.size > 50 ? '...' : ''}" }
69
+ if cb = @requests[response['id']]
70
+ cb.call(response['result'])
71
+ else
72
+ callback(:response, response['method'], response['result'])
73
+ callback(response['method'].to_sym, response['result'])
74
+ end
65
75
  end
66
76
  end
67
77
 
@@ -84,10 +94,10 @@ class Bitcoin::Network::CommandClient < EM::Connection
84
94
  end
85
95
 
86
96
  # register callbacks for monitor
87
- def register_monitor_callbacks
88
- on_monitor do |type, data|
89
- type, *params = type.split("_")
90
- callback(type, *((data || []) + (params || [])))
97
+ def register_monitor_callbacks params
98
+ on_monitor do |data|
99
+ next if data.is_a?(Hash) && data.keys == ["id"]
100
+ callback(params["channel"], data)
91
101
  end
92
102
  end
93
103
 
@@ -13,6 +13,7 @@ class Bitcoin::Network::CommandHandler < EM::Connection
13
13
  @node.command_connections << self
14
14
  @buf = BufferedTokenizer.new("\x00")
15
15
  @lock = Monitor.new
16
+ @monitors = []
16
17
  end
17
18
 
18
19
  # wrap logger and append prefix
@@ -21,10 +22,12 @@ class Bitcoin::Network::CommandHandler < EM::Connection
21
22
  end
22
23
 
23
24
  # respond to a command; send serialized response to the client
24
- def respond(cmd, data)
25
+ def respond(request, data)
25
26
  return unless data
27
+ request[:result] = data
28
+ request.delete(:params)
26
29
  @lock.synchronize do
27
- send_data([cmd, data].to_json + "\x00")
30
+ send_data(request.to_json + "\x00")
28
31
  end
29
32
  end
30
33
 
@@ -32,79 +35,159 @@ class Bitcoin::Network::CommandHandler < EM::Connection
32
35
  def receive_data data
33
36
  @buf.extract(data).each do |packet|
34
37
  begin
35
- cmd, args = JSON::parse(packet)
36
- log.debug { [cmd, args] }
37
- if cmd == "relay_tx"
38
- handle_relay_tx(*args)
39
- return
40
- end
41
- if respond_to?("handle_#{cmd}")
42
- respond(cmd, send("handle_#{cmd}", *args))
38
+ request = symbolize_keys(JSON::parse(packet))
39
+ log.debug { request }
40
+ case request[:method]
41
+ when "relay_tx"
42
+ return handle_relay_tx(request, request[:params])
43
+ when "monitor"
44
+ respond(request, handle_monitor(request, request[:params]))
43
45
  else
44
- respond(cmd, { error: "unknown command: #{cmd}. send 'help' for help." })
46
+ if respond_to?("handle_#{request[:method]}")
47
+ if request[:params] && request[:params].any?
48
+ respond(request, send("handle_#{request[:method]}", request[:params]))
49
+ else
50
+ respond(request, send("handle_#{request[:method]}"))
51
+ end
52
+ else
53
+ respond(request, { error: "unknown command: #{request[:method]}. send 'help' for help." })
54
+ end
45
55
  end
46
- rescue ArgumentError
47
- respond(cmd, { error: $!.message })
56
+ rescue
57
+ respond(request, { error: $!.message })
58
+ p $!; puts *$@
48
59
  end
49
60
  end
50
- rescue Exception
61
+ rescue
51
62
  p $!; puts *$@
52
63
  end
53
64
 
54
- # handle +monitor+ command; subscribe client to specified channels
65
+ def handle_connected
66
+ "connected"
67
+ end
68
+
69
+ # Handle +monitor+ command; subscribe client to specified channels
55
70
  # (+block+, +tx+, +output+, +connection+).
56
- # Some commands can have parameters, e.g. the number of confirmations
71
+ # Parameters can be appended to the channel name, e.g. the number of confirmations
57
72
  # +tx+ or +output+ should have. Parameters are appended to the command
58
73
  # name after an underscore (_), e.g. subscribe to channel "tx_6" to
59
74
  # receive only transactions with 6 confirmations.
60
- #
75
+ # You can send the last block/tx/output you know about, and it will also send you
76
+ # all the objects you're missing.
77
+ #
61
78
  # Receive new blocks:
62
79
  # bitcoin_node monitor block
80
+ # Receive blocks since block 123, and new ones as they come in:
81
+ # bitcoin_node monitor block_123
63
82
  # Receive new (unconfirmed) transactions:
64
83
  # bitcoin_node monitor tx
65
84
  # Receive transactions with 6 confirmations:
66
- # bitcoin_node monitor tx_6
67
- # Receive [txhash, address, value] for each output:
85
+ # bitcoin_node monitor tx_6
86
+ # Receive transactions since <txhash>, and new ones as they come in:
87
+ # bitcoin_node monitor tx_1_<txhash>
88
+ # Receive [txhash, idx, address, value] for each output:
68
89
  # bitcoin_node monitor output
90
+ # Receive outputs since <txhash>:<idx>, and new ones as they come in:
91
+ # bitcoin_node monitor output_1_<txhash>:<idx>
69
92
  # Receive peer connections/disconnections:
70
93
  # bitcoin_node monitor connection"
71
94
  # Combine multiple channels:
72
95
  # bitcoin_node monitor "block tx tx_1 tx_6 connection"
73
- #
96
+ #
74
97
  # NOTE: When a new block is found, it might include transactions that we
75
98
  # didn't previously receive as unconfirmed. To make sure you receive all
76
99
  # transactions, also subscribe to the tx_1 channel.
77
- def handle_monitor *channels
78
- channels.map(&:to_sym).each do |channel|
79
- @node.subscribe(channel) {|*data| respond("monitor", [channel, *data]) }
80
- name, *params = channel.to_s.split("_")
81
- send("handle_monitor_#{name}", *params)
82
- log.info { "Client subscribed to channel #{channel}" }
100
+ def handle_monitor request, params
101
+ log.info { "Client subscribed to channel #{params[:channel].inspect}" }
102
+ { id: send("handle_monitor_#{params[:channel]}", request, params) }
103
+ end
104
+
105
+ # Handle +unmonitor+ command; cancel given subscription.
106
+ # Parameter +id+ must be the subscription ID that was returned when calling +monitor+.
107
+ def handle_unmonitor request
108
+ id = request[:id]
109
+ raise "Monitor #{id} not found." unless @monitors[id]
110
+ @monitors[id][:channels].each {|name, id| @node.unsubscribe(name, id) }
111
+ { id: id }
112
+ end
113
+
114
+ # Handle +monitor block+ command;
115
+ def handle_monitor_block request, params
116
+ monitor_id = @monitors.size
117
+ id = @node.subscribe(:block) {|blk, depth| respond_monitor_block(request, blk, depth) }
118
+ add_monitor(params, [[:block, id]])
119
+ respond_missed_blocks(request, monitor_id) if params[:last]
120
+ monitor_id
121
+ end
122
+
123
+ def respond_missed_blocks request, monitor_id
124
+ params = @monitors[monitor_id][:params]
125
+ blk = @node.store.get_block(params[:last])
126
+ respond_monitor_block(request, blk)
127
+ while blk = blk.get_next_block
128
+ respond_monitor_block(request, blk)
83
129
  end
84
- nil
85
130
  end
86
131
 
87
- # Handle +monitor block+ command; send the current chain head
88
- # after client is subscribed to :block channel
89
- def handle_monitor_block
90
- head = Bitcoin::P::Block.new(@node.store.get_head.to_payload) rescue nil
91
- respond("monitor", ["block", [head, @node.store.get_depth]]) if head
132
+ def respond_monitor_block request, block, depth = nil
133
+ depth ||= block.depth
134
+ respond(request, { hash: block.hash, hex: block.to_payload.hth, depth: depth })
135
+ end
136
+
137
+ # TODO: params (min reorg depth)
138
+ def handle_monitor_reorg request, params
139
+ id = @node.subscribe(:reorg) do |new_main, new_side|
140
+ respond(request, { new_main: new_main, new_side: new_side })
141
+ end
142
+
143
+ add_monitor(params, [[:reorg, id]])
92
144
  end
93
145
 
94
146
  # Handle +monitor tx+ command.
95
147
  # When +conf+ is given, don't subscribe to the :tx channel for unconfirmed
96
148
  # transactions. Instead, subscribe to the :block channel, and whenever a new
97
149
  # block comes in, send all transactions that now have +conf+ confirmations.
98
- def handle_monitor_tx conf = nil
99
- return unless conf
100
- if conf.to_i == 0 # 'tx_0' is just an alias for 'tx'
101
- return @node.subscribe(:tx) {|*a| @node.notifiers[:tx_0].push(*a) }
150
+ def handle_monitor_tx request, params
151
+ monitor_id = @monitors.size
152
+ tx_id = @node.subscribe(:tx) {|tx, conf| respond_monitor_tx(request, monitor_id, tx, conf) }
153
+
154
+ conf = params[:conf].to_i
155
+ block_id = @node.subscribe(:block) do |block, depth|
156
+ next unless block = @node.store.get_block_by_depth(depth - conf + 1)
157
+ block.tx.each {|tx| respond_monitor_tx(request, monitor_id, tx, conf) }
158
+ end
159
+
160
+ add_monitor(params, [[:tx, tx_id], [:block, block_id]])
161
+
162
+ respond_missed_txs(request, params, monitor_id) if params[:last]
163
+
164
+ monitor_id
165
+ end
166
+
167
+ def respond_missed_txs request, params, monitor_id
168
+ return unless last_tx = @node.store.get_tx(params[:last])
169
+ notify = false; depth = @node.store.get_depth
170
+ (last_tx.get_block.depth..depth).each do |i|
171
+ blk = @node.store.get_block_by_depth(i)
172
+ blk.tx.each do |tx|
173
+ respond_monitor_tx(request, monitor_id, tx, (depth - blk.depth + 1)) if notify
174
+ notify = true if tx.hash == last_tx.hash
175
+ end
102
176
  end
103
- @node.subscribe(:block) do |block, depth|
104
- block = @node.store.get_block_by_depth(depth - conf.to_i + 1)
105
- next unless block
106
- block.tx.each {|tx| @node.notifiers["tx_#{conf}".to_sym].push([tx, conf.to_i]) }
177
+ end
178
+
179
+ def respond_monitor_tx request, monitor_id, tx, conf = nil
180
+ conf ||= tx.confirmations
181
+
182
+ params = @monitors[monitor_id][:params]
183
+
184
+ # filter by addresses
185
+ if params[:addresses]
186
+ addrs = tx.out.map(&:parsed_script).map(&:get_address)
187
+ return unless (params[:addresses] & addrs).any?
107
188
  end
189
+
190
+ respond(request, { hash: tx.hash, nhash: tx.nhash, hex: tx.to_payload.hth, conf: conf })
108
191
  end
109
192
 
110
193
  # Handle +monitor output+ command.
@@ -112,27 +195,82 @@ class Bitcoin::Network::CommandHandler < EM::Connection
112
195
  # This allows easy scanning for new payments without parsing the
113
196
  # tx format and running scripts.
114
197
  # See #handle_monitor_tx for confirmation behavior.
115
- def handle_monitor_output conf = 0
116
- return unless (conf = conf.to_i) > 0
117
- @node.subscribe(:block) do |block, depth|
118
- block = @node.store.get_block_by_depth(depth - conf + 1)
119
- next unless block
120
- block.tx.each do |tx|
121
- tx.out.each do |out|
122
- addr = Bitcoin::Script.new(out.pk_script).get_address
123
- res = [tx.hash, addr, out.value, conf]
124
- @node.push_notification("output_#{conf}".to_sym, res)
198
+ def handle_monitor_output request, params
199
+ monitor_id = @monitors.size
200
+
201
+ tx_id = @node.subscribe(:tx) do |tx, conf|
202
+ tx.out.each.with_index do |out, idx|
203
+ respond_monitor_output(request, monitor_id, tx, out, idx, conf)
204
+ end
205
+ end
206
+
207
+ if (conf = params[:conf].to_i) > 0
208
+ block_id = @node.subscribe(:block) do |block, depth|
209
+ block = @node.store.get_block_by_depth(depth - conf + 1)
210
+ next unless block
211
+ block.tx.each do |tx|
212
+ tx.out.each.with_index do |out, idx|
213
+ respond_monitor_output(request, monitor_id, tx, out, idx, conf)
214
+ end
125
215
  end
126
216
  end
127
217
  end
218
+
219
+ add_monitor(params, [[:tx, tx_id], [:block, block_id]])
220
+
221
+ respond_missed_outputs(request, monitor_id) if params[:last]
222
+
223
+ monitor_id
224
+ end
225
+
226
+ def respond_missed_outputs request, monitor_id
227
+ params = @monitors[monitor_id][:params]
228
+ last_hash, last_idx = *params[:last].split(":"); last_idx = last_idx.to_i
229
+ return unless last_tx = @node.store.get_tx(last_hash)
230
+ return unless last_out = last_tx.out[last_idx]
231
+ notify = false
232
+ depth = @node.store.get_depth
233
+ (last_tx.get_block.depth..depth).each do |i|
234
+ blk = @node.store.get_block_by_depth(i)
235
+ blk.tx.each do |tx|
236
+ tx.out.each.with_index do |out, idx|
237
+ if notify
238
+ respond_monitor_output(request, monitor_id, tx, out, idx, (depth - blk.depth + 1))
239
+ else
240
+ notify = true if tx.hash == last_hash && idx == last_idx
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ def respond_monitor_output request, monitor_id, tx, out, idx, conf
248
+ addr = out.parsed_script.get_address
249
+
250
+ params = @monitors[monitor_id][:params]
251
+
252
+ # filter by addresses
253
+ return if params[:addresses] && !params[:addresses].include?(addr)
254
+
255
+ respond(request, { nhash: tx.nhash, hash: tx.hash, idx: idx,
256
+ address: addr, value: out.value, conf: conf })
257
+ end
258
+
259
+ # Handle +filter monitor output+ command; add given +address+ to the list of
260
+ # filtered addresses in the params of the given monitor.
261
+ def handle_filter_monitor_output request
262
+ @monitors[request[:id]][:params][:addresses] << request[:address]
263
+ { id: request[:id] }
128
264
  end
129
265
 
130
266
  # Handle +monitor connection+ command; send current connections
131
267
  # after client is subscribed to :connection channel.
132
- def handle_monitor_connection
133
- @node.connections.select {|c| c.connected?}.each do |conn|
134
- respond("monitor", [:connection, [:connected, conn.info]])
268
+ def handle_monitor_connection request, params
269
+ id = @node.subscribe(:connection) {|data| respond(request, data) }
270
+ @node.connections.select {|c| c.connected?}.each do |conn|
271
+ respond(request, conn.info.merge(type: :connected))
135
272
  end
273
+ add_monitor(params, [[:connection, id]])
136
274
  end
137
275
 
138
276
  # Get various statistics.
@@ -141,21 +279,39 @@ class Bitcoin::Network::CommandHandler < EM::Connection
141
279
  blocks = @node.connections.map(&:version).compact.map(&:last_block) rescue nil
142
280
  established = @node.connections.select {|c| c.state == :connected }
143
281
  info = {
144
- :blocks => "#{@node.store.get_depth} (#{(blocks.inject{|a,b| a+=b; a } / blocks.size rescue '?' )})#{@node.store.in_sync? ? ' sync' : ''}",
145
- :addrs => "#{@node.addrs.select{|a| a.alive?}.size} (#{@node.addrs.size})",
146
- :connections => "#{established.size} established (#{established.select(&:outgoing?).size} out, #{established.select(&:incoming?).size} in), #{@node.connections.size - established.size} connecting",
147
- :queue => @node.queue.size,
148
- :inv_queue => @node.inv_queue.size,
149
- :inv_cache => @node.inv_cache.size,
150
- :network => @node.config[:network],
151
- :storage => @node.config[:storage],
152
- :version => Bitcoin.network[:protocol_version],
153
- :external_ip => @node.external_ip,
154
- :uptime => format_uptime(@node.uptime),
282
+ blocks: {
283
+ depth: @node.store.get_depth,
284
+ peers: (blocks.inject{|a,b| a+=b; a } / blocks.size rescue '?' ),
285
+ sync: @node.store.in_sync?,
286
+ },
287
+ addrs: {
288
+ alive: @node.addrs.select{|a| a.alive?}.size,
289
+ total: @node.addrs.size,
290
+ },
291
+ connections: {
292
+ established: established.size,
293
+ outgoing: established.select(&:outgoing?).size,
294
+ incoming: established.select(&:incoming?).size,
295
+ connecting: @node.connections.size - established.size,
296
+ },
297
+ queue: @node.queue.size,
298
+ inv_queue: @node.inv_queue.size,
299
+ inv_cache: @node.inv_cache.size,
300
+ network: @node.config[:network],
301
+ storage: @node.config[:storage],
302
+ version: Bitcoin.network[:protocol_version],
303
+ external_ip: @node.external_ip,
304
+ uptime: @node.uptime,
155
305
  }
156
- Bitcoin.namecoin? ? {:names => @node.store.db[:names].count}.merge(info) : info
306
+ Bitcoin.namecoin? ? {names: @node.store.db[:names].count}.merge(info) : info
307
+ end
308
+
309
+ def add_monitor params, channels
310
+ @monitors << { params: params, channels: channels }
311
+ @monitors.size - 1
157
312
  end
158
313
 
314
+
159
315
  # Get the currently active configuration.
160
316
  # bitcoin_node config
161
317
  def handle_config
@@ -166,49 +322,65 @@ class Bitcoin::Network::CommandHandler < EM::Connection
166
322
  # bitcoin_node connections
167
323
  def handle_connections
168
324
  @node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
169
- "#{c.host.rjust(15)}:#{c.port} [#{c.direction}, state: #{c.state}, " +
170
- "version: #{c.version.version rescue '?'}, " +
171
- "block: #{c.version.block rescue '?'}, " +
172
- "uptime: #{format_uptime(c.uptime) rescue 0}, " +
173
- "client: #{c.version.user_agent rescue '?'}]" }
325
+ {
326
+ type: c.direction, host: c.host, port: c.port, state: c.state,
327
+ uptime: c.uptime,
328
+ version: {
329
+ version: c.version.version,
330
+ services: c.version.services,
331
+ time: c.version.time,
332
+ nonce: c.version.nonce,
333
+ block: c.version.last_block,
334
+ client: (c.version.user_agent rescue '?'),
335
+ relay: c.version.relay,
336
+ }
337
+ }
338
+ }
174
339
  end
175
340
 
176
341
  # Connect to given peer(s).
177
- # bitcoin_node connect <ip>:<port>[,<ip>:<port>]
178
- def handle_connect *args
179
- args.each {|a| @node.connect_peer(*a.split(':')) }
180
- {:state => "Connecting..."}
342
+ # { method: "connect", params: {host: "localhost", port: 12345 }
343
+ def handle_connect params
344
+ @node.connect_peer(params[:host], params[:port])
345
+ { state: :connecting }
181
346
  end
182
347
 
183
348
  # Disconnect given peer(s).
184
- # bitcoin_node disconnect <ip>:<port>[,<ip>,<port>]
185
- def handle_disconnect *args
186
- args.each do |c|
187
- host, port = *c.split(":")
188
- conn = @node.connections.select{|c| c.host == host && c.port == port.to_i}.first
189
- conn.close_connection if conn
190
- end
191
- {:state => "Disconnected"}
349
+ # { method: "disconnect", params: {host: "localhost", port: 12345 }
350
+ def handle_disconnect params
351
+ conn = @node.connections.find {|c| c.host == params[:host] && c.port == params[:port].to_i}
352
+ conn.close_connection if conn
353
+ { state: :disconnected }
192
354
  end
193
355
 
194
356
  # Trigger the node to ask its peers for new blocks.
195
- # bitcoin_node getblocks
357
+ # { method: "getblocks", params: {} }
196
358
  def handle_getblocks
197
- @node.connections.sample.send_getblocks
198
- {:state => "Sending getblocks..."}
359
+ conn = @node.connections.sample
360
+ if conn
361
+ conn.send_getblocks
362
+ { state: :sent, peer: { host: conn.host, port: conn.port } }
363
+ else
364
+ raise "No peer connected"
365
+ end
199
366
  end
200
367
 
201
368
  # Trigger the node to ask its for new peer addresses.
202
- # bitcoin_node getaddr
369
+ # { method: "getaddr", params: {} }
203
370
  def handle_getaddr
204
- @node.connections.sample.send_getaddr
205
- {:state => "Sending getaddr..."}
371
+ conn = @node.connections.sample
372
+ if conn
373
+ conn.send_getaddr
374
+ { state: :sent, peer: { host: conn.host, port: conn.port } }
375
+ else
376
+ raise "No peer connected"
377
+ end
206
378
  end
207
379
 
208
380
  # Get known peer addresses (used by bin/bitcoin_dns_seed).
209
- # bitcoin_node addrs [count]
210
- def handle_addrs count = 32
211
- @node.addrs.weighted_sample(count.to_i) do |addr|
381
+ # { method: "getaddr", params: { count: 32 } }
382
+ def handle_addrs params = { count: 32 }
383
+ @node.addrs.weighted_sample(params[:count].to_i) do |addr|
212
384
  Time.now.tv_sec + 7200 - addr.time
213
385
  end.map do |addr|
214
386
  [addr.ip, addr.port, Time.now.tv_sec - addr.time] rescue nil
@@ -216,10 +388,16 @@ class Bitcoin::Network::CommandHandler < EM::Connection
216
388
  end
217
389
 
218
390
  # Trigger a rescan operation when used with a UtxoStore.
219
- # bitcoin_node rescan
391
+ # { method: "rescan" }
220
392
  def handle_rescan
221
- EM.defer { @node.store.rescan }
222
- {:state => "Rescanning ..."}
393
+ EM.defer {
394
+ begin
395
+ @node.store.rescan
396
+ rescue
397
+ puts "rescan: #{$!}"
398
+ end
399
+ }
400
+ { state: :rescanning }
223
401
  end
224
402
 
225
403
  # Get Time Since Last Block.
@@ -236,9 +414,11 @@ class Bitcoin::Network::CommandHandler < EM::Connection
236
414
  # After creating an unsigned transaction, one just needs to sign the sig_hashes
237
415
  # and send everything to #assemble_tx, to receive the complete transaction that
238
416
  # can be relayed to the network.
239
- def handle_create_tx keys, recipients, fee = 0
417
+ def handle_create_tx params = {}
418
+ params[:fee] ||= 0
419
+ #keys, recipients, fee = 0
240
420
  keystore = Bitcoin::Wallet::SimpleKeyStore.new(file: StringIO.new("[]"))
241
- keys.each do |k|
421
+ params[:keys].each do |k|
242
422
  begin
243
423
  key = Bitcoin::Key.from_base58(k)
244
424
  key = { addr: key.addr, key: key }
@@ -257,21 +437,25 @@ class Bitcoin::Network::CommandHandler < EM::Connection
257
437
  keystore.add_key(key)
258
438
  end
259
439
  wallet = Bitcoin::Wallet::Wallet.new(@node.store, keystore)
260
- tx = wallet.new_tx(recipients.map {|r| [:address, r[0], r[1]]}, fee)
440
+
441
+ tx = wallet.new_tx(params[:recipients].map {|r| [:address, r[0], r[1]]}, params[:fee])
261
442
  return { error: "Error creating tx." } unless tx
262
- [ tx.to_payload.hth, tx.in.map {|i| [i.sig_hash.hth, i.sig_address] rescue nil } ]
443
+ { hash: tx.hash, hex: tx.to_payload.hth,
444
+ missing_sigs: tx.in.map {|i| [i.sig_hash.hth, i.sig_address] rescue nil } }
263
445
  rescue
264
446
  { error: "Error creating tx: #{$!.message}" }
447
+ p $!; puts *$@
265
448
  end
266
449
 
267
- # Assemble an unsigned transaction from the +tx_hex+ and +sig_pubkeys+.
268
- # The +tx_hex+ is the regular transaction structure, with empty input scripts
450
+ # Assemble an unsigned transaction from the +tx+ and +sig_pubkeys+ params.
451
+ # The +tx+ is the regular transaction structure, with empty input scripts
269
452
  # (as returned by #create_tx when called without privkeys).
270
453
  # +sig_pubkeys+ is an array of [signature, pubkey] pairs used to build the
271
454
  # input scripts.
272
- def handle_assemble_tx tx_hex, sig_pubs
273
- tx = Bitcoin::P::Tx.new(tx_hex.htb)
274
- sig_pubs.each.with_index do |sig_pub, idx|
455
+ def handle_assemble_tx params = {}
456
+ # tx_hex, sig_pubs
457
+ tx = Bitcoin::P::Tx.new(params[:tx].htb)
458
+ params[:sig_pubs].each.with_index do |sig_pub, idx|
275
459
  sig, pub = *sig_pub.map(&:htb)
276
460
  script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, pub)
277
461
  tx.in[idx].script_sig_length = script_sig.bytesize
@@ -279,51 +463,56 @@ class Bitcoin::Network::CommandHandler < EM::Connection
279
463
  end
280
464
  tx = Bitcoin::P::Tx.new(tx.to_payload)
281
465
  tx.validator(@node.store).validate(raise_errors: true)
282
- tx.to_payload.hth
466
+ { hash: tx.hash, hex: tx.to_payload.hth }
283
467
  rescue
284
468
  { error: "Error assembling tx: #{$!.message}" }
469
+ p $!; puts *$@
285
470
  end
286
471
 
287
472
  # Relay given transaction (in hex).
288
473
  # bitcoin_node relay_tx <tx in hex>
289
- def handle_relay_tx hex, send = 3, wait = 3
474
+ def handle_relay_tx request, params = {}
475
+ params[:send] ||= 3
476
+ params[:wait] ||= 3
477
+ # request, hex, send = 3, wait = 3
290
478
  begin
291
- tx = Bitcoin::P::Tx.new(hex.htb)
479
+ tx = Bitcoin::P::Tx.new(params[:hex].htb)
292
480
  rescue
293
- return respond("relay_tx", { error: "Error decoding transaction." })
481
+ return respond(request, { error: "Error decoding transaction." })
294
482
  end
295
483
 
296
484
  validator = tx.validator(@node.store)
297
485
  unless validator.validate(rules: [:syntax])
298
- return respond("relay_tx", { error: "Transaction syntax invalid.",
486
+ return respond(request, { error: "Transaction syntax invalid.",
299
487
  details: validator.error })
300
488
  end
301
489
  unless validator.validate(rules: [:context])
302
- return respond("relay_tx", { error: "Transaction context invalid.",
490
+ return respond(request, { error: "Transaction context invalid.",
303
491
  details: validator.error })
304
492
  end
305
493
 
306
494
  #@node.store.store_tx(tx)
307
495
  @node.relay_tx[tx.hash] = tx
308
496
  @node.relay_propagation[tx.hash] = 0
309
- @node.connections.select(&:connected?).sample(send).each {|c| c.send_inv(:tx, tx.hash) }
497
+ @node.connections.select(&:connected?).sample(params[:send]).each {|c| c.send_inv(:tx, tx.hash) }
310
498
 
311
- EM.add_timer(wait) do
499
+ EM.add_timer(params[:wait]) do
312
500
  received = @node.relay_propagation[tx.hash]
313
- total = @node.connections.select(&:connected?).size - send
501
+ total = @node.connections.select(&:connected?).size - params[:send]
314
502
  percent = 100.0 / total * received
315
- respond("relay_tx", { success: true, hash: tx.hash, propagation: {
316
- received: received, sent: 1, percent: percent } })
503
+ respond(request, { success: true, hash: tx.hash, propagation: {
504
+ received: received, sent: 1, percent: percent } })
317
505
  end
318
506
  rescue
319
- respond("relay_tx", { error: $!.message, backtrace: $@ })
507
+ respond(request, { error: $!.message, backtrace: $@ })
508
+ p $!; puts *$@
320
509
  end
321
510
 
322
511
  # Stop the bitcoin node.
323
512
  # bitcoin_node stop
324
513
  def handle_stop
325
514
  Thread.start { sleep 0.1; @node.stop }
326
- {:state => "Stopping..."}
515
+ { state: :stopping }
327
516
  end
328
517
 
329
518
  # List all available commands.
@@ -333,28 +522,28 @@ class Bitcoin::Network::CommandHandler < EM::Connection
333
522
  end
334
523
 
335
524
  # Validate and store given block (in hex) as if it was received by a peer.
336
- # bitcoin_node store_block <block in hex>
337
- def handle_store_block hex
338
- block = Bitcoin::P::Block.new(hex.htb)
525
+ # { method: "store_block", params: { hex: <block data in hex> } }
526
+ def handle_store_block params
527
+ block = Bitcoin::P::Block.new(params[:hex].htb)
339
528
  @node.queue << [:block, block]
340
- { queued: [ :block, block.hash ] }
529
+ { queued: block.hash }
341
530
  end
342
531
 
343
532
  # Store given transaction (in hex) as if it was received by a peer.
344
- # bitcoin_node store_tx <tx in hex>
345
- def handle_store_tx hex
346
- tx = Bitcoin::P::Tx.new(hex.htb)
533
+ # { method: "store_tx", params: { hex: <tx data in hex> } }
534
+ def handle_store_tx params
535
+ tx = Bitcoin::P::Tx.new(params[:hex].htb)
347
536
  @node.queue << [:tx, tx]
348
- { queued: [ :tx, tx.hash ] }
537
+ { queued: tx.hash }
349
538
  end
350
539
 
351
- # format node uptime
352
- def format_uptime t
353
- mm, ss = t.divmod(60) #=> [4515, 21]
354
- hh, mm = mm.divmod(60) #=> [75, 15]
355
- dd, hh = hh.divmod(24) #=> [3, 3]
356
- "%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
357
- end
540
+ # # format node uptime
541
+ # def format_uptime t
542
+ # mm, ss = t.divmod(60) #=> [4515, 21]
543
+ # hh, mm = mm.divmod(60) #=> [75, 15]
544
+ # dd, hh = hh.divmod(24) #=> [3, 3]
545
+ # "%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
546
+ # end
358
547
 
359
548
  # disconnect notification clients when connection is closed
360
549
  def unbind
@@ -362,4 +551,20 @@ class Bitcoin::Network::CommandHandler < EM::Connection
362
551
  @node.command_connections.delete(self)
363
552
  end
364
553
 
554
+ private
555
+
556
+ def symbolize_keys(obj)
557
+ return obj unless [Hash, Array].include?(obj.class)
558
+ return obj.map {|v| symbolize_keys(v) } if obj.is_a?(Array)
559
+ obj.inject({}){|result, (key, value)|
560
+ new_key = key.is_a?(String) ? key.to_sym : key
561
+ new_value = case value
562
+ when Hash then symbolize_keys(value)
563
+ when Array then value.map {|v| symbolize_keys(v) }
564
+ else value; end
565
+ result[new_key] = new_value
566
+ result
567
+ }
568
+ end
569
+
365
570
  end