11use std:: collections:: BTreeSet ;
22
3+ use apollo_config:: behavior_mode:: BehaviorMode ;
34use apollo_config:: dumping:: SerializeConfig ;
45use apollo_config:: ParamPath ;
56use apollo_infra:: component_client:: RemoteClientConfig ;
67use apollo_infra:: component_server:: { LocalServerConfig , RemoteServerConfig } ;
78use apollo_infra_utils:: dumping:: serialize_to_file_test;
9+ use apollo_reverts:: RevertConfig ;
810use apollo_state_sync_config:: config:: { StateSyncConfig , StateSyncStaticConfig } ;
911use apollo_storage:: { StorageConfig , StorageScope } ;
12+ use blockifier:: blockifier:: config:: NativeClassesWhitelist ;
1013use rstest:: rstest;
14+ use starknet_api:: contract_address;
15+ use starknet_api:: core:: ChainId ;
1116use validator:: Validate ;
1217
1318use crate :: component_config:: ComponentConfig ;
@@ -290,7 +295,7 @@ fn config_manager_local_with_remote_enabled_is_rejected() {
290295
291296#[ test]
292297fn validation_only_with_tx_ingestion_disabled_succeeds ( ) {
293- let config = SequencerNodeConfig {
298+ let mut config = SequencerNodeConfig {
294299 validation_only : true ,
295300 components : ComponentConfig {
296301 gateway : ReactiveComponentExecutionConfig :: disabled ( ) ,
@@ -306,5 +311,181 @@ fn validation_only_with_tx_ingestion_disabled_succeeds() {
306311 state_sync_config : Some ( state_sync_config_with_full_archive ( ) ) ,
307312 ..Default :: default ( )
308313 } ;
314+ // `SequencerNodeConfig::default()` does not have internally consistent pointer-group values
315+ // (those are only reconciled by pointer resolution at load time), so normalize them before
316+ // exercising the validation_only logic this test targets.
317+ normalize_pointer_groups ( & mut config) ;
309318 assert ! ( config. validate_node_config( ) . is_ok( ) ) ;
310319}
320+
321+ /// Overwrites every present target of each multi-target `CONFIG_POINTERS` group with a single,
322+ /// consistent value, mirroring what pointer resolution does at load time. Lets a config assembled
323+ /// directly from `SequencerNodeConfig::default()` satisfy the cross-component equality invariant.
324+ fn normalize_pointer_groups ( config : & mut SequencerNodeConfig ) {
325+ let chain_id = ChainId :: Mainnet ;
326+ let eth_fee_token_address = contract_address ! ( "0x1" ) ;
327+ let strk_fee_token_address = contract_address ! ( "0x2" ) ;
328+ let max_cpu_time: u64 = 600 ;
329+
330+ config. validation_only = false ;
331+ if let Some ( sierra_compiler) = config. sierra_compiler_config . as_mut ( ) {
332+ sierra_compiler. max_cpu_time = max_cpu_time;
333+ }
334+ if let Some ( batcher) = config. batcher_config . as_mut ( ) {
335+ let static_config = & mut batcher. static_config ;
336+ static_config. block_builder_config . chain_info . chain_id = chain_id. clone ( ) ;
337+ static_config. storage . db_config . chain_id = chain_id. clone ( ) ;
338+ let fee_token_addresses =
339+ & mut static_config. block_builder_config . chain_info . fee_token_addresses ;
340+ fee_token_addresses. eth_fee_token_address = eth_fee_token_address;
341+ fee_token_addresses. strk_fee_token_address = strk_fee_token_address;
342+ static_config. contract_class_manager_config . native_compiler_config . max_cpu_time =
343+ max_cpu_time;
344+ static_config. pre_confirmed_cende_config . recorder_url =
345+ "https://recorder_url" . parse ( ) . unwrap ( ) ;
346+ static_config. block_builder_config . versioned_constants_overrides = None ;
347+ static_config. validation_only = false ;
348+ batcher. dynamic_config . native_classes_whitelist = NativeClassesWhitelist :: All ;
349+ }
350+ if let Some ( class_manager) = config. class_manager_config . as_mut ( ) {
351+ class_manager
352+ . static_config
353+ . class_storage_config
354+ . class_hash_storage_config
355+ . db_config
356+ . chain_id = chain_id. clone ( ) ;
357+ }
358+ if let Some ( consensus_manager) = config. consensus_manager_config . as_mut ( ) {
359+ consensus_manager
360+ . consensus_manager_config
361+ . static_config
362+ . storage_config
363+ . db_config
364+ . chain_id = chain_id. clone ( ) ;
365+ consensus_manager. context_config . static_config . chain_id = chain_id. clone ( ) ;
366+ consensus_manager. network_config . chain_id = chain_id. clone ( ) ;
367+ consensus_manager. context_config . static_config . behavior_mode = BehaviorMode :: Starknet ;
368+ consensus_manager. cende_config . recorder_url = "https://recorder_url" . parse ( ) . unwrap ( ) ;
369+ consensus_manager. revert_config = RevertConfig :: default ( ) ;
370+ }
371+ if let Some ( gateway) = config. gateway_config . as_mut ( ) {
372+ gateway. static_config . chain_info . chain_id = chain_id. clone ( ) ;
373+ let fee_token_addresses = & mut gateway. static_config . chain_info . fee_token_addresses ;
374+ fee_token_addresses. eth_fee_token_address = eth_fee_token_address;
375+ fee_token_addresses. strk_fee_token_address = strk_fee_token_address;
376+ gateway. static_config . contract_class_manager_config . native_compiler_config . max_cpu_time =
377+ max_cpu_time;
378+ gateway. static_config . stateful_tx_validator_config . validate_resource_bounds = true ;
379+ gateway. static_config . stateless_tx_validator_config . validate_resource_bounds = true ;
380+ gateway. static_config . stateful_tx_validator_config . versioned_constants_overrides = None ;
381+ gateway. dynamic_config . native_classes_whitelist = NativeClassesWhitelist :: All ;
382+ }
383+ if let Some ( l1_events_scraper) = config. l1_events_scraper_config . as_mut ( ) {
384+ l1_events_scraper. chain_id = chain_id. clone ( ) ;
385+ }
386+ if let Some ( l1_gas_price_scraper) = config. l1_gas_price_scraper_config . as_mut ( ) {
387+ l1_gas_price_scraper. chain_id = chain_id. clone ( ) ;
388+ }
389+ if let Some ( mempool) = config. mempool_config . as_mut ( ) {
390+ mempool. static_config . recorder_url = "https://recorder_url" . parse ( ) . unwrap ( ) ;
391+ mempool. static_config . validate_resource_bounds = true ;
392+ mempool. static_config . behavior_mode = BehaviorMode :: Starknet ;
393+ }
394+ if let Some ( mempool_p2p) = config. mempool_p2p_config . as_mut ( ) {
395+ mempool_p2p. network_config . chain_id = chain_id. clone ( ) ;
396+ }
397+ if let Some ( state_sync) = config. state_sync_config . as_mut ( ) {
398+ let static_config = & mut state_sync. static_config ;
399+ static_config. storage_config . db_config . chain_id = chain_id. clone ( ) ;
400+ if let Some ( network_config) = static_config. network_config . as_mut ( ) {
401+ network_config. chain_id = chain_id. clone ( ) ;
402+ }
403+ static_config. rpc_config . chain_id = chain_id. clone ( ) ;
404+ static_config. rpc_config . execution_config . eth_fee_contract_address = eth_fee_token_address;
405+ static_config. rpc_config . execution_config . strk_fee_contract_address =
406+ strk_fee_token_address;
407+ static_config. revert_config = RevertConfig :: default ( ) ;
408+ }
409+ }
410+
411+ /// A config assembled directly from `SequencerNodeConfig::default()` is not internally consistent
412+ /// on pointer-group values, so after normalizing those groups it validates `Ok`. This is the
413+ /// "full" positive case: every component is present and every group agrees.
414+ #[ test]
415+ fn pointer_groups_consistent_full_config_validates ( ) {
416+ let mut config = SequencerNodeConfig :: default ( ) ;
417+ normalize_pointer_groups ( & mut config) ;
418+ assert ! (
419+ config. validate_node_config( ) . is_ok( ) ,
420+ "normalized full config should validate: {:?}" ,
421+ config. validate_node_config( )
422+ ) ;
423+ }
424+
425+ /// Present-only guard: when only one owner of a pointer group is present (a partial/distributed
426+ /// deployment), the equality check has nothing to compare against and validates `Ok`.
427+ #[ test]
428+ fn pointer_groups_single_present_owner_validates ( ) {
429+ // Only `gateway_config` owns `native_classes_whitelist`/`validate_resource_bounds`; with the
430+ // batcher and mempool absent there is a single present value, so the group is trivially equal.
431+ let mut config = SequencerNodeConfig {
432+ batcher_config : None ,
433+ mempool_config : None ,
434+ ..SequencerNodeConfig :: default ( )
435+ } ;
436+ // Disable the now-absent components so the per-component "set iff running locally" check
437+ // passes.
438+ config. components . batcher = ReactiveComponentExecutionConfig :: disabled ( ) ;
439+ config. components . mempool = ReactiveComponentExecutionConfig :: disabled ( ) ;
440+ normalize_pointer_groups ( & mut config) ;
441+ assert ! (
442+ config. validate_node_config( ) . is_ok( ) ,
443+ "single-owner config should validate: {:?}" ,
444+ config. validate_node_config( )
445+ ) ;
446+ }
447+
448+ /// Negative: a uniform shared field (`chain_id`) diverging between two present owners fails.
449+ #[ test]
450+ fn pointer_group_chain_id_mismatch_fails ( ) {
451+ let mut config = SequencerNodeConfig :: default ( ) ;
452+ normalize_pointer_groups ( & mut config) ;
453+ // Diverge the gateway's chain_id from everyone else's.
454+ config. gateway_config . as_mut ( ) . unwrap ( ) . static_config . chain_info . chain_id = ChainId :: Sepolia ;
455+ let err = config. validate_node_config ( ) . unwrap_err ( ) ;
456+ assert ! ( format!( "{err:?}" ) . contains( "chain_id" ) , "Unexpected error: {err:?}" ) ;
457+ }
458+
459+ /// Negative covering the fee-token name asymmetry: the batcher/gateway `eth_fee_token_address` and
460+ /// the state_sync `eth_fee_contract_address` are the same logical value; diverging them fails.
461+ #[ test]
462+ fn pointer_group_eth_fee_token_name_asymmetry_mismatch_fails ( ) {
463+ let mut config = SequencerNodeConfig :: default ( ) ;
464+ normalize_pointer_groups ( & mut config) ;
465+ // state_sync stores it under `eth_fee_contract_address`; diverge it from the gateway/batcher.
466+ config
467+ . state_sync_config
468+ . as_mut ( )
469+ . unwrap ( )
470+ . static_config
471+ . rpc_config
472+ . execution_config
473+ . eth_fee_contract_address = contract_address ! ( "0xdead" ) ;
474+ let err = config. validate_node_config ( ) . unwrap_err ( ) ;
475+ assert ! ( format!( "{err:?}" ) . contains( "eth_fee_token_address" ) , "Unexpected error: {err:?}" ) ;
476+ }
477+
478+ /// Negative: the node-level `validation_only` source disagreeing with the batcher's copy (its lone
479+ /// pointer target, which actually drives batcher behavior) fails. Guards the source-vs-target
480+ /// group.
481+ #[ test]
482+ fn pointer_group_validation_only_mismatch_fails ( ) {
483+ let mut config = SequencerNodeConfig :: default ( ) ;
484+ normalize_pointer_groups ( & mut config) ;
485+ // Top-level `validation_only` is false (set by `normalize_pointer_groups`); diverge the
486+ // batcher's copy. The top-level flag stays false, so `validate_validation_only_config` is a
487+ // no-op and the equality group is what must catch this.
488+ config. batcher_config . as_mut ( ) . unwrap ( ) . static_config . validation_only = true ;
489+ let err = config. validate_node_config ( ) . unwrap_err ( ) ;
490+ assert ! ( format!( "{err:?}" ) . contains( "validation_only" ) , "Unexpected error: {err:?}" ) ;
491+ }
0 commit comments