bitcoin-ruby 0.0.1 → 0.0.2

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 (136) hide show
  1. data/.gitignore +4 -1
  2. data/Gemfile +21 -0
  3. data/README.rdoc +85 -25
  4. data/Rakefile +7 -3
  5. data/bin/bitcoin_node +39 -42
  6. data/bin/bitcoin_shell +1 -0
  7. data/bin/bitcoin_wallet +129 -53
  8. data/bitcoin-ruby.gemspec +4 -7
  9. data/concept-examples/blockchain-pow.rb +1 -1
  10. data/doc/CONFIG.rdoc +5 -5
  11. data/doc/EXAMPLES.rdoc +9 -5
  12. data/doc/NAMECOIN.rdoc +34 -0
  13. data/doc/NODE.rdoc +147 -10
  14. data/examples/balance.rb +10 -4
  15. data/examples/bbe_verify_tx.rb +7 -2
  16. data/examples/forwarder.rb +73 -0
  17. data/examples/generate_tx.rb +34 -0
  18. data/examples/simple_network_monitor_and_util.rb +187 -0
  19. data/examples/verify_tx.rb +1 -1
  20. data/lib/bitcoin.rb +308 -18
  21. data/lib/bitcoin/builder.rb +62 -36
  22. data/lib/bitcoin/config.rb +2 -0
  23. data/lib/bitcoin/connection.rb +11 -8
  24. data/lib/bitcoin/electrum/mnemonic.rb +162 -0
  25. data/lib/bitcoin/ffi/openssl.rb +187 -21
  26. data/lib/bitcoin/gui/addr_view.rb +2 -0
  27. data/lib/bitcoin/gui/conn_view.rb +2 -0
  28. data/lib/bitcoin/gui/connection.rb +2 -0
  29. data/lib/bitcoin/gui/em_gtk.rb +2 -0
  30. data/lib/bitcoin/gui/gui.rb +2 -0
  31. data/lib/bitcoin/gui/helpers.rb +2 -0
  32. data/lib/bitcoin/gui/tree_view.rb +2 -0
  33. data/lib/bitcoin/gui/tx_view.rb +2 -0
  34. data/lib/bitcoin/key.rb +77 -11
  35. data/lib/bitcoin/litecoin.rb +81 -0
  36. data/lib/bitcoin/logger.rb +20 -1
  37. data/lib/bitcoin/namecoin.rb +279 -0
  38. data/lib/bitcoin/network/command_client.rb +7 -6
  39. data/lib/bitcoin/network/command_handler.rb +229 -43
  40. data/lib/bitcoin/network/connection_handler.rb +182 -70
  41. data/lib/bitcoin/network/node.rb +231 -106
  42. data/lib/bitcoin/protocol.rb +44 -23
  43. data/lib/bitcoin/protocol/address.rb +5 -3
  44. data/lib/bitcoin/protocol/alert.rb +3 -4
  45. data/lib/bitcoin/protocol/aux_pow.rb +123 -0
  46. data/lib/bitcoin/protocol/block.rb +98 -18
  47. data/lib/bitcoin/protocol/handler.rb +6 -5
  48. data/lib/bitcoin/protocol/parser.rb +44 -19
  49. data/lib/bitcoin/protocol/tx.rb +105 -52
  50. data/lib/bitcoin/protocol/txin.rb +39 -19
  51. data/lib/bitcoin/protocol/txout.rb +28 -13
  52. data/lib/bitcoin/protocol/version.rb +16 -7
  53. data/lib/bitcoin/script.rb +579 -122
  54. data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
  55. data/lib/bitcoin/storage/models.rb +20 -7
  56. data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
  57. data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
  58. data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
  59. data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
  60. data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
  61. data/lib/bitcoin/storage/storage.rb +233 -28
  62. data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
  63. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
  64. data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
  65. data/lib/bitcoin/validation.rb +369 -0
  66. data/lib/bitcoin/version.rb +1 -1
  67. data/lib/bitcoin/wallet/coinselector.rb +3 -0
  68. data/lib/bitcoin/wallet/keygenerator.rb +3 -1
  69. data/lib/bitcoin/wallet/keystore.rb +6 -2
  70. data/lib/bitcoin/wallet/txdp.rb +6 -4
  71. data/lib/bitcoin/wallet/wallet.rb +54 -16
  72. data/spec/bitcoin/bitcoin_spec.rb +48 -3
  73. data/spec/bitcoin/builder_spec.rb +40 -17
  74. data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
  75. data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
  76. data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
  77. data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
  78. data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
  79. data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
  80. data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
  81. data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
  82. data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
  83. data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
  84. data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
  85. data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
  86. data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
  87. data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
  88. data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
  89. data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
  90. data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
  91. data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
  92. data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
  93. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
  94. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
  95. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
  96. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
  97. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
  98. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
  99. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
  100. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
  101. data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
  102. data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
  103. data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
  104. data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
  105. data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
  106. data/spec/bitcoin/key_spec.rb +128 -3
  107. data/spec/bitcoin/namecoin_spec.rb +182 -0
  108. data/spec/bitcoin/network_spec.rb +5 -3
  109. data/spec/bitcoin/node/command_api_spec.rb +376 -0
  110. data/spec/bitcoin/protocol/addr_spec.rb +2 -0
  111. data/spec/bitcoin/protocol/alert_spec.rb +2 -0
  112. data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
  113. data/spec/bitcoin/protocol/block_spec.rb +134 -39
  114. data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
  115. data/spec/bitcoin/protocol/inv_spec.rb +10 -8
  116. data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
  117. data/spec/bitcoin/protocol/ping_spec.rb +2 -0
  118. data/spec/bitcoin/protocol/tx_spec.rb +83 -17
  119. data/spec/bitcoin/protocol/version_spec.rb +7 -5
  120. data/spec/bitcoin/script/opcodes_spec.rb +412 -133
  121. data/spec/bitcoin/script/script_spec.rb +112 -13
  122. data/spec/bitcoin/spec_helper.rb +68 -0
  123. data/spec/bitcoin/storage/reorg_spec.rb +199 -0
  124. data/spec/bitcoin/storage/storage_spec.rb +337 -0
  125. data/spec/bitcoin/storage/validation_spec.rb +261 -0
  126. data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
  127. data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
  128. data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
  129. data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
  130. data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
  131. metadata +105 -51
  132. data/lib/bitcoin/storage/sequel.rb +0 -335
  133. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
  134. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
  135. data/spec/bitcoin/reorg_spec.rb +0 -129
  136. data/spec/bitcoin/storage_spec.rb +0 -229
