util.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. //! Timestamp arithmetic, genesis generation, and replay logging.
  19. use std::{
  20. collections::HashMap,
  21. fs::{self, File, OpenOptions},
  22. io::Write,
  23. path::Path,
  24. time::UNIX_EPOCH,
  25. };
  26. use darkfi_serial::{deserialize, deserialize_async, serialize};
  27. use sled_overlay::sled;
  28. use tinyjson::JsonValue;
  29. use super::{
  30. event::{Event, Header},
  31. EventGraphConfig, NULL_ID, N_EVENT_PARENTS,
  32. };
  33. use crate::{
  34. util::{encoding::base64, file::load_file},
  35. Result,
  36. };
  37. #[cfg(feature = "rpc")]
  38. use crate::rpc::{
  39. jsonrpc::{ErrorCode, JsonError, JsonResponse, JsonResult},
  40. util::json_map,
  41. };
  42. /// Milliseconds in one hour.
  43. pub(super) const HOUR: i64 = 3_600_000;
  44. /// Timestamp (millis) for the start of the hour `hours` offsets from now.
  45. pub(super) fn next_hour_timestamp(hours: i64) -> u64 {
  46. let now = UNIX_EPOCH.elapsed().unwrap().as_millis() as i64;
  47. ((now / HOUR) * HOUR + HOUR * hours) as u64
  48. }
  49. /// Whole hours elapsed since `ts`.
  50. pub(super) fn hours_since(ts: u64) -> u64 {
  51. let now = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
  52. (now - ts) / HOUR as u64
  53. }
  54. /// Timestamp of the next DAG rotation.
  55. ///
  56. /// # Panics
  57. ///
  58. /// Panics if `rotation_period` is zero.
  59. pub fn next_rotation_timestamp(starting_timestamp: u64, rotation_period: u64) -> u64 {
  60. if rotation_period == 0 {
  61. panic!("Rotation period cannot be 0");
  62. }
  63. let passed = hours_since(starting_timestamp);
  64. let rotations = passed.div_ceil(rotation_period);
  65. let until: i64 = (rotations * rotation_period - passed).try_into().unwrap();
  66. if until == 0 {
  67. next_hour_timestamp(1)
  68. } else {
  69. next_hour_timestamp(until)
  70. }
  71. }
  72. /// Milliseconds remaining until `next_rotation`.
  73. ///
  74. /// # Panics
  75. ///
  76. /// Panics if `next_rotation` is in the past.
  77. pub fn millis_until_next_rotation(next_rotation: u64) -> u64 {
  78. let now = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
  79. assert!(next_rotation >= now, "Next rotation is in the past");
  80. next_rotation - now
  81. }
  82. /// Generate the deterministic genesis event for the current rotation
  83. /// period, using the caller-provided [`EventGraphConfig`].
  84. ///
  85. /// * `hours_rotation == 0` -> timestamp is `initial_genesis`.
  86. /// * `hours_rotation > 0` -> timestamp is the most recent
  87. /// multiple-of-N boundary since `initial_genesis`.
  88. pub fn generate_genesis(config: &EventGraphConfig) -> Event {
  89. let timestamp = if config.hours_rotation == 0 {
  90. config.initial_genesis
  91. } else {
  92. let passed = hours_since(config.initial_genesis);
  93. let rotations = passed / config.hours_rotation;
  94. config.initial_genesis + (rotations * config.hours_rotation * HOUR as u64)
  95. };
  96. let content_hash = blake3::hash(&config.genesis_contents);
  97. let header = Header { timestamp, parents: [NULL_ID; N_EVENT_PARENTS], layer: 0, content_hash };
  98. Event { header, content: config.genesis_contents.clone() }
  99. }
  100. /// Append a replayer log entry for DAG state recreation.
  101. pub(super) fn replayer_log(datastore: &Path, cmd: String, value: Vec<u8>) -> Result<()> {
  102. fs::create_dir_all(datastore)?;
  103. let p = datastore.join("replayer.log");
  104. if !p.exists() {
  105. File::create(&p)?;
  106. }
  107. let mut f = OpenOptions::new().append(true).open(&p)?;
  108. writeln!(f, "{cmd} {}", base64::encode(&value))?;
  109. Ok(())
  110. }
  111. #[cfg(feature = "rpc")]
  112. pub async fn recreate_from_replayer_log(datastore: &Path) -> JsonResult {
  113. let log_path = datastore.join("replayer.log");
  114. if !log_path.exists() {
  115. return JsonResult::Error(JsonError::new(
  116. ErrorCode::ParseError,
  117. Some("Log not found".into()),
  118. 1,
  119. ))
  120. }
  121. let reader = load_file(&log_path).unwrap();
  122. let sled_db = sled::open(datastore.join("replayed_db")).unwrap();
  123. let dag = sled_db.open_tree("replayer").unwrap();
  124. for line in reader.lines() {
  125. let parts = line.split(' ').collect::<Vec<&str>>();
  126. if parts[0] == "insert" {
  127. let v: Event = deserialize(&base64::decode(parts[1]).unwrap()).unwrap();
  128. dag.insert(v.header.id().as_bytes(), serialize(&v)).unwrap();
  129. }
  130. }
  131. let mut graph = HashMap::new();
  132. for item in dag.iter() {
  133. let (id, val) = item.unwrap();
  134. let id = blake3::Hash::from_bytes((&id as &[u8]).try_into().unwrap());
  135. graph.insert(id, deserialize_async::<Event>(&val).await.unwrap());
  136. }
  137. let json_graph = graph.into_iter().map(|(k, v)| (k.to_string(), JsonValue::from(v))).collect();
  138. let values = json_map([("dag", JsonValue::Object(json_graph))]);
  139. JsonResponse::new(JsonValue::Object(HashMap::from([("eventgraph_info".into(), values)])), 1)
  140. .into()
  141. }
  142. /// Which DAG an event came from. Used by [`event_to_gource`] to
  143. /// pick the appropriate path prefix when formatting visualization
  144. /// output.
  145. #[derive(Copy, Clone, Debug)]
  146. pub enum DagKind {
  147. /// A rotating-DAG event (regular IRC traffic, etc.)
  148. Rotating,
  149. /// A static-DAG event (RLN registration or slash)
  150. Static,
  151. }
  152. impl DagKind {
  153. fn path_prefix(self) -> &'static str {
  154. match self {
  155. DagKind::Rotating => "rotating",
  156. DagKind::Static => "static",
  157. }
  158. }
  159. }
  160. /// Format an [`Event`] as a single Gource custom-log line.
  161. ///
  162. /// Output format (Gource custom log, pipe-delimited):
  163. ///
  164. /// ```text
  165. /// <unix-seconds>|<username>|A|/<dag-kind>/<layer>/<event-id-prefix>
  166. /// ```
  167. pub fn event_to_gource(ev: &Event, kind: DagKind) -> String {
  168. let unix_secs = ev.header.timestamp / 1_000;
  169. // First non-NULL parent -> 8-char hex prefix; otherwise "genesis".
  170. let username = ev
  171. .header
  172. .parents
  173. .iter()
  174. .find(|p| **p != NULL_ID)
  175. .map(|p| {
  176. let hex = p.to_hex();
  177. hex[..8.min(hex.len())].to_string()
  178. })
  179. .unwrap_or_else(|| "genesis".to_string());
  180. let id_hex = ev.id().to_hex();
  181. let id_prefix = &id_hex[..16.min(id_hex.len())];
  182. format!(
  183. "{}|{}|A|/{}/{:06}/{}",
  184. unix_secs,
  185. username,
  186. kind.path_prefix(),
  187. ev.header.layer,
  188. id_prefix,
  189. )
  190. }