Skip to content

Commit cc3a6ec

Browse files
node: Validate per-chain settings on config load
Reject invalid ChainSettings values at startup: max_block_range_size and max_event_only_range must be positive (i32), and block_batch_size, block_ptr_batch_size, block_ingestor_max_concurrent_json_rpc_calls, and get_logs_max_contracts must be non-zero (used as .buffered() args or filter batch limits where 0 would panic or silently break).
1 parent 9f60662 commit cc3a6ec

1 file changed

Lines changed: 276 additions & 4 deletions

File tree

node/src/config.rs

Lines changed: 276 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ impl ChainSection {
489489
}
490490

491491
for (name, chain) in self.chains.iter_mut() {
492-
chain.validate()?;
492+
chain.validate(name)?;
493493
if chain.cache_size <= reorg_threshold {
494494
return Err(anyhow!(
495495
"chain '{}': cache_size ({}) must be greater than reorg_threshold ({})",
@@ -701,6 +701,33 @@ impl Default for ChainSettings {
701701
}
702702
}
703703

704+
impl ChainSettings {
705+
fn validate(&self) -> Result<()> {
706+
anyhow::ensure!(
707+
self.max_block_range_size > 0,
708+
"max_block_range_size must be > 0"
709+
);
710+
anyhow::ensure!(
711+
self.max_event_only_range > 0,
712+
"max_event_only_range must be > 0"
713+
);
714+
anyhow::ensure!(self.block_batch_size > 0, "block_batch_size must be > 0");
715+
anyhow::ensure!(
716+
self.block_ptr_batch_size > 0,
717+
"block_ptr_batch_size must be > 0"
718+
);
719+
anyhow::ensure!(
720+
self.block_ingestor_max_concurrent_json_rpc_calls > 0,
721+
"block_ingestor_max_concurrent_json_rpc_calls must be > 0"
722+
);
723+
anyhow::ensure!(
724+
self.get_logs_max_contracts > 0,
725+
"get_logs_max_contracts must be > 0"
726+
);
727+
Ok(())
728+
}
729+
}
730+
704731
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
705732
pub struct Chain {
706733
pub shard: String,
@@ -731,19 +758,23 @@ fn default_blockchain_kind() -> BlockchainKind {
731758
}
732759

733760
impl Chain {
734-
fn validate(&mut self) -> Result<()> {
761+
fn validate(&mut self, name: &str) -> Result<()> {
735762
let mut labels = self.providers.iter().map(|p| &p.label).collect_vec();
736763
labels.sort();
737764
labels.dedup();
738765
if labels.len() != self.providers.len() {
739-
return Err(anyhow!("Provider labels must be unique"));
766+
return Err(anyhow!("chain {}: provider labels must be unique", name));
740767
}
741768

742769
// `Config` validates that `self.shard` references a configured shard
743770
for provider in self.providers.iter_mut() {
744771
provider.validate()?
745772
}
746773

774+
self.settings
775+
.validate()
776+
.map_err(|e| anyhow!("chain {}: {}", name, e))?;
777+
747778
Ok(())
748779
}
749780
}
@@ -1458,7 +1489,15 @@ where
14581489
#[cfg(test)]
14591490
mod tests {
14601491

1461-
use crate::config::{default_polling_interval, ChainSection, Web3Rule};
1492+
use std::time::Duration;
1493+
1494+
use crate::config::{
1495+
default_block_batch_size, default_block_ingestor_max_concurrent_json_rpc_calls,
1496+
default_block_ptr_batch_size, default_genesis_block_number, default_get_logs_max_contracts,
1497+
default_json_rpc_timeout, default_max_block_range_size, default_max_event_only_range,
1498+
default_polling_interval, default_request_retries, default_target_triggers_per_block_range,
1499+
ChainSection, Web3Rule,
1500+
};
14621501

14631502
use super::{
14641503
Chain, ChainSettings, Config, FirehoseProvider, Provider, ProviderDetails, Shard,
@@ -2341,4 +2380,237 @@ fdw_pool_size = [
23412380
// Node does not match and kill switch is on
23422381
assert!(!make_config("query-0", "index-0", true).is_block_ingestor());
23432382
}
2383+
2384+
#[test]
2385+
fn chain_settings_valid_overrides() {
2386+
let mut section = toml::from_str::<ChainSection>(
2387+
r#"
2388+
ingestor = "block_ingestor_node"
2389+
[mainnet]
2390+
shard = "primary"
2391+
json_rpc_timeout = 300
2392+
request_retries = 15
2393+
max_block_range_size = 2000
2394+
block_batch_size = 20
2395+
block_ptr_batch_size = 50
2396+
max_event_only_range = 1000
2397+
target_triggers_per_block_range = 200
2398+
get_logs_max_contracts = 5000
2399+
block_ingestor_max_concurrent_json_rpc_calls = 500
2400+
genesis_block_number = 1
2401+
provider = []
2402+
"#,
2403+
)
2404+
.unwrap();
2405+
2406+
assert!(section.validate().is_ok());
2407+
let settings = &section.chains.get("mainnet").unwrap().settings;
2408+
assert_eq!(settings.json_rpc_timeout, Duration::from_secs(300));
2409+
assert_eq!(settings.request_retries, 15);
2410+
assert_eq!(settings.max_block_range_size, 2000);
2411+
assert_eq!(settings.block_batch_size, 20);
2412+
assert_eq!(settings.block_ptr_batch_size, 50);
2413+
assert_eq!(settings.max_event_only_range, 1000);
2414+
assert_eq!(settings.target_triggers_per_block_range, 200);
2415+
assert_eq!(settings.get_logs_max_contracts, 5000);
2416+
assert_eq!(settings.block_ingestor_max_concurrent_json_rpc_calls, 500);
2417+
assert_eq!(settings.genesis_block_number, 1);
2418+
}
2419+
2420+
#[test]
2421+
fn chain_settings_defaults_match_env_vars() {
2422+
let settings = ChainSettings::default();
2423+
assert_eq!(settings.polling_interval, default_polling_interval());
2424+
assert_eq!(settings.json_rpc_timeout, default_json_rpc_timeout());
2425+
assert_eq!(settings.request_retries, default_request_retries());
2426+
assert_eq!(
2427+
settings.max_block_range_size,
2428+
default_max_block_range_size()
2429+
);
2430+
assert_eq!(settings.block_batch_size, default_block_batch_size());
2431+
assert_eq!(
2432+
settings.block_ptr_batch_size,
2433+
default_block_ptr_batch_size()
2434+
);
2435+
assert_eq!(
2436+
settings.max_event_only_range,
2437+
default_max_event_only_range()
2438+
);
2439+
assert_eq!(
2440+
settings.target_triggers_per_block_range,
2441+
default_target_triggers_per_block_range()
2442+
);
2443+
assert_eq!(
2444+
settings.get_logs_max_contracts,
2445+
default_get_logs_max_contracts()
2446+
);
2447+
assert_eq!(
2448+
settings.block_ingestor_max_concurrent_json_rpc_calls,
2449+
default_block_ingestor_max_concurrent_json_rpc_calls()
2450+
);
2451+
assert_eq!(
2452+
settings.genesis_block_number,
2453+
default_genesis_block_number()
2454+
);
2455+
}
2456+
2457+
#[test]
2458+
fn chain_settings_rejects_zero_max_block_range_size() {
2459+
let mut section = toml::from_str::<ChainSection>(
2460+
r#"
2461+
ingestor = "block_ingestor_node"
2462+
[mainnet]
2463+
shard = "primary"
2464+
max_block_range_size = 0
2465+
provider = []
2466+
"#,
2467+
)
2468+
.unwrap();
2469+
2470+
let err = section.validate().unwrap_err().to_string();
2471+
assert!(
2472+
err.contains("max_block_range_size must be > 0"),
2473+
"expected max_block_range_size error, got: {err}"
2474+
);
2475+
}
2476+
2477+
#[test]
2478+
fn chain_settings_rejects_negative_max_block_range_size() {
2479+
let mut section = toml::from_str::<ChainSection>(
2480+
r#"
2481+
ingestor = "block_ingestor_node"
2482+
[mainnet]
2483+
shard = "primary"
2484+
max_block_range_size = -1
2485+
provider = []
2486+
"#,
2487+
)
2488+
.unwrap();
2489+
2490+
let err = section.validate().unwrap_err().to_string();
2491+
assert!(
2492+
err.contains("max_block_range_size must be > 0"),
2493+
"expected max_block_range_size error, got: {err}"
2494+
);
2495+
}
2496+
2497+
#[test]
2498+
fn chain_settings_rejects_zero_max_event_only_range() {
2499+
let mut section = toml::from_str::<ChainSection>(
2500+
r#"
2501+
ingestor = "block_ingestor_node"
2502+
[mainnet]
2503+
shard = "primary"
2504+
max_event_only_range = 0
2505+
provider = []
2506+
"#,
2507+
)
2508+
.unwrap();
2509+
2510+
let err = section.validate().unwrap_err().to_string();
2511+
assert!(
2512+
err.contains("max_event_only_range must be > 0"),
2513+
"expected max_event_only_range error, got: {err}"
2514+
);
2515+
}
2516+
2517+
#[test]
2518+
fn chain_settings_rejects_zero_block_batch_size() {
2519+
let mut section = toml::from_str::<ChainSection>(
2520+
r#"
2521+
ingestor = "block_ingestor_node"
2522+
[mainnet]
2523+
shard = "primary"
2524+
block_batch_size = 0
2525+
provider = []
2526+
"#,
2527+
)
2528+
.unwrap();
2529+
2530+
let err = section.validate().unwrap_err().to_string();
2531+
assert!(
2532+
err.contains("block_batch_size must be > 0"),
2533+
"expected block_batch_size error, got: {err}"
2534+
);
2535+
}
2536+
2537+
#[test]
2538+
fn chain_settings_rejects_zero_block_ptr_batch_size() {
2539+
let mut section = toml::from_str::<ChainSection>(
2540+
r#"
2541+
ingestor = "block_ingestor_node"
2542+
[mainnet]
2543+
shard = "primary"
2544+
block_ptr_batch_size = 0
2545+
provider = []
2546+
"#,
2547+
)
2548+
.unwrap();
2549+
2550+
let err = section.validate().unwrap_err().to_string();
2551+
assert!(
2552+
err.contains("block_ptr_batch_size must be > 0"),
2553+
"expected block_ptr_batch_size error, got: {err}"
2554+
);
2555+
}
2556+
2557+
#[test]
2558+
fn chain_settings_rejects_zero_concurrent_json_rpc_calls() {
2559+
let mut section = toml::from_str::<ChainSection>(
2560+
r#"
2561+
ingestor = "block_ingestor_node"
2562+
[mainnet]
2563+
shard = "primary"
2564+
block_ingestor_max_concurrent_json_rpc_calls = 0
2565+
provider = []
2566+
"#,
2567+
)
2568+
.unwrap();
2569+
2570+
let err = section.validate().unwrap_err().to_string();
2571+
assert!(
2572+
err.contains("block_ingestor_max_concurrent_json_rpc_calls must be > 0"),
2573+
"expected block_ingestor_max_concurrent_json_rpc_calls error, got: {err}"
2574+
);
2575+
}
2576+
2577+
#[test]
2578+
fn chain_settings_rejects_zero_get_logs_max_contracts() {
2579+
let mut section = toml::from_str::<ChainSection>(
2580+
r#"
2581+
ingestor = "block_ingestor_node"
2582+
[mainnet]
2583+
shard = "primary"
2584+
get_logs_max_contracts = 0
2585+
provider = []
2586+
"#,
2587+
)
2588+
.unwrap();
2589+
2590+
let err = section.validate().unwrap_err().to_string();
2591+
assert!(
2592+
err.contains("get_logs_max_contracts must be > 0"),
2593+
"expected get_logs_max_contracts error, got: {err}"
2594+
);
2595+
}
2596+
2597+
#[test]
2598+
fn chain_settings_validation_error_includes_chain_name() {
2599+
let mut section = toml::from_str::<ChainSection>(
2600+
r#"
2601+
ingestor = "block_ingestor_node"
2602+
[my_chain]
2603+
shard = "primary"
2604+
block_batch_size = 0
2605+
provider = []
2606+
"#,
2607+
)
2608+
.unwrap();
2609+
2610+
let err = section.validate().unwrap_err().to_string();
2611+
assert!(
2612+
err.contains("my_chain"),
2613+
"expected chain name in error, got: {err}"
2614+
);
2615+
}
23442616
}

0 commit comments

Comments
 (0)