@@ -1,3 +1,7 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require 'json'
4
+
1
5
  # Client to connect to CommandHandler and issue requests or register for events
2
6
  class Bitcoin::Network::CommandClient < EM::Connection
3
7
 
@@ -35,11 +39,7 @@ class Bitcoin::Network::CommandClient < EM::Connection
35
39
  def unbind
36
40
  log.debug { "Disconnected." }
37
41
  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
42
+ EM.add_timer(@connection_attempts) do
43
43
  @connection_attempts += 1
44
44
  reconnect(@host, @port)
45
45
  post_init
@@ -86,7 +86,8 @@ class Bitcoin::Network::CommandClient < EM::Connection
86
86
  # register callbacks for monitor
87
87
  def register_monitor_callbacks
88
88
  on_monitor do |type, data|
89
- callback(type, *data)
89
+ type, *params = type.split("_")
90
+ callback(type, *((data || []) + (params || [])))
90
91
  end
91
92
  end
92
93
 
@@ -1,3 +1,5 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  require 'json'
2
4
  require 'monitor'
3
5
 
@@ -29,83 +31,156 @@ class Bitcoin::Network::CommandHandler < EM::Connection
29
31
  # receive request from the client
30
32
  def receive_data data
31
33
  @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."})
34
+ begin
35
+ cmd, args = JSON::parse(packet)
36
+ log.debug { [cmd, args] }
37
+ if cmd == "relay_tx"
38
+ handle_relay_tx(*args)
39
+ return
40
+ end
41
+ if respond_to?("handle_#{cmd}")
42
+ respond(cmd, send("handle_#{cmd}", *args))
43
+ else
44
+ respond(cmd, { error: "unknown command: #{cmd}. send 'help' for help." })
45
+ end
46
+ rescue ArgumentError
47
+ respond(cmd, { error: $!.message })
38
48
  end
39
49
  end
40
50
  rescue Exception
41
- p $!
51
+ p $!; puts *$@
42
52
  end
43
53
 
44
54
  # handle +monitor+ command; subscribe client to specified channels
45
- # (+block+, +tx+, +connection+)
55
+ # (+block+, +tx+, +output+, +connection+).
56
+ # Some commands can have parameters, e.g. the number of confirmations
57
+ # +tx+ or +output+ should have. Parameters are appended to the command
58
+ # name after an underscore (_), e.g. subscribe to channel "tx_6" to
59
+ # receive only transactions with 6 confirmations.
60
+ #
61
+ # Receive new blocks:
46
62
  # bitcoin_node monitor block
47
- # bitcoin_node monitor "block tx connection"
63
+ # Receive new (unconfirmed) transactions:
64
+ # bitcoin_node monitor tx
65
+ # Receive transactions with 6 confirmations:
66
+ # bitcoin_node monitor tx_6
67
+ # Receive [txhash, address, value] for each output:
68
+ # bitcoin_node monitor output
69
+ # Receive peer connections/disconnections:
70
+ # bitcoin_node monitor connection"
71
+ # Combine multiple channels:
72
+ # bitcoin_node monitor "block tx tx_1 tx_6 connection"
73
+ #
74
+ # NOTE: When a new block is found, it might include transactions that we
75
+ # didn't previously receive as unconfirmed. To make sure you receive all
76
+ # transactions, also subscribe to the tx_1 channel.
48
77
  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]])
