Skip to content

Commit 0773e65

Browse files
authored
apollo_node_config: introduce validation only node config. (#13557)
1 parent ca46897 commit 0773e65

8 files changed

Lines changed: 156 additions & 2 deletions

File tree

crates/apollo_batcher_config/src/config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ pub struct BatcherStaticConfig {
247247
// TODO(Amos): Move to commitment manager config.
248248
pub first_block_with_partial_block_hash: Option<FirstBlockWithPartialBlockHash>,
249249
pub storage_reader_server_static_config: StorageReaderServerStaticConfig,
250+
/// If true, the batcher only validates proposed blocks and cannot build proposals.
251+
/// Set via the node-level validation_only config pointer.
252+
pub validation_only: bool,
250253
}
251254

252255
impl SerializeConfig for BatcherStaticConfig {
@@ -308,6 +311,13 @@ impl SerializeConfig for BatcherStaticConfig {
308311
self.storage_reader_server_static_config.dump(),
309312
"storage_reader_server_static_config",
310313
));
314+
dump.append(&mut BTreeMap::from([ser_param(
315+
"validation_only",
316+
&self.validation_only,
317+
"If true, the batcher only validates proposed blocks and cannot build proposals. Set \
318+
via the node-level validation_only config pointer.",
319+
ParamPrivacyInput::Public,
320+
)]));
311321
dump
312322
}
313323
}
@@ -336,6 +346,7 @@ impl Default for BatcherStaticConfig {
336346
propose_l1_txs_every: 1, // Default is to propose L1 transactions every proposal.
337347
first_block_with_partial_block_hash: None,
338348
storage_reader_server_static_config: StorageReaderServerStaticConfig::default(),
349+
validation_only: false,
339350
}
340351
}
341352
}

crates/apollo_deployments/resources/app_configs/general_config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"behavior_mode": "starknet",
3+
"validation_only": false,
34
"chain_id": "",
45
"eth_fee_token_address": "",
56
"native_classes_whitelist": "[]",

crates/apollo_deployments/resources/app_configs/replacer_general_config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"starknet_url": "$$$_STARKNET_URL_$$$",
1111
"strk_fee_token_address": "$$$_STRK_FEE_TOKEN_ADDRESS_$$$",
1212
"validate_resource_bounds": true,
13+
"validation_only": false,
1314
"validator_id": "$$$_VALIDATOR_ID_$$$",
1415
"versioned_constants_overrides.#is_none": true,
1516
"versioned_constants_overrides.invoke_tx_max_n_steps": 10000000,

crates/apollo_integration_tests/src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ pub fn create_node_config(
363363
let state_sync_config = wrap_if_component_config_expected!(state_sync, state_sync_config);
364364

365365
let sequencer_node_config = SequencerNodeConfig {
366+
validation_only: false,
366367
base_layer_config,
367368
batcher_config,
368369
class_manager_config,

crates/apollo_node/resources/config_schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@
399399
"privacy": "Public",
400400
"value": 8091
401401
},
402+
"batcher_config.static_config.validation_only": {
403+
"description": "If true, the batcher only validates proposed blocks and cannot build proposals. Set via the node-level validation_only config pointer.",
404+
"pointer_target": "validation_only",
405+
"privacy": "Public"
406+
},
402407
"behavior_mode": {
403408
"description": "Behavior mode: 'starknet' for production, 'echonet' for test/replay mode.",
404409
"privacy": "TemporaryValue",
@@ -3999,6 +4004,11 @@
39994004
"privacy": "TemporaryValue",
40004005
"value": true
40014006
},
4007+
"validation_only": {
4008+
"description": "If true, the node validates proposed blocks but does not build proposals. Requires gateway, http_server, and mempool to be disabled.",
4009+
"privacy": "TemporaryValue",
4010+
"value": false
4011+
},
40024012
"validator_id": {
40034013
"description": "The ID of the validator. Also the address of this validator as a starknet contract.",
40044014
"privacy": "TemporaryValue",

crates/apollo_node_config/src/component_execution_config.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub const DEFAULT_INVALID_PORT: u16 = 0;
2121

2222
pub trait ExpectedComponentConfig {
2323
fn is_running_locally(&self) -> bool;
24+
fn is_disabled(&self) -> bool;
2425
}
2526

2627
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -41,6 +42,10 @@ impl ExpectedComponentConfig for ReactiveComponentExecutionMode {
4142
| ReactiveComponentExecutionMode::LocalExecutionWithRemoteDisabled => true,
4243
}
4344
}
45+
46+
fn is_disabled(&self) -> bool {
47+
*self == ReactiveComponentExecutionMode::Disabled
48+
}
4449
}
4550

