Skip to main content

moloch_verify/
runtime.rs

1//! Runtime invariant checking and monitoring.
2
3use std::collections::VecDeque;
4use std::sync::{Arc, RwLock};
5use std::time::{Duration, Instant};
6
7use crate::invariants::InvariantViolation;
8use crate::ChainState;
9
10/// Result of a runtime check.
11#[derive(Debug, Clone)]
12pub enum CheckResult {
13    /// Check passed.
14    Passed,
15    /// Check failed.
16    Failed(InvariantViolation),
17    /// Check was skipped.
18    Skipped,
19}
20
21impl CheckResult {
22    /// Check if passed.
23    pub fn is_ok(&self) -> bool {
24        matches!(self, Self::Passed)
25    }
26}
27
28/// A runtime check that can be executed.
29pub trait RuntimeCheck<S> {
30    /// Name of the check.
31    fn name(&self) -> &str;
32
33    /// Execute the check.
34    fn execute(&self, state: &S) -> CheckResult;
35
36    /// Priority (higher = more important).
37    fn priority(&self) -> u8 {
38        50
39    }
40}
41
42/// Pre-condition check.
43pub struct PreCondition<F> {
44    name: String,
45    check: F,
46}
47
48impl<F> PreCondition<F> {
49    /// Create a new pre-condition.
50    pub fn new(name: impl Into<String>, check: F) -> Self {
51        Self {
52            name: name.into(),
53            check,
54        }
55    }
56}
57
58impl<S, F> RuntimeCheck<S> for PreCondition<F>
59where
60    F: Fn(&S) -> bool,
61{
62    fn name(&self) -> &str {
63        &self.name
64    }
65
66    fn execute(&self, state: &S) -> CheckResult {
67        if (self.check)(state) {
68            CheckResult::Passed
69        } else {
70            CheckResult::Failed(InvariantViolation::new(
71                &self.name,
72                "pre-condition not satisfied",
73            ))
74        }
75    }
76
77    fn priority(&self) -> u8 {
78        100 // Pre-conditions are high priority
79    }
80}
81
82/// Post-condition check.
83pub struct PostCondition<F> {
84    name: String,
85    check: F,
86}
87
88impl<F> PostCondition<F> {
89    /// Create a new post-condition.
90    pub fn new(name: impl Into<String>, check: F) -> Self {
91        Self {
92            name: name.into(),
93            check,
94        }
95    }
96}
97
98impl<S, F> RuntimeCheck<S> for PostCondition<F>
99where
100    F: Fn(&S) -> bool,
101{
102    fn name(&self) -> &str {
103        &self.name
104    }
105
106    fn execute(&self, state: &S) -> CheckResult {
107        if (self.check)(state) {
108            CheckResult::Passed
109        } else {
110            CheckResult::Failed(InvariantViolation::new(
111                &self.name,
112                "post-condition not satisfied",
113            ))
114        }
115    }
116
117    fn priority(&self) -> u8 {
118        90 // Post-conditions are high priority
119    }
120}
121
122/// Record of a check execution.
123#[derive(Debug, Clone)]
124pub struct CheckRecord {
125    /// Check name.
126    pub name: String,
127    /// Result.
128    pub result: CheckResult,
129    /// Execution time.
130    pub duration: Duration,
131    /// Timestamp.
132    pub timestamp: Instant,
133}
134
135/// Monitor configuration.
136#[derive(Debug, Clone)]
137pub struct MonitorConfig {
138    /// Maximum history entries.
139    pub max_history: usize,
140    /// Check interval.
141    pub check_interval: Duration,
142    /// Stop on first failure.
143    pub stop_on_failure: bool,
144}
145
146impl Default for MonitorConfig {
147    fn default() -> Self {
148        Self {
149            max_history: 1000,
150            check_interval: Duration::from_secs(1),
151            stop_on_failure: false,
152        }
153    }
154}
155
156/// Runtime monitor for continuous verification.
157pub struct RuntimeMonitor<S> {
158    /// Configuration.
159    config: MonitorConfig,
160    /// Registered checks.
161    checks: Vec<Box<dyn RuntimeCheck<S> + Send + Sync>>,
162    /// Execution history.
163    history: Arc<RwLock<VecDeque<CheckRecord>>>,
164    /// Failure count.
165    failures: Arc<RwLock<usize>>,
166}
167
168impl<S: ChainState> RuntimeMonitor<S> {
169    /// Create a new monitor with default config.
170    pub fn new() -> Self {
171        Self::with_config(MonitorConfig::default())
172    }
173
174    /// Create with custom configuration.
175    pub fn with_config(config: MonitorConfig) -> Self {
176        Self {
177            config,
178            checks: Vec::new(),
179            history: Arc::new(RwLock::new(VecDeque::new())),
180            failures: Arc::new(RwLock::new(0)),
181        }
182    }
183
184    /// Add a check to the monitor.
185    pub fn add_check(&mut self, check: impl RuntimeCheck<S> + Send + Sync + 'static) {
186        self.checks.push(Box::new(check));
187        // Sort by priority (higher first)
188        self.checks.sort_by_key(|b| std::cmp::Reverse(b.priority()));
189    }
190
191    /// Run all checks against a state.
192    pub fn check_state(&self, state: &S) -> Vec<CheckRecord> {
193        let mut records = Vec::new();
194
195        for check in &self.checks {
196            let start = Instant::now();
197            let result = check.execute(state);
198            let duration = start.elapsed();
199
200            let record = CheckRecord {
201                name: check.name().to_string(),
202                result: result.clone(),
203                duration,
204                timestamp: start,
205            };
206
207            // Update failure count
208            if matches!(result, CheckResult::Failed(_)) {
209                *self.failures.write().unwrap() += 1;
210            }
211
212            records.push(record.clone());
213
214            // Add to history
215            let mut history = self.history.write().unwrap();
216            history.push_back(record);
217            while history.len() > self.config.max_history {
218                history.pop_front();
219            }
220
221            // Stop on failure if configured
222            if self.config.stop_on_failure && matches!(result, CheckResult::Failed(_)) {
223                break;
224            }
225        }
226
227        records
228    }
229
230    /// Get total failure count.
231    pub fn failure_count(&self) -> usize {
232        *self.failures.read().unwrap()
233    }
234
235    /// Get recent history.
236    pub fn recent_history(&self, count: usize) -> Vec<CheckRecord> {
237        self.history
238            .read()
239            .unwrap()
240            .iter()
241            .rev()
242            .take(count)
243            .cloned()
244            .collect()
245    }
246
247    /// Clear history.
248    pub fn clear_history(&self) {
249        self.history.write().unwrap().clear();
250        *self.failures.write().unwrap() = 0;
251    }
252}
253
254impl<S: ChainState> Default for RuntimeMonitor<S> {
255    fn default() -> Self {
256        Self::new()
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use moloch_core::{BlockHash, Hash};
264
265    struct MockState {
266        height: u64,
267        events: u64,
268    }
269
270    impl ChainState for MockState {
271        fn height(&self) -> u64 {
272            self.height
273        }
274
275        fn block_hash(&self) -> BlockHash {
276            BlockHash(Hash::ZERO)
277        }
278
279        fn mmr_root(&self) -> Hash {
280            Hash::ZERO
281        }
282
283        fn event_count(&self) -> u64 {
284            self.events
285        }
286    }
287
288    #[test]
289    fn test_precondition() {
290        let check = PreCondition::new("height_positive", |s: &MockState| s.height > 0);
291
292        let good_state = MockState {
293            height: 10,
294            events: 100,
295        };
296        assert!(check.execute(&good_state).is_ok());
297
298        let bad_state = MockState {
299            height: 0,
300            events: 0,
301        };
302        assert!(!check.execute(&bad_state).is_ok());
303    }
304
305    #[test]
306    fn test_runtime_monitor() {
307        let mut monitor = RuntimeMonitor::<MockState>::new();
308
309        monitor.add_check(PreCondition::new("height_check", |s: &MockState| {
310            s.height > 0
311        }));
312        monitor.add_check(PostCondition::new("events_check", |s: &MockState| {
313            s.events > 0
314        }));
315
316        let state = MockState {
317            height: 10,
318            events: 100,
319        };
320        let records = monitor.check_state(&state);
321
322        assert_eq!(records.len(), 2);
323        assert!(records.iter().all(|r| r.result.is_ok()));
324        assert_eq!(monitor.failure_count(), 0);
325    }
326
327    #[test]
328    fn test_monitor_failure_tracking() {
329        let mut monitor = RuntimeMonitor::<MockState>::new();
330        monitor.add_check(PreCondition::new("must_fail", |_: &MockState| false));
331
332        let state = MockState {
333            height: 10,
334            events: 100,
335        };
336        monitor.check_state(&state);
337
338        assert_eq!(monitor.failure_count(), 1);
339    }
340}