78
+ channels.map(&:to_sym).each do |channel|
79
+ @node.subscribe(channel) {|*data| respond("monitor", [channel, *data]) }
80
+ name, *params = channel.to_s.split("_")
81
+ send("handle_monitor_#{name}", *params)
82
+ log.info { "Client subscribed to channel #{channel}" }
83
+ end
84
+ nil
85
+ end
86
+
87
+ # Handle +monitor block+ command; send the current chain head
88
+ # after client is subscribed to :block channel
89
+ def handle_monitor_block
90
+ head = Bitcoin::P::Block.new(@node.store.get_head.to_payload) rescue nil
91
+ respond("monitor", ["block", [head, @node.store.get_depth]]) if head
92
+ end
93
+
94
+ # Handle +monitor tx+ command.
95
+ # When +conf+ is given, don't subscribe to the :tx channel for unconfirmed
96
+ # transactions. Instead, subscribe to the :block channel, and whenever a new
97
+ # block comes in, send all transactions that now have +conf+ confirmations.
98
+ def handle_monitor_tx conf = nil
99
+ return unless conf
100
+ if conf.to_i == 0 # 'tx_0' is just an alias for 'tx'
101
+ return @node.subscribe(:tx) {|*a| @node.notifiers[:tx_0].push(*a) }
102
+ end
103
+ @node.subscribe(:block) do |block, depth|
104
+ block = @node.store.get_block_by_depth(depth - conf.to_i + 1)
105
+ next unless block
106
+ block.tx.each {|tx| @node.notifiers["tx_#{conf}".to_sym].push([tx, conf.to_i]) }
107
+ end
108
+ end
109
+
110
+ # Handle +monitor output+ command.
111
+ # Receive tx hash, recipient address and value for each output.
112
+ # This allows easy scanning for new payments without parsing the
113
+ # tx format and running scripts.
114
+ # See #handle_monitor_tx for confirmation behavior.
115
+ def handle_monitor_output conf = 0
116
+ return unless (conf = conf.to_i) > 0
117
+ @node.subscribe(:block) do |block, depth|
118
+ block = @node.store.get_block_by_depth(depth - conf + 1)
119
+ next unless block
120
+ block.tx.each do |tx|
121
+ tx.out.each do |out|
122
+ addr = Bitcoin::Script.new(out.pk_script).get_address
123
+ res = [tx.hash, addr, out.value, conf]
124
+ @node.push_notification("output_#{conf}".to_sym, res)
60
125
  end
61
126
  end
62
127
  end
63
- nil
64
128
  end
65
129
 
66
- # display various statistics
130
+ # Handle +monitor connection+ command; send current connections
131
+ # after client is subscribed to :connection channel.
132
+ def handle_monitor_connection
133
+ @node.connections.select {|c| c.connected?}.each do |conn|
134
+ respond("monitor", [:connection, [:connected, conn.info]])
135
+ end
136
+ end
137
+
138
+ # Get various statistics.
67
139
  # bitcoin_node info
68
140
  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' : ''}",
141
+ blocks = @node.connections.map(&:version).compact.map(&:last_block) rescue nil
142
+ established = @node.connections.select {|c| c.state == :connected }
143
+ info = {
144
+ :blocks => "#{@node.store.get_depth} (#{(blocks.inject{|a,b| a+=b; a } / blocks.size rescue '?' )})#{@node.store.in_sync? ? ' sync' : ''}",
72
145
  :addrs => "#{@node.addrs.select{|a| a.alive?}.size} (#{@node.addrs.size})",
73
- :connections => "#{@node.connections.select{|c| c.state == :connected}.size} (#{@node.connections.size})",
146
+ :connections => "#{established.size} established (#{established.select(&:outgoing?).size} out, #{established.select(&:incoming?).size} in), #{@node.connections.size - established.size} connecting",
74
147
  :queue => @node.queue.size,
75
148
  :inv_queue => @node.inv_queue.size,
76
149
  :inv_cache => @node.inv_cache.size,
77
150
  :network => @node.config[:network],
78
151
  :storage => @node.config[:storage],
79
- :version => Bitcoin::Protocol::VERSION,
152
+ :version => Bitcoin.network[:protocol_version],
153
+ :external_ip => @node.external_ip,
80
154
  :uptime => format_uptime(@node.uptime),
81
155
  }
156
+ Bitcoin.namecoin? ? {:names => @node.store.db[:names].count}.merge(info) : info
82
157
  end
83
158
 
84
- # display configuration hash currently used
159
+ # Get the currently active configuration.
85
160
  # bitcoin_node config
86
161
  def handle_config
87
162
  @node.config
88
163
  end
89
164
 
90
- # display connected peers
165
+ # Get currently connected peers.
91
166
  # bitcoin_node connections
92
167
  def handle_connections
93
168
  @node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
94
- "#{c.host.rjust(15)}:#{c.port} [state: #{c.state}, " +
169
+ "#{c.host.rjust(15)}:#{c.port} [#{c.direction}, state: #{c.state}, " +
95
170
  "version: #{c.version.version rescue '?'}, " +
96
171
  "block: #{c.version.block rescue '?'}, " +
97
172
  "uptime: #{format_uptime(c.uptime) rescue 0}, " +
98
173
  "client: #{c.version.user_agent rescue '?'}]" }
99
174
  end
100
175
 
101
- # connect to given peer(s)
176
+ # Connect to given peer(s).
102
177
  # bitcoin_node connect <ip>:<port>[,<ip>:<port>]
103
178
  def handle_connect *args
104
179
  args.each {|a| @node.connect_peer(*a.split(':')) }
105
180
  {:state => "Connecting..."}
106
181
  end
107
182
 
108
- # disconnect peer(s)
183
+ # Disconnect given peer(s).
109
184
  # bitcoin_node disconnect <ip>:<port>[,<ip>,<port>]
110
185
  def handle_disconnect *args
111
186
  args.each do |c|
@@ -116,21 +191,21 @@ class Bitcoin::Network::CommandHandler < EM::Connection
116
191
  {:state => "Disconnected"}
