diff --git a/crates/apollo_deployments/jsonnet/testing/overrides.libsonnet b/crates/apollo_deployments/jsonnet/testing/overrides.libsonnet new file mode 100644 index 00000000000..8e9e87e366a --- /dev/null +++ b/crates/apollo_deployments/jsonnet/testing/overrides.libsonnet @@ -0,0 +1,167 @@ +// Testing `overrides` that satisfies the applicative config's schema. +{ + // Cross-cutting values (the CONFIG_POINTERS targets), referenced as flat keys. + chain_id: 'SN_SEPOLIA', + eth_fee_token_address: '0x1', + strk_fee_token_address: '0x2', + recorder_url: 'https://recorder_url', + starknet_url: 'https://starknet_url/', + native_classes_whitelist: '[]', + validator_id: '0x64', + versioned_constants_overrides: null, + + // Per-component required values, at their exact schema paths. + base_layer_config: { + bpo1_start_block_number: 9456501, + bpo2_start_block_number: 9504747, + fusaka_no_bpo_start_block_number: 9408577, + starknet_contract_address: '0x0000000000000000000000000000000000000001', + }, + batcher_config: { + dynamic_config: { + n_concurrent_txs: 100, + proposer_idle_detection_delay_millis: 1500, + }, + static_config: { + block_builder_config: { + bouncer_config: { + block_max_capacity: { + n_events: 5000, + receipt_l2_gas: 5800000000, + state_diff_size: 4000, + }, + }, + execute_config: { + n_workers: 28, + }, + }, + first_block_with_partial_block_hash: null, + }, + }, + class_manager_config: { + static_config: { + class_manager_config: { + max_compiled_contract_class_object_size: 4089446, + }, + }, + }, + committer_config: { + storage_config: { + cache_size: 10000000, + inner_storage_config: { + cache_size: 8589934592, + }, + }, + verify_state_diff_hash: true, + }, + consensus_manager_config: { + consensus_manager_config: { + dynamic_config: { + require_virtual_proposer_vote: false, + timeouts: { + proposal: { + base: 9.1, + max: 15.0, + }, + }, + }, + }, + context_config: { + dynamic_config: { + build_proposal_margin_millis: 1000, + compare_retrospective_block_hash: false, + min_l2_gas_price_per_height: '', + override_eth_to_fri_rate: null, + override_l1_data_gas_price_fri: null, + override_l1_gas_price_fri: null, + override_l2_gas_price_fri: null, + }, + }, + network_config: { + advertised_multiaddr: null, + bootstrap_peer_multiaddr: null, + port: 53080, + }, + staking_manager_config: { + dynamic_config: { + default_committee: '0,100:', + override_committee: null, + }, + }, + }, + gateway_config: { + static_config: { + authorized_declarer_accounts: null, + proof_archive_writer_config: { + bucket_name: 'test-bucket', + }, + stateful_tx_validator_config: { + max_allowed_nonce_gap: 200, + }, + stateless_tx_validator_config: { + max_contract_bytecode_size: 81920, + min_gas_price: 8000000000, + }, + }, + }, + http_server_config: { + static_config: { + port: 8080, + }, + }, + mempool_config: { + dynamic_config: { + transaction_ttl: 300, + }, + }, + mempool_p2p_config: { + network_config: { + advertised_multiaddr: null, + bootstrap_peer_multiaddr: null, + port: 53200, + }, + }, + monitoring_endpoint_config: { + port: 8082, + }, + sierra_compiler_config: { + audited_libfuncs_only: false, + max_bytecode_size: 81920, + }, + state_sync_config: { + static_config: { + central_sync_client_config: { + central_source_config: { + class_cache_size: 128, + concurrent_requests: 20, + http_headers: '', + max_classes_to_download: 20, + max_state_updates_to_download: 20, + max_state_updates_to_store_in_memory: 20, + retry_config: { + max_retries: 10, + retry_base_millis: 30, + retry_max_delay_millis: 30000, + }, + starknet_url: 'https://starknet_url/', + }, + sync_config: { + base_layer_propagation_sleep_duration: 10, + blocks_before_tip_to_disable_batching: 100, + blocks_max_stream_size: 1000, + collect_pending_data: false, + latest_block_poll_interval_millis: 500, + recoverable_error_sleep_duration: 3, + state_updates_max_stream_size: 1000, + store_sierras_and_casms_block_threshold: 0, + verify_blocks: false, + }, + }, + network_config: null, + p2p_sync_client_config: null, + rpc_config: { + port: 8083, + }, + }, + }, +} diff --git a/crates/apollo_deployments/src/deployment_definitions_test.rs b/crates/apollo_deployments/src/deployment_definitions_test.rs index 607e3d00c12..5a27a26c9aa 100644 --- a/crates/apollo_deployments/src/deployment_definitions_test.rs +++ b/crates/apollo_deployments/src/deployment_definitions_test.rs @@ -13,7 +13,7 @@ use crate::deployment_definitions::ComponentConfigInService; use crate::deployments::consolidated::ConsolidatedNodeServiceName; use crate::deployments::distributed::DistributedNodeServiceName; use crate::deployments::hybrid::HybridNodeServiceName; -use crate::jsonnet::assert_infra_matches_rust; +use crate::jsonnet::{assert_build_deserializes, assert_infra_matches_rust}; use crate::service::NodeType; use crate::test_utils::SecretsConfigOverride; @@ -49,6 +49,33 @@ fn distributed_infra_matches_rust() { assert_infra_matches_rust::(); } +/// Verifies build('consolidated', overrides) deserializes into SequencerNodeConfig per service. +#[test] +fn build_consolidated_deserializes_into_node_config() { + env::set_current_dir(resolve_project_relative_path("").unwrap()) + .expect("Couldn't set working dir."); + + assert_build_deserializes::(); +} + +/// Verifies build('hybrid', overrides) deserializes into SequencerNodeConfig per service. +#[test] +fn build_hybrid_deserializes_into_node_config() { + env::set_current_dir(resolve_project_relative_path("").unwrap()) + .expect("Couldn't set working dir."); + + assert_build_deserializes::(); +} + +/// Verifies build('distributed', overrides) deserializes into SequencerNodeConfig per service. +#[test] +fn build_distributed_deserializes_into_node_config() { + env::set_current_dir(resolve_project_relative_path("").unwrap()) + .expect("Couldn't set working dir."); + + assert_build_deserializes::(); +} + /// Test that the deployment file is up to date. #[test] fn deployment_files_are_up_to_date() { diff --git a/crates/apollo_deployments/src/jsonnet.rs b/crates/apollo_deployments/src/jsonnet.rs index ea465f35dd2..e1e77e9e6de 100644 --- a/crates/apollo_deployments/src/jsonnet.rs +++ b/crates/apollo_deployments/src/jsonnet.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use apollo_node_config::node_config::SequencerNodeConfig; use jrsonnet_evaluator::trace::PathResolver; use jrsonnet_evaluator::{FileImportResolver, State}; use serde_json::Value; @@ -8,14 +9,23 @@ use strum::IntoEnumIterator; use crate::service::{GetComponentConfigs, NodeService, NodeType}; const JSONNET_DIR: &str = "crates/apollo_deployments/jsonnet"; +const TESTING_OVERRIDES_PATH: &str = "testing/overrides.libsonnet"; -/// Evaluates `services/.jsonnet` (the per-layout infra renderer) and returns its JSON. -fn eval_layout_infra(layout: &str) -> Value { +/// Evaluates a jsonnet `snippet` against a fresh evaluator (stdlib installed, imports resolved +/// relative to the jsonnet dir) and converts the result to a serde `Value`. `context` labels the +/// evaluation in panic messages. +fn eval_jsonnet(context: &str, snippet: String) -> Value { let state = jsonnet_state(); let _guard = state.enter(); - let entry = format!("services/{layout}.jsonnet"); - let val = state.import(entry.as_str()).expect("failed to evaluate the layout infra renderer"); - serde_json::to_value(&val).expect("infra config is not serializable") + let val = state + .evaluate_snippet(context.to_owned(), snippet) + .expect("Failed to evaluate jsonnet snippet."); + serde_json::to_value(&val).expect("Failed to serialize jsonnet result to Value.") +} + +/// Evaluates `services/.jsonnet` (the per-layout infra renderer) and returns its JSON. +fn eval_layout_infra(layout: &str) -> Value { + eval_jsonnet("layout infra", format!("import 'services/{layout}.jsonnet'")) } /// A jrsonnet evaluator with the stdlib installed and file imports resolved relative to the jsonnet @@ -57,6 +67,41 @@ where } } +/// Evaluates `build(layout, overrides)` and returns its JSON: a map from service name to that +/// service's fully-assembled config. +fn eval_build(layout: &str, overrides: &str) -> Value { + let layout_literal = serde_json::to_string(layout).unwrap(); + eval_jsonnet( + "build", + format!("(import 'lib/build.libsonnet').build({layout_literal}, import '{overrides}')"), + ) +} + +/// Asserts that `build(layout, testing_overrides)` produces, for every service of layout `S`, an +/// object that deserializes into `SequencerNodeConfig`. +pub(crate) fn assert_build_deserializes() +where + S: GetComponentConfigs + IntoEnumIterator + Into, +{ + let some_service: NodeService = + S::iter().next().expect("a layout has at least one service").into(); + let layout = NodeType::from(&some_service).to_string(); + let built = eval_build(&layout, TESTING_OVERRIDES_PATH); + let services = built.as_object().unwrap(); + + // Sanity check: the build result should have at least one service. + assert!(!services.is_empty(), "build({layout}) produced no services"); + + for (service_name, config) in services { + serde_json::from_value::(config.clone()).unwrap_or_else(|error| { + panic!( + "service {service_name} of layout {layout} does not deserialize into \ + SequencerNodeConfig: {error}" + ) + }); + } +} + /// 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.