First commit: check status without sending email
[stakingWatchdog.git] / src / main.rs
1 /*
2 * API Reference: https://ethereum.github.io/beacon-APIs/
3 */
4
5 #![cfg_attr(debug_assertions, allow(unused_variables, unused_imports, dead_code))]
6
7 use std::{
8 fs,
9 net::{IpAddr, Ipv4Addr},
10 thread,
11 time::{self, Duration},
12 };
13
14 use anyhow::{Context, Result};
15 use reqwest::StatusCode;
16 use serde::Deserialize;
17 use serde_json::{json, Value};
18
19 use crate::config::Config;
20
21 mod config;
22 // mod error;
23
24 const FILE_CONF: &str = "config.ron";
25 const CHECK_PERIOD: Duration = Duration::from_secs(5); // 5s.
26 const EMAIL_RESEND_PERIOD: Duration = Duration::from_secs(12 * 60 * 60); // 12h.
27 const BASE_URI: &str = "http://localhost:5052/eth/v1/";
28
29 fn main() -> Result<()> {
30 println!("Staking Watchdog");
31
32 let config = Config::read(FILE_CONF)?;
33
34 println!("Configuration: {:?}", config);
35
36 let mut time_last_email_send = time::Instant::now() - EMAIL_RESEND_PERIOD;
37
38 loop {
39 let time_beginning_loop = time::Instant::now();
40
41 if let Err(error) = check_validators(&config.pub_keys) {
42 println!("Error: {:?}", error);
43 if time::Instant::now() - time_last_email_send >= EMAIL_RESEND_PERIOD {
44 // Send e-mail.
45 println!("Sending email...");
46
47 time_last_email_send = time::Instant::now();
48 }
49 }
50
51 let elapsed = time::Instant::now() - time_beginning_loop;
52
53 if elapsed < CHECK_PERIOD {
54 let to_wait = CHECK_PERIOD - elapsed;
55 thread::sleep(to_wait);
56 }
57 }
58 }
59
60 #[derive(Debug)]
61 enum CheckError {
62 HttpError(String),
63 NotSync,
64 InvalidSyncStatus,
65 NodeHavingIssues,
66 UnknownCodeFromHealthCheck(u16),
67 ReqwestError(reqwest::Error),
68 ValidatorError { pub_key: String, message: String },
69 ValidatorStatusError { pub_key: String, message: String },
70 }
71
72 impl From<reqwest::Error> for CheckError {
73 fn from(value: reqwest::Error) -> Self {
74 CheckError::ReqwestError(value)
75 }
76 }
77
78 #[derive(Deserialize, Debug)]
79 struct JsonValidatorState {
80 data: JsonValidatorStateData,
81 }
82
83 #[derive(Deserialize, Debug)]
84 struct JsonValidatorStateData {
85 status: String,
86 }
87
88 #[derive(Deserialize, Debug)]
89 struct JsonError {
90 code: u16,
91 message: String,
92 }
93
94 fn check_validators(pub_keys: &[String]) -> std::result::Result<(), CheckError> {
95 let url = BASE_URI;
96 let client = reqwest::blocking::Client::new();
97
98 let request_health = client
99 .get(format!("{url}node/health"))
100 .header("accept", "application/json");
101 match request_health.send() {
102 Ok(resp) => {
103 println!("{resp:?}");
104 match resp.status().as_u16() {
105 200 => (),
106 206 => return Err(CheckError::NotSync),
107 400 => return Err(CheckError::InvalidSyncStatus),
108 503 => return Err(CheckError::NodeHavingIssues),
109 code => return Err(CheckError::UnknownCodeFromHealthCheck(code)),
110 }
111 }
112 Err(error) => {
113 println!("{error:?}");
114 return Err(CheckError::HttpError(error.to_string()));
115 }
116 }
117
118 return Err(CheckError::NotSync);
119
120 for pub_key in pub_keys {
121 let request = client
122 .get(format!("{url}beacon/states/head/validators/0x{pub_key}"))
123 .header("accept", "application/json");
124 match request.send() {
125 Ok(resp) => {
126 println!("{resp:?}");
127 match resp.status().as_u16() {
128 200 => {
129 let json: JsonValidatorState = resp.json()?;
130 // println!("JSON:\n{:?}", json); // For Debug.
131 if json.data.status != "active_ongoing" {
132 return Err(CheckError::ValidatorStatusError {
133 pub_key: pub_key.clone(),
134 message: format!("Status: {}", json.data.status),
135 });
136 }
137 }
138 code => {
139 let json: JsonError = resp.json()?;
140 // println!("JSON:\n{:?}", json); // For Debug.
141 return Err(CheckError::ValidatorError {
142 pub_key: pub_key.clone(),
143 message: format!(
144 "Http error code: {}, message: {}",
145 code, json.message
146 ),
147 });
148 }
149 }
150 }
151 Err(error) => {
152 println!("{error:?}");
153 return Err(CheckError::ValidatorError {
154 pub_key: pub_key.clone(),
155 message: error.to_string(),
156 });
157 }
158 }
159 }
160
161 // match request_builder
162 // .header("Authorization", format!("Apikey {}", api_key))
163 // .send()
164 // {
165 // Ok(resp) => {
166 // if resp.status().is_success() {
167 // let content = resp.text().unwrap();
168 // Ok(serde_json::from_str(&content).unwrap())
169 // } else {
170 // Err(Box::new(Error {
171 // message: format!("Request unsuccessful to {}: {:#?}", &url, resp),
172 // }))
173 // }
174 // }
175 // Err(error) => Err(Box::new(Error {
176 // message: format!("Error during request to {}: {:?}", &url, error),
177 // })),
178 // }
179
180 // 1) Check health.
181
182 // 2) Check each validators.
183
184 Ok(())
185 }