117
192
  end
118
193
 
119
- # trigger node to ask peers for new blocks
194
+ # Trigger the node to ask its peers for new blocks.
120
195
  # bitcoin_node getblocks
121
196
  def handle_getblocks
122
197
  @node.connections.sample.send_getblocks
123
198
  {:state => "Sending getblocks..."}
124
199
  end
125
200
 
126
- # trigger node to ask for new peer addrs
201
+ # Trigger the node to ask its for new peer addresses.
127
202
  # bitcoin_node getaddr
128
203
  def handle_getaddr
129
204
  @node.connections.sample.send_getaddr
130
205
  {:state => "Sending getaddr..."}
131
206
  end
132
207
 
133
- # display known peer addrs (used by bin/bitcoin_dns_seed)
208
+ # Get known peer addresses (used by bin/bitcoin_dns_seed).
134
209
  # bitcoin_node addrs [count]
135
210
  def handle_addrs count = 32
136
211
  @node.addrs.weighted_sample(count.to_i) do |addr|
@@ -140,28 +215,139 @@ class Bitcoin::Network::CommandHandler < EM::Connection
140
215
  end.compact
141
216
  end
142
217
 
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)
218
+ # Trigger a rescan operation when used with a UtxoStore.
219
+ # bitcoin_node rescan
220
+ def handle_rescan
221
+ EM.defer { @node.store.rescan }
222
+ {:state => "Rescanning ..."}
223
+ end
224
+
225
+ # Get Time Since Last Block.
226
+ # bitcoin_node tslb
227
+ def handle_tslb
228
+ { tslb: (Time.now - @node.last_block_time).to_i }
229
+ end
230
+
231
+ # Create a transaction, collecting outputs from given +keys+, spending to +recipients+
232
+ # with an optional +fee+.
233
+ # Keys is an array that can contain either privkeys, pubkeys or addresses.
234
+ # When a privkey is given, the corresponding inputs are signed. If not, the
235
+ # signature_hash is computed and passed along with the response.
236
+ # After creating an unsigned transaction, one just needs to sign the sig_hashes
237
+ # and send everything to #assemble_tx, to receive the complete transaction that
238
+ # can be relayed to the network.
239
+ def handle_create_tx keys, recipients, fee = 0
240
+ keystore = Bitcoin::Wallet::SimpleKeyStore.new(file: StringIO.new("[]"))
241
+ keys.each do |k|
242
+ begin
243
+ key = Bitcoin::Key.from_base58(k)
244
+ key = { addr: key.addr, key: key }
245
+ rescue
246
+ if Bitcoin.valid_address?(k)
247
+ key = { addr: k }
248
+ else
249
+ begin
250
+ key = Bitcoin::Key.new(nil, k)
251
+ key = { addr: key.addr, key: key }
252
+ rescue
253
+ return { error: "Input not valid address, pub- or privkey: #{k}" }
254
+ end
255
+ end
256
+ end
257
+ keystore.add_key(key)
258
+ end
259
+ wallet = Bitcoin::Wallet::Wallet.new(@node.store, keystore)
260
+ tx = wallet.new_tx(recipients.map {|r| [:address, r[0], r[1]]}, fee)
261
+ return { error: "Error creating tx." } unless tx
262
+ [ tx.to_payload.hth, tx.in.map {|i| [i.sig_hash.hth, i.sig_address] rescue nil } ]
148
263
  rescue
149
- {:error => $!}
264
+ { error: "Error creating tx: #{$!.message}" }
150
265
  end
151
266
 
152
- # stop bitcoin node
267
+ # Assemble an unsigned transaction from the +tx_hex+ and +sig_pubkeys+.
268
+ # The +tx_hex+ is the regular transaction structure, with empty input scripts
269
+ # (as returned by #create_tx when called without privkeys).
270
+ # +sig_pubkeys+ is an array of [signature, pubkey] pairs used to build the
271
+ # input scripts.
272
+ def handle_assemble_tx tx_hex, sig_pubs
273
+ tx = Bitcoin::P::Tx.new(tx_hex.htb)
274
+ sig_pubs.each.with_index do |sig_pub, idx|
275
+ sig, pub = *sig_pub.map(&:htb)
276
+ script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, pub)
277
+ tx.in[idx].script_sig_length = script_sig.bytesize
278
+ tx.in[idx].script_sig = script_sig
279
+ end
280
+ tx = Bitcoin::P::Tx.new(tx.to_payload)
281
+ tx.validator(@node.store).validate(raise_errors: true)
282
+ tx.to_payload.hth
283
+ rescue
284
+ { error: "Error assembling tx: #{$!.message}" }
285
+ end
286
+
287
+ # Relay given transaction (in hex).
288
+ # bitcoin_node relay_tx <tx in hex>
289
+ def handle_relay_tx hex, send = 3, wait = 3
290
+ begin
291
+ tx = Bitcoin::P::Tx.new(hex.htb)
292
+ rescue
293
+ return respond("relay_tx", { error: "Error decoding transaction." })
294
+ end
295
+
296
+ validator = tx.validator(@node.store)
297
+ unless validator.validate(rules: [:syntax])
298
+ return respond("relay_tx", { error: "Transaction syntax invalid.",
299
+ details: validator.error })
300
+ end
301
+ unless validator.validate(rules: [:context])
302
+ return respond("relay_tx", { error: "Transaction context invalid.",
303
+ details: validator.error })
304
+ end
305
+
306
+ #@node.store.store_tx(tx)
307
+ @node.relay_tx[tx.hash] = tx
308
+ @node.relay_propagation[tx.hash] = 0
309
+ @node.connections.select(&:connected?).sample(send).each {|c| c.send_inv(:tx, tx.hash) }
310
+
311
+ EM.add_timer(wait) do
312
+ received = @node.relay_propagation[tx.hash]
313
+ total = @node.connections.select(&:connected?).size - send
314
+ percent = 100.0 / total * received
315
+ respond("relay_tx", { success: true, hash: tx.hash, propagation: {
316
+ received: received, sent: 1, percent: percent } })
317
+ end
318
+ rescue
319
+ respond("relay_tx", { error: $!.message, backtrace: $@ })
320
+ end
321
+
322
+ # Stop the bitcoin node.
153
323
  # bitcoin_node stop
