diff --git a/crates/papyrus_p2p_sync/src/client/class_test.rs b/crates/papyrus_p2p_sync/src/client/class_test.rs new file mode 100644 index 0000000000..403e71b772 --- /dev/null +++ b/crates/papyrus_p2p_sync/src/client/class_test.rs @@ -0,0 +1,187 @@ +use std::cmp::min; + +use futures::{FutureExt, StreamExt}; +use papyrus_common::pending_classes::ApiContractClass; +use papyrus_protobuf::sync::{ + BlockHashOrNumber, + ClassQuery, + DataOrFin, + DeclaredClass, + DeprecatedDeclaredClass, + Direction, + Query, + StateDiffChunk, +}; +use papyrus_storage::class::ClassStorageReader; +use papyrus_test_utils::{get_rng, GetTestInstance}; +use rand::{Rng, RngCore}; +use rand_chacha::ChaCha8Rng; +use starknet_api::block::BlockNumber; +use starknet_api::core::{ClassHash, CompiledClassHash}; +use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; +use starknet_api::state::ContractClass; + +use super::test_utils::{ + setup, + wait_for_marker, + MarkerKind, + TestArgs, + CLASS_DIFF_QUERY_LENGTH, + HEADER_QUERY_LENGTH, + SLEEP_DURATION_TO_LET_SYNC_ADVANCE, + TIMEOUT_FOR_TEST, +}; +use crate::client::state_diff_test::run_state_diff_sync; + +#[tokio::test] +async fn class_basic_flow() { + let TestArgs { + p2p_sync, + storage_reader, + mut mock_state_diff_response_manager, + mut mock_header_response_manager, + mut mock_class_response_manager, + // The test will fail if we drop this + mock_transaction_response_manager: _mock_transaction_responses_manager, + .. + } = setup(); + + let mut rng = get_rng(); + let (class_state_diffs, api_contract_classes): (Vec<_>, Vec<_>) = (0..HEADER_QUERY_LENGTH) + .map(|_| create_random_state_diff_chunk_with_class(&mut rng)) + .unzip(); + let header_state_diff_lengths = + class_state_diffs.iter().map(|class_state_diff| class_state_diff.len()).collect::>(); + + // Create a future that will receive queries, send responses and validate the results + let parse_queries_future = async move { + // Check that before we send state diffs there is no class query. + assert!(mock_class_response_manager.next().now_or_never().is_none()); + + run_state_diff_sync( + p2p_sync.config, + &mut mock_header_response_manager, + &mut mock_state_diff_response_manager, + header_state_diff_lengths.clone(), + class_state_diffs.clone().into_iter().map(Some).collect(), + ) + .await; + + let num_declare_class_state_diff_headers = + u64::try_from(header_state_diff_lengths.len()).unwrap(); + let num_class_queries = + num_declare_class_state_diff_headers.div_ceil(CLASS_DIFF_QUERY_LENGTH); + for i in 0..num_class_queries { + let start_block_number = i * CLASS_DIFF_QUERY_LENGTH; + let limit = min( + num_declare_class_state_diff_headers - start_block_number, + CLASS_DIFF_QUERY_LENGTH, + ); + + // Get a class query and validate it + let mut mock_class_responses_manager = + mock_class_response_manager.next().await.unwrap(); + assert_eq!( + *mock_class_responses_manager.query(), + Ok(ClassQuery(Query { + start_block: BlockHashOrNumber::Number(BlockNumber(start_block_number)), + direction: Direction::Forward, + limit, + step: 1, + })), + "If the limit of the query is too low, try to increase \ + SLEEP_DURATION_TO_LET_SYNC_ADVANCE", + ); + + for block_number in start_block_number..(start_block_number + limit) { + let class_hash = + class_state_diffs[usize::try_from(block_number).unwrap()].get_class_hash(); + let api_contract_class = + api_contract_classes[usize::try_from(block_number).unwrap()].clone(); + + let block_number = BlockNumber(block_number); + + // Check that before we've sent all parts the state diff wasn't written yet + let txn = storage_reader.begin_ro_txn().unwrap(); + assert_eq!(block_number, txn.get_class_marker().unwrap()); + + mock_class_responses_manager + .send_response(DataOrFin(Some((api_contract_class.clone(), class_hash)))) + .await + .unwrap(); + + wait_for_marker( + MarkerKind::Class, + &storage_reader, + block_number.unchecked_next(), + SLEEP_DURATION_TO_LET_SYNC_ADVANCE, + TIMEOUT_FOR_TEST, + ) + .await; + + let txn = storage_reader.begin_ro_txn().unwrap(); + let expected_class = match api_contract_class { + ApiContractClass::ContractClass(_) => ApiContractClass::ContractClass( + txn.get_class(&class_hash).unwrap().unwrap(), + ), + ApiContractClass::DeprecatedContractClass(_) => { + ApiContractClass::DeprecatedContractClass( + txn.get_deprecated_class(&class_hash).unwrap().unwrap(), + ) + } + }; + assert_eq!(api_contract_class, expected_class); + } + + mock_class_responses_manager.send_response(DataOrFin(None)).await.unwrap(); + } + }; + + tokio::select! { + sync_result = p2p_sync.run() => { + sync_result.unwrap(); + panic!("P2P sync aborted with no failure."); + } + _ = parse_queries_future => {} + } +} + +trait GetClassHash { + fn get_class_hash(&self) -> ClassHash; +} + +impl GetClassHash for StateDiffChunk { + fn get_class_hash(&self) -> ClassHash { + match self { + StateDiffChunk::DeclaredClass(declared_class) => declared_class.class_hash, + StateDiffChunk::DeprecatedDeclaredClass(deprecated_declared_class) => { + deprecated_declared_class.class_hash + } + _ => unreachable!(), + } + } +} + +fn create_random_state_diff_chunk_with_class( + rng: &mut ChaCha8Rng, +) -> (StateDiffChunk, ApiContractClass) { + let class_hash = ClassHash(rng.next_u64().into()); + if rng.gen_bool(0.5) { + let declared_class = DeclaredClass { + class_hash, + compiled_class_hash: CompiledClassHash(rng.next_u64().into()), + }; + ( + StateDiffChunk::DeclaredClass(declared_class), + ApiContractClass::ContractClass(ContractClass::get_test_instance(rng)), + ) + } else { + let deprecated_declared_class = DeprecatedDeclaredClass { class_hash }; + ( + StateDiffChunk::DeprecatedDeclaredClass(deprecated_declared_class), + ApiContractClass::DeprecatedContractClass(DeprecatedContractClass::get_test_instance( + rng, + )), + ) + } +} diff --git a/crates/papyrus_p2p_sync/src/client/mod.rs b/crates/papyrus_p2p_sync/src/client/mod.rs index ada0d1cf47..cefbb2f0e0 100644 --- a/crates/papyrus_p2p_sync/src/client/mod.rs +++ b/crates/papyrus_p2p_sync/src/client/mod.rs @@ -1,4 +1,6 @@ mod class; +#[cfg(test)] +mod class_test; mod header; #[cfg(test)] mod header_test; diff --git a/crates/papyrus_p2p_sync/src/client/state_diff_test.rs b/crates/papyrus_p2p_sync/src/client/state_diff_test.rs index 422e03a8b1..87e5a8fc6b 100644 --- a/crates/papyrus_p2p_sync/src/client/state_diff_test.rs +++ b/crates/papyrus_p2p_sync/src/client/state_diff_test.rs @@ -1,5 +1,9 @@ +use std::cmp::min; + +use futures::future::join; use futures::{FutureExt, StreamExt}; use indexmap::indexmap; +use papyrus_network::network_manager::GenericReceiver; use papyrus_protobuf::sync::{ BlockHashOrNumber, ContractDiff, @@ -20,12 +24,15 @@ use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::state::{StorageKey, ThinStateDiff}; use starknet_types_core::felt::Felt; use static_assertions::const_assert; +use tokio::sync::mpsc::{channel, Receiver}; use super::test_utils::{ create_block_hashes_and_signatures, setup, wait_for_marker, + HeaderTestPayload, MarkerKind, + StateDiffTestPayload, TestArgs, HEADER_QUERY_LENGTH, SLEEP_DURATION_TO_LET_SYNC_ADVANCE, @@ -33,7 +40,7 @@ use super::test_utils::{ TIMEOUT_FOR_TEST, WAIT_PERIOD_FOR_NEW_DATA, }; -use super::StateDiffQuery; +use super::{P2PSyncClientConfig, StateDiffQuery}; #[tokio::test] async fn state_diff_basic_flow() { @@ -53,74 +60,25 @@ async fn state_diff_basic_flow() { .. } = setup(); - let block_hashes_and_signatures = - create_block_hashes_and_signatures(HEADER_QUERY_LENGTH.try_into().unwrap()); let mut rng = get_rng(); // TODO(eitan): Add a 3rd constant for NUM_CHUNKS_PER_BLOCK so that ThinStateDiff is made from // multiple StateDiffChunks - let state_diffs = (0..HEADER_QUERY_LENGTH) - .map(|_| create_random_state_diff_chunk(&mut rng)) - .collect::>(); - - // Create a future that will receive queries, send responses and validate the results. - let parse_queries_future = async move { - // We wait for the state diff sync to see that there are no headers and start sleeping - tokio::time::sleep(SLEEP_DURATION_TO_LET_SYNC_ADVANCE).await; - - // Check that before we send headers there is no state diff query. - assert!(mock_state_diff_response_manager.next().now_or_never().is_none()); - let mut mock_header_responses_manager = mock_header_response_manager.next().await.unwrap(); - - // Send headers for entire query. - for (i, ((block_hash, block_signature), state_diff)) in - block_hashes_and_signatures.iter().zip(state_diffs.iter()).enumerate() - { - // Send responses - mock_header_responses_manager - .send_response(DataOrFin(Some(SignedBlockHeader { - block_header: BlockHeader { - block_hash: *block_hash, - block_header_without_hash: BlockHeaderWithoutHash { - block_number: BlockNumber(i.try_into().unwrap()), - ..Default::default() - }, - state_diff_length: Some(state_diff.len()), - ..Default::default() - }, - signatures: vec![*block_signature], - }))) - .await - .unwrap(); - } - - // We wait for the header sync to write the new headers. - tokio::time::sleep(SLEEP_DURATION_TO_LET_SYNC_ADVANCE).await; - - // Simulate time has passed so that state diff sync will resend query after it waited for - // new header - tokio::time::pause(); - tokio::time::advance(WAIT_PERIOD_FOR_NEW_DATA).await; - tokio::time::resume(); - + let (state_diffs, header_state_diff_lengths): (Vec<_>, Vec<_>) = (0..HEADER_QUERY_LENGTH) + .map(|_| { + let diff = create_random_state_diff_chunk(&mut rng); + let length = diff.len(); + (diff, length) + }) + .unzip(); + + let (state_diff_sender, mut state_diff_receiver) = channel(p2p_sync.config.buffer_size); + + // Create a future that will receive send responses and validate the results. + let test_future = async move { for (start_block_number, num_blocks) in [ (0u64, STATE_DIFF_QUERY_LENGTH), (STATE_DIFF_QUERY_LENGTH, HEADER_QUERY_LENGTH - STATE_DIFF_QUERY_LENGTH), ] { - // Get a state diff query and validate it - let mut mock_state_diff_responses_manager = - mock_state_diff_response_manager.next().await.unwrap(); - assert_eq!( - *mock_state_diff_responses_manager.query(), - Ok(StateDiffQuery(Query { - start_block: BlockHashOrNumber::Number(BlockNumber(start_block_number)), - direction: Direction::Forward, - limit: num_blocks, - step: 1, - })), - "If the limit of the query is too low, try to increase \ - SLEEP_DURATION_TO_LET_SYNC_ADVANCE", - ); - for block_number in start_block_number..(start_block_number + num_blocks) { let state_diff_chunk = state_diffs[usize::try_from(block_number).unwrap()].clone(); @@ -130,14 +88,12 @@ async fn state_diff_basic_flow() { let txn = storage_reader.begin_ro_txn().unwrap(); assert_eq!(block_number, txn.get_state_marker().unwrap()); - mock_state_diff_responses_manager - .send_response(DataOrFin(Some(state_diff_chunk.clone()))) - .await - .unwrap(); + state_diff_sender.send(Some(state_diff_chunk.clone())).await.unwrap(); // Check state diff was written to the storage. This way we make sure that the sync // writes to the storage each block's state diff before receiving all query // responses. + wait_for_marker( MarkerKind::State, &storage_reader, @@ -185,7 +141,8 @@ async fn state_diff_basic_flow() { }; assert_eq!(state_diff, expected_state_diff); } - mock_state_diff_responses_manager.send_response(DataOrFin(None)).await.unwrap(); + + state_diff_sender.send(None).await.unwrap(); } }; @@ -194,7 +151,16 @@ async fn state_diff_basic_flow() { sync_result.unwrap(); panic!("P2P sync aborted with no failure."); } - _ = parse_queries_future => {} + _ = join( + run_state_diff_sync_through_channel( + &mut mock_header_response_manager, + &mut mock_state_diff_response_manager, + header_state_diff_lengths, + &mut state_diff_receiver, + false, + ), + test_future, + ) => {} } } @@ -202,7 +168,7 @@ async fn state_diff_basic_flow() { // returned from parse_data_for_block. We currently dont have a way to check this. #[tokio::test] async fn state_diff_empty_state_diff() { - validate_state_diff_fails(1, vec![Some(StateDiffChunk::default())]).await; + validate_state_diff_fails(vec![1], vec![Some(StateDiffChunk::default())]).await; } // TODO(noamsp): Consider verifying that ParseDataError::BadPeerError(WrongStateDiffLength) was @@ -210,7 +176,7 @@ async fn state_diff_empty_state_diff() { #[tokio::test] async fn state_diff_stopped_in_middle() { validate_state_diff_fails( - 2, + vec![2], vec![ Some(StateDiffChunk::DeprecatedDeclaredClass(DeprecatedDeclaredClass::default())), None, @@ -224,7 +190,7 @@ async fn state_diff_stopped_in_middle() { #[tokio::test] async fn state_diff_not_split_correctly() { validate_state_diff_fails( - 2, + vec![2], vec![ Some(StateDiffChunk::DeprecatedDeclaredClass(DeprecatedDeclaredClass::default())), Some(StateDiffChunk::ContractDiff(ContractDiff { @@ -243,7 +209,7 @@ async fn state_diff_not_split_correctly() { #[tokio::test] async fn state_diff_conflicting() { validate_state_diff_fails( - 2, + vec![2], vec![ Some(StateDiffChunk::ContractDiff(ContractDiff { contract_address: ContractAddress::default(), @@ -259,7 +225,7 @@ async fn state_diff_conflicting() { ) .await; validate_state_diff_fails( - 2, + vec![2], vec![ Some(StateDiffChunk::ContractDiff(ContractDiff { contract_address: ContractAddress::default(), @@ -275,7 +241,7 @@ async fn state_diff_conflicting() { ) .await; validate_state_diff_fails( - 2, + vec![2], vec![ Some(StateDiffChunk::DeclaredClass(DeclaredClass { class_hash: ClassHash::default(), @@ -289,7 +255,7 @@ async fn state_diff_conflicting() { ) .await; validate_state_diff_fails( - 2, + vec![2], vec![ Some(StateDiffChunk::DeprecatedDeclaredClass(DeprecatedDeclaredClass { class_hash: ClassHash::default(), @@ -301,7 +267,7 @@ async fn state_diff_conflicting() { ) .await; validate_state_diff_fails( - 2, + vec![2], vec![ Some(StateDiffChunk::ContractDiff(ContractDiff { contract_address: ContractAddress::default(), @@ -319,12 +285,12 @@ async fn state_diff_conflicting() { } async fn validate_state_diff_fails( - state_diff_length_in_header: usize, + header_state_diff_lengths: Vec, state_diff_chunks: Vec>, ) { let TestArgs { - p2p_sync, storage_reader, + p2p_sync, mut mock_state_diff_response_manager, mut mock_header_response_manager, // The test will fail if we drop these @@ -333,36 +299,105 @@ async fn validate_state_diff_fails( .. } = setup(); - let (block_hash, block_signature) = *create_block_hashes_and_signatures(1).first().unwrap(); + let (state_diff_sender, mut state_diff_receiver) = channel(p2p_sync.config.buffer_size); + + // Create a future that will send responses and validate the results. + let test_future = async move { + for state_diff_chunk in state_diff_chunks { + // Check that before we've sent all parts the state diff wasn't written yet. + let txn = storage_reader.begin_ro_txn().unwrap(); + assert_eq!(0, txn.get_state_marker().unwrap().0); + + state_diff_sender.send(state_diff_chunk).await.unwrap(); + } + }; + + tokio::select! { + sync_result = p2p_sync.run() => { + sync_result.unwrap(); + panic!("P2P sync aborted with no failure."); + } + _ = join( + run_state_diff_sync_through_channel( + &mut mock_header_response_manager, + &mut mock_state_diff_response_manager, + header_state_diff_lengths, + &mut state_diff_receiver, + true, + ), + test_future + ) => {} + } +} + +// Advances the header sync with associated header state diffs. +// The receiver waits for external sender to provide the state diff chunks. +async fn run_state_diff_sync_through_channel( + mock_header_response_manager: &mut GenericReceiver, + mock_state_diff_response_manager: &mut GenericReceiver, + header_state_diff_lengths: Vec, + state_diff_chunk_receiver: &mut Receiver>, + should_assert_reported: bool, +) { + // We wait for the state diff sync to see that there are no headers and start sleeping + tokio::time::sleep(SLEEP_DURATION_TO_LET_SYNC_ADVANCE).await; + + // Check that before we send headers there is no state diff query. + assert!(mock_state_diff_response_manager.next().now_or_never().is_none()); - // Create a future that will receive queries, send responses and validate the results. - let parse_queries_future = async move { - // Send a single header. There's no need to fill the entire query. + let num_headers = header_state_diff_lengths.len(); + let block_hashes_and_signatures = + create_block_hashes_and_signatures(num_headers.try_into().unwrap()); + + // split the headers into queries of size HEADER_QUERY_LENGTH and send headers for each query + for header_querie in block_hashes_and_signatures + .iter() + .zip(header_state_diff_lengths.iter()) + .enumerate() + .collect::>() + .chunks(HEADER_QUERY_LENGTH.try_into().unwrap()) + { let mut mock_header_responses_manager = mock_header_response_manager.next().await.unwrap(); - mock_header_responses_manager - .send_response(DataOrFin(Some(SignedBlockHeader { - block_header: BlockHeader { - block_hash, - block_header_without_hash: BlockHeaderWithoutHash { - block_number: BlockNumber(0), + + for (i, ((block_hash, block_signature), header_state_diff_length)) in header_querie { + // Send header responses + mock_header_responses_manager + .send_response(DataOrFin(Some(SignedBlockHeader { + block_header: BlockHeader { + block_hash: *block_hash, + block_header_without_hash: BlockHeaderWithoutHash { + block_number: BlockNumber(u64::try_from(*i).unwrap()), + ..Default::default() + }, + state_diff_length: Some(**header_state_diff_length), ..Default::default() }, - state_diff_length: Some(state_diff_length_in_header), - ..Default::default() - }, - signatures: vec![block_signature], - }))) - .await - .unwrap(); + signatures: vec![*block_signature], + }))) + .await + .unwrap(); + } - // We wait for the header sync to write the new headers. - tokio::time::sleep(SLEEP_DURATION_TO_LET_SYNC_ADVANCE).await; + mock_header_responses_manager.send_response(DataOrFin(None)).await.unwrap(); + } - // Simulate time has passed so that state diff sync will resend query after it waited for - // new header - tokio::time::pause(); - tokio::time::advance(WAIT_PERIOD_FOR_NEW_DATA).await; - tokio::time::resume(); + // TODO(noamsp): remove sleep and wait until header marker writes the new headers. remove the + // comment from the StateDiffQuery about the limit being too low. We wait for the header + // sync to write the new headers. + tokio::time::sleep(SLEEP_DURATION_TO_LET_SYNC_ADVANCE).await; + + // Simulate time has passed so that state diff sync will resend query after it waited for + // new header + tokio::time::pause(); + tokio::time::advance(WAIT_PERIOD_FOR_NEW_DATA).await; + tokio::time::resume(); + + let num_state_diff_headers = u64::try_from(num_headers).unwrap(); + let num_state_diff_queries = num_state_diff_headers.div_ceil(STATE_DIFF_QUERY_LENGTH); + + for i in 0..num_state_diff_queries { + let start_block_number = i * STATE_DIFF_QUERY_LENGTH; + let limit = min(num_state_diff_headers - start_block_number, STATE_DIFF_QUERY_LENGTH); // Get a state diff query and validate it let mut mock_state_diff_responses_manager = @@ -370,38 +405,82 @@ async fn validate_state_diff_fails( assert_eq!( *mock_state_diff_responses_manager.query(), Ok(StateDiffQuery(Query { - start_block: BlockHashOrNumber::Number(BlockNumber(0)), + start_block: BlockHashOrNumber::Number(BlockNumber(start_block_number)), direction: Direction::Forward, - limit: 1, + limit, step: 1, - })) + })), + "If the limit of the query is too low, try to increase \ + SLEEP_DURATION_TO_LET_SYNC_ADVANCE", ); - // Send state diffs. - for state_diff_chunk in state_diff_chunks { - // Check that before we've sent all parts the state diff wasn't written yet. - let txn = storage_reader.begin_ro_txn().unwrap(); - assert_eq!(0, txn.get_state_marker().unwrap().0); + let mut current_state_diff_length = 0; + let destination_state_diff_length = + header_state_diff_lengths[start_block_number.try_into().unwrap() + ..(start_block_number + limit).try_into().unwrap()] + .iter() + .sum(); + + while current_state_diff_length < destination_state_diff_length { + let state_diff_chunk = state_diff_chunk_receiver.recv().await.unwrap(); mock_state_diff_responses_manager - .send_response(DataOrFin(state_diff_chunk)) + .send_response(DataOrFin(state_diff_chunk.clone())) .await .unwrap(); - } - // Asserts that a peer was reported due to a non-fatal error. - mock_state_diff_responses_manager.assert_reported(TIMEOUT_FOR_TEST).await; - }; + if let Some(state_diff_chunk) = state_diff_chunk { + if !state_diff_chunk.is_empty() { + current_state_diff_length += state_diff_chunk.len(); + continue; + } + } - tokio::select! { - sync_result = p2p_sync.run() => { - sync_result.unwrap(); - panic!("P2P sync aborted with no failure."); + break; } - _ = parse_queries_future => {} + + if should_assert_reported { + mock_state_diff_responses_manager.assert_reported(TIMEOUT_FOR_TEST).await; + continue; + } + + assert_eq!(current_state_diff_length, destination_state_diff_length); + let state_diff_chunk = state_diff_chunk_receiver.recv().await.unwrap(); + mock_state_diff_responses_manager + .send_response(DataOrFin(state_diff_chunk.clone())) + .await + .unwrap(); } } +pub(crate) async fn run_state_diff_sync( + config: P2PSyncClientConfig, + mock_header_response_manager: &mut GenericReceiver, + mock_state_diff_response_manager: &mut GenericReceiver, + header_state_diff_lengths: Vec, + state_diff_chunks: Vec>, +) { + let (state_diff_sender, mut state_diff_receiver) = channel(config.buffer_size); + tokio::join! { + run_state_diff_sync_through_channel( + mock_header_response_manager, + mock_state_diff_response_manager, + header_state_diff_lengths, + &mut state_diff_receiver, + false, + ), + async { + for state_diff in state_diff_chunks.chunks(STATE_DIFF_QUERY_LENGTH.try_into().unwrap()) { + for state_diff_chunk in state_diff { + state_diff_sender.send(state_diff_chunk.clone()).await.unwrap(); + } + + state_diff_sender.send(None).await.unwrap(); + } + } + }; +} + fn create_random_state_diff_chunk(rng: &mut ChaCha8Rng) -> StateDiffChunk { let mut state_diff_chunk = StateDiffChunk::get_test_instance(rng); let contract_address = ContractAddress::from(rng.next_u64()); diff --git a/crates/papyrus_p2p_sync/src/client/test_utils.rs b/crates/papyrus_p2p_sync/src/client/test_utils.rs index 96b8842208..d9423d1f9b 100644 --- a/crates/papyrus_p2p_sync/src/client/test_utils.rs +++ b/crates/papyrus_p2p_sync/src/client/test_utils.rs @@ -53,11 +53,13 @@ lazy_static! { stop_sync_at_block_number: None, }; } -type HeaderTestPayload = MockClientResponsesManager>; -type StateDiffTestPayload = MockClientResponsesManager>; -type TransactionTestPayload = +pub(crate) type HeaderTestPayload = + MockClientResponsesManager>; +pub(crate) type StateDiffTestPayload = + MockClientResponsesManager>; +pub(crate) type TransactionTestPayload = MockClientResponsesManager>; -type ClassTestPayload = +pub(crate) type ClassTestPayload = MockClientResponsesManager>; // TODO(Eitan): Use SqmrSubscriberChannels once there is a utility function for testing diff --git a/crates/papyrus_protobuf/src/converters/class.rs b/crates/papyrus_protobuf/src/converters/class.rs index 414ca5e43f..f16d0fd635 100644 --- a/crates/papyrus_protobuf/src/converters/class.rs +++ b/crates/papyrus_protobuf/src/converters/class.rs @@ -282,15 +282,23 @@ impl From for protobuf::Cairo1Class { .collect(), }); - let contract_class_version = format!( - "sierra-v{}.{}.{} cairo-v{}.{}.{}", - value.sierra_program[0], - value.sierra_program[1], - value.sierra_program[2], - value.sierra_program[3], - value.sierra_program[4], - value.sierra_program[5] - ); + // This length check and default option is needed for ContractClass test instances that + // don't properly set the sierra_program field. + // It is assumed that the first 6 elements of the sierra_program vector compose the contract + // class version. + let contract_class_version = if value.sierra_program.len() >= 6 { + format!( + "sierra-v{}.{}.{} cairo-v{}.{}.{}", + value.sierra_program[0], + value.sierra_program[1], + value.sierra_program[2], + value.sierra_program[3], + value.sierra_program[4], + value.sierra_program[5] + ) + } else { + "".to_string() + }; protobuf::Cairo1Class { abi, program, entry_points, contract_class_version } }