Skip to content

Commit

Permalink
Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
Eagle941 committed Jul 14, 2024
1 parent e30528e commit db372ee
Show file tree
Hide file tree
Showing 27 changed files with 2,367 additions and 3,390 deletions.
3,850 changes: 896 additions & 2,954 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
]

[workspace.package]
# Edition 2021 is chosen to support async.
edition = "2021"
# Version chosen only because 1.78.0 is the stable version when writing this Cargo.toml
# If this version is changed, amend the version set in `build.yml`
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ In the future, this tool is likely to evolve to support:
## How to Use

```bash
cargo run --release -- --db-path <PATHFINDER_DB> --start-block <BLOCK_NUM> --end-block <BLOCK_NUM>
cargo run --release -- --rpc-url <STARKNET_JSONRPC_ENDPOINT> --start-block <BLOCK_NUM> --end-block <BLOCK_NUM>
```

`PATHFINDER_DB` is the path of the Pathfinder sqlite database. The Pathfinder
Expand All @@ -42,7 +42,7 @@ This tool makes use of `tracing` library for log purposes. For this reason set
## Example

```bash
cargo run -- --db-path ../pathfinder/mainnet.sqlite --start-block 632917 --end-block 632917 --svg-out "histogram.svg"
cargo run --release -- --rpc-url https://starknet-mainnet.public.blastapi.io/rpc/v0_7 --start-block 632917 --end-block 632917 --svg-out "histogram.svg"
```

The command above replays all transactions of block
Expand Down
1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ tracing-subscriber = { version = "0.3.17", features = [
"ansi",
] }
exitcode = "1.1.2"
url = "2.5.2"
anyhow.workspace = true
tracing.workspace = true
itertools.workspace = true
3 changes: 2 additions & 1 deletion cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
use std::path::PathBuf;

use clap::Parser;
use url::Url;