154
324
  def handle_stop
155
325
  Thread.start { sleep 0.1; @node.stop }
156
326
  {:state => "Stopping..."}
157
327
  end
158
328
 
159
- # list all commands
329
+ # List all available commands.
160
330
  # bitcoin_node help
161
331
  def handle_help
162
332
  self.methods.grep(/^handle_(.*?)/).map {|m| m.to_s.sub(/^(.*?)_/, '')}
163
333
  end
164
334
 
335
+ # Validate and store given block (in hex) as if it was received by a peer.
336
+ # bitcoin_node store_block <block in hex>
337
+ def handle_store_block hex
338
+ block = Bitcoin::P::Block.new(hex.htb)
339
+ @node.queue << [:block, block]
340
+ { queued: [ :block, block.hash ] }
341
+ end
342
+
343
+ # Store given transaction (in hex) as if it was received by a peer.
344
+ # bitcoin_node store_tx <tx in hex>
345
+ def handle_store_tx hex
346
+ tx = Bitcoin::P::Tx.new(hex.htb)
347
+ @node.queue << [:tx, tx]
348
+ { queued: [ :tx, tx.hash ] }
349
+ end
350
+
165
351
  # format node uptime
166
352
  def format_uptime t
167
353
  mm, ss = t.divmod(60) #=> [4515, 21]
@@ -1,18 +1,24 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  require 'eventmachine'
2
4
 
3
5
  module Bitcoin::Network
4
6
 
5
7
  # Node network connection to a peer. Handles all the communication with a specific peer.
6
- # TODO: incoming/outgoing?
7
8
  class ConnectionHandler < EM::Connection
8
9
 
10
+ LATENCY_MAX = (5*60*1000) # 5min in ms
11
+
9
12
  include Bitcoin
10
13
  include Bitcoin::Storage
11
14
 
12
- attr_reader :host, :port, :state, :version
15
+ attr_reader :host, :port, :version, :direction
13
16
 
14
- def hth(h); h.unpack("H*")[0]; end
15
- def htb(h); [h].pack("H*"); end
17
+ # :new, :handshake, :connected, :disconnected
18
+ attr_reader :state
19
+
20
+ # latency of this connection based on last ping/pong
21
+ attr_reader :latency_ms
16
22
 
17
23
  def log
18
24
  @log ||= Logger::LogWrapper.new("#@host:#@port", @node.log)
@@ -24,12 +30,17 @@ module Bitcoin::Network
24
30
  end
25
31
 
26
32
  # create connection to +host+:+port+ for given +node+
27
- def initialize node, host, port
28
- @node, @host, @port = node, host, port
33
+ def initialize node, host, port, direction
34
+ @node, @host, @port, @direction = node, host, port, direction
29
35
  @parser = Bitcoin::Protocol::Parser.new(self)
30
36
  @state = :new
31
37
  @version = nil
32
38
  @started = nil
39
+ @port, @host = *Socket.unpack_sockaddr_in(get_peername) if get_peername
40
+ @ping_nonce = nil
41
+ @latency_ms = nil
42
+ @lock = Monitor.new
43
+ @last_getblocks = [] # the last few getblocks messages received
33
44
  rescue Exception
34
45
  log.fatal { "Error in #initialize" }
35
46
  p $!; puts $@; exit
@@ -37,36 +48,73 @@ module Bitcoin::Network
37
48
 
38
49
  # check if connection is wanted, begin handshake if it is, disconnect if not
39
50
  def post_init
40
- if @node.connections.size >= @node.config[:max][:connections]
41
- return close_connection unless @node.config[:connect].include?([@host, @port.to_s])
51
+ if incoming?
52
+ begin_handshake
42
53
  end
43
- log.info { "Connected to #{@host}:#{@port}" }
44
- @state = :established
45
- @node.connections << self
46
- on_handshake_begin
47
54
  rescue Exception
48
55
  log.fatal { "Error in #post_init" }
49
- p $!; puts $@; exit
56
+ p $!; puts *$@
57
+ end
58
+
59
+ # only called for outgoing connection
60
+ def connection_completed
61
+ begin_handshake
62
+ rescue Exception
63
+ log.fatal { "Error in #connection_completed" }
64
+ p $!; puts *$@
50
65
  end
51
66
 
52
67
  # receive data from peer and invoke Protocol::Parser
53
68
  def receive_data data
54
69
  #log.debug { "Receiving data (#{data.size} bytes)" }
