bitcoin-ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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