diff --git a/Cargo.lock b/Cargo.lock index 07d0d5ff17..a793928de2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6127,6 +6127,7 @@ dependencies = [ "papyrus_protobuf", "papyrus_storage", "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "starknet-types-core", "starknet_api", diff --git a/crates/papyrus_p2p_sync/Cargo.toml b/crates/papyrus_p2p_sync/Cargo.toml index 723f941650..0de39b967c 100644 --- a/crates/papyrus_p2p_sync/Cargo.toml +++ b/crates/papyrus_p2p_sync/Cargo.toml @@ -30,4 +30,6 @@ lazy_static.workspace = true papyrus_storage = { path = "../papyrus_storage", features = ["testing"] } static_assertions.workspace = true rand.workspace = true +rand_chacha.workspace = true test_utils = { path = "../test_utils" } +papyrus_protobuf = { path = "../papyrus_protobuf", features = ["testing"]} diff --git a/crates/papyrus_p2p_sync/src/client/mod.rs b/crates/papyrus_p2p_sync/src/client/mod.rs index 55b320ba32..f30cb94e5d 100644 --- a/crates/papyrus_p2p_sync/src/client/mod.rs +++ b/crates/papyrus_p2p_sync/src/client/mod.rs @@ -26,13 +26,13 @@ use papyrus_protobuf::sync::{ HeaderQuery, Query, SignedBlockHeader, + StateDiffChunk, StateDiffQuery, TransactionQuery, }; use papyrus_storage::{StorageError, StorageReader, StorageWriter}; use serde::{Deserialize, Serialize}; use starknet_api::block::{BlockNumber, BlockSignature}; -use starknet_api::state::ThinStateDiff; use starknet_api::transaction::{Transaction, TransactionOutput}; use state_diff::StateDiffStreamFactory; use stream_factory::{DataStreamFactory, DataStreamResult}; @@ -164,7 +164,7 @@ type ResponseReceiver = Box> + Unpin + Send + ' type HeaderQuerySender = QuerySender; type HeaderResponseReceiver = ResponseReceiver; type StateDiffQuerySender = QuerySender; -type StateDiffResponseReceiver = ResponseReceiver; +type StateDiffResponseReceiver = ResponseReceiver; type TransactionQuerySender = QuerySender; type TransactionResponseReceiver = ResponseReceiver<(Transaction, TransactionOutput)>; diff --git a/crates/papyrus_p2p_sync/src/client/state_diff.rs b/crates/papyrus_p2p_sync/src/client/state_diff.rs index 891c51ae5b..9b2885f68c 100644 --- a/crates/papyrus_p2p_sync/src/client/state_diff.rs +++ b/crates/papyrus_p2p_sync/src/client/state_diff.rs @@ -5,14 +5,16 @@ use futures::future::BoxFuture; use futures::{FutureExt, StreamExt}; use indexmap::IndexMap; use papyrus_proc_macros::latency_histogram; +use papyrus_protobuf::sync::StateDiffChunk; use papyrus_storage::header::HeaderStorageReader; use papyrus_storage::state::{StateStorageReader, StateStorageWriter}; use papyrus_storage::{StorageError, StorageReader, StorageWriter}; use starknet_api::block::BlockNumber; use starknet_api::state::ThinStateDiff; -use super::stream_factory::{BlockData, BlockNumberLimit, DataStreamFactory}; -use super::{P2PSyncError, ResponseReceiver, NETWORK_DATA_TIMEOUT}; +use super::ResponseReceiver; +use crate::client::stream_factory::{BlockData, BlockNumberLimit, DataStreamFactory}; +use crate::client::{P2PSyncError, NETWORK_DATA_TIMEOUT}; impl BlockData for (ThinStateDiff, BlockNumber) { #[latency_histogram("p2p_sync_state_diff_write_to_storage_latency_seconds", true)] @@ -26,8 +28,7 @@ impl BlockData for (ThinStateDiff, BlockNumber) { pub(crate) struct StateDiffStreamFactory; -// TODO(shahak): Change to StateDiffChunk. -impl DataStreamFactory for StateDiffStreamFactory { +impl DataStreamFactory for StateDiffStreamFactory { type Output = (ThinStateDiff, BlockNumber); const TYPE_DESCRIPTION: &'static str = "state diffs"; @@ -35,7 +36,7 @@ impl DataStreamFactory for StateDiffStreamFactory { #[latency_histogram("p2p_sync_state_diff_parse_data_for_block_latency_seconds", true)] fn parse_data_for_block<'a>( - state_diffs_receiver: &'a mut ResponseReceiver, + state_diff_chunks_receiver: &'a mut ResponseReceiver, block_number: BlockNumber, storage_reader: &'a StorageReader, ) -> BoxFuture<'a, Result, P2PSyncError>> { @@ -54,13 +55,13 @@ impl DataStreamFactory for StateDiffStreamFactory { })?; while current_state_diff_len < target_state_diff_len { - let (maybe_state_diff_part, _report_callback) = - tokio::time::timeout(NETWORK_DATA_TIMEOUT, state_diffs_receiver.next()) + let (maybe_state_diff_chunk, _report_callback) = + tokio::time::timeout(NETWORK_DATA_TIMEOUT, state_diff_chunks_receiver.next()) .await? .ok_or(P2PSyncError::ReceiverChannelTerminated { type_description: Self::TYPE_DESCRIPTION, })?; - let Some(state_diff_part) = maybe_state_diff_part?.0 else { + let Some(state_diff_chunk) = maybe_state_diff_chunk?.0 else { if current_state_diff_len == 0 { return Ok(None); } else { @@ -71,13 +72,13 @@ impl DataStreamFactory for StateDiffStreamFactory { } }; prev_result_len = current_state_diff_len; - if state_diff_part.is_empty() { + if state_diff_chunk.is_empty() { return Err(P2PSyncError::EmptyStateDiffPart); } // It's cheaper to calculate the length of `state_diff_part` than the length of // `result`. - current_state_diff_len += state_diff_part.len(); - unite_state_diffs(&mut result, state_diff_part)?; + current_state_diff_len += state_diff_chunk.len(); + unite_state_diffs(&mut result, state_diff_chunk)?; } if current_state_diff_len != target_state_diff_len { @@ -103,26 +104,41 @@ impl DataStreamFactory for StateDiffStreamFactory { #[latency_histogram("p2p_sync_state_diff_unite_state_diffs_latency_seconds", true)] fn unite_state_diffs( state_diff: &mut ThinStateDiff, - other_state_diff: ThinStateDiff, + state_diff_chunk: StateDiffChunk, ) -> Result<(), P2PSyncError> { - unite_state_diffs_field( - &mut state_diff.deployed_contracts, - other_state_diff.deployed_contracts, - )?; - unite_state_diffs_field(&mut state_diff.declared_classes, other_state_diff.declared_classes)?; - unite_state_diffs_field(&mut state_diff.nonces, other_state_diff.nonces)?; - unite_state_diffs_field(&mut state_diff.replaced_classes, other_state_diff.replaced_classes)?; - - for (other_contract_address, other_storage_diffs) in other_state_diff.storage_diffs { - match state_diff.storage_diffs.get_mut(&other_contract_address) { - Some(storage_diffs) => unite_state_diffs_field(storage_diffs, other_storage_diffs)?, - None => { - state_diff.storage_diffs.insert(other_contract_address, other_storage_diffs); + match state_diff_chunk { + StateDiffChunk::ContractDiff(contract_diff) => { + let mut chunk_deployed_contract = IndexMap::new(); + if let Some(class_hash) = contract_diff.class_hash { + chunk_deployed_contract.insert(contract_diff.contract_address, class_hash); + } + unite_state_diffs_field(&mut state_diff.deployed_contracts, chunk_deployed_contract)?; + let mut chunk_nonce = IndexMap::new(); + if let Some(nonce) = contract_diff.nonce { + chunk_nonce.insert(contract_diff.contract_address, nonce); + } + unite_state_diffs_field(&mut state_diff.nonces, chunk_nonce)?; + match state_diff.storage_diffs.get_mut(&contract_diff.contract_address) { + Some(storage_diffs) => { + unite_state_diffs_field(storage_diffs, contract_diff.storage_diffs)? + } + None => { + state_diff + .storage_diffs + .insert(contract_diff.contract_address, contract_diff.storage_diffs); + } } } + StateDiffChunk::DeclaredClass(declared_class) => { + let mut chunk_declared_class = IndexMap::new(); + chunk_declared_class + .insert(declared_class.class_hash, declared_class.compiled_class_hash); + unite_state_diffs_field(&mut state_diff.declared_classes, chunk_declared_class)?; + } + StateDiffChunk::DeprecatedDeclaredClass(deprecated_declared_class) => { + state_diff.deprecated_declared_classes.push(deprecated_declared_class.class_hash); + } } - - state_diff.deprecated_declared_classes.extend(other_state_diff.deprecated_declared_classes); Ok(()) } 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 343ee5ee3c..65e79e36d5 100644 --- a/crates/papyrus_p2p_sync/src/client/state_diff_test.rs +++ b/crates/papyrus_p2p_sync/src/client/state_diff_test.rs @@ -2,17 +2,27 @@ use std::time::Duration; use assert_matches::assert_matches; use futures::{FutureExt, SinkExt, StreamExt}; -use indexmap::{indexmap, IndexMap}; -use papyrus_common::state::create_random_state_diff; -use papyrus_protobuf::sync::{BlockHashOrNumber, DataOrFin, Direction, Query, SignedBlockHeader}; +use indexmap::indexmap; +use papyrus_protobuf::sync::{ + BlockHashOrNumber, + ContractDiff, + DataOrFin, + DeclaredClass, + DeprecatedDeclaredClass, + Direction, + Query, + SignedBlockHeader, + StateDiffChunk, +}; use papyrus_storage::state::StateStorageReader; +use rand::RngCore; +use rand_chacha::ChaCha8Rng; use starknet_api::block::{BlockHeader, BlockNumber}; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; -use starknet_api::hash::StarkHash; use starknet_api::state::{StorageKey, ThinStateDiff}; use starknet_types_core::felt::Felt; use static_assertions::const_assert; -use test_utils::get_rng; +use test_utils::{get_rng, GetTestInstance}; use super::test_utils::{ create_block_hashes_and_signatures, @@ -26,41 +36,6 @@ use super::{P2PSyncError, StateDiffQuery}; const TIMEOUT_FOR_TEST: Duration = Duration::from_secs(5); -fn split_state_diff(state_diff: ThinStateDiff) -> Vec { - let mut result = Vec::new(); - if !state_diff.deployed_contracts.is_empty() { - result.push(ThinStateDiff { - deployed_contracts: state_diff.deployed_contracts, - ..Default::default() - }) - } - if !state_diff.storage_diffs.is_empty() { - result.push(ThinStateDiff { storage_diffs: state_diff.storage_diffs, ..Default::default() }) - } - if !state_diff.declared_classes.is_empty() { - result.push(ThinStateDiff { - declared_classes: state_diff.declared_classes, - ..Default::default() - }) - } - if !state_diff.deprecated_declared_classes.is_empty() { - result.push(ThinStateDiff { - deprecated_declared_classes: state_diff.deprecated_declared_classes, - ..Default::default() - }) - } - if !state_diff.nonces.is_empty() { - result.push(ThinStateDiff { nonces: state_diff.nonces, ..Default::default() }) - } - if !state_diff.replaced_classes.is_empty() { - result.push(ThinStateDiff { - replaced_classes: state_diff.replaced_classes, - ..Default::default() - }) - } - result -} - #[tokio::test] async fn state_diff_basic_flow() { // Asserting the constants so the test can assume there will be 2 state diff queries for a @@ -84,8 +59,9 @@ async fn state_diff_basic_flow() { let block_hashes_and_signatures = create_block_hashes_and_signatures(HEADER_QUERY_LENGTH.try_into().unwrap()); let mut rng = get_rng(); - let state_diffs = - (0..HEADER_QUERY_LENGTH).map(|_| create_random_state_diff(&mut rng)).collect::>(); + 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 { @@ -133,21 +109,18 @@ async fn state_diff_basic_flow() { ); for block_number in start_block_number..(start_block_number + num_blocks) { - let expected_state_diff: &ThinStateDiff = - &state_diffs[usize::try_from(block_number).unwrap()]; - let state_diff_parts = split_state_diff(expected_state_diff.clone()); + let state_diff_chunk = state_diffs[usize::try_from(block_number).unwrap()].clone(); let block_number = BlockNumber(block_number); - for state_diff_part in state_diff_parts { - // 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_state_marker().unwrap()); - state_diffs_sender - .send((Ok(DataOrFin(Some(state_diff_part))), Box::new(|| {}))) - .await - .unwrap(); - } + // 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_state_marker().unwrap()); + + state_diffs_sender + .send((Ok(DataOrFin(Some(state_diff_chunk.clone()))), Box::new(|| {}))) + .await + .unwrap(); tokio::time::sleep(SLEEP_DURATION_TO_LET_SYNC_ADVANCE).await; @@ -156,8 +129,41 @@ async fn state_diff_basic_flow() { // responses. let txn = storage_reader.begin_ro_txn().unwrap(); assert_eq!(block_number.unchecked_next(), txn.get_state_marker().unwrap()); - let state_diff = txn.get_state_diff(block_number).unwrap().unwrap(); - assert_eq!(state_diff, *expected_state_diff); + + let expected_state_diff = txn.get_state_diff(block_number).unwrap().unwrap(); + let state_diff = match state_diff_chunk { + StateDiffChunk::ContractDiff(contract_diff) => { + let mut deployed_contracts = indexmap! {}; + if let Some(class_hash) = contract_diff.class_hash { + deployed_contracts.insert(contract_diff.contract_address, class_hash); + }; + let mut nonces = indexmap! {}; + if let Some(nonce) = contract_diff.nonce { + nonces.insert(contract_diff.contract_address, nonce); + } + ThinStateDiff { + deployed_contracts, + nonces, + storage_diffs: indexmap! { + contract_diff.contract_address => contract_diff.storage_diffs + }, + ..Default::default() + } + } + StateDiffChunk::DeclaredClass(declared_class) => ThinStateDiff { + declared_classes: indexmap! { + declared_class.class_hash => declared_class.compiled_class_hash + }, + ..Default::default() + }, + StateDiffChunk::DeprecatedDeclaredClass(deprecated_declared_class) => { + ThinStateDiff { + deprecated_declared_classes: vec![deprecated_declared_class.class_hash], + ..Default::default() + } + } + }; + assert_eq!(expected_state_diff, state_diff); } state_diffs_sender.send((Ok(DataOrFin(None)), Box::new(|| {}))).await.unwrap(); } @@ -174,7 +180,7 @@ async fn state_diff_basic_flow() { async fn validate_state_diff_fails( state_diff_length_in_header: usize, - state_diff_parts: Vec>, + state_diff_chunks: Vec>, error_validator: impl Fn(P2PSyncError), ) { let TestArgs { @@ -224,13 +230,13 @@ async fn validate_state_diff_fails( ); // Send state diffs. - for state_diff_part in state_diff_parts { + 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_diffs_sender - .send((Ok(DataOrFin(state_diff_part)), Box::new(|| {}))) + .send((Ok(DataOrFin(state_diff_chunk)), Box::new(|| {}))) .await .unwrap(); } @@ -247,22 +253,32 @@ async fn validate_state_diff_fails( } } +pub 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()); + let class_hash = ClassHash(rng.next_u64().into()); + match &mut state_diff_chunk { + StateDiffChunk::ContractDiff(contract_diff) => { + contract_diff.contract_address = contract_address; + contract_diff.class_hash = Some(class_hash); + } + StateDiffChunk::DeclaredClass(declared_class) => { + declared_class.class_hash = class_hash; + declared_class.compiled_class_hash = CompiledClassHash(rng.next_u64().into()); + } + StateDiffChunk::DeprecatedDeclaredClass(deprecated_declared_class) => { + deprecated_declared_class.class_hash = class_hash; + } + } + state_diff_chunk +} + #[tokio::test] async fn state_diff_empty_state_diff() { - validate_state_diff_fails(1, vec![Some(ThinStateDiff::default())], |error| { + validate_state_diff_fails(1, vec![Some(StateDiffChunk::default())], |error| { assert_matches!(error, P2PSyncError::EmptyStateDiffPart) }) .await; - - validate_state_diff_fails( - 1, - vec![Some(ThinStateDiff { - storage_diffs: indexmap! {ContractAddress::default() => IndexMap::default()}, - ..Default::default() - })], - |error| assert_matches!(error, P2PSyncError::EmptyStateDiffPart), - ) - .await; } #[tokio::test] @@ -270,10 +286,7 @@ async fn state_diff_stopped_in_middle() { validate_state_diff_fails( 2, vec![ - Some(ThinStateDiff { - deprecated_declared_classes: vec![ClassHash::default()], - ..Default::default() - }), + Some(StateDiffChunk::DeprecatedDeclaredClass(DeprecatedDeclaredClass::default())), None, ], |error| assert_matches!(error, P2PSyncError::WrongStateDiffLength { expected_length, possible_lengths } if expected_length == 2 && possible_lengths == vec![1]), @@ -281,40 +294,21 @@ async fn state_diff_stopped_in_middle() { .await; } -#[tokio::test] -async fn state_diff_not_splitted_correctly() { - validate_state_diff_fails( - 2, - vec![ - Some(ThinStateDiff { - deprecated_declared_classes: vec![ClassHash::default()], - ..Default::default() - }), - Some(ThinStateDiff { - deprecated_declared_classes: vec![ - ClassHash(StarkHash::ONE), ClassHash(StarkHash::TWO) - ], - ..Default::default() - }), - ], - |error| assert_matches!(error, P2PSyncError::WrongStateDiffLength { expected_length, possible_lengths } if expected_length == 2 && possible_lengths == vec![1, 3]), - ) - .await; -} - #[tokio::test] async fn state_diff_conflicting() { validate_state_diff_fails( 2, vec![ - Some(ThinStateDiff { - deployed_contracts: indexmap! { ContractAddress::default() => ClassHash::default() }, + Some(StateDiffChunk::ContractDiff(ContractDiff { + contract_address: ContractAddress::default(), + class_hash: Some(ClassHash::default()), ..Default::default() - }), - Some(ThinStateDiff { - deployed_contracts: indexmap! { ContractAddress::default() => ClassHash::default() }, + })), + Some(StateDiffChunk::ContractDiff(ContractDiff { + contract_address: ContractAddress::default(), + class_hash: Some(ClassHash::default()), ..Default::default() - }), + })), ], |error| assert_matches!(error, P2PSyncError::ConflictingStateDiffParts), ) @@ -322,18 +316,16 @@ async fn state_diff_conflicting() { validate_state_diff_fails( 2, vec![ - Some(ThinStateDiff { - storage_diffs: indexmap! { ContractAddress::default() => indexmap! { - StorageKey::default() => Felt::default() - }}, + Some(StateDiffChunk::ContractDiff(ContractDiff { + contract_address: ContractAddress::default(), + storage_diffs: indexmap! { StorageKey::default() => Felt::default() }, ..Default::default() - }), - Some(ThinStateDiff { - storage_diffs: indexmap! { ContractAddress::default() => indexmap! { - StorageKey::default() => Felt::default() - }}, + })), + Some(StateDiffChunk::ContractDiff(ContractDiff { + contract_address: ContractAddress::default(), + storage_diffs: indexmap! { StorageKey::default() => Felt::default() }, ..Default::default() - }), + })), ], |error| assert_matches!(error, P2PSyncError::ConflictingStateDiffParts), ) @@ -341,18 +333,14 @@ async fn state_diff_conflicting() { validate_state_diff_fails( 2, vec![ - Some(ThinStateDiff { - declared_classes: indexmap! { - ClassHash::default() => CompiledClassHash::default() - }, - ..Default::default() - }), - Some(ThinStateDiff { - declared_classes: indexmap! { - ClassHash::default() => CompiledClassHash::default() - }, - ..Default::default() - }), + Some(StateDiffChunk::DeclaredClass(DeclaredClass { + class_hash: ClassHash::default(), + compiled_class_hash: CompiledClassHash::default(), + })), + Some(StateDiffChunk::DeclaredClass(DeclaredClass { + class_hash: ClassHash::default(), + compiled_class_hash: CompiledClassHash::default(), + })), ], |error| assert_matches!(error, P2PSyncError::ConflictingStateDiffParts), ) @@ -360,29 +348,12 @@ async fn state_diff_conflicting() { validate_state_diff_fails( 2, vec![ - Some(ThinStateDiff { - deprecated_declared_classes: vec![ClassHash::default()], - ..Default::default() - }), - Some(ThinStateDiff { - deprecated_declared_classes: vec![ClassHash::default()], - ..Default::default() - }), - ], - |error| assert_matches!(error, P2PSyncError::ConflictingStateDiffParts), - ) - .await; - validate_state_diff_fails( - 2, - vec![ - Some(ThinStateDiff { - nonces: indexmap! { ContractAddress::default() => Nonce::default() }, - ..Default::default() - }), - Some(ThinStateDiff { - nonces: indexmap! { ContractAddress::default() => Nonce::default() }, - ..Default::default() - }), + Some(StateDiffChunk::DeprecatedDeclaredClass(DeprecatedDeclaredClass { + class_hash: ClassHash::default(), + })), + Some(StateDiffChunk::DeprecatedDeclaredClass(DeprecatedDeclaredClass { + class_hash: ClassHash::default(), + })), ], |error| assert_matches!(error, P2PSyncError::ConflictingStateDiffParts), ) @@ -390,14 +361,16 @@ async fn state_diff_conflicting() { validate_state_diff_fails( 2, vec![ - Some(ThinStateDiff { - replaced_classes: indexmap! { ContractAddress::default() => ClassHash::default() }, + Some(StateDiffChunk::ContractDiff(ContractDiff { + contract_address: ContractAddress::default(), + nonce: Some(Nonce::default()), ..Default::default() - }), - Some(ThinStateDiff { - replaced_classes: indexmap! { ContractAddress::default() => ClassHash::default() }, + })), + Some(StateDiffChunk::ContractDiff(ContractDiff { + contract_address: ContractAddress::default(), + nonce: Some(Nonce::default()), ..Default::default() - }), + })), ], |error| assert_matches!(error, P2PSyncError::ConflictingStateDiffParts), ) diff --git a/crates/papyrus_p2p_sync/src/client/test_utils.rs b/crates/papyrus_p2p_sync/src/client/test_utils.rs index 21dfc96a1c..c05276d73d 100644 --- a/crates/papyrus_p2p_sync/src/client/test_utils.rs +++ b/crates/papyrus_p2p_sync/src/client/test_utils.rs @@ -2,13 +2,18 @@ use std::time::Duration; use futures::channel::mpsc::{Receiver, Sender}; use lazy_static::lazy_static; -use papyrus_protobuf::sync::{HeaderQuery, SignedBlockHeader, StateDiffQuery, TransactionQuery}; +use papyrus_protobuf::sync::{ + HeaderQuery, + SignedBlockHeader, + StateDiffChunk, + StateDiffQuery, + TransactionQuery, +}; use papyrus_storage::test_utils::get_test_storage; use papyrus_storage::StorageReader; use starknet_api::block::{BlockHash, BlockSignature}; use starknet_api::crypto::utils::Signature; use starknet_api::hash::StarkHash; -use starknet_api::state::ThinStateDiff; use starknet_api::transaction::{Transaction, TransactionOutput}; use starknet_types_core::felt::Felt; @@ -41,7 +46,7 @@ pub struct TestArgs { #[allow(dead_code)] pub transaction_query_receiver: Receiver, pub headers_sender: Sender>, - pub state_diffs_sender: Sender>, + pub state_diffs_sender: Sender>, #[allow(dead_code)] pub transaction_sender: Sender>, }