/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{ collections::HashMap, fs::{File, OpenOptions}, io::Write, time::UNIX_EPOCH, }; use darkfi_serial::{deserialize, deserialize_async, serialize}; use log::error; use tinyjson::JsonValue; use crate::{ event_graph::{Event, GENESIS_CONTENTS, INITIAL_GENESIS, NULL_ID, N_EVENT_PARENTS}, rpc::{ jsonrpc::{ErrorCode, JsonError, JsonResponse, JsonResult}, util::json_map, }, util::{encoding::base64, file::load_file, path::expand_path}, Result, }; /// Seconds in a day pub(super) const DAY: i64 = 86400; /// Calculate the midnight timestamp given a number of days. /// If `days` is 0, calculate the midnight timestamp of today. pub(super) fn midnight_timestamp(days: i64) -> u64 { // Get current time let now = UNIX_EPOCH.elapsed().unwrap().as_secs() as i64; // Find the timestamp for the midnight of the current day let cur_midnight = (now / DAY) * DAY; // Adjust for days_from_now (cur_midnight + (DAY * days)) as u64 } /// Calculate the number of days since a given midnight timestamp. pub(super) fn days_since(midnight_ts: u64) -> u64 { // Get current time let now = UNIX_EPOCH.elapsed().unwrap().as_secs(); // Calculate the difference between the current timestamp // and the given midnight timestamp let elapsed_seconds = now - midnight_ts; // Convert the elapsed seconds into days elapsed_seconds / DAY as u64 } /// Calculate the timestamp of the next DAG rotation. pub(super) fn next_rotation_timestamp(starting_timestamp: u64, rotation_period: u64) -> u64 { // Prevent division by 0 if rotation_period == 0 { panic!("Rotation period cannot be 0"); } // Calculate the number of days since the given starting point let days_passed = days_since(starting_timestamp); // Find out how many rotation periods have occurred since // the starting point. // Note: when rotation_period = 1, rotations_since_start = days_passed let rotations_since_start = (days_passed + rotation_period - 1) / rotation_period; // Find out the number of days until the next rotation. Panic if result is beyond the range // of i64. let days_until_next_rotation: i64 = (rotations_since_start * rotation_period - days_passed).try_into().unwrap(); // Get the timestamp for the next rotation if days_until_next_rotation == 0 { // If there are 0 days until the next rotation, we want // to rotate tomorrow, at midnight. This is a special case. return midnight_timestamp(1) } midnight_timestamp(days_until_next_rotation) } /// Calculate the time in seconds until the next_rotation, given /// as a timestamp. /// `next_rotation` here represents a timestamp in UNIX epoch format. pub(super) fn seconds_until_next_rotation(next_rotation: u64) -> u64 { // Store `now` in a variable in order to avoid a TOCTOU error. // There may be a drift of one second between this panic check and // the return value if we get unlucky. let now = UNIX_EPOCH.elapsed().unwrap().as_secs(); if next_rotation < now { panic!("Next rotation timestamp is in the past"); } next_rotation - now } /// Generate a deterministic genesis event corresponding to the DAG's configuration. pub(super) fn generate_genesis(days_rotation: u64) -> Event { // Days rotation is u64 except zero let timestamp = if days_rotation == 0 { INITIAL_GENESIS } else { // First check how many days passed since initial genesis. let days_passed = days_since(INITIAL_GENESIS); // Calculate the number of days_rotation intervals since INITIAL_GENESIS let rotations_since_genesis = days_passed / days_rotation; // Calculate the timestamp of the most recent event INITIAL_GENESIS + (rotations_since_genesis * days_rotation * DAY as u64) }; Event { timestamp, content: GENESIS_CONTENTS.to_vec(), parents: [NULL_ID; N_EVENT_PARENTS], layer: 0, } } pub(super) fn replayer_log(cmd: String, value: Vec) -> Result<()> { let mut replayer_log_file = expand_path("/tmp")?; replayer_log_file.push("replayer.log"); if !replayer_log_file.exists() { File::create(&replayer_log_file)?; }; let mut file = OpenOptions::new().append(true).open(&replayer_log_file)?; let v = base64::encode(&value); let f = format!("{cmd} {v}"); writeln!(file, "{}", f)?; Ok(()) } pub async fn recreate_from_replayer_log() -> JsonResult { let mut replayer_log_file = expand_path("/tmp").unwrap(); replayer_log_file.push("replayer.log"); if !replayer_log_file.exists() { error!("Error loading replaied log"); return JsonResult::Error(JsonError::new( ErrorCode::ParseError, Some("Error loading replaied log".to_string()), 1, )) }; let reader = load_file(&replayer_log_file).unwrap(); let datastore = expand_path("/tmp/replayed_db").unwrap(); let sled_db = sled::open(datastore).unwrap(); let dag = sled_db.open_tree("replayer").unwrap(); for line in reader.lines() { let line = line.split(' ').collect::>(); if line[0] == "insert" { let v = base64::decode(line[1]).unwrap(); let v: Event = deserialize(&v).unwrap(); let v_se = serialize(&v); dag.insert(v.id().as_bytes(), v_se).unwrap(); } } let mut graph = HashMap::new(); for iter_elem in dag.iter() { let (id, val) = iter_elem.unwrap(); let id = blake3::Hash::from_bytes((&id as &[u8]).try_into().unwrap()); let val: Event = deserialize_async(&val).await.unwrap(); graph.insert(id, val); } let json_graph = graph .into_iter() .map(|(k, v)| { let key = k.to_string(); let value = JsonValue::from(v); (key, value) }) .collect(); let values = json_map([("dag", JsonValue::Object(json_graph))]); let result = JsonValue::Object(HashMap::from([("eventgraph_info".to_string(), values)])); JsonResponse::new(result, 1).into() } #[cfg(test)] mod tests { use super::*; #[test] fn test_days_since() { let five_days_ago = midnight_timestamp(-5); assert_eq!(days_since(five_days_ago), 5); let today = midnight_timestamp(0); assert_eq!(days_since(today), 0); } #[test] fn test_next_rotation_timestamp() { let starting_point = midnight_timestamp(-10); let rotation_period = 7; // The first rotation since the starting point would be 3 days ago. // So the next rotation should be 4 days from now. let expected = midnight_timestamp(4); assert_eq!(next_rotation_timestamp(starting_point, rotation_period), expected); // When starting from today with a rotation period of 1 (day), // we should get tomorrow's timestamp. // This is a special case. let midnight_today: u64 = midnight_timestamp(0); let midnight_tomorrow = midnight_today + 86400u64; // add a day, in seconds assert_eq!(midnight_tomorrow, next_rotation_timestamp(midnight_today, 1)); } #[test] #[should_panic] fn test_next_rotation_timestamp_panics_on_overflow() { next_rotation_timestamp(0, u64::MAX); } #[test] #[should_panic] fn test_next_rotation_timestamp_panics_on_division_by_zero() { next_rotation_timestamp(0, 0); } #[test] fn test_seconds_until_next_rotation_is_within_rotation_interval() { let days_rotation = 1u64; // The amount of time in seconds between rotations. let rotation_interval = days_rotation * 86400u64; let next_rotation_timestamp = next_rotation_timestamp(INITIAL_GENESIS, days_rotation); let s = seconds_until_next_rotation(next_rotation_timestamp); assert!(s < rotation_interval); } }