Skip to main content

shadowforge_lib/adapters/
timelock.rs

1//! Time-lock adapter implementing the [`TimeLockService`] port.
2
3use chrono::{DateTime, Utc};
4
5use crate::domain::errors::TimeLockError;
6use crate::domain::ports::TimeLockService;
7use crate::domain::timelock;
8use crate::domain::types::{Payload, TimeLockPuzzle};
9
10/// Adapter delegating to the domain time-lock implementation.
11pub struct TimeLockServiceImpl {
12    /// Estimated squarings per second on current hardware.
13    squarings_per_sec: u64,
14}
15
16impl TimeLockServiceImpl {
17    /// Create a new time-lock service with the given calibration.
18    #[must_use]
19    pub const fn new(squarings_per_sec: u64) -> Self {
20        Self { squarings_per_sec }
21    }
22}
23
24impl Default for TimeLockServiceImpl {
25    fn default() -> Self {
26        Self::new(timelock::default_squarings_per_sec())
27    }
28}
29
30impl TimeLockService for TimeLockServiceImpl {
31    fn lock(
32        &self,
33        payload: &Payload,
34        unlock_at: DateTime<Utc>,
35    ) -> Result<TimeLockPuzzle, TimeLockError> {
36        timelock::create_puzzle(payload, unlock_at, self.squarings_per_sec)
37    }
38
39    fn unlock(&self, puzzle: &TimeLockPuzzle) -> Result<Payload, TimeLockError> {
40        timelock::solve_puzzle(puzzle)
41    }
42
43    fn try_unlock(&self, puzzle: &TimeLockPuzzle) -> Result<Option<Payload>, TimeLockError> {
44        timelock::try_solve_puzzle(puzzle, self.squarings_per_sec)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    type TestResult = Result<(), Box<dyn std::error::Error>>;
53
54    #[test]
55    fn adapter_roundtrip() -> TestResult {
56        let service = TimeLockServiceImpl::default();
57        let payload = Payload::from_bytes(b"adapter test".to_vec());
58        let unlock_at = Utc::now();
59
60        let puzzle = service.lock(&payload, unlock_at)?;
61        let recovered = service.unlock(&puzzle)?;
62
63        assert_eq!(recovered.as_bytes(), payload.as_bytes());
64        Ok(())
65    }
66
67    #[test]
68    fn adapter_try_unlock_returns_none_for_future() -> TestResult {
69        let service = TimeLockServiceImpl::default();
70        let payload = Payload::from_bytes(b"future test".to_vec());
71        let unlock_at = Utc::now() + chrono::Duration::hours(24);
72
73        let puzzle = service.lock(&payload, unlock_at)?;
74        let result = service.try_unlock(&puzzle)?;
75
76        assert!(result.is_none());
77        Ok(())
78    }
79}