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}