Skip to content

Commit 53b6f0a

Browse files
authored
apollo_node_config: batcher support for validation only node config. (#13625)
1 parent 0773e65 commit 53b6f0a

5 files changed

Lines changed: 178 additions & 21 deletions

File tree

crates/apollo_batcher/src/batcher.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ pub struct Batcher {
157157
pub storage_writer: Box<dyn BatcherStorageWriter>,
158158
pub committer_client: SharedCommitterClient,
159159
pub l1_events_provider_client: SharedL1EventsProviderClient,
160-
pub mempool_client: SharedMempoolClient,
160+
pub mempool_client: Option<SharedMempoolClient>,
161161
pub config_manager_client: SharedConfigManagerClient,
162162

163163
/// Used to create block builders.
@@ -212,13 +212,19 @@ impl Batcher {
212212
storage_writer: Box<dyn BatcherStorageWriter>,
213213
committer_client: SharedCommitterClient,
214214
l1_events_provider_client: SharedL1EventsProviderClient,
215-
mempool_client: SharedMempoolClient,
215+
mempool_client: Option<SharedMempoolClient>,
216216
config_manager_client: SharedConfigManagerClient,
217217
block_builder_factory: Box<dyn BlockBuilderFactoryTrait>,
218218
pre_confirmed_block_writer_factory: Box<dyn PreconfirmedBlockWriterFactoryTrait>,
219219
commitment_manager: ApolloCommitmentManager,
220220
storage_reader_server_handle: AbortHandle,
221221
) -> Self {
222+
assert!(
223+
config.static_config.validation_only == mempool_client.is_none(),
224+
"validation_only={} but mempool_client is {}",
225+
config.static_config.validation_only,
226+
if mempool_client.is_none() { "None" } else { "Some" }
227+
);
222228
Self {
223229
config,
224230
storage_reader,
@@ -276,6 +282,9 @@ impl Batcher {
276282
&mut self,
277283
propose_block_input: ProposeBlockInput,
278284
) -> BatcherResult<()> {
285+
if self.config.static_config.validation_only {
286+
return Err(BatcherError::ProposingNotSupported);
287+
}
279288
let block_number = propose_block_input.block_info.block_number;
280289
let proposal_metrics_handle = ProposalMetricsHandle::new();
281290
let active_height = self.active_height.ok_or(BatcherError::NoActiveHeight)?;
@@ -285,12 +294,17 @@ impl Batcher {
285294
propose_block_input.retrospective_block_hash,
286295
)?;
287296

297+
let mempool_client = self
298+
.mempool_client
299+
.as_ref()
300+
.expect("Mempool client must be present in non-validation-only mode.");
301+
288302
// TODO(yair): extract function for the following calls, use join_all.
289303
info!(
290304
"Notifying the mempool we start to work on block {}, round {}.",
291305
block_number, propose_block_input.proposal_round
292306
);
293-
self.mempool_client.commit_block(CommitBlockArgs::default()).await.map_err(|err| {
307+
mempool_client.commit_block(CommitBlockArgs::default()).await.map_err(|err| {
294308
error!(
295309
"Mempool is not ready to start proposal {}: {}.",
296310
propose_block_input.proposal_id, err
@@ -301,7 +315,7 @@ impl Batcher {
301315
"Updating gas price for block {}, round {} in Mempool client",
302316
block_number, propose_block_input.proposal_round
303317
);
304-
self.mempool_client
318+
mempool_client
305319
.update_gas_price(
306320
propose_block_input.block_info.gas_prices.strk_gas_prices.l2_gas_price.get(),
307321
)
@@ -333,7 +347,7 @@ impl Batcher {
333347
TxProviderPhase::Mempool
334348
};
335349
let tx_provider = ProposeTransactionProvider::new(
336-
self.mempool_client.clone(),
350+
mempool_client.clone(),
337351
self.l1_events_provider_client.clone(),
338352
self.config.static_config.max_l1_handler_txs_per_block_proposal,
339353
propose_block_input.block_info.block_number,
@@ -613,7 +627,11 @@ impl Batcher {
613627

614628
#[instrument(skip(self), err)]
615629
pub async fn get_batch_timestamp(&self) -> BatcherResult<UnixTimestamp> {
616-
self.mempool_client.resolve_batch_timestamp().await.map_err(|err| {
630+
let mempool_client = self.mempool_client.as_ref().expect(
631+
"Mempool client must be present in non-validation-only mode. Unreachable code when \
632+
validation-only mode is enabled.",
633+
);
634+
mempool_client.resolve_batch_timestamp().await.map_err(|err| {
617635
error!("Failed to get timestamp from mempool: {err}");
618636
BatcherError::InternalError
619637
})
@@ -933,16 +951,16 @@ impl Batcher {
933951
BATCHER_L1_EVENTS_PROVIDER_ERRORS.increment(1);
934952
}
935953

936-
// Notify the mempool of the new block.
937-
let mempool_result = self
938-
.mempool_client
939-
.commit_block(CommitBlockArgs { address_to_nonce, rejected_tx_hashes })
940-
.await;
941-
942-
if let Err(mempool_err) = mempool_result {
943-
// Recoverable error, mempool won't be updated with the new block.
944-
error!("Failed to commit block to mempool: {}", mempool_err);
945-
};
954+
// Notify the mempool of the new block (skipped in validation-only mode).
955+
if let Some(mempool_client) = &self.mempool_client {
956+
let mempool_result = mempool_client
957+
.commit_block(CommitBlockArgs { address_to_nonce, rejected_tx_hashes })
958+
.await;
959+
if let Err(mempool_err) = mempool_result {
960+
// Recoverable error, mempool won't be updated with the new block.
961+
error!("Failed to commit block to mempool: {}", mempool_err);
962+
}
963+
}
946964

947965
BUILDING_HEIGHT.increment(1);
948966
Ok(())
@@ -1412,7 +1430,7 @@ impl DynamicConfigProvider for BatcherDynamicConfigProvider {
14121430
pub async fn create_batcher(
14131431
config: BatcherConfig,
14141432
committer_client: SharedCommitterClient,
1415-
mempool_client: SharedMempoolClient,
1433+
mempool_client: Option<SharedMempoolClient>,
14161434
l1_events_provider_client: SharedL1EventsProviderClient,
14171435
class_manager_client: SharedClassManagerClient,
14181436
pre_confirmed_cende_client: Arc<dyn PreconfirmedCendeClientTrait>,

crates/apollo_batcher/src/batcher_test.rs

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ use apollo_infra::component_client::ClientError;
3333
use apollo_infra::component_definitions::ComponentStarter;
3434
use apollo_l1_events_types::errors::{L1EventsProviderClientError, L1EventsProviderError};
3535
use apollo_l1_events_types::{MockL1EventsProviderClient, SessionState};
36-
use apollo_mempool_types::communication::{MempoolClientError, MockMempoolClient};
36+
use apollo_mempool_types::communication::{
37+
MempoolClientError,
38+
MockMempoolClient,
39+
SharedMempoolClient,
40+
};
3741
use apollo_mempool_types::mempool_types::CommitBlockArgs;
3842
use apollo_state_sync_types::state_sync_types::SyncBlock;
3943
use apollo_storage::db::DbError;
@@ -256,6 +260,11 @@ async fn create_batcher_impl<R: BatcherStorageReader + 'static>(
256260
clients: MockClients,
257261
config: BatcherConfig,
258262
) -> Batcher {
263+
let mempool_client: Option<SharedMempoolClient> = if config.static_config.validation_only {
264+
None
265+
} else {
266+
Some(Arc::new(clients.mempool_client))
267+
};
259268
let committer_client = Arc::new(clients.committer_client);
260269
let commitment_manager = CommitmentManager::create_commitment_manager(
261270
&config.static_config.commitment_manager_config,
@@ -275,7 +284,7 @@ async fn create_batcher_impl<R: BatcherStorageReader + 'static>(
275284
storage_writer,
276285
committer_client,
277286
Arc::new(clients.l1_provider_client),
278-
Arc::new(clients.mempool_client),
287+
mempool_client,
279288
Arc::new(mock_config_manager),
280289
Box::new(clients.block_builder_factory),
281290
Box::new(clients.pre_confirmed_block_writer_factory),
@@ -291,6 +300,39 @@ fn abort_signal_sender() -> AbortSignalSender {
291300
tokio::sync::oneshot::channel().0
292301
}
293302

303+
/// Calls `Batcher::new` with an explicit `mempool_client`, bypassing the auto-derivation in
304+
/// `create_batcher_impl`. Used to test the consistency assert in `Batcher::new`.
305+
async fn new_batcher_with_mempool_override(
306+
deps: MockDependencies,
307+
mempool_client: Option<SharedMempoolClient>,
308+
) {
309+
let storage_reader = Arc::new(deps.storage_reader);
310+
let committer_client = Arc::new(deps.clients.committer_client);
311+
let commitment_manager = CommitmentManager::create_commitment_manager(
312+
&deps.batcher_config.static_config.commitment_manager_config,
313+
storage_reader.clone(),
314+
committer_client.clone(),
315+
)
316+
.await;
317+
let mut mock_config_manager = MockConfigManagerClient::new();
318+
mock_config_manager
319+
.expect_get_batcher_dynamic_config()
320+
.returning(|| Ok(BatcherDynamicConfig::default()));
321+
Batcher::new(
322+
deps.batcher_config,
323+
storage_reader,
324+
Box::new(deps.storage_writer),
325+
committer_client,
326+
Arc::new(deps.clients.l1_provider_client),
327+
mempool_client,
328+
Arc::new(mock_config_manager),
329+
Box::new(deps.clients.block_builder_factory),
330+
Box::new(deps.clients.pre_confirmed_block_writer_factory),
331+
commitment_manager,
332+
tokio::spawn(async {}).abort_handle(),
333+
);
334+
}
335+
294336
async fn batcher_propose_and_commit_block(
295337
mock_dependencies: MockDependencies,
296338
) -> Result<DecisionReachedResponse, BatcherError> {
@@ -1840,3 +1882,90 @@ async fn test_reversed_state_diff() {
18401882
reversed_state_diff.nonces,
18411883
);
18421884
}
1885+
1886+
fn validation_only_mock_dependencies() -> MockDependencies {
1887+
let mut deps = MockDependencies::default();
1888+
deps.batcher_config.static_config.validation_only = true;
1889+
deps
1890+
}
1891+
1892+
#[tokio::test]
1893+
async fn validation_only_propose_block_returns_not_supported() {
1894+
let mut batcher = create_batcher(validation_only_mock_dependencies()).await;
1895+
batcher.start_height(StartHeightInput { height: INITIAL_HEIGHT }).await.unwrap();
1896+
1897+
let result = batcher.propose_block(propose_block_input(PROPOSAL_ID)).await;
1898+
1899+
assert_eq!(result, Err(BatcherError::ProposingNotSupported));
1900+
}
1901+
1902+
#[tokio::test]
1903+
#[should_panic(expected = "Mempool client must be present in non-validation-only mode.")]
1904+
async fn validation_only_get_batch_timestamp_panics() {
1905+
let batcher = create_batcher(validation_only_mock_dependencies()).await;
1906+
batcher.get_batch_timestamp().await.unwrap();
1907+
}
1908+
1909+
#[tokio::test]
1910+
async fn validation_only_validate_block_succeeds() {
1911+
let mut mock_deps = validation_only_mock_dependencies();
1912+
let mut block_builder_factory = MockBlockBuilderFactoryTrait::new();
1913+
mock_create_builder_for_validate_block(
1914+
&mut block_builder_factory,
1915+
Ok(BlockExecutionArtifacts::create_for_testing().await),
1916+
);
1917+
mock_deps.clients.block_builder_factory = block_builder_factory;
1918+
mock_deps.clients.l1_provider_client.expect_start_block().returning(|_, _| Ok(()));
1919+
1920+
let mut batcher = create_batcher(mock_deps).await;
1921+
batcher.start_height(StartHeightInput { height: INITIAL_HEIGHT }).await.unwrap();
1922+
1923+
batcher.validate_block(validate_block_input(PROPOSAL_ID)).await.unwrap();
1924+
1925+
let finish_proposal = FinishProposalInput {
1926+
proposal_id: PROPOSAL_ID,
1927+
final_n_executed_txs: DUMMY_FINAL_N_EXECUTED_TXS,
1928+
};
1929+
let result = batcher.finish_proposal(finish_proposal).await.unwrap();
1930+
assert_matches!(result, FinishProposalStatus::Finished(_));
1931+
}
1932+
1933+
#[tokio::test]
1934+
async fn validation_only_decision_reached_skips_mempool_notification() {
1935+
let mut mock_deps = validation_only_mock_dependencies();
1936+
1937+
// The mempool_client on MockClients still exists but must not be called.
1938+
mock_deps.clients.mempool_client.checkpoint();
1939+
1940+
mock_deps.clients.l1_provider_client.expect_start_block().returning(|_, _| Ok(()));
1941+
mock_deps.clients.l1_provider_client.expect_commit_block().times(1).returning(|_, _, _| Ok(()));
1942+
mock_deps.storage_writer.expect_commit_proposal().returning(|_, _, _| Ok(()));
1943+
1944+
let mut block_builder_factory = MockBlockBuilderFactoryTrait::new();
1945+
mock_create_builder_for_validate_block(
1946+
&mut block_builder_factory,
1947+
Ok(BlockExecutionArtifacts::create_for_testing().await),
1948+
);
1949+
mock_deps.clients.block_builder_factory = block_builder_factory;
1950+
1951+
let mut batcher = create_batcher(mock_deps).await;
1952+
batcher.start_height(StartHeightInput { height: INITIAL_HEIGHT }).await.unwrap();
1953+
batcher.validate_block(validate_block_input(PROPOSAL_ID)).await.unwrap();
1954+
batcher.await_active_proposal(DUMMY_FINAL_N_EXECUTED_TXS).await.unwrap();
1955+
1956+
// decision_reached must succeed and not call mempool_client.commit_block.
1957+
batcher.decision_reached(DecisionReachedInput { proposal_id: PROPOSAL_ID }).await.unwrap();
1958+
}
1959+
1960+
#[tokio::test]
1961+
#[should_panic(expected = "validation_only=false but mempool_client is None")]
1962+
async fn validation_only_flag_false_with_no_mempool_panics() {
1963+
new_batcher_with_mempool_override(MockDependencies::default(), None).await;
1964+
}
1965+
1966+
#[tokio::test]
1967+
#[should_panic(expected = "validation_only=true but mempool_client is Some")]
1968+
async fn validation_only_flag_true_with_mempool_panics() {
1969+
let mempool: Option<SharedMempoolClient> = Some(Arc::new(MockMempoolClient::new()));
1970+
new_batcher_with_mempool_override(validation_only_mock_dependencies(), mempool).await;
1971+
}

crates/apollo_batcher_types/src/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub enum BatcherError {
4545
ProposalAlreadyFinished { proposal_id: ProposalId },
4646
#[error("Proposal failed.")]
4747
ProposalFailed,
48+
#[error("Proposing is not supported in validation-only mode.")]
49+
ProposingNotSupported,
4850
#[error("Proposal with ID {proposal_id} not found.")]
4951
ProposalNotFound { proposal_id: ProposalId },
5052
#[error(

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+
// TODO(asaf): Add a validation-only node to the integration test.
366367
validation_only: false,
367368
base_layer_config,
368369
batcher_config,

crates/apollo_node/src/components.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,15 @@ pub async fn create_node_components(
8686
let committer_client = clients
8787
.get_committer_shared_client()
8888
.expect("Committer client should be available");
89-
let mempool_client =
90-
clients.get_mempool_shared_client().expect("Mempool client should be available");
89+
let mempool_client = if config.validation_only {
90+
None
91+
} else {
92+
Some(
93+
clients
94+
.get_mempool_shared_client()
95+
.expect("Mempool client should be available in non-validation-only mode."),
96+
)
97+
};
9198
let l1_events_provider_client = clients
9299
.get_l1_events_provider_shared_client()
93100
.expect("L1 Provider client should be available");

0 commit comments

Comments
 (0)