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,118 @@
|
|
1
|
+
##
|
2
|
+
# # Node Discovery Protocol
|
3
|
+
#
|
4
|
+
# * [Node] - an entity on the network
|
5
|
+
# * [Node] ID - 512 bit public key of node
|
6
|
+
#
|
7
|
+
# The Node Discovery protocol provides a way to find RLPx nodes that can be
|
8
|
+
# connected to. It uses a Kademlia-like protocol to maintain a distributed
|
9
|
+
# database of the IDs and endpoints of all listening nodes.
|
10
|
+
#
|
11
|
+
# Each node keeps a node table as described in the Kademlia paper (Maymounkov,
|
12
|
+
# Mazières 2002). The node table is configured with a bucket size of 16
|
13
|
+
# (denoted `k` in Kademlia), concurrency of 3 (denoted `α` in Kademlia), and 8
|
14
|
+
# bits per hop (denoted `b` in Kademlia) for routing. The eviction check
|
15
|
+
# interval is 75 milliseconds, and the idle bucket-refresh interval is 3600
|
16
|
+
# seconds.
|
17
|
+
#
|
18
|
+
# In order to maintain a well-formed network, RLPx nodes should try to connect
|
19
|
+
# to an unspecified number of close nodes. To increase resilience against Sybil
|
20
|
+
# attacks, nodes should also connect to randomly chosen, non-close nodes.
|
21
|
+
#
|
22
|
+
# Each node runs the UDP-based RPC protocol defined below. The `FIND_DATA` and
|
23
|
+
# `STORE` requests from the Kademlia paper are not part of the protocol since
|
24
|
+
# the Node Discovery Protocol does not provide DHT functionality.
|
25
|
+
#
|
26
|
+
# ## Joining the network
|
27
|
+
#
|
28
|
+
# When joining the network, fills its node table by performing a recursive Find
|
29
|
+
# Node operation with its own ID as the 'Target'. The initial Find Node request
|
30
|
+
# is sent to one or more bootstrap nodes.
|
31
|
+
#
|
32
|
+
# ## RPC Protocol
|
33
|
+
#
|
34
|
+
# RLPx nodes that want to accept incoming connections should listen on the same
|
35
|
+
# port number for UDP packets (Node Discovery Protocol) and TCP connections
|
36
|
+
# (RLPx protocol).
|
37
|
+
#
|
38
|
+
# All requests time out after 300ms. Requests are not re-sent.
|
39
|
+
#
|
40
|
+
# ## Packet Data
|
41
|
+
#
|
42
|
+
# All packets contain an `Expiration` date to guard against replay attacks. The
|
43
|
+
# date should be interpreted as a UNIX timestamp. The receiver should discard
|
44
|
+
# any packet whose `Expiration` value is in the past.
|
45
|
+
#
|
46
|
+
# ### Ping (type 0x01)
|
47
|
+
#
|
48
|
+
# Ping packets can be sent and received at any time. The receiver should reply
|
49
|
+
# with a Pong packet and update the IP/Port of the sender in its node table.
|
50
|
+
#
|
51
|
+
# PingNode packet-type: 0x01
|
52
|
+
#
|
53
|
+
# struct PingNode <= 59 bytes
|
54
|
+
# {
|
55
|
+
# h256 version = 0x3; <= 1
|
56
|
+
# Endpoint from; <= 23
|
57
|
+
# Endpoint to; <= 23
|
58
|
+
# unsigned expiration; <= 9
|
59
|
+
# }
|
60
|
+
#
|
61
|
+
# struct Endpoint <= 24 = [17,3,3]
|
62
|
+
# {
|
63
|
+
# unsigned address; // BE encoded 32-bit or 128-bit unsigned (layer3 address; size determins ipv4 vs ipv6)
|
64
|
+
# unsigned udpPort; // BE encoded 16-bit unsigned
|
65
|
+
# unsigned tcpPort; // BE encoded 16-bit unsigned
|
66
|
+
# }
|
67
|
+
#
|
68
|
+
# ### Pong (type 0x02)
|
69
|
+
#
|
70
|
+
# Pong is the reply to a Ping packet.
|
71
|
+
#
|
72
|
+
# Pong packet-type: 0x02
|
73
|
+
#
|
74
|
+
# struct Pong <= 66 bytes
|
75
|
+
# {
|
76
|
+
# Endpoint to;
|
77
|
+
# h256 echo;
|
78
|
+
# unsigned expiration;
|
79
|
+
# }
|
80
|
+
#
|
81
|
+
# ### Find Node (type 0x03)
|
82
|
+
#
|
83
|
+
# Find Node packets are sent to locate nodes close to a given target ID. The
|
84
|
+
# receiver should reply with a Neighbours packet containing the `k` nodes
|
85
|
+
# closest to target that it knows about.
|
86
|
+
#
|
87
|
+
# FindNode packet-type: 0x03
|
88
|
+
#
|
89
|
+
# struct FindNode <= 76 bytes
|
90
|
+
# {
|
91
|
+
# NodeId target; // Id of a node. The responding node will send back nodes closest to the target.
|
92
|
+
# unsigned expiration;
|
93
|
+
# }
|
94
|
+
#
|
95
|
+
# ### Neighbours (type 0x04)
|
96
|
+
#
|
97
|
+
# Neighbours is the reply to Find Node. It contains up to `k` nodes that the
|
98
|
+
# sender knows which are closest to the requested 'Target`.
|
99
|
+
#
|
100
|
+
# Neighbours packet-type: 0x04
|
101
|
+
#
|
102
|
+
# struct Neighbours <= 1423
|
103
|
+
# {
|
104
|
+
# list nodes: struct Neighbours <= 88...1411; 76...1219
|
105
|
+
# {
|
106
|
+
# inline Endpoint endpoint;
|
107
|
+
# NodeId node;
|
108
|
+
# };
|
109
|
+
# unsigned expiration;
|
110
|
+
# }
|
111
|
+
#
|
112
|
+
|
113
|
+
|
114
|
+
require 'devp2p/discovery/address'
|
115
|
+
require 'devp2p/discovery/node'
|
116
|
+
require 'devp2p/discovery/kademlia_protocol_adapter'
|
117
|
+
require 'devp2p/discovery/protocol'
|
118
|
+
require 'devp2p/discovery/transport'
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
require 'resolv'
|
5
|
+
|
6
|
+
module DEVp2p
|
7
|
+
module Discovery
|
8
|
+
|
9
|
+
class Address
|
10
|
+
|
11
|
+
attr :udp_port, :tcp_port
|
12
|
+
|
13
|
+
def self.from_endpoint(ip, udp_port, tcp_port="\x00\x00")
|
14
|
+
new(ip, udp_port, tcp_port, true)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(ip, udp_port, tcp_port=nil, from_binary=false)
|
18
|
+
tcp_port ||= udp_port
|
19
|
+
|
20
|
+
if from_binary
|
21
|
+
raise ArgumentError, "invalid ip" unless [4,16].include?(ip.size)
|
22
|
+
|
23
|
+
@udp_port = dec_port udp_port
|
24
|
+
@tcp_port = dec_port tcp_port
|
25
|
+
else
|
26
|
+
raise ArgumentError, "udp_port must be Integer" unless udp_port.is_a?(Integer)
|
27
|
+
raise ArgumentError, "tcp_port must be Integer" unless tcp_port.is_a?(Integer)
|
28
|
+
|
29
|
+
@udp_port = udp_port
|
30
|
+
@tcp_port = tcp_port
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
@ip = from_binary ? IPAddr.new_ntoh(ip) : IPAddr.new(ip)
|
35
|
+
rescue IPAddr::InvalidAddressError => e
|
36
|
+
ips = Resolv.getaddresses(ip).sort {|addr| addr =~ /:/ ? 1 : 0 } # use ipv4 first
|
37
|
+
raise e if ips.empty?
|
38
|
+
@ip = ips[0]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def ip
|
43
|
+
@ip.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
def update(addr)
|
47
|
+
@tcp_port = addr.tcp_port if @tcp_port.nil? || @tcp_port == 0
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# addresses equal if they share ip and udp_port
|
52
|
+
#
|
53
|
+
def ==(other)
|
54
|
+
[ip, udp_port] == [other.ip, other.udp_port]
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"Address(#{ip}:#{udp_port})"
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_h
|
62
|
+
{ip: ip, udp_port: udp_port, tcp_port: tcp_port}
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_b
|
66
|
+
[@ip.hton, enc_port(udp_port), enc_port(tcp_port)]
|
67
|
+
end
|
68
|
+
alias to_endpoint to_b
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def enc_port(p)
|
73
|
+
Utils.int_to_big_endian4(p)[-2..-1]
|
74
|
+
end
|
75
|
+
|
76
|
+
def dec_port(b)
|
77
|
+
Utils.big_endian_to_int(b)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module DEVp2p
|
4
|
+
module Discovery
|
5
|
+
|
6
|
+
class Node < Kademlia::Node
|
7
|
+
|
8
|
+
def self.from_uri(uri)
|
9
|
+
ip, port, pubkey = Utils.host_port_pubkey_from_uri(uri)
|
10
|
+
new(pubkey, Address.new(ip, port.to_i))
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :address
|
14
|
+
|
15
|
+
def initialize(pubkey, address=nil)
|
16
|
+
raise ArgumentError, 'invalid address' unless address.nil? || address.is_a?(Address)
|
17
|
+
|
18
|
+
super(pubkey)
|
19
|
+
|
20
|
+
self.address = address
|
21
|
+
@reputation = 0
|
22
|
+
@rlpx_version = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_uri
|
26
|
+
Utils.host_port_pubkey_to_uri(address.ip, address.udp_port, pubkey)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,342 @@
|
|
1
|
+
# -*- encoding : ascii-8bit -*-
|
2
|
+
|
3
|
+
module DEVp2p
|
4
|
+
module Discovery
|
5
|
+
|
6
|
+
class Protocol < Kademlia::WireInterface
|
7
|
+
|
8
|
+
VERSION = 4
|
9
|
+
|
10
|
+
EXPIRATION = 60 # let messages expire after N seconds
|
11
|
+
|
12
|
+
CMD_ID_MAP = {
|
13
|
+
ping: 1,
|
14
|
+
pong: 2,
|
15
|
+
find_node: 3,
|
16
|
+
neighbours: 4
|
17
|
+
}.freeze
|
18
|
+
REV_CMD_ID_MAP = CMD_ID_MAP.map {|k,v| [v,k] }.to_h.freeze
|
19
|
+
|
20
|
+
# number of required top-level list elements for each cmd_id.
|
21
|
+
# elements beyond this length are trimmed.
|
22
|
+
CMD_ELEM_COUNT_MAP = {
|
23
|
+
ping: 4,
|
24
|
+
poing: 3,
|
25
|
+
find_node: 2,
|
26
|
+
neighbours: 2
|
27
|
+
}
|
28
|
+
|
29
|
+
attr :pubkey, :kademlia
|
30
|
+
|
31
|
+
def initialize(app, transport)
|
32
|
+
@app = app
|
33
|
+
@transport = transport
|
34
|
+
|
35
|
+
@privkey = Utils.decode_hex app.config[:node][:privkey_hex]
|
36
|
+
@pubkey = Crypto.privtopub @privkey
|
37
|
+
|
38
|
+
@nodes = {} # nodeid => Node
|
39
|
+
@node = Node.new(pubkey, @transport.address)
|
40
|
+
|
41
|
+
@kademlia = KademliaProtocolAdapter.new @node, self
|
42
|
+
|
43
|
+
uri = Utils.host_port_pubkey_to_uri(ip, udp_port, pubkey)
|
44
|
+
logger.info "starting discovery proto", enode: uri
|
45
|
+
end
|
46
|
+
|
47
|
+
def bootstrap(nodes)
|
48
|
+
@kademlia.bootstrap(nodes) unless nodes.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# return node or create new, update address if supplied
|
53
|
+
#
|
54
|
+
def get_node(nodeid, address=nil)
|
55
|
+
raise ArgumentError, 'invalid nodeid' unless nodeid.size == Kademlia::PUBKEY_SIZE / 8
|
56
|
+
raise ArgumentError, 'must give either address or existing nodeid' unless address || @nodes.has_key?(nodeid)
|
57
|
+
|
58
|
+
@nodes[nodeid] = Node.new nodeid, address if !@nodes.has_key?(nodeid)
|
59
|
+
node = @nodes[nodeid]
|
60
|
+
|
61
|
+
if address
|
62
|
+
raise ArgumentError, 'address must be Address' unless address.instance_of?(Address)
|
63
|
+
node.address = address
|
64
|
+
end
|
65
|
+
|
66
|
+
node
|
67
|
+
end
|
68
|
+
|
69
|
+
def sign(msg)
|
70
|
+
msg = Crypto.keccak256 msg
|
71
|
+
Crypto.ecdsa_sign msg, @privkey
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# UDP packets are structured as follows:
|
76
|
+
#
|
77
|
+
# hash || signature || packet-type || packet-data
|
78
|
+
#
|
79
|
+
# * packet-type: single byte < 2**7 // valid values are [1,4]
|
80
|
+
# * packet-data: RLP encoded list. Packet properties are serialized in
|
81
|
+
# the order in which they're defined. See packet-data below.
|
82
|
+
#
|
83
|
+
# Offset |
|
84
|
+
# 0 | MDC | Ensures integrity of packet.
|
85
|
+
# 65 | signature | Ensures authenticity of sender, `SIGN(sender-privkey, MDC)`
|
86
|
+
# 97 | type | Single byte in range [1, 4] that determines the structure of Data
|
87
|
+
# 98 | data | RLP encoded, see section Packet Data
|
88
|
+
#
|
89
|
+
# The packets are signed and authenticated. The sender's Node ID is
|
90
|
+
# determined by recovering the public key from the signature.
|
91
|
+
#
|
92
|
+
# sender-pubkey = ECRECOVER(Signature)
|
93
|
+
#
|
94
|
+
# The integrity of the packet can then be verified by computing the
|
95
|
+
# expected MDC of the packet as:
|
96
|
+
#
|
97
|
+
# MDC = keccak256(sender-pubkey || type || data)
|
98
|
+
#
|
99
|
+
# As an optimization, implementations may look up the public key by the
|
100
|
+
# UDP sending address and compute MDC before recovering the sender ID. If
|
101
|
+
# the MDC values do not match, the packet can be dropped.
|
102
|
+
#
|
103
|
+
def pack(cmd_id, payload)
|
104
|
+
raise ArgumentError, 'invalid cmd_id' unless REV_CMD_ID_MAP.has_key?(cmd_id)
|
105
|
+
raise ArgumentError, 'payload must be Array' unless payload.is_a?(Array)
|
106
|
+
|
107
|
+
cmd_id = encode_cmd_id cmd_id
|
108
|
+
expiration = encode_expiration Time.now.to_i + EXPIRATION
|
109
|
+
|
110
|
+
encoded_data = RLP.encode(payload + [expiration])
|
111
|
+
signed_data = Crypto.keccak256 "#{cmd_id}#{encoded_data}"
|
112
|
+
signature = Crypto.ecdsa_sign signed_data, @privkey
|
113
|
+
|
114
|
+
raise InvalidSignatureError unless signature.size == 65
|
115
|
+
|
116
|
+
mdc = Crypto.keccak256 "#{signature}#{cmd_id}#{encoded_data}"
|
117
|
+
raise InvalidMACError unless mdc.size == 32
|
118
|
+
|
119
|
+
"#{mdc}#{signature}#{cmd_id}#{encoded_data}"
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# macSize = 256 / 8 = 32
|
124
|
+
# sigSize = 520 / 8 = 65
|
125
|
+
# headSize = macSize + sigSize = 97
|
126
|
+
#
|
127
|
+
def unpack(message)
|
128
|
+
mdc = message[0,32]
|
129
|
+
if mdc != Crypto.keccak256(message[32..-1])
|
130
|
+
logger.warn 'packet with wrong mcd'
|
131
|
+
raise InvalidMessageMAC
|
132
|
+
end
|
133
|
+
|
134
|
+
signature = message[32,65]
|
135
|
+
raise InvalidSignatureError unless signature.size == 65
|
136
|
+
|
137
|
+
signed_data = Crypto.keccak256(message[97..-1])
|
138
|
+
remote_pubkey = Crypto.ecdsa_recover(signed_data, signature)
|
139
|
+
raise InvalidKeyError unless remote_pubkey.size == Kademlia::PUBKEY_SIZE / 8
|
140
|
+
|
141
|
+
cmd_id = decode_cmd_id message[97]
|
142
|
+
cmd = REV_CMD_ID_MAP[cmd_id]
|
143
|
+
|
144
|
+
payload = RLP.decode message[98..-1], strict: false
|
145
|
+
raise InvalidPayloadError unless payload.instance_of?(Array)
|
146
|
+
|
147
|
+
# ignore excessive list elements as required by EIP-8
|
148
|
+
payload = payload[0, CMD_ELEM_COUNT_MAP[cmd]||payload.size]
|
149
|
+
|
150
|
+
return remote_pubkey, cmd_id, payload, mdc
|
151
|
+
end
|
152
|
+
|
153
|
+
def receive_message(address, message)
|
154
|
+
logger.debug "<<< message", address: address
|
155
|
+
raise ArgumentError, 'address must be Address' unless address.instance_of?(Address)
|
156
|
+
|
157
|
+
begin
|
158
|
+
remote_pubkey, cmd_id, payload, mdc = unpack message
|
159
|
+
|
160
|
+
# Note: as of discovery version 4, expiration is the last element for
|
161
|
+
# all packets. This might not be the case for a later version, but
|
162
|
+
# just popping the last element is good enough for now.
|
163
|
+
expiration = decode_expiration payload.pop
|
164
|
+
raise PacketExpired if Time.now.to_i > expiration
|
165
|
+
rescue DefectiveMessage
|
166
|
+
logger.debug $!
|
167
|
+
return
|
168
|
+
end
|
169
|
+
|
170
|
+
cmd = "recv_#{REV_CMD_ID_MAP[cmd_id]}"
|
171
|
+
nodeid = remote_pubkey
|
172
|
+
|
173
|
+
get_node(nodeid, address) unless @nodes.has_key?(nodeid)
|
174
|
+
send cmd, nodeid, payload, mdc
|
175
|
+
end
|
176
|
+
|
177
|
+
def send_message(node, message)
|
178
|
+
raise ArgumentError, 'node must have address' unless node.address
|
179
|
+
logger.debug ">>> message", address: node.address
|
180
|
+
@transport.send_message node.address, message
|
181
|
+
end
|
182
|
+
|
183
|
+
def send_ping(node)
|
184
|
+
raise ArgumentError, "node must be Node" unless node.is_a?(Node)
|
185
|
+
raise ArgumentError, "cannot ping self" if node == @node
|
186
|
+
|
187
|
+
logger.debug ">>> ping", remoteid: node
|
188
|
+
|
189
|
+
version = RLP::Sedes.big_endian_int.serialize VERSION
|
190
|
+
payload = [
|
191
|
+
version,
|
192
|
+
Address.new(ip, udp_port, tcp_port).to_endpoint,
|
193
|
+
node.address.to_endpoint
|
194
|
+
]
|
195
|
+
|
196
|
+
message = pack CMD_ID_MAP[:ping], payload
|
197
|
+
send_message node, message
|
198
|
+
|
199
|
+
message[0,32] # return the MDC to identify pongs
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Update ip, port in node table. Addresses can only be learned by ping
|
204
|
+
# messages.
|
205
|
+
#
|
206
|
+
def recv_ping(nodeid, payload, mdc)
|
207
|
+
if payload.size != 3
|
208
|
+
logger.error "invalid ping payload", payload: payload
|
209
|
+
return
|
210
|
+
end
|
211
|
+
|
212
|
+
node = get_node nodeid
|
213
|
+
logger.debug "<<< ping", node: node
|
214
|
+
|
215
|
+
remote_address = Address.from_endpoint(*payload[1]) # from
|
216
|
+
my_address = Address.from_endpoint(*payload[2]) # my address
|
217
|
+
|
218
|
+
get_node(nodeid).address.update remote_address
|
219
|
+
@kademlia.recv_ping node, mdc
|
220
|
+
end
|
221
|
+
|
222
|
+
def send_pong(node, token)
|
223
|
+
logger.debug ">>> pong", remoteid: node
|
224
|
+
|
225
|
+
payload = [node.address.to_endpoint, token]
|
226
|
+
raise InvalidPayloadError unless [4,16].include?(payload[0][0].size)
|
227
|
+
|
228
|
+
message = pack CMD_ID_MAP[:pong], payload
|
229
|
+
send_message node, message
|
230
|
+
end
|
231
|
+
|
232
|
+
def recv_pong(nodeid, payload, mdc)
|
233
|
+
if payload.size != 2
|
234
|
+
logger.error 'invalid pong payload', payload: payload
|
235
|
+
return
|
236
|
+
end
|
237
|
+
|
238
|
+
raise InvalidPayloadError unless payload[0].size == 3
|
239
|
+
raise InvalidPayloadError unless [4,16].include?(payload[0][0].size)
|
240
|
+
|
241
|
+
my_address = Address.from_endpoint *payload[0]
|
242
|
+
echoed = payload[1]
|
243
|
+
|
244
|
+
if @nodes.include?(nodeid)
|
245
|
+
node = get_node nodeid
|
246
|
+
@kademlia.recv_pong node, echoed
|
247
|
+
else
|
248
|
+
logger.debug "<<< unexpected pong from unknown node"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def send_find_node(node, target_node_id)
|
253
|
+
target_node_id = Utils.zpad_int target_node_id, Kademlia::PUBKEY_SIZE/8
|
254
|
+
logger.debug ">>> find_node", remoteid: node
|
255
|
+
|
256
|
+
message = pack CMD_ID_MAP[:find_node], [target_node_id]
|
257
|
+
send_message node, message
|
258
|
+
end
|
259
|
+
|
260
|
+
def recv_find_node(nodeid, payload, mdc)
|
261
|
+
node = get_node nodeid
|
262
|
+
|
263
|
+
logger.debug "<<< find_node", remoteid: node
|
264
|
+
raise InvalidPayloadError unless payload[0].size == Kademlia::PUBKEY_SIZE/8
|
265
|
+
|
266
|
+
target = Utils.big_endian_to_int payload[0]
|
267
|
+
@kademlia.recv_find_node node, target
|
268
|
+
end
|
269
|
+
|
270
|
+
def send_neighbours(node, neighbours)
|
271
|
+
raise ArgumentError, 'neighbours must be Array' unless neighbours.instance_of?(Array)
|
272
|
+
raise ArgumentError, 'neighbours must be Node' unless neighbours.all? {|n| n.is_a?(Node) }
|
273
|
+
|
274
|
+
nodes = neighbours.map {|n| n.address.to_endpoint + [n.pubkey] }
|
275
|
+
logger.debug ">>> neighbours", remoteid: node, count: nodes.size
|
276
|
+
|
277
|
+
message = pack CMD_ID_MAP[:neighbours], [nodes]
|
278
|
+
send_message node, message
|
279
|
+
end
|
280
|
+
|
281
|
+
def recv_neighbours(nodeid, payload, mdc)
|
282
|
+
node = get_node nodeid
|
283
|
+
raise InvalidPayloadError unless payload.size == 1
|
284
|
+
raise InvalidPayloadError unless payload[0].instance_of?(Array)
|
285
|
+
logger.debug "<<< neighbours", remoteid: node, count: payload[0].size
|
286
|
+
|
287
|
+
neighbours_set = payload[0].uniq
|
288
|
+
logger.warn "received duplicates" if neighbours_set.size < payload[0].size
|
289
|
+
|
290
|
+
neighbours = neighbours_set.map do |n|
|
291
|
+
if n.size != 4 || ![4,16].include?(n[0].size)
|
292
|
+
logger.error "invalid neighbours format", neighbours: n
|
293
|
+
return
|
294
|
+
end
|
295
|
+
|
296
|
+
n = n.dup
|
297
|
+
nodeid = n.pop
|
298
|
+
address = Address.from_endpoint *n
|
299
|
+
get_node nodeid, address
|
300
|
+
end
|
301
|
+
|
302
|
+
@kademlia.recv_neighbours node, neighbours
|
303
|
+
end
|
304
|
+
|
305
|
+
def ip
|
306
|
+
@app.config[:discovery][:listen_host]
|
307
|
+
end
|
308
|
+
|
309
|
+
def udp_port
|
310
|
+
@app.config[:discovery][:listen_port]
|
311
|
+
end
|
312
|
+
|
313
|
+
def tcp_port
|
314
|
+
@app.config[:p2p][:listen_port]
|
315
|
+
end
|
316
|
+
|
317
|
+
private
|
318
|
+
|
319
|
+
def logger
|
320
|
+
@logger ||= Logger.new("#{udp_port}.p2p.discovery").tap {|l| l.level = :info }
|
321
|
+
end
|
322
|
+
|
323
|
+
def encode_cmd_id(cmd_id)
|
324
|
+
cmd_id.chr
|
325
|
+
end
|
326
|
+
|
327
|
+
def decode_cmd_id(byte)
|
328
|
+
byte.ord
|
329
|
+
end
|
330
|
+
|
331
|
+
def encode_expiration(i)
|
332
|
+
RLP::Sedes.big_endian_int.serialize(i)
|
333
|
+
end
|
334
|
+
|
335
|
+
def decode_expiration(b)
|
336
|
+
RLP::Sedes.big_endian_int.deserialize(b)
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
end
|