diff --git a/crates/starknet_committer/src/block_committer.rs b/crates/starknet_committer/src/block_committer.rs index 9194df2b884..b05ff953c5f 100644 --- a/crates/starknet_committer/src/block_committer.rs +++ b/crates/starknet_committer/src/block_committer.rs @@ -1,4 +1,6 @@ pub mod commit; +#[cfg(test)] +pub mod commit_test; pub mod errors; pub mod input; #[cfg(any(feature = "testing", test))] diff --git a/crates/starknet_committer/src/block_committer/commit_test.rs b/crates/starknet_committer/src/block_committer/commit_test.rs new file mode 100644 index 00000000000..96b0a1b55ca --- /dev/null +++ b/crates/starknet_committer/src/block_committer/commit_test.rs @@ -0,0 +1,180 @@ +use std::collections::HashMap; +use std::sync::LazyLock; + +use rand::rngs::SmallRng; +use rand::SeedableRng; +use rstest::rstest; +use rstest_reuse::{apply, template}; +use starknet_api::core::{ClassHash, ContractAddress}; +use starknet_api::hash::{HashOutput, StateRoots}; +use starknet_patricia_storage::map_storage::MapStorage; +use starknet_types_core::felt::Felt; + +use crate::block_committer::commit::{CommitBlockImpl, CommitBlockTrait}; +use crate::block_committer::input::{ + Input, + InputContext, + ReaderConfig, + StarknetStorageKey, + StarknetStorageValue, + StateDiff, +}; +use crate::block_committer::state_diff_generator::generate_random_state_diff; +use crate::db::facts_db::db::FactsDb; +use crate::db::facts_db::types::FactsDbInitialRead; +use crate::db::forest_trait::{ForestReader, ForestWriter}; +use crate::db::index_db::db::IndexDb; +use crate::patricia_merkle_tree::types::CompiledClassHash; + +static FIRST_CONTRACT_ADDRESS: LazyLock = + LazyLock::new(|| ContractAddress::from(1_u128)); +static SECOND_CONTRACT_ADDRESS: LazyLock = + LazyLock::new(|| ContractAddress::from(1_u128 << 100)); + +const EXPECTED_ROOTS_SIMPLE_CASE: StateRoots = StateRoots { + contracts_trie_root_hash: HashOutput(Felt::from_hex_unchecked( + "0x1dfab71737a9b4528835c11c3cef25d2587ef8afe5098d3b21f2ea77e944500", + )), + classes_trie_root_hash: HashOutput(Felt::from_hex_unchecked( + "0x7ae8007b033a1c032c6a791331f56a652833b0b25633587318276f4462c8c1c", + )), +}; + +const EXPECTED_ROOTS_RANDOM_CASE: StateRoots = StateRoots { + contracts_trie_root_hash: HashOutput(Felt::from_hex_unchecked( + "0x2877d3a2370c501255c8451f615f4f2aa900f6acf2f0cd36b735b1108a4a0e4", + )), + classes_trie_root_hash: HashOutput(Felt::from_hex_unchecked("0x0")), +}; + +const N_RANDOM_STATE_UPDATES: usize = 200; + +#[template] +#[rstest] +#[case([get_first_state_diff(), get_second_state_diff()], EXPECTED_ROOTS_SIMPLE_CASE)] +#[case(get_random_state_diffs(), EXPECTED_ROOTS_RANDOM_CASE)] +fn state_diff_cases(#[case] state_diffs: [StateDiff; 2], #[case] expected_roots: StateRoots) {} + +#[apply(state_diff_cases)] +#[tokio::test] +#[rstest] +async fn test_commit_two_consecutive_blocks_facts_layout( + #[case] state_diffs: [StateDiff; 2], + #[case] expected_roots: StateRoots, +) { + test_commit_two_consecutive_blocks::>( + |storage| FactsDb::new(storage), + FactsDbInitialRead::default(), + state_diffs, + expected_roots, + ) + .await; +} + +#[apply(state_diff_cases)] +#[tokio::test] +#[rstest] +async fn test_commit_two_consecutive_blocks_index_layout( + #[case] state_diffs: [StateDiff; 2], + #[case] expected_roots: StateRoots, +) { + test_commit_two_consecutive_blocks::>( + |storage| IndexDb::new(storage), + FactsDbInitialRead::default(), + state_diffs, + expected_roots, + ) + .await; +} + +async fn test_commit_two_consecutive_blocks< + ReaderInputContext: InputContext + Clone + Send, + Db: ForestReader + ForestWriter, +>( + db_generator: impl FnOnce(MapStorage) -> Db, + initial_read_context: ReaderInputContext, + state_diffs: [StateDiff; 2], + expected_roots: StateRoots, +) { + let [first_state_diff, second_state_diff] = state_diffs; + + let storage = MapStorage::default(); + let mut db = db_generator(storage); + + let mut input = Input { + state_diff: first_state_diff, + initial_read_context: initial_read_context.clone(), + config: ReaderConfig::default(), + }; + let filled_forest = CommitBlockImpl::commit_block(input, &mut db, None).await.unwrap(); + db.write(&filled_forest).await.unwrap(); + + input = Input { + state_diff: second_state_diff, + initial_read_context, + config: ReaderConfig::default(), + }; + + let filled_forest = CommitBlockImpl::commit_block(input, &mut db, None).await.unwrap(); + db.write(&filled_forest).await.unwrap(); + + assert_eq!(filled_forest.state_roots(), expected_roots); +} + +fn get_first_state_diff() -> StateDiff { + let mut contract_class_changes = HashMap::new(); + contract_class_changes.insert(*FIRST_CONTRACT_ADDRESS, ClassHash(Felt::ONE)); + contract_class_changes.insert(*SECOND_CONTRACT_ADDRESS, ClassHash(Felt::TWO)); + + let mut individual_storage_changes = HashMap::new(); + individual_storage_changes + .insert(StarknetStorageKey::from(1_u128), StarknetStorageValue(Felt::from(1_u128))); + individual_storage_changes + .insert(StarknetStorageKey::from(2_u128), StarknetStorageValue(Felt::from(2_u128))); + + let mut storage_updates = HashMap::new(); + storage_updates.insert(*FIRST_CONTRACT_ADDRESS, individual_storage_changes.clone()); + storage_updates.insert(*SECOND_CONTRACT_ADDRESS, individual_storage_changes); + + StateDiff { + address_to_class_hash: contract_class_changes, + address_to_nonce: HashMap::new(), + class_hash_to_compiled_class_hash: HashMap::new(), + storage_updates, + } +} + +fn get_second_state_diff() -> StateDiff { + let mut contract_class_changes = HashMap::new(); + contract_class_changes.insert(*FIRST_CONTRACT_ADDRESS, ClassHash(Felt::TWO)); + + let mut individual_storage_changes = HashMap::new(); + individual_storage_changes + .insert(StarknetStorageKey::from(1_u128), StarknetStorageValue(Felt::from(2_u128))); + individual_storage_changes + .insert(StarknetStorageKey::from(2_u128), StarknetStorageValue(Felt::from(4_u128))); + + let mut storage_updates = HashMap::new(); + storage_updates.insert(*SECOND_CONTRACT_ADDRESS, individual_storage_changes); + + let mut declarations = HashMap::new(); + declarations.insert(ClassHash(Felt::THREE), CompiledClassHash(Felt::THREE)); + + StateDiff { + address_to_class_hash: contract_class_changes, + address_to_nonce: HashMap::new(), + class_hash_to_compiled_class_hash: declarations, + storage_updates, + } +} + +fn get_random_state_diffs() -> [StateDiff; 2] { + // Use a constant seed for reproducibility. + let mut seed = 42_u64; + let mut rng = SmallRng::seed_from_u64(seed); + let first_state_diff = generate_random_state_diff(&mut rng, N_RANDOM_STATE_UPDATES, None); + seed += 1; + rng = SmallRng::seed_from_u64(seed); + let second_state_diff = generate_random_state_diff(&mut rng, N_RANDOM_STATE_UPDATES, None); + [first_state_diff, second_state_diff] +} diff --git a/crates/starknet_committer/src/db/facts_db/types.rs b/crates/starknet_committer/src/db/facts_db/types.rs index 88bf767fcf0..13a6e7486a9 100644 --- a/crates/starknet_committer/src/db/facts_db/types.rs +++ b/crates/starknet_committer/src/db/facts_db/types.rs @@ -58,7 +58,16 @@ impl<'a> SubTreeTrait<'a> for FactsSubTree<'a> { } } /// Used for reading the roots in facts layout case. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct FactsDbInitialRead(pub StateRoots); +impl Default for FactsDbInitialRead { + fn default() -> Self { + Self(StateRoots { + contracts_trie_root_hash: HashOutput::ROOT_OF_EMPTY_TREE, + classes_trie_root_hash: HashOutput::ROOT_OF_EMPTY_TREE, + }) + } +} + impl InputContext for FactsDbInitialRead {}