Class: Bitcoin::Network::CommandHandler

Inherits:
EM::Connection
  • Object
show all
Defined in:
lib/bitcoin/network/command_handler.rb

Overview

Started by the Node, accepts connections from CommandClient and answers requests or registers for events and notifies the clients when they happen.

Instance Method Summary collapse

Constructor Details

#initialize(node) ⇒ CommandHandler

create new CommandHandler



11
12
13
14
15
16
# File 'lib/bitcoin/network/command_handler.rb', line 11

def initialize node
  @node = node
  @node.command_connections << self
  @buf = BufferedTokenizer.new("\x00")
  @lock = Monitor.new
end

Instance Method Details

#format_uptime(t) ⇒ Object

format node uptime



352
353
354
355
356
357
# File 'lib/bitcoin/network/command_handler.rb', line 352

def format_uptime t
  mm, ss = t.divmod(60)            #=> [4515, 21]
  hh, mm = mm.divmod(60)           #=> [75, 15]
  dd, hh = hh.divmod(24)           #=> [3, 3]
  "%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
end

#handle_addrs(count = 32) ⇒ Object

Get known peer addresses (used by bin/bitcoin_dns_seed).

bitcoin_node addrs [count]


210
211
212
213
214
215
216
# File 'lib/bitcoin/network/command_handler.rb', line 210

def handle_addrs count = 32
  @node.addrs.weighted_sample(count.to_i) do |addr|
    Time.now.tv_sec + 7200 - addr.time
  end.map do |addr|
    [addr.ip, addr.port, Time.now.tv_sec - addr.time] rescue nil
  end.compact
end

#handle_assemble_tx(tx_hex, sig_pubs) ⇒ Object

Assemble an unsigned transaction from the tx_hex and sig_pubkeys. The tx_hex is the regular transaction structure, with empty input scripts (as returned by #create_tx when called without privkeys). sig_pubkeys is an array of [signature, pubkey] pairs used to build the input scripts.



272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/bitcoin/network/command_handler.rb', line 272

def handle_assemble_tx tx_hex, sig_pubs
  tx = Bitcoin::P::Tx.new(tx_hex.htb)
  sig_pubs.each.with_index do |sig_pub, idx|
    sig, pub = *sig_pub.map(&:htb)
    script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, pub)
    tx.in[idx].script_sig_length = script_sig.bytesize
    tx.in[idx].script_sig = script_sig
  end
  tx = Bitcoin::P::Tx.new(tx.to_payload)
  tx.validator(@node.store).validate(raise_errors: true)
  tx.to_payload.hth
rescue
  { error: "Error assembling tx: #{$!.message}" }
end

#handle_configObject

Get the currently active configuration.

bitcoin_node config


161
162
163
# File 'lib/bitcoin/network/command_handler.rb', line 161

def handle_config
  @node.config
end

#handle_connect(*args) ⇒ Object

Connect to given peer(s).

bitcoin_node connect <ip>:<port>[,<ip>:<port>]


178
179
180
181
# File 'lib/bitcoin/network/command_handler.rb', line 178

def handle_connect *args
  args.each {|a| @node.connect_peer(*a.split(':')) }
  {:state => "Connecting..."}
end

#handle_connectionsObject

Get currently connected peers.

bitcoin_node connections


167
168
169
170
171
172
173
174
# File 'lib/bitcoin/network/command_handler.rb', line 167

def handle_connections
  @node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
    "#{c.host.rjust(15)}:#{c.port} [#{c.direction}, state: #{c.state}, " +
    "version: #{c.version.version rescue '?'}, " +
    "block: #{c.version.block rescue '?'}, " +
    "uptime: #{format_uptime(c.uptime) rescue 0}, " +
    "client: #{c.version.user_agent rescue '?'}]" }
end

#handle_create_tx(keys, recipients, fee = 0) ⇒ Object

Create a transaction, collecting outputs from given keys, spending to recipients with an optional fee. Keys is an array that can contain either privkeys, pubkeys or addresses. When a privkey is given, the corresponding inputs are signed. If not, the signature_hash is computed and passed along with the response. After creating an unsigned transaction, one just needs to sign the sig_hashes and send everything to #assemble_tx, to receive the complete transaction that can be relayed to the network.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/bitcoin/network/command_handler.rb', line 239