4651
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -56,6 +61,10 @@ impl ExpectedComponentConfig for ActiveComponentExecutionMode {
5661
ActiveComponentExecutionMode::Enabled => true,
5762
}
5863
}
64+
65+
fn is_disabled(&self) -> bool {
66+
*self == ActiveComponentExecutionMode::Disabled
67+
}
5968
}
6069

6170
/// Reactive component configuration.
@@ -185,6 +194,10 @@ impl ExpectedComponentConfig for ReactiveComponentExecutionConfig {
185194
fn is_running_locally(&self) -> bool {
186195
self.execution_mode.is_running_locally()
187196
}
197+
198+
fn is_disabled(&self) -> bool {
199+
self.execution_mode.is_disabled()
200+
}
188201
}
189202

190203
/// Active component configuration.
@@ -215,6 +228,10 @@ impl ExpectedComponentConfig for ActiveComponentExecutionConfig {
215228
fn is_running_locally(&self) -> bool {
216229
self.execution_mode.is_running_locally()
217230
}
231+
232+
fn is_disabled(&self) -> bool {
233+
self.execution_mode.is_disabled()
234+
}
218235
}
219236

220237
impl ActiveComponentExecutionConfig {

crates/apollo_node_config/src/config_test.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use apollo_infra_utils::dumping::serialize_to_file_test;
55
use rstest::rstest;
66
use validator::Validate;
77

8+
use crate::component_config::ComponentConfig;
89
use crate::component_execution_config::{
10+
ActiveComponentExecutionConfig,
911
ReactiveComponentExecutionConfig,
1012
ReactiveComponentExecutionMode,
1113
};
@@ -110,3 +112,63 @@ fn monitoring_config(
110112
let component_exe_config = MonitoringConfig { collect_metrics, collect_profiling_metrics };
111113
assert_eq!(component_exe_config.validate().is_ok(), expected_successful_validation);
112114
}
115+
116+
#[test]
117+
fn validation_only_with_gateway_enabled_fails() {
118+
// gateway is running locally in the default config.
119+
let config = SequencerNodeConfig { validation_only: true, ..Default::default() };
120+
let err = config.validate_node_config().unwrap_err();
121+
assert!(format!("{err:?}").contains("gateway"), "Unexpected error: {err:?}");
122+
}
123+
124+
#[test]
125+
fn validation_only_with_http_server_enabled_fails() {
126+
// Disable gateway to reach the http_server check; http_server is enabled in the default.
127+
let config = SequencerNodeConfig {
128+
validation_only: true,
129+
components: ComponentConfig {
130+
gateway: ReactiveComponentExecutionConfig::disabled(),
131+
..Default::default()
132+
},
133+
gateway_config: None,
134+
..Default::default()
135+
};
136+
let err = config.validate_node_config().unwrap_err();
137+
assert!(format!("{err:?}").contains("http_server"), "Unexpected error: {err:?}");
138+
}
139+
140+
#[test]
141+
fn validation_only_with_mempool_enabled_fails() {
142+
// Disable gateway and http_server to reach the mempool check; mempool runs locally by default.
143+
let config = SequencerNodeConfig {
144+
validation_only: true,
145+
components: ComponentConfig {
146+
gateway: ReactiveComponentExecutionConfig::disabled(),
147+
http_server: ActiveComponentExecutionConfig::disabled(),
148+
..Default::default()
149+
},
150+
gateway_config: None,
151+
http_server_config: None,
152+
..Default::default()
153+
};
154+
let err = config.validate_node_config().unwrap_err();
155+
assert!(format!("{err:?}").contains("mempool"), "Unexpected error: {err:?}");
156+
}
157+
158+
#[test]
159+
fn validation_only_with_tx_ingestion_disabled_succeeds() {
160+
let config = SequencerNodeConfig {
161+
validation_only: true,
162+
components: ComponentConfig {
163+
gateway: ReactiveComponentExecutionConfig::disabled(),
164+
http_server: ActiveComponentExecutionConfig::disabled(),
165+
mempool: ReactiveComponentExecutionConfig::disabled(),
166+
..Default::default()
167+
},
168+
gateway_config: None,
169+
http_server_config: None,
170+
mempool_config: None,
171+
..Default::default()
172+
};
173+
assert!(config.validate_node_config().is_ok());
174+
}

crates/apollo_node_config/src/node_config.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use apollo_config::dumping::{
1212
generate_struct_pointer,
1313
prepend_sub_config_name,
1414
ser_optional_sub_config,
15+
ser_param,
1516
ser_pointer_target_param,
1617
set_pointing_param_paths,
1718
ConfigPointers,
@@ -20,7 +21,7 @@ use apollo_config::dumping::{
2021
};
2122
use apollo_config::loading::load_and_process_config;
2223
use apollo_config::validators::config_validate;
23-
use apollo_config::{ConfigError, ParamPath, SerializedParam};
24+
use apollo_config::{ConfigError, ParamPath, ParamPrivacyInput, SerializedParam};
2425
use apollo_config_manager_config::config::ConfigManagerConfig;
2526
use apollo_consensus_config::config::ConsensusDynamicConfig;
2627
use apollo_consensus_manager_config::config::ConsensusManagerConfig;
@@ -213,6 +214,15 @@ pub static CONFIG_POINTERS: LazyLock<ConfigPointers> = LazyLock::new(|| {
213214
"mempool_config.static_config.behavior_mode",
214215
]),
215216
));
217+
pointers.push((
218+
ser_pointer_target_param(
219+
"validation_only",
220+
&false,
221+
"If true, the node validates proposed blocks but does not build proposals. Requires \
222+
gateway, http_server, and mempool to be disabled.",
223+
),
224+
set_pointing_param_paths(&["batcher_config.static_config.validation_only"]),
225+
));
216226
pointers
217227
});
218228