55
- @parser.parse(data)
70
+ @lock.synchronize { @parser.parse(data) }
71
+ rescue
72
+ log.warn { "Error handling data: #{data.hth}" }
73
+ p $!; puts *$@
56
74
  end
57
75
 
58
76
  # connection closed; notify listeners and cleanup connection from node
59
77
  def unbind
60
- log.info { "Disconnected #{@host}:#{@port}" }
61
- @node.notifiers[:connection].push([:disconnected, [@host, @port]])
78
+ log.info { "Disconnected" }
79
+ @node.push_notification(:connection, [:disconnected, [@host, @port]])
62
80
  @state = :disconnected
63
81
  @node.connections.delete(self)
64
82
  end
65
83
 
84
+ # begin handshake
85
+ # TODO: disconnect if we don't complete within a reasonable time
86
+ def begin_handshake
87
+ if incoming? && !@node.accept_connections?
88
+ return close_connection unless @node.config[:connect].include?([@host, @port.to_s])
89
+ end
90
+ log.info { "Established #{@direction} connection" }
91
+ @node.connections << self
92
+ @state = :handshake
93
+ # incoming connections wait to receive a version
94
+ send_version if outgoing?
95
+ rescue Exception
96
+ log.fatal { "Error in #begin_handshake" }
97
+ p $!; puts *$@
98
+ end
99
+
100
+ # complete handshake; set state, started time, notify listeners and add address to Node
101
+ def complete_handshake
102
+ if @state == :handshake
103
+ log.debug { 'Handshake completed' }
104
+ @state = :connected
105
+ @started = Time.now
106
+ @node.push_notification(:connection, [:connected, info])
107
+ @node.addrs << addr
108
+ end
109
+ end
110
+
66
111
  # received +inv_tx+ message for given +hash+.
67
112
  # add to inv_queue, unlesss maximum is reached
68
113
  def on_inv_transaction(hash)
69
- log.debug { ">> inv transaction: #{hth(hash)}" }
114
+ log.debug { ">> inv transaction: #{hash.hth}" }
115
+ if @node.relay_propagation.keys.include?(hash.hth)
116
+ @node.relay_propagation[hash.hth] += 1
117
+ end
70
118
  return if @node.inv_queue.size >= @node.config[:max][:inv]
71
119
  @node.queue_inv([:tx, hash, self])
72
120
  end
@@ -74,7 +122,7 @@ module Bitcoin::Network
74
122
  # received +inv_block+ message for given +hash+.
75
123
  # add to inv_queue, unless maximum is reached
76
124
  def on_inv_block(hash)
77
- log.debug { ">> inv block: #{hth(hash)}" }
125
+ log.debug { ">> inv block: #{hash.hth}" }
78
126
  return if @node.inv_queue.size >= @node.config[:max][:inv]
79
127
  @node.queue_inv([:block, hash, self])
80
128
  end
@@ -82,8 +130,9 @@ module Bitcoin::Network
82
130
  # received +get_tx+ message for given +hash+.
83
131
  # send specified tx if we have it
84
132
  def on_get_transaction(hash)
85
- log.debug { ">> get transaction: #{hash.unpack("H*")[0]}" }
86
- tx = @node.store.get_tx(hash.unpack("H*")[0])
133
+ log.debug { ">> get transaction: #{hash.hth}" }
134
+ tx = @node.store.get_tx(hash.hth)
135
+ tx ||= @node.relay_tx[hash.hth]
87
136
  return unless tx
88
137
  pkt = Bitcoin::Protocol.pkt("tx", tx.to_payload)
89
138
  log.debug { "<< tx: #{tx.hash}" }
@@ -92,16 +141,13 @@ module Bitcoin::Network
92
141
 
93
142
  # received +get_block+ message for given +hash+.
94
143
  # send specified block if we have it
95
- # TODO
96
144
  def on_get_block(hash)
97
- log.debug { ">> get block: #{hth(hash)}" }
98
- end
99
-
100
- # send +inv+ message with given +type+ for given +obj+
101
- def send_inv type, obj
102
- pkt = Protocol.inv_pkt(type, [[obj.hash].pack("H*")])
103
- log.debug { "<< inv #{type}: #{obj.hash}" }
104
- send_data(pkt)
145
+ log.debug { ">> get block: #{hash.hth}" }
146
+ blk = @node.store.get_block(hash.hth)
147
+ return unless blk
148
+ pkt = Bitcoin::Protocol.pkt("block", blk.to_payload)
149
+ log.debug { "<< block: #{blk.hash}" }
150
+ send_data pkt
105
151
  end
106
152
 
107
153
  # received +addr+ message for given +addr+.
@@ -109,7 +155,7 @@ module Bitcoin::Network
109
155
  def on_addr(addr)
110
156
  log.debug { ">> addr: #{addr.ip}:#{addr.port} alive: #{addr.alive?}, service: #{addr.service}" }
111
157
  @node.addrs << addr
112
- @node.notifiers[:addr].push(addr)
158
+ @node.push_notification(:addr, addr)
113
159
  end
114
160
 
115
161
  # received +tx+ message for given +tx+.
@@ -136,18 +182,23 @@ module Bitcoin::Network
136
182
  # received +version+ message for given +version+.
137
183
  # send +verack+ message and complete handshake
138
184
  def on_version(version)
