use crate::module::ipc::protocol::RequestPayload;
use crate::module::traits::ModuleError;
use std::collections::HashMap;
use std::sync::Mutex;
use tracing::{debug, warn};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationResult {
Allowed,
Denied(String),
}
pub struct RequestValidator {
rate_limiters: Mutex<HashMap<String, RateLimiter>>,
max_requests_per_second: u64,
time_window_seconds: u64,
}
struct RateLimiter {
request_timestamps: Vec<u64>,
current_index: usize,
buffer_size: usize,
}
impl RateLimiter {
fn new(max_requests: u64, window_seconds: u64) -> Self {
let buffer_size =
((max_requests * 2).max(100) as usize).max((window_seconds as usize).min(1000)); Self {
request_timestamps: Vec::with_capacity(buffer_size),
current_index: 0,
buffer_size,
}
}
fn check_rate_limit(&mut self, max_requests: u64, window_seconds: u64) -> bool {
let now = crate::utils::current_timestamp();
let cutoff = now.saturating_sub(window_seconds);
self.request_timestamps.retain(|&ts| ts > cutoff);
if self.request_timestamps.len() < max_requests as usize {
if self.request_timestamps.len() < self.buffer_size {
self.request_timestamps.push(now);
} else {
self.request_timestamps[self.current_index] = now;
self.current_index = (self.current_index + 1) % self.buffer_size;
}
true
} else {
false
}
}
}
impl RequestValidator {
pub fn new() -> Self {
Self::with_rate_limit(100, 1) }
pub fn with_rate_limit(max_requests_per_second: u64, time_window_seconds: u64) -> Self {
Self {
rate_limiters: Mutex::new(HashMap::new()),
max_requests_per_second,
time_window_seconds,
}
}
#[inline]
pub fn validate_request(
&self,
_module_id: &str,
payload: &RequestPayload,
) -> Result<ValidationResult, ModuleError> {
match payload {
RequestPayload::Handshake { .. } => Ok(ValidationResult::Allowed),
RequestPayload::GetBlock { .. }
| RequestPayload::GetBlockHeader { .. }
| RequestPayload::GetTransaction { .. }
| RequestPayload::HasTransaction { .. }
| RequestPayload::GetChainTip
| RequestPayload::GetBlockHeight
| RequestPayload::GetUtxo { .. }
| RequestPayload::SubscribeEvents { .. }
| RequestPayload::GetMempoolTransactions
| RequestPayload::GetMempoolTransaction { .. }
| RequestPayload::GetMempoolSize
| RequestPayload::GetNetworkStats
| RequestPayload::GetNetworkPeers
| RequestPayload::GetChainInfo
| RequestPayload::GetBlockByHeight { .. }
| RequestPayload::GetLightningNodeUrl
| RequestPayload::GetLightningInfo
| RequestPayload::GetPaymentState { .. }
| RequestPayload::CheckTransactionInMempool { .. }
| RequestPayload::GetFeeEstimate { .. }
| RequestPayload::ReadFile { .. }
| RequestPayload::WriteFile { .. }
| RequestPayload::DeleteFile { .. }
| RequestPayload::ListDirectory { .. }
| RequestPayload::CreateDirectory { .. }
| RequestPayload::GetFileMetadata { .. }
| RequestPayload::RegisterRpcEndpoint { .. }
| RequestPayload::UnregisterRpcEndpoint { .. }
| RequestPayload::RegisterTimer { .. }
| RequestPayload::CancelTimer { .. }
| RequestPayload::ScheduleTask { .. }
| RequestPayload::ReportMetric { .. }
| RequestPayload::GetModuleMetrics { .. }
| RequestPayload::GetAllMetrics
| RequestPayload::GetModuleHealth { .. }
| RequestPayload::GetAllModuleHealth
| RequestPayload::ReportModuleHealth { .. }
| RequestPayload::DiscoverModules
| RequestPayload::GetModuleInfo { .. }
| RequestPayload::IsModuleAvailable { .. }
| RequestPayload::PublishEvent { .. }
| RequestPayload::CallModule { .. }
| RequestPayload::RegisterModuleApi { .. }
| RequestPayload::UnregisterModuleApi
| RequestPayload::SendMeshPacketToPeer { .. } => Ok(ValidationResult::Allowed),
| RequestPayload::SendStratumV2MessageToPeer { .. } => Ok(ValidationResult::Allowed),
| RequestPayload::GetBlockTemplate { .. } => Ok(ValidationResult::Allowed),
| RequestPayload::SubmitBlock { .. } => Ok(ValidationResult::Allowed),
| RequestPayload::MergeBlockServeDenylist { .. }
| RequestPayload::GetBlockServeDenylistSnapshot
| RequestPayload::ClearBlockServeDenylist
| RequestPayload::ReplaceBlockServeDenylist { .. }
| RequestPayload::MergeTxServeDenylist { .. }
| RequestPayload::GetTxServeDenylistSnapshot
| RequestPayload::ClearTxServeDenylist
| RequestPayload::ReplaceTxServeDenylist { .. }
| RequestPayload::GetSyncStatus
| RequestPayload::BanPeer { .. }
| RequestPayload::SetBlockServeMaintenanceMode { .. }
| RequestPayload::RegisterCliSpec { .. } => Ok(ValidationResult::Allowed),
}
}
pub fn validate_no_consensus_modification(
&self,
module_id: &str,
operation: &str,
) -> Result<(), ModuleError> {
debug!(
"Validated no consensus modification for module {} operation: {}",
module_id, operation
);
Ok(())
}
pub fn validate_resource_limits(
&self,
module_id: &str,
_operation: &str,
) -> Result<(), ModuleError> {
let mut limiters = self.rate_limiters.lock().unwrap();
let limiter = limiters.entry(module_id.to_string()).or_insert_with(|| {
RateLimiter::new(self.max_requests_per_second, self.time_window_seconds)
});
if !limiter.check_rate_limit(self.max_requests_per_second, self.time_window_seconds) {
warn!(
"Rate limit exceeded for module {}: {} requests per {} seconds",
module_id, self.max_requests_per_second, self.time_window_seconds
);
return Err(ModuleError::RateLimitExceeded(format!(
"Module {} exceeded rate limit: {} requests per {} seconds",
module_id, self.max_requests_per_second, self.time_window_seconds
)));
}
debug!(
"Rate limit check passed for module {} operation: {}",
module_id, _operation
);
Ok(())
}
}
impl Default for RequestValidator {
fn default() -> Self {
Self::new()
}
}