|
12 | 12 | # See the License for the specific language governing permissions and
|
13 | 13 | # limitations under the License.
|
14 | 14 |
|
15 |
| -from math import inf |
| 15 | +import uuid |
| 16 | +from collections import deque |
16 | 17 | from typing import TYPE_CHECKING, Iterable, Optional
|
17 | 18 |
|
18 | 19 | from structlog import get_logger
|
@@ -53,14 +54,15 @@ def __init__(self, protocol: 'HathorProtocol') -> None:
|
53 | 54 | # Time we sent last PING message.
|
54 | 55 | self.ping_start_time: Optional[float] = None
|
55 | 56 |
|
| 57 | + # Salt used in the last PING message. |
| 58 | + self.ping_salt: Optional[str] = None |
| 59 | + |
56 | 60 | # Time we got last PONG response to a PING message.
|
57 | 61 | self.ping_last_response: float = 0
|
58 | 62 |
|
59 | 63 | # 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) |
64 | 66 |
|
65 | 67 | # The last blocks from the best blockchain in the peer
|
66 | 68 | self.peer_best_blockchain: list[HeightInfo] = []
|
@@ -146,7 +148,7 @@ def handle_get_peers(self, payload: str) -> None:
|
146 | 148 | self.send_peers(self.protocol.connections.iter_ready_connections())
|
147 | 149 |
|
148 | 150 | 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. |
150 | 152 | """
|
151 | 153 | peers = []
|
152 | 154 | for conn in connections:
|
@@ -185,28 +187,40 @@ def send_ping(self) -> None:
|
185 | 187 | """ Send a PING command. Usually you would use `send_ping_if_necessary` to
|
186 | 188 | prevent wasting bandwidth.
|
187 | 189 | """
|
| 190 | + # Add a salt number to prevent peers from faking rtt. |
188 | 191 | 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) |
190 | 194 |
|
191 |
| - def send_pong(self) -> None: |
| 195 | + def send_pong(self, salt: str) -> None: |
192 | 196 | """ Send a PONG command as a response to a PING command.
|
193 | 197 | """
|
194 |
| - self.send_message(ProtocolMessages.PONG) |
| 198 | + self.send_message(ProtocolMessages.PONG, salt) |
195 | 199 |
|
196 | 200 | def handle_ping(self, payload: str) -> None:
|
197 | 201 | """Executed when a PING command is received. It responds with a PONG message."""
|
198 |
| - self.send_pong() |
| 202 | + self.send_pong(payload) |
199 | 203 |
|
200 | 204 | def handle_pong(self, payload: str) -> None:
|
201 | 205 | """Executed when a PONG message is received."""
|
202 | 206 | if self.ping_start_time is None:
|
203 | 207 | # This should never happen.
|
204 | 208 | return
|
| 209 | + if self.ping_salt != payload: |
| 210 | + # Ignore pong without salts. |
| 211 | + return |
205 | 212 | 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() |
208 | 217 | 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)) |
210 | 224 |
|
211 | 225 | def send_get_best_blockchain(self, n_blocks: Optional[int] = None) -> None:
|
212 | 226 | """ Send a GET-BEST-BLOCKCHAIN command, requesting a list of the latest
|
|
0 commit comments