139
- log.info { ">> version: #{version.version}" }
185
+ log.debug { ">> version: #{version.version}" }
186
+ @node.external_ips << version.to.split(":")[0]
140
187
  @version = version
141
- log.info { "<< verack" }
188
+ log.debug { "<< verack" }
142
189
  send_data( Protocol.verack_pkt )
143
- on_handshake_complete
190
+
191
+ # sometimes other nodes don't bother to send a verack back,
192
+ # but we can consider the handshake complete once we sent ours.
193
+ # apparently it can happen on incoming and outgoing connections alike
194
+ complete_handshake
144
195
  end
145
196
 
146
197
  # received +verack+ message.
147
198
  # complete handshake if it isn't completed already
148
199
  def on_verack
149
- log.info { ">> verack" }
150
- on_handshake_complete if handshake?
200
+ log.debug { ">> verack" }
201
+ complete_handshake if outgoing?
151
202
  end
152
203
 
153
204
  # received +alert+ message for given +alert+.
@@ -156,25 +207,80 @@ module Bitcoin::Network
156
207
  log.warn { ">> alert: #{alert.inspect}" }
157
208
  end
158
209
 
210
+ # received +getblocks+ message.
211
+ # TODO: locator fallback
212
+ def on_getblocks(version, hashes, stop_hash)
213
+ # remember the last few received getblocks messages and ignore duplicate ones
214
+ # fixes unexplained issue where remote node is bombarding us with the same getblocks
215
+ # message over and over (probably related to missing locator fallback handling)
216
+ return if @last_getblocks && @last_getblocks.include?([version, hashes, stop_hash])
217
+ @last_getblocks << [version, hashes, stop_hash]
218
+ @last_getblocks.shift if @last_getblocks.size > 3
219
+
220
+ blk = @node.store.db[:blk][hash: hashes[0].htb.blob]
221
+ depth = blk[:depth] if blk
222
+ log.info { ">> getblocks #{hashes[0]} (#{depth || 'unknown'})" }
223
+
224
+ return unless depth && depth <= @node.store.get_depth
225
+ range = (depth+1..depth+500)
226
+ blocks = @node.store.db[:blk].where(chain: 0, depth: range).select(:hash).all +
227
+ [@node.store.db[:blk].select(:hash)[chain: 0, depth: depth+502]]
228
+ send_inv(:block, *blocks.map {|b| b[:hash].hth })
229
+ end
230
+
231
+ # received +getaddr+ message.
232
+ # send +addr+ message with peer addresses back.
233
+ def on_getaddr
234
+ addrs = @node.addrs.select{|a| a.time > Time.now.to_i - 10800 }.shuffle[0..250]
235
+ log.debug { "<< addr (#{addrs.size})" }
236
+ send_data P::Addr.pkt(*addrs)
237
+ end
238
+
239
+ # begin handshake; send +version+ message
240
+ def send_version
241
+ from = "#{@node.external_ip}:#{@node.config[:listen][1]}"
242
+ version = Bitcoin::Protocol::Version.new({
243
+ :version => 70001,
244
+ :last_block => @node.store.get_depth,
245
+ :from => from,
246
+ :to => @host,
247
+ :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
248
+ #:user_agent => "/Satoshi:0.8.3/",
249
+ })
250
+ send_data(version.to_pkt)
251
+ log.debug { "<< version: #{Bitcoin.network[:protocol_version]}" }
252
+ end
253
+
254
+ # send +inv+ message with given +type+ for given +obj+
255
+ def send_inv type, *hashes
256
+ hashes.each_slice(251) do |slice|
257
+ pkt = Protocol.inv_pkt(type, slice.map(&:htb))
258
+ log.debug { "<< inv #{type}: #{slice[0][0..16]}" + (slice.size > 1 ? "..#{slice[-1][0..16]}" : "") }
259
+ send_data(pkt)
260
+ end
261
+ end
262
+
159
263
  # send +getdata tx+ message for given tx +hash+
160
264
  def send_getdata_tx(hash)
161
265
  pkt = Protocol.getdata_pkt(:tx, [hash])
162
- log.debug { "<< getdata tx: #{hth(hash)}" }
266
+ log.debug { "<< getdata tx: #{hash.hth}" }
163
267
  send_data(pkt)
164
268
  end
165
269
 
166
270
  # send +getdata block+ message for given block +hash+
167
271
  def send_getdata_block(hash)
168
272
  pkt = Protocol.getdata_pkt(:block, [hash])
169
- log.debug { "<< getdata block: #{hth(hash)}" }
273
+ log.debug { "<< getdata block: #{hash.hth}" }
170
274
  send_data(pkt)
171
275
  end
172
276
 
173
277
  # send +getblocks+ message
174
278
  def send_getblocks locator = @node.store.get_locator
175
- return get_genesis_block if @node.store.get_depth == -1
176
- pkt = Protocol.pkt("getblocks", [Bitcoin::network[:magic_head],
177
- locator.size.chr, *locator.map{|l| htb(l).reverse}, "\x00"*32].join)
279
+ if @node.store.get_depth == -1
280
+ EM.add_timer(3) { send_getblocks }
281
+ return get_genesis_block
282
+ end
283
+ pkt = Protocol.getblocks_pkt(@version.version, locator)
178
284
  log.info { "<< getblocks: #{locator.first}" }
179
285
  send_data(pkt)
