phoxal_runtime_contract/
origin.rs1use std::time::Duration;
9
10use crate::identity::TimelineId;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct BootId(u64);
15
16impl BootId {
17 pub fn current() -> Self {
19 Self(host_boot_identity())
20 }
21
22 pub const fn from_raw(value: u64) -> Self {
28 Self(value)
29 }
30
31 pub const fn get(self) -> u64 {
33 self.0
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct ExecutionOrigin {
40 boot: BootId,
41 boot_ns: u64,
42 timeline: TimelineId,
43}
44
45impl ExecutionOrigin {
46 pub fn try_mint() -> Option<Self> {
48 Some(Self {
49 boot: BootId::current(),
50 boot_ns: read_boot_clock_ns()?,
51 timeline: TimelineId::mint(),
52 })
53 }
54
55 #[expect(
62 clippy::expect_used,
63 reason = "every robot instant this execution stamps is an offset on the boot clock \
64 read here; a host that cannot read it can produce no trustworthy instant, \
65 so continuing would mean fabricating time"
66 )]
67 pub fn mint() -> Self {
68 Self::try_mint().expect("the host boot clock must be readable to start an execution")
69 }
70
71 pub const fn new(boot: BootId, boot_ns: u64, timeline: TimelineId) -> Self {
77 Self {
78 boot,
79 boot_ns,
80 timeline,
81 }
82 }
83
84 pub const fn boot(self) -> BootId {
86 self.boot
87 }
88
89 pub const fn boot_ns(self) -> u64 {
91 self.boot_ns
92 }
93
94 pub const fn timeline(self) -> TimelineId {
96 self.timeline
97 }
98
99 pub fn encode(self) -> String {
101 format!("{}:{}:{}", self.boot.0, self.boot_ns, self.timeline.get())
102 }
103
104 pub fn decode(value: &str) -> Option<Self> {
106 let mut parts = value.split(':');
107 let boot = BootId(parts.next()?.parse().ok()?);
108 let boot_ns = parts.next()?.parse().ok()?;
109 let timeline = TimelineId::from_raw(parts.next()?.parse().ok()?)?;
110 if parts.next().is_some() {
111 return None;
112 }
113 Some(Self {
114 boot,
115 boot_ns,
116 timeline,
117 })
118 }
119}
120
121fn read_boot_clock_ns() -> Option<u64> {
122 #[cfg(target_os = "linux")]
127 const CLOCK: libc::clockid_t = libc::CLOCK_BOOTTIME;
128 #[cfg(not(target_os = "linux"))]
129 const CLOCK: libc::clockid_t = libc::CLOCK_MONOTONIC;
130
131 let mut timespec = libc::timespec {
132 tv_sec: 0,
133 tv_nsec: 0,
134 };
135 let outcome = unsafe { libc::clock_gettime(CLOCK, &raw mut timespec) };
137 if outcome != 0 {
138 return None;
139 }
140 Some(
141 u64::try_from(timespec.tv_sec)
142 .ok()?
143 .saturating_mul(1_000_000_000)
144 .saturating_add(u64::try_from(timespec.tv_nsec).ok()?),
145 )
146}
147
148fn host_boot_identity() -> u64 {
149 #[cfg(target_os = "linux")]
150 {
151 if let Ok(boot_id) = std::fs::read_to_string("/proc/sys/kernel/random/boot_id") {
152 return fnv1a(boot_id.trim().as_bytes());
153 }
154 }
155 #[cfg(target_os = "macos")]
156 {
157 let mut boottime = libc::timeval {
158 tv_sec: 0,
159 tv_usec: 0,
160 };
161 let mut size = std::mem::size_of::<libc::timeval>();
162 let outcome = unsafe {
164 libc::sysctlbyname(
165 c"kern.boottime".as_ptr(),
166 (&raw mut boottime).cast(),
167 &raw mut size,
168 std::ptr::null_mut(),
169 0,
170 )
171 };
172 if outcome == 0 {
173 return fnv1a(&boottime.tv_sec.to_le_bytes());
174 }
175 }
176 let wall = std::time::SystemTime::now()
177 .duration_since(std::time::UNIX_EPOCH)
178 .map(|since| since.as_secs())
179 .unwrap_or(0);
180 let uptime = read_boot_clock_ns()
181 .map(|ns| Duration::from_nanos(ns).as_secs())
182 .unwrap_or(0);
183 fnv1a(&wall.saturating_sub(uptime).to_le_bytes())
184}
185
186fn fnv1a(bytes: &[u8]) -> u64 {
187 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
188 for byte in bytes {
189 hash ^= u64::from(*byte);
190 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
191 }
192 hash
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn origin_round_trips_and_rejects_noncanonical_shapes() {
201 let origin = ExecutionOrigin::mint();
202 assert_eq!(ExecutionOrigin::decode(&origin.encode()), Some(origin));
203 assert_eq!(ExecutionOrigin::decode("garbage"), None);
204 assert_eq!(ExecutionOrigin::decode("1:2:0"), None);
205 assert_eq!(ExecutionOrigin::decode("1:2:3:4"), None);
206 }
207
208 #[test]
209 fn boot_identity_is_stable_within_one_boot() {
210 assert_eq!(BootId::current(), BootId::current());
211 }
212}