Class: Bitcoin::Network::ConnectionHandler

Inherits:
EM::Connection
  • Object
show all
Includes:
Bitcoin, Storage
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

Storage::BACKENDS

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

Util::RETARGET_INTERVAL

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Storage

log

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

#directionObject (readonly)

Returns the value of attribute direction.



15
16
17
# File 'lib/bitcoin/network/connection_handler.rb', line 15

def direction
  @direction
end

#hostObject (readonly)

Returns the value of attribute host.



15
16
17
# File 'lib/bitcoin/network/connection_handler.rb', line 15

def host
  @host
end

#latency_msObject (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

#portObject (readonly)

Returns the value of attribute port.



15
16
17
# File 'lib/bitcoin/network/connection_handler.rb', line 15

def port
  @port
end

#stateObject (readonly)

:new, :handshake, :connected, :disconnected



18
19
20
# File 'lib/bitcoin/network/connection_handler.rb', line 18

def state
  @state
end

#versionObject (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

#addrObject

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_handshakeObject

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_handshakeObject

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_completedObject

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_blockObject

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

Returns:

  • (Boolean)


381
# File 'lib/bitcoin/network/connection_handler.rb', line 381

def incoming?; @direction == :in; end

#infoObject

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

#logObject



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_getaddrObject

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_beginObject

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_verackObject

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

Returns:

  • (Boolean)


382
# File 'lib/bitcoin/network/connection_handler.rb', line 382

def outgoing?; @direction == :out; end

#post_initObject

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_getaddrObject

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_pingObject

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_versionObject

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

#unbindObject

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

#uptimeObject

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