phoxal_runtime_contract/
origin.rs1use std::time::Duration;
2
3use crate::TimelineId;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct BootId(u64);
8
9impl BootId {
10 pub fn current() -> Self {
12 Self(host_boot_identity())
13 }
14
15 #[doc(hidden)]
17 pub const fn from_raw(value: u64) -> Self {
18 Self(value)
19 }
20
21 pub const fn get(self) -> u64 {
23 self.0
24 }
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub struct ExecutionOrigin {
30 boot: BootId,
31 boot_ns: u64,
32 timeline: TimelineId,
33}
34
35impl ExecutionOrigin {
36 pub fn try_mint() -> Option<Self> {
38 Some(Self {
39 boot: BootId::current(),
40 boot_ns: read_boot_clock_ns()?,
41 timeline: TimelineId::mint(),
42 })
43 }
44
45 pub fn mint() -> Self {
47 Self::try_mint().expect("the host boot clock must be readable to start an execution")
48 }
49
50 #[doc(hidden)]
52 pub const fn new(boot: BootId, boot_ns: u64, timeline: TimelineId) -> Self {
53 Self {
54 boot,
55 boot_ns,
56 timeline,
57 }
58 }
59
60 pub const fn boot(self) -> BootId {
62 self.boot
63 }
64
65 pub const fn boot_ns(self) -> u64 {
67 self.boot_ns
68 }
69
70 pub const fn timeline(self) -> TimelineId {
72 self.timeline
73 }
74
75 pub fn encode(self) -> String {
77 format!("{}:{}:{}", self.boot.0, self.boot_ns, self.timeline.get())
78 }
79
80 pub fn decode(value: &str) -> Option<Self> {
82 let mut parts = value.split(':');
83 let boot = BootId(parts.next()?.parse().ok()?);
84 let boot_ns = parts.next()?.parse().ok()?;
85 let timeline = TimelineId::from_raw(parts.next()?.parse().ok()?)?;
86 if parts.next().is_some() {
87 return None;
88 }
89 Some(Self {
90 boot,
91 boot_ns,
92 timeline,
93 })
94 }
95}
96
97fn read_boot_clock_ns() -> Option<u64> {
98 #[cfg(target_os = "linux")]
103 const CLOCK: libc::clockid_t = libc::CLOCK_BOOTTIME;
104 #[cfg(not(target_os = "linux"))]
105 const CLOCK: libc::clockid_t = libc::CLOCK_MONOTONIC;
106
107 let mut timespec = libc::timespec {
108 tv_sec: 0,
109 tv_nsec: 0,
110 };
111 let outcome = unsafe { libc::clock_gettime(CLOCK, &raw mut timespec) };
113 if outcome != 0 {
114 return None;
115 }
116 Some(
117 u64::try_from(timespec.tv_sec)
118 .ok()?
119 .saturating_mul(1_000_000_000)
120 .saturating_add(u64::try_from(timespec.tv_nsec).ok()?),
121 )
122}
123
124fn host_boot_identity() -> u64 {
125 #[cfg(target_os = "linux")]
126 {
127 if let Ok(boot_id) = std::fs::read_to_string("/proc/sys/kernel/random/boot_id") {
128 return fnv1a(boot_id.trim().as_bytes());
129 }
130 }
131 #[cfg(target_os = "macos")]
132 {
133 let mut boottime = libc::timeval {
134 tv_sec: 0,
135 tv_usec: 0,
136 };
137 let mut size = std::mem::size_of::<libc::timeval>();
138 let outcome = unsafe {
140 libc::sysctlbyname(
141 c"kern.boottime".as_ptr(),
142 (&raw mut boottime).cast(),
143 &raw mut size,
144 std::ptr::null_mut(),
145 0,
146 )
147 };
148 if outcome == 0 {
149 return fnv1a(&boottime.tv_sec.to_le_bytes());
150 }
151 }
152 let wall = std::time::SystemTime::now()
153 .duration_since(std::time::UNIX_EPOCH)
154 .map(|since| since.as_secs())
155 .unwrap_or(0);
156 let uptime = read_boot_clock_ns()
157 .map(|ns| Duration::from_nanos(ns).as_secs())
158 .unwrap_or(0);
159 fnv1a(&wall.saturating_sub(uptime).to_le_bytes())
160}
161
162fn fnv1a(bytes: &[u8]) -> u64 {
163 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
164 for byte in bytes {
165 hash ^= u64::from(*byte);
166 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
167 }
168 hash
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn origin_round_trips_and_rejects_noncanonical_shapes() {
177 let origin = ExecutionOrigin::mint();
178 assert_eq!(ExecutionOrigin::decode(&origin.encode()), Some(origin));
179 assert_eq!(ExecutionOrigin::decode("garbage"), None);
180 assert_eq!(ExecutionOrigin::decode("1:2:0"), None);
181 assert_eq!(ExecutionOrigin::decode("1:2:3:4"), None);
182 }
183
184 #[test]
185 fn boot_identity_is_stable_within_one_boot() {
186 assert_eq!(BootId::current(), BootId::current());
187 }
188}