Class: Bitcoin::Protocol::Parser
- Inherits:
-
Object
- Object
- Bitcoin::Protocol::Parser
- Defined in:
- lib/bitcoin/protocol/parser.rb
Overview
Instance Attribute Summary collapse
-
#stats ⇒ Object
readonly
Returns the value of attribute stats.
Instance Method Summary collapse
- #handle_mempool_request(*_) ⇒ Object
- #handle_notfound_reply(payload) ⇒ Object
- #handle_reject(payload) ⇒ Object
- #handle_stream_error(type, msg) ⇒ Object
-
#initialize(handler = nil) ⇒ Parser
constructor
A new instance of Parser.
- #parse(buf) ⇒ Object
- #parse_addr(payload) ⇒ Object
- #parse_alert(payload) ⇒ Object
- #parse_buffer ⇒ Object
- #parse_error(*err) ⇒ Object
- #parse_getblocks(payload) ⇒ Object
-
#parse_headers(payload) ⇒ Object
rubocop:enable CyclomaticComplexity.
-
#parse_inv(payload, type = :put) ⇒ Object
handles inv/getdata packets.
- #parse_mrkle_block(payload) ⇒ Object
- #parse_verack ⇒ Object
- #parse_version(payload) ⇒ Object
-
#process_pkt(command, payload) ⇒ Object
rubocop:disable CyclomaticComplexity.
Constructor Details
Instance Attribute Details
#stats ⇒ Object (readonly)
Returns the value of attribute stats.
7 8 9 |
# File 'lib/bitcoin/protocol/parser.rb', line 7 def stats @stats end |
Instance Method Details
#handle_mempool_request(*_) ⇒ Object
122 123 124 125 126 127 128 |
# File 'lib/bitcoin/protocol/parser.rb', line 122 def handle_mempool_request(*_) return unless @version.fields[:version] >= 60_002 # Protocol version >= 60002 return unless ( @version.fields[:services] & Bitcoin::Protocol::Version::NODE_NETWORK ) == 1 # NODE_NETWORK bit set in Services @h.on_mempool if @h.respond_to?(:on_mempool) end |
#handle_notfound_reply(payload) ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/bitcoin/protocol/parser.rb', line 130 def handle_notfound_reply(payload) return unless @h.respond_to?(:on_notfound) _, payload = Protocol.unpack_var_int(payload) payload.each_byte.each_slice(36) do |i| hash = i[4..-1].reverse.pack('C32') case i[0] when 1 then @h.on_notfound(:tx, hash) when 2 then @h.on_notfound(:block, hash) else parse_error(:notfound, [i.pack('C*'), hash]) end end end |
#handle_reject(payload) ⇒ Object
151 152 153 154 |
# File 'lib/bitcoin/protocol/parser.rb', line 151 def handle_reject(payload) return unless @h.respond_to?(:on_reject) @h.on_reject Bitcoin::Protocol::Reject.parse(payload) end |
#handle_stream_error(type, msg) ⇒ Object
193 194 195 196 197 198 199 200 201 |
# File 'lib/bitcoin/protocol/parser.rb', line 193 def handle_stream_error(type, msg) # TODO: replace by writing a real logger/exception handler case type when :close puts "closing packet stream (#{msg})" else puts [type, msg].inspect end end |
#parse(buf) ⇒ Object
156 157 158 159 160 |
# File 'lib/bitcoin/protocol/parser.rb', line 156 def parse(buf) @buf += buf while parse_buffer; end @buf end |
#parse_addr(payload) ⇒ Object
84 85 86 87 88 89 90 91 92 93 |
# File 'lib/bitcoin/protocol/parser.rb', line 84 def parse_addr(payload) _, payload = Protocol.unpack_var_int(payload) payload.each_byte.each_slice(30) do |i| begin @h.on_addr(Addr.new(i.pack('C*'))) rescue StandardError parse_error(:addr, i.pack('C*')) end end end |
#parse_alert(payload) ⇒ Object
108 109 110 |
# File 'lib/bitcoin/protocol/parser.rb', line 108 def parse_alert(payload) # nop (https://github.com/lian/bitcoin-ruby/issues/268) end |
#parse_buffer ⇒ Object
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/bitcoin/protocol/parser.rb', line 162 def parse_buffer head_magic = Bitcoin.network[:magic_head] head_size = 24 return false if @buf.size < head_size magic, cmd, length, checksum = @buf.unpack('a4A12Va4') payload = @buf[head_size...head_size + length] if magic == head_magic if Digest::SHA256.digest(Digest::SHA256.digest(payload))[0...4] != checksum if (length < 50_000) && (payload.size < length) size_info = [payload.size, length].join('/') handle_stream_error(:debug, "chunked packet stream (#{size_info})") else handle_stream_error(:close, 'checksum mismatch') end return end @buf = @buf[head_size + length..-1] || '' process_pkt(cmd, payload) else handle_stream_error(:close, 'head_magic not found') @buf = '' end # not empty yet? parse more. !@buf[0].nil? end |
#parse_error(*err) ⇒ Object
203 204 205 206 207 |
# File 'lib/bitcoin/protocol/parser.rb', line 203 def parse_error(*err) @stats['total_errors'] += 1 return unless @h.respond_to?(:on_error) @h.on_error(*err) end |
#parse_getblocks(payload) ⇒ Object
112 113 114 115 116 117 118 119 |
# File 'lib/bitcoin/protocol/parser.rb', line 112 def parse_getblocks(payload) version, payload = payload.unpack('Va*') count, payload = Protocol.unpack_var_int(payload) buf, payload = payload.unpack("a#{count * 32}a*") hashes = buf.each_byte.each_slice(32).map { |i| i.reverse.pack('C32').hth } stop_hash = payload[0..32].reverse_hth [version, hashes, stop_hash] end |
#parse_headers(payload) ⇒ Object
rubocop:enable CyclomaticComplexity
47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/bitcoin/protocol/parser.rb', line 47 def parse_headers(payload) return unless @h.respond_to?(:on_headers) buf = StringIO.new(payload) count = Protocol.unpack_var_int_from_io(buf) headers = Array.new(count) do break if buf.eof? b = Block.new b.parse_data_from_io(buf, true) b end @h.on_headers(headers) end |
#parse_inv(payload, type = :put) ⇒ Object
handles inv/getdata packets
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/bitcoin/protocol/parser.rb', line 61 def parse_inv(payload, type = :put) count, payload = Protocol.unpack_var_int(payload) payload.each_byte.each_slice(36).with_index do |i, idx| hash = i[4..-1].reverse.pack('C32') case i[0] when 1 type == :put ? @h.on_inv_transaction(hash) : @h.on_get_transaction(hash) when 2 if type == :put if @h.respond_to?(:on_inv_block_v2) @h.on_inv_block_v2(hash, idx, count) else @h.on_inv_block(hash) end else @h.on_get_block(hash) end else parse_error :parse_inv, i.pack('C*') end end end |
#parse_mrkle_block(payload) ⇒ Object
144 145 146 147 148 149 |
# File 'lib/bitcoin/protocol/parser.rb', line 144 def parse_mrkle_block(payload) return unless @h.respond_to?(:on_mrkle_block) b = Block.new b.parse_data_from_io(payload, :filtered) @h.on_mrkle_block(b) end |
#parse_verack ⇒ Object
95 96 97 98 99 100 101 |
# File 'lib/bitcoin/protocol/parser.rb', line 95 def parse_verack if @h.respond_to?(:on_verack) @h.on_verack else @h.respond_to?(:on_handshake_complete) ? @h.on_handshake_complete : nil end end |
#parse_version(payload) ⇒ Object
103 104 105 106 |
# File 'lib/bitcoin/protocol/parser.rb', line 103 def parse_version(payload) @version = Bitcoin::Protocol::Version.parse(payload) @h.on_version(@version) end |
#process_pkt(command, payload) ⇒ Object
rubocop:disable CyclomaticComplexity
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/bitcoin/protocol/parser.rb', line 16 def process_pkt(command, payload) @stats['total_packets'] += 1 @stats['total_bytes'] += payload.bytesize @stats[command] ? (@stats[command] += 1) : @stats[command] = 1 case command when 'tx' then @h.on_tx(Tx.new(payload)) when 'block' then @h.on_block(Block.new(payload)) when 'headers' then parse_headers(payload) when 'inv' then parse_inv(payload, :put) when 'getdata' then parse_inv(payload, :get) when 'addr' then parse_addr(payload) when 'getaddr' then @h.on_getaddr if @h.respond_to?(:on_getaddr) when 'verack' then parse_verack when 'version' then parse_version(payload) when 'alert' then parse_alert(payload) when 'ping' then @h.on_ping(payload.unpack('Q')[0]) when 'pong' then @h.on_pong(payload.unpack('Q')[0]) when 'getblocks' then @h.on_getblocks(*parse_getblocks(payload)) \ if @h.respond_to?(:on_getblocks) when 'getheaders' then @h.on_getheaders(*parse_getblocks(payload)) \ if @h.respond_to?(:on_getheaders) when 'mempool' then handle_mempool_request(payload) when 'notfound' then handle_notfound_reply(payload) when 'merkleblock' then parse_mrkle_block(payload) when 'reject' then handle_reject(payload) else parse_error :unknown_packet, [command, payload.hth] end end |