Skip to content

Commit

Permalink
chore(blockifier_reexecution): refactor cli to reduce code duplication (
Browse files Browse the repository at this point in the history
#1814)

* chore(blockifier_reexecution): refactor cli to reduce code duplication

* chore(blockifier_reexecution): explicitly state command args

* chore(blockifier_reexecution): separate old block hash

* chore(blockifier_reexecution): move reexecution to seperate function

* test(blockifier_reexecution): reexecution test on blocks
  • Loading branch information
aner-starkware committed Nov 7, 2024
1 parent 7012888 commit a37cf42
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 135 deletions.
167 changes: 54 additions & 113 deletions crates/blockifier_reexecution/src/main.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -23,34 +23,33 @@ 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)]
/// 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 get files from gc bucket.
#[clap(long, short = 'd', default_value = None)]
directory_path: Option<String>,
},

Expand All @@ -60,9 +59,10 @@ 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 to json files. Default:
// "./crates/blockifier_reexecution/resources/block_{block_number}".
// TODO(Aner): add possibility to get files from gc bucket.
#[clap(long, short = 'd', default_value = None)]
directory_path: Option<String>,
},
}
Expand All @@ -75,44 +75,26 @@ 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 {
url: node_url,
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,
} => {
Command::WriteRpcRepliesToJson { node_url, block_number, directory_path } => {
let directory_path = directory_path.unwrap_or(format!(
"./crates/blockifier_reexecution/resources/block_{block_number}/"
));
Expand All @@ -123,56 +105,32 @@ 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 serializable_data_next_block =
consecutive_state_readers.get_serializable_data_next_block().unwrap();

let old_block_number = BlockNumber(
block_info_next_block.block_number.0 - constants::STORED_BLOCK_HASH_BUFFER,
);
let old_block_hash = consecutive_state_readers.get_old_block_hash().unwrap();

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 blockifier_transactions_next_block = &last_block_state_reader
.api_txs_to_blockifier_txs_next_block(transactions_next_block.clone())
.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(&directory_path, "reexecution_data.json")
.unwrap();

println!(
"RPC replies required for reexecuting block {block_number} written to json file."
Expand All @@ -184,26 +142,9 @@ fn main() {
"./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");

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.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
67 changes: 53 additions & 14 deletions crates/blockifier_reexecution/src/state_reader/test_state_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,54 +65,77 @@ 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<()> {
fs::create_dir_all(file_path)
.unwrap_or_else(|_| panic!("Failed to create directory {file_path}."));
.unwrap_or_else(|err| panic!("Failed to create directory {file_path}. Error: {err}"));
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 write to file {full_file_path}. Error: {err}"));
Ok(())
}

pub fn read_from_file(full_file_path: &str) -> ReexecutionResult<Self> {
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)?)
}
}

impl From<SerializableOfflineReexecutionData> 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,
}
}
}
Expand All @@ -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<Mutex<Option<StarknetContractClassMapping>>>,
Expand Down Expand Up @@ -441,6 +464,22 @@ impl ConsecutiveTestStateReaders {
),
}
}

pub fn get_serializable_data_next_block(&self) -> ReexecutionResult<SerializableDataNextBlock> {
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<BlockHash> {
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<TestStateReader> for ConsecutiveTestStateReaders {
Expand Down
Loading

0 comments on commit a37cf42

Please sign in to comment.