2 * API Reference: https://ethereum.github.io/beacon-APIs/
10 time
::{self, Duration
},
15 message
::header
::ContentType
, transport
::smtp
::authentication
::Credentials
, Message
,
16 SmtpTransport
, Transport
,
18 use serde
::Deserialize
;
20 use crate::config
::Config
;
24 const FILE_CONF
: &str = "config.ron";
25 const CHECK_PERIOD
: Duration
= Duration
::from_secs(10); // 10 s.
26 const PING_TIMEOUT
: Duration
= Duration
::from_secs(10); // 10 s.
27 const EMAIL_RESEND_PERIOD
: Duration
= Duration
::from_secs(2 * 60 * 60); // 2 h.
28 const STATE_PRINT_PERIOD
: Duration
= Duration
::from_secs(15 * 60); // 15 min.
29 const BASE_URI
: &str = "http://localhost:5052/eth/v1/";
31 fn main() -> Result
<()> {
32 println!("Staking Watchdog");
34 let config
= Config
::read(FILE_CONF
)?
;
37 "Configuration: {:?}",
39 smtp_password
: "*****".to_string(),
44 let check_alive_error_mutex
= start_check_alive_thread();
46 let mut time_last_email_send
= time
::Instant
::now() - EMAIL_RESEND_PERIOD
;
47 let mut time_last_state_printed
= time
::Instant
::now() - STATE_PRINT_PERIOD
;
48 let mut error_state
= false;
51 let time_beginning_loop
= time
::Instant
::now();
53 if let Err(error
) = check_validators(&config
.pub_keys
)
55 .and(check_alive_error_mutex
.lock().unwrap().as_ref())
58 println!("Error: {:?}", error
);
59 if time
::Instant
::now() - time_last_email_send
>= EMAIL_RESEND_PERIOD
{
61 println!("Sending email...");
63 "Watchdog: Staking error",
64 &format!("Error: {}", error
),
65 &config
.smtp_relay_address
,
67 &config
.smtp_password
,
69 Err(email_error
) => println!("Error sending email: {:?}", email_error
),
71 println!("Email successfully sent");
72 time_last_email_send
= time
::Instant
::now();
79 println!("End of erroneous state");
82 if time
::Instant
::now() - time_last_state_printed
>= STATE_PRINT_PERIOD
{
83 println!("No error detected");
84 time_last_state_printed
= time
::Instant
::now();
88 let elapsed
= time
::Instant
::now() - time_beginning_loop
;
90 if elapsed
< CHECK_PERIOD
{
91 let to_wait
= CHECK_PERIOD
- elapsed
;
92 thread
::sleep(to_wait
);
103 UnknownCodeFromHealthCheck(u16),
104 ReqwestError(reqwest
::Error
),
105 ValidatorError
{ pub_key
: String
, message
: String
},
106 ValidatorStatusError
{ pub_key
: String
, message
: String
},
110 impl fmt
::Display
for CheckError
{
111 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
113 CheckError
::HttpError(text
) => {
116 "Beacon node health check can't be reached (HTTP error): {}",
120 CheckError
::NotSync
=> {
123 "Beacon node health check is syncing (currently not sync)"
126 CheckError
::InvalidSyncStatus
=> {
127 write!(f
, "Beacon node health check returns an invalid status code")
129 CheckError
::NodeHavingIssues
=> {
132 "Beacon node health check is not initilized or having issue"
135 CheckError
::UnknownCodeFromHealthCheck(code
) => {
138 "Beacon node health check returns an unknown code: {}",
142 CheckError
::ReqwestError(error
) => {
143 write!(f
, "Error from reqwest: {}", error
)
145 CheckError
::ValidatorError
{ pub_key
, message
} => {
146 write!(f
, "Validator '{}' returns an error: {}", pub_key
, message
)
148 CheckError
::ValidatorStatusError
{ pub_key
, message
} => {
151 "Validator '{}' returns a status error: {}",
155 CheckError
::CheckAlive
=> {
156 write!(f
, "Check alive ping hasn't been received")
162 impl From
<reqwest
::Error
> for CheckError
{
163 fn from(value
: reqwest
::Error
) -> Self {
164 CheckError
::ReqwestError(value
)
168 #[derive(Deserialize, Debug)]
169 struct JsonValidatorState
{
170 data
: JsonValidatorStateData
,
173 #[derive(Deserialize, Debug)]
174 struct JsonValidatorStateData
{
178 #[derive(Deserialize, Debug)]
184 fn check_validators(pub_keys
: &[String
]) -> std
::result
::Result
<(), CheckError
> {
186 let client
= reqwest
::blocking
::Client
::new();
188 let request_health
= client
189 .get(format!("{url}node/health"))
190 .header("accept", "application/json");
191 match request_health
.send() {
193 // println!("{resp:?}"); // For debug.
194 match resp
.status().as_u16() {
196 206 => return Err(CheckError
::NotSync
),
197 400 => return Err(CheckError
::InvalidSyncStatus
),
198 503 => return Err(CheckError
::NodeHavingIssues
),
199 code
=> return Err(CheckError
::UnknownCodeFromHealthCheck(code
)),
203 println!("{error:?}");
204 return Err(CheckError
::HttpError(error
.to_string()));
208 for pub_key
in pub_keys
{
210 .get(format!("{url}beacon/states/head/validators/0x{pub_key}"))
211 .header("accept", "application/json");
212 match request
.send() {
214 // println!("{resp:?}"); // For debug.
215 match resp
.status().as_u16() {
217 let json
: JsonValidatorState
= resp
.json()?
;
218 if json
.data
.status
!= "active_ongoing" {
219 return Err(CheckError
::ValidatorStatusError
{
220 pub_key
: pub_key
.clone(),
221 message
: format!("Status: {}", json
.data
.status
),
226 let json
: JsonError
= resp
.json()?
;
227 return Err(CheckError
::ValidatorError
{
228 pub_key
: pub_key
.clone(),
230 "Http error code: {}, message: {}",
238 return Err(CheckError
::ValidatorError
{
239 pub_key
: pub_key
.clone(),
240 message
: error
.to_string(),
252 smtp_relay_address
: &str,
256 let email
= Message
::builder()
258 .from("Staking Watchdog <redmine@d-lan.net>".parse()?
)
259 .to("Greg Burri <greg.burri@gmail.com>".parse()?
)
261 .header(ContentType
::TEXT_PLAIN
)
262 .body(body
.to_string())?
;
264 let creds
= Credentials
::new(login
.to_string(), pass
.to_string());
266 // Open a remote connection to gmail
267 let mailer
= SmtpTransport
::relay(smtp_relay_address
)?
272 let response
= mailer
.send(&email
)?
;
274 println!("{:?}", response
);
279 fn start_check_alive_thread() -> Arc
<Mutex
<std
::result
::Result
<(), CheckError
>>> {
280 let check_alive_error_mutex
: Arc
<Mutex
<std
::result
::Result
<(), CheckError
>>> =
281 Arc
::new(Mutex
::new(Ok(())));
282 let check_alive_error_mutex_copy
= check_alive_error_mutex
.clone();
284 let _thread_check_alive_handle
= thread
::spawn(move || {
285 let socket
= UdpSocket
::bind("0.0.0.0:8739").unwrap();
286 socket
.set_read_timeout(Some(PING_TIMEOUT
)).unwrap();
288 let mut buffer
= [0u8; 8];
291 match socket
.recv_from(&mut buffer
) {
293 let mut check_alive_error
= check_alive_error_mutex
.lock().unwrap();
295 *check_alive_error
= Ok(());
296 socket
.send_to(&buffer
, &src
).unwrap();
298 *check_alive_error
= Err(CheckError
::CheckAlive
);
302 let mut check_alive_error
= check_alive_error_mutex
.lock().unwrap();
303 *check_alive_error
= Err(CheckError
::CheckAlive
);
309 check_alive_error_mutex_copy