Class: Bitcoin::Network::CommandHandler
- Inherits:
-
EM::Connection
- Object
- EM::Connection
- Bitcoin::Network::CommandHandler
- 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
-
#format_uptime(t) ⇒ Object
format node uptime.
-
#handle_addrs(count = 32) ⇒ Object
Get known peer addresses (used by bin/bitcoin_dns_seed).
-
#handle_assemble_tx(tx_hex, sig_pubs) ⇒ Object
Assemble an unsigned transaction from the
tx_hexandsig_pubkeys. -
#handle_config ⇒ Object
Get the currently active configuration.
-
#handle_connect(*args) ⇒ Object
Connect to given peer(s).
-
#handle_connections ⇒ Object
Get currently connected peers.
-
#handle_create_tx(keys, recipients, fee = 0) ⇒ Object
Create a transaction, collecting outputs from given
keys, spending torecipientswith an optionalfee. -
#handle_disconnect(*args) ⇒ Object
Disconnect given peer(s).
-
#handle_getaddr ⇒ Object
Trigger the node to ask its for new peer addresses.
-
#handle_getblocks ⇒ Object
Trigger the node to ask its peers for new blocks.
-
#handle_help ⇒ Object
List all available commands.
-
#handle_info ⇒ Object
Get various statistics.
-
#handle_monitor(*channels) ⇒ Object
handle
monitorcommand; subscribe client to specified channels (block,tx,output,connection). -
#handle_monitor_block ⇒ Object
Handle monitor block command; send the current chain head after client is subscribed to :block channel.
-
#handle_monitor_connection ⇒ Object
Handle monitor connection command; send current connections after client is subscribed to :connection channel.
-
#handle_monitor_output(conf = 0) ⇒ Object
Handle monitor output command.
-
#handle_monitor_tx(conf = nil) ⇒ Object
Handle monitor tx command.
-
#handle_relay_tx(hex, send = 3, wait = 3) ⇒ Object
Relay given transaction (in hex).
-
#handle_rescan ⇒ Object
Trigger a rescan operation when used with a UtxoStore.
-
#handle_stop ⇒ Object
Stop the bitcoin node.
-
#handle_store_block(hex) ⇒ Object
Validate and store given block (in hex) as if it was received by a peer.
-
#handle_store_tx(hex) ⇒ Object
Store given transaction (in hex) as if it was received by a peer.
-
#handle_tslb ⇒ Object
Get Time Since Last Block.
-
#initialize(node) ⇒ CommandHandler
constructor
create new CommandHandler.
-
#log ⇒ Object
wrap logger and append prefix.
-
#receive_data(data) ⇒ Object
receive request from the client.
-
#respond(cmd, data) ⇒ Object
respond to a command; send serialized response to the client.
-
#unbind ⇒ Object
disconnect notification clients when connection is closed.
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_config ⇒ Object
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_connections ⇒ Object
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_getaddr ⇒ Object
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_getblocks ⇒ Object
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_help ⇒ Object
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_info ⇒ Object
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_block ⇒ Object
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_connection ⇒ Object
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: $!., backtrace: $@ }) end |
#handle_rescan ⇒ Object
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_stop ⇒ Object
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_tslb ⇒ Object
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 |
#log ⇒ Object
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: $!. }) 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 |
#unbind ⇒ Object
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 |