Skip to content

Commit 4d14bc7

Browse files
committed
feat(p2p): Add ping salt and improve rtt information
1 parent 3794dd5 commit 4d14bc7

File tree

3 files changed

+35
-20
lines changed

3 files changed

+35
-20
lines changed

hathor/p2p/resources/status.py

Lines changed: 1 addition & 0 deletions
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

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from math import inf
15+
import uuid
16+
from collections import deque
1617
from typing import TYPE_CHECKING, Iterable, Optional
1718

1819
from structlog import get_logger
@@ -53,14 +54,15 @@ def __init__(self, protocol: 'HathorProtocol') -> None:
5354
# Time we sent last PING message.
5455
self.ping_start_time: Optional[float] = None
5556

57+
# Salt used in the last PING message.
58+
self.ping_salt: Optional[str] = None
59+
5660
# Time we got last PONG response to a PING message.
5761
self.ping_last_response: float = 0
5862

5963
# 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
64+
self.rtt_window: deque[float] = deque()
65+
self.MAX_RTT_WINDOW: int = 200 # Last 200 samples (~= 10 minutes)
6466

6567
# The last blocks from the best blockchain in the peer
6668
self.peer_best_blockchain: list[HeightInfo] = []
@@ -146,7 +148,7 @@ def handle_get_peers(self, payload: str) -> None:
146148
self.send_peers(self.protocol.connections.iter_ready_connections())
147149

148150
def send_peers(self, connections: Iterable['HathorProtocol']) -> None:
149-
""" Send a PEERS command with a list of all known peers.
151+
""" Send a PEERS command with a list of all connected peers.
150152
"""
151153
peers = []
152154
for conn in connections:
@@ -185,28 +187,40 @@ def send_ping(self) -> None:
185187
""" Send a PING command. Usually you would use `send_ping_if_necessary` to
186188
prevent wasting bandwidth.
187189
"""
190+
# Add a salt number to prevent peers from faking rtt.
188191
self.ping_start_time = self.reactor.seconds()
189-
self.send_message(ProtocolMessages.PING)
192+
self.ping_salt = str(uuid.uuid4())
193+
self.send_message(ProtocolMessages.PING, self.ping_salt)
190194

191-
def send_pong(self) -> None:
195+
def send_pong(self, salt: str) -> None:
192196
""" Send a PONG command as a response to a PING command.
193197
"""
194-
self.send_message(ProtocolMessages.PONG)
198+
self.send_message(ProtocolMessages.PONG, salt)
195199

196200
def handle_ping(self, payload: str) -> None:
197201
"""Executed when a PING command is received. It responds with a PONG message."""
198-
self.send_pong()
202+
self.send_pong(payload)
199203

200204
def handle_pong(self, payload: str) -> None:
201205
"""Executed when a PONG message is received."""
202206
if self.ping_start_time is None:
203207
# This should never happen.
204208
return
209+
if self.ping_salt != payload:
210+
# Ignore pong without salts.
211+
return
205212
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)
213+
rtt = self.ping_last_response - self.ping_start_time
214+
self.rtt_window.appendleft(rtt)
215+
if len(self.rtt_window) > self.MAX_RTT_WINDOW:
216+
self.rtt_window.pop()
208217
self.ping_start_time = None
209-
self.log.debug('rtt updated', rtt=self.ping_rtt, min_rtt=self.ping_min_rtt)
218+
self.ping_salt = None
219+
self.log.debug('rtt updated',
220+
latest=rtt,
221+
min=min(self.rtt_window),
222+
max=max(self.rtt_window),
223+
avg=sum(self.rtt_window) / len(self.rtt_window))
210224

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

tests/p2p/test_protocol.py

Lines changed: 7 additions & 7 deletions
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)