Class: Bitcoin::Network::ConnectionHandler
- Inherits:
-
EM::Connection
- Object
- EM::Connection
- Bitcoin::Network::ConnectionHandler
- Defined in:
- lib/bitcoin/network/connection_handler.rb
Overview
Node network connection to a peer. Handles all the communication with a specific peer.
Constant Summary collapse
- LATENCY_MAX =
5min in ms
(5*60*1000)
Constants included from Storage
Constants included from Bitcoin
CENT, COIN, MAX_BLOCK_SIGOPS, MAX_BLOCK_SIZE, MAX_BLOCK_SIZE_GEN, MAX_ORPHAN_TRANSACTIONS, MIN_FEE_MODE, Bitcoin::NETWORKS, P, VERSION
Constants included from Util
Instance Attribute Summary collapse
-
#direction ⇒ Object
readonly
Returns the value of attribute direction.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#latency_ms ⇒ Object
readonly
latency of this connection based on last ping/pong.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#state ⇒ Object
readonly
:new, :handshake, :connected, :disconnected.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Instance Method Summary collapse
-
#addr ⇒ Object
get Addr object for this connection.
-
#begin_handshake ⇒ Object
begin handshake TODO: disconnect if we don’t complete within a reasonable time.
-
#complete_handshake ⇒ Object
complete handshake; set state, started time, notify listeners and add address to Node.
-
#connection_completed ⇒ Object
only called for outgoing connection.
-
#get_genesis_block ⇒ Object
ask for the genesis block.
- #incoming? ⇒ Boolean
-
#info ⇒ Object
get info hash.
-
#initialize(node, host, port, direction) ⇒ ConnectionHandler
constructor
create connection to
host
:port
for givennode
. - #log ⇒ Object
-
#on_addr(addr) ⇒ Object
received
addr
message for givenaddr
. -
#on_alert(alert) ⇒ Object
received
alert
message for givenalert
. -
#on_block(blk) ⇒ Object
received
block
message for givenblk
. -
#on_get_block(hash) ⇒ Object
received
get_block
message for givenhash
. -
#on_get_transaction(hash) ⇒ Object
received
get_tx
message for givenhash
. -
#on_getaddr ⇒ Object
received
getaddr
message. -
#on_getblocks(version, hashes, stop_hash) ⇒ Object
received
getblocks
message. -
#on_handshake_begin ⇒ Object
begin handshake; send
version
message. -
#on_headers(headers) ⇒ Object
received
headers
message for givenheaders
. -
#on_inv_block(hash) ⇒ Object
received
inv_block
message for givenhash
. -
#on_inv_transaction(hash) ⇒ Object
received
inv_tx
message for givenhash
. -
#on_ping(nonce) ⇒ Object
received
ping
message with givennonce
. -
#on_pong(nonce) ⇒ Object
received
pong
message with givennonce
. -
#on_tx(tx) ⇒ Object
received
tx
message for giventx
. -
#on_verack ⇒ Object
received
verack
message. -
#on_version(version) ⇒ Object
received
version
message for givenversion
. - #outgoing? ⇒ Boolean
-
#post_init ⇒ Object
check if connection is wanted, begin handshake if it is, disconnect if not.
-
#receive_data(data) ⇒ Object
receive data from peer and invoke Protocol::Parser.
-
#send_getaddr ⇒ Object
send
getaddr
message. -
#send_getblocks(locator = @node.store.get_locator) ⇒ Object
send
getblocks
message. -
#send_getdata_block(hash) ⇒ Object
send getdata block message for given block
hash
. -
#send_getdata_tx(hash) ⇒ Object
send getdata tx message for given tx
hash
. -
#send_getheaders(locator = @node.store.get_locator) ⇒ Object
send
getheaders
message. -
#send_inv(type, *hashes) ⇒ Object
send
inv
message with giventype
for givenobj
. -
#send_ping ⇒ Object
send
ping
message TODO: wait for pong and disconnect if it doesn’t arrive (and version is new enough). -
#send_version ⇒ Object
begin handshake; send
version
message. -
#unbind ⇒ Object
connection closed; notify listeners and cleanup connection from node.
-
#uptime ⇒ Object
how long has this connection been open?.
Methods included from Storage
Methods included from Bitcoin
network, network=, network_name, network_project, require_dependency
Methods included from Util
#address_type, #address_version, #base58_checksum?, #base58_to_int, #bitcoin_byte_hash, #bitcoin_elliptic_curve, #bitcoin_hash, #bitcoin_mrkl, #bitcoin_signed_message_hash, #block_average_hashing_time, #block_average_mining_time, #block_creation_reward, #block_difficulty, #block_hash, #block_hashes_to_win, #block_next_retarget, #block_probability, #blockchain_total_btc, #checksum, #decode_base58, #decode_compact_bits, #decode_target, #encode_address, #encode_base58, #encode_compact_bits, #generate_address, #generate_key, #hash160, #hash160_from_address, #hash160_to_address, #hash160_to_p2sh_address, #hash_mrkl_branch, #hash_mrkl_tree, #inspect_key, #int_to_base58, #mrkl_branch_root, #open_key, #p2sh_version, #pubkey_to_address, #regenerate_public_key, #sha256, #sign_data, #sign_message, #valid_address?, #verify_message, #verify_signature
Constructor Details
#initialize(node, host, port, direction) ⇒ ConnectionHandler
create connection to host
:port
for given node
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/bitcoin/network/connection_handler.rb', line 33 def initialize node, host, port, direction @node, @host, @port, @direction = node, host, port, direction @parser = Bitcoin::Protocol::Parser.new(self) @state = :new @version = nil @started = nil @port, @host = *Socket.unpack_sockaddr_in(get_peername) if get_peername @ping_nonce = nil @latency_ms = nil @lock = Monitor.new @last_getblocks = [] # the last few getblocks messages received rescue Exception log.fatal { "Error in #initialize" } p $!; puts $@; exit end |
Instance Attribute Details
#direction ⇒ Object (readonly)
Returns the value of attribute direction.
15 16 17 |
# File 'lib/bitcoin/network/connection_handler.rb', line 15 def direction @direction end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
15 16 17 |
# File 'lib/bitcoin/network/connection_handler.rb', line 15 def host @host end |
#latency_ms ⇒ Object (readonly)
latency of this connection based on last ping/pong
21 22 23 |
# File 'lib/bitcoin/network/connection_handler.rb', line 21 def latency_ms @latency_ms end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
15 16 17 |
# File 'lib/bitcoin/network/connection_handler.rb', line 15 def port @port end |
#state ⇒ Object (readonly)
:new, :handshake, :connected, :disconnected
18 19 20 |
# File 'lib/bitcoin/network/connection_handler.rb', line 18 def state @state end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
15 16 17 |
# File 'lib/bitcoin/network/connection_handler.rb', line 15 def version @version end |
Instance Method Details
#addr ⇒ Object
get Addr object for this connection
360 361 362 363 364 365 366 |
# File 'lib/bitcoin/network/connection_handler.rb', line 360 def addr return @addr if @addr @addr = P::Addr.new @addr.time, @addr.service, @addr.ip, @addr.port = Time.now.tv_sec, @version.services, @host, @port @addr end |
#begin_handshake ⇒ Object
begin handshake TODO: disconnect if we don’t complete within a reasonable time
86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/bitcoin/network/connection_handler.rb', line 86 def begin_handshake if incoming? && !@node.accept_connections? return close_connection unless @node.config[:connect].include?([@host, @port.to_s]) end log.info { "Established #{@direction} connection" } @node.connections << self @state = :handshake # incoming connections wait to receive a version send_version if outgoing? rescue Exception log.fatal { "Error in #begin_handshake" } p $!; puts *$@ end |
#complete_handshake ⇒ Object
complete handshake; set state, started time, notify listeners and add address to Node
101 102 103 104 105 106 107 108 109 |
# File 'lib/bitcoin/network/connection_handler.rb', line 101 def complete_handshake if @state == :handshake log.debug { 'Handshake completed' } @state = :connected @started = Time.now @node.push_notification(:connection, [:connected, info]) @node.addrs << addr end end |
#connection_completed ⇒ Object
only called for outgoing connection
60 61 62 63 64 65 |
# File 'lib/bitcoin/network/connection_handler.rb', line 60 def connection_completed begin_handshake rescue Exception log.fatal { "Error in #connection_completed" } p $!; puts *$@ end |
#get_genesis_block ⇒ Object
ask for the genesis block
321 322 323 324 325 |
# File 'lib/bitcoin/network/connection_handler.rb', line 321 def get_genesis_block log.info { "Asking for genesis block" } pkt = Protocol.getdata_pkt(:block, [Bitcoin::network[:genesis_hash].htb]) send_data(pkt) end |
#incoming? ⇒ Boolean
381 |
# File 'lib/bitcoin/network/connection_handler.rb', line 381 def incoming?; @direction == :in; end |
#info ⇒ Object
get info hash
373 374 375 376 377 378 379 |
# File 'lib/bitcoin/network/connection_handler.rb', line 373 def info { :host => @host, :port => @port, :state => @state, :version => (@version.version rescue 0), :block => @version.last_block, :started => @started.to_i, :user_agent => @version.user_agent } end |
#log ⇒ Object
23 24 25 |
# File 'lib/bitcoin/network/connection_handler.rb', line 23 def log @log ||= Logger::LogWrapper.new("#@host:#@port", @node.log) end |
#on_addr(addr) ⇒ Object
received addr
message for given addr
. store addr in node and notify listeners
155 156 157 158 159 |
# File 'lib/bitcoin/network/connection_handler.rb', line 155 def on_addr(addr) log.debug { ">> addr: #{addr.ip}:#{addr.port} alive: #{addr.alive?}, service: #{addr.service}" } @node.addrs << addr @node.push_notification(:addr, addr) end |
#on_alert(alert) ⇒ Object
received alert
message for given alert
. TODO: implement alert logic, store, display, relay
206 207 208 |
# File 'lib/bitcoin/network/connection_handler.rb', line 206 def on_alert(alert) log.warn { ">> alert: #{alert.inspect}" } end |
#on_block(blk) ⇒ Object
received block
message for given blk
. push block to storage queue
170 171 172 173 |
# File 'lib/bitcoin/network/connection_handler.rb', line 170 def on_block(blk) log.debug { ">> block: #{blk.hash} (#{blk.payload.size} bytes)" } @node.queue.push([:block, blk]) end |
#on_get_block(hash) ⇒ Object
received get_block
message for given hash
. send specified block if we have it
144 145 146 147 148 149 150 151 |
# File 'lib/bitcoin/network/connection_handler.rb', line 144 def on_get_block(hash) log.debug { ">> get block: #{hash.hth}" } blk = @node.store.get_block(hash.hth) return unless blk pkt = Bitcoin::Protocol.pkt("block", blk.to_payload) log.debug { "<< block: #{blk.hash}" } send_data pkt end |
#on_get_transaction(hash) ⇒ Object
received get_tx
message for given hash
. send specified tx if we have it
132 133 134 135 136 137 138 139 140 |
# File 'lib/bitcoin/network/connection_handler.rb', line 132 def on_get_transaction(hash) log.debug { ">> get transaction: #{hash.hth}" } tx = @node.store.get_tx(hash.hth) tx ||= @node.relay_tx[hash.hth] return unless tx pkt = Bitcoin::Protocol.pkt("tx", tx.to_payload) log.debug { "<< tx: #{tx.hash}" } send_data pkt end |
#on_getaddr ⇒ Object
received getaddr
message. send addr
message with peer addresses back.
233 234 235 236 237 |
# File 'lib/bitcoin/network/connection_handler.rb', line 233 def on_getaddr addrs = @node.addrs.select{|a| a.time > Time.now.to_i - 10800 }.shuffle[0..250] log.debug { "<< addr (#{addrs.size})" } send_data P::Addr.pkt(*addrs) end |
#on_getblocks(version, hashes, stop_hash) ⇒ Object
received getblocks
message. TODO: locator fallback
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/bitcoin/network/connection_handler.rb', line 212 def on_getblocks(version, hashes, stop_hash) # remember the last few received getblocks messages and ignore duplicate ones # fixes unexplained issue where remote node is bombarding us with the same getblocks # message over and over (probably related to missing locator fallback handling) return if @last_getblocks && @last_getblocks.include?([version, hashes, stop_hash]) @last_getblocks << [version, hashes, stop_hash] @last_getblocks.shift if @last_getblocks.size > 3 blk = @node.store.db[:blk][hash: hashes[0].htb.blob] depth = blk[:depth] if blk log.info { ">> getblocks #{hashes[0]} (#{depth || 'unknown'})" } return unless depth && depth <= @node.store.get_depth range = (depth+1..depth+500) blocks = @node.store.db[:blk].where(chain: 0, depth: range).select(:hash).all + [@node.store.db[:blk].select(:hash)[chain: 0, depth: depth+502]] send_inv(:block, *blocks.map {|b| b[:hash].hth }) end |
#on_handshake_begin ⇒ Object
begin handshake; send version
message
344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/bitcoin/network/connection_handler.rb', line 344 def on_handshake_begin @state = :handshake from = "#{@node.external_ip}:#{@node.config[:listen][1]}" version = Bitcoin::Protocol::Version.new({ :version => 70001, :last_block => @node.store.get_depth, :from => from, :to => @host, :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/", #:user_agent => "/Satoshi:0.8.1/", }) send_data(version.to_pkt) log.debug { "<< version (#{Bitcoin.network[:protocol_version]})" } end |
#on_headers(headers) ⇒ Object
received headers
message for given headers
. push each header to storage queue
177 178 179 180 |
# File 'lib/bitcoin/network/connection_handler.rb', line 177 def on_headers(headers) log.info { ">> headers (#{headers.size})" } headers.each {|h| @node.queue.push([:block, h])} end |
#on_inv_block(hash) ⇒ Object
received inv_block
message for given hash
. add to inv_queue, unless maximum is reached
124 125 126 127 128 |
# File 'lib/bitcoin/network/connection_handler.rb', line 124 def on_inv_block(hash) log.debug { ">> inv block: #{hash.hth}" } return if @node.inv_queue.size >= @node.config[:max][:inv] @node.queue_inv([:block, hash, self]) end |
#on_inv_transaction(hash) ⇒ Object
received inv_tx
message for given hash
. add to inv_queue, unlesss maximum is reached
113 114 115 116 117 118 119 120 |
# File 'lib/bitcoin/network/connection_handler.rb', line 113 def on_inv_transaction(hash) log.debug { ">> inv transaction: #{hash.hth}" } if @node.relay_propagation.keys.include?(hash.hth) @node.relay_propagation[hash.hth] += 1 end return if @node.inv_queue.size >= @node.config[:max][:inv] @node.queue_inv([:tx, hash, self]) end |
#on_ping(nonce) ⇒ Object
received ping
message with given nonce
. send pong
message back, if nonce
is set. network versions <=60000 don’t set the nonce and don’t expect a pong.
330 331 332 333 |
# File 'lib/bitcoin/network/connection_handler.rb', line 330 def on_ping nonce log.debug { ">> ping (#{nonce})" } send_data(Protocol.pong_pkt(nonce)) if nonce end |
#on_pong(nonce) ⇒ Object
received pong
message with given nonce
.
336 337 338 339 340 341 |
# File 'lib/bitcoin/network/connection_handler.rb', line 336 def on_pong nonce if @ping_nonce == nonce @latency_ms = (Time.now - @ping_time) * 1000.0 end log.debug { ">> pong (#{nonce}), latency: #{@latency_ms.to_i}ms" } end |
#on_tx(tx) ⇒ Object
received tx
message for given tx
. push tx to storage queue
163 164 165 166 |
# File 'lib/bitcoin/network/connection_handler.rb', line 163 def on_tx(tx) log.debug { ">> tx: #{tx.hash} (#{tx.payload.size} bytes)" } @node.queue.push([:tx, tx]) end |
#on_verack ⇒ Object
received verack
message. complete handshake if it isn’t completed already
199 200 201 202 |
# File 'lib/bitcoin/network/connection_handler.rb', line 199 def on_verack log.debug { ">> verack" } complete_handshake if outgoing? end |
#on_version(version) ⇒ Object
received version
message for given version
. send verack
message and complete handshake
184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/bitcoin/network/connection_handler.rb', line 184 def on_version(version) log.debug { ">> version: #{version.version}" } @node.external_ips << version.to.split(":")[0] @version = version log.debug { "<< verack" } send_data( Protocol.verack_pkt ) # sometimes other nodes don't bother to send a verack back, # but we can consider the handshake complete once we sent ours. # apparently it can happen on incoming and outgoing connections alike complete_handshake end |
#outgoing? ⇒ Boolean
382 |
# File 'lib/bitcoin/network/connection_handler.rb', line 382 def outgoing?; @direction == :out; end |
#post_init ⇒ Object
check if connection is wanted, begin handshake if it is, disconnect if not
50 51 52 53 54 55 56 57 |
# File 'lib/bitcoin/network/connection_handler.rb', line 50 def post_init if incoming? begin_handshake end rescue Exception log.fatal { "Error in #post_init" } p $!; puts *$@ end |
#receive_data(data) ⇒ Object
receive data from peer and invoke Protocol::Parser
68 69 70 71 72 73 74 |
# File 'lib/bitcoin/network/connection_handler.rb', line 68 def receive_data data #log.debug { "Receiving data (#{data.size} bytes)" } @lock.synchronize { @parser.parse(data) } rescue log.warn { "Error handling data: #{data.hth}" } p $!; puts *$@ end |
#send_getaddr ⇒ Object
send getaddr
message
298 299 300 301 |
# File 'lib/bitcoin/network/connection_handler.rb', line 298 def send_getaddr log.debug { "<< getaddr" } send_data(Protocol.pkt("getaddr", "")) end |
#send_getblocks(locator = @node.store.get_locator) ⇒ Object
send getblocks
message
278 279 280 281 282 283 284 285 286 |
# File 'lib/bitcoin/network/connection_handler.rb', line 278 def send_getblocks locator = @node.store.get_locator if @node.store.get_depth == -1 EM.add_timer(3) { send_getblocks } return get_genesis_block end pkt = Protocol.getblocks_pkt(@version.version, locator) log.info { "<< getblocks: #{locator.first}" } send_data(pkt) end |
#send_getdata_block(hash) ⇒ Object
send getdata block message for given block hash
271 272 273 274 275 |
# File 'lib/bitcoin/network/connection_handler.rb', line 271 def send_getdata_block(hash) pkt = Protocol.getdata_pkt(:block, [hash]) log.debug { "<< getdata block: #{hash.hth}" } send_data(pkt) end |
#send_getdata_tx(hash) ⇒ Object
send getdata tx message for given tx hash
264 265 266 267 268 |
# File 'lib/bitcoin/network/connection_handler.rb', line 264 def send_getdata_tx(hash) pkt = Protocol.getdata_pkt(:tx, [hash]) log.debug { "<< getdata tx: #{hash.hth}" } send_data(pkt) end |
#send_getheaders(locator = @node.store.get_locator) ⇒ Object
send getheaders
message
289 290 291 292 293 294 295 |
# File 'lib/bitcoin/network/connection_handler.rb', line 289 def send_getheaders locator = @node.store.get_locator return get_genesis_block if @node.store.get_depth == -1 pkt = Protocol.pkt("getheaders", [Bitcoin::network[:magic_head], locator.size.chr, *locator.map{|l| l.htb_reverse}, "\x00"*32].join) log.debug { "<< getheaders: #{locator.first}" } send_data(pkt) end |
#send_inv(type, *hashes) ⇒ Object
send inv
message with given type
for given obj
255 256 257 258 259 260 261 |
# File 'lib/bitcoin/network/connection_handler.rb', line 255 def send_inv type, *hashes hashes.each_slice(251) do |slice| pkt = Protocol.inv_pkt(type, slice.map(&:htb)) log.debug { "<< inv #{type}: #{slice[0][0..16]}" + (slice.size > 1 ? "..#{slice[-1][0..16]}" : "") } send_data(pkt) end end |
#send_ping ⇒ Object
send ping
message TODO: wait for pong and disconnect if it doesn’t arrive (and version is new enough)
305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/bitcoin/network/connection_handler.rb', line 305 def send_ping if @version.version > Bitcoin::Protocol::BIP0031_VERSION @latency_ms = LATENCY_MAX @ping_nonce = rand(0xffffffff) @ping_time = Time.now log.debug { "<< ping (#{@ping_nonce})" } send_data(Protocol.ping_pkt(@ping_nonce)) else # set latency to 5 seconds, terrible but this version should be obsolete now @latency_ms = (5*1000) log.debug { "<< ping" } send_data(Protocol.ping_pkt) end end |
#send_version ⇒ Object
begin handshake; send version
message
240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/bitcoin/network/connection_handler.rb', line 240 def send_version from = "#{@node.external_ip}:#{@node.config[:listen][1]}" version = Bitcoin::Protocol::Version.new({ :version => 70001, :last_block => @node.store.get_depth, :from => from, :to => @host, :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/", #:user_agent => "/Satoshi:0.8.3/", }) send_data(version.to_pkt) log.debug { "<< version: #{Bitcoin.network[:protocol_version]}" } end |
#unbind ⇒ Object
connection closed; notify listeners and cleanup connection from node
77 78 79 80 81 82 |
# File 'lib/bitcoin/network/connection_handler.rb', line 77 def unbind log.info { "Disconnected" } @node.push_notification(:connection, [:disconnected, [@host, @port]]) @state = :disconnected @node.connections.delete(self) end |
#uptime ⇒ Object
how long has this connection been open?
28 29 30 |
# File 'lib/bitcoin/network/connection_handler.rb', line 28 def uptime @started ? (Time.now - @started).to_i : 0 end |