@@ -489,7 +489,9 @@ impl ChainSection {
489489 }
490490
491491 for ( name, chain) in self . chains . iter_mut ( ) {
492- chain. validate ( name) ?;
492+ chain
493+ . validate ( )
494+ . map_err ( |e| anyhow ! ( "chain '{}': {}" , name, e) ) ?;
493495 if chain. cache_size <= reorg_threshold {
494496 return Err ( anyhow ! (
495497 "chain '{}': cache_size ({}) must be greater than reorg_threshold ({})" ,
@@ -641,7 +643,7 @@ impl ChainSection {
641643/// `polling_interval`.
642644#[ derive( Clone , Debug , PartialEq , Deserialize , Serialize ) ]
643645pub struct ChainSettings {
644- /// Set by `polling_interval` (milliseconds). Defaults to `GRAPH_ETHEREUM_POLLING_INTERVAL `.
646+ /// Set by `polling_interval` (milliseconds). Defaults to `ETHEREUM_POLLING_INTERVAL `.
645647 #[ serde(
646648 default = "default_polling_interval" ,
647649 deserialize_with = "deserialize_duration_millis"
@@ -703,6 +705,15 @@ impl Default for ChainSettings {
703705
704706impl ChainSettings {
705707 fn validate ( & self ) -> Result < ( ) > {
708+ anyhow:: ensure!(
709+ !self . polling_interval. is_zero( ) ,
710+ "polling_interval must be > 0"
711+ ) ;
712+ anyhow:: ensure!(
713+ !self . json_rpc_timeout. is_zero( ) ,
714+ "json_rpc_timeout must be > 0"
715+ ) ;
716+ anyhow:: ensure!( self . request_retries > 0 , "request_retries must be > 0" ) ;
706717 anyhow:: ensure!(
707718 self . max_block_range_size > 0 ,
708719 "max_block_range_size must be > 0"
@@ -724,6 +735,10 @@ impl ChainSettings {
724735 self . get_logs_max_contracts > 0 ,
725736 "get_logs_max_contracts must be > 0"
726737 ) ;
738+ anyhow:: ensure!(
739+ self . target_triggers_per_block_range > 0 ,
740+ "target_triggers_per_block_range must be > 0"
741+ ) ;
727742 Ok ( ( ) )
728743 }
729744}
@@ -758,22 +773,22 @@ fn default_blockchain_kind() -> BlockchainKind {
758773}
759774
760775impl Chain {
761- fn validate ( & mut self , name : & str ) -> Result < ( ) > {
776+ fn validate ( & mut self ) -> Result < ( ) > {
762777 let mut labels = self . providers . iter ( ) . map ( |p| & p. label ) . collect_vec ( ) ;
763778 labels. sort ( ) ;
764779 labels. dedup ( ) ;
765780 if labels. len ( ) != self . providers . len ( ) {
766- return Err ( anyhow ! ( "chain {}: provider labels must be unique" , name ) ) ;
781+ return Err ( anyhow ! ( "provider labels must be unique" ) ) ;
767782 }
768783
769784 // `Config` validates that `self.shard` references a configured shard
770785 for provider in self . providers . iter_mut ( ) {
771786 provider. validate ( ) ?
772787 }
773788
774- self . settings
775- . validate ( )
776- . map_err ( |e| anyhow ! ( "chain {}: {}" , name , e ) ) ? ;
789+ if self . protocol == BlockchainKind :: Ethereum {
790+ self . settings . validate ( ) ? ;
791+ }
777792
778793 Ok ( ( ) )
779794 }
@@ -2594,6 +2609,86 @@ fdw_pool_size = [
25942609 ) ;
25952610 }
25962611
2612+ #[ test]
2613+ fn chain_settings_rejects_zero_polling_interval ( ) {
2614+ let mut section = toml:: from_str :: < ChainSection > (
2615+ r#"
2616+ ingestor = "block_ingestor_node"
2617+ [mainnet]
2618+ shard = "primary"
2619+ polling_interval = 0
2620+ provider = []
2621+ "# ,
2622+ )
2623+ . unwrap ( ) ;
2624+
2625+ let err = section. validate ( ) . unwrap_err ( ) . to_string ( ) ;
2626+ assert ! (
2627+ err. contains( "polling_interval must be > 0" ) ,
2628+ "expected polling_interval error, got: {err}"
2629+ ) ;
2630+ }
2631+
2632+ #[ test]
2633+ fn chain_settings_rejects_zero_json_rpc_timeout ( ) {
2634+ let mut section = toml:: from_str :: < ChainSection > (
2635+ r#"
2636+ ingestor = "block_ingestor_node"
2637+ [mainnet]
2638+ shard = "primary"
2639+ json_rpc_timeout = 0
2640+ provider = []
2641+ "# ,
2642+ )
2643+ . unwrap ( ) ;
2644+
2645+ let err = section. validate ( ) . unwrap_err ( ) . to_string ( ) ;
2646+ assert ! (
2647+ err. contains( "json_rpc_timeout must be > 0" ) ,
2648+ "expected json_rpc_timeout error, got: {err}"
2649+ ) ;
2650+ }
2651+
2652+ #[ test]
2653+ fn chain_settings_rejects_zero_request_retries ( ) {
2654+ let mut section = toml:: from_str :: < ChainSection > (
2655+ r#"
2656+ ingestor = "block_ingestor_node"
2657+ [mainnet]
2658+ shard = "primary"
2659+ request_retries = 0
2660+ provider = []
2661+ "# ,
2662+ )
2663+ . unwrap ( ) ;
2664+
2665+ let err = section. validate ( ) . unwrap_err ( ) . to_string ( ) ;
2666+ assert ! (
2667+ err. contains( "request_retries must be > 0" ) ,
2668+ "expected request_retries error, got: {err}"
2669+ ) ;
2670+ }
2671+
2672+ #[ test]
2673+ fn chain_settings_rejects_zero_target_triggers_per_block_range ( ) {
2674+ let mut section = toml:: from_str :: < ChainSection > (
2675+ r#"
2676+ ingestor = "block_ingestor_node"
2677+ [mainnet]
2678+ shard = "primary"
2679+ target_triggers_per_block_range = 0
2680+ provider = []
2681+ "# ,
2682+ )
2683+ . unwrap ( ) ;
2684+
2685+ let err = section. validate ( ) . unwrap_err ( ) . to_string ( ) ;
2686+ assert ! (
2687+ err. contains( "target_triggers_per_block_range must be > 0" ) ,
2688+ "expected target_triggers_per_block_range error, got: {err}"
2689+ ) ;
2690+ }
2691+
25972692 #[ test]
25982693 fn chain_settings_validation_error_includes_chain_name ( ) {
25992694 let mut section = toml:: from_str :: < ChainSection > (
0 commit comments