Skip to content

Commit cb457b6

Browse files
authored
feat/fix: add functionality & fix off-by-ones in SlotCalculator (#20)
* fix off-by-one block calc, while still inclusive of end timestamp * feat/fix: add functionality & fix off-by-ones in SlotCalculator * Fix slot calcs with new offsets * comment
1 parent dedd9ea commit cb457b6

File tree

3 files changed

+96
-8
lines changed

3 files changed

+96
-8
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ openssl = { version = "0.10", features = ["vendored"] }
9898
reqwest = "0.12.9"
9999
url = "2.5.4"
100100
proptest = "1.6.0"
101-
101+
chrono = "0.4.38"
102102
hex = { package = "const-hex", version = "1.10", default-features = false, features = [
103103
"alloc",
104104
] }

crates/types/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ hex.workspace = true
1717
serde = { workspace = true, features = ["derive"] }
1818
serde_json.workspace = true
1919
thiserror.workspace = true
20+
chrono.workspace = true
2021

2122
[dev-dependencies]
2223
tokio = { version = "1.37.0", features = ["macros"] }

crates/types/src/slot.rs

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ impl SlotCalculator {
2222

2323
/// Creates a new slot calculator for Holesky.
2424
pub const fn holesky() -> Self {
25-
Self { start_timestamp: 1695902400, slot_offset: 0, slot_duration: 12 }
25+
// begin slot calculation for Holesky from block number 1, slot number 2, timestamp 1695902424
26+
// because of a strange 324 second gap between block 0 and 1 which
27+
// should have been 27 slots, but which is recorded as 2 slots in chain data
28+
Self { start_timestamp: 1695902424, slot_offset: 2, slot_duration: 12 }
2629
}
2730

2831
/// Creates a new slot calculator for Ethereum mainnet.
@@ -31,12 +34,36 @@ impl SlotCalculator {
3134
}
3235

3336
/// Calculates the slot for a given timestamp.
37+
/// This only works for timestamps that are GEQ to the chain's start_timestamp.
3438
pub const fn calculate_slot(&self, timestamp: u64) -> u64 {
3539
let elapsed = timestamp - self.start_timestamp;
36-
let slots = elapsed.saturating_div(self.slot_duration);
40+
let slots = elapsed.div_ceil(self.slot_duration);
3741
slots + self.slot_offset
3842
}
3943

44+
/// Calculates how many seconds into the block window for a given timestamp.
45+
pub const fn calculate_timepoint_within_slot(&self, timestamp: u64) -> u64 {
46+
(timestamp - self.slot_utc_offset()) % self.slot_duration
47+
}
48+
49+
/// Calculates the start and end timestamps for a given slot
50+
pub const fn calculate_slot_window(&self, slot_number: u64) -> (u64, u64) {
51+
let end_of_slot =
52+
((slot_number - self.slot_offset) * self.slot_duration) + self.start_timestamp;
53+
let start_of_slot = end_of_slot - self.slot_duration;
54+
(start_of_slot, end_of_slot)
55+
}
56+
57+
/// The current slot number.
58+
pub fn current_slot(&self) -> u64 {
59+
self.calculate_slot(chrono::Utc::now().timestamp() as u64)
60+
}
61+
62+
/// The current number of seconds into the block window.
63+
pub fn current_timepoint_within_slot(&self) -> u64 {
64+
self.calculate_timepoint_within_slot(chrono::Utc::now().timestamp() as u64)
65+
}
66+
4067
/// The timestamp of the first PoS block in the chain.
4168
pub const fn start_timestamp(&self) -> u64 {
4269
self.start_timestamp
@@ -51,6 +78,11 @@ impl SlotCalculator {
5178
pub const fn slot_duration(&self) -> u64 {
5279
self.slot_duration
5380
}
81+
82+
/// The offset in seconds between UTC time and slot mining times
83+
const fn slot_utc_offset(&self) -> u64 {
84+
self.start_timestamp % self.slot_duration
85+
}
5486
}
5587

5688
#[cfg(test)]
@@ -61,25 +93,80 @@ mod tests {
6193
fn test_basic_slot_calculations() {
6294
let calculator = SlotCalculator::new(0, 0, 12);
6395
assert_eq!(calculator.calculate_slot(0), 0);
96+
97+
assert_eq!(calculator.calculate_slot(1), 1);
98+
assert_eq!(calculator.calculate_slot(11), 1);
6499
assert_eq!(calculator.calculate_slot(12), 1);
65-
assert_eq!(calculator.calculate_slot(35), 2);
100+
101+
assert_eq!(calculator.calculate_slot(13), 2);
102+
assert_eq!(calculator.calculate_slot(23), 2);
103+
assert_eq!(calculator.calculate_slot(24), 2);
104+
105+
assert_eq!(calculator.calculate_slot(25), 3);
106+
assert_eq!(calculator.calculate_slot(35), 3);
66107
assert_eq!(calculator.calculate_slot(36), 3);
67108
}
68109

69110
#[test]
70111
fn test_holesky_slot_calculations() {
71112
let calculator = SlotCalculator::holesky();
72-
assert_eq!(calculator.calculate_slot(1695902400), 0);
73-
assert_eq!(calculator.calculate_slot(1695902412), 1);
74-
assert_eq!(calculator.calculate_slot(1695902416), 1);
75-
assert_eq!(calculator.calculate_slot(1738866996), 3580383);
113+
// block 1 == slot 2 == timestamp 1695902424
114+
// timestamp 1695902424 == slot 2
115+
assert_eq!(calculator.calculate_slot(1695902424), 2);
116+
// the next second, timestamp 1695902425 == slot 3
117+
assert_eq!(calculator.calculate_slot(1695902425), 3);
118+
119+
// block 3557085 == slot 3919127 == timestamp 1742931924
120+
// timestamp 1742931924 == slot 3919127
121+
assert_eq!(calculator.calculate_slot(1742931924), 3919127);
122+
// the next second, timestamp 1742931925 == slot 3919128
123+
assert_eq!(calculator.calculate_slot(1742931925), 3919128);
124+
}
125+
126+
#[test]
127+
fn test_holesky_slot_timepoint_calculations() {
128+
let calculator = SlotCalculator::holesky();
129+
// calculate timepoint in slot
130+
assert_eq!(calculator.calculate_timepoint_within_slot(1695902424), 0);
131+
assert_eq!(calculator.calculate_timepoint_within_slot(1695902425), 1);
132+
assert_eq!(calculator.calculate_timepoint_within_slot(1695902435), 11);
133+
assert_eq!(calculator.calculate_timepoint_within_slot(1695902436), 0);
134+
}
135+
136+
#[test]
137+
fn test_holesky_slot_window() {
138+
let calculator = SlotCalculator::holesky();
139+
// calculate slot window
140+
assert_eq!(calculator.calculate_slot_window(2), (1695902412, 1695902424));
141+
assert_eq!(calculator.calculate_slot_window(3), (1695902424, 1695902436));
76142
}
77143

78144
#[test]
79145
fn test_mainnet_slot_calculations() {
80146
let calculator = SlotCalculator::mainnet();
147+
assert_eq!(calculator.calculate_slot(1663224179), 4700013);
148+
assert_eq!(calculator.calculate_slot(1663224180), 4700014);
149+
81150
assert_eq!(calculator.calculate_slot(1738863035), 11003251);
82151
assert_eq!(calculator.calculate_slot(1738866239), 11003518);
83152
assert_eq!(calculator.calculate_slot(1738866227), 11003517);
84153
}
154+
155+
#[test]
156+
fn test_mainnet_slot_timepoint_calculations() {
157+
let calculator = SlotCalculator::mainnet();
158+
// calculate timepoint in slot
159+
assert_eq!(calculator.calculate_timepoint_within_slot(1663224179), 0);
160+
assert_eq!(calculator.calculate_timepoint_within_slot(1663224180), 1);
161+
assert_eq!(calculator.calculate_timepoint_within_slot(1663224190), 11);
162+
assert_eq!(calculator.calculate_timepoint_within_slot(1663224191), 0);
163+
}
164+
165+
#[test]
166+
fn test_ethereum_slot_window() {
167+
let calculator = SlotCalculator::mainnet();
168+
// calculate slot window
169+
assert_eq!(calculator.calculate_slot_window(4700013), (1663224167, 1663224179));
170+
assert_eq!(calculator.calculate_slot_window(4700014), (1663224179, 1663224191));
171+
}
85172
}

0 commit comments

Comments
 (0)