Skip to content

Commit d4eb41b

Browse files
apollo_batcher: test commitment manager (#11367)
1 parent 0a365a8 commit d4eb41b

File tree

4 files changed

+344
-3
lines changed

4 files changed

+344
-3
lines changed
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
use std::panic;
2+
use std::sync::Arc;
3+
use std::time::Duration;
4+
5+
use apollo_batcher_config::config::BatcherConfig;
6+
use apollo_committer_types::communication::MockCommitterClient;
7+
use apollo_storage::StorageResult;
8+
use assert_matches::assert_matches;
9+
use mockall::predicate::eq;
10+
use rstest::{fixture, rstest};
11+
use starknet_api::block::{BlockHash, BlockNumber};
12+
use starknet_api::block_hash::block_hash_calculator::PartialBlockHashComponents;
13+
use starknet_api::core::StateDiffCommitment;
14+
use tokio::sync::mpsc::{Receiver, Sender};
15+
use tokio::time::{sleep, timeout};
16+
17+
use crate::batcher::MockBatcherStorageReader;
18+
use crate::commitment_manager::errors::CommitmentManagerError;
19+
use crate::commitment_manager::{CommitmentManager, CommitmentManagerConfig};
20+
use crate::test_utils::{test_state_diff, MockStateCommitter, INITIAL_HEIGHT};
21+
22+
type MockCommitmentManager = CommitmentManager<MockStateCommitter>;
23+
24+
struct MockDependencies {
25+
pub(crate) storage_reader: MockBatcherStorageReader,
26+
pub(crate) batcher_config: BatcherConfig,
27+
pub(crate) committer_client: MockCommitterClient,
28+
}
29+
30+
#[fixture]
31+
fn mock_dependencies() -> MockDependencies {
32+
MockDependencies {
33+
storage_reader: MockBatcherStorageReader::new(),
34+
batcher_config: BatcherConfig::default(),
35+
committer_client: MockCommitterClient::new(),
36+
}
37+
}
38+
39+
fn add_initial_heights(mock_dependencies: &mut MockDependencies) {
40+
mock_dependencies.storage_reader.expect_height().returning(|| Ok(INITIAL_HEIGHT));
41+
mock_dependencies.storage_reader.expect_global_root_height().returning(|| Ok(INITIAL_HEIGHT));
42+
}
43+
44+
fn get_dummy_parent_hash_and_partial_block_hash_components(
45+
height: &BlockNumber,
46+
) -> StorageResult<(Option<BlockHash>, Option<PartialBlockHashComponents>)> {
47+
let partial_block_hash_components =
48+
PartialBlockHashComponents { block_number: *height, ..Default::default() };
49+
Ok((Some(BlockHash::default()), Some(partial_block_hash_components)))
50+
}
51+
52+
fn get_number_of_tasks_in_sender<T>(sender: &Sender<T>) -> usize {
53+
sender.max_capacity() - sender.capacity()
54+
}
55+
56+
fn get_number_of_tasks_in_receiver<T>(receiver: &Receiver<T>) -> usize {
57+
receiver.max_capacity() - receiver.capacity()
58+
}
59+
60+
async fn create_mock_commitment_manager(
61+
mock_dependencies: MockDependencies,
62+
) -> MockCommitmentManager {
63+
let commitment_manager_config = CommitmentManagerConfig {
64+
tasks_channel_size: 1,
65+
results_channel_size: 1,
66+
wait_for_tasks_channel: false,
67+
};
68+
CommitmentManager::create_commitment_manager(
69+
&mock_dependencies.batcher_config,
70+
// TODO(Amos): Use commitment manager config in batcher config, once it's added.
71+
&commitment_manager_config,
72+
&mock_dependencies.storage_reader,
73+
Arc::new(mock_dependencies.committer_client),
74+
)
75+
.await
76+
}
77+
78+
async fn await_results<T>(receiver: &mut Receiver<T>, expected_n_results: usize) -> Vec<T> {
79+
let max_n_retries = 3;
80+
let mut n_retries = 0;
81+
while get_number_of_tasks_in_receiver(receiver) < expected_n_results {
82+
sleep(Duration::from_millis(500)).await;
83+
n_retries += 1;
84+
if n_retries >= max_n_retries {
85+
panic!(
86+
"Timed out waiting for {} results after {} retries.",
87+
expected_n_results, max_n_retries
88+
);
89+
}
90+
}
91+
let mut results = Vec::new();
92+
while let Ok(result) = receiver.try_recv() {
93+
results.push(result);
94+
}
95+
assert_eq!(
96+
results.len(),
97+
expected_n_results,
98+
"Number of received results should be equal to expected number of results."
99+
);
100+
results
101+
}
102+
103+
#[rstest]
104+
#[tokio::test]
105+
async fn test_create_commitment_manager(mut mock_dependencies: MockDependencies) {
106+
add_initial_heights(&mut mock_dependencies);
107+
let commitment_manager = create_mock_commitment_manager(mock_dependencies).await;
108+
109+
assert_eq!(
110+
commitment_manager.get_commitment_task_offset(),
111+
INITIAL_HEIGHT,
112+
"Commitment task offset should be equal to initial height."
113+
);
114+
assert_eq!(
115+
get_number_of_tasks_in_sender(&commitment_manager.tasks_sender),
116+
0,
117+
"There should be no tasks in the channel."
118+
);
119+
}
120+
121+
#[rstest]
122+
#[tokio::test]
123+
async fn test_create_commitment_manager_with_missing_tasks(
124+
mut mock_dependencies: MockDependencies,
125+
) {
126+
let global_root_height = INITIAL_HEIGHT.prev().unwrap();
127+
mock_dependencies.storage_reader.expect_height().returning(|| Ok(INITIAL_HEIGHT));
128+
mock_dependencies
129+
.storage_reader
130+
.expect_global_root_height()
131+
.returning(move || Ok(global_root_height));
132+
mock_dependencies
133+
.storage_reader
134+
.expect_get_parent_hash_and_partial_block_hash_components()
135+
.with(eq(global_root_height))
136+
.returning(|height| get_dummy_parent_hash_and_partial_block_hash_components(&height));
137+
mock_dependencies
138+
.storage_reader
139+
.expect_get_state_diff()
140+
.with(eq(global_root_height))
141+
.returning(|_| Ok(Some(test_state_diff())));
142+
143+
let mut commitment_manager = create_mock_commitment_manager(mock_dependencies).await;
144+
145+
assert_eq!(commitment_manager.get_commitment_task_offset(), INITIAL_HEIGHT,);
146+
assert_eq!(get_number_of_tasks_in_sender(&commitment_manager.tasks_sender), 1,);
147+
commitment_manager.state_committer.pop_task_and_insert_result().await;
148+
let results = await_results(&mut commitment_manager.results_receiver, 1).await;
149+
let result = results.first().unwrap();
150+
assert_eq!(result.height, global_root_height);
151+
}
152+
153+
#[rstest]
154+
#[tokio::test]
155+
async fn test_add_commitment_task(mut mock_dependencies: MockDependencies) {
156+
add_initial_heights(&mut mock_dependencies);
157+
let state_diff = test_state_diff();
158+
let state_diff_commitment = Some(StateDiffCommitment::default());
159+
160+
let mut commitment_manager = create_mock_commitment_manager(mock_dependencies).await;
161+
162+
// Verify incorrect height results in error.
163+
let incorrect_height = INITIAL_HEIGHT.next().unwrap();
164+
let result = commitment_manager
165+
.add_commitment_task(incorrect_height, state_diff.clone(), state_diff_commitment)
166+
.await;
167+
assert_matches!(
168+
result,
169+
Err(CommitmentManagerError::WrongTaskHeight { expected, actual, .. })
170+
if expected == INITIAL_HEIGHT && actual == incorrect_height
171+
);
172+
173+
assert_eq!(
174+
commitment_manager.config.tasks_channel_size, 1,
175+
"Tasks channel size should be 1 for this test."
176+
);
177+
178+
// Verify correct height adds the task successfully.
179+
commitment_manager
180+
.add_commitment_task(INITIAL_HEIGHT, state_diff.clone(), state_diff_commitment)
181+
.await
182+
.unwrap_or_else(|_| panic!("Failed to add commitment task with correct height."));
183+
assert_eq!(
184+
get_number_of_tasks_in_sender(&commitment_manager.tasks_sender),
185+
1,
186+
"There should be one task in the channel."
187+
);
188+
189+
// Verify adding task when channel is full results in waiting, when config is set.
190+
commitment_manager.config.wait_for_tasks_channel = true;
191+
let add_task_future = commitment_manager.add_commitment_task(
192+
INITIAL_HEIGHT.next().unwrap(),
193+
state_diff.clone(),
194+
state_diff_commitment,
195+
);
196+
let add_task_result = timeout(Duration::from_millis(500), add_task_future).await;
197+
assert!(
198+
add_task_result.is_err(),
199+
"Commitment manager should wait when adding task to full channel, when configured to do \
200+
so."
201+
);
202+
203+
// Verify that after popping a task, adding the task succeeds.
204+
commitment_manager.state_committer.pop_task_and_insert_result().await;
205+
commitment_manager
206+
.add_commitment_task(INITIAL_HEIGHT.next().unwrap(), state_diff, state_diff_commitment)
207+
.await
208+
.expect("Failed to add commitment task after freeing up space.");
209+
assert_eq!(get_number_of_tasks_in_sender(&commitment_manager.tasks_sender), 1,);
210+
}
211+
212+
#[rstest]
213+
#[tokio::test]
214+
#[should_panic(expected = "Failed to send commitment task to state committer because the channel \
215+
is full. Block: 4")]
216+
async fn test_add_commitment_task_full(mut mock_dependencies: MockDependencies) {
217+
add_initial_heights(&mut mock_dependencies);
218+
let state_diff = test_state_diff();
219+
let state_diff_commitment = Some(StateDiffCommitment::default());
220+
221+
let mut commitment_manager = create_mock_commitment_manager(mock_dependencies).await;
222+
223+
assert_eq!(
224+
commitment_manager.config.tasks_channel_size, 1,
225+
"Tasks channel size should be 1 for this test."
226+
);
227+
228+
commitment_manager
229+
.add_commitment_task(INITIAL_HEIGHT, state_diff.clone(), state_diff_commitment)
230+
.await
231+
.unwrap_or_else(|_| panic!("Failed to add commitment task with correct height."));
232+
233+
commitment_manager
234+
.add_commitment_task(
235+
INITIAL_HEIGHT.next().unwrap(),
236+
state_diff.clone(),
237+
state_diff_commitment,
238+
)
239+
.await
240+
.expect("This call should panic.")
241+
}
242+
243+
#[rstest]
244+
#[tokio::test]
245+
async fn test_get_commitment_results(mut mock_dependencies: MockDependencies) {
246+
add_initial_heights(&mut mock_dependencies);
247+
let state_diff = test_state_diff();
248+
let state_diff_commitment = Some(StateDiffCommitment::default());
249+
250+
let commitment_manager_config = CommitmentManagerConfig {
251+
tasks_channel_size: 2,
252+
results_channel_size: 2,
253+
wait_for_tasks_channel: false,
254+
};
255+
let mut commitment_manager = MockCommitmentManager::create_commitment_manager(
256+
&mock_dependencies.batcher_config,
257+
// TODO(Amos): Use commitment manager config in batcher config, once it's added.
258+
&commitment_manager_config,
259+
&mock_dependencies.storage_reader,
260+
Arc::new(mock_dependencies.committer_client),
261+
)
262+
.await;
263+
264+
// Verify the commitment manager doesn't wait if there are no results.
265+
let results = commitment_manager.get_commitment_results().await;
266+
assert!(results.is_empty(), "There should be no commitment results initially.");
267+
268+
// Add two tasks and simulate their completion by the mock state committer.
269+
commitment_manager
270+
.add_commitment_task(INITIAL_HEIGHT, state_diff.clone(), state_diff_commitment)
271+
.await
272+
.unwrap();
273+
commitment_manager
274+
.add_commitment_task(
275+
INITIAL_HEIGHT.next().unwrap(),
276+
state_diff.clone(),
277+
state_diff_commitment,
278+
)
279+
.await
280+
.unwrap();
281+
commitment_manager.state_committer.pop_task_and_insert_result().await;
282+
commitment_manager.state_committer.pop_task_and_insert_result().await;
283+
284+
let results = await_results(&mut commitment_manager.results_receiver, 2).await;
285+
let first_result = results.first().unwrap();
286+
let second_result = results.get(1).unwrap();
287+
assert_eq!(first_result.height, INITIAL_HEIGHT,);
288+
assert_eq!(second_result.height, INITIAL_HEIGHT.next().unwrap(),);
289+
}

crates/apollo_batcher/src/commitment_manager/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ pub(crate) mod errors;
2828
pub(crate) mod state_committer;
2929
pub(crate) mod types;
3030

31+
#[cfg(test)]
32+
pub(crate) mod commitment_manager_test;
33+
3134
pub(crate) const DEFAULT_TASKS_CHANNEL_SIZE: usize = 1000;
3235
pub(crate) const DEFAULT_RESULTS_CHANNEL_SIZE: usize = 1000;
3336

@@ -263,6 +266,7 @@ impl<S: StateCommitterTrait> CommitmentManager<S> {
263266
/// If `should_finalize_block_hash` is true, finalizes the commitment by calculating the block
264267
/// hash using the global root, the parent block hash and the partial block hash components.
265268
/// Otherwise, returns the final commitment with no block hash.
269+
// TODO(Rotem): Test this function.
266270
pub(crate) fn final_commitment_output<R: BatcherStorageReader + ?Sized>(
267271
storage_reader: Arc<R>,
268272
CommitmentTaskOutput { height, global_root }: CommitmentTaskOutput,

crates/apollo_batcher/src/commitment_manager/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub(crate) struct CommitmentTaskInput {
1212
}
1313

1414
/// Output of commitment tasks.
15+
#[cfg_attr(test, derive(Default))]
1516
pub(crate) struct CommitmentTaskOutput {
1617
pub(crate) global_root: GlobalRoot,
1718
pub(crate) height: BlockNumber,

0 commit comments

Comments
 (0)