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