use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use crate::types::{HashHex, PubkeyHex};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetStatusRequest;
impl GetStatusRequest {
pub const METHOD: &'static str = "get_status";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetStatusResponse {
pub pubkey: PubkeyHex,
#[serde(skip_serializing_if = "Option::is_none")]
pub validator_index: Option<u32>,
pub can_participate: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_duty_at: Option<u64>,
pub active_connections: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetDutyHistoryRequest {
pub limit: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub since: Option<u64>,
}
impl GetDutyHistoryRequest {
pub const METHOD: &'static str = "get_duty_history";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetDutyHistoryResponse {
pub entries: Vec<DutyEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DutyEntry {
pub at: u64,
pub kind: DutyKind,
pub ok: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<String>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DutyKind {
Propose,
Attest,
SignCheckpoint,
ObserveL1,
}
pub fn now_unix_seconds() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetSlashingDbRequest;
impl GetSlashingDbRequest {
pub const METHOD: &'static str = "get_slashing_db";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetSlashingDbResponse {
pub last_proposed_slot: u64,
pub last_attested_source: u64,
pub last_attested_target: u64,
pub last_attested_root: HashHex,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportSlashingDbRequest {
pub path: String,
}
impl ExportSlashingDbRequest {
pub const METHOD: &'static str = "export_slashing_db";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportSlashingDbResponse {
pub written_bytes: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResetSlashingDbRequest {
pub confirm_token: String,
}
impl ResetSlashingDbRequest {
pub const METHOD: &'static str = "reset_slashing_db";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResetSlashingDbResponse {
pub reset: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StopNodeRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
impl StopNodeRequest {
pub const METHOD: &'static str = "stop_node";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StopNodeResponse {
pub accepted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReloadConfigRequest;
impl ReloadConfigRequest {
pub const METHOD: &'static str = "reload_config";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReloadConfigResponse {
pub accepted: bool,
pub applied_changes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthzRequest;
impl HealthzRequest {
pub const METHOD: &'static str = "healthz";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthzResponse {
pub ok: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetVersionRequest;
impl GetVersionRequest {
pub const METHOD: &'static str = "get_version";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetVersionResponse {
pub version: String,
pub build_commit: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn method_names_are_snake_case() {
let names = [
GetStatusRequest::METHOD,
GetDutyHistoryRequest::METHOD,
GetSlashingDbRequest::METHOD,
ExportSlashingDbRequest::METHOD,
ResetSlashingDbRequest::METHOD,
StopNodeRequest::METHOD,
ReloadConfigRequest::METHOD,
HealthzRequest::METHOD,
GetVersionRequest::METHOD,
];
for m in names {
assert!(
m.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'),
"not snake_case: {m:?}",
);
}
}
#[test]
fn duty_kind_snake_case() {
assert_eq!(
serde_json::to_string(&DutyKind::Propose).unwrap(),
"\"propose\""
);
assert_eq!(
serde_json::to_string(&DutyKind::SignCheckpoint).unwrap(),
"\"sign_checkpoint\""
);
assert_eq!(
serde_json::to_string(&DutyKind::ObserveL1).unwrap(),
"\"observe_l1\""
);
}
#[test]
fn get_status_roundtrip() {
let r = GetStatusResponse {
pubkey: PubkeyHex::new([7u8; 48]),
validator_index: Some(3),
can_participate: true,
last_duty_at: Some(now_unix_seconds()),
active_connections: 2,
};
let j = serde_json::to_string(&r).unwrap();
let back: GetStatusResponse = serde_json::from_str(&j).unwrap();
assert_eq!(back.pubkey, r.pubkey);
assert_eq!(back.validator_index, r.validator_index);
assert_eq!(back.can_participate, r.can_participate);
}
#[test]
fn now_unix_seconds_is_sane() {
let t = now_unix_seconds();
assert!(t > 1_600_000_000);
assert!(t < 4_102_444_800);
}
}