def handle_create_tx keys, recipients, fee = 0
  keystore = Bitcoin::Wallet::SimpleKeyStore.new(file: StringIO.new("[]"))
  keys.each do |k|
    begin
      key = Bitcoin::Key.from_base58(k)
      key = { addr: key.addr, key: key }
    rescue
      if Bitcoin.valid_address?(k)
        key = { addr: k }
      else
        begin
          key = Bitcoin::Key.new(nil, k)
          key = { addr: key.addr, key: key }
        rescue
          return { error: "Input not valid address, pub- or privkey: #{k}" }
        end
      end
    end
    keystore.add_key(key)
  end
  wallet = Bitcoin::Wallet::Wallet.new(@node.store, keystore)
  tx = wallet.new_tx(recipients.map {|r| [:address, r[0], r[1]]}, fee)
  return { error: "Error creating tx." }  unless tx
  [ tx.to_payload.hth, tx.in.map {|i| [i.sig_hash.hth, i.sig_address] rescue nil } ]
rescue
  { error: "Error creating tx: #{$!.message}" }
end

#handle_disconnect(*args) ⇒ Object

Disconnect given peer(s).

bitcoin_node disconnect <ip>:<port>[,<ip>,<port>]


185
186
187
188
189
190
191
192
# File 'lib/bitcoin/network/command_handler.rb', line 185

def handle_disconnect *args
  args.each do |c|
    host, port = *c.split(":")
    conn = @node.connections.select{|c| c.host == host && c.port == port.to_i}.first
    conn.close_connection  if conn
  end
  {:state => "Disconnected"}
end

#handle_getaddrObject

Trigger the node to ask its for new peer addresses.

bitcoin_node getaddr


203
204
205
206
# File 'lib/bitcoin/network/command_handler.rb', line 203

def handle_getaddr
  @node.connections.sample.send_getaddr
  {:state => "Sending getaddr..."}
end

#handle_getblocksObject

Trigger the node to ask its peers for new blocks.

bitcoin_node getblocks


196
197
198
199
# File 'lib/bitcoin/network/command_handler.rb', line 196

def handle_getblocks
  @node.connections.sample.send_getblocks
  {:state => "Sending getblocks..."}
end

#handle_helpObject

List all available commands.

bitcoin_node help


331
332
333
# File 'lib/bitcoin/network/command_handler.rb', line 331

def handle_help
  self.methods.grep(/^handle_(.*?)/).map {|m| m.to_s.sub(/^(.*?)_/, '')}
end

#handle_infoObject

Get various statistics.

bitcoin_node info


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/bitcoin/network/command_handler.rb', line 140

def handle_info
  blocks = @node.connections.map(&:version).compact.map(&:last_block) rescue nil
  established = @node.connections.select {|c| c.state == :connected }
  info = {
    :blocks => "#{@node.store.get_depth} (#{(blocks.inject{|a,b| a+=b; a } / blocks.size rescue '?' )})#{@node.store.in_sync? ? ' sync' : ''}",
    :addrs => "#{@node.addrs.select{|a| a.alive?}.size} (#{@node.addrs.size})",
    :connections => "#{established.size} established (#{established.select(&:outgoing?).size} out, #{established.select(&:incoming?).size} in), #{@node.connections.size - established.size} connecting",
    :queue => @node.queue.size,
    :inv_queue => @node.inv_queue.size,
    :inv_cache => @node.inv_cache.size,
    :network => @node.config[:network],
    :storage => @node.config[:storage],
    :version => Bitcoin.network[:protocol_version],
    :external_ip => @node.external_ip,
    :uptime => format_uptime(@node.uptime),
  }
  Bitcoin.namecoin? ? {:names => @node.store.db[:names].count}.merge(info) : info
end

#handle_monitor(*channels) ⇒ Object

handle monitor command; subscribe client to specified channels (block, tx, output, connection). Some commands can have parameters, e.g. the number of confirmations tx or output should have. Parameters are appended to the command name after an underscore (_), e.g. subscribe to channel “tx_6” to receive only transactions with 6 confirmations.

