/ src / rpc / clock_sync.rs
clock_sync.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 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
 20  use std::{net::UdpSocket, time::Duration};
 21  
 22  use tracing::debug;
 23  use url::Url;
 24  
 25  use crate::{util::time::Timestamp, Error, Result};
 26  
 27  /// Clock sync parameters
 28  const RETRIES: u8 = 10;
 29  /// TODO: Loop through set of ntps, get their average response concurrenyly.
 30  const NTP_ADDRESS: &str = "pool.ntp.org:123";
 31  const EPOCH: u32 = 2208988800; // 1900
 32  
 33  /// Raw NTP request execution
 34  pub 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
 59  pub 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  
 79  async 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  }