bitcoin-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/COPYING +18 -0
- data/Gemfile +4 -0
- data/README.rdoc +189 -0
- data/Rakefile +104 -0
- data/bin/bitcoin_dns_seed +130 -0
- data/bin/bitcoin_gui +80 -0
- data/bin/bitcoin_node +174 -0
- data/bin/bitcoin_shell +12 -0
- data/bin/bitcoin_wallet +323 -0
- data/bitcoin-ruby.gemspec +27 -0
- data/concept-examples/blockchain-pow.rb +151 -0
- data/doc/CONFIG.rdoc +66 -0
- data/doc/EXAMPLES.rdoc +9 -0
- data/doc/NODE.rdoc +35 -0
- data/doc/STORAGE.rdoc +21 -0
- data/doc/WALLET.rdoc +102 -0
- data/examples/balance.rb +60 -0
- data/examples/bbe_verify_tx.rb +55 -0
- data/examples/connect.rb +36 -0
- data/examples/relay_tx.rb +22 -0
- data/examples/verify_tx.rb +57 -0
- data/lib/bitcoin.rb +370 -0
- data/lib/bitcoin/builder.rb +266 -0
- data/lib/bitcoin/config.rb +56 -0
- data/lib/bitcoin/connection.rb +126 -0
- data/lib/bitcoin/ffi/openssl.rb +121 -0
- data/lib/bitcoin/gui/addr_view.rb +42 -0
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
- data/lib/bitcoin/gui/conn_view.rb +36 -0
- data/lib/bitcoin/gui/connection.rb +68 -0
- data/lib/bitcoin/gui/em_gtk.rb +28 -0
- data/lib/bitcoin/gui/gui.builder +1643 -0
- data/lib/bitcoin/gui/gui.rb +290 -0
- data/lib/bitcoin/gui/helpers.rb +113 -0
- data/lib/bitcoin/gui/tree_view.rb +82 -0
- data/lib/bitcoin/gui/tx_view.rb +67 -0
- data/lib/bitcoin/key.rb +125 -0
- data/lib/bitcoin/logger.rb +65 -0
- data/lib/bitcoin/network/command_client.rb +93 -0
- data/lib/bitcoin/network/command_handler.rb +179 -0
- data/lib/bitcoin/network/connection_handler.rb +274 -0
- data/lib/bitcoin/network/node.rb +399 -0
- data/lib/bitcoin/protocol.rb +140 -0
- data/lib/bitcoin/protocol/address.rb +48 -0
- data/lib/bitcoin/protocol/alert.rb +47 -0
- data/lib/bitcoin/protocol/block.rb +154 -0
- data/lib/bitcoin/protocol/handler.rb +38 -0
- data/lib/bitcoin/protocol/parser.rb +148 -0
- data/lib/bitcoin/protocol/tx.rb +205 -0
- data/lib/bitcoin/protocol/txin.rb +97 -0
- data/lib/bitcoin/protocol/txout.rb +73 -0
- data/lib/bitcoin/protocol/version.rb +70 -0
- data/lib/bitcoin/script.rb +634 -0
- data/lib/bitcoin/storage/dummy.rb +164 -0
- data/lib/bitcoin/storage/models.rb +133 -0
- data/lib/bitcoin/storage/sequel.rb +335 -0
- data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
- data/lib/bitcoin/storage/storage.rb +243 -0
- data/lib/bitcoin/version.rb +3 -0
- data/lib/bitcoin/wallet/coinselector.rb +30 -0
- data/lib/bitcoin/wallet/keygenerator.rb +75 -0
- data/lib/bitcoin/wallet/keystore.rb +203 -0
- data/lib/bitcoin/wallet/txdp.rb +116 -0
- data/lib/bitcoin/wallet/wallet.rb +243 -0
- data/spec/bitcoin/bitcoin_spec.rb +472 -0
- data/spec/bitcoin/builder_spec.rb +90 -0
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
- data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
- data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
- data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
- data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
- data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
- data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
- data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
- data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
- data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
- data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
- data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
- data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
- data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
- data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
- data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
- data/spec/bitcoin/key_spec.rb +123 -0
- data/spec/bitcoin/network_spec.rb +48 -0
- data/spec/bitcoin/protocol/addr_spec.rb +68 -0
- data/spec/bitcoin/protocol/alert_spec.rb +20 -0
- data/spec/bitcoin/protocol/block_spec.rb +101 -0
- data/spec/bitcoin/protocol/inv_spec.rb +124 -0
- data/spec/bitcoin/protocol/ping_spec.rb +49 -0
- data/spec/bitcoin/protocol/tx_spec.rb +226 -0
- data/spec/bitcoin/protocol/version_spec.rb +77 -0
- data/spec/bitcoin/reorg_spec.rb +129 -0
- data/spec/bitcoin/script/opcodes_spec.rb +417 -0
- data/spec/bitcoin/script/script_spec.rb +246 -0
- data/spec/bitcoin/spec_helper.rb +36 -0
- data/spec/bitcoin/storage_spec.rb +229 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
- data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
- metadata +295 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
module Bitcoin::Gui
|
2
|
+
class TxView < TreeView
|
3
|
+
|
4
|
+
def initialize gui, replace = nil
|
5
|
+
super(gui, :tx_view, [
|
6
|
+
[GObject::TYPE_STRING, "Type"],
|
7
|
+
[GObject::TYPE_STRING, "Hash"],
|
8
|
+
[GObject::TYPE_STRING, "Value", :format_value_col],
|
9
|
+
[GObject::TYPE_INT, "Confirmations"],
|
10
|
+
[GObject::TYPE_STRING, "Direction"],
|
11
|
+
])
|
12
|
+
GObject.signal_connect(@view, "row-activated") do |view, path, column|
|
13
|
+
res, iter = @model.get_iter(path)
|
14
|
+
next unless res
|
15
|
+
tx_hash = @model.get_value(iter, 1).get_string
|
16
|
+
@gui.display_tx(tx_hash)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def update txouts
|
21
|
+
EM.defer do
|
22
|
+
@model.clear
|
23
|
+
txouts.each do |txout|
|
24
|
+
row = @model.append(nil)
|
25
|
+
@model.set_value(row, 0, txout.type.to_s)
|
26
|
+
@model.set_value(row, 1, txout.get_tx.hash)
|
27
|
+
@model.set_value(row, 2, txout.value.to_s)
|
28
|
+
@model.set_value(row, 3, txout.get_tx.confirmations)
|
29
|
+
@model.set_value(row, 4, "incoming")
|
30
|
+
if txin = txout.get_next_in
|
31
|
+
row = @model.append(nil)
|
32
|
+
@model.set_value(row, 0, txout.type.to_s)
|
33
|
+
@model.set_value(row, 1, txin.get_tx.hash)
|
34
|
+
@model.set_value(row, 2, (0 - txout.value).to_s)
|
35
|
+
@model.set_value(row, 3, txin.get_tx.confirmations)
|
36
|
+
@model.set_value(row, 4, "outgoing")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
@view.set_model @model
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class TxInView < TreeView
|
45
|
+
def initialize gui, replace = nil
|
46
|
+
super(gui, [
|
47
|
+
[GObject::TYPE_STRING, "Type"],
|
48
|
+
[GObject::TYPE_STRING, "From"],
|
49
|
+
[GObject::TYPE_STRING, "Value", :format_value_col]
|
50
|
+
])
|
51
|
+
old = @gui.builder.get_object("tx_view")
|
52
|
+
end
|
53
|
+
|
54
|
+
def update txins
|
55
|
+
@model.clear
|
56
|
+
txins.each do |txin|
|
57
|
+
txout = txin.get_prev_out
|
58
|
+
row = @model.append(nil)
|
59
|
+
@model.set_value(row, 0, txout.type.to_s)
|
60
|
+
@model.set_value(row, 1, txout.get_addresses.join(", "))
|
61
|
+
@model.set_value(row, 2, txout.value.to_s)
|
62
|
+
end
|
63
|
+
@view.set_model @model
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
data/lib/bitcoin/key.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
|
3
|
+
# Elliptic Curve key as used in bitcoin.
|
4
|
+
class Key
|
5
|
+
|
6
|
+
# Generate a new keypair.
|
7
|
+
# Bitcoin::Key.generate
|
8
|
+
def self.generate
|
9
|
+
k = new; k.generate; k
|
10
|
+
end
|
11
|
+
|
12
|
+
# Import private key from base58 fromat as described in
|
13
|
+
# https://en.bitcoin.it/wiki/Private_key#Base_58_Wallet_Import_format and
|
14
|
+
# https://en.bitcoin.it/wiki/Base58Check_encoding#Encoding_a_private_key.
|
15
|
+
# See also #to_base58
|
16
|
+
def self.from_base58(str)
|
17
|
+
hex = Bitcoin.decode_base58(str)
|
18
|
+
version, key, checksum = hex.unpack("a2a64a8")
|
19
|
+
raise "Invalid version" unless version == Bitcoin.network[:privkey_version]
|
20
|
+
raise "Invalid checksum" unless Bitcoin.checksum(version + key) == checksum
|
21
|
+
new(key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def == other
|
25
|
+
self.priv == other.priv
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create a new key with given +privkey+ and +pubkey+.
|
29
|
+
# Bitcoin::Key.new
|
30
|
+
# Bitcoin::Key.new(privkey)
|
31
|
+
# Bitcoin::Key.new(nil, pubkey)
|
32
|
+
def initialize privkey = nil, pubkey = nil
|
33
|
+
@key = Bitcoin.bitcoin_elliptic_curve
|
34
|
+
set_priv(privkey) if privkey
|
35
|
+
set_pub(pubkey) if pubkey
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generate new priv/pub key.
|
39
|
+
def generate
|
40
|
+
@key.generate_key
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the private key (in hex).
|
44
|
+
def priv
|
45
|
+
return nil unless @key.private_key
|
46
|
+
@key.private_key.to_hex.rjust(64, '0')
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set the private key to +priv+ (in hex).
|
50
|
+
def priv= priv
|
51
|
+
set_priv(priv)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the public key (in hex).
|
55
|
+
# In case the key was initialized with only
|
56
|
+
# a private key, the public key is regenerated.
|
57
|
+
def pub
|
58
|
+
unless @key.public_key
|
59
|
+
if @key.private_key
|
60
|
+
set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1])
|
61
|
+
else
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
@key.public_key.to_hex.rjust(130, '0')
|
66
|
+
end
|
67
|
+
|
68
|
+
# Set the public key (in hex).
|
69
|
+
def pub= pub
|
70
|
+
set_pub(pub)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get the hash160 of the public key.
|
74
|
+
def hash160
|
75
|
+
Bitcoin.hash160(pub)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get the address corresponding to the public key.
|
79
|
+
def addr
|
80
|
+
Bitcoin.hash160_to_address(hash160)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Sign +data+ with the key.
|
84
|
+
# key1 = Bitcoin::Key.generate
|
85
|
+
# sig = key.sign("some data")
|
86
|
+
def sign(data)
|
87
|
+
@key.dsa_sign_asn1(data)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Verify signature +sig+ for +data+.
|
91
|
+
# key2 = Bitcoin::Key.new(nil, key1.pub)
|
92
|
+
# key2.verify("some data", sig)
|
93
|
+
def verify(data, sig)
|
94
|
+
@key.dsa_verify_asn1(data, sig)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Export private key to base58 format.
|
98
|
+
# See also Key.from_base58
|
99
|
+
def to_base58
|
100
|
+
data = Bitcoin.network[:privkey_version] + priv
|
101
|
+
hex = data + Bitcoin.checksum(data)
|
102
|
+
Bitcoin.int_to_base58( hex.to_i(16) )
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
# Regenerate public key from the private key.
|
108
|
+
def regenerate_pubkey
|
109
|
+
set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1])
|
110
|
+
end
|
111
|
+
|
112
|
+
# Set +priv+ as the new private key (converting from hex).
|
113
|
+
def set_priv(priv)
|
114
|
+
@key.private_key = OpenSSL::BN.from_hex(priv)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Set +pub+ as the new public key (converting from hex).
|
118
|
+
def set_pub(pub)
|
119
|
+
@key.public_key = OpenSSL::PKey::EC::Point.from_hex(@key.group, pub)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
if Bitcoin.require_dependency :log4r, exit: false
|
2
|
+
# monkey-patch Log4r to accept level names as symbols
|
3
|
+
class Log4r::Logger
|
4
|
+
def level= l = 0
|
5
|
+
_level = l.is_a?(Fixnum) ? l : Log4r::LNAMES.index(l.to_s.upcase)
|
6
|
+
Log4r::Log4rTools.validate_level(_level)
|
7
|
+
@level = _level
|
8
|
+
LoggerFactory.define_methods(self)
|
9
|
+
Log4r::Logger.log_internal {"Logger '#{@fullname}' set to #{LNAMES[@level]}"}
|
10
|
+
@level
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Bitcoin
|
16
|
+
# this is a very simple logger that is used if log4r is not available
|
17
|
+
module Logger
|
18
|
+
|
19
|
+
class Logger
|
20
|
+
LEVELS = [:debug, :info, :warn, :error, :fatal]
|
21
|
+
|
22
|
+
attr_accessor :level
|
23
|
+
|
24
|
+
def initialize(name)
|
25
|
+
@name, @level = name, :info
|
26
|
+
end
|
27
|
+
|
28
|
+
def level= level
|
29
|
+
@level = level.is_a?(Fixnum) ? LEVELS[level] : level.to_sym
|
30
|
+
end
|
31
|
+
|
32
|
+
LEVELS.each do |level|
|
33
|
+
define_method(level) do |*msg, &block|
|
34
|
+
return if LEVELS.index(level.to_sym) < LEVELS.index(@level.to_sym)
|
35
|
+
msg = block ? block.call : msg.join
|
36
|
+
puts "#{level.to_s.upcase.ljust(5)} #{@name}: #{msg}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# wrap a logger and prepend a special name in front of the messages
|
42
|
+
class LogWrapper
|
43
|
+
def initialize(name, log); @name, @log = name, log; end
|
44
|
+
def method_missing(m, *a, &blk)
|
45
|
+
@log.send(m, *a, &proc{ "#{@name} #{blk.call}" })
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# create a logger with given +name+. if log4r is installed, the logger
|
50
|
+
# will have a stdout and a fileout outputter to `log/<name>.log`.
|
51
|
+
# otherwise, the internal dummy logger is used which only logs to stdout.
|
52
|
+
def self.create name, level = :info
|
53
|
+
if defined?(Log4r)
|
54
|
+
@log = Log4r::Logger.new(name.to_s)
|
55
|
+
@log.level = level
|
56
|
+
@log.outputters << Log4r::Outputter.stdout
|
57
|
+
@log.outputters << Log4r::FileOutputter.new("fout", :filename => "log/#{name}.log")
|
58
|
+
else
|
59
|
+
@log = Bitcoin::Logger::Logger.new(name)
|
60
|
+
end
|
61
|
+
@log
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# Client to connect to CommandHandler and issue requests or register for events
|
2
|
+
class Bitcoin::Network::CommandClient < EM::Connection
|
3
|
+
|
4
|
+
# create new client connecting to +host+:+port+ and executing callbacks from +block+,
|
5
|
+
# passing +args+ in.
|
6
|
+
# CommandClient.connect(host, port) do
|
7
|
+
# on_connected { request("info") }
|
8
|
+
# on_info {|i| p i}
|
9
|
+
# end
|
10
|
+
def initialize host, port, block, *args
|
11
|
+
@host, @port = host, port
|
12
|
+
@args = args
|
13
|
+
@callbacks = {}
|
14
|
+
@block = block
|
15
|
+
instance_eval &block if block
|
16
|
+
@buffer = BufferedTokenizer.new("\x00")
|
17
|
+
@connection_attempts = 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def log;
|
21
|
+
@log ||= Bitcoin::Logger.create(:client)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.connect host, port, *args, &block
|
25
|
+
client = EM.connect(host, port.to_i, self, host, port.to_i, block, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
# call +connected+ callback
|
29
|
+
def post_init
|
30
|
+
log.debug { "Connected" }
|
31
|
+
callback :connected
|
32
|
+
end
|
33
|
+
|
34
|
+
# call +disconnected+ callback and try to reconnect
|
35
|
+
def unbind
|
36
|
+
log.debug { "Disconnected." }
|
37
|
+
callback :disconnected
|
38
|
+
if @connection_attempts > 1
|
39
|
+
log.info { "Trying to start server..." }
|
40
|
+
EM.defer { system("bin/bitcoin_node", "--quiet") }
|
41
|
+
end
|
42
|
+
EM.add_timer(1) do
|
43
|
+
@connection_attempts += 1
|
44
|
+
reconnect(@host, @port)
|
45
|
+
post_init
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# request command +cmd+ with +args+ from the server
|
50
|
+
def request cmd, *args
|
51
|
+
log.debug { "request: #{cmd} #{args.inspect}" }
|
52
|
+
register_monitor_callbacks if cmd.to_sym == :monitor
|
53
|
+
send_data([cmd, args].to_json + "\x00")
|
54
|
+
end
|
55
|
+
|
56
|
+
# receive response from server
|
57
|
+
def receive_data data
|
58
|
+
@connection_attempts = 0
|
59
|
+
@buffer.extract(data).each do |packet|
|
60
|
+
cmd, *data = *JSON.load(packet)
|
61
|
+
log.debug { d = data.inspect
|
62
|
+
"response: #{cmd} #{d[0...50]}#{d.size > 50 ? '...' : ''}" }
|
63
|
+
callback(:response, cmd, *data)
|
64
|
+
callback(cmd.to_sym, *data)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# call the callback specified by +name+ passing in +args+
|
69
|
+
def callback name, *args
|
70
|
+
cb = @callbacks[name.to_sym]
|
71
|
+
return unless cb
|
72
|
+
log.debug { "callback: #{name}" }
|
73
|
+
cb.call(*args)
|
74
|
+
end
|
75
|
+
|
76
|
+
# register callback methods
|
77
|
+
def method_missing(name, *args, &block)
|
78
|
+
if name =~ /^on_/
|
79
|
+
@callbacks[name.to_s.split("on_")[1].to_sym] = block
|
80
|
+
log.debug { "callback #{name} registered" }
|
81
|
+
else
|
82
|
+
super(name, *args)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# register callbacks for monitor
|
87
|
+
def register_monitor_callbacks
|
88
|
+
on_monitor do |type, data|
|
89
|
+
callback(type, *data)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'monitor'
|
3
|
+
|
4
|
+
# Started by the Node, accepts connections from CommandClient and answers requests or
|
5
|
+
# registers for events and notifies the clients when they happen.
|
6
|
+
class Bitcoin::Network::CommandHandler < EM::Connection
|
7
|
+
|
8
|
+
# create new CommandHandler
|
9
|
+
def initialize node
|
10
|
+
@node = node
|
11
|
+
@node.command_connections << self
|
12
|
+
@buf = BufferedTokenizer.new("\x00")
|
13
|
+
@lock = Monitor.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# wrap logger and append prefix
|
17
|
+
def log
|
18
|
+
@log ||= Bitcoin::Logger::LogWrapper.new("command:", @node.log)
|
19
|
+
end
|
20
|
+
|
21
|
+
# respond to a command; send serialized response to the client
|
22
|
+
def respond(cmd, data)
|
23
|
+
return unless data
|
24
|
+
@lock.synchronize do
|
25
|
+
send_data([cmd, data].to_json + "\x00")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# receive request from the client
|
30
|
+
def receive_data data
|
31
|
+
@buf.extract(data).each do |packet|
|
32
|
+
cmd, args = JSON::parse(packet)
|
33
|
+
log.debug { [cmd, args] }
|
34
|
+
if respond_to?("handle_#{cmd}")
|
35
|
+
respond(cmd, send("handle_#{cmd}", *args))
|
36
|
+
else
|
37
|
+
respond(cmd, {:error => "unknown command: #{cmd}. send 'help' for help."})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
rescue Exception
|
41
|
+
p $!
|
42
|
+
end
|
43
|
+
|
44
|
+
# handle +monitor+ command; subscribe client to specified channels
|
45
|
+
# (+block+, +tx+, +connection+)
|
46
|
+
# bitcoin_node monitor block
|
47
|
+
# bitcoin_node monitor "block tx connection"
|
48
|
+
def handle_monitor *channels
|
49
|
+
channels.each do |channel|
|
50
|
+
@node.notifiers[channel.to_sym].subscribe do |*data|
|
51
|
+
respond("monitor", [channel, *data])
|
52
|
+
end
|
53
|
+
case channel.to_sym
|
54
|
+
when :block
|
55
|
+
head = Bitcoin::P::Block.new(@node.store.get_head.to_payload) rescue nil
|
56
|
+
respond("monitor", ["block", [head, @node.store.get_depth.to_s]]) if head
|
57
|
+
when :connection
|
58
|
+
@node.connections.select {|c| c.connected?}.each do |conn|
|
59
|
+
respond("monitor", [:connection, [:connected, conn.info]])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# display various statistics
|
67
|
+
# bitcoin_node info
|
68
|
+
def handle_info
|
69
|
+
blocks = @node.connections.map(&:version).compact.map(&:block) rescue nil
|
70
|
+
{
|
71
|
+
:blocks => "#{@node.store.get_depth} (#{(blocks.inject{|a,b| a+=b;a} / blocks.size rescue '?')})#{@node.in_sync ? ' sync' : ''}",
|
72
|
+
:addrs => "#{@node.addrs.select{|a| a.alive?}.size} (#{@node.addrs.size})",
|
73
|
+
:connections => "#{@node.connections.select{|c| c.state == :connected}.size} (#{@node.connections.size})",
|
74
|
+
:queue => @node.queue.size,
|
75
|
+
:inv_queue => @node.inv_queue.size,
|
76
|
+
:inv_cache => @node.inv_cache.size,
|
77
|
+
:network => @node.config[:network],
|
78
|
+
:storage => @node.config[:storage],
|
79
|
+
:version => Bitcoin::Protocol::VERSION,
|
80
|
+
:uptime => format_uptime(@node.uptime),
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
# display configuration hash currently used
|
85
|
+
# bitcoin_node config
|
86
|
+
def handle_config
|
87
|
+
@node.config
|
88
|
+
end
|
89
|
+
|
90
|
+
# display connected peers
|
91
|
+
# bitcoin_node connections
|
92
|
+
def handle_connections
|
93
|
+
@node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
|
94
|
+
"#{c.host.rjust(15)}:#{c.port} [state: #{c.state}, " +
|
95
|
+
"version: #{c.version.version rescue '?'}, " +
|
96
|
+
"block: #{c.version.block rescue '?'}, " +
|
97
|
+
"uptime: #{format_uptime(c.uptime) rescue 0}, " +
|
98
|
+
"client: #{c.version.user_agent rescue '?'}]" }
|
99
|
+
end
|
100
|
+
|
101
|
+
# connect to given peer(s)
|
102
|
+
# bitcoin_node connect <ip>:<port>[,<ip>:<port>]
|
103
|
+
def handle_connect *args
|
104
|
+
args.each {|a| @node.connect_peer(*a.split(':')) }
|
105
|
+
{:state => "Connecting..."}
|
106
|
+
end
|
107
|
+
|
108
|
+
# disconnect peer(s)
|
109
|
+
# bitcoin_node disconnect <ip>:<port>[,<ip>,<port>]
|
110
|
+
def handle_disconnect *args
|
111
|
+
args.each do |c|
|
112
|
+
host, port = *c.split(":")
|
113
|
+
conn = @node.connections.select{|c| c.host == host && c.port == port.to_i}.first
|
114
|
+
conn.close_connection if conn
|
115
|
+
end
|
116
|
+
{:state => "Disconnected"}
|
117
|
+
end
|
118
|
+
|
119
|
+
# trigger node to ask peers for new blocks
|
120
|
+
# bitcoin_node getblocks
|
121
|
+
def handle_getblocks
|
122
|
+
@node.connections.sample.send_getblocks
|
123
|
+
{:state => "Sending getblocks..."}
|
124
|
+
end
|
125
|
+
|
126
|
+
# trigger node to ask for new peer addrs
|
127
|
+
# bitcoin_node getaddr
|
128
|
+
def handle_getaddr
|
129
|
+
@node.connections.sample.send_getaddr
|
130
|
+
{:state => "Sending getaddr..."}
|
131
|
+
end
|
132
|
+
|
133
|
+
# display known peer addrs (used by bin/bitcoin_dns_seed)
|
134
|
+
# bitcoin_node addrs [count]
|
135
|
+
def handle_addrs count = 32
|
136
|
+
@node.addrs.weighted_sample(count.to_i) do |addr|
|
137
|
+
Time.now.tv_sec + 7200 - addr.time
|
138
|
+
end.map do |addr|
|
139
|
+
[addr.ip, addr.port, Time.now.tv_sec - addr.time] rescue nil
|
140
|
+
end.compact
|
141
|
+
end
|
142
|
+
|
143
|
+
# relay given transaction (in hex)
|
144
|
+
# bitcoin_node relay_tx <tx data>
|
145
|
+
def handle_relay_tx data
|
146
|
+
tx = Bitcoin::Protocol::Tx.from_hash(data)
|
147
|
+
@node.relay_tx(tx)
|
148
|
+
rescue
|
149
|
+
{:error => $!}
|
150
|
+
end
|
151
|
+
|
152
|
+
# stop bitcoin node
|
153
|
+
# bitcoin_node stop
|
154
|
+
def handle_stop
|
155
|
+
Thread.start { sleep 0.1; @node.stop }
|
156
|
+
{:state => "Stopping..."}
|
157
|
+
end
|
158
|
+
|
159
|
+
# list all commands
|
160
|
+
# bitcoin_node help
|
161
|
+
def handle_help
|
162
|
+
self.methods.grep(/^handle_(.*?)/).map {|m| m.to_s.sub(/^(.*?)_/, '')}
|
163
|
+
end
|
164
|
+
|
165
|
+
# format node uptime
|
166
|
+
def format_uptime t
|
167
|
+
mm, ss = t.divmod(60) #=> [4515, 21]
|
168
|
+
hh, mm = mm.divmod(60) #=> [75, 15]
|
169
|
+
dd, hh = hh.divmod(24) #=> [3, 3]
|
170
|
+
"%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
|
171
|
+
end
|
172
|
+
|
173
|
+
# disconnect notification clients when connection is closed
|
174
|
+
def unbind
|
175
|
+
#@node.notifiers.unsubscribe(@notify_sid) if @notify_sid
|
176
|
+
@node.command_connections.delete(self)
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|