2 * API Reference: https://ethereum.github.io/beacon-APIs/
5 #![cfg_attr(debug_assertions, allow(unused_variables, unused_imports, dead_code))]
9 net
::{IpAddr
, Ipv4Addr
},
11 time
::{self, Duration
},
14 use anyhow
::{Context
, Result
};
16 message
::header
::ContentType
, transport
::smtp
::authentication
::Credentials
, Message
,
17 SmtpTransport
, Transport
,
19 use reqwest
::StatusCode
;
20 use serde
::Deserialize
;
21 use serde_json
::{json
, Value
};
23 use crate::config
::Config
;
28 const FILE_CONF
: &str = "config.ron";
29 const CHECK_PERIOD
: Duration
= Duration
::from_secs(10); // 10 s.
30 const EMAIL_RESEND_PERIOD
: Duration
= Duration
::from_secs(6 * 60 * 60); // 6 h.
31 const STATE_PRINT_PERIOD
: Duration
= Duration
::from_secs(15 * 60); // 15 min.
32 const BASE_URI
: &str = "http://localhost:5052/eth/v1/";
34 fn main() -> Result
<()> {
35 println!("Staking Watchdog");
37 let config
= Config
::read(FILE_CONF
)?
;
40 "Configuration: {:?}",
42 smtp_password
: "*****".to_string(),
47 let mut time_last_email_send
= time
::Instant
::now() - EMAIL_RESEND_PERIOD
;
48 let mut time_last_state_printed
= time
::Instant
::now() - STATE_PRINT_PERIOD
;
49 let mut error_state
= false;
52 let time_beginning_loop
= time
::Instant
::now();
54 if let Err(error
) = check_validators(&config
.pub_keys
) {
56 println!("Error: {:?}", error
);
57 if time
::Instant
::now() - time_last_email_send
>= EMAIL_RESEND_PERIOD
{
59 println!("Sending email...");
62 &format!("Error: {:?}", error
),
64 &config
.smtp_password
,
66 Err(email_error
) => println!("Error sending email: {:?}", email_error
),
68 println!("Email successfully sent");
69 time_last_email_send
= time
::Instant
::now();
76 println!("End of erroneous state");
79 if time
::Instant
::now() - time_last_state_printed
>= STATE_PRINT_PERIOD
{
80 println!("No error detected");
81 time_last_state_printed
= time
::Instant
::now();
85 let elapsed
= time
::Instant
::now() - time_beginning_loop
;
87 if elapsed
< CHECK_PERIOD
{
88 let to_wait
= CHECK_PERIOD
- elapsed
;
89 thread
::sleep(to_wait
);
100 UnknownCodeFromHealthCheck(u16),
101 ReqwestError(reqwest
::Error
),
102 ValidatorError
{ pub_key
: String
, message
: String
},
103 ValidatorStatusError
{ pub_key
: String
, message
: String
},
106 impl From
<reqwest
::Error
> for CheckError
{
107 fn from(value
: reqwest
::Error
) -> Self {
108 CheckError
::ReqwestError(value
)
112 #[derive(Deserialize, Debug)]
113 struct JsonValidatorState
{
114 data
: JsonValidatorStateData
,
117 #[derive(Deserialize, Debug)]
118 struct JsonValidatorStateData
{
122 #[derive(Deserialize, Debug)]
128 fn check_validators(pub_keys
: &[String
]) -> std
::result
::Result
<(), CheckError
> {
130 let client
= reqwest
::blocking
::Client
::new();
132 let request_health
= client
133 .get(format!("{url}node/health"))
134 .header("accept", "application/json");
135 match request_health
.send() {
137 // println!("{resp:?}"); // For debug.
138 match resp
.status().as_u16() {
140 206 => return Err(CheckError
::NotSync
),
141 400 => return Err(CheckError
::InvalidSyncStatus
),
142 503 => return Err(CheckError
::NodeHavingIssues
),
143 code
=> return Err(CheckError
::UnknownCodeFromHealthCheck(code
)),
147 println!("{error:?}");
148 return Err(CheckError
::HttpError(error
.to_string()));
152 for pub_key
in pub_keys
{
154 .get(format!("{url}beacon/states/head/validators/0x{pub_key}"))
155 .header("accept", "application/json");
156 match request
.send() {
158 // println!("{resp:?}"); // For debug.
159 match resp
.status().as_u16() {
161 let json
: JsonValidatorState
= resp
.json()?
;
162 if json
.data
.status
!= "active_ongoing" {
163 return Err(CheckError
::ValidatorStatusError
{
164 pub_key
: pub_key
.clone(),
165 message
: format!("Status: {}", json
.data
.status
),
170 let json
: JsonError
= resp
.json()?
;
171 return Err(CheckError
::ValidatorError
{
172 pub_key
: pub_key
.clone(),
174 "Http error code: {}, message: {}",
182 return Err(CheckError
::ValidatorError
{
183 pub_key
: pub_key
.clone(),
184 message
: error
.to_string(),
193 fn send_email(title
: &str, body
: &str, login
: &str, pass
: &str) -> Result
<()> {
194 let email
= Message
::builder()
196 .from("Staking Watchdog <redmine@d-lan.net>".parse()?
)
197 .to("Greg Burri <greg.burri@gmail.com>".parse()?
)
199 .header(ContentType
::TEXT_PLAIN
)
200 .body(body
.to_string())?
;
202 let creds
= Credentials
::new(login
.to_string(), pass
.to_string());
204 // Open a remote connection to gmail
205 let mailer
= SmtpTransport
::relay("mail.gandi.net")?
210 let response
= mailer
.send(&email
)?
;
212 println!("{:?}", response
);