1use std::collections::VecDeque;
4use std::sync::{Arc, RwLock};
5use std::time::{Duration, Instant};
6
7use crate::invariants::InvariantViolation;
8use crate::ChainState;
9
10#[derive(Debug, Clone)]
12pub enum CheckResult {
13 Passed,
15 Failed(InvariantViolation),
17 Skipped,
19}
20
21impl CheckResult {
22 pub fn is_ok(&self) -> bool {
24 matches!(self, Self::Passed)
25 }
26}
27
28pub trait RuntimeCheck<S> {
30 fn name(&self) -> &str;
32
33 fn execute(&self, state: &S) -> CheckResult;
35
36 fn priority(&self) -> u8 {
38 50
39 }
40}
41
42pub struct PreCondition<F> {
44 name: String,
45 check: F,
46}
47
48impl<F> PreCondition<F> {
49 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 }
80}
81
82pub struct PostCondition<F> {
84 name: String,
85 check: F,
86}
87
88impl<F> PostCondition<F> {
89 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 }
120}
121
122#[derive(Debug, Clone)]
124pub struct CheckRecord {
125 pub name: String,
127 pub result: CheckResult,
129 pub duration: Duration,
131 pub timestamp: Instant,
133}
134
135#[derive(Debug, Clone)]
137pub struct MonitorConfig {
138 pub max_history: usize,
140 pub check_interval: Duration,
142 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
156pub struct RuntimeMonitor<S> {
158 config: MonitorConfig,
160 checks: Vec<Box<dyn RuntimeCheck<S> + Send + Sync>>,
162 history: Arc<RwLock<VecDeque<CheckRecord>>>,
164 failures: Arc<RwLock<usize>>,
166}
167
168impl<S: ChainState> RuntimeMonitor<S> {
169 pub fn new() -> Self {
171 Self::with_config(MonitorConfig::default())
172 }
173
174 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 pub fn add_check(&mut self, check: impl RuntimeCheck<S> + Send + Sync + 'static) {
186 self.checks.push(Box::new(check));
187 self.checks.sort_by_key(|b| std::cmp::Reverse(b.priority()));
189 }
190
191 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 if matches!(result, CheckResult::Failed(_)) {
209 *self.failures.write().unwrap() += 1;
210 }
211
212 records.push(record.clone());
213
214 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 if self.config.stop_on_failure && matches!(result, CheckResult::Failed(_)) {
223 break;
224 }
225 }
226
227 records
228 }
229
230 pub fn failure_count(&self) -> usize {
232 *self.failures.read().unwrap()
233 }
234
235 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 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}