util.rs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 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. use std::time::UNIX_EPOCH;
  19. use crate::event_graph::{Event, GENESIS_CONTENTS, INITIAL_GENESIS, NULL_ID, N_EVENT_PARENTS};
  20. /// Seconds in a day
  21. pub(super) const DAY: i64 = 86400;
  22. /// Calculate the midnight timestamp given a number of days.
  23. /// If `days` is 0, calculate the midnight timestamp of today.
  24. pub(super) fn midnight_timestamp(days: i64) -> u64 {
  25. // Get current time
  26. let now = UNIX_EPOCH.elapsed().unwrap().as_secs() as i64;
  27. // Find the timestamp for the midnight of the current day
  28. let cur_midnight = (now / DAY) * DAY;
  29. // Adjust for days_from_now
  30. (cur_midnight + (DAY * days)) as u64
  31. }
  32. /// Calculate the number of days since a given midnight timestamp.
  33. pub(super) fn days_since(midnight_ts: u64) -> u64 {
  34. // Get current time
  35. let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
  36. // Calculate the difference between the current timestamp
  37. // and the given midnight timestamp
  38. let elapsed_seconds = now - midnight_ts;
  39. // Convert the elapsed seconds into days
  40. elapsed_seconds / DAY as u64
  41. }
  42. /// Calculate the timestamp of the next DAG rotation.
  43. pub(super) fn next_rotation_timestamp(starting_timestamp: u64, rotation_period: u64) -> u64 {
  44. // Prevent division by 0
  45. if rotation_period == 0 {
  46. panic!("Rotation period cannot be 0");
  47. }
  48. // Calculate the number of days since the given starting point
  49. let days_passed = days_since(starting_timestamp);
  50. // Find out how many rotation periods have occurred since
  51. // the starting point.
  52. // Note: when rotation_period = 1, rotations_since_start = days_passed
  53. let rotations_since_start = (days_passed + rotation_period - 1) / rotation_period;
  54. // Find out the number of days until the next rotation. Panic if result is beyond the range
  55. // of i64.
  56. let days_until_next_rotation: i64 =
  57. (rotations_since_start * rotation_period - days_passed).try_into().unwrap();
  58. // Get the timestamp for the next rotation
  59. if days_until_next_rotation == 0 {
  60. // If there are 0 days until the next rotation, we want
  61. // to rotate tomorrow, at midnight. This is a special case.
  62. return midnight_timestamp(1)
  63. }
  64. midnight_timestamp(days_until_next_rotation)
  65. }
  66. /// Calculate the time in seconds until the next_rotation, given
  67. /// as a timestamp.
  68. /// `next_rotation` here represents a timestamp in UNIX epoch format.
  69. pub(super) fn seconds_until_next_rotation(next_rotation: u64) -> u64 {
  70. // Store `now` in a variable in order to avoid a TOCTOU error.
  71. // There may be a drift of one second between this panic check and
  72. // the return value if we get unlucky.
  73. let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
  74. if next_rotation < now {
  75. panic!("Next rotation timestamp is in the past");
  76. }
  77. next_rotation - now
  78. }
  79. /// Generate a deterministic genesis event corresponding to the DAG's configuration.
  80. pub(super) fn generate_genesis(days_rotation: u64) -> Event {
  81. // Days rotation is u64 except zero
  82. let genesis_days_rotation = if days_rotation == 0 { 1 } else { days_rotation };
  83. // First check how many days passed since initial genesis.
  84. let days_passed = days_since(INITIAL_GENESIS);
  85. // Calculate the number of days_rotation intervals since INITIAL_GENESIS
  86. let rotations_since_genesis = days_passed / genesis_days_rotation;
  87. // Calculate the timestamp of the most recent event
  88. let timestamp =
  89. INITIAL_GENESIS + (rotations_since_genesis * genesis_days_rotation * DAY as u64);
  90. Event {
  91. timestamp,
  92. content: GENESIS_CONTENTS.to_vec(),
  93. parents: [NULL_ID; N_EVENT_PARENTS],
  94. layer: 0,
  95. }
  96. }
  97. #[cfg(test)]
  98. mod tests {
  99. use crate::event_graph::INITIAL_GENESIS;
  100. use super::*;
  101. #[test]
  102. fn test_days_since() {
  103. let five_days_ago = midnight_timestamp(-5);
  104. assert_eq!(days_since(five_days_ago), 5);
  105. let today = midnight_timestamp(0);
  106. assert_eq!(days_since(today), 0);
  107. }
  108. #[test]
  109. fn test_next_rotation_timestamp() {
  110. let starting_point = midnight_timestamp(-10);
  111. let rotation_period = 7;
  112. // The first rotation since the starting point would be 3 days ago.
  113. // So the next rotation should be 4 days from now.
  114. let expected = midnight_timestamp(4);
  115. assert_eq!(next_rotation_timestamp(starting_point, rotation_period), expected);
  116. // When starting from today with a rotation period of 1 (day),
  117. // we should get tomorrow's timestamp.
  118. // This is a special case.
  119. let midnight_today: u64 = midnight_timestamp(0);
  120. let midnight_tomorrow = midnight_today + 86400u64; // add a day, in seconds
  121. assert_eq!(midnight_tomorrow, next_rotation_timestamp(midnight_today, 1));
  122. }
  123. #[test]
  124. #[should_panic]
  125. fn test_next_rotation_timestamp_panics_on_overflow() {
  126. next_rotation_timestamp(0, u64::MAX);
  127. }
  128. #[test]
  129. #[should_panic]
  130. fn test_next_rotation_timestamp_panics_on_division_by_zero() {
  131. next_rotation_timestamp(0, 0);
  132. }
  133. #[test]
  134. fn test_seconds_until_next_rotation_is_within_rotation_interval() {
  135. let days_rotation = 1u64;
  136. // The amount of time in seconds between rotations.
  137. let rotation_interval = days_rotation * 86400u64;
  138. let next_rotation_timestamp = next_rotation_timestamp(INITIAL_GENESIS, days_rotation);
  139. let s = seconds_until_next_rotation(next_rotation_timestamp);
  140. assert!(s < rotation_interval);
  141. }
  142. }