diff --git a/crates/apollo_deployments/jsonnet/lib/applicative_config.libsonnet b/crates/apollo_deployments/jsonnet/lib/applicative_config.libsonnet index 442363d8868..73506e1ba1b 100644 --- a/crates/apollo_deployments/jsonnet/lib/applicative_config.libsonnet +++ b/crates/apollo_deployments/jsonnet/lib/applicative_config.libsonnet @@ -488,7 +488,7 @@ function(overrides) l1_events_scraper_config: { chain_id: chainId, finality: 10, - l1_block_time_seconds: 12.0, + l1_block_time_seconds: 12, polling_interval_seconds: 30.0, set_provider_historic_height_to_l2_genesis: false, startup_rewind_time_seconds: 21600.0, diff --git a/crates/apollo_deployments/resources/app_configs/l1_events_scraper_config.json b/crates/apollo_deployments/resources/app_configs/l1_events_scraper_config.json index 99ca99f35a1..d1af3c3d6af 100644 --- a/crates/apollo_deployments/resources/app_configs/l1_events_scraper_config.json +++ b/crates/apollo_deployments/resources/app_configs/l1_events_scraper_config.json @@ -1,6 +1,6 @@ { "l1_events_scraper_config.finality": 10, - "l1_events_scraper_config.l1_block_time_seconds": 12.0, + "l1_events_scraper_config.l1_block_time_seconds": 12, "l1_events_scraper_config.polling_interval_seconds": 30, "l1_events_scraper_config.set_provider_historic_height_to_l2_genesis": false, "l1_events_scraper_config.startup_rewind_time_seconds": 21600 diff --git a/crates/apollo_deployments/resources/app_configs/replacer_l1_events_scraper_config.json b/crates/apollo_deployments/resources/app_configs/replacer_l1_events_scraper_config.json index 99ca99f35a1..d1af3c3d6af 100644 --- a/crates/apollo_deployments/resources/app_configs/replacer_l1_events_scraper_config.json +++ b/crates/apollo_deployments/resources/app_configs/replacer_l1_events_scraper_config.json @@ -1,6 +1,6 @@ { "l1_events_scraper_config.finality": 10, - "l1_events_scraper_config.l1_block_time_seconds": 12.0, + "l1_events_scraper_config.l1_block_time_seconds": 12, "l1_events_scraper_config.polling_interval_seconds": 30, "l1_events_scraper_config.set_provider_historic_height_to_l2_genesis": false, "l1_events_scraper_config.startup_rewind_time_seconds": 21600 diff --git a/crates/apollo_deployments/src/deployment_definitions.rs b/crates/apollo_deployments/src/deployment_definitions.rs index 8945f93edd4..6105bf548fb 100644 --- a/crates/apollo_deployments/src/deployment_definitions.rs +++ b/crates/apollo_deployments/src/deployment_definitions.rs @@ -8,7 +8,8 @@ mod deployment_definitions_test; pub(crate) const CONFIG_BASE_DIR: &str = "crates/apollo_deployments/resources/"; pub(crate) const RETRIES_FOR_L1_SERVICES: usize = 0; -const BASE_APP_CONFIGS_DIR_PATH: &str = "crates/apollo_deployments/resources/app_configs"; +pub(crate) const BASE_APP_CONFIGS_DIR_PATH: &str = + "crates/apollo_deployments/resources/app_configs"; #[derive( Hash, Clone, Debug, Display, Serialize, PartialEq, Eq, PartialOrd, Ord, EnumIter, AsRefStr, diff --git a/crates/apollo_deployments/src/deployment_definitions_test.rs b/crates/apollo_deployments/src/deployment_definitions_test.rs index 5a27a26c9aa..d05f1ba6c9b 100644 --- a/crates/apollo_deployments/src/deployment_definitions_test.rs +++ b/crates/apollo_deployments/src/deployment_definitions_test.rs @@ -13,13 +13,28 @@ use crate::deployment_definitions::ComponentConfigInService; use crate::deployments::consolidated::ConsolidatedNodeServiceName; use crate::deployments::distributed::DistributedNodeServiceName; use crate::deployments::hybrid::HybridNodeServiceName; -use crate::jsonnet::{assert_build_deserializes, assert_infra_matches_rust}; +use crate::jsonnet::{ + assert_build_deserializes, + assert_infra_matches_rust, + test_applicative_matches_app_configs, +}; use crate::service::NodeType; use crate::test_utils::SecretsConfigOverride; const SECRETS_FOR_TESTING_ENV_PATH: &str = "crates/apollo_deployments/resources/testing_secrets.json"; +/// Verifies the applicative config emitted by jsonnet matches the committed `app_configs/*.json` +/// (the deployment's non-overridable value layer), up to overridable keys, secrets, and integers +/// jsonnet can't represent. +#[test] +fn applicative_matches_app_configs() { + env::set_current_dir(resolve_project_relative_path("").unwrap()) + .expect("Couldn't set working dir."); + + test_applicative_matches_app_configs(); +} + /// Verifies the jsonnet hybrid infra config matches the Rust deployment definitions (hybrid.rs). #[test] fn hybrid_infra_matches_rust() { diff --git a/crates/apollo_deployments/src/jsonnet.rs b/crates/apollo_deployments/src/jsonnet.rs index e1e77e9e6de..87c6dfafb00 100644 --- a/crates/apollo_deployments/src/jsonnet.rs +++ b/crates/apollo_deployments/src/jsonnet.rs @@ -1,12 +1,18 @@ +use std::collections::{BTreeMap, BTreeSet}; use std::path::PathBuf; -use apollo_node_config::node_config::SequencerNodeConfig; +use apollo_config::dumping::SerializeConfig; +use apollo_config::{FIELD_SEPARATOR, IS_NONE_MARK}; +use apollo_node_config::config_utils::{config_to_preset, private_parameters}; +use apollo_node_config::node_config::{SequencerNodeConfig, CONFIG_POINTERS}; use jrsonnet_evaluator::trace::PathResolver; use jrsonnet_evaluator::{FileImportResolver, State}; use serde_json::Value; use strum::IntoEnumIterator; -use crate::service::{GetComponentConfigs, NodeService, NodeType}; +use crate::deployment_definitions::BASE_APP_CONFIGS_DIR_PATH; +use crate::service::{GetComponentConfigs, NodeService, NodeType, KEYS_TO_BE_REPLACED}; +use crate::test_utils::is_path_prefix; const JSONNET_DIR: &str = "crates/apollo_deployments/jsonnet"; const TESTING_OVERRIDES_PATH: &str = "testing/overrides.libsonnet"; @@ -102,6 +108,96 @@ where } } +/// Asserts the applicative config emitted by jsonnet reproduces the committed `app_configs/*.json` +/// for every keys, except keys that are overridable, secret, or under `components.*`. +pub fn test_applicative_matches_app_configs() { + // Applicative side: the single consolidated `node` service carries every component's business + // config; round-trip through the config struct and render it in the app_configs preset format. + let built = eval_build("consolidated", TESTING_OVERRIDES_PATH); + let node = built.get("node").expect("consolidated has a `node` service").clone(); + let parsed: SequencerNodeConfig = serde_json::from_value(node).unwrap(); + let build_preset = config_to_preset(&serde_json::json!(parsed.dump())); + let build_map = build_preset.as_object().unwrap(); + + let excluded = non_default_paths(); + let is_excluded = |path: &str| { + is_path_prefix("components", path) || excluded.iter().any(|key| is_path_prefix(key, path)) + }; + + let app_config_map = merged_app_configs(); + + let mut mismatches = Vec::new(); + for (key, app_config_value) in &app_config_map { + if is_excluded(key) { + continue; + } + match build_map.get(key) { + Some(build_value) => { + if build_value != app_config_value { + mismatches.push(format!( + "{key}: applicative={build_value} app_config={app_config_value}" + )); + } + } + None => mismatches + .push(format!("{key}: missing in applicative (app_config={app_config_value})")), + } + } + + assert!( + mismatches.is_empty(), + "applicative config diverges from app_configs/*.json at {} non-overridable, non-secret \ + keys:\n {}", + mismatches.len(), + mismatches.join("\n ") + ); +} + +/// Merges every base `app_configs/.json` (skipping the derived `replacer_*` files) into +/// a single flat dotted-key map. +fn merged_app_configs() -> BTreeMap { + let mut app_config_map: BTreeMap = BTreeMap::new(); + for entry in std::fs::read_dir(BASE_APP_CONFIGS_DIR_PATH).expect("app_configs dir exists") { + let path = entry.expect("readable dir entry").path(); + let is_json = path.extension().is_some_and(|extension| extension == "json"); + let is_replacer = path.file_name().unwrap().to_string_lossy().starts_with("replacer_"); + if !is_json || is_replacer { + continue; + } + let contents = std::fs::read_to_string(&path).expect("app_config file is readable"); + let object: serde_json::Map = + serde_json::from_str(&contents).expect("app_config is a JSON object"); + app_config_map.extend(object); + } + app_config_map +} + +/// The config paths that are overridable or secrets or passed as pointers. +fn non_default_paths() -> BTreeSet { + // An optional config is marked overridable/secret as `.#is_none`; the override replaces + // the whole option, so exclude the `` subtree (not just the marker). + let is_none_suffix = format!("{FIELD_SEPARATOR}{IS_NONE_MARK}"); + let insert_with_option_root = |paths: &mut BTreeSet, key: &str| { + paths.insert(key.to_string()); + if let Some(option_root) = key.strip_suffix(&is_none_suffix) { + paths.insert(option_root.to_string()); + } + }; + + let mut paths = BTreeSet::new(); + for key in KEYS_TO_BE_REPLACED.iter() { + insert_with_option_root(&mut paths, key); + } + for ((target_path, _param), pointing_paths) in CONFIG_POINTERS.iter() { + paths.insert(target_path.clone()); + paths.extend(pointing_paths.iter().cloned()); + } + for key in private_parameters() { + insert_with_option_root(&mut paths, &key); + } + paths +} + /// Clones a `components` map with `url` and `port` removed from each component object — the two /// fields the Rust config leaves as deploy-time placeholders, so they can't be compared against the /// jsonnet's baked-in real values. diff --git a/crates/apollo_deployments/src/test_utils.rs b/crates/apollo_deployments/src/test_utils.rs index 5abd7cc74c4..dfb296fdf5a 100644 --- a/crates/apollo_deployments/src/test_utils.rs +++ b/crates/apollo_deployments/src/test_utils.rs @@ -9,6 +9,12 @@ use url::Url; pub(crate) const FIX_BINARY_NAME: &str = "deployment_generator"; +/// Returns `true` if `prefix` is a path-prefix of the dotted config key `path`: either the same key +/// or a dot-bounded ancestor of it (so `range_check` does not match the sibling `range_check96`). +pub(crate) fn is_path_prefix(prefix: &str, path: &str) -> bool { + path.strip_prefix(prefix).is_some_and(|rest| rest.is_empty() || rest.starts_with('.')) +} + #[derive(Serialize)] pub struct SecretsConfigOverride { #[serde(