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