bitcoin-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. data/.gitignore +12 -0
  2. data/COPYING +18 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +189 -0
  5. data/Rakefile +104 -0
  6. data/bin/bitcoin_dns_seed +130 -0
  7. data/bin/bitcoin_gui +80 -0
  8. data/bin/bitcoin_node +174 -0
  9. data/bin/bitcoin_shell +12 -0
  10. data/bin/bitcoin_wallet +323 -0
  11. data/bitcoin-ruby.gemspec +27 -0
  12. data/concept-examples/blockchain-pow.rb +151 -0
  13. data/doc/CONFIG.rdoc +66 -0
  14. data/doc/EXAMPLES.rdoc +9 -0
  15. data/doc/NODE.rdoc +35 -0
  16. data/doc/STORAGE.rdoc +21 -0
  17. data/doc/WALLET.rdoc +102 -0
  18. data/examples/balance.rb +60 -0
  19. data/examples/bbe_verify_tx.rb +55 -0
  20. data/examples/connect.rb +36 -0
  21. data/examples/relay_tx.rb +22 -0
  22. data/examples/verify_tx.rb +57 -0
  23. data/lib/bitcoin.rb +370 -0
  24. data/lib/bitcoin/builder.rb +266 -0
  25. data/lib/bitcoin/config.rb +56 -0
  26. data/lib/bitcoin/connection.rb +126 -0
  27. data/lib/bitcoin/ffi/openssl.rb +121 -0
  28. data/lib/bitcoin/gui/addr_view.rb +42 -0
  29. data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
  30. data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
  31. data/lib/bitcoin/gui/conn_view.rb +36 -0
  32. data/lib/bitcoin/gui/connection.rb +68 -0
  33. data/lib/bitcoin/gui/em_gtk.rb +28 -0
  34. data/lib/bitcoin/gui/gui.builder +1643 -0
  35. data/lib/bitcoin/gui/gui.rb +290 -0
  36. data/lib/bitcoin/gui/helpers.rb +113 -0
  37. data/lib/bitcoin/gui/tree_view.rb +82 -0
  38. data/lib/bitcoin/gui/tx_view.rb +67 -0
  39. data/lib/bitcoin/key.rb +125 -0
  40. data/lib/bitcoin/logger.rb +65 -0
  41. data/lib/bitcoin/network/command_client.rb +93 -0
  42. data/lib/bitcoin/network/command_handler.rb +179 -0
  43. data/lib/bitcoin/network/connection_handler.rb +274 -0
  44. data/lib/bitcoin/network/node.rb +399 -0
  45. data/lib/bitcoin/protocol.rb +140 -0
  46. data/lib/bitcoin/protocol/address.rb +48 -0
  47. data/lib/bitcoin/protocol/alert.rb +47 -0
  48. data/lib/bitcoin/protocol/block.rb +154 -0
  49. data/lib/bitcoin/protocol/handler.rb +38 -0
  50. data/lib/bitcoin/protocol/parser.rb +148 -0
  51. data/lib/bitcoin/protocol/tx.rb +205 -0
  52. data/lib/bitcoin/protocol/txin.rb +97 -0
  53. data/lib/bitcoin/protocol/txout.rb +73 -0
  54. data/lib/bitcoin/protocol/version.rb +70 -0
  55. data/lib/bitcoin/script.rb +634 -0
  56. data/lib/bitcoin/storage/dummy.rb +164 -0
  57. data/lib/bitcoin/storage/models.rb +133 -0
  58. data/lib/bitcoin/storage/sequel.rb +335 -0
  59. data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
  60. data/lib/bitcoin/storage/storage.rb +243 -0
  61. data/lib/bitcoin/version.rb +3 -0
  62. data/lib/bitcoin/wallet/coinselector.rb +30 -0
  63. data/lib/bitcoin/wallet/keygenerator.rb +75 -0
  64. data/lib/bitcoin/wallet/keystore.rb +203 -0
  65. data/lib/bitcoin/wallet/txdp.rb +116 -0
  66. data/lib/bitcoin/wallet/wallet.rb +243 -0
  67. data/spec/bitcoin/bitcoin_spec.rb +472 -0
  68. data/spec/bitcoin/builder_spec.rb +90 -0
  69. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
  70. data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
  71. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
  72. data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
  73. data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
  74. data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
  75. data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
  76. data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
  77. data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
  78. data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
  79. data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
  80. data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
  81. data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
  82. data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
  83. data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
  84. data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
  85. data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
  86. data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
  87. data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
  88. data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
  89. data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
  90. data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
  91. data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
  92. data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
  93. data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
  94. data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
  95. data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
  96. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
  97. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
  98. data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
  99. data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
  100. data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
  101. data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
  102. data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
  103. data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
  104. data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
  105. data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
  106. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
  107. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
  108. data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
  109. data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
  110. data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
  111. data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
  112. data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
  113. data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
  114. data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
  115. data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
  116. data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
  117. data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
  118. data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
  119. data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
  120. data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
  121. data/spec/bitcoin/key_spec.rb +123 -0
  122. data/spec/bitcoin/network_spec.rb +48 -0
  123. data/spec/bitcoin/protocol/addr_spec.rb +68 -0
  124. data/spec/bitcoin/protocol/alert_spec.rb +20 -0
  125. data/spec/bitcoin/protocol/block_spec.rb +101 -0
  126. data/spec/bitcoin/protocol/inv_spec.rb +124 -0
  127. data/spec/bitcoin/protocol/ping_spec.rb +49 -0
  128. data/spec/bitcoin/protocol/tx_spec.rb +226 -0
  129. data/spec/bitcoin/protocol/version_spec.rb +77 -0
  130. data/spec/bitcoin/reorg_spec.rb +129 -0
  131. data/spec/bitcoin/script/opcodes_spec.rb +417 -0
  132. data/spec/bitcoin/script/script_spec.rb +246 -0
  133. data/spec/bitcoin/spec_helper.rb +36 -0
  134. data/spec/bitcoin/storage_spec.rb +229 -0
  135. data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
  136. data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
  137. data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
  138. data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
  139. data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
  140. 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
@@ -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