Receive new blocks:

bitcoin_node monitor block

Receive new (unconfirmed) transactions:

bitcoin_node monitor tx

Receive transactions with 6 confirmations:

bitcoin_node monitor tx_6

Receive [txhash, address, value] for each output:

bitcoin_node monitor output

Receive peer connections/disconnections:

bitcoin_node monitor connection"

Combine multiple channels:

bitcoin_node monitor "block tx tx_1 tx_6 connection"

NOTE: When a new block is found, it might include transactions that we didn’t previously receive as unconfirmed. To make sure you receive all transactions, also subscribe to the tx_1 channel.



77
78
79
80
81
82
83
84
85
# File 'lib/bitcoin/network/command_handler.rb', line 77

def handle_monitor *channels
  channels.map(&:to_sym).each do |channel|
    @node.subscribe(channel) {|*data| respond("monitor", [channel, *data]) }
    name, *params = channel.to_s.split("_")
    send("handle_monitor_#{name}", *params)
    log.info { "Client subscribed to channel #{channel}" }
  end
  nil
end

#handle_monitor_blockObject

Handle monitor block command; send the current chain head after client is subscribed to :block channel



89
90
91
92
# File 'lib/bitcoin/network/command_handler.rb', line 89

def handle_monitor_block
  head = Bitcoin::P::Block.new(@node.store.get_head.to_payload) rescue nil
  respond("monitor", ["block", [head, @node.store.get_depth]])  if head
end

#handle_monitor_connectionObject

Handle monitor connection command; send current connections after client is subscribed to :connection channel.



132
133
134
135
136
# File 'lib/bitcoin/network/command_handler.rb', line 132

def handle_monitor_connection
  @node.connections.select {|c| c.connected?}.each do |conn|
    respond("monitor", [:connection, [:connected, conn.info]])
  end
end

#handle_monitor_output(conf = 0) ⇒ Object

Handle monitor output command. Receive tx hash, recipient address and value for each output. This allows easy scanning for new payments without parsing the tx format and running scripts. See #handle_monitor_tx for confirmation behavior.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/bitcoin/network/command_handler.rb', line 115

def handle_monitor_output conf = 0
  return  unless (conf = conf.to_i) > 0
  @node.subscribe(:block) do |block, depth|
    block = @node.store.get_block_by_depth(depth - conf + 1)
    next  unless block
    block.tx.each do |tx|
      tx.out.each do |out|
        addr = Bitcoin::Script.new(out.pk_script).get_address
        res = [tx.hash, addr, out.value, conf]
        @node.push_notification("output_#{conf}".to_sym, res)
      end
    end
  end
end

#handle_monitor_tx(conf = nil) ⇒ Object

Handle monitor tx command. When conf is given, don’t subscribe to the :tx channel for unconfirmed transactions. Instead, subscribe to the :block channel, and whenever a new block comes in, send all transactions that now have conf confirmations.



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/bitcoin/network/command_handler.rb', line 98

def handle_monitor_tx conf = nil
  return  unless conf
  if conf.to_i == 0 # 'tx_0' is just an alias for 'tx'
    return @node.subscribe(:tx) {|*a| @node.notifiers[:tx_0].push(*a) }
  end
  @node.subscribe(:block) do |block, depth|
    block = @node.store.get_block_by_depth(depth - conf.to_i + 1)
    next  unless block
    block.tx.each {|tx| @node.notifiers["tx_#{conf}".to_sym].push([tx, conf.to_i]) }
  end
end

#handle_relay_tx(hex, send = 3, wait = 3) ⇒ Object

Relay given transaction (in hex).

bitcoin_node relay_tx <tx in hex>


289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/bitcoin/network/command_handler.rb', line 289

