devp2p 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/devp2p/app_helper.rb +1 -1
- data/lib/devp2p/base_app.rb +5 -9
- data/lib/devp2p/base_protocol.rb +5 -2
- data/lib/devp2p/command.rb +1 -1
- data/lib/devp2p/control.rb +1 -1
- data/lib/devp2p/discovery.rb +1 -1
- data/lib/devp2p/discovery/protocol.rb +6 -4
- data/lib/devp2p/discovery/{transport.rb → service.rb} +3 -4
- data/lib/devp2p/p2p_protocol.rb +2 -10
- data/lib/devp2p/peer.rb +24 -8
- data/lib/devp2p/peer_manager.rb +24 -9
- data/lib/devp2p/sync_queue.rb +37 -10
- data/lib/devp2p/utils.rb +4 -0
- data/lib/devp2p/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a9119de12a32ac914f74a103d5544358404a241
|
4
|
+
data.tar.gz: 1f7107a8432d2a9b7a74c676623eeeceec717f42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 869067f4897deb649c694a535eedc8d5cee3a725625c3760a6a069f933cdb08c93d5d16141b9a10cdabba60238c472476e5f98c2a32dea4cddafeadd3d65046f
|
7
|
+
data.tar.gz: 8340dc9d8f31803e250ecfc859a9397f9f84e70ad80513fc6874e6a4b825c78d8b777689bb286bbceb8ea934b97b0f037e53933483e8fdbc18ae16f032fa95de
|
data/lib/devp2p/app_helper.rb
CHANGED
@@ -10,7 +10,7 @@ module DEVp2p
|
|
10
10
|
bootstrap_node_pubkey = Crypto.privtopub bootstrap_node_privkey
|
11
11
|
enode = Utils.host_port_pubkey_to_uri "0.0.0.0", base_port, bootstrap_node_pubkey
|
12
12
|
|
13
|
-
services = [Discovery::
|
13
|
+
services = [Discovery::Service, PeerManager]#, service_class]
|
14
14
|
|
15
15
|
base_config = {}
|
16
16
|
services.each {|s| Utils.update_config_with_defaults base_config, s.default_config }
|
data/lib/devp2p/base_app.rb
CHANGED
@@ -31,7 +31,7 @@ module DEVp2p
|
|
31
31
|
|
32
32
|
logger.info "registering service", service: klass.name
|
33
33
|
@container.add type: klass, as: get_actor_name(klass.name), args: args
|
34
|
-
services[klass.name] =
|
34
|
+
services[klass.name] = actor(klass.name)
|
35
35
|
|
36
36
|
klass
|
37
37
|
end
|
@@ -51,18 +51,14 @@ module DEVp2p
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def start
|
54
|
-
services.
|
55
|
-
|
56
|
-
|
57
|
-
services[n] = actor(n)
|
58
|
-
services[n].start
|
54
|
+
services.each_value do |service|
|
55
|
+
service.start if service.stopped?
|
59
56
|
end
|
60
57
|
end
|
61
58
|
|
62
59
|
def stop
|
63
|
-
services.
|
64
|
-
|
65
|
-
services[n] = nil
|
60
|
+
services.each_value do |service|
|
61
|
+
service.stop if service.alive?
|
66
62
|
end
|
67
63
|
|
68
64
|
#@container.shutdown
|
data/lib/devp2p/base_protocol.rb
CHANGED
@@ -55,7 +55,10 @@ module DEVp2p
|
|
55
55
|
def stop
|
56
56
|
logger.debug 'stopping', proto: Actor.current
|
57
57
|
service.on_wire_protocol_stop Actor.current
|
58
|
+
|
58
59
|
super
|
60
|
+
|
61
|
+
terminate
|
59
62
|
end
|
60
63
|
|
61
64
|
def _run
|
@@ -119,7 +122,7 @@ module DEVp2p
|
|
119
122
|
send_packet packet
|
120
123
|
end
|
121
124
|
|
122
|
-
name =
|
125
|
+
name = Utils.class_to_cmd_name(klass)
|
123
126
|
singleton_class.send(:define_method, "receive_#{name}", &receive)
|
124
127
|
singleton_class.send(:define_method, "create_#{name}", &create)
|
125
128
|
singleton_class.send(:define_method, "send_#{name}", &send_packet)
|
@@ -128,7 +131,7 @@ module DEVp2p
|
|
128
131
|
end
|
129
132
|
end
|
130
133
|
|
131
|
-
@cmd_by_id = klasses.map {|k| [k.cmd_id, Utils.
|
134
|
+
@cmd_by_id = klasses.map {|k| [k.cmd_id, Utils.class_to_cmd_name(k)] }.to_h
|
132
135
|
end
|
133
136
|
|
134
137
|
end
|
data/lib/devp2p/command.rb
CHANGED
@@ -20,7 +20,7 @@ module DEVp2p
|
|
20
20
|
|
21
21
|
case structure
|
22
22
|
when RLP::Sedes::CountableList
|
23
|
-
RLP.encode data, structure
|
23
|
+
RLP.encode data, sedes: structure
|
24
24
|
when Hash
|
25
25
|
raise ArgumentError, 'structure and data length mismatch' unless data.size == structure.size
|
26
26
|
RLP.encode data, sedes: RLP::Sedes::List.new(elements: sedes)
|
data/lib/devp2p/control.rb
CHANGED
data/lib/devp2p/discovery.rb
CHANGED
@@ -28,15 +28,15 @@ module DEVp2p
|
|
28
28
|
|
29
29
|
attr :pubkey, :kademlia
|
30
30
|
|
31
|
-
def initialize(app,
|
31
|
+
def initialize(app, service)
|
32
32
|
@app = app
|
33
|
-
@
|
33
|
+
@service = service
|
34
34
|
|
35
35
|
@privkey = Utils.decode_hex app.config[:node][:privkey_hex]
|
36
36
|
@pubkey = Crypto.privtopub @privkey
|
37
37
|
|
38
38
|
@nodes = {} # nodeid => Node
|
39
|
-
@node = Node.new(pubkey, @
|
39
|
+
@node = Node.new(pubkey, @service.address)
|
40
40
|
|
41
41
|
@kademlia = KademliaProtocolAdapter.new @node, self
|
42
42
|
|
@@ -172,12 +172,14 @@ module DEVp2p
|
|
172
172
|
|
173
173
|
get_node(nodeid, address) unless @nodes.has_key?(nodeid)
|
174
174
|
send cmd, nodeid, payload, mdc
|
175
|
+
rescue
|
176
|
+
logger.error 'invalid message', error: $!
|
175
177
|
end
|
176
178
|
|
177
179
|
def send_message(node, message)
|
178
180
|
raise ArgumentError, 'node must have address' unless node.address
|
179
181
|
logger.debug ">>> message", address: node.address
|
180
|
-
@
|
182
|
+
@service.async.send_message node.address, message
|
181
183
|
end
|
182
184
|
|
183
185
|
def send_ping(node)
|
@@ -6,7 +6,7 @@ module DEVp2p
|
|
6
6
|
##
|
7
7
|
# Persist the list of known nodes with their reputation.
|
8
8
|
#
|
9
|
-
class
|
9
|
+
class Service < BaseService
|
10
10
|
include Celluloid::IO
|
11
11
|
|
12
12
|
name 'discovery'
|
@@ -69,9 +69,8 @@ module DEVp2p
|
|
69
69
|
|
70
70
|
super
|
71
71
|
|
72
|
-
@
|
73
|
-
|
74
|
-
)
|
72
|
+
nodes = @app.config[:discovery][:bootstrap_nodes] || []
|
73
|
+
@protocol.bootstrap( nodes.map {|x| Node.from_uri(x) } )
|
75
74
|
end
|
76
75
|
|
77
76
|
def stop
|
data/lib/devp2p/p2p_protocol.rb
CHANGED
@@ -122,16 +122,8 @@ module DEVp2p
|
|
122
122
|
|
123
123
|
class <<self
|
124
124
|
# special: we need this packet before the protocol can be initialized
|
125
|
-
def get_hello_packet(
|
126
|
-
|
127
|
-
version: 55,
|
128
|
-
client_version_string: peer.config[:client_version_string],
|
129
|
-
capabilities: peer.capabilities,
|
130
|
-
listen_port: peer.config[:p2p][:listen_port],
|
131
|
-
remote_pubkey: peer.config[:node][:id]
|
132
|
-
}
|
133
|
-
|
134
|
-
payload = Hello.encode_payload(res)
|
125
|
+
def get_hello_packet(data)
|
126
|
+
payload = Hello.encode_payload(data.merge(version: 55))
|
135
127
|
Packet.new protocol_id, Hello.cmd_id, payload
|
136
128
|
end
|
137
129
|
end
|
data/lib/devp2p/peer.rb
CHANGED
@@ -7,7 +7,7 @@ module DEVp2p
|
|
7
7
|
|
8
8
|
DUMB_REMOTE_TIMEOUT = 10.0
|
9
9
|
|
10
|
-
attr :config, :protocols, :
|
10
|
+
attr :config, :protocols, :remote_client_version, :remote_pubkey
|
11
11
|
|
12
12
|
def initialize(peermanager, socket, remote_pubkey=nil)
|
13
13
|
@peermanager = peermanager
|
@@ -19,11 +19,12 @@ module DEVp2p
|
|
19
19
|
@stopped = true
|
20
20
|
@hello_received = false
|
21
21
|
|
22
|
+
_, @port, _, @ip = @socket.peeraddr
|
22
23
|
@remote_client_version = ''
|
23
24
|
logger.debug "peer init", peer: Actor.current
|
24
25
|
|
25
26
|
privkey = Utils.decode_hex @config[:node][:privkey_hex]
|
26
|
-
hello_packet = P2PProtocol.get_hello_packet
|
27
|
+
hello_packet = P2PProtocol.get_hello_packet hello_data
|
27
28
|
|
28
29
|
@mux = MultiplexedSession.new privkey, hello_packet, remote_pubkey
|
29
30
|
@remote_pubkey = remote_pubkey
|
@@ -35,12 +36,14 @@ module DEVp2p
|
|
35
36
|
@safe_to_read = true
|
36
37
|
@safe_to_read_cond = Celluloid::Condition.new
|
37
38
|
|
38
|
-
_, @port, _, @ip = @socket.peeraddr
|
39
|
-
|
40
39
|
# stop peer if hello not received in DUMB_REMOTE_TIMEOUT
|
41
40
|
after(DUMB_REMOTE_TIMEOUT) { check_if_dumb_remote }
|
42
41
|
end
|
43
42
|
|
43
|
+
def wait_to_read
|
44
|
+
@safe_to_read_cond.wait unless @safe_to_read
|
45
|
+
end
|
46
|
+
|
44
47
|
##
|
45
48
|
# if peer is responder, then the remote_pubkey will not be available before
|
46
49
|
# the first packet is received
|
@@ -188,7 +191,7 @@ module DEVp2p
|
|
188
191
|
async.run_egress_message
|
189
192
|
|
190
193
|
while !stopped?
|
191
|
-
|
194
|
+
wait_to_read
|
192
195
|
|
193
196
|
begin
|
194
197
|
@socket.wait_readable
|
@@ -236,6 +239,10 @@ module DEVp2p
|
|
236
239
|
logger.debug "multiplexer error", peer: Actor.current, error: e
|
237
240
|
report_error "multiplexer error"
|
238
241
|
stop
|
242
|
+
rescue
|
243
|
+
logger.debug "ingress message error", peer: Actor.current, error: $!
|
244
|
+
report_error "ingress message error"
|
245
|
+
stop
|
239
246
|
end
|
240
247
|
alias run run_ingress_message
|
241
248
|
|
@@ -247,10 +254,11 @@ module DEVp2p
|
|
247
254
|
def stop
|
248
255
|
if !stopped?
|
249
256
|
@stopped = true
|
250
|
-
logger.debug "stopped", peer: Actor.current
|
251
257
|
|
252
258
|
@protocols.each_value {|proto| proto.stop }
|
253
259
|
@peermanager.delete Actor.current
|
260
|
+
|
261
|
+
logger.info "peer stopped", peer: Actor.current
|
254
262
|
terminate
|
255
263
|
end
|
256
264
|
end
|
@@ -265,6 +273,14 @@ module DEVp2p
|
|
265
273
|
@logger ||= Logger.new "#{@config[:p2p][:listen_port]}.p2p.peer"
|
266
274
|
end
|
267
275
|
|
276
|
+
def hello_data
|
277
|
+
{ client_version_string: config[:client_version_string],
|
278
|
+
capabilities: capabilities,
|
279
|
+
listen_port: config[:p2p][:listen_port],
|
280
|
+
remote_pubkey: config[:node][:id]
|
281
|
+
}
|
282
|
+
end
|
283
|
+
|
268
284
|
def handle_packet(packet)
|
269
285
|
raise ArgumentError, 'packet must be Packet' unless packet.is_a?(Packet)
|
270
286
|
|
@@ -273,7 +289,7 @@ module DEVp2p
|
|
273
289
|
|
274
290
|
packet.cmd_id = cmd_id # rewrite
|
275
291
|
protocol.receive_packet packet
|
276
|
-
rescue UnknownCommandError
|
292
|
+
rescue UnknownCommandError => e
|
277
293
|
logger.error 'received unknown cmd', error: e, packet: packet
|
278
294
|
end
|
279
295
|
|
@@ -285,8 +301,8 @@ module DEVp2p
|
|
285
301
|
@protocols.each_value do |protocol|
|
286
302
|
if packet.cmd_id < max_id + protocol.max_cmd_id + 1
|
287
303
|
return protocol, packet.cmd_id - (max_id == 0 ? 0 : max_id + 1)
|
288
|
-
max_id += protocol.max_cmd_id
|
289
304
|
end
|
305
|
+
max_id += protocol.max_cmd_id
|
290
306
|
end
|
291
307
|
raise UnknownCommandError, "no protocol for id #{packet.cmd_id}"
|
292
308
|
end
|
data/lib/devp2p/peer_manager.rb
CHANGED
@@ -13,7 +13,7 @@ module DEVp2p
|
|
13
13
|
#
|
14
14
|
class PeerManager < WiredService
|
15
15
|
include Celluloid::IO
|
16
|
-
finalizer :
|
16
|
+
finalizer :finalize
|
17
17
|
|
18
18
|
name 'peermanager'
|
19
19
|
required_services []
|
@@ -36,6 +36,7 @@ module DEVp2p
|
|
36
36
|
logger.info "PeerManager init"
|
37
37
|
|
38
38
|
@peers = []
|
39
|
+
@excluded = []
|
39
40
|
@errors = @config[:log_disconnects] ? PeerErrors.new : PeerErrorsBase.new
|
40
41
|
|
41
42
|
@wire_protocol = P2PProtocol
|
@@ -46,7 +47,7 @@ module DEVp2p
|
|
46
47
|
end
|
47
48
|
|
48
49
|
@connect_timeout = 2.0
|
49
|
-
@connect_loop_delay = 0.
|
50
|
+
@connect_loop_delay = 0.5
|
50
51
|
@discovery_delay = 0.5
|
51
52
|
|
52
53
|
@host = @config[:p2p][:listen_host]
|
@@ -85,13 +86,17 @@ module DEVp2p
|
|
85
86
|
|
86
87
|
def add(peer)
|
87
88
|
@peers.push peer
|
88
|
-
#link peer
|
89
89
|
end
|
90
90
|
|
91
91
|
def delete(peer)
|
92
92
|
@peers.delete peer
|
93
93
|
end
|
94
94
|
|
95
|
+
def exclude(peer)
|
96
|
+
@excluded.push peer.remote_pubkey
|
97
|
+
peer.stop
|
98
|
+
end
|
99
|
+
|
95
100
|
def on_hello_received(proto, version, client_version_string, capabilities, listen_port, remote_pubkey)
|
96
101
|
logger.debug 'hello_received', listen_port: listen_port, peer: proto.peer, num_peers: @peers.size
|
97
102
|
|
@@ -114,7 +119,7 @@ module DEVp2p
|
|
114
119
|
end
|
115
120
|
|
116
121
|
def broadcast(protocol, command_name, args=[], kwargs={}, num_peers=nil, exclude_peers=[])
|
117
|
-
logger.debug "broadcasting", protocol: protocol, command: command_name, num_peers: num_peers, exclude_peers: exclude_peers
|
122
|
+
logger.debug "broadcasting", protocol: protocol, command: command_name, num_peers: num_peers, exclude_peers: exclude_peers.map(&:to_s)
|
118
123
|
raise ArgumentError, 'invalid num_peers' unless num_peers.nil? || num_peers > 0
|
119
124
|
|
120
125
|
peers_with_proto = @peers.select {|p| p.protocols.include?(protocol) && !exclude_peers.include?(p) }
|
@@ -129,7 +134,7 @@ module DEVp2p
|
|
129
134
|
args.push kwargs
|
130
135
|
peer.protocols[protocol].send "send_#{command_name}", *args
|
131
136
|
|
132
|
-
peer.
|
137
|
+
peer.wait_to_read
|
133
138
|
logger.debug "broadcasting done", ts: Time.now
|
134
139
|
end
|
135
140
|
end
|
@@ -248,12 +253,22 @@ module DEVp2p
|
|
248
253
|
end
|
249
254
|
|
250
255
|
node = neighbours.sample
|
251
|
-
logger.debug 'connecting random neighbour', node: node
|
252
256
|
|
253
257
|
local_pubkey = Crypto.privtopub Utils.decode_hex(@config[:node][:privkey_hex])
|
254
|
-
|
255
|
-
|
258
|
+
if node.pubkey == local_pubkey
|
259
|
+
logger.debug 'connecting random neighbour', node: node, skipped: true, reason: 'myself'
|
260
|
+
next
|
261
|
+
end
|
262
|
+
if @peers.any? {|p| node.pubkey == p.remote_pubkey }
|
263
|
+
logger.debug 'connecting random neighbour', node: node, skipped: true, reason: 'already connected'
|
264
|
+
next
|
265
|
+
end
|
266
|
+
if @excluded.any? {|pubkey| node.pubkey == pubkey }
|
267
|
+
logger.debug 'connecting random neighbour', node: node, skipped: true, reason: 'excluded peer'
|
268
|
+
next
|
269
|
+
end
|
256
270
|
|
271
|
+
logger.debug 'connecting random neighbour', node: node, skipped: false
|
257
272
|
connect node.address.ip, node.address.tcp_port, node.pubkey
|
258
273
|
end
|
259
274
|
|
@@ -265,7 +280,7 @@ module DEVp2p
|
|
265
280
|
cond.wait
|
266
281
|
end
|
267
282
|
|
268
|
-
def
|
283
|
+
def finalize
|
269
284
|
@server.close if @server && !@server.closed?
|
270
285
|
end
|
271
286
|
|
data/lib/devp2p/sync_queue.rb
CHANGED
@@ -5,33 +5,56 @@
|
|
5
5
|
#
|
6
6
|
class SyncQueue
|
7
7
|
|
8
|
-
|
8
|
+
attr :queue, :max_size
|
9
|
+
|
10
|
+
def initialize(max_size=nil)
|
9
11
|
@queue = []
|
10
12
|
@num_waiting = 0
|
11
|
-
|
13
|
+
|
14
|
+
@max_size = max_size
|
15
|
+
@cond_full = Celluloid::Condition.new
|
16
|
+
@cond_empty = Celluloid::Condition.new
|
12
17
|
end
|
13
18
|
|
14
|
-
def enq(obj)
|
15
|
-
|
16
|
-
|
19
|
+
def enq(obj, non_block=false)
|
20
|
+
loop do
|
21
|
+
if full?
|
22
|
+
if non_block
|
23
|
+
raise ThreadError, 'queue full'
|
24
|
+
else
|
25
|
+
begin
|
26
|
+
@num_waiting += 1
|
27
|
+
@cond_full.wait
|
28
|
+
ensure
|
29
|
+
@num_waiting -= 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
else
|
33
|
+
@queue.push obj
|
34
|
+
@cond_empty.signal
|
35
|
+
return obj
|
36
|
+
end
|
37
|
+
end
|
17
38
|
end
|
18
39
|
alias << enq
|
19
40
|
|
20
41
|
def deq(non_block=false)
|
21
42
|
loop do
|
22
|
-
if
|
43
|
+
if empty?
|
23
44
|
if non_block
|
24
45
|
raise ThreadError, 'queue empty'
|
25
46
|
else
|
26
47
|
begin
|
27
48
|
@num_waiting += 1
|
28
|
-
@
|
49
|
+
@cond_empty.wait
|
29
50
|
ensure
|
30
51
|
@num_waiting -= 1
|
31
52
|
end
|
32
53
|
end
|
33
54
|
else
|
34
|
-
|
55
|
+
obj = @queue.shift
|
56
|
+
@cond_full.signal
|
57
|
+
return obj
|
35
58
|
end
|
36
59
|
end
|
37
60
|
end
|
@@ -39,13 +62,13 @@ class SyncQueue
|
|
39
62
|
# Same as pop except it will not remove the element from queue, just peek.
|
40
63
|
def peek(non_block=false)
|
41
64
|
loop do
|
42
|
-
if
|
65
|
+
if empty?
|
43
66
|
if non_block
|
44
67
|
raise ThreadError, 'queue empty'
|
45
68
|
else
|
46
69
|
begin
|
47
70
|
@num_waiting += 1
|
48
|
-
@
|
71
|
+
@cond_empty.wait
|
49
72
|
ensure
|
50
73
|
@num_waiting -= 1
|
51
74
|
end
|
@@ -56,6 +79,10 @@ class SyncQueue
|
|
56
79
|
end
|
57
80
|
end
|
58
81
|
|
82
|
+
def full?
|
83
|
+
@max_size && @queue.size >= @max_size
|
84
|
+
end
|
85
|
+
|
59
86
|
def empty?
|
60
87
|
@queue.empty?
|
61
88
|
end
|
data/lib/devp2p/utils.rb
CHANGED
data/lib/devp2p/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devp2p
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Xie
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
@@ -84,16 +84,16 @@ dependencies:
|
|
84
84
|
name: bitcoin-secp256k1
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
89
|
+
version: '0.4'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
96
|
+
version: '0.4'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rlp
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,7 +178,7 @@ files:
|
|
178
178
|
- lib/devp2p/discovery/kademlia_protocol_adapter.rb
|
179
179
|
- lib/devp2p/discovery/node.rb
|
180
180
|
- lib/devp2p/discovery/protocol.rb
|
181
|
-
- lib/devp2p/discovery/
|
181
|
+
- lib/devp2p/discovery/service.rb
|
182
182
|
- lib/devp2p/exception.rb
|
183
183
|
- lib/devp2p/frame.rb
|
184
184
|
- lib/devp2p/kademlia.rb
|