180
286
  end
@@ -183,7 +289,7 @@ module Bitcoin::Network
183
289
  def send_getheaders locator = @node.store.get_locator
184
290
  return get_genesis_block if @node.store.get_depth == -1
185
291
  pkt = Protocol.pkt("getheaders", [Bitcoin::network[:magic_head],
186
- locator.size.chr, *locator.map{|l| htb(l).reverse}, "\x00"*32].join)
292
+ locator.size.chr, *locator.map{|l| l.htb_reverse}, "\x00"*32].join)
187
293
  log.debug { "<< getheaders: #{locator.first}" }
188
294
  send_data(pkt)
189
295
  end
@@ -197,30 +303,27 @@ module Bitcoin::Network
197
303
  # send +ping+ message
198
304
  # TODO: wait for pong and disconnect if it doesn't arrive (and version is new enough)
199
305
  def send_ping
200
- nonce = rand(0xffffffff)
201
- log.debug { "<< ping (#{nonce})" }
202
- send_data(Protocol.ping_pkt(nonce))
306
+ if @version.version > Bitcoin::Protocol::BIP0031_VERSION
307
+ @latency_ms = LATENCY_MAX
308
+ @ping_nonce = rand(0xffffffff)
309
+ @ping_time = Time.now
310
+ log.debug { "<< ping (#{@ping_nonce})" }
311
+ send_data(Protocol.ping_pkt(@ping_nonce))
312
+ else
313
+ # set latency to 5 seconds, terrible but this version should be obsolete now
314
+ @latency_ms = (5*1000)
315
+ log.debug { "<< ping" }
316
+ send_data(Protocol.ping_pkt)
317
+ end
203
318
  end
204
319
 
205
320
  # ask for the genesis block
206
321
  def get_genesis_block
207
322
  log.info { "Asking for genesis block" }
208
- pkt = Protocol.getdata_pkt(:block, [htb(Bitcoin::network[:genesis_hash])])
323
+ pkt = Protocol.getdata_pkt(:block, [Bitcoin::network[:genesis_hash].htb])
209
324
  send_data(pkt)
210
325
  end
211
326
 
212
- # complete handshake; set state, started time, notify listeners and add address to Node
213
- def on_handshake_complete
214
- return unless handshake?
215
- log.debug { "handshake complete" }
216
- @state = :connected
217
- @started = Time.now
218
- @node.notifiers[:connection].push([:connected, info])
219
- @node.addrs << addr
220
- # send_getaddr
221
- # EM.add_periodic_timer(15) { send_ping }
222
- end
223
-
224
327
  # received +ping+ message with given +nonce+.
225
328
  # send +pong+ message back, if +nonce+ is set.
226
329
  # network versions <=60000 don't set the nonce and don't expect a pong.
@@ -230,28 +333,33 @@ module Bitcoin::Network
230
333
  end
231
334
 
232
335
  # received +pong+ message with given +nonce+.
233
- # TODO: see #send_ping
234
336
  def on_pong nonce
235
- log.debug { ">> pong (#{nonce})" }
337
+ if @ping_nonce == nonce
338
+ @latency_ms = (Time.now - @ping_time) * 1000.0
339
+ end
340
+ log.debug { ">> pong (#{nonce}), latency: #{@latency_ms.to_i}ms" }
236
341
  end
237
342
 
238
343
  # begin handshake; send +version+ message
239
344
  def on_handshake_begin
240
345
  @state = :handshake
241
- block = @node.store.get_depth
242
- from = "127.0.0.1:8333"
243
- from_id = Bitcoin::Protocol::Uniq
244
- to = @node.config[:listen].join(':')
245
-
246
- pkt = Protocol.version_pkt(from_id, from, to, block)
247
- log.info { "<< version (#{Bitcoin::Protocol::VERSION})" }
248
- send_data(pkt)
346
+ from = "#{@node.external_ip}:#{@node.config[:listen][1]}"
347
+ version = Bitcoin::Protocol::Version.new({
348
+ :version => 70001,
349
+ :last_block => @node.store.get_depth,
350
+ :from => from,
351
+ :to => @host,
352
+ :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
353
+ #:user_agent => "/Satoshi:0.8.1/",
354
+ })
355
+ send_data(version.to_pkt)
356
+ log.debug { "<< version (#{Bitcoin.network[:protocol_version]})" }
249
357
  end
250
358
 
251
359
  # get Addr object for this connection
252
360
  def addr
253
361
  return @addr if @addr
254
- @addr = Bitcoin::Protocol::Addr.new
362
+ @addr = P::Addr.new
255
363
  @addr.time, @addr.service, @addr.ip, @addr.port =
256
364
  Time.now.tv_sec, @version.services, @host, @port
257
365
  @addr
@@ -265,10 +373,14 @@ module Bitcoin::Network
265
373
  def info
266
374
  {
267
375
  :host => @host, :port => @port, :state => @state,
268
- :version => @version.version, :block => @version.block, :started => @started.to_i,
269
- :user_agent => @version.user_agent
376
+ :version => (@version.version rescue 0), :block => @version.last_block,
377
+ :started => @started.to_i, :user_agent => @version.user_agent
270
378
  }
271
379
  end
380
+
381
+ def incoming?; @direction == :in; end
382
+ def outgoing?; @direction == :out; end
383
+
272
384
  end
273
385
 
274
386
  end