darkfi/rpc/
clock_sync.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19//! Clock sync module
20use std::{net::UdpSocket, time::Duration};
21
22use tracing::debug;
23use url::Url;
24
25use crate::{util::time::Timestamp, Error, Result};
26
27/// Clock sync parameters
28const RETRIES: u8 = 10;
29/// TODO: Loop through set of ntps, get their average response concurrenyly.
30const NTP_ADDRESS: &str = "pool.ntp.org:123";
31const EPOCH: u32 = 2208988800; // 1900
32
33/// Raw NTP request execution
34pub async fn ntp_request() -> Result<Timestamp> {
35    // Create socket
36    let sock = UdpSocket::bind("0.0.0.0:0")?;
37    sock.set_read_timeout(Some(Duration::from_secs(5)))?;
38    sock.set_write_timeout(Some(Duration::from_secs(5)))?;
39
40    // Execute request
41    let mut packet = [0u8; 48];
42    packet[0] = (3 << 6) | (4 << 3) | 3;
43    sock.send_to(&packet, NTP_ADDRESS)?;
44
45    // Parse response
46    sock.recv(&mut packet[..])?;
47    let (bytes, _) = packet[40..44].split_at(core::mem::size_of::<u32>());
48    let num = u32::from_be_bytes(bytes.try_into().unwrap());
49    let timestamp = Timestamp::from_u64((num - EPOCH) as u64);
50
51    Ok(timestamp)
52}
53
54/// This is a very simple check to verify that the system time is correct.
55///
56/// Retry loop is used in case discrepancies are found.
57/// If all retries fail, system clock is considered invalid.
58/// TODO: 1. Add proxy functionality in order not to leak connections
59pub async fn check_clock(peers: &[Url]) -> Result<()> {
60    debug!(target: "rpc::clock_sync", "System clock check started...");
61    let mut r = 0;
62    while r < RETRIES {
63        if let Err(e) = clock_check(peers).await {
64            debug!(target: "rpc::clock_sync", "Error during clock check: {e:#?}");
65            r += 1;
66            continue
67        };
68        break
69    }
70
71    debug!(target: "rpc::clock_sync", "System clock check finished. Retries: {r}");
72    if r == RETRIES {
73        return Err(Error::InvalidClock)
74    }
75
76    Ok(())
77}
78
79async fn clock_check(_peers: &[Url]) -> Result<()> {
80    // Start elapsed time counter to cover for all requests and processing time
81    let requests_start = Timestamp::current_time();
82    // Poll one of the peers for their current UTC timestamp
83    //let peer_time = peer_request(peers).await?;
84    let peer_time = Some(Timestamp::current_time());
85
86    // Start elapsed time counter to cover for NTP request and processing time
87    let ntp_request_start = Timestamp::current_time();
88    // Poll ntp.org for current timestamp
89    let ntp_time = ntp_request().await?;
90
91    // Stop elapsed time counters
92    let ntp_elapsed_time = ntp_request_start.elapsed()?;
93    let requests_elapsed_time = requests_start.elapsed()?;
94
95    // Current system time
96    let system_time = Timestamp::current_time();
97
98    // Add elapsed time to response times
99    let ntp_time = ntp_time.checked_add(ntp_elapsed_time)?;
100    let peer_time = match peer_time {
101        None => None,
102        Some(p) => Some(p.checked_add(requests_elapsed_time)?),
103    };
104
105    debug!(target: "rpc::clock_sync", "peer_time: {peer_time:#?}");
106    debug!(target: "rpc::clock_sync", "ntp_time: {ntp_time:#?}");
107    debug!(target: "rpc::clock_sync", "system_time: {system_time:#?}");
108
109    // We verify that system time is equal to peer (if exists) and ntp times
110    let check = match peer_time {
111        Some(p) => (system_time == p) && (system_time == ntp_time),
112        None => system_time == ntp_time,
113    };
114
115    match check {
116        true => Ok(()),
117        false => Err(Error::InvalidClock),
118    }
119}