diff --git a/crates/blockifier_reexecution/src/main.rs b/crates/blockifier_reexecution/src/main.rs index 0110ed8975..eae0a3a7f2 100644 --- a/crates/blockifier_reexecution/src/main.rs +++ b/crates/blockifier_reexecution/src/main.rs @@ -1,13 +1,13 @@ -use blockifier::abi::constants; -use blockifier_reexecution::assert_eq_state_diff; -use blockifier_reexecution::state_reader::reexecution_state_reader::ReexecutionStateReader; use blockifier_reexecution::state_reader::test_state_reader::{ - ConsecutiveStateReaders, ConsecutiveTestStateReaders, OfflineConsecutiveStateReaders, + SerializableDataPrevBlock, SerializableOfflineReexecutionData, }; -use blockifier_reexecution::state_reader::utils::JSON_RPC_VERSION; +use blockifier_reexecution::state_reader::utils::{ + reexecute_and_verify_correctness, + JSON_RPC_VERSION, +}; use clap::{Args, Parser, Subcommand}; use starknet_api::block::BlockNumber; use starknet_gateway::config::RpcStateReaderConfig; @@ -23,35 +23,34 @@ pub struct BlockifierReexecutionCliArgs { command: Command, } -#[derive(Args, Debug)] -struct SharedArgs { - /// Node url. - /// Default: https://free-rpc.nethermind.io/mainnet-juno/. Won't work for big tests. - #[clap(long, short = 'n', default_value = "https://free-rpc.nethermind.io/mainnet-juno/")] - node_url: String, - - /// Block number. - #[clap(long, short = 'b')] - block_number: u64, -} - #[derive(Debug, Subcommand)] enum Command { /// Runs the RPC test. RpcTest { - #[clap(flatten)] - url_and_block_number: SharedArgs, + /// Node url. + #[clap(long, short = 'n')] + node_url: String, + + /// Block number. + #[clap(long, short = 'b')] + block_number: u64, }, /// Writes the RPC queries to json files. WriteRpcRepliesToJson { - #[clap(flatten)] - url_and_block_number: SharedArgs, + /// Node url. + #[clap(long, short = 'n')] + node_url: String, - /// Directory path to json files. - /// Default: "./crates/blockifier_reexecution/resources/block_{block_number}". - #[clap(long, default_value = None)] - directory_path: Option, + /// Block number. + #[clap(long, short = 'b')] + block_number: u64, + + // Directory path to json files. Default: + // "./crates/blockifier_reexecution/resources/block_{block_number}". + // TODO(Aner): add possibility to retrieve files from gc bucket. + #[clap(long, short = 'd', default_value = None)] + full_file_path: Option, }, // Reexecutes the block from JSON files. @@ -60,10 +59,11 @@ enum Command { #[clap(long, short = 'b')] block_number: u64, - /// Directory path to json files. - /// Default: "./crates/blockifier_reexecution/resources/block_{block_number}". - #[clap(long, default_value = None)] - directory_path: Option, + // Directory path to json files. Default: + // "./crates/blockifier_reexecution/resources/block_{block_number}". + // TODO(Aner): add possibility to retrieve files from gc bucket. + #[clap(long, short = 'd', default_value = None)] + full_file_path: Option, }, } @@ -75,7 +75,7 @@ fn main() { let args = BlockifierReexecutionCliArgs::parse(); match args.command { - Command::RpcTest { url_and_block_number: SharedArgs { node_url, block_number } } => { + Command::RpcTest { node_url, block_number } => { println!("Running RPC test for block number {block_number} using node url {node_url}.",); let config = RpcStateReaderConfig { @@ -83,38 +83,21 @@ fn main() { json_rpc_version: JSON_RPC_VERSION.to_string(), }; - let test_state_readers_last_and_current_block = ConsecutiveTestStateReaders::new( + reexecute_and_verify_correctness(ConsecutiveTestStateReaders::new( BlockNumber(block_number - 1), Some(config), false, - ); - - let all_txs_in_next_block = - test_state_readers_last_and_current_block.get_next_block_txs().unwrap(); - - let expected_state_diff = - test_state_readers_last_and_current_block.get_next_block_state_diff().unwrap(); - - let mut transaction_executor = - test_state_readers_last_and_current_block.get_transaction_executor(None).unwrap(); - - transaction_executor.execute_txs(&all_txs_in_next_block); - // Finalize block and read actual statediff. - let (actual_state_diff, _, _) = - transaction_executor.finalize().expect("Couldn't finalize block"); + )); // Compare the expected and actual state differences // by avoiding discrepancies caused by insertion order - assert_eq_state_diff!(expected_state_diff, actual_state_diff); println!("RPC test passed successfully."); } - Command::WriteRpcRepliesToJson { - url_and_block_number: SharedArgs { node_url, block_number }, - directory_path, - } => { - let directory_path = directory_path.unwrap_or(format!( - "./crates/blockifier_reexecution/resources/block_{block_number}/" + Command::WriteRpcRepliesToJson { node_url, block_number, full_file_path } => { + let full_file_path = full_file_path.unwrap_or(format!( + "./crates/blockifier_reexecution/resources/block_{block_number}/reexecution_data.\ + json" )); // TODO(Aner): refactor to reduce code duplication. @@ -123,87 +106,47 @@ fn main() { json_rpc_version: JSON_RPC_VERSION.to_string(), }; - let ConsecutiveTestStateReaders { last_block_state_reader, next_block_state_reader } = + let consecutive_state_readers = ConsecutiveTestStateReaders::new(BlockNumber(block_number - 1), Some(config), true); - let block_info_next_block = next_block_state_reader.get_block_info().unwrap(); - - let old_block_number = BlockNumber( - block_info_next_block.block_number.0 - constants::STORED_BLOCK_HASH_BUFFER, - ); - - let old_block_hash = - last_block_state_reader.get_old_block_hash(old_block_number).unwrap(); - - let starknet_version = next_block_state_reader.get_starknet_version().unwrap(); - - let state_diff_next_block = next_block_state_reader.get_state_diff().unwrap(); - - let transactions_next_block = next_block_state_reader.get_all_txs_in_block().unwrap(); + let serializable_data_next_block = + consecutive_state_readers.get_serializable_data_next_block().unwrap(); - let blockifier_transactions_next_block = &last_block_state_reader - .api_txs_to_blockifier_txs_next_block(transactions_next_block.clone()) - .unwrap(); + let old_block_hash = consecutive_state_readers.get_old_block_hash().unwrap(); - let mut transaction_executor = last_block_state_reader - .get_transaction_executor( - next_block_state_reader.get_block_context().unwrap(), - None, - ) - .unwrap(); - - transaction_executor.execute_txs(blockifier_transactions_next_block); - - let block_state = transaction_executor.block_state.unwrap(); - let initial_reads = block_state.get_initial_reads().unwrap(); - - let contract_class_mapping = - block_state.state.get_contract_class_mapping_dumper().unwrap(); - - let serializable_offline_reexecution_data = SerializableOfflineReexecutionData { - state_maps: initial_reads.into(), - block_info_next_block, - starknet_version, - transactions_next_block, - contract_class_mapping, - state_diff_next_block, - old_block_hash, + // Run the reexecution test and get the state maps and contract class mapping. + let block_state = reexecute_and_verify_correctness(consecutive_state_readers).unwrap(); + let serializable_data_prev_block = SerializableDataPrevBlock { + state_maps: block_state.get_initial_reads().unwrap().into(), + contract_class_mapping: block_state + .state + .get_contract_class_mapping_dumper() + .unwrap(), }; - serializable_offline_reexecution_data - .write_to_file(&directory_path, "reexecution_data.json") - .unwrap(); + // Write the reexecution data to a json file. + SerializableOfflineReexecutionData { + serializable_data_prev_block, + serializable_data_next_block, + old_block_hash, + } + .write_to_file(&full_file_path) + .unwrap(); println!( "RPC replies required for reexecuting block {block_number} written to json file." ); } - Command::ReexecuteBlock { block_number, directory_path } => { - let full_file_path = directory_path.unwrap_or(format!( - "./crates/blockifier_reexecution/resources/block_{block_number}" - )) + "/reexecution_data.json"; - - let serializable_offline_reexecution_data = - SerializableOfflineReexecutionData::read_from_file(&full_file_path).unwrap(); - - let reexecution_state_readers = - OfflineConsecutiveStateReaders::new(serializable_offline_reexecution_data.into()); - - let expected_state_diff = - reexecution_state_readers.get_next_block_state_diff().unwrap(); - - let all_txs_in_next_block = reexecution_state_readers.get_next_block_txs().unwrap(); - - let mut transaction_executor = - reexecution_state_readers.get_transaction_executor(None).unwrap(); - - transaction_executor.execute_txs(&all_txs_in_next_block); - // Finalize block and read actual statediff. - let (actual_state_diff, _, _) = - transaction_executor.finalize().expect("Couldn't finalize block"); + Command::ReexecuteBlock { block_number, full_file_path } => { + let full_file_path = full_file_path.unwrap_or(format!( + "./crates/blockifier_reexecution/resources/block_{block_number}/reexecution_data.\ + json" + )); - assert_eq!(expected_state_diff, actual_state_diff); + reexecute_and_verify_correctness( + OfflineConsecutiveStateReaders::new_from_file(&full_file_path).unwrap(), + ); println!("Reexecution test for block {block_number} passed successfully."); } diff --git a/crates/blockifier_reexecution/src/state_reader/raw_rpc_json_test.rs b/crates/blockifier_reexecution/src/state_reader/raw_rpc_json_test.rs index 0fdd100f81..f79d104685 100644 --- a/crates/blockifier_reexecution/src/state_reader/raw_rpc_json_test.rs +++ b/crates/blockifier_reexecution/src/state_reader/raw_rpc_json_test.rs @@ -19,7 +19,7 @@ use starknet_gateway::rpc_objects::BlockHeader; use crate::state_reader::compile::legacy_to_contract_class_v0; use crate::state_reader::serde_utils::deserialize_transaction_json_to_starknet_api_tx; -use crate::state_reader::utils::ReexecutionStateMaps; +use crate::state_reader::utils::{reexecute_block_for_testing, ReexecutionStateMaps}; #[fixture] fn block_header() -> BlockHeader { @@ -162,3 +162,23 @@ fn serialize_state_maps() { assert_eq!(serializable_state_maps, deserialized_state_maps); assert_eq!(original_state_maps, deserialized_state_maps.try_into().unwrap()); } + +#[rstest] +// TODO(Aner): Add block for each starknet version and for declare, deploy, replace_class, etc. +#[case::v_0_13_0(600001)] +#[case::v_0_13_1(620978)] +#[case::v_0_13_1_1(649367)] +#[case::v_0_13_2(685878)] +#[case::v_0_13_2_1(700000)] +#[case::invoke_with_replace_class_syscall(780008)] +#[case::invoke_with_deploy_syscall(870136)] +#[case::example_deploy_account_v1(837408)] +#[case::example_deploy_account_v3(837792)] +#[case::example_declare_v1(837461)] +#[case::example_declare_v2(822636)] +#[case::example_declare_v3(825013)] +#[case::example_l1_handler(868429)] +#[ignore = "Requires downloading JSON files prior to running; Long test, run with --release flag."] +fn test_block_reexecution(#[case] block_number: u64) { + reexecute_block_for_testing(block_number); +} diff --git a/crates/blockifier_reexecution/src/state_reader/test_state_reader.rs b/crates/blockifier_reexecution/src/state_reader/test_state_reader.rs index 6c7448ca8e..a77be1b7d3 100644 --- a/crates/blockifier_reexecution/src/state_reader/test_state_reader.rs +++ b/crates/blockifier_reexecution/src/state_reader/test_state_reader.rs @@ -65,29 +65,39 @@ pub struct OfflineReexecutionData { } #[derive(Serialize, Deserialize)] -pub struct SerializableOfflineReexecutionData { - pub state_maps: ReexecutionStateMaps, +pub struct SerializableDataNextBlock { pub block_info_next_block: BlockInfo, pub starknet_version: StarknetVersion, pub transactions_next_block: Vec<(Transaction, TransactionHash)>, pub state_diff_next_block: CommitmentStateDiff, +} + +#[derive(Serialize, Deserialize)] +pub struct SerializableDataPrevBlock { + pub state_maps: ReexecutionStateMaps, pub contract_class_mapping: StarknetContractClassMapping, +} + +#[derive(Serialize, Deserialize)] +pub struct SerializableOfflineReexecutionData { + pub serializable_data_prev_block: SerializableDataPrevBlock, + pub serializable_data_next_block: SerializableDataNextBlock, pub old_block_hash: BlockHash, } impl SerializableOfflineReexecutionData { - pub fn write_to_file(&self, file_path: &str, file_name: &str) -> ReexecutionResult<()> { + pub fn write_to_file(&self, full_file_path: &str) -> ReexecutionResult<()> { + let file_path = full_file_path.rsplit_once('/').expect("Invalid file path.").0; fs::create_dir_all(file_path) - .unwrap_or_else(|_| panic!("Failed to create directory {file_path}.")); - let full_file_path = file_path.to_owned() + "/" + file_name; - fs::write(full_file_path.clone(), serde_json::to_string_pretty(&self)?) - .unwrap_or_else(|_| panic!("Failed to write to file {full_file_path}.")); + .unwrap_or_else(|err| panic!("Failed to create directory {file_path}. Error: {err}")); + fs::write(full_file_path, serde_json::to_string_pretty(&self)?) + .unwrap_or_else(|err| panic!("Failed to write to file {full_file_path}. Error: {err}")); Ok(()) } pub fn read_from_file(full_file_path: &str) -> ReexecutionResult { - let file_content = fs::read_to_string(full_file_path).unwrap_or_else(|_| { - panic!("Failed to read reexecution data from file {full_file_path}.") + let file_content = fs::read_to_string(full_file_path).unwrap_or_else(|err| { + panic!("Failed to read reexecution data from file {full_file_path}. Error: {err}") }); Ok(serde_json::from_str(&file_content)?) } @@ -95,24 +105,37 @@ impl SerializableOfflineReexecutionData { impl From for OfflineReexecutionData { fn from(value: SerializableOfflineReexecutionData) -> Self { + let SerializableOfflineReexecutionData { + serializable_data_prev_block: + SerializableDataPrevBlock { state_maps, contract_class_mapping }, + serializable_data_next_block: + SerializableDataNextBlock { + block_info_next_block, + starknet_version, + transactions_next_block, + state_diff_next_block, + }, + old_block_hash, + } = value; + let offline_state_reader_prev_block = OfflineStateReader { - state_maps: value.state_maps.try_into().expect("Failed to deserialize state maps."), - contract_class_mapping: value.contract_class_mapping, - old_block_hash: value.old_block_hash, + state_maps: state_maps.try_into().expect("Failed to deserialize state maps."), + contract_class_mapping, + old_block_hash, }; let transactions_next_block = offline_state_reader_prev_block - .api_txs_to_blockifier_txs_next_block(value.transactions_next_block) + .api_txs_to_blockifier_txs_next_block(transactions_next_block) .expect("Failed to convert starknet-api transactions to blockifier transactions."); Self { offline_state_reader_prev_block, block_context_next_block: BlockContext::new( - value.block_info_next_block, + block_info_next_block, get_chain_info(), - VersionedConstants::get(&value.starknet_version).unwrap().clone(), + VersionedConstants::get(&starknet_version).unwrap().clone(), BouncerConfig::max(), ), transactions_next_block, - state_diff_next_block: value.state_diff_next_block, + state_diff_next_block, } } } @@ -136,7 +159,7 @@ impl Default for RetryConfig { } pub struct TestStateReader { - pub(crate) rpc_state_reader: RpcStateReader, + rpc_state_reader: RpcStateReader, pub(crate) retry_config: RetryConfig, #[allow(dead_code)] contract_class_mapping_dumper: Arc>>, @@ -441,6 +464,22 @@ impl ConsecutiveTestStateReaders { ), } } + + pub fn get_serializable_data_next_block(&self) -> ReexecutionResult { + Ok(SerializableDataNextBlock { + block_info_next_block: self.next_block_state_reader.get_block_info()?, + starknet_version: self.next_block_state_reader.get_starknet_version()?, + transactions_next_block: self.next_block_state_reader.get_all_txs_in_block()?, + state_diff_next_block: self.next_block_state_reader.get_state_diff()?, + }) + } + + pub fn get_old_block_hash(&self) -> ReexecutionResult { + self.last_block_state_reader.get_old_block_hash(BlockNumber( + self.next_block_state_reader.get_block_context()?.block_info().block_number.0 + - constants::STORED_BLOCK_HASH_BUFFER, + )) + } } impl ConsecutiveStateReaders for ConsecutiveTestStateReaders { diff --git a/crates/blockifier_reexecution/src/state_reader/utils.rs b/crates/blockifier_reexecution/src/state_reader/utils.rs index 11bdf0f035..81349c190b 100644 --- a/crates/blockifier_reexecution/src/state_reader/utils.rs +++ b/crates/blockifier_reexecution/src/state_reader/utils.rs @@ -1,7 +1,8 @@ use std::collections::{BTreeMap, HashMap}; use blockifier::context::{ChainInfo, FeeTokenAddresses}; -use blockifier::state::cached_state::{CommitmentStateDiff, StateMaps}; +use blockifier::state::cached_state::{CachedState, CommitmentStateDiff, StateMaps}; +use blockifier::state::state_api::StateReader; use indexmap::IndexMap; use papyrus_execution::{ETH_FEE_CONTRACT_ADDRESS, STRK_FEE_CONTRACT_ADDRESS}; use pretty_assertions::assert_eq; @@ -13,7 +14,12 @@ use starknet_api::test_utils::read_json_file; use starknet_gateway::config::RpcStateReaderConfig; use starknet_types_core::felt::Felt; +use crate::assert_eq_state_diff; use crate::state_reader::errors::ReexecutionError; +use crate::state_reader::test_state_reader::{ + ConsecutiveStateReaders, + OfflineConsecutiveStateReaders, +}; pub const RPC_NODE_URL: &str = "https://free-rpc.nethermind.io/mainnet-juno/"; pub const JSON_RPC_VERSION: &str = "2.0"; @@ -166,17 +172,47 @@ impl From for ComparableStateDiff { } } +pub fn reexecute_and_verify_correctness< + S: StateReader + Send + Sync, + T: ConsecutiveStateReaders, +>( + consecutive_state_readers: T, +) -> Option> { + let expected_state_diff = consecutive_state_readers.get_next_block_state_diff().unwrap(); + + let all_txs_in_next_block = consecutive_state_readers.get_next_block_txs().unwrap(); + + let mut transaction_executor = + consecutive_state_readers.get_transaction_executor(None).unwrap(); + + transaction_executor.execute_txs(&all_txs_in_next_block); + // Finalize block and read actual statediff. + let (actual_state_diff, _, _) = + transaction_executor.finalize().expect("Couldn't finalize block"); + + assert_eq_state_diff!(expected_state_diff, actual_state_diff); + + transaction_executor.block_state +} + +pub fn reexecute_block_for_testing(block_number: u64) { + // In tests we are already in the blockifier_reexecution directory. + let full_file_path = format!("./resources/block_{block_number}/reexecution_data.json"); + + reexecute_and_verify_correctness( + OfflineConsecutiveStateReaders::new_from_file(&full_file_path).unwrap(), + ); + + println!("Reexecution test for block {block_number} passed successfully."); +} + /// Asserts equality between two `CommitmentStateDiff` structs, ignoring insertion order. #[macro_export] macro_rules! assert_eq_state_diff { ($expected_state_diff:expr, $actual_state_diff:expr $(,)?) => { pretty_assertions::assert_eq!( - blockifier_reexecution::state_reader::utils::ComparableStateDiff::from( - $expected_state_diff, - ), - blockifier_reexecution::state_reader::utils::ComparableStateDiff::from( - $actual_state_diff, - ), + $crate::state_reader::utils::ComparableStateDiff::from($expected_state_diff,), + $crate::state_reader::utils::ComparableStateDiff::from($actual_state_diff,), "Expected and actual state diffs do not match.", ); };