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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +2 -2
- data/COPYING +1 -1
- data/Gemfile +5 -11
- data/README.rdoc +11 -5
- data/Rakefile +5 -0
- data/bin/bitcoin_node +11 -29
- data/bin/bitcoin_node_cli +81 -0
- data/bin/bitcoin_wallet +9 -6
- data/doc/NODE.rdoc +79 -26
- data/examples/bbe_verify_tx.rb +1 -1
- data/examples/index_nhash.rb +24 -0
- data/examples/reindex_p2sh_addrs.rb +44 -0
- data/lib/bitcoin.rb +135 -20
- data/lib/bitcoin/builder.rb +233 -63
- data/lib/bitcoin/key.rb +89 -16
- data/lib/bitcoin/litecoin.rb +13 -11
- data/lib/bitcoin/namecoin.rb +5 -4
- data/lib/bitcoin/network/command_client.rb +23 -13
- data/lib/bitcoin/network/command_handler.rb +336 -131
- data/lib/bitcoin/network/connection_handler.rb +14 -13
- data/lib/bitcoin/network/node.rb +61 -20
- data/lib/bitcoin/protocol.rb +5 -1
- data/lib/bitcoin/protocol/block.rb +15 -3
- data/lib/bitcoin/protocol/parser.rb +3 -3
- data/lib/bitcoin/protocol/tx.rb +82 -20
- data/lib/bitcoin/protocol/txin.rb +7 -0
- data/lib/bitcoin/protocol/txout.rb +12 -9
- data/lib/bitcoin/script.rb +329 -75
- data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
- data/lib/bitcoin/storage/models.rb +6 -11
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
- data/lib/bitcoin/storage/storage.rb +161 -97
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
- data/lib/bitcoin/validation.rb +87 -56
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +38 -0
- data/spec/bitcoin/builder_spec.rb +177 -0
- data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
- data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
- data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
- data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
- data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
- data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
- data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
- data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
- data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
- data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
- data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
- data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
- data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
- data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
- data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
- data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
- data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
- data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
- data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
- data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
- data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
- data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
- data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
- data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
- data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
- data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
- data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
- data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
- data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
- data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
- data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
- data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
- data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
- data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
- data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
- data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
- data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
- data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
- data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
- data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
- data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
- data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
- data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
- data/spec/bitcoin/key_spec.rb +79 -8
- data/spec/bitcoin/namecoin_spec.rb +1 -1
- data/spec/bitcoin/node/command_api_spec.rb +373 -86
- data/spec/bitcoin/performance/storage_spec.rb +41 -0
- data/spec/bitcoin/protocol/addr_spec.rb +7 -5
- data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
- data/spec/bitcoin/protocol/block_spec.rb +6 -0
- data/spec/bitcoin/protocol/tx_spec.rb +184 -1
- data/spec/bitcoin/protocol/txin_spec.rb +27 -0
- data/spec/bitcoin/protocol/txout_spec.rb +27 -0
- data/spec/bitcoin/script/opcodes_spec.rb +74 -3
- data/spec/bitcoin/script/script_spec.rb +271 -0
- data/spec/bitcoin/spec_helper.rb +34 -6
- data/spec/bitcoin/storage/models_spec.rb +104 -0
- data/spec/bitcoin/storage/reorg_spec.rb +42 -11
- data/spec/bitcoin/storage/storage_spec.rb +58 -15
- data/spec/bitcoin/storage/validation_spec.rb +44 -14
- data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
- data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
- data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
- 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 ==
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
data/lib/bitcoin/litecoin.rb
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
x.report("v2"){
|
|
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
|
data/lib/bitcoin/namecoin.rb
CHANGED
|
@@ -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
|
|
269
|
-
|
|
270
|
-
address: get_address, expires_in: expires_in }
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
log.debug { d =
|
|
62
|
-
"response: #{
|
|
63
|
-
|
|
64
|
-
|
|
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 |
|
|
89
|
-
|
|
90
|
-
callback(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
36
|
-
log.debug {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
|
47
|
-
respond(
|
|
56
|
+
rescue
|
|
57
|
+
respond(request, { error: $!.message })
|
|
58
|
+
p $!; puts *$@
|
|
48
59
|
end
|
|
49
60
|
end
|
|
50
|
-
rescue
|
|
61
|
+
rescue
|
|
51
62
|
p $!; puts *$@
|
|
52
63
|
end
|
|
53
64
|
|
|
54
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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.
|
|
134
|
-
|
|
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
|
-
:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
:
|
|
154
|
-
|
|
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? ? {:
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
#
|
|
178
|
-
def handle_connect
|
|
179
|
-
|
|
180
|
-
{:
|
|
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
|
-
#
|
|
185
|
-
def handle_disconnect
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
#
|
|
357
|
+
# { method: "getblocks", params: {} }
|
|
196
358
|
def handle_getblocks
|
|
197
|
-
@node.connections.sample
|
|
198
|
-
|
|
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
|
-
#
|
|
369
|
+
# { method: "getaddr", params: {} }
|
|
203
370
|
def handle_getaddr
|
|
204
|
-
@node.connections.sample
|
|
205
|
-
|
|
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
|
-
#
|
|
210
|
-
def handle_addrs
|
|
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
|
-
#
|
|
391
|
+
# { method: "rescan" }
|
|
220
392
|
def handle_rescan
|
|
221
|
-
EM.defer {
|
|
222
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 +
|
|
268
|
-
# The +
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
316
|
-
|
|
503
|
+
respond(request, { success: true, hash: tx.hash, propagation: {
|
|
504
|
+
received: received, sent: 1, percent: percent } })
|
|
317
505
|
end
|
|
318
506
|
rescue
|
|
319
|
-
respond(
|
|
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
|
-
{:
|
|
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
|
-
#
|
|
337
|
-
def handle_store_block
|
|
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:
|
|
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
|
-
#
|
|
345
|
-
def handle_store_tx
|
|
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:
|
|
537
|
+
{ queued: tx.hash }
|
|
349
538
|
end
|
|
350
539
|
|
|
351
|
-
# format node uptime
|
|
352
|
-
def format_uptime t
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|