devp2p 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +22 -0
- data/lib/devp2p.rb +57 -0
- data/lib/devp2p/app_helper.rb +85 -0
- data/lib/devp2p/base_app.rb +80 -0
- data/lib/devp2p/base_protocol.rb +136 -0
- data/lib/devp2p/base_service.rb +55 -0
- data/lib/devp2p/command.rb +82 -0
- data/lib/devp2p/configurable.rb +32 -0
- data/lib/devp2p/connection_monitor.rb +77 -0
- data/lib/devp2p/control.rb +32 -0
- data/lib/devp2p/crypto.rb +73 -0
- data/lib/devp2p/crypto/ecc_x.rb +133 -0
- data/lib/devp2p/crypto/ecies.rb +134 -0
- data/lib/devp2p/discovery.rb +118 -0
- data/lib/devp2p/discovery/address.rb +83 -0
- data/lib/devp2p/discovery/kademlia_protocol_adapter.rb +11 -0
- data/lib/devp2p/discovery/node.rb +32 -0
- data/lib/devp2p/discovery/protocol.rb +342 -0
- data/lib/devp2p/discovery/transport.rb +105 -0
- data/lib/devp2p/exception.rb +30 -0
- data/lib/devp2p/frame.rb +197 -0
- data/lib/devp2p/kademlia.rb +48 -0
- data/lib/devp2p/kademlia/k_bucket.rb +178 -0
- data/lib/devp2p/kademlia/node.rb +40 -0
- data/lib/devp2p/kademlia/protocol.rb +284 -0
- data/lib/devp2p/kademlia/routing_table.rb +131 -0
- data/lib/devp2p/kademlia/wire_interface.rb +30 -0
- data/lib/devp2p/multiplexed_session.rb +110 -0
- data/lib/devp2p/multiplexer.rb +358 -0
- data/lib/devp2p/p2p_protocol.rb +170 -0
- data/lib/devp2p/packet.rb +35 -0
- data/lib/devp2p/peer.rb +329 -0
- data/lib/devp2p/peer_errors.rb +35 -0
- data/lib/devp2p/peer_manager.rb +274 -0
- data/lib/devp2p/rlpx_session.rb +434 -0
- data/lib/devp2p/sync_queue.rb +76 -0
- data/lib/devp2p/utils.rb +106 -0
- data/lib/devp2p/version.rb +13 -0
- data/lib/devp2p/wired_service.rb +30 -0
- metadata +227 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module DEVp2p
|
4
|
+
module Kademlia
|
5
|
+
|
6
|
+
##
|
7
|
+
# defines the methods used by KademliaProtocol
|
8
|
+
#
|
9
|
+
class WireInterface
|
10
|
+
|
11
|
+
def send_ping(node)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def send_pong(node, id)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def send_find_node(nodeid)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def send_neighbours(node, neighbours)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module DEVp2p
|
4
|
+
|
5
|
+
class MultiplexedSession < Multiplexer
|
6
|
+
|
7
|
+
def initialize(privkey, hello_packet, remote_pubkey=nil)
|
8
|
+
@hello_packet = hello_packet
|
9
|
+
@remote_pubkey = remote_pubkey
|
10
|
+
|
11
|
+
@message_queue = SyncQueue.new # wire msg egress queue
|
12
|
+
@packet_queue = SyncQueue.new # packet ingress queue
|
13
|
+
|
14
|
+
@is_initiator = !!remote_pubkey
|
15
|
+
@handshake_finished = false
|
16
|
+
|
17
|
+
ecc = Crypto::ECCx.new privkey
|
18
|
+
@rlpx_session = RLPxSession.new ecc, @is_initiator
|
19
|
+
|
20
|
+
super(@rlpx_session)
|
21
|
+
|
22
|
+
send_init_msg if @is_initiator
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_message
|
26
|
+
@message_queue.deq
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_packet
|
30
|
+
@packet_queue.deq
|
31
|
+
end
|
32
|
+
|
33
|
+
def ready?
|
34
|
+
@rlpx_session.ready?
|
35
|
+
end
|
36
|
+
|
37
|
+
def initiator?
|
38
|
+
@is_initiator
|
39
|
+
end
|
40
|
+
|
41
|
+
def remote_pubkey
|
42
|
+
@remote_pubkey || @rlpx_session.remote_pubkey
|
43
|
+
end
|
44
|
+
|
45
|
+
def remote_pubkey=(key)
|
46
|
+
@remote_pubkey = key
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_message(msg)
|
50
|
+
@handshake_finished ? add_message_post_handshake(msg) : add_message_during_handshake(msg)
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# encodes a packet and adds the message(s) to the message queue.
|
55
|
+
#
|
56
|
+
def add_packet(packet)
|
57
|
+
raise MultiplexedSessionError, 'session is not ready' unless ready?
|
58
|
+
raise ArgumentError, 'packet must be instance of Packet' unless packet.is_a?(Packet)
|
59
|
+
|
60
|
+
super(packet)
|
61
|
+
|
62
|
+
pop_all_frames.each {|f| @message_queue.enq f.as_bytes }
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def send_init_msg
|
68
|
+
auth_msg = @rlpx_session.create_auth_message @remote_pubkey
|
69
|
+
auth_msg_ct = @rlpx_session.encrypt_auth_message auth_msg
|
70
|
+
|
71
|
+
@message_queue.enq auth_msg_ct
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_message_during_handshake(msg)
|
75
|
+
raise MultiplexedSessionError, 'handshake after ready is not allowed' if ready?
|
76
|
+
|
77
|
+
if initiator?
|
78
|
+
# expecting auth ack message
|
79
|
+
rest = @rlpx_session.decode_auth_ack_message msg
|
80
|
+
@rlpx_session.setup_cipher
|
81
|
+
|
82
|
+
# add remains (hello) to queue
|
83
|
+
add_message_post_handshake(rest) unless rest.empty?
|
84
|
+
else
|
85
|
+
# expecting auth_init
|
86
|
+
rest = @rlpx_session.decode_authentication msg
|
87
|
+
auth_ack_msg = @rlpx_session.create_auth_ack_message
|
88
|
+
auth_ack_msg_ct = @rlpx_session.encrypt_auth_ack_message auth_ack_msg
|
89
|
+
|
90
|
+
@message_queue.enq auth_ack_msg_ct
|
91
|
+
|
92
|
+
@rlpx_session.setup_cipher
|
93
|
+
add_message_post_handshake(rest) unless rest.empty?
|
94
|
+
end
|
95
|
+
|
96
|
+
@handshake_finished = true
|
97
|
+
raise MultiplexedSessionError, 'session is not ready after handshake' unless @rlpx_session.ready?
|
98
|
+
|
99
|
+
add_packet @hello_packet
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_message_post_handshake(msg)
|
103
|
+
decode(msg).each do |packet|
|
104
|
+
@packet_queue.enq packet
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,358 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module DEVp2p
|
4
|
+
|
5
|
+
##
|
6
|
+
# Multiplexing of protocols is performed via dynamic framing and fair
|
7
|
+
# queueing. Dequeuing packets is performed in a cycle which dequeues one or
|
8
|
+
# more packets from the queue(s) of each active protocol. The multiplexor
|
9
|
+
# determines the amount of bytes to send for each protocol prior to each
|
10
|
+
# round of dequeuing packets.
|
11
|
+
#
|
12
|
+
# If the size of an RLP-encoded packet is less than 1KB then the protocol may
|
13
|
+
# request that the network layer prioritize the delivery of the packet. This
|
14
|
+
# should be used if and only if the packet must be delivered before all other
|
15
|
+
# packets.
|
16
|
+
#
|
17
|
+
# The network layer maintains two queues and three buffers per protocol:
|
18
|
+
#
|
19
|
+
# * a queue for normal packets, a queue for priority packets
|
20
|
+
# * a chunked-frame buffer, a normal-frame buffer, and a priority-frame buffer
|
21
|
+
#
|
22
|
+
# Implemented Variant:
|
23
|
+
#
|
24
|
+
# each sub protocol has three queues: prio, normal, chunked
|
25
|
+
#
|
26
|
+
# protocols are queried round robin
|
27
|
+
#
|
28
|
+
class Multiplexer
|
29
|
+
|
30
|
+
extend Configurable
|
31
|
+
add_config(
|
32
|
+
max_window_size: 8 * 1024,
|
33
|
+
max_priority_frame_size: 1024,
|
34
|
+
max_payload_size: 10 * 1024**2,
|
35
|
+
frame_cipher: nil,
|
36
|
+
)
|
37
|
+
|
38
|
+
attr :decode_buffer
|
39
|
+
|
40
|
+
def initialize(frame_cipher=nil)
|
41
|
+
@frame_cipher = frame_cipher || self.class.frame_cipher
|
42
|
+
@last_protocol = nil
|
43
|
+
@decode_buffer = "" # byte array
|
44
|
+
|
45
|
+
# protocol_id: {normal: queue, chunked: queue, prio: queue}
|
46
|
+
@queues = {}
|
47
|
+
|
48
|
+
# protocol_id: counter
|
49
|
+
@sequence_id = {}
|
50
|
+
|
51
|
+
# decode: {protocol_id: {sequence_id: buffer}
|
52
|
+
@chunked_buffers = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# A protocol is considered active if it's queue contains one or more
|
57
|
+
# packets.
|
58
|
+
#
|
59
|
+
def num_active_protocols
|
60
|
+
@queues.keys.select {|id| active_protocol?(id) }.size
|
61
|
+
end
|
62
|
+
|
63
|
+
def active_protocol?(id)
|
64
|
+
!@queues[id].values.all?(&:empty?)
|
65
|
+
end
|
66
|
+
|
67
|
+
# pws = protocol_window_size = window_size / active_protocol_count
|
68
|
+
def protocol_window_size(id=nil)
|
69
|
+
if id && !active_protocol?(id)
|
70
|
+
s = max_window_size / (1 + num_active_protocols)
|
71
|
+
else
|
72
|
+
s = max_window_size / [1, num_active_protocols].max
|
73
|
+
end
|
74
|
+
|
75
|
+
s - s % 16 # should be a multiple of padding size # FIXME: 16 should be constant
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_protocol(id)
|
79
|
+
raise ArgumentError, 'protocol already added' if @queues.include?(id)
|
80
|
+
|
81
|
+
@queues[id] = {
|
82
|
+
normal: SyncQueue.new,
|
83
|
+
chunked: SyncQueue.new,
|
84
|
+
priority: SyncQueue.new
|
85
|
+
}
|
86
|
+
@sequence_id[id] = 0
|
87
|
+
@chunked_buffers[id] = {}
|
88
|
+
@last_protocol = id
|
89
|
+
end
|
90
|
+
|
91
|
+
def next_protocol
|
92
|
+
protocols = @queues.keys
|
93
|
+
if @last_protocol == protocols.last
|
94
|
+
proto = protocols.first
|
95
|
+
else
|
96
|
+
proto = protocols[protocols.index(@last_protocol) + 1]
|
97
|
+
end
|
98
|
+
|
99
|
+
@last_protocol = proto
|
100
|
+
proto
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_packet(packet)
|
104
|
+
sid = @sequence_id[packet.protocol_id]
|
105
|
+
@sequence_id[packet.protocol_id] = (sid + 1) % TT16
|
106
|
+
|
107
|
+
frames = Frame.new(
|
108
|
+
packet.protocol_id, packet.cmd_id, packet.payload, sid,
|
109
|
+
protocol_window_size(packet.protocol_id),
|
110
|
+
false, nil, @frame_cipher
|
111
|
+
).frames
|
112
|
+
|
113
|
+
queues = @queues[packet.protocol_id]
|
114
|
+
|
115
|
+
if packet.prioritize
|
116
|
+
raise FrameError, "invalid priority packet frames" unless frames.size == 1
|
117
|
+
raise FrameError, "frame too large for priority packet" unless frames[0].frame_size <= max_priority_frame_size
|
118
|
+
|
119
|
+
queues[:priority].enq frames[0]
|
120
|
+
elsif frames.size == 1
|
121
|
+
queues[:normal].enq frames[0]
|
122
|
+
else
|
123
|
+
frames.each {|f| queues[:chunked].enq f }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# If priority packet and normal packet exist:
|
129
|
+
# send up to pws/2 bytes from each (priority first)
|
130
|
+
# else if priority packet and chunked-frame exist:
|
131
|
+
# send up to pws/2 bytes from each
|
132
|
+
# else if normal packet and chunked-frame exist:
|
133
|
+
# send up to pws/2 bytes from each
|
134
|
+
# else
|
135
|
+
# read pws bytes from active buffer
|
136
|
+
#
|
137
|
+
# If there are bytes leftover -- for example, if the bytes sent is < pws,
|
138
|
+
# then repeat the cycle.
|
139
|
+
#
|
140
|
+
def pop_frames_for_protocol(id)
|
141
|
+
pws = protocol_window_size
|
142
|
+
queues = @queues[id]
|
143
|
+
|
144
|
+
frames = []
|
145
|
+
size = 0
|
146
|
+
|
147
|
+
while size < pws
|
148
|
+
frames_added = 0
|
149
|
+
|
150
|
+
%i(priority normal chunked).each do |qn|
|
151
|
+
q = queues[qn]
|
152
|
+
|
153
|
+
if !q.empty?
|
154
|
+
fs = q.peek.frame_size
|
155
|
+
if size + fs <= pws
|
156
|
+
frames.push q.deq
|
157
|
+
size += fs
|
158
|
+
frames_added += 1
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# add no more than two in order to send normal and priority first
|
163
|
+
# i.e. next is 'priority' again
|
164
|
+
#
|
165
|
+
# FIXME: too weird
|
166
|
+
#
|
167
|
+
break if frames_added == 2
|
168
|
+
end
|
169
|
+
|
170
|
+
break if frames_added == 0 # empty queues
|
171
|
+
end
|
172
|
+
|
173
|
+
# the following can not be guaranteed, as pws might have been different
|
174
|
+
# at the time where packets were framed and added to the queues
|
175
|
+
#
|
176
|
+
# frames.map(&:frame_size).sum <= pws
|
177
|
+
return frames
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Returns the frames for the next protocol up to protocol window size bytes.
|
182
|
+
#
|
183
|
+
def pop_frames
|
184
|
+
protocols = @queues.keys
|
185
|
+
idx = protocols.index next_protocol
|
186
|
+
protocols = protocols[idx..-1] + protocols[0,idx]
|
187
|
+
|
188
|
+
protocols.each do |id|
|
189
|
+
frames = pop_frames_for_protocol id
|
190
|
+
return frames unless frames.empty?
|
191
|
+
end
|
192
|
+
|
193
|
+
[]
|
194
|
+
end
|
195
|
+
|
196
|
+
def pop_all_frames
|
197
|
+
frames = []
|
198
|
+
loop do
|
199
|
+
r = pop_frames
|
200
|
+
frames.concat r
|
201
|
+
break if r.empty?
|
202
|
+
end
|
203
|
+
frames
|
204
|
+
end
|
205
|
+
|
206
|
+
def pop_all_frames_as_bytes
|
207
|
+
pop_all_frames.map(&:as_bytes).join
|
208
|
+
end
|
209
|
+
|
210
|
+
def decode_header(buffer)
|
211
|
+
raise ArgumentError, "buffer too small" unless buffer.size >= 32
|
212
|
+
|
213
|
+
if @frame_cipher
|
214
|
+
header = @frame_cipher.decrypt_header(buffer[0, Frame.header_size + Frame.mac_size])
|
215
|
+
else
|
216
|
+
# header: frame-size || header-data || padding
|
217
|
+
header = buffer[0, Frame.header_size]
|
218
|
+
end
|
219
|
+
|
220
|
+
header
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# w/o encryption
|
225
|
+
# peak info buffer for body_size
|
226
|
+
#
|
227
|
+
# return nil if buffer is not long enough to decode frame
|
228
|
+
#
|
229
|
+
def decode_body(buffer, header=nil)
|
230
|
+
return [nil, buffer] if buffer.size < Frame.header_size
|
231
|
+
|
232
|
+
header ||= decode_header buffer[0, Frame.header_size + Frame.mac_size]
|
233
|
+
body_size = Frame.decode_body_size header
|
234
|
+
|
235
|
+
if @frame_cipher
|
236
|
+
body = @frame_cipher.decrypt_body(buffer[(Frame.header_size+Frame.mac_size)..-1], body_size)
|
237
|
+
raise MultiplexerError, 'body length mismatch' unless body.size == body_size
|
238
|
+
|
239
|
+
bytes_read = Frame.header_size + Frame.mac_size + Utils.ceil16(body.size) + Frame.mac_size
|
240
|
+
else
|
241
|
+
header = buffer[0, Frame.header_size]
|
242
|
+
body_offset = Frame.header_size + Frame.mac_size
|
243
|
+
body = buffer[body_offset, body_size]
|
244
|
+
raise MultiplexerError, 'body length mismatch' unless body.size == body_size
|
245
|
+
|
246
|
+
bytes_read = Utils.ceil16(body_offset + body_size + Frame.mac_size)
|
247
|
+
end
|
248
|
+
raise MultiplexerError, "bytes not padded" unless bytes_read % Frame.padding == 0
|
249
|
+
|
250
|
+
# normal, chunked-n: RLP::List.new(protocol_type[, sequence_id])
|
251
|
+
# chunked-0: RLP::List.new(protocol_type, sequence_id, total_packet_size)
|
252
|
+
header_data = nil
|
253
|
+
begin
|
254
|
+
header_data = RLP.decode(header[3..-1], sedes: Frame.header_sedes, strict: false)
|
255
|
+
rescue RLP::Error::RLPException => e
|
256
|
+
logger.error(e)
|
257
|
+
raise MultiplexerError, 'invalid rlp data'
|
258
|
+
end
|
259
|
+
|
260
|
+
if header_data.size == 3
|
261
|
+
chunked_0 = true
|
262
|
+
total_payload_size = header_data[2]
|
263
|
+
raise MultiplexerError, "invalid total payload size" unless total_payload_size < 2**32
|
264
|
+
else
|
265
|
+
chunked_0 = false
|
266
|
+
total_payload_size = nil
|
267
|
+
end
|
268
|
+
|
269
|
+
protocol_id = header_data[0]
|
270
|
+
raise MultiplexerError, "invalid protocol id" unless protocol_id < TT16
|
271
|
+
|
272
|
+
if header_data.size > 1
|
273
|
+
sequence_id = header_data[1]
|
274
|
+
raise MultiplexerError, "invalid sequence id" unless sequence_id < TT16
|
275
|
+
else
|
276
|
+
sequence_id = nil
|
277
|
+
end
|
278
|
+
|
279
|
+
raise MultiplexerError, "unknown protocol id #{protocol_id}" unless @chunked_buffers.has_key?(protocol_id)
|
280
|
+
|
281
|
+
chunkbuf = @chunked_buffers[protocol_id]
|
282
|
+
if chunkbuf.has_key?(sequence_id)
|
283
|
+
packet = chunkbuf[sequence_id]
|
284
|
+
|
285
|
+
raise MultiplexerError, "received chunked_0 frame for existing buffer #{sequence_id} of protocol #{protocol_id}" if chunked_0
|
286
|
+
raise MultiplexerError, "too much data for chunked buffer #{sequence_id} of protocol #{protocol_id}" if body.size > (packet.total_payload_size - packet.payload.size)
|
287
|
+
|
288
|
+
packet.payload += body
|
289
|
+
if packet.total_payload_size == packet.payload.size
|
290
|
+
packet.total_payload_size = nil
|
291
|
+
chunkbuf.delete sequence_id
|
292
|
+
return packet
|
293
|
+
end
|
294
|
+
else
|
295
|
+
# body of normal, chunked_0: rlp(packet-type) [|| rlp(packet-data)] || padding
|
296
|
+
item, item_end = RLP.consume_item(body, 0)
|
297
|
+
cmd_id = RLP::Sedes.big_endian_int.deserialize item
|
298
|
+
|
299
|
+
if chunked_0
|
300
|
+
payload = body[item_end..-1]
|
301
|
+
total_payload_size -= item_end
|
302
|
+
else
|
303
|
+
payload = body[item_end..-1]
|
304
|
+
end
|
305
|
+
|
306
|
+
packet = Packet.new protocol_id, cmd_id, payload
|
307
|
+
if chunked_0
|
308
|
+
raise MultiplexerError, "total payload size smaller than initial chunk" if total_payload_size < payload.size
|
309
|
+
|
310
|
+
# shouldn't have been chunked, whatever
|
311
|
+
return packet if total_payload_size == payload.size
|
312
|
+
|
313
|
+
raise MultiplexerError, 'chunked_0 must have sequence id' if sequence_id.nil?
|
314
|
+
|
315
|
+
packet.total_payload_size = total_payload_size
|
316
|
+
chunkbuf[sequence_id] = packet
|
317
|
+
|
318
|
+
return nil
|
319
|
+
else
|
320
|
+
return packet # normal (non-chunked)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def decode(data='')
|
326
|
+
@decode_buffer.concat(data) unless data.empty?
|
327
|
+
|
328
|
+
unless @cached_decode_header
|
329
|
+
if @decode_buffer.size < Frame.header_size + Frame.mac_size
|
330
|
+
return []
|
331
|
+
else
|
332
|
+
@cached_decode_header = decode_header @decode_buffer
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
body_size = Frame.decode_body_size @cached_decode_header
|
337
|
+
required_len = Frame.header_size + Frame.mac_size + Utils.ceil16(body_size) + Frame.mac_size
|
338
|
+
|
339
|
+
if @decode_buffer.size >= required_len
|
340
|
+
packet = decode_body @decode_buffer, @cached_decode_header
|
341
|
+
@cached_decode_header = nil
|
342
|
+
@decode_buffer = @decode_buffer[required_len..-1]
|
343
|
+
|
344
|
+
return packet ? ([packet] + decode) : decode
|
345
|
+
end
|
346
|
+
|
347
|
+
[]
|
348
|
+
end
|
349
|
+
|
350
|
+
private
|
351
|
+
|
352
|
+
def logger
|
353
|
+
@logger ||= Logger.new('multiplexer')
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
|
358
|
+
end
|