Skip to content

Commit d8507cd

Browse files
apollo_gateway: test get complied class caching base case (#10204)
1 parent 311e22e commit d8507cd

File tree

3 files changed

+312
-39
lines changed

3 files changed

+312
-39
lines changed

crates/apollo_gateway/src/state_reader_test.rs

Lines changed: 280 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use blockifier::state::errors::StateError;
2020
use blockifier::state::state_api::{StateReader, StateResult};
2121
use blockifier::state::state_reader_and_contract_manager::StateReaderAndContractManager;
2222
use blockifier::test_utils::initial_test_state::state_reader_and_contract_manager_for_testing;
23-
use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass;
2423
use lazy_static::lazy_static;
2524
use mockall::predicate;
2625
use rstest::rstest;
@@ -34,7 +33,7 @@ use starknet_api::block::{
3433
GasPrices,
3534
NonzeroGasPrice,
3635
};
37-
use starknet_api::contract_class::{ContractClass, SierraVersion};
36+
use starknet_api::contract_class::ContractClass;
3837
use starknet_api::core::{ClassHash, SequencerContractAddress};
3938
use starknet_api::data_availability::L1DataAvailabilityMode;
4039
use starknet_api::state::SierraContractClass;
@@ -62,6 +61,178 @@ fn state_reader_and_contract_manager(
6261
)
6362
}
6463

