script/research/clock_sync: code improvements

This commit is contained in:
aggstam
2022-05-06 18:42:33 +03:00
parent 4c1cf3cee6
commit c27da2b6f4
3 changed files with 181 additions and 21 deletions

View File

@@ -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]

View 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())
}
}

View File

@@ -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(())
}