https://github.com/lambdaclass/starknet-replay
Provides a way of reading a real Starknet State, so you can re-execute an existing transaction in any of the Starknet networks in an easy way
https://github.com/lambdaclass/starknet-replay
Last synced: 10 months ago
JSON representation
Provides a way of reading a real Starknet State, so you can re-execute an existing transaction in any of the Starknet networks in an easy way
- Host: GitHub
- URL: https://github.com/lambdaclass/starknet-replay
- Owner: lambdaclass
- License: apache-2.0
- Created: 2024-06-03T15:20:17.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2025-08-22T23:32:22.000Z (10 months ago)
- Last Synced: 2025-08-23T00:39:38.535Z (10 months ago)
- Language: Rust
- Size: 120 MB
- Stars: 9
- Watchers: 1
- Forks: 1
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Codeowners: CODEOWNERS
Awesome Lists containing this project
README
# starknet-replay
Provides a way of reading a real Starknet State, so you can re-execute an existing transaction in any of the Starknet networks in an easy way
## Getting Started
### Prerequisites
- Linux or macOS (aarch64 included) only for now
- LLVM 19 with MLIR
- Rust 1.78.0 or later, since cairo-native makes use of the u128 abi change.
- Git
### Setup
Run the following make target to install dependencies:
```bash
make deps
```
It will automatically install LLVM 19 with MLIR on macos, if you are using linux you must do it manually. On debian, you can use `apt.llvm.org`, or build it from source.
This project is integrated with Cairo Native, see [Cairo Native Setup](#cairo-native-setup) to set it up correctly
Some environment variable are needed, you can automatically set them by sourcing `env.sh`. If the script doesn't adjust to your specific environment you can `cp` it into `.env` or `.envrc` and modify it.
```bash
# Cairo Native
export LLVM_SYS_191_PREFIX=/path/to/llvm-19
export MLIR_SYS_190_PREFIX=/path/to/llvm-19
export TABLEGEN_190_PREFIX=/path/to/llvm-19
# RPC
export RPC_ENDPOINT_MAINNET=rpc.endpoint.mainnet.com
export RPC_ENDPOINT_TESTNET=rpc.endpoint.testnet.com
```
On macos, you may also need to set the following to avoid linking errors:
```bash
export LIBRARY_PATH=/opt/homebrew/lib
```
Once you have installed dependencies and set the needed environment variables, you can build the project and run the tests:
```bash
make build
make test
```
### Cairo Native Setup
Starknet Replay is currenlty integrated with [Cairo Native](https://github.com/lambdaclass/cairo_native), which makes the execution of sierra programs possible through native machine code. To use it, the following needs to be setup:
- On mac with brew, running `make deps` should have installed LLVM 19 with MLIR, otherwise, you must install it manually. On Debian, you can use `apt.llvm.org`, or build it from source.
- The `LLVM_SYS_191_PREFIX`, `MLIR_SYS_190_PREFIX` and `TABLEGEN_190_PREFIX` environment variable needs to point to said installation. In macOS, run:
```
export LLVM_SYS_190_PREFIX=/opt/homebrew/opt/llvm@19
export MLIR_SYS_191_PREFIX=/opt/homebrew/opt/llvm@19
export TABLEGEN_190_PREFIX=/opt/homebrew/opt/llvm@19
```
and you're set.
Afterwards, compiling with the feature flag `cairo-native` will enable native execution. You can check out some example test code that uses it under `tests/cairo_native.rs`.
#### Using ahead of time compilation with Native.
## replay
You can use the replay crate to execute transactions or blocks via the CLI. For example:
```bash
* cargo run tx 0x04ba569a40a866fd1cbb2f3d3ba37ef68fb91267a4931a377d6acc6e5a854f9a mainnet 648461
* cargo run block mainnet 648655
* cargo run block-range 90000 90002 mainnet
* cargo run block-txs mainnet 633538 0x021c594980fc2503b2e62a1bb9ce811e7ae22c1478fb0602146745edc9d03bb6 0x13e148692edfbbb4de5d983c6875780e2397e34a432a7355bf2172435ecec0e
```
> [!IMPORTANT]
> Compiled contracts are cached to disk at `./cache/native/` directory. This saves time when reexecuting transactions, but can also cause errors if you try to run a contract that was compiled with a different Cairo Native version.
>
> Make sure to remove the directory every time you update the Cairo Native version. Running `make clean` will automatically remove it.
### RPC Timeout
The RPC time out is handled in two different ways:
- RPC request timeout (in seconds): How many seconds to wait before generating a timeout. By default, the RPC timeout is set to 90 seconds. However, using the env var `RPC_TIMEOUT` this value can be customized.
- RPC request retry: How many times the request is re-sent before failing due to timeout. By default, every RPC request is retried 10 times. However, this limit can be customized by setting the `RPC_RETRY_LIMIT` env var.
By setting both env vars, if any RPC request fails with a timeout, starknet-replay will retry sending the request with a limit of `RPC_RETRY_LIMIT` times awaiting `RPC_TIMEOUT` seconds for the response before generating another timeout.
An exponential-backoff algorithm distributes the retries in time, reducing the amount of simultaneous RPC requests to avoid new timeouts. If the limit of retries is reached, a new request timeout will cease the retrail process and return an timeout error.
### Benchmarks
To run benchmarks with the replay crate, you can use either `bench-block-range` or `bench-tx` commands. These make sure to cache all needed information (including cairo native compilation) before the actual execution. To use it you must compile the binary under the benchmark flag.
```bash
* cargo run --features benchmark bench-tx 0x04ba569a40a866fd1cbb2f3d3ba37ef68fb91267a4931a377d6acc6e5a854f9a mainnet 648461 1
* cargo run --features benchmark bench-block-range 90000 90002 mainnet 1
```
These commands are like `tx` and `block-range` commands, but with the number of runs to execute as their last argument.
### Logging
This projects uses tracing with env-filter, so logging can be modified by the RUST_LOG environment variable. By default, only info events from the replay crate are shown.
As an example, to show only error messages from the replay crate, run:
```bash
RUST_LOG=replay=error cargo run block mainnet 648461
```
### Comparing with VM
To compare Native execution with the VM, you can use the `state_dump` feature. It will save to disk the execution info and state diff of every contract executed.
- If executing Native, the dumps will be saved at: `state_dumps/native/block{block_number}/{tx_hash}.json`
- If paired with `only_cairo_vm` feature, the dumps will be saved at: `state_dumps/vm/block{block_number}/{tx_hash}.json`
To compare the outputs, you can use the following scripts. Some of them required `delta` (modern diff).
- `cmp_state_dumps.py`. Prints which transactions match with the VM and which differ.
```bash
> python3 ./scripts/cmp_state_dumps.py
Starting comparison with 16 workers
DIFF 1478358 0xde8db1dc28c7ab48192d9aad1d5c8b08e732738f12b9945f591caa48e4dfa0
Finished comparison
MATCH 9
DIFF 1
```
- `delta_state_dumps.sh`. It opens delta to review the differences between VM and Native with each transaction.
```bash
> ./scripts/delta_state_dumps.sh
```
### Replaying isolated calls
The replay crate supports executing isolated calls inside of a transaction, although it probably won't work in every scenario.
First, obtain the full state dump of a transaction:
```bash
cargo run --features state_dump -- tx \
0x01368e23fc6ba5eaf064b9e64f5cddda0c6d565b6f64cb8f036e0d1928a99c79 mainnet 1000000
```
Then, extract the desired call (by its call index). In this case, I will try to re-execute starting from the third call (that is, with index 2).
```bash
./scripts/extract_call.py \
state_dumps/native/block1000000/0x01368e23fc6ba5eaf064b9e64f5cddda0c6d565b6f64cb8f036e0d1928a99c79.json \
2 > call.json
```
Finally, re-execute it with the `call` command.
```bash
cargo run -- call call.json \
0x01368e23fc6ba5eaf064b9e64f5cddda0c6d565b6f64cb8f036e0d1928a99c79 1000000 mainnet
```
The `state_dump` feature can be used to save the execution result to either
- `call_state_dumps/native/{tx_hash}.json`
- `call_state_dumps/vm/{tx_hash}.json`
### Benchmarking
First, build the benchmarking binaries.
```bash
make deps-bench
```
Then, you can benchmark a single transaction by running:
```bash
./scripts/benchmark_tx.sh
```
If you want to benchmark a full block, you could run:
```bash
./scripts/benchmark_block.sh
```
If you just want to benchmarks a few different sample transactions, run:
```bash
./scripts/benchmark_txs.sh
```
This generates the following files in the `bench_data` directory:
- `{native,vm}-data-*.json` - execution time of each contract call.
- `{native,vm}-logs-*.json` - stdout from running the benchmark.
At the end of the run, you can generate a report by executing:
``` bash
python plotting/plot_execution_time.py native-data vm-data --output-dir --no-display
```
The report will be generated to `/report.html`
### Benchmarking Compilation
You can benchmark the compilation of a block range by running:
```bash
./scripts/benchmark_compilation.sh
```
This will save compilation data to `./bench_data/compilation---.json`.
At the end of the run, you can generate a report by running:
``` bash
python plotting/plot_compilation_stats.py --output-dir --no-display
```
The report will be generated to `/report.html`
## Block Composition
You can check the average of txs, swaps, transfers (the last two in %) inside an average block, separeted by the day of execution. The results
will be saved in a json file inside the floder `block_composition` as a vector of block execution where each of the is entrypoint call tree.
To generate the need information run this command:
`cargo run --release -F block-composition block-compose `
## Libfunc Profiling
You can gather information about each libfunc execution in a transaction. To do so, run this command:
`cargo run --release -F with-libfunc-profiling block-range `
This will create a `libfunc_profiles/block/.json` for every transaction executed, containing a list of entrypoints executed. Every entrypoint of that list contains a `profile_summary`, which contains information about the execution of every libfunc. An example of a profile would be:
```json
{
"block_number": 641561,
"tx": "0x2e0abd9a260095622f71ff8869aaee0267af1199be78ad5ad91a3c83df0ad08",
"entrypoints": [
{
"class_hash": "0x36078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f",
"selector": "0x162da33a4585851fe8d3af3c2a9c60b557814e221e0d4f30ff0b2189d9c7775",
"profile_summary": [
{
"libfunc_name": "struct_construct",
"samples": 1,
"total_time": 1,
"average_time": 1.0,
"std_deviation": 0.0,
"quartiles": [
1,
1,
1,
1,
1
]
},
...
]
},
...
]
}
```
## Plotting
In the `plotting` directory, you can find python scripts to plot relevant information.
To run them, you must first execute the benchmarks to obtain both the execution data and the execution logs.
- `python ./plotting/plot_execution_time.py native-data vm-data`: Plots the benchmark data of Native and VM.
- `python ./plotting/plot_compilation_logs.py native-logs`: Plots the compilation logs for Native and VM.
- `python ./plotting/plot_compilation_stats.py *.json`: Plots the compilation stats for Native.
- `python ./plotting/plot_block_composition.py native-logs`: Average of txs, swaps, transfers inside an average block, separeted by the day of execution.