64+
struct GetCompiledClassTestScenario {
65+
expectations: GetCompiledClassTestExpectation,
66+
67+
// Test result.
68+
expected_result: StateResult<RunnableCompiledClass>,
69+
}
70+
71+
struct GetCompiledClassTestExpectation {
72+
// Class manager client.
73+
get_executable_result: ClassManagerClientResult<Option<ExecutableClass>>,
74+
n_calls_to_get_executable: usize,
75+
get_sierra_result: ClassManagerClientResult<Option<SierraContractClass>>,
76+
n_calls_to_get_sierra: usize,
77+
78+
// State sync client.
79+
is_class_declared_at_result: Option<StateSyncClientResult<bool>>,
80+
is_cairo_1_class_declared_at_result: Option<StateSyncClientResult<bool>>,
81+
}
82+
83+
fn add_expectation_to_mock_state_sync_client_and_mock_class_manager_client(
84+
mock_class_manager_client: &mut MockClassManagerClient,
85+
mock_state_sync_client: &mut MockStateSyncClient,
86+
expectation: GetCompiledClassTestExpectation,
87+
) {
88+
add_expectation_to_mock_class_manager_client(
89+
mock_class_manager_client,
90+
expectation.get_executable_result,
91+
expectation.n_calls_to_get_executable,
92+
expectation.get_sierra_result,
93+
expectation.n_calls_to_get_sierra,
94+
);
95+
add_expectation_to_mock_state_sync_client(
96+
mock_state_sync_client,
97+
expectation.is_class_declared_at_result,
98+
expectation.is_cairo_1_class_declared_at_result,
99+
);
100+
}
101+
102+
fn add_expectation_to_mock_state_sync_client(
103+
mock_state_sync_client: &mut MockStateSyncClient,
104+
is_class_declared_at_result: Option<StateSyncClientResult<bool>>,
105+
is_cairo_1_class_declared_at_result: Option<StateSyncClientResult<bool>>,
106+
) {
107+
if let Some(is_class_declared_at_result) = is_class_declared_at_result {
108+
mock_state_sync_client
109+
.expect_is_class_declared_at()
110+
.times(1)
111+
.return_once(move |_, _| is_class_declared_at_result);
112+
}
113+
if let Some(is_cairo_1_class_declared_at_result) = is_cairo_1_class_declared_at_result {
114+
mock_state_sync_client
115+
.expect_is_cairo_1_class_declared_at()
116+
.times(1)
117+
.return_once(move |_, _| is_cairo_1_class_declared_at_result);
118+
}
119+
}
120+
121+
fn add_expectation_to_mock_class_manager_client(
122+
mock_class_manager_client: &mut MockClassManagerClient,
123+
get_executable_result: ClassManagerClientResult<Option<ExecutableClass>>,
124+
n_calls_to_get_executable: usize,
125+
get_sierra_result: ClassManagerClientResult<Option<SierraContractClass>>,
126+
n_calls_to_get_sierra: usize,
127+
) {
128+
mock_class_manager_client
129+
.expect_get_executable()
130+
.times(n_calls_to_get_executable)
131+
.return_once(move |_| get_executable_result);
132+
133+
mock_class_manager_client
134+
.expect_get_sierra()
135+
.times(n_calls_to_get_sierra)
136+
.return_once(move |_| get_sierra_result);
137+
}
138+
139+
const CACHED_EXPECTATION: GetCompiledClassTestExpectation = GetCompiledClassTestExpectation {
140+
get_executable_result: Ok(None), // Not called due to caching.
141+
n_calls_to_get_executable: 0,
142+
get_sierra_result: Ok(None), // Not called due to caching.
143+
n_calls_to_get_sierra: 0,
144+
is_class_declared_at_result: None, // Not called due to caching.
145+
is_cairo_1_class_declared_at_result: None,
146+
};
147+
148+
// Factory functions for different scenarios.
149+
fn cairo_1_declared_scenario() -> GetCompiledClassTestScenario {
150+
GetCompiledClassTestScenario {
151+
expectations: GetCompiledClassTestExpectation {
152+
get_executable_result: Ok(Some(DUMMY_CONTRACT_CLASS.clone())),
153+
n_calls_to_get_executable: 1,
154+
get_sierra_result: Ok(Some(SierraContractClass::default())),
155+
n_calls_to_get_sierra: 1,
156+
is_class_declared_at_result: Some(Ok(true)),
157+
is_cairo_1_class_declared_at_result: None,
158+
},
159+
expected_result: Ok(DUMMY_COMPILED_CLASS.clone()),
160+
}
161+
}
162+
163+
fn cairo_0_declared_scenario() -> GetCompiledClassTestScenario {
164+
GetCompiledClassTestScenario {
165+
expectations: GetCompiledClassTestExpectation {
166+
get_executable_result: Ok(Some(DUMMY_CONTRACT_CLASS_V0.clone())),
167+
n_calls_to_get_executable: 1,
168+
get_sierra_result: Ok(None), // Cairo 0 doesn't use Sierra.
169+
n_calls_to_get_sierra: 0,
170+
is_class_declared_at_result: Some(Ok(true)),
171+
is_cairo_1_class_declared_at_result: None,
172+
},
173+
expected_result: Ok(DUMMY_COMPILED_CLASS_V0.clone()),
174+
}
175+
}
176+
177+
fn not_declared_scenario() -> GetCompiledClassTestScenario {
178+
GetCompiledClassTestScenario {
179+
expectations: GetCompiledClassTestExpectation {
180+
get_executable_result: Ok(None), // Not called since not declared.
181+
n_calls_to_get_executable: 0,
182+
get_sierra_result: Ok(None), // Not called since not declared.
183+
n_calls_to_get_sierra: 0,
184+
is_class_declared_at_result: Some(Ok(false)),
185+
is_cairo_1_class_declared_at_result: None,
186+
},
187+
expected_result: Err(StateError::UndeclaredClassHash(*DUMMY_CLASS_HASH)),
188+
}
189+
}
190+
191+
fn cached_cairo_1_declared_scenario() -> GetCompiledClassTestScenario {
192+
GetCompiledClassTestScenario {
193+
expectations: GetCompiledClassTestExpectation {
194+
is_cairo_1_class_declared_at_result: Some(Ok(true)), // Verification call.
195+
..CACHED_EXPECTATION
196+
},
197+
expected_result: Ok(DUMMY_COMPILED_CLASS.clone()),
198+
}
199+
}
200+
201+
fn cached_cairo_0_declared_scenario() -> GetCompiledClassTestScenario {
202+
GetCompiledClassTestScenario {
203+
expectations: GetCompiledClassTestExpectation {
204+
is_cairo_1_class_declared_at_result: None, // Not called for Cairo 0.
205+
..CACHED_EXPECTATION
206+
},
207+
expected_result: Ok(DUMMY_COMPILED_CLASS_V0.clone()),
208+
}
209+
}
210+
211+
fn cached_but_verification_failed_after_reorg_scenario() -> GetCompiledClassTestScenario {
212+
GetCompiledClassTestScenario {
213+
expectations: GetCompiledClassTestExpectation {
214+
is_cairo_1_class_declared_at_result: Some(Ok(false)), // Verification fails.
215+
..CACHED_EXPECTATION
216+
},
217+
expected_result: Err(StateError::UndeclaredClassHash(*DUMMY_CLASS_HASH)),
218+
}
219+
}
220+
221+
fn not_declared_but_in_manager_scenario() -> GetCompiledClassTestScenario {
222+
GetCompiledClassTestScenario {
223+
expectations: GetCompiledClassTestExpectation {
224+
get_executable_result: Ok(Some(DUMMY_CONTRACT_CLASS.clone())), /* In manager but not
225+
* declared. */
226+
n_calls_to_get_executable: 0, // Not called since not declared.
227+
get_sierra_result: Ok(Some(SierraContractClass::default())),
228+
n_calls_to_get_sierra: 0, // Not called since not declared.
229+
is_class_declared_at_result: Some(Ok(false)),
230+
is_cairo_1_class_declared_at_result: None,
231+
},
232+
expected_result: Err(StateError::UndeclaredClassHash(*DUMMY_CLASS_HASH)),
233+
}
234+
}
235+
65236
#[tokio::test]
66237
async fn test_get_block_info() {
67238
let mut mock_state_sync_client = MockStateSyncClient::new();
@@ -234,20 +405,15 @@ async fn test_get_class_hash_at() {
234405
assert_eq!(result, expected_result);
235406
}
236407

237-
fn dummy_casm_contract_class() -> CasmContractClass {
238-
CasmContractClass {
239-
compiler_version: "0.0.0".to_string(),
240-
prime: Default::default(),
241-
bytecode: Default::default(),
242-
bytecode_segment_lengths: Default::default(),
243-
hints: Default::default(),
244-
pythonic_hints: Default::default(),
245-
entry_points_by_type: Default::default(),
246-
}
247-
}
248-
249408
lazy_static! {
250409
static ref DUMMY_CLASS_HASH: ClassHash = class_hash!("0x2");
410+
static ref DUMMY_CONTRACT_CLASS: ContractClass = ContractClass::test_casm_contract_class();
411+
static ref DUMMY_CONTRACT_CLASS_V0: ContractClass =
412+
ContractClass::test_deprecated_casm_contract_class();
413+
static ref DUMMY_COMPILED_CLASS: RunnableCompiledClass =
414+
DUMMY_CONTRACT_CLASS.clone().try_into().unwrap();
415+
static ref DUMMY_COMPILED_CLASS_V0: RunnableCompiledClass =
416+
DUMMY_CONTRACT_CLASS_V0.clone().try_into().unwrap();
251417
}
252418

253419
fn assert_eq_state_result(
@@ -263,24 +429,34 @@ fn assert_eq_state_result(
263429
}
264430
}
265431

266-
// TODO(Arni): add test for class is Cairo 0.
267432
#[rstest]
268433
#[case::class_declared(
269-
Ok(Some(ContractClass::V1((dummy_casm_contract_class(), SierraVersion::default())))),
434+
Ok(Some(DUMMY_CONTRACT_CLASS.clone())),
435+
1,
270436
Ok(Some(SierraContractClass::default())),
271437
1,
272438
Ok(true),
273-
Ok(RunnableCompiledClass::V1((dummy_casm_contract_class(), SierraVersion::default()).try_into().unwrap())),
439+
Ok(DUMMY_COMPILED_CLASS.clone()),
440+
)]
441+
#[case::cairo_0_class_declared(
442+
Ok(Some(DUMMY_CONTRACT_CLASS_V0.clone())),
443+
1,
444+
Ok(None),
445+
0,
446+
Ok(true),
447+
Ok(DUMMY_COMPILED_CLASS_V0.clone()),
274448
)]
275449
#[case::class_not_declared_but_in_class_manager(
276-
Ok(Some(ContractClass::V1((dummy_casm_contract_class(), SierraVersion::default())))),
450+
Ok(Some(DUMMY_CONTRACT_CLASS.clone())),
451+
0,
277452
Ok(Some(SierraContractClass::default())),
278453
0,
279454
Ok(false),
280455
Err(StateError::UndeclaredClassHash(*DUMMY_CLASS_HASH)),
281456
)]
282457
#[case::class_not_declared(
283458
Ok(None),
459+
0,
284460
Ok(None),
285461
0,
286462
Ok(false),
@@ -294,8 +470,9 @@ fn assert_eq_state_result(
294470
/// behavior.
295471
async fn test_get_compiled_class(
296472
#[case] get_executable_result: ClassManagerClientResult<Option<ExecutableClass>>,
473+
#[case] n_calls_to_get_executable: usize,
297474
#[case] get_sierra_result: ClassManagerClientResult<Option<SierraContractClass>>,
298-
#[case] n_calls_to_class_manager_client: usize,
475+
#[case] n_calls_to_get_sierra: usize,
299476
#[case] is_class_declared_at_result: StateSyncClientResult<bool>,
300477
#[case] expected_result: StateResult<RunnableCompiledClass>,
301478
) {
@@ -308,13 +485,13 @@ async fn test_get_compiled_class(
308485

309486
mock_class_manager_client
310487
.expect_get_executable()
311-
.times(n_calls_to_class_manager_client)
488+
.times(n_calls_to_get_executable)
312489
.with(predicate::eq(class_hash))
313490
.return_once(move |_| get_executable_result);
314491

315492
mock_class_manager_client
316493
.expect_get_sierra()
317-
.times(n_calls_to_class_manager_client)
494+
.times(n_calls_to_get_sierra)
318495
.with(predicate::eq(class_hash))
319496
.return_once(move |_| get_sierra_result);
320497

@@ -347,6 +524,7 @@ async fn test_get_compiled_class(
347524
async fn test_get_compiled_class_panics_when_class_exists_in_sync_but_not_in_class_manager() {
348525
test_get_compiled_class(
349526
Ok(None),
527+
1,
350528
Ok(None),
351529
1,
352530
Ok(true),
@@ -355,4 +533,84 @@ async fn test_get_compiled_class_panics_when_class_exists_in_sync_but_not_in_cla
355533
.await;
356534
}
357535

358-
// TODO(Arni): Add tests that check the caching logic.
536+
// TODO(Arni): Check if any test cases here should move to the tests of
537+
// `StateReaderAndContractManager`.
538+
#[rstest]
539+
#[case::cairo_0_declared_and_cached(
540+
cairo_0_declared_scenario(),
541+
cached_cairo_0_declared_scenario()
542+
)]
543+
#[case::cairo_1_declared_and_cached(
544+
cairo_1_declared_scenario(),
545+
cached_cairo_1_declared_scenario()
546+
)]
547+
#[case::cairo_1_declared_then_verification_failed_after_reorg(
548+
cairo_1_declared_scenario(),
549+
cached_but_verification_failed_after_reorg_scenario()
550+
)]
551+
#[case::not_declared_but_in_manager_then_declared(
552+
not_declared_but_in_manager_scenario(),
553+
cairo_1_declared_scenario()
554+
)]
555+
#[case::not_declared_then_declared(not_declared_scenario(), cairo_1_declared_scenario())]
556+
#[case::not_declared_both_rounds(not_declared_scenario(), not_declared_scenario())]
557+
#[tokio::test]
558+
async fn test_get_compiled_class_caching_scenarios(
559+
#[case] first_scenario: GetCompiledClassTestScenario,
560+
#[case] second_scenario: GetCompiledClassTestScenario,
561+
) {
562+
let block_number = BlockNumber(0); // Not used in the test.
563+
let class_hash = *DUMMY_CLASS_HASH;
564+
565+
let contract_class_manager = ContractClassManager::start(ContractClassManagerConfig::default());
566+
567+
// First execution.
568+
let mut mock_state_sync_client = MockStateSyncClient::new();
569+
let mut mock_class_manager_client = MockClassManagerClient::new();
570+
add_expectation_to_mock_state_sync_client_and_mock_class_manager_client(
571+
&mut mock_class_manager_client,
572+
&mut mock_state_sync_client,
573+
first_scenario.expectations,
574+
);
575+
576+
let first_state_reader_and_class_manager = state_reader_and_contract_manager(
577+
Arc::new(mock_state_sync_client),
578+
Arc::new(mock_class_manager_client),
579+
contract_class_manager.clone(),
580+
block_number,
581+
tokio::runtime::Handle::current(),
582+
);
583+
let first_result = tokio::task::spawn_blocking({
584+
let state_reader = first_state_reader_and_class_manager;
585+
move || state_reader.get_compiled_class(class_hash)
586+
})
587+
.await
588+
.unwrap();
589+
590+
// Second execution.
591+
let mut mock_state_sync_client = MockStateSyncClient::new();
592+
let mut mock_class_manager_client = MockClassManagerClient::new();
593+
add_expectation_to_mock_state_sync_client_and_mock_class_manager_client(
594+
&mut mock_class_manager_client,
595+
&mut mock_state_sync_client,
596+
second_scenario.expectations,
597+
);
598+
599+
let second_state_reader_and_class_manager = state_reader_and_contract_manager(
600+
Arc::new(mock_state_sync_client),
601+
Arc::new(mock_class_manager_client),
602+
contract_class_manager,
603+
block_number,
604+
tokio::runtime::Handle::current(),
605+
);
606+
let second_result = tokio::task::spawn_blocking({
607+
let state_reader = second_state_reader_and_class_manager;
608+
move || state_reader.get_compiled_class(class_hash)
609+
})
610+
.await
611+
.unwrap();
612+
613+
// Verify results
614+
assert_eq_state_result(&first_result, &first_scenario.expected_result);
615+
assert_eq_state_result(&second_result, &second_scenario.expected_result);
616+
}

0 commit comments

Comments
 (0)