#[derive(Clone, Parser, Debug)]
pub struct Args {
/// The path of the Pathfinder database file.
#[arg(long)]
pub db_path: PathBuf,
pub rpc_url: Url,

/// The starting block to replay transactions.
#[arg(long)]
Expand Down
8 changes: 4 additions & 4 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use starknet_replay::profiler::analysis::extract_libfuncs_weight;
use starknet_replay::profiler::report::write_to_file;
use starknet_replay::runner::replay_range::ReplayRange;
use starknet_replay::runner::run_replay;
use starknet_replay::storage::pathfinder::PathfinderStorage;
use starknet_replay::storage::rpc::RpcStorage;

use crate::args::Args;

Expand Down Expand Up @@ -90,7 +90,7 @@ fn check_file(path: &Option<PathBuf>, overwrite: bool) -> anyhow::Result<()> {
/// replay.
/// - Any error during execution of the replayer..
fn run(args: Args) -> anyhow::Result<()> {
let database_path = args.db_path;
let rpc_url = args.rpc_url;
let start_block = args.start_block;
let end_block = args.end_block;
let svg_path = args.svg_out;
Expand All @@ -102,14 +102,14 @@ fn run(args: Args) -> anyhow::Result<()> {
check_file(&txt_out, overwrite)?;
check_file(&trace_out, overwrite)?;

let storage = PathfinderStorage::new(database_path)?;
let storage = RpcStorage::new(rpc_url)?;

let replay_range = ReplayRange::new(start_block, end_block)?;

tracing::info!(%start_block, %end_block, "Re-executing blocks");
let start_time = std::time::Instant::now();

let visited_pcs = run_replay(&replay_range, &trace_out, &storage.clone())?;
let visited_pcs = run_replay(&replay_range, &trace_out, &storage)?;

let elapsed = start_time.elapsed();
tracing::info!(?elapsed, "Finished");
Expand Down
3 changes: 3 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Ensure this is matching `Cargo.toml`.
edition = "2021"

# Better readability
format_code_in_doc_comments = true
wrap_comments = true
Expand Down
22 changes: 15 additions & 7 deletions starknet-replay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,37 @@ cairo-lang-compiler = "2.6.0"
cairo-lang-sierra = "2.6.0"
cairo-lang-utils = "2.6.0"
cairo-lang-sierra-generator = "2.6.0"
# Some changes are required to `eqlabs/pathfinder` and to
# `starkware-libs/blockifier`. These changes are in the branch `extract_libfunc`
# of Reilabs fork and need to be merged in main branch.
# Some changes are required to `starkware-libs/blockifier`. These changes are
# in the branch `extract_libfunc` of Reilabs fork and need to be merged
# in main branch.
# Hardcoding the commit hash for the time being.
pathfinder-common = { git = "https:/reilabs/pathfinder.git", rev = "fdeb5b0d1747d6396e5b0774140026a3b1beae2f" }
pathfinder-executor = { git = "https:/reilabs/pathfinder.git", rev = "fdeb5b0d1747d6396e5b0774140026a3b1beae2f" }
pathfinder-rpc = { git = "https:/reilabs/pathfinder.git", rev = "fdeb5b0d1747d6396e5b0774140026a3b1beae2f" }
pathfinder-storage = { git = "https:/reilabs/pathfinder.git", rev = "fdeb5b0d1747d6396e5b0774140026a3b1beae2f" }
blockifier = { git = "https:/reilabs/blockifier.git", rev = "fdb86caf85435cb42ae363c7b04b5620bc80c24a" }
# `plotters` is using the latest (as of 30-May-2024) commit on the branch
# `next-release-devel` because it contains the fix for bug #551 related to
# anchoring of labels when rotated. Issue #26.
plotters = { git = "https:/plotters-rs/plotters.git", rev = "a7a3f8989af20931dd9e7e1f204d5254de3a8053" }
flate2 = "1.0.25"
rayon = "1.8.0"
starknet_api = "0.10.0"
starknet = "0.5.0"
serde = "1.0.192"
serde_json = "1.0.105"
serde_with = "3.0.0"
smol_str = { version = "0.2.0", features = ["serde"] }
thiserror = "1.0.61"
jsonrpc = { version = "0.18.0", features = ["minreq_http"] }
url = "2.5.2"
hex = "0.4.3"
anyhow.workspace = true
tracing.workspace = true
itertools.workspace = true

[dependencies.minreq]
version = "2.11.2"
features = ["https"]

[dev-dependencies]
indoc = "2.0.5"
rand = "0.8.4"
rand_chacha = "0.3.1"
test-log = { version = "0.2.16", features = ["trace"] }
2 changes: 0 additions & 2 deletions starknet-replay/src/block_number/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ use std::fmt;

use serde::{Deserialize, Serialize};

pub mod pathfinder;

/// `BlockNumber` is represented as a `u64` integer.
#[derive(
Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
Expand Down
18 changes: 0 additions & 18 deletions starknet-replay/src/block_number/pathfinder/mod.rs

This file was deleted.

49 changes: 40 additions & 9 deletions starknet-replay/src/error/database.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
//! This file contains the enum `Error` for all the errors returned by the
//! module `pathfinder_db`.

use pathfinder_storage::BlockId;
use std::num::TryFromIntError;
use std::str::Utf8Error;

use hex::FromHexError;
use starknet_api::hash::StarkFelt;
use thiserror::Error;

use crate::block_number::BlockNumber;

#[derive(Debug, Error)]
pub enum Error {
/// `ConnectToDatabase` is used to encapsulate errors of type
Expand All @@ -13,11 +18,6 @@ pub enum Error {
#[error(transparent)]
ConnectToDatabase(anyhow::Error),

/// `BlockNotFound` variant is returned when the block requested from the
/// Pathfinder database isn't found.
#[error("Block number {block_id:?} not found in database.")]
BlockNotFound { block_id: BlockId },

/// `GetLatestBlockNumber` is used to encapsulate errors of type
/// [`anyhow::Error`] which are originating from the
/// function [`crate::storage::pathfinder::PathfinderStorage#method.
Expand Down Expand Up @@ -51,24 +51,55 @@ pub enum Error {
/// [`crate::storage::pathfinder::PathfinderStorage#method.
/// get_transactions_and_receipts_for_block`].
#[error("Transactions for block {block_id:?} not found.")]
GetTransactionsAndReceiptsNotFound { block_id: BlockId },
GetTransactionsAndReceiptsNotFound { block_id: BlockNumber },

/// `ContractClassNotFound` is used for `None` results from the database in
/// the function
/// [`crate::storage::pathfinder::PathfinderStorage#method.
/// get_contract_class_at_block`].
#[error("Contract Class {class_hash:?} not found in Database at block {block_id:?}.")]
ContractClassNotFound {
block_id: BlockId,
block_id: BlockNumber,
class_hash: StarkFelt,
},

/// `MinReq` is used for errors when creating a new
/// [`crate::storage::rpc::RpcStorage`].
#[error(transparent)]
MinReq(#[from] jsonrpc::minreq_http::Error),

/// `JsonRpc` is used for errors parsing RPC responses.
#[error(transparent)]
JsonRpc(#[from] jsonrpc::Error),

/// `Decompress` variant is for errors reported when decompressing a
/// [`starknet::core::types::CompressedLegacyContractClass`] fails.
#[error(transparent)]
Decompress(#[from] std::io::Error),

/// `Serde` variant is for errors reported by the crate [`serde_json`].
#[error(transparent)]
Serde(#[from] serde_json::Error),

/// `Starknet` variant is for errors reported by the crate [`starknet_api`]
#[error(transparent)]
Starknet(#[from] starknet_api::StarknetApiError),

/// `GetChainId` is used to encapsulate errors of type [`anyhow::Error`]
/// which are originating from the function
/// [`crate::storage::pathfinder::PathfinderStorage#method.
/// get_chain_id`].
#[error(transparent)]
GetChainId(anyhow::Error),
DecodeHex(#[from] FromHexError),

#[error("Chain id returned from RPC endpoint is not valid hex number.")]
InvalidHex(),

#[error(transparent)]
DecodeBytes(#[from] Utf8Error),

#[error(transparent)]
BlockInfo(#[from] TryFromIntError),

/// The `Unknown` variant is for any other uncategorised error.
#[error("Unknown Error communicating with Pathfinder database: {0:?}")]
Expand Down
16 changes: 10 additions & 6 deletions starknet-replay/src/error/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@

use std::num::TryFromIntError;

use pathfinder_executor::TransactionExecutionError;
use cairo_lang_starknet_classes::casm_contract_class::StarknetSierraCompilationError;
use thiserror::Error;

use crate::block_number::BlockNumber;
use crate::error::DatabaseError;

#[derive(Debug, Error)]
pub enum Error {
/// `PathfinderExecutor` is for errors reported by the crate
/// [`pathfinder_executor`].
#[error(transparent)]
PathfinderExecutor(#[from] TransactionExecutionError),

/// `GenerateReplayWork` is used to encapsulate errors of type
/// [`anyhow::Error`] which are originating from the function
/// [`crate::runner::generate_replay_work`].
Expand Down Expand Up @@ -69,6 +64,15 @@ pub enum Error {
#[error(transparent)]
Serde(#[from] serde_json::Error),

/// `SierraCompiler` variant is for errors reported when compiling Sierra
/// contracts to CASM.
#[error(transparent)]
SierraCompiler(#[from] StarknetSierraCompilationError),

/// The `Unknown` variant is for any other uncategorised error.
#[error("Error converting {0:?} into {1:?}")]
IntoError(String, String),

/// The `Unknown` variant is for any other uncategorised error.
#[error("Unknown Error during block replay: {0:?}")]
Unknown(String),
Expand Down
62 changes: 33 additions & 29 deletions starknet-replay/src/profiler/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,48 @@ use cairo_lang_sierra::program::Program;
use cairo_lang_starknet_classes::contract_class::ContractClass as CairoContractClass;
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use itertools::Itertools;
use pathfinder_rpc::v02::types::{ContractClass, SierraContractClass};
use starknet::core::types::ContractClass;

use crate::profiler::replace_ids::replace_sierra_ids_in_program;
use crate::profiler::replay_statistics::ReplayStatistics;
use crate::profiler::{ProfilerError, SierraProfiler};
use crate::runner::replay_class_hash::VisitedPcs;
use crate::storage::Storage;

/// Converts transforms a [`pathfinder_rpc::v02::types::SierraContractClass`] in
/// Sierra [`cairo_lang_sierra::program::Program`].
/// Converts transforms a [`starknet::core::types::ContractClass`] in Sierra
/// [`cairo_lang_sierra::program::Program`].
///
/// # Arguments
///
/// - `ctx`: The input [`pathfinder_rpc::v02::types::SierraContractClass`]
/// - `ctx`: The input [`starknet::core::types::ContractClass`]
///
/// # Errors
///
/// Returns [`Err`] if there is a serde deserialisation issue.
fn get_sierra_program_from_class_definition(
ctx: &SierraContractClass,
) -> Result<Program, ProfilerError> {
let json = serde_json::json!({
"abi": [],
"sierra_program": ctx.sierra_program,
"contract_class_version": ctx.contract_class_version,
"entry_points_by_type": ctx.entry_points_by_type,
});
let contract_class: CairoContractClass = serde_json::from_value::<CairoContractClass>(json)?;
// TODO: `extract_sierra_program` returns an error of type `Felt252SerdeError`
// which is private. For ease of integration with `thiserror`, it needs to be
// made public. Issue #20
let sierra_program = contract_class.extract_sierra_program().map_err(|_| {
ProfilerError::Unknown("Error extracting sierra program".to_string().to_string())
})?;
let sierra_program = replace_sierra_ids_in_program(&sierra_program);
Ok(sierra_program)
fn get_sierra_program_from_class_definition(ctx: &ContractClass) -> Result<Program, ProfilerError> {
match ctx {
ContractClass::Sierra(ctx) => {
let mut json = serde_json::to_value(ctx)?;
json.as_object_mut()
.ok_or(ProfilerError::Unknown(
"Failed serialising `ContractClass`.".to_string(),
))?
.remove("abi");
let contract_class: CairoContractClass =
serde_json::from_value::<CairoContractClass>(json)?;
// TODO: `extract_sierra_program` returns an error of type `Felt252SerdeError`
// which is private. For ease of integration with `thiserror`, it needs to be
// made public. Issue #20
let sierra_program = contract_class.extract_sierra_program().map_err(|_| {
ProfilerError::Unknown("Error extracting sierra program".to_string().to_string())
})?;
let sierra_program = replace_sierra_ids_in_program(&sierra_program);
Ok(sierra_program)
}
ContractClass::Legacy(_) => {
Err(ProfilerError::Unknown("Not a Sierra contract.".to_string()))
}
}
}

/// Constructs the default configuration for the profiler.
Expand Down Expand Up @@ -89,9 +95,7 @@ pub fn extract_libfuncs_weight(
let mut local_cumulative_libfuncs_weight: ReplayStatistics = ReplayStatistics::new();

for (replay_class_hash, all_pcs) in visited_pcs {
let Ok(ContractClass::Sierra(contract_class)) =
storage.get_contract_class_at_block(replay_class_hash)
else {
let Ok(contract_class) = storage.get_contract_class_at_block(replay_class_hash) else {
continue;
};

Expand Down Expand Up @@ -165,10 +169,10 @@ mod tests {
.unwrap_or_else(|_| panic!("Unable to read file {sierra_program_json_file}"));
let sierra_program_json: serde_json::Value = serde_json::from_str(&sierra_program_json)
.unwrap_or_else(|_| panic!("Unable to parse {sierra_program_json_file} to json"));
let contract_class: SierraContractClass =
serde_json::from_value::<SierraContractClass>(sierra_program_json).unwrap_or_else(
|_| panic!("Unable to parse {sierra_program_json_file} to SierraContractClass"),
);
let contract_class: ContractClass =
serde_json::from_value::<ContractClass>(sierra_program_json).unwrap_or_else(|_| {
panic!("Unable to parse {sierra_program_json_file} to SierraContractClass")
});
let sierra_program = get_sierra_program_from_class_definition(&contract_class)
.unwrap_or_else(|_| {
panic!("Unable to create Program {sierra_program_json_file} to SierraContractClass")
Expand Down
Loading

0 comments on commit db372ee

Please sign in to comment.