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
),
66 &config
.smtp_password
,
68 Err(email_error
) => println!("Error sending email: {:?}", email_error
),
70 println!("Email successfully sent");
71 time_last_email_send
= time
::Instant
::now();
78 println!("End of erroneous state");
81 if time
::Instant
::now() - time_last_state_printed
>= STATE_PRINT_PERIOD
{
82 println!("No error detected");
83 time_last_state_printed
= time
::Instant
::now();
87 let elapsed
= time
::Instant
::now() - time_beginning_loop
;
89 if elapsed
< CHECK_PERIOD
{
90 let to_wait
= CHECK_PERIOD
- elapsed
;
91 thread
::sleep(to_wait
);
102 UnknownCodeFromHealthCheck(u16),
103 ReqwestError(reqwest
::Error
),
104 ValidatorError
{ pub_key
: String
, message
: String
},
105 ValidatorStatusError
{ pub_key
: String
, message
: String
},
109 impl fmt
::Display
for CheckError
{
110 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
112 CheckError
::HttpError(text
) => {
115 "Beacon node health check can't be reached (HTTP error): {}",
119 CheckError
::NotSync
=> {
122 "Beacon node health check is syncing (currently not sync)"
125 CheckError
::InvalidSyncStatus
=> {
126 write!(f
, "Beacon node health check returns an invalid status code")
128 CheckError
::NodeHavingIssues
=> {
131 "Beacon node health check is not initilized or having issue"
134 CheckError
::UnknownCodeFromHealthCheck(code
) => {
137 "Beacon node health check returns an unknown code: {}",
141 CheckError
::ReqwestError(error
) => {
142 write!(f
, "Error from reqwest: {}", error
)
144 CheckError
::ValidatorError
{ pub_key
, message
} => {
145 write!(f
, "Validator '{}' returns an error: {}", pub_key
, message
)
147 CheckError
::ValidatorStatusError
{ pub_key
, message
} => {
150 "Validator '{}' returns a status error: {}",
154 CheckError
::CheckAlive
=> {
155 write!(f
, "Check alive ping hasn't been received")
161 impl From
<reqwest
::Error
> for CheckError
{
162 fn from(value
: reqwest
::Error
) -> Self {
163 CheckError
::ReqwestError(value
)
167 #[derive(Deserialize, Debug)]
168 struct JsonValidatorState
{
169 data
: JsonValidatorStateData
,
172 #[derive(Deserialize, Debug)]
173 struct JsonValidatorStateData
{
177 #[derive(Deserialize, Debug)]
183 fn check_validators(pub_keys
: &[String
]) -> std
::result
::Result
<(), CheckError
> {
185 let client
= reqwest
::blocking
::Client
::new();
187 let request_health
= client
188 .get(format!("{url}node/health"))
189 .header("accept", "application/json");
190 match request_health
.send() {
192 // println!("{resp:?}"); // For debug.
193 match resp
.status().as_u16() {
195 206 => return Err(CheckError
::NotSync
),
196 400 => return Err(CheckError
::InvalidSyncStatus
),
197 503 => return Err(CheckError
::NodeHavingIssues
),
198 code
=> return Err(CheckError
::UnknownCodeFromHealthCheck(code
)),
202 println!("{error:?}");
203 return Err(CheckError
::HttpError(error
.to_string()));
207 for pub_key
in pub_keys
{
209 .get(format!("{url}beacon/states/head/validators/0x{pub_key}"))
210 .header("accept", "application/json");
211 match request
.send() {
213 // println!("{resp:?}"); // For debug.
214 match resp
.status().as_u16() {
216 let json
: JsonValidatorState
= resp
.json()?
;
217 if json
.data
.status
!= "active_ongoing" {
218 return Err(CheckError
::ValidatorStatusError
{
219 pub_key
: pub_key
.clone(),
220 message
: format!("Status: {}", json
.data
.status
),
225 let json
: JsonError
= resp
.json()?
;
226 return Err(CheckError
::ValidatorError
{
227 pub_key
: pub_key
.clone(),
229 "Http error code: {}, message: {}",
237 return Err(CheckError
::ValidatorError
{
238 pub_key
: pub_key
.clone(),
239 message
: error
.to_string(),
248 fn send_email(title
: &str, body
: &str, login
: &str, pass
: &str) -> Result
<()> {
249 let email
= Message
::builder()
251 .from("Staking Watchdog <redmine@d-lan.net>".parse()?
)
252 .to("Greg Burri <greg.burri@gmail.com>".parse()?
)
254 .header(ContentType
::TEXT_PLAIN
)
255 .body(body
.to_string())?
;
257 let creds
= Credentials
::new(login
.to_string(), pass
.to_string());
259 // Open a remote connection to gmail
260 let mailer
= SmtpTransport
::relay("mail.infomaniak.com")?
265 let response
= mailer
.send(&email
)?
;
267 println!("{:?}", response
);
272 fn start_check_alive_thread() -> Arc
<Mutex
<std
::result
::Result
<(), CheckError
>>> {
273 let check_alive_error_mutex
: Arc
<Mutex
<std
::result
::Result
<(), CheckError
>>> =
274 Arc
::new(Mutex
::new(Ok(())));
275 let check_alive_error_mutex_copy
= check_alive_error_mutex
.clone();
277 let _thread_check_alive_handle
= thread
::spawn(move || {
278 let socket
= UdpSocket
::bind("0.0.0.0:8739").unwrap();
279 socket
.set_read_timeout(Some(PING_TIMEOUT
)).unwrap();
281 let mut buffer
= [0u8; 8];
284 match socket
.recv_from(&mut buffer
) {
286 let mut check_alive_error
= check_alive_error_mutex
.lock().unwrap();
288 *check_alive_error
= Ok(());
289 socket
.send_to(&buffer
, &src
).unwrap();
291 *check_alive_error
= Err(CheckError
::CheckAlive
);
295 let mut check_alive_error
= check_alive_error_mutex
.lock().unwrap();
296 *check_alive_error
= Err(CheckError
::CheckAlive
);
302 check_alive_error_mutex_copy