use std::collections::{HashMap, HashSet};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
pub type ServerStateDb = Arc<Mutex<HashMap<String, ServerState>>>;
pub type ServerConfigDb = Arc<Mutex<HashMap<String, Server>>>;
fn default_max_connections() -> usize {
10_000
}
fn default_handshake_timeout_ms() -> u64 {
10_000
}
fn default_idle_timeout_ms() -> u64 {
0 }
fn default_max_message_bytes() -> usize {
256 * 1024 * 1024 }
fn default_shutdown_grace_ms() -> u64 {
5_000
}
#[derive(Debug, Deserialize, Serialize, Hash, Eq, PartialEq, Clone)]
pub struct Server {
pub name: String,
pub endpoint: String,
pub enabled: bool,
}
impl Server {
pub fn ws_url(&self) -> String {
format!("ws://{}", self.endpoint)
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ServerConfig {
pub listen_address: String,
pub listen_port: u16,
pub upstream_reconnect_ms: u64,
pub upstream_monitoring_ms: u64,
pub upstream_status_ms: u64,
pub servers: Vec<Server>,
#[serde(default = "default_max_connections")]
pub max_connections: usize,
#[serde(default = "default_handshake_timeout_ms")]
pub handshake_timeout_ms: u64,
#[serde(default = "default_idle_timeout_ms")]
pub idle_timeout_ms: u64,
#[serde(default = "default_max_message_bytes")]
pub max_message_bytes: usize,
#[serde(default = "default_shutdown_grace_ms")]
pub shutdown_grace_ms: u64,
#[serde(default)]
pub metrics_address: Option<String>,
#[serde(default)]
pub metrics_port: Option<u16>,
}
impl ServerConfig {
pub fn validate(&self) -> Result<()> {
if self.listen_address.trim().is_empty() {
bail!("listen_address must not be empty");
}
if self.listen_port == 0 {
bail!("listen_port must be non-zero");
}
if self.upstream_reconnect_ms == 0 {
bail!("upstream_reconnect_ms must be greater than 0");
}
if self.upstream_monitoring_ms == 0 {
bail!("upstream_monitoring_ms must be greater than 0");
}
if self.upstream_status_ms == 0 {
bail!("upstream_status_ms must be greater than 0");
}
if self.max_connections == 0 {
bail!("max_connections must be greater than 0");
}
if self.max_message_bytes == 0 {
bail!("max_message_bytes must be greater than 0");
}
if self.servers.is_empty() {
bail!("config must define at least one server");
}
let enabled: Vec<&Server> = self.servers.iter().filter(|s| s.enabled).collect();
if enabled.is_empty() {
bail!("config must have at least one enabled server");
}
let mut seen = HashSet::new();
for s in &enabled {
if s.endpoint.trim().is_empty() {
bail!("server '{}' has an empty endpoint", s.name);
}
if !seen.insert(s.endpoint.as_str()) {
bail!(
"duplicate upstream endpoint '{}' — endpoints must be unique",
s.endpoint
);
}
}
if let Some(0) = self.metrics_port {
bail!("metrics_port must be non-zero when set");
}
Ok(())
}
pub fn proxy_limits(&self) -> ProxyLimits {
ProxyLimits {
handshake_timeout_ms: self.handshake_timeout_ms,
idle_timeout_ms: self.idle_timeout_ms,
max_message_bytes: self.max_message_bytes,
}
}
}
#[derive(Debug)]
pub struct ServerState {
pub connections: Arc<AtomicUsize>,
pub enabled: bool,
pub online: bool,
pub stale: bool,
pub trace_begin_block: u32,
pub trace_end_block: u32,
pub chain_state_begin_block: u32,
pub chain_state_end_block: u32,
}
impl ServerState {
pub fn new() -> ServerState {
ServerState {
connections: Arc::new(AtomicUsize::new(0)),
enabled: true,
online: false,
stale: false,
trace_begin_block: 0,
trace_end_block: 0,
chain_state_begin_block: 0,
chain_state_end_block: 0,
}
}
}
impl Default for ServerState {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct StaticConfig {
pub upstream_reconnect_ms: u64,
pub upstream_monitoring_ms: u64,
pub upstream_status_ms: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct ProxyLimits {
pub handshake_timeout_ms: u64,
pub idle_timeout_ms: u64,
pub max_message_bytes: usize,
}
pub fn build_state_db(servers: Vec<Server>) -> ServerStateDb {
Arc::new(Mutex::new(
servers
.iter()
.filter(|s| s.enabled)
.map(|s| (s.endpoint.clone(), ServerState::new()))
.collect::<HashMap<String, ServerState>>(),
))
}
pub fn build_config_db(servers: Vec<Server>) -> ServerConfigDb {
Arc::new(Mutex::new(
servers
.into_iter()
.filter(|s| s.enabled)
.map(|s| (s.endpoint.clone(), s))
.collect::<HashMap<String, Server>>(),
))
}