casper_devnet/
state.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::path::{Path, PathBuf};
4use std::sync::{
5    Arc,
6    atomic::{AtomicBool, AtomicU32},
7};
8use time::OffsetDateTime;
9use tokio::fs as tokio_fs;
10
11/// Process classification used in logs and reporting.
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub enum ProcessKind {
14    Node,
15    Sidecar,
16}
17
18/// Logical process grouping (kept for parity with NCTL UX).
19#[derive(Clone, Debug, Serialize, Deserialize)]
20pub enum ProcessGroup {
21    Validators1,
22    Validators2,
23    Validators3,
24}
25
26/// Runtime status of a tracked process.
27#[derive(Clone, Debug, Serialize, Deserialize)]
28pub enum ProcessStatus {
29    Running,
30    Stopped,
31    Exited,
32    Unknown,
33    Skipped,
34}
35
36/// Persisted record of a process and its lifecycle details.
37#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct ProcessRecord {
39    pub id: String,
40    pub node_id: u32,
41    pub kind: ProcessKind,
42    pub group: ProcessGroup,
43    pub command: String,
44    pub args: Vec<String>,
45    pub cwd: String,
46    pub pid: Option<u32>,
47    #[serde(skip)]
48    pub pid_handle: Option<Arc<AtomicU32>>,
49    #[serde(skip)]
50    pub shutdown_handle: Option<Arc<AtomicBool>>,
51    pub stdout_path: String,
52    pub stderr_path: String,
53    #[serde(with = "time::serde::rfc3339::option")]
54    pub started_at: Option<OffsetDateTime>,
55    #[serde(with = "time::serde::rfc3339::option")]
56    pub stopped_at: Option<OffsetDateTime>,
57    pub exit_code: Option<i32>,
58    pub exit_signal: Option<i32>,
59    pub last_status: ProcessStatus,
60}
61
62/// State snapshot stored for process bookkeeping.
63#[derive(Clone, Debug, Serialize, Deserialize)]
64pub struct State {
65    #[serde(with = "time::serde::rfc3339")]
66    pub created_at: OffsetDateTime,
67    #[serde(with = "time::serde::rfc3339")]
68    pub updated_at: OffsetDateTime,
69    pub last_block_height: Option<u64>,
70    pub processes: Vec<ProcessRecord>,
71    #[serde(skip)]
72    path: PathBuf,
73}
74
75impl State {
76    pub async fn new(path: PathBuf) -> Result<Self> {
77        let now = OffsetDateTime::now_utc();
78        let state = Self {
79            created_at: now,
80            updated_at: now,
81            last_block_height: None,
82            processes: Vec::new(),
83            path,
84        };
85        state.persist().await?;
86        Ok(state)
87    }
88
89    pub async fn touch(&mut self) -> Result<()> {
90        self.updated_at = OffsetDateTime::now_utc();
91        self.persist().await
92    }
93
94    async fn persist(&self) -> Result<()> {
95        ensure_parent(&self.path).await?;
96        let contents = serde_json::to_string_pretty(self)?;
97        tokio_fs::write(&self.path, contents).await?;
98        Ok(())
99    }
100}
101
102async fn ensure_parent(path: &Path) -> Result<()> {
103    if let Some(parent) = path.parent() {
104        tokio_fs::create_dir_all(parent).await?;
105    }
106    Ok(())
107}