bitcoin-ruby 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|