def handle_relay_tx hex, send = 3, wait = 3
  begin
    tx = Bitcoin::P::Tx.new(hex.htb)
  rescue
    return respond("relay_tx", { error: "Error decoding transaction." })
  end

  validator = tx.validator(@node.store)
  unless validator.validate(rules: [:syntax])
    return respond("relay_tx", { error: "Transaction syntax invalid.",
                   details: validator.error })
  end
  unless validator.validate(rules: [:context])
    return respond("relay_tx", { error: "Transaction context invalid.",
                   details: validator.error })
  end

  #@node.store.store_tx(tx)
  @node.relay_tx[tx.hash] = tx
  @node.relay_propagation[tx.hash] = 0
  @node.connections.select(&:connected?).sample(send).each {|c| c.send_inv(:tx, tx.hash) }

  EM.add_timer(wait) do
    received = @node.relay_propagation[tx.hash]
    total = @node.connections.select(&:connected?).size - send
    percent = 100.0 / total * received
    respond("relay_tx", { success: true, hash: tx.hash, propagation: {
                received: received, sent: 1, percent: percent } })
  end
rescue
  respond("relay_tx", { error: $!.message, backtrace: $@ })
end

#handle_rescanObject

Trigger a rescan operation when used with a UtxoStore.

bitcoin_node rescan


220
221
222
223
# File 'lib/bitcoin/network/command_handler.rb', line 220

def handle_rescan
  EM.defer { @node.store.rescan }
  {:state => "Rescanning ..."}
end

#handle_stopObject

Stop the bitcoin node.

bitcoin_node stop


324
325
326
327
# File 'lib/bitcoin/network/command_handler.rb', line 324

def handle_stop
  Thread.start { sleep 0.1; @node.stop }
  {:state => "Stopping..."}
end

#handle_store_block(hex) ⇒ Object

Validate and store given block (in hex) as if it was received by a peer.

bitcoin_node store_block <block in hex>


337
338
339
340
341
# File 'lib/bitcoin/network/command_handler.rb', line 337

def handle_store_block hex
  block = Bitcoin::P::Block.new(hex.htb)
  @node.queue << [:block, block]
  { queued: [ :block, block.hash ] }
end

#handle_store_tx(hex) ⇒ Object

Store given transaction (in hex) as if it was received by a peer.

bitcoin_node store_tx <tx in hex>


345
346
347
348
349
# File 'lib/bitcoin/network/command_handler.rb', line 345

def handle_store_tx hex
  tx = Bitcoin::P::Tx.new(hex.htb)
  @node.queue << [:tx, tx]
  { queued: [ :tx, tx.hash ] }
end

#handle_tslbObject

Get Time Since Last Block.

bitcoin_node tslb


227
228
229
# File 'lib/bitcoin/network/command_handler.rb', line 227

def handle_tslb
  { tslb: (Time.now - @node.last_block_time).to_i }
end

#logObject

wrap logger and append prefix



19
20
21
# File 'lib/bitcoin/network/command_handler.rb', line 19

def log
  @log ||= Bitcoin::Logger::LogWrapper.new("command:", @node.log)
end

#receive_data(data) ⇒ Object

receive request from the client



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/bitcoin/network/command_handler.rb', line 32

def receive_data data
  @buf.extract(data).each do |packet|
    begin
      cmd, args = JSON::parse(packet)
      log.debug { [cmd, args] }
      if cmd == "relay_tx"
        handle_relay_tx(*args)
        return
      end
      if respond_to?("handle_#{cmd}")
        respond(cmd, send("handle_#{cmd}", *args))
      else
        respond(cmd, { error: "unknown command: #{cmd}. send 'help' for help." })
      end
    rescue ArgumentError
      respond(cmd, { error: $!.message })
    end
  end
rescue Exception
  p $!; puts *$@
end

#respond(cmd, data) ⇒ Object

respond to a command; send serialized response to the client



24
25
26
27
28
29
# File 'lib/bitcoin/network/command_handler.rb', line 24

def respond(cmd, data)
  return  unless data
  @lock.synchronize do
    send_data([cmd, data].to_json + "\x00")
  end
end

#unbindObject

disconnect notification clients when connection is closed



360
361
362
363
# File 'lib/bitcoin/network/command_handler.rb', line 360

def unbind
  #@node.notifiers.unsubscribe(@notify_sid)  if @notify_sid
  @node.command_connections.delete(self)
end