darkfi/event_graph/
util.rs1use std::{
20 collections::HashMap,
21 fs::{self, File, OpenOptions},
22 io::Write,
23 path::Path,
24 time::UNIX_EPOCH,
25};
26
27use darkfi_serial::{deserialize, deserialize_async, serialize};
28use sled_overlay::sled;
29use tinyjson::JsonValue;
30use tracing::error;
31
32use crate::{
33 event_graph::{Event, GENESIS_CONTENTS, INITIAL_GENESIS, NULL_ID, N_EVENT_PARENTS},
34 util::{encoding::base64, file::load_file},
35 Result,
36};
37
38#[cfg(feature = "rpc")]
39use crate::rpc::{
40 jsonrpc::{ErrorCode, JsonError, JsonResponse, JsonResult},
41 util::json_map,
42};
43
44use super::event::Header;
45
46pub(super) const HOUR: i64 = 3_600_000;
48
49pub(super) fn next_hour_timestamp(hours: i64) -> u64 {
52 let now = UNIX_EPOCH.elapsed().unwrap().as_millis() as i64;
54
55 let next_hour = (now / HOUR) * HOUR;
57
58 (next_hour + (HOUR * hours)) as u64
60}
61
62pub(super) fn hours_since(next_hour_ts: u64) -> u64 {
64 let now = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
66
67 let elapsed_seconds = now - next_hour_ts;
70
71 elapsed_seconds / HOUR as u64
73}
74
75pub fn next_rotation_timestamp(starting_timestamp: u64, rotation_period: u64) -> u64 {
77 if rotation_period == 0 {
79 panic!("Rotation period cannot be 0");
80 }
81 let hours_passed = hours_since(starting_timestamp);
83
84 let rotations_since_start = hours_passed.div_ceil(rotation_period);
88
89 let hours_until_next_rotation: i64 =
92 (rotations_since_start * rotation_period - hours_passed).try_into().unwrap();
93
94 if hours_until_next_rotation == 0 {
96 return next_hour_timestamp(1)
99 }
100 next_hour_timestamp(hours_until_next_rotation)
101}
102
103pub fn millis_until_next_rotation(next_rotation: u64) -> u64 {
107 let now = UNIX_EPOCH.elapsed().unwrap().as_millis() as u64;
111 if next_rotation < now {
112 panic!("Next rotation timestamp is in the past");
113 }
114 next_rotation - now
115}
116
117pub fn generate_genesis(hours_rotation: u64) -> Event {
119 let parents = [NULL_ID; N_EVENT_PARENTS];
120 let layer = 0;
121 let content = GENESIS_CONTENTS.to_vec();
122
123 if hours_rotation == 0 {
125 return Event { header: Header { timestamp: INITIAL_GENESIS, parents, layer }, content }
126 }
127
128 let hours_passed = hours_since(INITIAL_GENESIS);
130
131 let rotations_since_genesis = hours_passed / hours_rotation;
133
134 let timestamp = INITIAL_GENESIS + (rotations_since_genesis * hours_rotation * HOUR as u64);
136
137 Event { header: Header { timestamp, parents, layer }, content }
138}
139
140pub(super) fn replayer_log(datastore: &Path, cmd: String, value: Vec<u8>) -> Result<()> {
141 fs::create_dir_all(datastore)?;
142 let datastore = datastore.join("replayer.log");
143 if !datastore.exists() {
144 File::create(&datastore)?;
145 };
146
147 let mut file = OpenOptions::new().append(true).open(&datastore)?;
148 let v = base64::encode(&value);
149 let f = format!("{cmd} {v}");
150 writeln!(file, "{f}")?;
151
152 Ok(())
153}
154
155#[cfg(feature = "rpc")]
156pub async fn recreate_from_replayer_log(datastore: &Path) -> JsonResult {
157 let log_path = datastore.join("replayer.log");
158 if !log_path.exists() {
159 error!("Error loading replayed log");
160 return JsonResult::Error(JsonError::new(
161 ErrorCode::ParseError,
162 Some("Error loading replayed log".to_string()),
163 1,
164 ))
165 };
166
167 let reader = load_file(&log_path).unwrap();
168
169 let db_datastore = datastore.join("replayed_db");
170
171 let sled_db = sled::open(db_datastore).unwrap();
172 let dag = sled_db.open_tree("replayer").unwrap();
173
174 for line in reader.lines() {
175 let line = line.split(' ').collect::<Vec<&str>>();
176 if line[0] == "insert" {
177 let v = base64::decode(line[1]).unwrap();
178 let v: Event = deserialize(&v).unwrap();
179 let v_se = serialize(&v);
180 dag.insert(v.header.id().as_bytes(), v_se).unwrap();
181 }
182 }
183
184 let mut graph = HashMap::new();
185 for iter_elem in dag.iter() {
186 let (id, val) = iter_elem.unwrap();
187 let id = blake3::Hash::from_bytes((&id as &[u8]).try_into().unwrap());
188 let val: Event = deserialize_async(&val).await.unwrap();
189 graph.insert(id, val);
190 }
191
192 let json_graph = graph
193 .into_iter()
194 .map(|(k, v)| {
195 let key = k.to_string();
196 let value = JsonValue::from(v);
197 (key, value)
198 })
199 .collect();
200 let values = json_map([("dag", JsonValue::Object(json_graph))]);
201 let result = JsonValue::Object(HashMap::from([("eventgraph_info".to_string(), values)]));
202
203 JsonResponse::new(result, 1).into()
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_hours_since() {
212 let five_hours_ago = next_hour_timestamp(-5);
213 assert_eq!(hours_since(five_hours_ago), 5);
214
215 let this_hour = next_hour_timestamp(0);
216 assert_eq!(hours_since(this_hour), 0);
217 }
218
219 #[test]
220 fn test_next_rotation_timestamp() {
221 let starting_point = next_hour_timestamp(-10);
222 let rotation_period = 7;
223
224 let expected = next_hour_timestamp(4);
227 assert_eq!(next_rotation_timestamp(starting_point, rotation_period), expected);
228
229 let this_hour: u64 = next_hour_timestamp(0);
233 let next_hour = this_hour + 3_600_000u64; assert_eq!(next_hour, next_rotation_timestamp(this_hour, 1));
235 }
236
237 #[test]
238 #[should_panic]
239 fn test_next_rotation_timestamp_panics_on_overflow() {
240 next_rotation_timestamp(0, u64::MAX);
241 }
242
243 #[test]
244 #[should_panic]
245 fn test_next_rotation_timestamp_panics_on_division_by_zero() {
246 next_rotation_timestamp(0, 0);
247 }
248
249 #[test]
250 fn test_millis_until_next_rotation_is_within_rotation_interval() {
251 let hours_rotation = 1u64;
252 let rotation_interval = hours_rotation * 3_600_000u64;
254 let next_rotation_timestamp = next_rotation_timestamp(INITIAL_GENESIS, hours_rotation);
255 let s = millis_until_next_rotation(next_rotation_timestamp);
256 assert!(s < rotation_interval);
257 }
258}