lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 95bcf4cc2a0aec19fc323620d71d8c517f87e2f8
parent 5d069ecec1e4045be2a51cf8ed27b31552384b34
Author: triesap <tyson@radroots.org>
Date:   Wed,  1 Jul 2026 22:42:13 +0000

relay_transport: reject zero fetch limits

Diffstat:
Mcrates/relay_transport/src/error.rs | 3+++
Mcrates/relay_transport/src/fetch.rs | 35++++++++++++++++++++++++++++++-----
Mcrates/relay_transport/tests/transport.rs | 36+++++++++++++++++++++++++++++++++++-
3 files changed, 68 insertions(+), 6 deletions(-)

diff --git a/crates/relay_transport/src/error.rs b/crates/relay_transport/src/error.rs @@ -38,6 +38,9 @@ pub enum RadrootsRelayTransportError { #[error("Relay fetch filters must not be empty")] EmptyFetchFilters, + #[error("Relay fetch {field} must be greater than zero")] + InvalidFetchLimit { field: &'static str }, + #[error("JSON error: {0}")] Json(#[from] serde_json::Error), diff --git a/crates/relay_transport/src/fetch.rs b/crates/relay_transport/src/fetch.rs @@ -104,6 +104,7 @@ impl RadrootsRelayFetchRequest { where I: IntoIterator<Item = RadrootsNostrFilter>, { + ensure_positive_limit("max_events", max_events)?; Ok(Self { mode, observed_at_ms, @@ -124,14 +125,19 @@ impl RadrootsRelayFetchRequest { self } - pub fn with_timeout_ms(mut self, timeout_ms: u64) -> Self { + pub fn with_timeout_ms(mut self, timeout_ms: u64) -> Result<Self, RadrootsRelayTransportError> { + ensure_positive_timeout("timeout_ms", timeout_ms)?; self.timeout_ms = timeout_ms; - self + Ok(self) } - pub fn with_raw_event_scan_limit(mut self, max_raw_events: usize) -> Self { + pub fn with_raw_event_scan_limit( + mut self, + max_raw_events: usize, + ) -> Result<Self, RadrootsRelayTransportError> { + ensure_positive_limit("max_raw_events", max_raw_events)?; self.max_raw_events = max_raw_events; - self + Ok(self) } pub fn mode(&self) -> RadrootsRelayFetchMode { @@ -167,7 +173,26 @@ fn default_raw_event_scan_limit(max_events: usize) -> usize { max_events .saturating_mul(DEFAULT_RELAY_FETCH_RAW_SCAN_MULTIPLIER) .max(max_events) - .max(1) +} + +fn ensure_positive_limit( + field: &'static str, + value: usize, +) -> Result<(), RadrootsRelayTransportError> { + if value == 0 { + return Err(RadrootsRelayTransportError::InvalidFetchLimit { field }); + } + Ok(()) +} + +fn ensure_positive_timeout( + field: &'static str, + value: u64, +) -> Result<(), RadrootsRelayTransportError> { + if value == 0 { + return Err(RadrootsRelayTransportError::InvalidFetchLimit { field }); + } + Ok(()) } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/relay_transport/tests/transport.rs b/crates/relay_transport/tests/transport.rs @@ -488,6 +488,38 @@ fn fetch_requests_reject_empty_filter_sets() { )); } +#[test] +fn fetch_requests_reject_zero_limits_and_timeouts() { + let filter = post_relay_fetch_filter(1); + assert!(matches!( + RadrootsRelayFetchRequest::fetch(1_000, 0, [filter.clone()]), + Err(RadrootsRelayTransportError::InvalidFetchLimit { field }) if field == "max_events" + )); + assert!(matches!( + RadrootsRelayFetchRequest::subscription(1_000, 0, [filter.clone()]), + Err(RadrootsRelayTransportError::InvalidFetchLimit { field }) if field == "max_events" + )); + + let request = + RadrootsRelayFetchRequest::fetch(1_000, 1, [filter]).expect("valid fetch request"); + assert!(matches!( + request.clone().with_timeout_ms(0), + Err(RadrootsRelayTransportError::InvalidFetchLimit { field }) if field == "timeout_ms" + )); + assert!(matches!( + request.clone().with_raw_event_scan_limit(0), + Err(RadrootsRelayTransportError::InvalidFetchLimit { field }) if field == "max_raw_events" + )); + + let request = request + .with_timeout_ms(1) + .expect("minimum timeout") + .with_raw_event_scan_limit(1) + .expect("minimum raw scan limit"); + assert_eq!(request.timeout_ms(), 1); + assert_eq!(request.max_raw_events(), 1); +} + #[tokio::test] async fn fetch_ingests_events_and_records_relay_observations() { let signed = signed_post("hello"); @@ -896,7 +928,9 @@ async fn fetch_raw_scan_limit_bounds_noisy_adapter_output() { let receipt = fetch_and_ingest_relay_events( &adapter, &store, - post_relay_fetch_request(1_130, 1).with_raw_event_scan_limit(2), + post_relay_fetch_request(1_130, 1) + .with_raw_event_scan_limit(2) + .expect("raw scan limit"), ) .await .expect("fetch ingest");