snops_common/state/
agent_status.rs

1use chrono::{DateTime, Utc};
2use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
4
5use super::snarkos_status::SnarkOSStatus;
6
7#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub enum NodeStatus {
9    /// The last known status of the node is unknown
10    #[default]
11    Unknown,
12    /// The node can be started and is not currently running
13    Standby,
14    /// The node waiting on other tasks to complete before starting
15    PendingStart,
16    /// The node is running
17    Running(SnarkOSStatus),
18    /// The node has exited with a status code
19    Exited(u8),
20    /// The node was online and is in the process of shutting down
21    Stopping,
22    /// The node has been stopped and some extra time is needed before it can be
23    /// started again
24    LedgerWriting,
25}
26
27impl From<SnarkOSStatus> for NodeStatus {
28    fn from(status: SnarkOSStatus) -> Self {
29        NodeStatus::Running(status)
30    }
31}
32
33#[derive(Debug, Default, Clone, Serialize, Deserialize)]
34pub struct LatestBlockInfo {
35    pub height: u32,
36    pub state_root: String,
37    pub block_hash: String,
38    pub previous_hash: String,
39    pub block_timestamp: i64,
40    pub update_time: DateTime<Utc>,
41}
42
43/// Age to stop considering blocks for scoring
44const MAX_BLOCK_AGE: u32 = 3600;
45/// Age to stop considering updates for scoring
46const MAX_UPDATE_AGE: u32 = 60;
47/// Number of seconds before update time is worth comparing over
48///
49/// If two infos have the same block time, and they are both within this many
50/// seconds, they are considered equal. Any infos older than this time are
51/// penalized for being stale.
52const UPDATE_AGE_INDIFFERENCE: u32 = 5;
53
54impl LatestBlockInfo {
55    /// Ranking function for block info to sort competing nodes by "freshness"
56    pub fn score(&self, now: &DateTime<Utc>) -> u32 {
57        // a score from 3600 to 0 based on the age of the block (3600 = block this
58        // second)
59        let block_age_score =
60            if let Some(block_time) = DateTime::from_timestamp(self.block_timestamp, 0) {
61                // the number of seconds since the block was created
62                let block_age = now
63                    .signed_duration_since(block_time)
64                    .num_seconds()
65                    .clamp(0, MAX_BLOCK_AGE as i64);
66                MAX_BLOCK_AGE.saturating_sub(block_age as u32)
67            } else {
68                0
69            };
70
71        // the number of seconds since the agent last updated the block info
72        let update_age = now
73            .signed_duration_since(self.update_time)
74            .num_seconds()
75            .clamp(0, MAX_UPDATE_AGE as i64);
76        // a score from 60 to 0 based on the age of the update (60 = update this
77        // second). Ignore the top 5 seconds for indifference between "fresh" agents
78        let update_age_score = MAX_UPDATE_AGE
79            .saturating_sub(update_age as u32)
80            .clamp(0, MAX_UPDATE_AGE - UPDATE_AGE_INDIFFERENCE);
81
82        // prefer blocks that are newer and have been updated more recently
83        // never prefer a block that is older than the latest
84        (block_age_score * (MAX_UPDATE_AGE >> 1) + update_age_score)
85            // Penalize agents that have not been updated in half the max update age
86            .saturating_sub(MAX_UPDATE_AGE >> 1)
87    }
88}
89
90pub type TransferId = u32;
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub enum TransferStatusUpdate {
94    /// The transfer has started.
95    Start {
96        /// A description of the transfer.
97        desc: String,
98        /// The number of bytes expected to transfer.
99        total: u64,
100        /// The time the transfer started.
101        time: DateTime<Utc>,
102    },
103    /// The transfer has made progress.
104    Progress {
105        /// The current number of bytes transferred.
106        downloaded: u64,
107    },
108    /// The transfer has ended.
109    End {
110        /// An interruption reason, if any.
111        interruption: Option<String>,
112    },
113    /// The transfer has been cleaned up.
114    Cleanup,
115}
116
117#[derive(Debug, Default, Clone, Serialize, Deserialize)]
118pub struct TransferStatus {
119    /// Description of the transfer
120    pub desc: String,
121    /// The time the transfer started (relative to the agent's startup time)
122    pub started_at: DateTime<Utc>,
123    /// The time the transfer was last updated (relative to the agent's startup)
124    pub updated_at: DateTime<Utc>,
125    /// Amount of data transferred in bytes
126    pub downloaded_bytes: u64,
127    /// Total amount of data to be transferred in bytes
128    pub total_bytes: u64,
129    /// A transfer interruption reason, if any.
130    pub interruption: Option<String>,
131}
132
133#[derive(Debug, Default, Clone, Serialize, Deserialize)]
134pub struct AgentStatus {
135    /// Version of the agent binary
136    pub agent_version: Option<String>,
137    /// The latest block info
138    pub block_info: Option<LatestBlockInfo>,
139    /// The status of the node
140    pub node_status: NodeStatus,
141    /// The time the agent was stated
142    pub start_time: Option<DateTime<Utc>>,
143    /// The time the agent connected to the control plane
144    pub connected_time: Option<DateTime<Utc>>,
145    /// A map of transfers in progress
146    pub transfers: IndexMap<TransferId, TransferStatus>,
147}