@@ -33,7 +33,11 @@ use apollo_infra::component_client::ClientError;
3333use apollo_infra:: component_definitions:: ComponentStarter ;
3434use apollo_l1_events_types:: errors:: { L1EventsProviderClientError , L1EventsProviderError } ;
3535use 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+ } ;
3741use apollo_mempool_types:: mempool_types:: CommitBlockArgs ;
3842use apollo_state_sync_types:: state_sync_types:: SyncBlock ;
3943use 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+
294336async 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+ }
0 commit comments