mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
script/research/clock_sync: code improvements
This commit is contained in:
@@ -3,10 +3,21 @@ name = "clock_sync"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies.darkfi]
|
||||
path = "../../../"
|
||||
features = ["async-runtime", "rpc"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Ntp request
|
||||
ntp = "0.5.0"
|
||||
reqwest = "0.11.10"
|
||||
|
||||
# Misc
|
||||
async-std = "1.11.0"
|
||||
async-native-tls = "0.4.0"
|
||||
log = "0.4.16"
|
||||
serde_json = "1.0.81"
|
||||
tokio = { version = "1.18.1", features = ["full"] }
|
||||
simplelog = "0.12.0"
|
||||
thiserror = "1.0.24"
|
||||
|
||||
[workspace]
|
||||
|
||||
55
script/research/clock_sync/src/error.rs
Normal file
55
script/research/clock_sync/src/error.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ClockError {
|
||||
#[error("AsyncNativeTls error: '{0}'")]
|
||||
AsyncNativeTlsError(String),
|
||||
#[error("FromUtf8 error: '{0}'")]
|
||||
FromUtf8Error(String),
|
||||
#[error("System clock is not correct!")]
|
||||
InvalidClock,
|
||||
#[error("Io error: '{0}'")]
|
||||
IoError(String),
|
||||
#[error("NTP error: '{0}'")]
|
||||
NtpError(String),
|
||||
#[error("SerdeJson error: '{0}'")]
|
||||
SerdeJsonError(String),
|
||||
#[error("SystemTime error: '{0}'")]
|
||||
SysTimeError(String),
|
||||
}
|
||||
|
||||
pub type ClockResult<T> = std::result::Result<T, ClockError>;
|
||||
|
||||
impl From<async_native_tls::Error> for ClockError {
|
||||
fn from(err: async_native_tls::Error) -> ClockError {
|
||||
ClockError::AsyncNativeTlsError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for ClockError {
|
||||
fn from(err: std::string::FromUtf8Error) -> ClockError {
|
||||
ClockError::FromUtf8Error(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ntp::errors::Error> for ClockError {
|
||||
fn from(err: ntp::errors::Error) -> ClockError {
|
||||
ClockError::NtpError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ClockError {
|
||||
fn from(err: serde_json::Error) -> ClockError {
|
||||
ClockError::SerdeJsonError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ClockError {
|
||||
fn from(err: std::io::Error) -> ClockError {
|
||||
ClockError::IoError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::time::SystemTimeError> for ClockError {
|
||||
fn from(err: std::time::SystemTimeError) -> ClockError {
|
||||
ClockError::SysTimeError(err.to_string())
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,95 @@
|
||||
use serde_json::Value;
|
||||
use async_std::{
|
||||
io::{ReadExt, WriteExt},
|
||||
net::TcpStream,
|
||||
};
|
||||
use std::{
|
||||
error::Error,
|
||||
thread,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
// This is a very simple check to verify that system time is correct.
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Poll worldtimeapi.org for current UTC timestamp
|
||||
let response =
|
||||
reqwest::get("https://worldtimeapi.org/api/timezone/Etc/UTC").await?.text().await?;
|
||||
let worldtimeapi: Value = serde_json::from_str(&response).unwrap();
|
||||
println!("worldtimeapi: {}", worldtimeapi["unixtime"].to_string());
|
||||
use log::{debug, error, info};
|
||||
use serde_json::Value;
|
||||
use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
|
||||
|
||||
use darkfi::Result;
|
||||
|
||||
mod error;
|
||||
|
||||
use crate::error::{ClockError, ClockResult};
|
||||
|
||||
// Execution parameters
|
||||
const RETRIES: u8 = 5;
|
||||
const WORLDTIMEAPI_ADDRESS: &str = "worldtimeapi.org";
|
||||
const WORLDTIMEAPI_ADDRESS_WITH_PORT: &str = "worldtimeapi.org:443";
|
||||
const WORLDTIMEAPI_PAYLOAD: &[u8; 88] = b"GET /api/timezone/Etc/UTC HTTP/1.1\r\nHost: worldtimeapi.org\r\nAccept: application/json\r\n\r\n";
|
||||
const NTP_ADDRESS: &str = "0.pool.ntp.org:123";
|
||||
const EPOCH: u64 = 2208988800; //1900
|
||||
|
||||
// Raw https request execution for worldtimeapi
|
||||
async fn worldtimeapi_request() -> ClockResult<Value> {
|
||||
// Create connection
|
||||
let stream = TcpStream::connect(WORLDTIMEAPI_ADDRESS_WITH_PORT).await?;
|
||||
let mut stream = async_native_tls::connect(WORLDTIMEAPI_ADDRESS, stream).await?;
|
||||
stream.write_all(WORLDTIMEAPI_PAYLOAD).await?;
|
||||
|
||||
// Execute request
|
||||
let mut res = vec![0_u8; 1024];
|
||||
stream.read(&mut res).await?;
|
||||
|
||||
// Parse response
|
||||
let reply = String::from_utf8(res)?;
|
||||
let lines = reply.split('\n');
|
||||
// JSON data exist in last row of response
|
||||
let last = lines.last().unwrap().trim_matches(char::from(0));
|
||||
debug!("worldtimeapi json response: {}", last);
|
||||
let reply = serde_json::from_str(last)?;
|
||||
Ok(reply)
|
||||
}
|
||||
|
||||
// This is a very simple check to verify that system time is correct.
|
||||
// Retry loop is used to in case discrepancies are found.
|
||||
// If all retries fail, system clock is considered invalid.
|
||||
// TODO: 1. Add proxy functionality in order not to leak connections
|
||||
// 2. Improve requests and/or add extra protocols
|
||||
async fn check_clock() -> ClockResult<()> {
|
||||
debug!("System clock check started...");
|
||||
let mut r = 0;
|
||||
while r < RETRIES {
|
||||
let check = clock_check().await;
|
||||
match check {
|
||||
Ok(()) => break,
|
||||
Err(e) => error!("Error during clock check: {}", e),
|
||||
}
|
||||
r += 1;
|
||||
}
|
||||
|
||||
debug!("System clock check finished. Retries: {}", r);
|
||||
match r {
|
||||
RETRIES => Err(ClockError::InvalidClock),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn clock_check() -> ClockResult<()> {
|
||||
// Start elapsed time counter to cover for all requests and processing time
|
||||
let requests_start = Instant::now();
|
||||
// Poll worldtimeapi.org for current UTC timestamp
|
||||
let worldtimeapi_response = worldtimeapi_request().await?;
|
||||
|
||||
// Start elapsed time counter to cover for ntp request and processing time
|
||||
let ntp_request_start = Instant::now();
|
||||
// Poll ntp.org for current timestamp
|
||||
let address = "0.pool.ntp.org:123";
|
||||
let response: ntp::packet::Packet = ntp::request(address).unwrap();
|
||||
// Remove 1900 epoch(2208988800) to reach UTC timestamp
|
||||
let ntp_time = response.transmit_time.sec as u64 - 2208988800;
|
||||
println!("ntp_time: {}", ntp_time);
|
||||
let ntp_response: ntp::packet::Packet = ntp::request(NTP_ADDRESS)?;
|
||||
|
||||
// Extract worldtimeapi timestamp from json
|
||||
let mut worldtimeapi_time = worldtimeapi_response["unixtime"].as_u64().unwrap();
|
||||
|
||||
// Remove 1900 epoch to reach UTC timestamp for ntp timestamp
|
||||
let mut ntp_time = ntp_response.transmit_time.sec as u64 - EPOCH;
|
||||
|
||||
// Add elapsed time to respone times
|
||||
ntp_time += ntp_request_start.elapsed().as_secs();
|
||||
worldtimeapi_time += requests_start.elapsed().as_secs();
|
||||
|
||||
// To simulate wrong clock, we sleep some time
|
||||
//let one_sec = Duration::new(1, 0);
|
||||
@@ -27,11 +97,35 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// Current system time
|
||||
let system_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
|
||||
println!("SystemTime: {}", system_time);
|
||||
|
||||
debug!("worldtimeapi_time: {}", worldtimeapi_time);
|
||||
debug!("ntp_time: {}", ntp_time);
|
||||
debug!("system_time: {}", system_time);
|
||||
|
||||
// We verify that system time is equal to worldtimeapi or ntp
|
||||
let check = (system_time == worldtimeapi) || (system_time == ntp_time);
|
||||
assert!(check, "System clock is not correct!");
|
||||
let check = (system_time == worldtimeapi_time) && (system_time == ntp_time);
|
||||
match check {
|
||||
true => Ok(()),
|
||||
false => Err(ClockError::InvalidClock),
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> Result<()> {
|
||||
TermLogger::init(
|
||||
LevelFilter::Debug,
|
||||
Config::default(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
)?;
|
||||
|
||||
match check_clock().await {
|
||||
Ok(()) => info!("System clock is correct!"),
|
||||
Err(_) => {
|
||||
error!("System clock is invalid, terminating...");
|
||||
return Err(darkfi::Error::OperationFailed)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user