Skip to content

Commit e2d9278

Browse files
committed
feat(p2p): Add ping salt and improve rtt information
1 parent 9aad945 commit e2d9278

File tree

3 files changed

+38
-20
lines changed

3 files changed

+38
-20
lines changed

hathor/p2p/resources/status.py

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def render_GET(self, request):
6969
'address': '{}:{}'.format(remote.host, remote.port),
7070
'state': conn.state.state_name,
7171
# 'received_bytes': conn.received_bytes,
72+
'rtt': list(conn.state.rtt_window),
7273
'last_message': time.time() - conn.last_message,
7374
'plugins': status,
7475
'warning_flags': [flag.value for flag in conn.warning_flags],

hathor/p2p/states/ready.py

+30-13
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from math import inf
15+
from collections import deque
1616
from typing import TYPE_CHECKING, Iterable, Optional
1717

1818
from structlog import get_logger
@@ -53,14 +53,18 @@ def __init__(self, protocol: 'HathorProtocol') -> None:
5353
# Time we sent last PING message.
5454
self.ping_start_time: Optional[float] = None
5555

56+
# Salt used in the last PING message.
57+
self.ping_salt: Optional[str] = None
58+
59+
# Salt size in bytes.
60+
self.ping_salt_size: int = 32
61+
5662
# Time we got last PONG response to a PING message.
5763
self.ping_last_response: float = 0
5864

5965
# Round-trip time of the last PING/PONG.
60-
self.ping_rtt: float = inf
61-
62-
# Minimum round-trip time among PING/PONG.
63-
self.ping_min_rtt: float = inf
66+
self.rtt_window: deque[float] = deque()
67+
self.MAX_RTT_WINDOW: int = 200 # Last 200 samples (~= 10 minutes)
6468

6569
# The last blocks from the best blockchain in the peer
6670
self.peer_best_blockchain: list[HeightInfo] = []
@@ -146,7 +150,7 @@ def handle_get_peers(self, payload: str) -> None:
146150
self.send_peers(self.protocol.connections.iter_ready_connections())
147151

148152
def send_peers(self, connections: Iterable['HathorProtocol']) -> None:
149-
""" Send a PEERS command with a list of all known peers.
153+
""" Send a PEERS command with a list of all connected peers.
150154
"""
151155
peers = []
152156
for conn in connections:
@@ -185,28 +189,41 @@ def send_ping(self) -> None:
185189
""" Send a PING command. Usually you would use `send_ping_if_necessary` to
186190
prevent wasting bandwidth.
187191
"""
192+
# Add a salt number to prevent peers from faking rtt.
188193
self.ping_start_time = self.reactor.seconds()
189-
self.send_message(ProtocolMessages.PING)
194+
rng = self.protocol.connections.rng
195+
self.ping_salt = rng.randbytes(self.ping_salt_size).hex()
196+
self.send_message(ProtocolMessages.PING, self.ping_salt)
190197

191-
def send_pong(self) -> None:
198+
def send_pong(self, salt: str) -> None:
192199
""" Send a PONG command as a response to a PING command.
193200
"""
194-
self.send_message(ProtocolMessages.PONG)
201+
self.send_message(ProtocolMessages.PONG, salt)
195202

196203
def handle_ping(self, payload: str) -> None:
197204
"""Executed when a PING command is received. It responds with a PONG message."""
198-
self.send_pong()
205+
self.send_pong(payload)
199206

200207
def handle_pong(self, payload: str) -> None:
201208
"""Executed when a PONG message is received."""
202209
if self.ping_start_time is None:
203210
# This should never happen.
204211
return
212+
if self.ping_salt != payload:
213+
# Ignore pong without salts.
214+
return
205215
self.ping_last_response = self.reactor.seconds()
206-
self.ping_rtt = self.ping_last_response - self.ping_start_time
207-
self.ping_min_rtt = min(self.ping_min_rtt, self.ping_rtt)
216+
rtt = self.ping_last_response - self.ping_start_time
217+
self.rtt_window.appendleft(rtt)
218+
if len(self.rtt_window) > self.MAX_RTT_WINDOW:
219+
self.rtt_window.pop()
208220
self.ping_start_time = None
209-
self.log.debug('rtt updated', rtt=self.ping_rtt, min_rtt=self.ping_min_rtt)
221+
self.ping_salt = None
222+
self.log.debug('rtt updated',
223+
latest=rtt,
224+
min=min(self.rtt_window),
225+
max=max(self.rtt_window),
226+
avg=sum(self.rtt_window) / len(self.rtt_window))
210227

211228
def send_get_best_blockchain(self, n_blocks: Optional[int] = None) -> None:
212229
""" Send a GET-BEST-BLOCKCHAIN command, requesting a list of the latest

tests/p2p/test_protocol.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -356,14 +356,14 @@ def test_send_ping(self):
356356
self.conn.run_one_step() # TIPS
357357
self.assertIsConnected()
358358
self.clock.advance(5)
359-
self.assertEqual(b'PING\r\n', self.conn.peek_tr1_value())
360-
self.assertEqual(b'PING\r\n', self.conn.peek_tr2_value())
359+
self.assertRegex(self.conn.peek_tr1_value(), b'^PING .*\r\n')
360+
self.assertRegex(self.conn.peek_tr2_value(), b'^PING .*\r\n')
361361
self.conn.run_one_step() # PING
362362
self.conn.run_one_step() # GET-TIPS
363363
self.conn.run_one_step() # GET-BEST-BLOCKCHAIN
364-
self.assertEqual(b'PONG\r\n', self.conn.peek_tr1_value())
365-
self.assertEqual(b'PONG\r\n', self.conn.peek_tr2_value())
366-
while b'PONG\r\n' in self.conn.peek_tr1_value():
364+
self.assertRegex(self.conn.peek_tr1_value(), b'PONG .*\r\n')
365+
self.assertRegex(self.conn.peek_tr2_value(), b'PONG .*\r\n')
366+
while b'PONG ' in self.conn.peek_tr1_value():
367367
self.conn.run_one_step()
368368
self.assertEqual(self.clock.seconds(), self.conn.proto1.last_message)
369369

@@ -489,8 +489,8 @@ def test_send_ping(self):
489489
self.assertAndStepConn(self.conn, b'^TIPS')
490490
self.assertAndStepConn(self.conn, b'^TIPS')
491491
self.assertAndStepConn(self.conn, b'^TIPS-END')
492-
self.assertEqual(b'PONG\r\n', self.conn.peek_tr1_value())
493-
self.assertEqual(b'PONG\r\n', self.conn.peek_tr2_value())
492+
self.assertRegex(self.conn.peek_tr1_value(), b'^PONG .*\r\n')
493+
self.assertRegex(self.conn.peek_tr2_value(), b'^PONG .*\r\n')
494494
while b'PONG\r\n' in self.conn.peek_tr1_value():
495495
self.conn.run_one_step()
496496
self.assertEqual(self.clock.seconds(), self.conn.proto1.last_message)

0 commit comments

Comments
 (0)