@@ -223,6 +233,9 @@ pub static CONFIG_NON_POINTERS_WHITELIST: LazyLock<Pointers> =
223233
/// The configurations of the various components of the node.
224234
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Validate)]
225235
pub struct SequencerNodeConfig {
236+
/// If true, the node validates proposed blocks but does not build proposals.
237+
/// Requires gateway, http_server, and mempool to be disabled.
238+
pub validation_only: bool,
226239
// Infra related configs.
227240
#[validate(nested)]
228241
pub components: ComponentConfig,
@@ -269,6 +282,13 @@ pub struct SequencerNodeConfig {
269282

270283
impl SerializeConfig for SequencerNodeConfig {
271284
fn dump(&self) -> BTreeMap<ParamPath, SerializedParam> {
285+
let mut dump = BTreeMap::from([ser_param(
286+
"validation_only",
287+
&self.validation_only,
288+
"If true, the node validates proposed blocks but does not build proposals. Requires \
289+
gateway, http_server, and mempool to be disabled.",
290+
ParamPrivacyInput::Public,
291+
)]);
272292
let sub_configs = vec![
273293
// Infra related configs.
274294
prepend_sub_config_name(self.components.dump(), "components"),
@@ -300,13 +320,15 @@ impl SerializeConfig for SequencerNodeConfig {
300320
ser_optional_sub_config(&self.state_sync_config, "state_sync_config"),
301321
];
302322

303-
sub_configs.into_iter().flatten().collect()
323+
dump.extend(sub_configs.into_iter().flatten());
324+
dump
304325
}
305326
}
306327

307328
impl Default for SequencerNodeConfig {
308329
fn default() -> Self {
309330
Self {
331+
validation_only: false,
310332
// Infra related configs.
311333
components: ComponentConfig::default(),
312334
config_manager_config: Some(ConfigManagerConfig::default()),
@@ -547,6 +569,35 @@ impl SequencerNodeConfig {
547569
}
548570
}
549571

572+
self.validate_validation_only_config()?;
573+
574+
Ok(())
575+
}
576+
577+
/// Validates that when `validation_only=true`, all tx-ingestion components are disabled.
578+
fn validate_validation_only_config(&self) -> Result<(), ConfigError> {
579+
if !self.validation_only {
580+
return Ok(());
581+
}
582+
if !self.components.gateway.is_disabled() {
583+
return Err(ConfigError::ComponentConfigMismatch {
584+
component_config_mismatch: "gateway must be disabled when validation_only is true"
585+
.to_string(),
586+
});
587+
}
588+
if !self.components.http_server.is_disabled() {
589+
return Err(ConfigError::ComponentConfigMismatch {
590+
component_config_mismatch: "http_server must be disabled when validation_only is \
591+
true"
592+
.to_string(),
593+
});
594+
}
595+
if !self.components.mempool.is_disabled() {
596+
return Err(ConfigError::ComponentConfigMismatch {
597+
component_config_mismatch: "mempool must be disabled when validation_only is true"
598+
.to_string(),
599+
});
600+
}
550601
Ok(())
551602
}
552603
}

0 commit comments

Comments
 (0)