diff --git a/.gitignore b/.gitignore index 61a1546..6ef3332 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ compiled_programs/ state_dumps/ rpc_cache bench_data +block_composition/ diff --git a/Cargo.lock b/Cargo.lock index 5f89573..893358e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -852,9 +852,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-slice-cast" @@ -1650,8 +1650,10 @@ checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.52.6", ] @@ -1678,9 +1680,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -1688,9 +1690,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -1900,9 +1902,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -1943,9 +1945,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-bigint" @@ -3093,10 +3095,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + [[package]] name = "gimli" version = "0.31.1" @@ -3463,9 +3477,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -3505,9 +3519,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -3559,7 +3573,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "native-tls", "tokio", @@ -3578,7 +3592,7 @@ dependencies = [ "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.2", + "hyper 1.6.0", "pin-project-lite", "socket2", "tokio", @@ -4320,7 +4334,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom", + "getrandom 0.2.15", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -4402,7 +4416,7 @@ dependencies = [ "fnv", "futures", "futures-ticker", - "getrandom", + "getrandom 0.2.15", "hex_fmt", "instant", "libp2p-core", @@ -4738,7 +4752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -4808,9 +4822,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -5033,9 +5047,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" dependencies = [ "bitflags 2.8.0", "cfg-if", @@ -5059,9 +5073,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" @@ -5642,7 +5656,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.22", + "toml_edit 0.22.23", ] [[package]] @@ -5802,7 +5816,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -5855,7 +5869,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror 1.0.69", ] @@ -5922,6 +5936,7 @@ version = "0.1.0" dependencies = [ "anyhow", "blockifier", + "chrono", "clap", "dotenvy", "rpc-state-reader", @@ -5992,7 +6007,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -6079,7 +6094,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -6268,9 +6283,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.43" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.8.0", "errno", @@ -6293,9 +6308,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.21" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "log", "once_cell", @@ -6338,9 +6353,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -6382,9 +6397,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "salsa20" @@ -6581,9 +6596,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -6937,9 +6952,9 @@ dependencies = [ [[package]] name = "sprs" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704ef26d974e8a452313ed629828cd9d4e4fa34667ca1ad9d6b1fffa43c6e166" +checksum = "8bff8419009a08f6cb7519a602c5590241fbff1446bcc823c07af15386eb801b" dependencies = [ "ndarray", "num-complex", @@ -7111,7 +7126,7 @@ dependencies = [ "ark-ff 0.4.2", "bigdecimal", "crypto-bigint", - "getrandom", + "getrandom 0.2.15", "hex", "num-bigint", "serde", @@ -7555,13 +7570,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", - "getrandom", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -7855,7 +7870,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.22", + "toml_edit 0.22.23", ] [[package]] @@ -7880,15 +7895,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.24", + "winnow 0.7.0", ] [[package]] @@ -8113,9 +8128,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -8184,12 +8199,12 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.23.21", + "rustls 0.23.22", "rustls-pki-types", "serde", "serde_json", "url", - "webpki-roots 0.26.7", + "webpki-roots 0.26.8", ] [[package]] @@ -8245,7 +8260,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom", + "getrandom 0.2.15", "serde", ] @@ -8337,6 +8352,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -8449,9 +8473,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] @@ -8685,9 +8709,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.24" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419" dependencies = [ "memchr", ] @@ -8702,6 +8726,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/README.md b/README.md index 9bdd1ee..d87ce2d 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ If you just want to benchmarks a few different sample transactions, run: ./scripts/benchmark_txs.sh ``` -This generates the following files in the `bench_data` directory: +This generates the following files in the `bench_data` directory: - `{native,vm}-data-*.json` - execution time of each contract call. - `{native,vm}-data-*.json` - stdout from running the benchmark. @@ -165,6 +165,13 @@ Additionally, the benchmarking scripts also run `plot_execution_time.py`, genera - `plot-*-speedup.svg` - violin plot for the speedup by contract class - `plot-*.csv` - raw csv preprocessed data +## 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 ` + ## Plotting In the `plotting` directory, you can find python scripts to plot relevant information. @@ -178,4 +185,4 @@ To run them, you must first execute the benchmarks to obtain both the execution - `python ./plotting/plot_compilation_time.py native-logs`: Native compilation time, by contract class - `python ./plotting/plot_compilation_time_trend.py native-logs`: Native and Casm compilation time, by the sierra contract size. - `python ./plotting/plot_compilation_time_finer.py native-logs`: Native compilation time, with fine-grained stage separation, by contract class. - +- `python ./plotting/plot_block_composition.py native-logs`: Average of txs, swaps, transfers inside an average block, separeted by the day of execution. diff --git a/plotting/plot_block_composition.py b/plotting/plot_block_composition.py new file mode 100644 index 0000000..134be48 --- /dev/null +++ b/plotting/plot_block_composition.py @@ -0,0 +1,200 @@ +from argparse import ArgumentParser +import os +import pandas as pd +import seaborn as sns +import matplotlib.pyplot as plt +from utils import flatmap +import json + +TRANSFER_ENTRYPOINT_HASH = ( + '0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e' +) +SWAP_ENTRYPOINT_HASHES = [ + # SWAP_ENTRYPOINT_HASH + '0x15543c3708653cda9d418b4ccd3be11368e40636c10c44b18cfe756b6d88b29', + # SWAP_EXACT_TOKEN_TO_ENTRYPOINT_HASH + '0xe9f3b52dc560050c4c679481500c1b1e2ba7496b6a0831638c1acaedcbc6ac', + # MULTI_ROUTE_SWAP_ENTRYPOINT_HASH + '0x1171593aa5bdadda4d6b0efde6cc94ee7649c3163d5efeb19da6c16d63a2a63', + # SWAP_EXACT_TOKENS_FOR_TOKENS + '0x3276861cf5e05d6daf8f352cabb47df623eb10c383ab742fcc7abea94d5c5cc', + # SWAP_EXACT_TOKENS_FOR_TOKENS + '0x2c0f7bf2d6cf5304c29171bf493feb222fef84bdaf17805a6574b0c2e8bcc87', +] + + +argument_parser = ArgumentParser('Block composition') +argument_parser.add_argument('block_execution_info') +arguments = argument_parser.parse_args() + + +def count_transfers(transactions): + count = 0 + + for tx in transactions: + if tx == None: + continue + + # in general, a pure transfer is made of two entrypoints: __execute__, transfer + if ( + tx['execute_call_info'] != None + and len(tx['execute_call_info']) <= 2 + ): + for entrypoint in tx['execute_call_info']: + if entrypoint['selector'] == TRANSFER_ENTRYPOINT_HASH: + count += 1 + + return count + + +def count_transfers_ptg(transactions): + tx_len = count_tx(transactions) + return count_transfers(transactions) / tx_len * 100 if tx_len > 0 else 0 + + +def count_swaps(transactions): + def is_swap(entrypoint): + return entrypoint['selector'] in SWAP_ENTRYPOINT_HASHES + + count = 0 + + for tx in transactions: + if tx == None: + continue + + if tx['execute_call_info'] != None and any( + is_swap(entrypoint) for entrypoint in tx['execute_call_info'] + ): + count += 1 + + return count + + +def count_swaps_ptg(transactions): + tx_len = count_tx(transactions) + return count_swaps(transactions) / tx_len * 100 if tx_len > 0 else 0 + + +def count_tx(transactions): + txs_without_none = [tx for tx in transactions if tx is not None] + return len(txs_without_none) + + +def load_data(path): + def process(block): + # An entrypoint is a dict of groups of entrypoints (each with objectives) + # since each group is a tree of calls (an entrypoint can be called during the execution + # of another) we need to flatten them to make them process friendly + block['entrypoints'] = list( + map(flatten_call_trees, block['entrypoints']) + ) + + return { + 'block': block['block_number'], + 'timestamp': pd.Timestamp(block['block_timestamp']), + 'txs': count_tx(block['entrypoints']), + 'transfers': count_transfers(block['entrypoints']), + 'swaps': count_swaps(block['entrypoints']), + 'transfers_ptg': count_transfers_ptg(block['entrypoints']), + 'swaps_ptg': count_swaps_ptg(block['entrypoints']), + } + + df = pd.DataFrame() + + for filename in os.listdir(path): + blocks = json.load(open(path + '/' + filename)) + + block_df = pd.DataFrame(blocks) + + df = pd.concat([df, block_df]) + + df = df.apply(process, axis=1).dropna().apply(pd.Series) + + return df + + +def flatten_call_trees(entrypoints): + if entrypoints['validate_call_info'] != None: + entrypoints['validate_call_info'] = flatten_call_tree( + entrypoints['validate_call_info'] + ) + + if entrypoints['execute_call_info'] != None: + entrypoints['execute_call_info'] = flatten_call_tree( + entrypoints['execute_call_info'] + ) + + if entrypoints['fee_transfer_call_info'] != None: + entrypoints['fee_transfer_call_info'] = flatten_call_tree( + entrypoints['fee_transfer_call_info'] + ) + + return entrypoints + + +def flatten_call_tree(call_tree): + calls = list(flatmap(flatten_call_tree, call_tree['inner'])) + + calls.append(call_tree['root']) + + return calls + + +df = load_data(arguments.block_execution_info) + +df_by_timestamp = df.groupby(pd.Grouper(key='timestamp', freq='D')).agg( + avg_txs=('txs', 'mean'), + avg_transfers=('transfers', 'mean'), + avg_swaps=('swaps', 'mean'), + avg_percentage_transfers=('transfers_ptg', 'mean'), + avg_percentage_swaps=('swaps_ptg', 'mean'), +) + +fig, axs = plt.subplots(2, figsize=(10, 7)) + +sns.lineplot( + data=df_by_timestamp, + x='timestamp', + y='avg_txs', + ax=axs[0], + label='average txs', +) +sns.lineplot( + data=df_by_timestamp, + x='timestamp', + y='avg_transfers', + ax=axs[0], + label='average transfers', +) +sns.lineplot( + data=df_by_timestamp, + x='timestamp', + y='avg_swaps', + ax=axs[0], + label='average swaps', +) +sns.lineplot( + data=df_by_timestamp, + x='timestamp', + y='avg_percentage_transfers', + ax=axs[1], + label='average transfers', +) +sns.lineplot( + data=df_by_timestamp, + x='timestamp', + y='avg_percentage_swaps', + ax=axs[1], + label='average swaps', +) + +axs.flat[0].set(xlabel='day', ylabel='average') +axs.flat[1].set(xlabel='day', ylabel='average (%)') + +fig.subplots_adjust(wspace=1, hspace=0.5) + +axs[0].set_title('Average txs, transfers and swaps in a block') +axs[1].set_title('Average percentage of swaps and tranfers in a block') + + +plt.show() diff --git a/plotting/utils.py b/plotting/utils.py index 21054df..46d87b4 100644 --- a/plotting/utils.py +++ b/plotting/utils.py @@ -1,4 +1,8 @@ import pandas as pd +import itertools + +def flatmap(f, iterable): + return itertools.chain.from_iterable(map(f, iterable)) def format_hash(class_hash): diff --git a/replay/Cargo.toml b/replay/Cargo.toml index 15ec996..904d74e 100644 --- a/replay/Cargo.toml +++ b/replay/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2021" [features] +default = ["block-composition"] +block-composition = ["structured_logging", "dep:serde", "dep:serde_json"] benchmark = ["dep:serde", "dep:serde_json", "dep:serde_with"] # The only_cairo_vm feature is designed to avoid executing transitions with cairo_native and instead use cairo_vm exclusively only_cairo_vm = ["rpc-state-reader/only_casm"] @@ -31,3 +33,4 @@ serde_json = { workspace = true, optional = true } serde_with = { workspace = true, optional = true } dotenvy = "0.15.7" anyhow.workspace = true +chrono = "0.4.39" diff --git a/replay/src/block_composition.rs b/replay/src/block_composition.rs new file mode 100644 index 0000000..72324f2 --- /dev/null +++ b/replay/src/block_composition.rs @@ -0,0 +1,115 @@ +use std::{ + error::Error, + fs::{self, File}, + path::Path, +}; + +use blockifier::{ + execution::call_info::CallInfo, + transaction::{errors::TransactionExecutionError, objects::TransactionExecutionInfo}, +}; +use serde::Serialize; +use starknet_api::core::{ClassHash, EntryPointSelector}; + +type BlockExecutionInfo = Vec<( + u64, // block number + String, // block timestamp + Vec>, +)>; + +#[derive(Debug, Serialize)] +struct CallTree { + root: EntryPointExecution, + inner: Vec, +} + +#[derive(Debug, Serialize)] +struct BlockEntryPoints { + block_number: u64, + block_timestamp: String, + entrypoints: Vec, +} + +#[derive(Debug, Serialize)] +struct TxEntryPoint { + validate_call_info: Option, + execute_call_info: Option, + fee_transfer_call_info: Option, +} + +#[derive(Debug, Serialize)] +struct EntryPointExecution { + class_hash: ClassHash, + selector: EntryPointSelector, +} + +/// Saves to a json the resulting list of `BlockEntryPoints` +pub fn save_entry_point_execution( + file_path: &Path, + executions: BlockExecutionInfo, +) -> Result<(), Box> { + if let Some(parent_path) = file_path.parent() { + fs::create_dir_all(parent_path)?; + } + + let mut blocks: Vec = Vec::new(); + + for (block_number, block_timestamp, executions) in executions { + let entrypoints = executions + .into_iter() + .map(|execution_rst| { + let execution = execution_rst.unwrap(); + let mut block_entry_point = TxEntryPoint { + validate_call_info: None, + execute_call_info: None, + fee_transfer_call_info: None, + }; + + if let Some(call) = execution.validate_call_info { + block_entry_point.validate_call_info = Some(get_inner_class_executions(call)); + } + if let Some(call) = execution.execute_call_info { + block_entry_point.execute_call_info = Some(get_inner_class_executions(call)); + } + if let Some(call) = execution.fee_transfer_call_info { + block_entry_point.fee_transfer_call_info = + Some(get_inner_class_executions(call)); + } + + block_entry_point + }) + .collect::>(); + + blocks.push(BlockEntryPoints { + block_number, + block_timestamp, + entrypoints, + }); + } + + let file = File::create(file_path)?; + serde_json::to_writer_pretty(file, &blocks)?; + + Ok(()) +} + +fn get_inner_class_executions(call: CallInfo) -> CallTree { + // class hash can initially be None, but it is always added before execution + let class_hash = call.call.class_hash.unwrap(); + + let top_class = EntryPointExecution { + class_hash, + selector: call.call.entry_point_selector, + }; + + let inner = call + .inner_calls + .into_iter() + .map(get_inner_class_executions) + .collect(); + + CallTree { + root: top_class, + inner, + } +} diff --git a/replay/src/main.rs b/replay/src/main.rs index 79d34ba..b8520f4 100644 --- a/replay/src/main.rs +++ b/replay/src/main.rs @@ -21,15 +21,24 @@ use { aggregate_executions, execute_block_range, fetch_block_range_data, fetch_transaction_data, BenchmarkingData, }, - std::path::PathBuf, std::time::Instant, }; +#[cfg(feature = "block-composition")] +use { + block_composition::save_entry_point_execution, chrono::DateTime, + rpc_state_reader::execution::fetch_block_context, +}; + +#[cfg(any(feature = "benchmark", feature = "block-composition"))] +use std::path::PathBuf; #[cfg(feature = "profiling")] use {std::thread, std::time::Duration}; #[cfg(feature = "benchmark")] mod benchmark; +#[cfg(feature = "block-composition")] +mod block_composition; #[cfg(feature = "state_dump")] mod state_dump; @@ -90,6 +99,15 @@ Caches all rpc data before the benchmark runs to provide accurate results" #[arg(short, long, default_value=PathBuf::from("data").into_os_string())] output: PathBuf, }, + #[cfg(feature = "block-composition")] + #[clap( + about = "Executes a range of blocks and writes down to a file every entrypoint executed." + )] + BlockCompose { + block_start: u64, + block_end: u64, + chain: String, + }, } fn main() { @@ -319,6 +337,68 @@ fn main() { ); } } + #[cfg(feature = "block-composition")] + ReplayExecute::BlockCompose { + block_start, + block_end, + chain, + } => { + info!("executing block range: {} - {}", block_start, block_end); + + let mut block_executions = Vec::new(); + + for block_number in block_start..=block_end { + let _block_span = info_span!("block", number = block_number).entered(); + + let mut state = build_cached_state(&chain, block_number - 1); + let reader = build_reader(&chain, block_number); + + let flags = ExecutionFlags { + only_query: false, + charge_fee: false, + validate: true, + }; + + let block_context = fetch_block_context(&reader).unwrap(); + + // fetch and execute transactions + let entrypoints = reader + .get_block_with_tx_hashes() + .unwrap() + .transactions + .into_iter() + .map(|hash| { + let (tx, _) = + fetch_transaction_with_state(&reader, &hash, flags.clone()).unwrap(); + let execution = tx.execute(&mut state, &block_context); + #[cfg(feature = "state_dump")] + state_dump::create_state_dump( + &mut state, + block_number, + &hash.to_string(), + &execution, + ); + execution + }) + .collect::>(); + + let block_timestamp = DateTime::from_timestamp( + block_context.block_info().block_timestamp.0 as i64, + 0, + ) + .unwrap() + .to_string(); + + block_executions.push((block_number, block_timestamp, entrypoints)); + } + + let path = PathBuf::from(format!( + "block_composition/block-compose-{}-{}-{}.json", + block_start, block_end, chain + )); + + save_entry_point_execution(&path, block_executions).unwrap(); + } } } @@ -375,38 +455,7 @@ fn show_execution_data( let execution_info_result = tx.execute(state, &context); #[cfg(feature = "state_dump")] - { - use std::path::Path; - - let root = if cfg!(feature = "only_cairo_vm") { - Path::new("state_dumps/vm") - } else if cfg!(feature = "with-sierra-emu") { - Path::new("state_dumps/emu") - } else { - Path::new("state_dumps/native") - }; - let root = root.join(format!("block{}", block_number)); - - std::fs::create_dir_all(&root).ok(); - - let mut path = root.join(&tx_hash_str); - path.set_extension("json"); - - match &execution_info_result { - Ok(execution_info) => { - state_dump::dump_state_diff(state, execution_info, &path) - .inspect_err(|err| error!("failed to dump state diff: {err}")) - .ok(); - } - Err(err) => { - // If we have no execution info, we write the error - // to a file so that it can be compared anyway - state_dump::dump_error(err, &path) - .inspect_err(|err| error!("failed to dump state diff: {err}")) - .ok(); - } - } - } + state_dump::create_state_dump(state, block_number, &tx_hash_str, &execution_info_result); let execution_info = match execution_info_result { Ok(x) => x, diff --git a/replay/src/state_dump.rs b/replay/src/state_dump.rs index f26c3ae..bf27fc7 100644 --- a/replay/src/state_dump.rs +++ b/replay/src/state_dump.rs @@ -29,8 +29,47 @@ use starknet_api::{ transaction::fields::{Calldata, Fee}, }; use starknet_types_core::felt::Felt; +use tracing::error; -pub fn dump_state_diff( +pub fn create_state_dump( + state: &mut CachedState, + block_number: u64, + tx_hash_str: &str, + execution_info_result: &Result, +) { + use std::path::Path; + + let root = if cfg!(feature = "only_cairo_vm") { + Path::new("state_dumps/vm") + } else if cfg!(feature = "with-sierra-emu") { + Path::new("state_dumps/emu") + } else { + Path::new("state_dumps/native") + }; + let root = root.join(format!("block{}", block_number)); + + std::fs::create_dir_all(&root).ok(); + + let mut path = root.join(tx_hash_str); + path.set_extension("json"); + + match execution_info_result { + Ok(execution_info) => { + dump_state_diff(state, execution_info, &path) + .inspect_err(|err| error!("failed to dump state diff: {err}")) + .ok(); + } + Err(err) => { + // If we have no execution info, we write the error + // to a file so that it can be compared anyway + dump_error(err, &path) + .inspect_err(|err| error!("failed to dump state diff: {err}")) + .ok(); + } + } +} + +fn dump_state_diff( state: &mut CachedState, execution_info: &TransactionExecutionInfo, path: &Path, @@ -52,7 +91,7 @@ pub fn dump_state_diff( Ok(()) } -pub fn dump_error(err: &TransactionExecutionError, path: &Path) -> anyhow::Result<()> { +fn dump_error(err: &TransactionExecutionError, path: &Path) -> anyhow::Result<()> { if let Some(parent) = path.parent() { let _ = fs::create_dir_all(parent); }