From a79f133d95618a8f6b2033592d02ec95c9d3a21c Mon Sep 17 00:00:00 2001 From: Esteve Soler Arderiu Date: Fri, 29 May 2026 15:15:17 +0200 Subject: [PATCH] feat: sync all tooling changes from ethrex main Port all tooling changes that landed on ethrex main after the initial ethrex-tooling repo was created: New tools/files: - ef_tests/engine: in-process engine API EF tests runner (#6665) - ef_tests/state_v2/statetest: goevmlab fuzzing subcommand (#6663) - migrations/src/bin/bench_migration + seed_migration_test (#6598) - trace_compare/: opcodeTracer compare script (#6595) - sync/peer_top.py: snap sync observability (#6470) Changes to existing tools: - Add release-fast profile for faster EF test rebuilds - Share merkle pool in blockchain/state_v2 test runners (#6665) - Update amsterdam/zkevm Make targets to be idempotent - Bump fixture URLs for bal-devnet-6/7 (#6574, #6653, #6671) - Bump zkevm fixtures to v0.3.3 (#6527) - Harden JSON-RPC defaults in sync/l2 configs (#6627) - Move getBlockAccessList to eth namespace in REPL (#6709) - Statetest CLI follow-ups (#6721) - Docker monitor disk-full handling (#6500) - Various version bumps and dep updates --- Cargo.lock | 1252 +++++++++-------- Cargo.toml | 12 + ef_tests/blockchain/.fixtures_url_amsterdam | 2 +- ef_tests/blockchain/.fixtures_url_zkevm | 2 +- ef_tests/blockchain/Cargo.toml | 1 + ef_tests/blockchain/Makefile | 47 +- ef_tests/blockchain/test_runner.rs | 94 +- ef_tests/blockchain/tests/all.rs | 81 +- ef_tests/engine/.fixtures_url | 1 + ef_tests/engine/.fixtures_url_amsterdam | 1 + ef_tests/engine/.gitignore | 3 + ef_tests/engine/Cargo.toml | 40 + ef_tests/engine/Makefile | 48 + ef_tests/engine/src/engine_ctx.rs | 95 ++ ef_tests/engine/src/exception_mapper.rs | 234 +++ ef_tests/engine/src/fixture.rs | 443 ++++++ ef_tests/engine/src/harness.rs | 280 ++++ ef_tests/engine/src/lib.rs | 11 + ef_tests/engine/src/report.rs | 14 + ef_tests/engine/src/runner.rs | 456 ++++++ ef_tests/engine/tests/all.rs | 143 ++ ef_tests/state/.fixtures_url_amsterdam | 2 +- ef_tests/state/Makefile | 16 +- ef_tests/state/runner/levm_runner.rs | 1 + ef_tests/state_v2/Makefile | 2 +- ef_tests/state_v2/src/main.rs | 49 +- ef_tests/state_v2/src/modules/block_runner.rs | 47 +- ef_tests/state_v2/src/modules/deserialize.rs | 17 +- ef_tests/state_v2/src/modules/error.rs | 8 + ef_tests/state_v2/src/modules/mod.rs | 1 + ef_tests/state_v2/src/modules/parser.rs | 24 +- ef_tests/state_v2/src/modules/report.rs | 27 +- ef_tests/state_v2/src/modules/runner.rs | 15 +- ef_tests/state_v2/src/modules/statetest.rs | 244 ++++ ef_tests/state_v2/src/modules/types.rs | 117 +- ef_tests/state_v2/src/modules/utils.rs | 138 +- l2/dev/docker-compose.yaml | 2 +- migrations/Cargo.toml | 11 + migrations/src/bin/bench_migration.rs | 100 ++ migrations/src/bin/seed_migration_test.rs | 219 +++ repl/src/commands/admin.rs | 14 + repl/src/commands/debug.rs | 7 - repl/src/commands/eth.rs | 7 + repl/src/formatter.rs | 160 ++- sync/Makefile | 9 +- sync/docker-compose.multisync.yaml | 19 +- sync/docker-compose.yml | 1 + sync/docker_monitor.py | 317 ++++- sync/peer_top.py | 270 ++++ trace_compare/README.md | 53 + trace_compare/compare.sh | 160 +++ 51 files changed, 4578 insertions(+), 739 deletions(-) create mode 100644 ef_tests/engine/.fixtures_url create mode 100644 ef_tests/engine/.fixtures_url_amsterdam create mode 100644 ef_tests/engine/.gitignore create mode 100644 ef_tests/engine/Cargo.toml create mode 100644 ef_tests/engine/Makefile create mode 100644 ef_tests/engine/src/engine_ctx.rs create mode 100644 ef_tests/engine/src/exception_mapper.rs create mode 100644 ef_tests/engine/src/fixture.rs create mode 100644 ef_tests/engine/src/harness.rs create mode 100644 ef_tests/engine/src/lib.rs create mode 100644 ef_tests/engine/src/report.rs create mode 100644 ef_tests/engine/src/runner.rs create mode 100644 ef_tests/engine/tests/all.rs create mode 100644 ef_tests/state_v2/src/modules/statetest.rs create mode 100644 migrations/src/bin/bench_migration.rs create mode 100644 migrations/src/bin/seed_migration_test.rs create mode 100644 sync/peer_top.py create mode 100644 trace_compare/README.md create mode 100755 trace_compare/compare.sh diff --git a/Cargo.lock b/Cargo.lock index e34a691..2a8ce61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array 0.14.7", ] @@ -74,7 +74,7 @@ dependencies = [ "reqwest 0.12.28", "serde", "serde_json", - "sha3", + "sha3 0.10.9", "sp1-sdk", "tokio", "tracing", @@ -170,7 +170,7 @@ dependencies = [ "either", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "serde", "serde_json", @@ -217,9 +217,9 @@ dependencies = [ [[package]] name = "alloy-core" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e8604b0c092fabc80d075ede181c9b9e596249c70b99253082d7e689836529" +checksum = "62ddde5968de6044d67af107ad835bc0069a7ca245870b94c5958a7d8712b184" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -230,9 +230,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" +checksum = "a475bb02d9cef2dbb99065c1664ab3fe1f9352e21d6d5ed3f02cdbfc06ed1abc" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -241,7 +241,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow 0.7.15", + "winnow 1.0.3", ] [[package]] @@ -285,14 +285,16 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.3" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" dependencies = [ "alloy-primitives", "alloy-rlp", "borsh", + "once_cell", "serde", + "thiserror 2.0.18", ] [[package]] @@ -335,9 +337,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" +checksum = "7c36c9d7f9021601b04bfef14a4b64849f6d73116a4e91e071d7fbfe10247901" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -401,9 +403,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" dependencies = [ "alloy-rlp", "bytes", @@ -411,7 +413,7 @@ dependencies = [ "const-hex", "derive_more 2.1.1", "foldhash 0.2.0", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap 2.14.0", "itoa", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -422,8 +424,9 @@ dependencies = [ "rapidhash", "ruint", "rustc-hash 2.1.2", + "secp256k1 0.31.1", "serde", - "sha3", + "sha3 0.11.0", ] [[package]] @@ -448,14 +451,14 @@ dependencies = [ "async-stream", "async-trait", "auto_impl", - "dashmap 6.1.0", + "dashmap 6.2.1", "either", "futures", "futures-utils-wasm", "lru 0.16.4", "parking_lot 0.12.5", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.4", "serde", "serde_json", "thiserror 2.0.18", @@ -499,7 +502,7 @@ dependencies = [ "alloy-transport-http", "futures", "pin-project", - "reqwest 0.13.2", + "reqwest 0.13.4", "serde", "serde_json", "tokio", @@ -546,7 +549,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "derive_more 2.1.1", - "rand 0.8.5", + "rand 0.8.6", "serde", "strum 0.27.2", ] @@ -611,15 +614,15 @@ dependencies = [ "async-trait", "eth-keystore", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.8.5", + "rand 0.8.6", "thiserror 2.0.18", ] [[package]] name = "alloy-sol-macro" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +checksum = "840128ed2b2971d6d4668a553fe403a82683d3acc646c73e75887e7157408033" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -631,9 +634,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +checksum = "63ec265e5d65d725175f6ca7711c970824c90ef9c0d1f1973711d4150ee612dd" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -643,16 +646,16 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "sha3", + "sha3 0.11.0", "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-macro-input" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +checksum = "89bf01077f18650876cfa682eb1f949967b5cde03f1a51c955c469d2c9b4aa67" dependencies = [ "alloy-json-abi", "const-hex", @@ -668,19 +671,19 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +checksum = "857b470ecdd2ed38beaf82ad1a38c516a8ff75266750f38b9eeed001d575241b" dependencies = [ "serde", - "winnow 0.7.15", + "winnow 1.0.3", ] [[package]] name = "alloy-sol-types" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +checksum = "384cf252de0db2dec52821eac037a7f57e2aa33fe5b900ce6fe39973402341f1" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -720,7 +723,7 @@ dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.13.2", + "reqwest 0.13.4", "serde_json", "tower 0.5.3", "tracing", @@ -836,11 +839,11 @@ dependencies = [ "clap 4.6.1", "clap_complete", "ethrex", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-rpc", - "ethrex-storage 9.0.0", + "ethrex-storage 13.0.0", "eyre", "hex", "lazy_static", @@ -1119,7 +1122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1129,7 +1132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1139,7 +1142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1227,15 +1230,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "zeroize", @@ -1243,9 +1246,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -1402,7 +1405,7 @@ dependencies = [ "getrandom 0.2.17", "instant", "pin-project-lite", - "rand 0.8.5", + "rand 0.8.6", "tokio", ] @@ -1455,7 +1458,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1465,7 +1468,7 @@ dependencies = [ "quote", "regex", "rustc-hash 1.1.0", - "shlex", + "shlex 1.3.0", "syn 2.0.117", ] @@ -1475,7 +1478,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1483,7 +1486,7 @@ dependencies = [ "quote", "regex", "rustc-hash 2.1.2", - "shlex", + "shlex 1.3.0", "syn 2.0.117", ] @@ -1493,7 +1496,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1501,7 +1504,7 @@ dependencies = [ "quote", "regex", "rustc-hash 2.1.2", - "shlex", + "shlex 1.3.0", "syn 2.0.117", ] @@ -1522,15 +1525,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.4" +version = "0.1.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" +checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" [[package]] name = "bitcoin_hashes" -version = "0.14.1" +version = "0.14.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" dependencies = [ "bitcoin-io", "hex-conservative", @@ -1544,9 +1547,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "2c61cd05405eb1d0f3a4660f802bad76ece84b6e722426342ba5dd511f724e97" dependencies = [ "serde_core", ] @@ -1586,9 +1589,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.4" +version = "1.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" dependencies = [ "arrayref", "arrayvec", @@ -1616,6 +1619,15 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -1687,6 +1699,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.12.1" @@ -1699,9 +1720,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byte-slice-cast" @@ -1846,22 +1867,16 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cexpr" version = "0.6.0" @@ -1925,7 +1940,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] @@ -1979,9 +1994,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.2" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb" +checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" dependencies = [ "clap 4.6.1", ] @@ -2050,9 +2065,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" dependencies = [ "castaway", "cfg-if", @@ -2086,9 +2101,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.18.1" +version = "1.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -2104,11 +2119,12 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -2201,9 +2217,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" @@ -2276,7 +2292,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "crossterm_winapi", "mio", "parking_lot 0.12.5", @@ -2292,7 +2308,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "crossterm_winapi", "derive_more 2.1.1", "document-features", @@ -2343,6 +2359,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "csv" version = "1.4.0" @@ -2364,6 +2389,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "ctr" version = "0.9.2" @@ -2380,7 +2415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.31.2", + "nix 0.31.3", "windows-sys 0.61.2", ] @@ -2467,9 +2502,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -2559,9 +2594,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "datatest-stable" @@ -2717,10 +2752,20 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.2", +] + [[package]] name = "directories" version = "5.0.1" @@ -2798,7 +2843,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "block2", "libc", "objc2", @@ -2806,9 +2851,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -2838,7 +2883,7 @@ checksum = "9ac1e888d6830712d565b2f3a974be3200be9296bc1b03db8251a4cbf18a4a34" dependencies = [ "digest 0.10.7", "futures", - "rand 0.8.5", + "rand 0.8.6", "reqwest 0.12.28", "thiserror 1.0.69", "tokio", @@ -2903,18 +2948,41 @@ dependencies = [ "bytes", "datatest-stable", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-guest-program", "ethrex-prover", - "ethrex-rlp 9.0.0", - "ethrex-storage 9.0.0", + "ethrex-rlp 13.0.0", + "ethrex-storage 13.0.0", "ethrex-vm", "hex", "lazy_static", + "rayon", + "regex", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "ef_tests-engine" +version = "4.0.0" +dependencies = [ + "anyhow", + "bytes", + "ctor", + "datatest-stable", + "ethrex-blockchain", + "ethrex-common 13.0.0", + "ethrex-p2p", + "ethrex-rpc", + "ethrex-storage 13.0.0", + "hex", + "rayon", "regex", "serde", "serde_json", + "tempfile", "tokio", ] @@ -2928,11 +2996,11 @@ dependencies = [ "clap_complete", "colored", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-levm", - "ethrex-rlp 9.0.0", - "ethrex-storage 9.0.0", + "ethrex-rlp 13.0.0", + "ethrex-storage 13.0.0", "ethrex-vm", "hex", "itertools 0.13.0", @@ -2957,12 +3025,12 @@ dependencies = [ "clap_complete", "colored", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-l2-rpc", "ethrex-levm", - "ethrex-rlp 9.0.0", - "ethrex-storage 9.0.0", + "ethrex-rlp 13.0.0", + "ethrex-storage 13.0.0", "ethrex-vm", "hex", "prettytable-rs", @@ -2977,9 +3045,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -3155,12 +3223,12 @@ dependencies = [ "hex", "hmac", "pbkdf2", - "rand 0.8.5", + "rand 0.8.6", "scrypt", "serde", "serde_json", "sha2 0.10.9", - "sha3", + "sha3 0.10.9", "thiserror 1.0.69", "uuid 0.8.2", ] @@ -3194,15 +3262,15 @@ dependencies = [ [[package]] name = "ethrex" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "anyhow", "bytes", "clap 4.6.1", "directories", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-config", "ethrex-crypto", "ethrex-dev", @@ -3212,11 +3280,11 @@ dependencies = [ "ethrex-l2-rpc", "ethrex-metrics", "ethrex-p2p", - "ethrex-repl 9.0.0", - "ethrex-rlp 9.0.0", + "ethrex-repl 13.0.0", + "ethrex-rlp 13.0.0", "ethrex-rpc", "ethrex-sdk", - "ethrex-storage 9.0.0", + "ethrex-storage 13.0.0", "ethrex-storage-rollup", "ethrex-vm", "eyre", @@ -3224,7 +3292,7 @@ dependencies = [ "itertools 0.14.0", "lazy_static", "local-ip-address", - "rand 0.8.5", + "rand 0.8.6", "rayon", "reqwest 0.12.28", "secp256k1 0.30.0", @@ -3246,17 +3314,17 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "crossbeam", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-metrics", - "ethrex-rlp 9.0.0", - "ethrex-storage 9.0.0", - "ethrex-trie 9.0.0", + "ethrex-rlp 13.0.0", + "ethrex-storage 13.0.0", + "ethrex-trie 13.0.0", "ethrex-vm", "rayon", "rustc-hash 2.1.2", @@ -3286,7 +3354,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "sha3", + "sha3 0.10.9", "thiserror 2.0.18", "tinyvec", "tracing", @@ -3295,15 +3363,15 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "crc32fast", "ethereum-types", "ethrex-crypto", - "ethrex-rlp 9.0.0", - "ethrex-trie 9.0.0", + "ethrex-rlp 13.0.0", + "ethrex-trie 13.0.0", "hex", "hex-literal", "hex-simd", @@ -3325,10 +3393,10 @@ dependencies = [ [[package]] name = "ethrex-config" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-p2p", "hex", "serde", @@ -3337,8 +3405,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "ark-bn254", "ark-ec", @@ -3362,8 +3430,8 @@ dependencies = [ [[package]] name = "ethrex-dev" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "envy", @@ -3382,15 +3450,15 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "ethereum-types", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-l2-common", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-vm", "hex", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3405,8 +3473,8 @@ dependencies = [ [[package]] name = "ethrex-l2" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "agg_mode_sdk", "alloy", @@ -3419,26 +3487,26 @@ dependencies = [ "envy", "ethereum-types", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-config", "ethrex-l2-common", "ethrex-l2-rpc", "ethrex-levm", "ethrex-metrics", - "ethrex-monitor 9.0.0", + "ethrex-monitor 13.0.0", "ethrex-p2p", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-rpc", "ethrex-sdk", - "ethrex-storage 9.0.0", + "ethrex-storage 13.0.0", "ethrex-storage-rollup", - "ethrex-trie 9.0.0", + "ethrex-trie 13.0.0", "ethrex-vm", "futures", "hex", "jsonwebtoken", "lazy_static", - "rand 0.8.5", + "rand 0.8.6", "ratatui", "reqwest 0.12.28", "secp256k1 0.30.0", @@ -3457,12 +3525,12 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "ethereum-types", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "k256 0.13.4 (registry+https://github.com/rust-lang/crates.io-index)", "lambdaworks-crypto 0.13.0", @@ -3476,8 +3544,8 @@ dependencies = [ [[package]] name = "ethrex-l2-prover" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "anyhow", "bincode", @@ -3485,14 +3553,14 @@ dependencies = [ "clap 4.6.1", "ethereum-types", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-guest-program", "ethrex-l2", "ethrex-l2-common", "ethrex-prover", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-sdk", - "ethrex-storage 9.0.0", + "ethrex-storage 13.0.0", "ethrex-vm", "hex", "rkyv", @@ -3508,20 +3576,20 @@ dependencies = [ [[package]] name = "ethrex-l2-rpc" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "axum 0.8.9", "bytes", "ethereum-types", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-l2-common", "ethrex-p2p", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-rpc", - "ethrex-storage 9.0.0", + "ethrex-storage 13.0.0", "ethrex-storage-rollup", "hex", "reqwest 0.12.28", @@ -3539,14 +3607,14 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "derive_more 1.0.0", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "malachite", "rayon", "rustc-hash 2.1.2", @@ -3557,11 +3625,11 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "axum 0.8.9", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "prometheus", "serde", "serde_json", @@ -3578,13 +3646,13 @@ dependencies = [ "bytes", "chrono", "crossterm 0.29.0", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-config", "ethrex-l2-common", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-rpc", "ethrex-sdk", - "ethrex-storage 9.0.0", + "ethrex-storage 13.0.0", "ethrex-storage-rollup", "futures", "hex", @@ -3602,19 +3670,19 @@ dependencies = [ [[package]] name = "ethrex-monitor" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "chrono", "crossterm 0.29.0", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-config", "ethrex-l2-common", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-rpc", "ethrex-sdk", - "ethrex-storage 9.0.0", + "ethrex-storage 13.0.0", "ethrex-storage-rollup", "futures", "hex", @@ -3632,8 +3700,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "aes", "aes-gcm", @@ -3643,14 +3711,14 @@ dependencies = [ "ctr", "ethereum-types", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-l2-common", "ethrex-metrics", - "ethrex-rlp 9.0.0", - "ethrex-storage 9.0.0", + "ethrex-rlp 13.0.0", + "ethrex-storage 13.0.0", "ethrex-storage-rollup", - "ethrex-trie 9.0.0", + "ethrex-trie 13.0.0", "futures", "hex", "hkdf", @@ -3659,7 +3727,7 @@ dependencies = [ "lazy_static", "lru 0.16.4", "prometheus", - "rand 0.8.5", + "rand 0.8.6", "rayon", "rocksdb", "rustc-hash 2.1.2", @@ -3678,15 +3746,15 @@ dependencies = [ [[package]] name = "ethrex-prover" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bincode", "bytes", "clap 4.6.1", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-guest-program", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-vm", "rkyv", "serde", @@ -3713,15 +3781,15 @@ dependencies = [ "rustyline", "serde", "serde_json", - "sha3", + "sha3 0.10.9", "thiserror 2.0.18", "tokio", ] [[package]] name = "ethrex-repl" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "clap 4.6.1", "colored", @@ -3732,7 +3800,7 @@ dependencies = [ "rustyline", "serde", "serde_json", - "sha3", + "sha3 0.10.9", "thiserror 2.0.18", "tokio", ] @@ -3753,8 +3821,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "ethereum-types", @@ -3763,8 +3831,8 @@ dependencies = [ [[package]] name = "ethrex-rpc" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "axum 0.8.9", "axum-extra", @@ -3772,18 +3840,18 @@ dependencies = [ "envy", "ethereum-types", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-metrics", "ethrex-p2p", - "ethrex-rlp 9.0.0", - "ethrex-storage 9.0.0", - "ethrex-trie 9.0.0", + "ethrex-rlp 13.0.0", + "ethrex-storage 13.0.0", + "ethrex-trie 13.0.0", "ethrex-vm", "hex", "hex-literal", "jsonwebtoken", - "rand 0.8.5", + "rand 0.8.6", "reqwest 0.12.28", "secp256k1 0.30.0", "serde", @@ -3797,20 +3865,20 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber 0.3.23", - "uuid 1.23.0", + "uuid 1.23.2", ] [[package]] name = "ethrex-sdk" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "ethereum-types", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-l2-common", "ethrex-l2-rpc", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "ethrex-rpc", "ethrex-sdk-contract-utils", "hex", @@ -3827,8 +3895,8 @@ dependencies = [ [[package]] name = "ethrex-sdk-contract-utils" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "thiserror 2.0.18", "tracing", @@ -3851,7 +3919,7 @@ dependencies = [ "libmdbx", "serde", "serde_json", - "sha3", + "sha3 0.10.9", "thiserror 2.0.18", "tokio", "tracing", @@ -3859,15 +3927,15 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "anyhow", "bytes", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", - "ethrex-rlp 9.0.0", - "ethrex-trie 9.0.0", + "ethrex-rlp 13.0.0", + "ethrex-trie 13.0.0", "fastbloom", "lru 0.16.4", "rayon", @@ -3882,13 +3950,13 @@ dependencies = [ [[package]] name = "ethrex-storage-rollup" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "async-trait", "bincode", "ethereum-types", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-l2-common", "futures", "rkyv", @@ -3911,7 +3979,7 @@ dependencies = [ "libmdbx", "serde", "serde_json", - "sha3", + "sha3 0.10.9", "smallvec", "thiserror 2.0.18", "tracing", @@ -3919,15 +3987,15 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "anyhow", "bytes", "crossbeam", "ethereum-types", "ethrex-crypto", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "lazy_static", "rayon", "rkyv", @@ -3938,16 +4006,16 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "9.0.0" -source = "git+https://github.com/lambdaclass/ethrex?branch=main#648719f817482391009c9ac6373712bdf98975cc" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex?branch=main#5f159535d7d24922c0490615153f3800d2378f52" dependencies = [ "bytes", "derive_more 1.0.0", "dyn-clone", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-crypto", "ethrex-levm", - "ethrex-rlp 9.0.0", + "ethrex-rlp 13.0.0", "rayon", "rustc-hash 2.1.2", "serde", @@ -4090,7 +4158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.6", "rustc-hex", "static_assertions", ] @@ -4359,7 +4427,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "libc", "libgit2-sys", "log", @@ -4391,16 +4459,16 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "ignore", "walkdir", ] [[package]] name = "gmp-mpfr-sys" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfc928d8ff4ab3767a3674cf55f81186436fb6070866bb1443ffe65a640d2d6" +checksum = "7db155b537cb791b133341f99f68371d86ee7fa4c79aacfbc376d72d23c70531" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4455,9 +4523,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -4542,15 +4610,18 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", - "serde", - "serde_core", ] [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "headers" @@ -4671,9 +4742,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -4729,11 +4800,20 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc" dependencies = [ "atomic-waker", "bytes", @@ -4813,7 +4893,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.6.4", "system-configuration", "tokio", "tower-service", @@ -4952,9 +5032,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -5053,7 +5133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -5117,16 +5197,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is-terminal" version = "0.4.17" @@ -5188,27 +5258,32 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys 0.3.1", + "jni-macros", + "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.18", "walkdir", - "windows-sys 0.45.0", + "windows-link", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "jni-sys 0.4.1", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.117", ] [[package]] @@ -5242,9 +5317,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -5320,16 +5395,41 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + [[package]] name = "keccak-asm" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" +checksum = "1766b89733097006f3a1388a02849865d6bc98c89273cb622e29fdd209922183" dependencies = [ "digest 0.10.7", "sha3-asm", ] +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "kzg-rs" version = "0.2.8" @@ -5350,11 +5450,11 @@ version = "0.12.0" source = "git+https://github.com/lambdaclass/lambdaworks.git?rev=5f8f2cfcc8a1a22f77e8dff2d581f1166eefb80b#5f8f2cfcc8a1a22f77e8dff2d581f1166eefb80b" dependencies = [ "lambdaworks-math 0.12.0", - "rand 0.8.5", + "rand 0.8.6", "rand_chacha 0.3.1", "serde", "sha2 0.10.9", - "sha3", + "sha3 0.10.9", ] [[package]] @@ -5364,11 +5464,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" dependencies = [ "lambdaworks-math 0.13.0", - "rand 0.8.5", + "rand 0.8.6", "rand_chacha 0.3.1", "serde", "sha2 0.10.9", - "sha3", + "sha3 0.10.9", ] [[package]] @@ -5377,7 +5477,7 @@ version = "0.12.0" source = "git+https://github.com/lambdaclass/lambdaworks.git?rev=5f8f2cfcc8a1a22f77e8dff2d581f1166eefb80b#5f8f2cfcc8a1a22f77e8dff2d581f1166eefb80b" dependencies = [ "getrandom 0.2.17", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", ] @@ -5391,7 +5491,7 @@ dependencies = [ "getrandom 0.2.17", "num-bigint 0.4.6", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", ] @@ -5413,15 +5513,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" -version = "0.18.3+1.9.2" +version = "0.18.4+1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +checksum = "9b26f66f35e1871b22efcf7191564123d2a446ca0538cde63c23adfefa9b15b7" dependencies = [ "cc", "libc", @@ -5454,7 +5554,7 @@ dependencies = [ "anyhow", "arrayref", "arrayvec", - "bitflags 2.11.1", + "bitflags 2.12.0", "derive_more 1.0.0", "impls", "indexmap 2.14.0", @@ -5468,9 +5568,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -5487,6 +5587,7 @@ dependencies = [ "libc", "libz-sys", "lz4-sys", + "zstd-sys", ] [[package]] @@ -5501,7 +5602,7 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand 0.8.5", + "rand 0.8.6", "serde", "sha2 0.9.9", ] @@ -5590,7 +5691,7 @@ dependencies = [ "clap 4.6.1", "ethereum-types", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-l2-common", "ethrex-l2-rpc", "ethrex-rpc", @@ -5619,9 +5720,9 @@ dependencies = [ [[package]] name = "local-ip-address" -version = "0.6.11" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a59a0cb1c7f84471ad5cd38d768c2a29390d17f1ff2827cdf49bc53e8ac70b" +checksum = "aa08fb2b1ec3ea84575e94b489d06d4ce0cbf052d12acd515838f50e3c3d63e3" dependencies = [ "libc", "neli", @@ -5639,9 +5740,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru" @@ -5679,9 +5780,9 @@ dependencies = [ [[package]] name = "macro-string" -version = "0.1.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" dependencies = [ "proc-macro2", "quote", @@ -5768,9 +5869,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memmap2" @@ -5794,10 +5895,13 @@ dependencies = [ "clap 4.6.1", "ethrex-blockchain", "ethrex-common 1.0.0", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-storage 1.0.0", - "ethrex-storage 9.0.0", + "ethrex-storage 13.0.0", + "libc", + "rocksdb", "tokio", + "tracing-subscriber 0.3.23", ] [[package]] @@ -5833,9 +5937,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -5886,7 +5990,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "byteorder", "derive_builder", "getset", @@ -5924,7 +6028,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cfg-if", "cfg_aliases", "libc", @@ -5936,7 +6040,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cfg-if", "cfg_aliases", "libc", @@ -5944,11 +6048,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cfg-if", "cfg_aliases", "libc", @@ -6034,9 +6138,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-format" @@ -6230,15 +6334,14 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.77" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe4646e360ec77dff7dde40ed3d6c5fee52d156ef4a62f53973d38294dad87f" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -6262,9 +6365,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.113" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad2f2c0eba47118757e4c6d2bff2838f3e0523380021356e7875e858372ce644" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -6317,7 +6420,7 @@ dependencies = [ "p3-mds 0.2.3-succinct", "p3-poseidon2 0.2.3-succinct", "p3-symmetric 0.2.3-succinct", - "rand 0.8.5", + "rand 0.8.6", "serde", ] @@ -6332,22 +6435,22 @@ dependencies = [ "p3-field 0.2.3-succinct", "p3-poseidon2 0.2.3-succinct", "p3-symmetric 0.2.3-succinct", - "rand 0.8.5", + "rand 0.8.6", "serde", ] [[package]] name = "p3-bn254-fr" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9abf208fbfe540d6e2a6caaa2a9a345b1c8cb23ffdcdfcc6987244525d4fc821" +checksum = "2077757c7cb514202ccb5368f521f23f5709c720599e6545c683c66e0a52d2d8" dependencies = [ "ff 0.13.1", "num-bigint 0.4.6", - "p3-field 0.3.2-succinct", - "p3-poseidon2 0.3.2-succinct", - "p3-symmetric 0.3.2-succinct", - "rand 0.8.5", + "p3-field 0.4.3-succinct", + "p3-poseidon2 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", + "rand 0.8.6", "serde", ] @@ -6367,14 +6470,14 @@ dependencies = [ [[package]] name = "p3-challenger" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42b725b453bbb35117a1abf0ddfd900b0676063d6e4231e0fa6bb0d76018d8ad" +checksum = "b6a908924d43e4cfb93fb41c8346cac211b70314385a9037e9241f5b7f3eaf77" dependencies = [ - "p3-field 0.3.2-succinct", - "p3-maybe-rayon 0.3.2-succinct", - "p3-symmetric 0.3.2-succinct", - "p3-util 0.3.2-succinct", + "p3-field 0.4.3-succinct", + "p3-maybe-rayon 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", + "p3-util 0.4.3-succinct", "serde", "tracing", ] @@ -6408,14 +6511,14 @@ dependencies = [ [[package]] name = "p3-dft" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56a1f81101bff744b7ebba7f4497e917a2c6716d6e62736e4a56e555a2d98cb7" +checksum = "be6408b10a2c27eb13a7d5580c546c2179a8dc7dbc10a990657311891f9b41c0" dependencies = [ - "p3-field 0.3.2-succinct", - "p3-matrix 0.3.2-succinct", - "p3-maybe-rayon 0.3.2-succinct", - "p3-util 0.3.2-succinct", + "p3-field 0.4.3-succinct", + "p3-matrix 0.4.3-succinct", + "p3-maybe-rayon 0.4.3-succinct", + "p3-util 0.4.3-succinct", "tracing", ] @@ -6429,21 +6532,21 @@ dependencies = [ "num-bigint 0.4.6", "num-traits", "p3-util 0.2.3-succinct", - "rand 0.8.5", + "rand 0.8.6", "serde", ] [[package]] name = "p3-field" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36459d4acb03d08097d713f336c7393990bb489ab19920d4f68658c7a5c10968" +checksum = "3dc75969ca3ac847f43e632ab979d59ff7a68f9eac8dbf8edcbba47fc2e1d3aa" dependencies = [ "itertools 0.12.1", "num-bigint 0.4.6", "num-traits", - "p3-util 0.3.2-succinct", - "rand 0.8.5", + "p3-util 0.4.3-succinct", + "rand 0.8.6", "serde", ] @@ -6493,16 +6596,18 @@ dependencies = [ [[package]] name = "p3-koala-bear" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1f52bcb6be38bdc8fa6b38b3434d4eedd511f361d4249fd798c6a5ef817b40" +checksum = "3a9683cd0ef68100df7c62490533047bcf19c04c4a0fa1efc9d7c1e03e31f6b3" dependencies = [ + "cfg-if", "num-bigint 0.4.6", - "p3-field 0.3.2-succinct", - "p3-mds 0.3.2-succinct", - "p3-poseidon2 0.3.2-succinct", - "p3-symmetric 0.3.2-succinct", - "rand 0.8.5", + "p3-field 0.4.3-succinct", + "p3-mds 0.4.3-succinct", + "p3-poseidon2 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", + "rand 0.8.6", + "rustc_version 0.4.1", "serde", ] @@ -6516,22 +6621,22 @@ dependencies = [ "p3-field 0.2.3-succinct", "p3-maybe-rayon 0.2.3-succinct", "p3-util 0.2.3-succinct", - "rand 0.8.5", + "rand 0.8.6", "serde", "tracing", ] [[package]] name = "p3-matrix" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e9cd136a4095a25c41a9edfdcce2dfae58ef01639317813bdbbd5b55c583" +checksum = "75c3f150ceb90e09539413bf481e618d05ee19210b4e467d2902eb82d2e15281" dependencies = [ "itertools 0.12.1", - "p3-field 0.3.2-succinct", - "p3-maybe-rayon 0.3.2-succinct", - "p3-util 0.3.2-succinct", - "rand 0.8.5", + "p3-field 0.4.3-succinct", + "p3-maybe-rayon 0.4.3-succinct", + "p3-util 0.4.3-succinct", + "rand 0.8.6", "serde", "tracing", ] @@ -6547,9 +6652,9 @@ dependencies = [ [[package]] name = "p3-maybe-rayon" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e524d47a49fb4265611303339c4ef970d892817b006cc330dad18afb91e411b1" +checksum = "e0641952b42da45e1dfa2d4a2a3163e330f944ad9740942f35026c0a71a605f1" [[package]] name = "p3-mds" @@ -6563,22 +6668,22 @@ dependencies = [ "p3-matrix 0.2.3-succinct", "p3-symmetric 0.2.3-succinct", "p3-util 0.2.3-succinct", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] name = "p3-mds" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f6cb8edcb276033d43769a3725570c340d2ed6f35c3cca4cddeee07718fa376" +checksum = "aa4a5f250e174dcfca5cbeac6ad75713924e7e7320e0a335e3c50b8b1f4fe8ec" dependencies = [ "itertools 0.12.1", - "p3-dft 0.3.2-succinct", - "p3-field 0.3.2-succinct", - "p3-matrix 0.3.2-succinct", - "p3-symmetric 0.3.2-succinct", - "p3-util 0.3.2-succinct", - "rand 0.8.5", + "p3-dft 0.4.3-succinct", + "p3-field 0.4.3-succinct", + "p3-matrix 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", + "p3-util 0.4.3-succinct", + "rand 0.8.6", ] [[package]] @@ -6608,21 +6713,21 @@ dependencies = [ "p3-field 0.2.3-succinct", "p3-mds 0.2.3-succinct", "p3-symmetric 0.2.3-succinct", - "rand 0.8.5", + "rand 0.8.6", "serde", ] [[package]] name = "p3-poseidon2" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a26197df2097b98ab7038d59a01e1fe1a0f545e7e04aa9436b2454b1836654f" +checksum = "522986377b2164c5f94f2dae88e0e0a3d169cc6239202ef4aeb4322d60feffd0" dependencies = [ "gcd", - "p3-field 0.3.2-succinct", - "p3-mds 0.3.2-succinct", - "p3-symmetric 0.3.2-succinct", - "rand 0.8.5", + "p3-field 0.4.3-succinct", + "p3-mds 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", + "rand 0.8.6", "serde", ] @@ -6639,12 +6744,12 @@ dependencies = [ [[package]] name = "p3-symmetric" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1d3b5202096bca57cde912fbbb9cbaedaf5ac7c42a924c7166b98709d64d21" +checksum = "9047ce85c086a9b3f118e10078f10636f7bfeed5da871a04da0b61400af8793a" dependencies = [ "itertools 0.12.1", - "p3-field 0.3.2-succinct", + "p3-field 0.4.3-succinct", "serde", ] @@ -6678,9 +6783,9 @@ dependencies = [ [[package]] name = "p3-util" -version = "0.3.2-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec5f0388aa6d935ca3a17444086120f393f0b2f0816010b5ff95998c1c4095e3" +checksum = "cff962f8eaa5f36e0447cee7c241f6b4b475fadf3ee61f154327a26bb4e009ba" dependencies = [ "serde", ] @@ -6798,7 +6903,7 @@ dependencies = [ "ff 0.12.1", "group 0.12.1", "lazy_static", - "rand 0.8.5", + "rand 0.8.6", "static_assertions", "subtle", ] @@ -6813,7 +6918,7 @@ dependencies = [ "ff 0.13.1", "group 0.13.0", "lazy_static", - "rand 0.8.5", + "rand 0.8.6", "static_assertions", "subtle", ] @@ -6935,7 +7040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -6962,18 +7067,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -7137,7 +7242,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", ] [[package]] @@ -7177,7 +7282,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "hex", "procfs-core", "rustix 0.38.44", @@ -7189,7 +7294,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "hex", ] @@ -7218,7 +7323,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.11.1", + "bitflags 2.12.0", "num-traits", "rand 0.9.4", "rand_chacha 0.9.0", @@ -7311,7 +7416,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.6.3", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -7349,7 +7454,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -7402,9 +7507,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -7498,7 +7603,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cassowary", "compact_str", "crossterm 0.28.1", @@ -7557,7 +7662,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", ] [[package]] @@ -7635,7 +7740,7 @@ version = "4.0.0" dependencies = [ "ethrex", "ethrex-blockchain", - "ethrex-common 9.0.0", + "ethrex-common 13.0.0", "ethrex-config", "ethrex-l2-common", "ethrex-l2-rpc", @@ -7643,7 +7748,7 @@ dependencies = [ "ethrex-rpc", "hex", "nix 0.30.1", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "sha2 0.10.9", "tokio", @@ -7702,9 +7807,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", @@ -7935,7 +8040,7 @@ version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f64fbacb86008394aaebd3454f9643b7d5a782bd251135e17c5b33da592d84d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "revm-bytecode", "revm-primitives", "serde", @@ -7985,13 +8090,13 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" +checksum = "73389e0c99e664f919275ab5b5b0471391fe9a8de61e1dff9b1eaf56a90f16e3" dependencies = [ "bytecheck", "bytes", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap 2.14.0", "munge", "ptr_meta", @@ -7999,14 +8104,14 @@ dependencies = [ "rend", "rkyv_derive", "tinyvec", - "uuid 1.23.0", + "uuid 1.23.2", ] [[package]] name = "rkyv_derive" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" +checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6" dependencies = [ "proc-macro2", "quote", @@ -8056,9 +8161,9 @@ dependencies = [ [[package]] name = "rug" -version = "1.29.0" +version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f6c8f906c90b48e0c1745c9f814c3a31c5eba847043b05c3e9a934dec7c4b3" +checksum = "07a8857882aec59d27254b02481c709327c13de6fad1da60bfc4f9783eaaa61e" dependencies = [ "az", "gmp-mpfr-sys", @@ -8068,9 +8173,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -8085,7 +8190,7 @@ dependencies = [ "parity-scale-codec", "primitive-types 0.12.2", "proptest", - "rand 0.8.5", + "rand 0.8.6", "rand 0.9.4", "rlp 0.5.2", "ruint-macro", @@ -8148,7 +8253,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -8161,7 +8266,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -8170,9 +8275,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -8207,9 +8312,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -8217,9 +8322,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", @@ -8244,9 +8349,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -8278,7 +8383,7 @@ version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "cfg-if", "clipboard-win", "fd-lock", @@ -8463,7 +8568,7 @@ dependencies = [ "bitcoin_hashes", "cfg-if", "k256 0.13.4 (git+https://github.com/sp1-patches/elliptic-curves?tag=patch-k256-13.4-sp1-5.0.0)", - "rand 0.8.5", + "rand 0.8.6", "secp256k1-sys 0.10.0", "serde", ] @@ -8502,7 +8607,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -8588,9 +8693,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "indexmap 2.14.0", "itoa", @@ -8634,11 +8739,12 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" dependencies = [ "base64", + "bs58", "chrono", "hex", "indexmap 1.9.3", @@ -8653,9 +8759,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -8736,19 +8842,29 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", - "keccak", + "keccak 0.1.6", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", ] [[package]] name = "sha3-asm" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" +checksum = "9f3f15d4e239ebe08413eed880e0f9b5af4b40ee0472543320efa91d488e96a7" dependencies = [ "cc", "cfg-if", @@ -8769,6 +8885,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook" version = "0.3.18" @@ -8825,6 +8947,16 @@ dependencies = [ "value-trait", ] +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -8845,9 +8977,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "size" @@ -8863,39 +8995,38 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slop-algebra" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733912d564a68ff209707e71fdc517d4ff82d4362b6a409f6a8241dfcb7a576a" +checksum = "3e8abf7cfad18c0580576e8adc01f7fa27b1cb19432e451e82950c9a445a7cfc" dependencies = [ "itertools 0.14.0", - "p3-field 0.3.2-succinct", + "p3-field 0.4.3-succinct", "serde", ] [[package]] name = "slop-bn254" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a71b23ede427299e139fb822c5d0ea8fb931dc297eba0c6e2f30f774c04ebc81" +checksum = "c327e0927fabf9c0ae9a7b0333027dc04431e5981ab80d7bbe994d6c4ce35fc1" dependencies = [ "ff 0.13.1", - "p3-bn254-fr 0.3.2-succinct", + "p3-bn254-fr 0.4.3-succinct", "serde", "slop-algebra", "slop-challenger", "slop-poseidon2", "slop-symmetric", - "zkhash", ] [[package]] name = "slop-challenger" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e4993210936ab317c0d56ee8257e1cdfe6c2fae4df1e158737f034e21d45f9" +checksum = "c263e731bb694d4465eedae7ecd6faf1f277198e751f3c209a1c4186d80d1b6b" dependencies = [ "futures", - "p3-challenger 0.3.2-succinct", + "p3-challenger 0.4.3-succinct", "serde", "slop-algebra", "slop-symmetric", @@ -8903,9 +9034,9 @@ dependencies = [ [[package]] name = "slop-koala-bear" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8986e94b9a43d58fc8ce5bf111b0985479ab888ced923e3052fb19943f7859b4" +checksum = "0a8cdc74f04d13e738b4628312b16dfec6a2e547bde697c8bdcf556884c6e91f" dependencies = [ "lazy_static", "p3-koala-bear", @@ -8918,29 +9049,29 @@ dependencies = [ [[package]] name = "slop-poseidon2" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b06e4a24cba104a0a39740eedd97e60e8896926cc38e6a58d5866cc9811affa" +checksum = "3aedfc3bf87cf2694bd108c039d663c346c7bb807491a7db6701eb9dff5c6e5d" dependencies = [ - "p3-poseidon2 0.3.2-succinct", + "p3-poseidon2 0.4.3-succinct", ] [[package]] name = "slop-primitives" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0b66701c82f6aab97f4990b5d9ed7463beb5b5042dbe5eda5f6c71a6207b35" +checksum = "f30e6e332c3cb103541bed9f5f477b769df1b5fb6076b5e2386569c94b1475dc" dependencies = [ "slop-algebra", ] [[package]] name = "slop-symmetric" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d159948b924fd00f280064d7a049e43dceb2f26067f32fb99570d3169969ee" +checksum = "4cb1de854325a8c36a1bdfee5514e1d4c9f39290ae19eeaecb21ca6ee88d96d6" dependencies = [ - "p3-symmetric 0.3.2-succinct", + "p3-symmetric 0.4.3-succinct", ] [[package]] @@ -8990,9 +9121,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -9033,7 +9164,7 @@ dependencies = [ "p3-field 0.2.3-succinct", "p3-maybe-rayon 0.2.3-succinct", "p3-util 0.2.3-succinct", - "rand 0.8.5", + "rand 0.8.6", "range-set-blaze", "rrs-succinct", "serde", @@ -9083,7 +9214,7 @@ dependencies = [ "p3-uni-stark", "p3-util 0.2.3-succinct", "pathdiff", - "rand 0.8.5", + "rand 0.8.6", "rayon", "rayon-scan", "serde", @@ -9170,13 +9301,13 @@ dependencies = [ [[package]] name = "sp1-lib" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b96392c1b1c197beaa6b0806099a8d73643a09d5ac0874e26c9c5153a7fcb4c" +checksum = "d0d5f56efe1d2a980d0f46083863ef4fdf715ed70cc32668c9e5725af145b8d9" dependencies = [ "bincode", "serde", - "sp1-primitives 6.1.0", + "sp1-primitives 6.2.2", ] [[package]] @@ -9201,9 +9332,9 @@ dependencies = [ [[package]] name = "sp1-primitives" -version = "6.1.0" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b77098dae9d62e080be3af253188c08e7e96e666423306654eede0110bf363" +checksum = "13ad00052921b993af682403b378c8fe23c40382f9790f093c8fac0f30433c5e" dependencies = [ "bincode", "blake3", @@ -9289,7 +9420,7 @@ dependencies = [ "p3-symmetric 0.2.3-succinct", "p3-uni-stark", "p3-util 0.2.3-succinct", - "rand 0.8.5", + "rand 0.8.6", "rayon", "serde", "sp1-core-executor", @@ -9355,7 +9486,7 @@ dependencies = [ "p3-symmetric 0.2.3-succinct", "p3-util 0.2.3-succinct", "pathdiff", - "rand 0.8.5", + "rand 0.8.6", "serde", "sp1-core-machine", "sp1-derive", @@ -9494,7 +9625,7 @@ dependencies = [ "group 0.13.0", "pairing 0.23.0", "rand_core 0.6.4", - "sp1-lib 6.1.0", + "sp1-lib 6.2.2", "subtle", ] @@ -9633,9 +9764,9 @@ dependencies = [ [[package]] name = "subenum" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3d08fe7078c57309d5c3d938e50eba95ba1d33b9c3a101a8465fc6861a5416" +checksum = "5eee3fb942ed39f3971438fcc7e05e20717e599e14c5c7cb50edd0df2a44b274" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -9652,7 +9783,7 @@ dependencies = [ "byteorder", "crunchy", "lazy_static", - "rand 0.8.5", + "rand 0.8.6", "rustc-hex", ] @@ -9662,6 +9793,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -9686,9 +9823,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +checksum = "ec005042c7d952febc1a3ef5b0f6674e9054aa836877a31c90b20e25b3d31744" dependencies = [ "paste", "proc-macro2", @@ -9737,7 +9874,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -9785,7 +9922,7 @@ dependencies = [ "percent-encoding", "pest", "pest_derive", - "rand 0.8.5", + "rand 0.8.6", "regex", "serde", "serde_json", @@ -10007,9 +10144,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -10017,7 +10154,7 @@ dependencies = [ "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] @@ -10157,14 +10294,14 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] @@ -10173,7 +10310,7 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] @@ -10226,7 +10363,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand 0.8.5", + "rand 0.8.6", "slab", "tokio", "tokio-util", @@ -10253,20 +10390,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower 0.5.3", "tower-layer", "tower-service", + "url", ] [[package]] @@ -10295,11 +10432,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber 0.3.23", @@ -10440,9 +10578,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -10533,7 +10671,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -10580,9 +10718,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -10735,11 +10873,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -10748,14 +10886,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -10766,9 +10904,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -10776,9 +10914,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -10786,9 +10924,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -10799,9 +10937,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -10847,7 +10985,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.0", "hashbrown 0.15.5", "indexmap 2.14.0", "semver 1.0.28", @@ -10869,9 +11007,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -10889,18 +11027,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -11035,15 +11173,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -11089,21 +11218,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -11152,12 +11266,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -11176,12 +11284,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -11200,12 +11302,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -11236,12 +11332,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -11260,12 +11350,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -11284,12 +11368,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -11308,12 +11386,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -11352,9 +11424,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -11368,6 +11440,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -11417,7 +11495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.12.0", "indexmap 2.14.0", "log", "serde", @@ -11487,18 +11565,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e" dependencies = [ "proc-macro2", "quote", @@ -11507,9 +11585,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -11599,10 +11677,10 @@ dependencies = [ "jubjub", "lazy_static", "pasta_curves 0.5.1", - "rand 0.8.5", + "rand 0.8.6", "serde", "sha2 0.10.9", - "sha3", + "sha3 0.10.9", "subtle", ] @@ -11611,3 +11689,13 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 72f8297..11d9d6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "archive_sync", "ef_tests/blockchain", + "ef_tests/engine", "ef_tests/state", "ef_tests/state_v2", "hive_report", @@ -88,6 +89,9 @@ crossbeam = "0.8.4" rayon = "1.10.0" rkyv = { version = "0.8.10", features = ["std", "unaligned"] } tempfile = "3.8" +anyhow = "1.0.86" +datatest-stable = "0.2.9" +libc = "0.2" uuid = { version = "1.18.1", features = ["v4"] } tower-http = { version = "0.6.2", features = ["cors"] } indexmap = { version = "2.11.4" } @@ -106,5 +110,13 @@ codegen-units = 1 inherits = "release" debug = 2 +# Release-grade opt-level without thin-LTO so ef_tests rebuilds are fast. +[profile.release-fast] +inherits = "release" +lto = false +codegen-units = 16 +debug = "line-tables-only" +incremental = true + [patch.crates-io] secp256k1 = { git = "https://github.com/sp1-patches/rust-secp256k1", tag = "patch-0.30.0-sp1-5.0.0" } diff --git a/ef_tests/blockchain/.fixtures_url_amsterdam b/ef_tests/blockchain/.fixtures_url_amsterdam index 2290401..566881c 100644 --- a/ef_tests/blockchain/.fixtures_url_amsterdam +++ b/ef_tests/blockchain/.fixtures_url_amsterdam @@ -1 +1 @@ -https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.6.1/fixtures_bal.tar.gz +https://github.com/ethereum/execution-specs/releases/download/tests-bal%40v7.2.0/fixtures_bal.tar.gz diff --git a/ef_tests/blockchain/.fixtures_url_zkevm b/ef_tests/blockchain/.fixtures_url_zkevm index 7b92a04..1c4b11f 100644 --- a/ef_tests/blockchain/.fixtures_url_zkevm +++ b/ef_tests/blockchain/.fixtures_url_zkevm @@ -1 +1 @@ -https://github.com/ethereum/execution-spec-tests/releases/download/zkevm%40v0.3.0/fixtures_zkevm.tar.gz +https://github.com/ethereum/execution-spec-tests/releases/download/zkevm%40v0.3.3/fixtures_zkevm.tar.gz diff --git a/ef_tests/blockchain/Cargo.toml b/ef_tests/blockchain/Cargo.toml index c296ec5..4f64a0e 100644 --- a/ef_tests/blockchain/Cargo.toml +++ b/ef_tests/blockchain/Cargo.toml @@ -24,6 +24,7 @@ lazy_static.workspace = true tokio = { workspace = true, features = ["full"] } datatest-stable = "0.2.9" regex = "1.11.1" +rayon.workspace = true [lib] path = "./lib.rs" diff --git a/ef_tests/blockchain/Makefile b/ef_tests/blockchain/Makefile index 214a0fa..1e078b9 100644 --- a/ef_tests/blockchain/Makefile +++ b/ef_tests/blockchain/Makefile @@ -16,6 +16,12 @@ AMSTERDAM_FIXTURES_FILE := .fixtures_url_amsterdam AMSTERDAM_ARTIFACT := amsterdam-tests.tar.gz AMSTERDAM_URL := $(shell cat $(AMSTERDAM_FIXTURES_FILE)) +# zkevm@v0.3.3 ships fixtures filled against an older Amsterdam base +# (bal@v5.6.1). Extracting them on top of the bal-devnet-7 tree would +# clobber the newer fixtures with stale gas-accounting expectations, so we +# keep them in a separate root and only the stateless harness reads from it. +ZKEVM_VECTORS_ROOT := vectors_zkevm +ZKEVM_VECTORS_DIR := $(ZKEVM_VECTORS_ROOT)/eest ZKEVM_FIXTURES_FILE := .fixtures_url_zkevm ZKEVM_ARTIFACT := zkevm-tests.tar.gz ZKEVM_URL := $(shell cat $(ZKEVM_FIXTURES_FILE)) @@ -44,14 +50,20 @@ $(LEGACYTEST_VECTORS_DIR): $(LEGACYTEST_ARTIFACT) $(AMSTERDAM_ARTIFACT): $(AMSTERDAM_FIXTURES_FILE) curl -L -o $(AMSTERDAM_ARTIFACT) $(AMSTERDAM_URL) -amsterdam-vectors: $(AMSTERDAM_ARTIFACT) $(SPECTEST_VECTORS_DIR) +$(SPECTEST_VECTORS_DIR)/for_amsterdam: $(AMSTERDAM_ARTIFACT) $(SPECTEST_VECTORS_DIR) tar -xzf $(AMSTERDAM_ARTIFACT) --strip-components=2 -C $(SPECTEST_VECTORS_DIR) fixtures/blockchain_tests/for_amsterdam +amsterdam-vectors: $(SPECTEST_VECTORS_DIR)/for_amsterdam + $(ZKEVM_ARTIFACT): $(ZKEVM_FIXTURES_FILE) curl -L -o $(ZKEVM_ARTIFACT) $(ZKEVM_URL) -zkevm-vectors: $(ZKEVM_ARTIFACT) $(SPECTEST_VECTORS_DIR) - tar -xzf $(ZKEVM_ARTIFACT) --strip-components=2 -C $(SPECTEST_VECTORS_DIR) fixtures/blockchain_tests/for_amsterdam/amsterdam/eip8025_optional_proofs +$(ZKEVM_VECTORS_DIR): $(ZKEVM_ARTIFACT) + rm -rf $(ZKEVM_VECTORS_DIR) + mkdir -p $(ZKEVM_VECTORS_DIR) + tar -xzf $(ZKEVM_ARTIFACT) --strip-components=2 -C $(ZKEVM_VECTORS_DIR) fixtures/blockchain_tests/for_amsterdam + +zkevm-vectors: $(ZKEVM_VECTORS_DIR) help: ## 📚 Show help for each of the Makefile recipes @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -59,21 +71,28 @@ help: ## 📚 Show help for each of the Makefile recipes download-test-vectors: $(VECTORS_TARGETS) amsterdam-vectors zkevm-vectors ## 📥 Download test vectors clean-vectors: ## 🗑️ Clean test vectors - rm -rf $(VECTORS_ROOT) + rm -rf $(VECTORS_ROOT) $(ZKEVM_VECTORS_ROOT) rm -f $(SPECTEST_ARTIFACT) $(LEGACYTEST_ARTIFACT) $(AMSTERDAM_ARTIFACT) $(ZKEVM_ARTIFACT) -test-levm: $(VECTORS_TARGETS) amsterdam-vectors zkevm-vectors ## 🧪 Run blockchain tests with LEVM - cargo test --profile release-with-debug +test-levm: $(VECTORS_TARGETS) amsterdam-vectors ## 🧪 Run blockchain tests with LEVM + cargo test --profile release-fast -test-sp1: $(VECTORS_TARGETS) amsterdam-vectors zkevm-vectors - cargo test --profile release-with-debug --features sp1 +test-sp1: $(VECTORS_TARGETS) amsterdam-vectors + cargo test --profile release-fast --features sp1 -test-stateless: $(VECTORS_TARGETS) amsterdam-vectors zkevm-vectors - cargo test --profile release-with-debug --features stateless +test-stateless: zkevm-vectors + cargo test --profile release-fast --features stateless -test-stateless-zkevm: $(VECTORS_TARGETS) amsterdam-vectors zkevm-vectors - cargo test --profile release-with-debug --features stateless -- eip8025_optional_proofs +test-stateless-zkevm: zkevm-vectors + cargo test --profile release-fast --features stateless -- eip8025_optional_proofs -test: ## 🧪 Run blockchain tests with LEVM both with state and stateless +test: ## 🧪 Run blockchain tests with LEVM both with state and stateless $(MAKE) test-levm - $(MAKE) test-stateless + # Narrow stateless coverage to the EIP-8025 optional-proofs suite. The + # zkevm@v0.3.3 fixtures are filled against bal@v5.6.1, which is out of + # sync with this branch's bal-devnet-6+ (and bal-devnet-7-prep) gas + # accounting; the broader `test-stateless` invocation introduced by + # #6527 trips ~549 of those fixtures with `GasUsedMismatch` / + # `ReceiptsRootMismatch` / `BlockAccessListHashMismatch`. Re-broaden + # once the zkevm bundle is regenerated against the current bal spec. + $(MAKE) test-stateless-zkevm diff --git a/ef_tests/blockchain/test_runner.rs b/ef_tests/blockchain/test_runner.rs index 85fac6d..f894419 100644 --- a/ef_tests/blockchain/test_runner.rs +++ b/ef_tests/blockchain/test_runner.rs @@ -1,14 +1,30 @@ -use std::{collections::HashMap, path::Path}; +use std::{collections::HashMap, path::Path, sync::Arc}; use crate::{ fork::Fork, types::{BlockChainExpectedException, BlockExpectedException, BlockWithRLP, TestUnit}, }; use ethrex_blockchain::{ - Blockchain, BlockchainOptions, + Blockchain, error::{ChainError, InvalidBlockError}, fork_choice::apply_fork_choice, }; + +thread_local! { + /// Per-OS-thread merkleization pool, lazily built on first use. Mirrors the + /// pattern used by `tooling/ef_tests/engine` so the ~10k+ blockchain tests + /// don't each spawn a fresh 17-thread rayon pool inside `Blockchain::new`. + /// The merkle protocol's 16 worker jobs cross-communicate via channels, so + /// each pool may have only one concurrent `in_place_scope` caller; keying by + /// `thread_local!` makes the calling test-runner thread the natural + /// exclusive owner. + static MERKLE_POOL: std::cell::OnceCell> = + const { std::cell::OnceCell::new() }; +} + +fn merkle_pool() -> Arc { + MERKLE_POOL.with(|cell| cell.get_or_init(Blockchain::build_merkle_pool).clone()) +} #[cfg(feature = "stateless")] use ethrex_common::types::block_execution_witness::RpcExecutionWitness; use ethrex_common::{ @@ -85,7 +101,7 @@ pub async fn run_ef_test( check_prestate_against_db(test_key, test, &store); // Blockchain EF tests are meant for L1. - let blockchain = Blockchain::new(store.clone(), BlockchainOptions::default()); + let blockchain = Blockchain::default_with_store_and_pool(store.clone(), merkle_pool()); // Early return if the exception is in the rlp decoding of the block for bf in &test.blocks { @@ -158,8 +174,12 @@ async fn run( "Warning: Returned exception {error:?} does not match expected {expected_exception:?}", ); } - // Expected exception matched — stop processing further blocks of this test. - break; + // Expected exception matched — block was rejected, but the test may + // still expect subsequent blocks to be processed (e.g. fork-transition + // tests where a block at the pre-fork timestamp fails and a block at + // the post-fork timestamp succeeds, both built on the same parent). + // Continue with the next block in the fixture. + continue; } Ok(_) => { if expects_exception { @@ -188,7 +208,7 @@ async fn run( async fn run_two_pass_parallel(test_key: &str, test: &TestUnit) -> Result<(), String> { // ---- Pass 1: sequential, collect BALs ---- let store1 = build_store_for_test(test).await; - let blockchain1 = Blockchain::new(store1.clone(), BlockchainOptions::default()); + let blockchain1 = Blockchain::default_with_store_and_pool(store1.clone(), merkle_pool()); let mut bals: Vec = Vec::with_capacity(test.blocks.len()); @@ -220,7 +240,7 @@ async fn run_two_pass_parallel(test_key: &str, test: &TestUnit) -> Result<(), St // ---- Pass 2: parallel (BAL-driven), verify post-state ---- let store2 = build_store_for_test(test).await; - let blockchain2 = Blockchain::new(store2.clone(), BlockchainOptions::default()); + let blockchain2 = Blockchain::default_with_store_and_pool(store2.clone(), merkle_pool()); for (block_fixture, bal) in test.blocks.iter().zip(bals.iter()) { let block: CoreBlock = block_fixture.block().unwrap().clone().into(); @@ -554,29 +574,71 @@ async fn run_stateless_from_fixture( let block: CoreBlock = block_data.clone().into(); let block_number = block.header.number; + // Absent bytes means "expected to succeed"; malformed bytes are a hard error. + let expected_valid = match block_data.stateless_output_bytes.as_deref() { + None => true, + Some(bytes) => parse_expected_valid_flag(bytes).map_err(|e| { + format!("Malformed statelessOutputBytes for {test_key} block {block_number}: {e}") + })?, + }; + + // Parse and conversion errors must always fail; only the execution outcome is + // matched against `expected_valid` so the (false, Err(_)) arm below cannot + // absorb regressions in deserialization or witness conversion. let rpc_witness: RpcExecutionWitness = serde_json::from_value(witness_json.clone()) .map_err(|e| { - format!("Failed to parse executionWitness for block {block_number}: {e}") + format!("executionWitness parse failed for {test_key} block {block_number}: {e}") })?; - let execution_witness = rpc_witness .into_execution_witness(*chain_config, block_number) - .map_err(|e| format!("Witness conversion failed for block {block_number}: {e}"))?; + .map_err(|e| { + format!("witness conversion failed for {test_key} block {block_number}: {e}") + })?; let program_input = ProgramInput::new(vec![block], execution_witness); - - let execute_result = match backend_type { + let exec_result = match backend_type { BackendType::Exec => ExecBackend::new().execute(program_input), #[cfg(feature = "sp1")] BackendType::SP1 => Sp1Backend::new().execute(program_input), }; - if let Err(e) = execute_result { - return Err(format!( - "Stateless execution from fixture failed for {test_key} block {block_number}: {e}" - )); + match (expected_valid, exec_result) { + (true, Ok(_)) | (false, Err(_)) => {} + (true, Err(e)) => { + return Err(format!( + "Stateless execution from fixture failed for {test_key} block {block_number}: {e}" + )); + } + (false, Ok(_)) => { + return Err(format!( + "Stateless execution from fixture succeeded for {test_key} block \ + {block_number} but fixture expected it to fail (invalid executionWitness)" + )); + } } } Ok(()) } + +/// Decode the `valid` byte (index 32) from a zkevm-fixture `statelessOutputBytes` hex +/// string, encoded as `new_payload_request_root (32 B) ++ valid (1 B) ++ padding`. +#[cfg(feature = "stateless")] +fn parse_expected_valid_flag(hex: &str) -> Result { + let trimmed = hex.strip_prefix("0x").unwrap_or(hex); + let byte_hex = trimmed.get(64..66).ok_or_else(|| { + format!( + "expected at least 33 bytes (66 hex chars), got {} hex chars", + trimmed.len() + ) + })?; + let byte = u8::from_str_radix(byte_hex, 16) + .map_err(|e| format!("invalid hex at byte 32 ({byte_hex:?}): {e}"))?; + match byte { + 0 => Ok(false), + 1 => Ok(true), + n => Err(format!( + "invalid validity byte 0x{n:02x} (expected 0x00 or 0x01)" + )), + } +} diff --git a/ef_tests/blockchain/tests/all.rs b/ef_tests/blockchain/tests/all.rs index f2443a3..03c47de 100644 --- a/ef_tests/blockchain/tests/all.rs +++ b/ef_tests/blockchain/tests/all.rs @@ -6,6 +6,13 @@ use std::path::Path; #[cfg(all(feature = "sp1", feature = "stateless"))] compile_error!("Only one of `sp1` and `stateless` can be enabled at a time."); +// test-levm / test-sp1 read snobal-devnet-6 + legacy from `vectors/`. +// test-stateless reads zkevm@v0.3.3 (the only bundle that ships executionWitness) +// from a separate `vectors_zkevm/` so its older bal@v5.6.1 base never overlays +// the snobal fixtures used by the other suites. +#[cfg(feature = "stateless")] +const TEST_FOLDER: &str = "vectors_zkevm/"; +#[cfg(not(feature = "stateless"))] const TEST_FOLDER: &str = "vectors/"; // Base skips shared by all runs. @@ -18,23 +25,91 @@ const SKIPPED_BASE: &[&str] = &[ "ValueOverflowParis", // Skip because it's a "Create" Blob Transaction, which doesn't actually exist. It never reaches the EVM because we can't even parse it as an actual Transaction. "createBlobhashTx", + // EIP-8025 optional-proofs fixtures filled against bal@v5.6.1 (devnets/bal/3), + // which predates EELS PR #2711 "immutable intrinsic_state_gas for EIP-7702". + // Expected gas assumes the auth refund still deducts from block-accounted state + // gas; our devnet-4 (bal@v5.7.0) impl correctly keeps intrinsic_state_gas + // immutable and routes the refund to the reservoir only. Re-enable once the + // zkevm@v0.4.x release ships fixtures regenerated against devnet-4. + "witness_codes_redelegation_old_marker_included_new_marker_excluded", + "witness_codes_reset_delegation", + "witness_codes_reverted_transaction", + "witness_codes_failed_create_includes_factory", + "witness_codes_reverted_create_same_hash_then_read", + "witness_codes_create_then_selfdestruct_same_tx", + // Additional EIP-8025 optional-proofs fixtures whose expected gas magnitudes + // disagree with bal-devnet-7 (bal@v7.1.1) state-gas accounting. Same root + // cause as the block above: zkevm@v0.3.3 bundle is pinned at an older bal + // spec (storage_set / new_account / cpsb constants pre-recalibration plus + // earlier refund-channel semantics) and the broader fork.py changes from + // EELS PRs #2815/#2816/#2823/#2827/#2828. Re-enable once the zkevm bundle + // is regenerated against bal-7. + "witness_codes_delegation_set_in_same_block", + "witness_codes_auth_nonce_mismatch", + "witness_codes_dedup_identical_bytecode", + "witness_codes_create2_excludes_new_bytecode", + "witness_codes_reverted_inner_call", + "witness_codes_create_same_hash_then_read", + "witness_codes_create_then_call_same_block", + "witness_codes_create_then_call_same_tx", + "witness_codes_failed_create_after_initcode_read", + "witness_codes_initcode_calls_existing_contract", + "witness_excludes_bytecode_created_in_same_block", + "witness_keeps_prestate_code_read_even_if_later_created_with_same_hash", + "witness_codes_selfdestruct_in_initcode", + "witness_codes_selfdestruct_beneficiary_no_code", + "witness_state_delete_with_new_dirty_sibling_omits_post_state_node", + "witness_state_block_diff_delete_insert_before_delete_order", + "witness_state_delete_then_insert_uses_insert_before_delete_order", + "witness_state_sstore_into_empty_storage_omits_post_state_nodes", + "witness_state_sstore_new_slot_omits_post_state_nodes", + "validation_state_missing_absent_slot_proof_leaf_node", + "validation_state_missing_storage_proof_node", ]; // Extra skips added only for prover backends. -#[cfg(feature = "sp1")] +#[cfg(all(feature = "sp1", not(feature = "stateless")))] const EXTRA_SKIPS: &[&str] = &[ // I believe these tests fail because of how much stress they put into the zkVM, they probably cause an OOM though this should be checked "static_Call50000", "Return50000", "static_Call1MB1024Calldepth", ]; -#[cfg(not(feature = "sp1"))] +#[cfg(feature = "stateless")] +const EXTRA_SKIPS: &[&str] = &[ + // zkevm@v0.3.3 tolerance tests: the fixture's `statelessOutputBytes` declares `valid = 1` + // because the executed path does not actually consume the malformed/extra/missing witness + // entry, but our RpcExecutionWitness conversion eagerly validates the full witness and + // rejects it. Re-enable once the witness conversion is lazy per EIP-8025 §Tolerance. + "validation_headers_malformed_rlp_header", + "validation_headers_missing_oldest_blockhash_ancestor", + "validation_headers_missing_parent_header", + "validation_state_extra_unused_trie_node", + // zkevm@v0.3.3 rejection tests: `statelessOutputBytes` declares `valid = 0` so the guest + // program must reject the deliberately-incomplete witness, but our stateless path runs + // to completion instead of detecting the missing entry. Re-enable once the witness + // completeness checks land (missing delegation/external-code bytecodes, non-contiguous + // header chain detection). + "validation_codes_missing_delegated_code_on_insufficient_balance_call", + "validation_codes_missing_external_code_read_target", + "validation_codes_missing_redelegation_old_marker", + "validation_codes_missing_sender_delegation_marker", + "validation_headers_non_contiguous_chain", + // zkevm@v0.3.3 conversion-time rejection: `statelessOutputBytes` declares `valid = 0` and + // our `into_execution_witness` correctly rejects the witness because it can't extract the + // initial state root without the parent header. Since 5a597e67d the runner treats + // conversion errors as unconditional regressions, so this correct-rejection-at-the-wrong- + // stage trips the test. Re-enable once conversion is lazy enough to defer the parent- + // header check to execution. + "validation_headers_empty_block_missing_mandatory_parent", +]; +#[cfg(not(any(feature = "sp1", feature = "stateless")))] const EXTRA_SKIPS: &[&str] = &[]; // Select backend #[cfg(feature = "stateless")] const BACKEND: Option = Some(BackendType::Exec); -#[cfg(feature = "sp1")] +#[cfg(all(feature = "sp1", not(feature = "stateless")))] const BACKEND: Option = Some(BackendType::SP1); #[cfg(not(any(feature = "sp1", feature = "stateless")))] const BACKEND: Option = None; diff --git a/ef_tests/engine/.fixtures_url b/ef_tests/engine/.fixtures_url new file mode 100644 index 0000000..ea7c838 --- /dev/null +++ b/ef_tests/engine/.fixtures_url @@ -0,0 +1 @@ +https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz diff --git a/ef_tests/engine/.fixtures_url_amsterdam b/ef_tests/engine/.fixtures_url_amsterdam new file mode 100644 index 0000000..78577b4 --- /dev/null +++ b/ef_tests/engine/.fixtures_url_amsterdam @@ -0,0 +1 @@ +https://github.com/ethereum/execution-spec-tests/releases/download/snobal-devnet-6%40v1.1.0/fixtures_snobal-devnet-6.tar.gz diff --git a/ef_tests/engine/.gitignore b/ef_tests/engine/.gitignore new file mode 100644 index 0000000..a31f579 --- /dev/null +++ b/ef_tests/engine/.gitignore @@ -0,0 +1,3 @@ +vectors/ +tests.tar.gz +amsterdam-tests.tar.gz diff --git a/ef_tests/engine/Cargo.toml b/ef_tests/engine/Cargo.toml new file mode 100644 index 0000000..9a51298 --- /dev/null +++ b/ef_tests/engine/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "ef_tests-engine" +version.workspace = true +edition.workspace = true +authors.workspace = true +documentation.workspace = true +license.workspace = true + +[dependencies] +ethrex-common.workspace = true +ethrex-storage.workspace = true +ethrex-rpc.workspace = true +ethrex-blockchain.workspace = true +ethrex-p2p.workspace = true +rayon.workspace = true + +serde.workspace = true +serde_json.workspace = true +tokio = { workspace = true, features = ["full"] } +anyhow.workspace = true +bytes.workspace = true +hex.workspace = true +tempfile.workspace = true +regex = "1.11.1" + +[dev-dependencies] +datatest-stable.workspace = true +ctor = "0.2" + +[lib] +path = "src/lib.rs" + +[[test]] +name = "all" +harness = false + +[features] +default = ["c-kzg"] +c-kzg = ["ethrex-blockchain/c-kzg"] +rocksdb = ["ethrex-storage/rocksdb"] diff --git a/ef_tests/engine/Makefile b/ef_tests/engine/Makefile new file mode 100644 index 0000000..dfbd089 --- /dev/null +++ b/ef_tests/engine/Makefile @@ -0,0 +1,48 @@ +.PHONY: download-test-vectors clean-vectors test test-rocksdb test-strict amsterdam-vectors help + +VECTORS_ROOT := vectors +FIXTURES_FILE := .fixtures_url +SPECTEST_ARTIFACT := tests.tar.gz +SPECTEST_VECTORS_DIR := $(VECTORS_ROOT)/eest +SPECTEST_URL := $(shell cat $(FIXTURES_FILE)) + +AMSTERDAM_FIXTURES_FILE := .fixtures_url_amsterdam +AMSTERDAM_ARTIFACT := amsterdam-tests.tar.gz +AMSTERDAM_URL := $(shell cat $(AMSTERDAM_FIXTURES_FILE)) + +VECTORS_TARGETS := $(SPECTEST_VECTORS_DIR) + +$(SPECTEST_ARTIFACT): $(FIXTURES_FILE) + $(MAKE) clean-vectors + curl -L -o $(SPECTEST_ARTIFACT) $(SPECTEST_URL) + +$(SPECTEST_VECTORS_DIR): $(SPECTEST_ARTIFACT) + rm -rf $(SPECTEST_VECTORS_DIR) + mkdir -p $(SPECTEST_VECTORS_DIR) + tar -xzf $(SPECTEST_ARTIFACT) --strip-components=2 -C $(SPECTEST_VECTORS_DIR) fixtures/blockchain_tests_engine + +$(AMSTERDAM_ARTIFACT): $(AMSTERDAM_FIXTURES_FILE) + curl -L -o $(AMSTERDAM_ARTIFACT) $(AMSTERDAM_URL) + +$(SPECTEST_VECTORS_DIR)/for_amsterdam: $(AMSTERDAM_ARTIFACT) $(SPECTEST_VECTORS_DIR) + tar -xzf $(AMSTERDAM_ARTIFACT) --strip-components=2 -C $(SPECTEST_VECTORS_DIR) fixtures/blockchain_tests_engine/for_amsterdam + +amsterdam-vectors: $(SPECTEST_VECTORS_DIR)/for_amsterdam ## 📥 Overlay snobal-devnet-6 engine fixtures onto vectors/eest + +help: ## 📚 Show help for each Makefile target + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +download-test-vectors: $(VECTORS_TARGETS) amsterdam-vectors ## 📥 Download engine-format test vectors + +clean-vectors: ## 🗑️ Clean test vectors + rm -rf $(VECTORS_ROOT) + rm -f $(SPECTEST_ARTIFACT) $(AMSTERDAM_ARTIFACT) + +test: $(VECTORS_TARGETS) amsterdam-vectors ## 🧪 Run engine-format fixture suite (InMemory, release) + cargo test -p ef_tests-engine --release + +test-rocksdb: $(VECTORS_TARGETS) amsterdam-vectors ## 🧪 Run with RocksDB backend + cargo test -p ef_tests-engine --release --features rocksdb + +test-strict: $(VECTORS_TARGETS) amsterdam-vectors ## 🧪 Run with strict validationError checking + ETHREX_ENGINE_STRICT_EXCEPTIONS=1 cargo test -p ef_tests-engine --release diff --git a/ef_tests/engine/src/engine_ctx.rs b/ef_tests/engine/src/engine_ctx.rs new file mode 100644 index 0000000..af4d816 --- /dev/null +++ b/ef_tests/engine/src/engine_ctx.rs @@ -0,0 +1,95 @@ +//! In-process engine-API RpcApiContext factory for the ef_tests-engine harness. +//! +//! Lives in the test tooling (not in `ethrex-rpc`) because the shared statics +//! and the thread-local rayon pool below exist solely to amortise per-fixture +//! cost across the ~5600 fixtures this crate runs. Production has no reason +//! to share a `SyncManager` across `RpcApiContext`s or to hand out a single +//! merkle pool per worker thread. + +use std::sync::Arc; + +use bytes::Bytes; +use ethrex_blockchain::Blockchain; +use ethrex_common::types::DEFAULT_BUILDER_GAS_CEIL; +use ethrex_p2p::sync_manager::SyncManager; +use ethrex_rpc::{ + ClientVersion, GasTipEstimator, NodeData, RpcApiContext, start_block_executor, + test_utils::{ + all_namespaces_for_tests, dummy_sync_manager, example_local_node_record, example_p2p_node, + }, +}; +use ethrex_storage::Store; +use tokio::sync::{Mutex as TokioMutex, OnceCell}; + +/// Shared SyncManager for `engine_only_context`. Allocated once per process so the +/// RLPxInitiator OS thread (spawned by `dummy_actor::spawn_on_thread`) is created +/// exactly once regardless of how many harnesses are built. +/// +/// Verified unused in the engine and eth/block handler paths: +/// `rg "context\.peer_handler|ctx\.peer_handler" crates/networking/rpc/engine/` -> empty +/// `rg "context\.peer_handler|ctx\.peer_handler" crates/networking/rpc/eth/block.rs` -> empty +static SHARED_SYNCER: OnceCell> = OnceCell::const_new(); + +thread_local! { + /// Per-OS-thread merkleization pool, lazily built on first use. + /// The merkle protocol requires its 16 worker jobs to run concurrently and + /// communicate via channels, so each pool can have only ONE concurrent + /// `in_place_scope` caller. Keying by `thread_local!` makes the calling + /// tokio worker thread the natural exclusive owner of its pool — there are + /// at most `num_cpus` worker threads alive, so total OS-thread cost is + /// bounded by `num_cpus * 17` instead of `fixture_count * 17`. + static THREAD_LOCAL_MERKLE_POOL: std::cell::OnceCell> = + const { std::cell::OnceCell::new() }; +} + +fn thread_local_merkle_pool() -> Arc { + THREAD_LOCAL_MERKLE_POOL.with(|cell| cell.get_or_init(Blockchain::build_merkle_pool).clone()) +} + +/// In-process engine-API context for testing, sharing the P2P scaffold across calls. +/// +/// Reuses a single `Arc` per process (via `SHARED_SYNCER`), so the +/// RLPxInitiator OS thread is allocated exactly once regardless of fixture count. +/// `peer_handler` is `None`; the engine handlers and `eth_getBlockByNumber` do not +/// touch it (confirmed by the `rg` invariants above). +/// `syncer` is `Some(shared)` with `SyncMode::Full`, satisfying the engine handler +/// requirements in `engine_forkchoiceUpdated*` and `engine_newPayload*`. +pub async fn engine_only_context(storage: Store) -> RpcApiContext { + let shared_syncer = SHARED_SYNCER + .get_or_init(|| async { Arc::new(dummy_sync_manager().await) }) + .await + .clone(); + let blockchain = Arc::new(Blockchain::default_with_store_and_pool( + storage.clone(), + thread_local_merkle_pool(), + )); + let local_node_record = example_local_node_record(); + let block_worker_channel = start_block_executor(blockchain.clone()); + RpcApiContext { + storage, + blockchain, + active_filters: Default::default(), + syncer: Some(shared_syncer), + peer_handler: None, + node_data: NodeData { + jwt_secret: Default::default(), + local_p2p_node: example_p2p_node(), + local_node_record, + client_version: ClientVersion::new( + "ethrex".to_string(), + "0.1.0".to_string(), + "test".to_string(), + "abcd1234".to_string(), + "x86_64-unknown-linux".to_string(), + "1.70.0".to_string(), + ), + extra_data: Bytes::new(), + }, + gas_tip_estimator: Arc::new(TokioMutex::new(GasTipEstimator::new())), + log_filter_handler: None, + gas_ceil: DEFAULT_BUILDER_GAS_CEIL, + block_worker_channel, + ws: None, + allowed_namespaces: Arc::new(all_namespaces_for_tests()), + } +} diff --git a/ef_tests/engine/src/exception_mapper.rs b/ef_tests/engine/src/exception_mapper.rs new file mode 100644 index 0000000..8ef79e7 --- /dev/null +++ b/ef_tests/engine/src/exception_mapper.rs @@ -0,0 +1,234 @@ +//! Maps EEST canonical exception names (e.g. `TransactionException.NONCE_MISMATCH_TOO_LOW`) +//! to ethrex's actual error wording. +//! +//! Ported from `execution-specs/packages/testing/src/execution_testing/client_clis/clis/ethrex.py`. +//! When the Python mapper is updated (new exceptions or reworded ethrex messages), this file +//! must be updated in lock-step. The tests-vs-mapper drift is the main source of +//! `validation_error` mismatch noise; keep this honest. +//! +//! Match logic: a fixture's expected `validation_error` is one or more canonical names +//! (e.g. `"TransactionException.A|TransactionException.B"`). We consider it a match if +//! any alternative's substring OR regex (both tables consulted) matches ethrex's actual +//! validationError string. + +use regex::Regex; +use std::collections::HashMap; +use std::sync::OnceLock; + +#[derive(Clone, Copy)] +enum Kind { + /// Plain `actual.contains(text)`. + Sub, + /// `Regex::new(text).is_match(actual)`. + Re, +} + +struct Entry { + canonical: &'static str, + kind: Kind, + text: &'static str, +} + +// ─── Mapping tables (kept in lock-step with the Python `EthrexExceptionMapper`) ──────── + +#[rustfmt::skip] +const PATTERNS: &[Entry] = &[ + // ─── mapping_substring ───────────────────────────────────────────────────────── + Entry { canonical: "BlockException.INVALID_GASLIMIT", kind: Kind::Sub, + text: "Gas limit changed more than allowed from the parent" }, + Entry { canonical: "TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED", kind: Kind::Sub, + text: "Exceeded MAX_BLOB_GAS_PER_BLOCK" }, + Entry { canonical: "BlockException.INVALID_DEPOSIT_EVENT_LAYOUT", kind: Kind::Sub, + text: "Invalid deposit request layout" }, + Entry { canonical: "BlockException.INVALID_REQUESTS", kind: Kind::Sub, + text: "Requests hash does not match the one in the header after executing" }, + Entry { canonical: "BlockException.INVALID_RECEIPTS_ROOT", kind: Kind::Sub, + text: "Receipts Root does not match the one in the header after executing" }, + Entry { canonical: "BlockException.INVALID_STATE_ROOT", kind: Kind::Sub, + text: "World State Root does not match the one in the header after executing" }, + Entry { canonical: "BlockException.GAS_USED_OVERFLOW", kind: Kind::Sub, + text: "Gas allowance exceeded" }, + Entry { canonical: "BlockException.INVALID_BLOCK_ACCESS_LIST", kind: Kind::Sub, + text: "Block access list hash does not match the one in the header after executing" }, + Entry { canonical: "BlockException.INVALID_BAL_HASH", kind: Kind::Sub, + text: "Block access list hash does not match the one in the header after executing" }, + Entry { canonical: "BlockException.INCORRECT_BLOCK_FORMAT", kind: Kind::Sub, + text: "not in strictly ascending order for" }, + Entry { canonical: "BlockException.BLOCK_ACCESS_LIST_GAS_LIMIT_EXCEEDED", kind: Kind::Sub, + text: "Block access list exceeds gas limit" }, + Entry { canonical: "BlockException.INVALID_GAS_USED", kind: Kind::Sub, + text: "Gas used doesn't match value in header" }, + Entry { canonical: "BlockException.INCORRECT_BLOB_GAS_USED", kind: Kind::Sub, + text: "Blob gas used doesn't match value in header" }, + Entry { canonical: "BlockException.INVALID_BASEFEE_PER_GAS", kind: Kind::Sub, + text: "Base fee per gas is incorrect" }, + + // ─── mapping_regex ──────────────────────────────────────────────────────────── + Entry { canonical: "TransactionException.PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS", kind: Kind::Re, + text: r"(?i)priority fee.* is greater than max fee.*" }, + Entry { canonical: "TransactionException.TYPE_4_EMPTY_AUTHORIZATION_LIST", kind: Kind::Re, + text: r"(?i)empty authorization list" }, + Entry { canonical: "TransactionException.SENDER_NOT_EOA", kind: Kind::Re, + text: r"reject transactions from senders with deployed code|Sender account .* shouldn't be a contract" }, + Entry { canonical: "TransactionException.NONCE_MISMATCH_TOO_LOW", kind: Kind::Re, + text: r"nonce \d+ too low, expected \d+|Nonce mismatch.*" }, + Entry { canonical: "TransactionException.NONCE_MISMATCH_TOO_HIGH", kind: Kind::Re, + text: r"Nonce mismatch.*" }, + Entry { canonical: "TransactionException.TYPE_3_TX_MAX_BLOB_GAS_ALLOWANCE_EXCEEDED", kind: Kind::Re, + text: r"blob gas used \d+ exceeds maximum allowance \d+" }, + Entry { canonical: "TransactionException.TYPE_3_TX_ZERO_BLOBS", kind: Kind::Re, + text: r"blob transactions present in pre-cancun payload|empty blobs|Type 3 transaction without blobs" }, + Entry { canonical: "TransactionException.TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH", kind: Kind::Re, + text: r"blob version not supported|Invalid blob versioned hash" }, + Entry { canonical: "TransactionException.TYPE_2_TX_PRE_FORK", kind: Kind::Re, + text: r"Type 2 transactions are not supported before the London fork" }, + Entry { canonical: "TransactionException.TYPE_3_TX_PRE_FORK", kind: Kind::Re, + text: r"blob versioned hashes not supported|Type 3 transactions are not supported before the Cancun fork" }, + Entry { canonical: "TransactionException.TYPE_4_TX_CONTRACT_CREATION", kind: Kind::Re, + text: r"unexpected length|Contract creation in type 4 transaction|Error decoding field 'to' of type primitive_types::H160: InvalidLength" }, + Entry { canonical: "TransactionException.TYPE_3_TX_CONTRACT_CREATION", kind: Kind::Re, + text: r"unexpected length|Contract creation in type 3 transaction|Error decoding field 'to' of type primitive_types::H160: InvalidLength" }, + Entry { canonical: "TransactionException.TYPE_4_TX_PRE_FORK", kind: Kind::Re, + text: r"eip 7702 transactions present in pre-prague payload|Type 4 transactions are not supported before the Prague fork" }, + Entry { canonical: "TransactionException.INSUFFICIENT_ACCOUNT_FUNDS", kind: Kind::Re, + text: r"lack of funds \(\d+\) for max fee \(\d+\)|Insufficient account funds" }, + Entry { canonical: "TransactionException.INTRINSIC_GAS_TOO_LOW", kind: Kind::Re, + text: r"gas floor exceeds the gas limit|call gas cost exceeds the gas limit|Transaction gas limit lower than the minimum gas cost to execute the transaction|Transaction gas limit lower than the gas cost floor for calldata tokens" }, + Entry { canonical: "TransactionException.INTRINSIC_GAS_BELOW_FLOOR_GAS_COST", kind: Kind::Re, + text: r"Transaction gas limit lower than the gas cost floor for calldata tokens" }, + Entry { canonical: "TransactionException.INSUFFICIENT_MAX_FEE_PER_GAS", kind: Kind::Re, + text: r"gas price is less than basefee|Insufficient max fee per gas" }, + Entry { canonical: "TransactionException.INSUFFICIENT_MAX_FEE_PER_BLOB_GAS", kind: Kind::Re, + text: r"blob gas price is greater than max fee per blob gas|Insufficient max fee per blob gas.*" }, + Entry { canonical: "TransactionException.INITCODE_SIZE_EXCEEDED", kind: Kind::Re, + text: r"create initcode size limit|Initcode size exceeded.*" }, + Entry { canonical: "TransactionException.NONCE_IS_MAX", kind: Kind::Re, + text: r"Nonce is max" }, + Entry { canonical: "TransactionException.GAS_ALLOWANCE_EXCEEDED", kind: Kind::Re, + text: r"Gas allowance exceeded.*" }, + Entry { canonical: "BlockException.GAS_USED_OVERFLOW", kind: Kind::Re, + text: r"Gas allowance exceeded.*" }, + Entry { canonical: "TransactionException.TYPE_3_TX_BLOB_COUNT_EXCEEDED", kind: Kind::Re, + text: r"Blob count exceeded.*" }, + Entry { canonical: "TransactionException.GASLIMIT_PRICE_PRODUCT_OVERFLOW", kind: Kind::Re, + text: r"Invalid transaction: Gas limit price product overflow.*" }, + Entry { canonical: "TransactionException.GAS_LIMIT_EXCEEDS_MAXIMUM", kind: Kind::Re, + text: r"Invalid transaction: Transaction gas limit exceeds maximum.*" }, + Entry { canonical: "BlockException.INVALID_DEPOSIT_EVENT_LAYOUT", kind: Kind::Re, + text: r"Invalid deposit request layout|BAL validation failed.*" }, + Entry { canonical: "BlockException.SYSTEM_CONTRACT_CALL_FAILED", kind: Kind::Re, + text: r"System call failed.*" }, + Entry { canonical: "BlockException.SYSTEM_CONTRACT_EMPTY", kind: Kind::Re, + text: r"System contract:.* has no code after deployment" }, + Entry { canonical: "BlockException.INCORRECT_BLOB_GAS_USED", kind: Kind::Re, + text: r"Blob gas used doesn't match value in header" }, + Entry { canonical: "BlockException.RLP_STRUCTURES_ENCODING", kind: Kind::Re, + text: r"Error decoding field '\D+' of type \w+.*" }, + Entry { canonical: "BlockException.INCORRECT_EXCESS_BLOB_GAS", kind: Kind::Re, + text: r".* Excess blob gas is incorrect" }, + Entry { canonical: "BlockException.INVALID_BLOCK_HASH", kind: Kind::Re, + text: r"Invalid block hash. Expected \w+, got \w+" }, + Entry { canonical: "BlockException.RLP_BLOCK_LIMIT_EXCEEDED", kind: Kind::Re, + text: r"Maximum block size exceeded.*" }, + Entry { canonical: "BlockException.INVALID_BAL_HASH", kind: Kind::Re, + text: r"BAL validation failed" }, + Entry { canonical: "BlockException.INVALID_BLOCK_ACCESS_LIST", kind: Kind::Re, + text: r"Block access list contains index \d+ exceeding max valid index \d+|Failed to RLP decode BAL|Block access list .+ not in strictly ascending order.*|BAL validation failed for (tx \d+|system_tx|withdrawal): .*|BAL validation failed: .*|absent from BAL|Block access list slot .+ is in both storage_changes and storage_reads.*|Invalid block hash" }, + Entry { canonical: "BlockException.INCORRECT_BLOCK_FORMAT", kind: Kind::Re, + text: r"Block access list hash does not match the one in the header after executing|Block access list contains index \d+ exceeding max valid index \d+|Failed to RLP decode BAL|Block access list accounts not in strictly ascending order.*" }, +]; + +fn compiled() -> &'static HashMap<&'static str, Regex> { + static CELL: OnceLock> = OnceLock::new(); + CELL.get_or_init(|| { + PATTERNS + .iter() + .filter(|e| matches!(e.kind, Kind::Re)) + .map(|e| { + let re = Regex::new(e.text) + .unwrap_or_else(|err| panic!("invalid mapper regex `{}`: {err}", e.text)); + (e.text, re) + }) + .collect() + }) +} + +/// Whether ethrex's `actual` validationError matches a single canonical exception name. +pub fn matches_canonical(canonical: &str, actual: &str) -> bool { + let regexes = compiled(); + for entry in PATTERNS { + if entry.canonical != canonical { + continue; + } + let hit = match entry.kind { + Kind::Sub => actual.contains(entry.text), + Kind::Re => regexes + .get(entry.text) + .map(|re| re.is_match(actual)) + .unwrap_or(false), + }; + if hit { + return true; + } + } + false +} + +/// `expected` may be one or more `|`-separated canonical names. Any alternative +/// matching `actual` is treated as a match. +pub fn matches(expected: &str, actual: &str) -> bool { + expected + .split('|') + .map(str::trim) + .filter(|s| !s.is_empty()) + .any(|alt| matches_canonical(alt, actual)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn substring_match_bal_hash() { + assert!(matches_canonical( + "BlockException.INVALID_BAL_HASH", + "Block access list hash does not match the one in the header after executing", + )); + } + + #[test] + fn regex_match_nonce_too_low() { + assert!(matches_canonical( + "TransactionException.NONCE_MISMATCH_TOO_LOW", + "nonce 5 too low, expected 7", + )); + assert!(matches_canonical( + "TransactionException.NONCE_MISMATCH_TOO_LOW", + "Nonce mismatch for sender 0xabc", + )); + } + + #[test] + fn or_alternatives() { + assert!(matches( + "TransactionException.NONCE_MISMATCH_TOO_LOW|TransactionException.NONCE_MISMATCH_TOO_HIGH", + "Nonce mismatch for sender 0xabc", + )); + } + + #[test] + fn unknown_canonical_returns_false() { + assert!(!matches_canonical( + "TransactionException.DOES_NOT_EXIST", + "anything at all", + )); + } + + #[test] + fn no_alternative_matches() { + assert!(!matches( + "BlockException.INVALID_BAL_HASH", + "some unrelated ethrex message", + )); + } +} diff --git a/ef_tests/engine/src/fixture.rs b/ef_tests/engine/src/fixture.rs new file mode 100644 index 0000000..1ef641c --- /dev/null +++ b/ef_tests/engine/src/fixture.rs @@ -0,0 +1,443 @@ +// EEST BlockchainEngineFixture deserialization. Schema: packages/testing/src/execution_testing/fixtures/blockchain.py + +use anyhow::Context; +use ethrex_common::{H256, U256, types::Genesis}; +use serde::Deserialize; +use serde_json::Value; +use std::collections::BTreeMap; + +/// One JSON file holds many fixtures keyed by test name. +pub type EngineFixtureFile = BTreeMap; + +#[derive(Debug, Deserialize)] +pub struct EngineFixture { + pub network: String, + #[serde(rename = "genesisBlockHeader")] + pub genesis_block_header: FixtureHeader, + pub pre: Value, + pub config: Value, + #[serde(rename = "engineNewPayloads")] + pub engine_new_payloads: Vec, + pub lastblockhash: H256, +} + +/// Genesis block header as represented in EEST engine fixtures. +/// Captures all fields required for genesis construction and block identification. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FixtureHeader { + /// The computed block hash (JSON key: "hash"). + #[serde(rename = "hash")] + pub block_hash: H256, + pub state_root: H256, + pub number: U256, + // Fields required for Genesis construction (keys match camelCase of the field name): + pub coinbase: String, + pub difficulty: String, + pub gas_limit: String, + pub nonce: String, + pub mix_hash: String, + pub timestamp: String, + #[serde(default)] + pub extra_data: Option, + #[serde(default)] + pub base_fee_per_gas: Option, + #[serde(default)] + pub withdrawals_root: Option, + #[serde(default)] + pub blob_gas_used: Option, + #[serde(default)] + pub excess_blob_gas: Option, + #[serde(default)] + pub parent_beacon_block_root: Option, + #[serde(default)] + pub requests_hash: Option, +} + +#[derive(Debug, Deserialize)] +pub struct FixturePayload { + /// Pre-built positional args for engine_newPayloadVx. + pub params: Vec, + #[serde(rename = "newPayloadVersion", deserialize_with = "de_str_u8")] + pub new_payload_version: u8, + #[serde(rename = "forkchoiceUpdatedVersion", deserialize_with = "de_str_u8")] + pub forkchoice_updated_version: u8, + #[serde(default, rename = "validationError")] + pub validation_error: Option, + #[serde(default, rename = "errorCode", deserialize_with = "de_opt_str_i32")] + pub error_code: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum ValidationError { + Single(String), + List(Vec), +} + +impl FixturePayload { + /// Returns `true` when neither `validationError` nor `errorCode` is set. + pub fn valid(&self) -> bool { + self.validation_error.is_none() && self.error_code.is_none() + } + + /// Extract `blockHash` from `params[0]` (the ExecutionPayload object). + pub fn head_block_hash(&self) -> anyhow::Result { + let payload = self + .params + .first() + .context("engineNewPayload params is empty")?; + let hash_str = payload["blockHash"] + .as_str() + .context("params[0].blockHash is missing or not a string")?; + let hash = hash_str + .parse::() + .map_err(|e| anyhow::anyhow!("invalid blockHash hex: {e}"))?; + Ok(hash) + } +} + +impl EngineFixture { + /// Parse the fixture `network` field. Returns `(genesis_fork, transition)` where + /// `transition = Some((target_fork, activation_time_secs))` when the fixture is a + /// fork-transition test (e.g. `CancunToPragueAtTime15k`). + pub(crate) fn schedule( + &self, + ) -> anyhow::Result<( + ethrex_common::types::Fork, + Option<(ethrex_common::types::Fork, u64)>, + )> { + parse_network(&self.network) + } + + /// The "active" fork for skip checks: the transition target if present, else the genesis fork. + pub fn fork(&self) -> anyhow::Result { + let (genesis_fork, transition) = self.schedule()?; + Ok(transition.map(|(to, _)| to).unwrap_or(genesis_fork)) + } + + /// Build a `Genesis` value from the fixture's `pre` + `config` + `genesisBlockHeader`. + /// + /// Mirrors what `hive/clients/ethrex/mapper.jq` does: assembles a Geth-style genesis + /// JSON (alloc, config with fork activations, header fields) and deserializes it. + pub fn build_genesis(&self) -> anyhow::Result { + let chain_id = parse_chain_id(&self.config)?; + let (genesis_fork, transition) = self.schedule()?; + let mut config_json = build_chain_config_json(genesis_fork, transition, chain_id); + // EEST fixtures carry their own per-fork blobSchedule (Cancun/Prague/Osaka/BPO*/Amsterdam) + // with fork-name keys and hex-string values. Convert and inject; otherwise post-Cancun + // payloads that rely on non-default blob params get rejected. + if let Some(eest_schedule) = self.config.get("blobSchedule") { + let converted = convert_blob_schedule(eest_schedule); + config_json + .as_object_mut() + .expect("config_json is an object") + .insert("blobSchedule".into(), converted); + } + let genesis_json = build_genesis_json(&self.genesis_block_header, &self.pre, config_json); + serde_json::from_value::(genesis_json).context("Failed to deserialize Genesis") + } +} + +// ─── Private helpers ────────────────────────────────────────────────────────── + +/// Deserialize a `u8` from either a JSON number or a string (e.g. `"1"`). +/// EEST fixtures encode version fields as strings. +fn de_str_u8<'de, D: serde::Deserializer<'de>>(d: D) -> Result { + use serde::de::Error; + let v: Value = Value::deserialize(d)?; + match &v { + Value::Number(n) => n + .as_u64() + .and_then(|n| u8::try_from(n).ok()) + .ok_or_else(|| D::Error::custom(format!("invalid u8: {n}"))), + Value::String(s) => s + .parse::() + .map_err(|_| D::Error::custom(format!("invalid u8 string: {s}"))), + other => Err(D::Error::custom(format!("expected u8, got: {other}"))), + } +} + +/// Deserialize `Option` from a JSON number OR a string (EEST encodes `errorCode` +/// as a quoted decimal e.g. `"-32602"` in some fixtures, plain int in others). +fn de_opt_str_i32<'de, D: serde::Deserializer<'de>>(d: D) -> Result, D::Error> { + use serde::de::Error; + let opt = Option::::deserialize(d)?; + let Some(v) = opt else { return Ok(None) }; + let n: i64 = match &v { + Value::Null => return Ok(None), + Value::Number(n) => n + .as_i64() + .ok_or_else(|| D::Error::custom(format!("not an i64: {n}")))?, + Value::String(s) => s + .parse::() + .map_err(|e| D::Error::custom(format!("invalid i32 string '{s}': {e}")))?, + other => return Err(D::Error::custom(format!("expected i32, got: {other}"))), + }; + i32::try_from(n) + .map(Some) + .map_err(|_| D::Error::custom(format!("i32 overflow: {n}"))) +} + +/// Parse `config.chainid` which may be a hex string ("0x01") or decimal string ("1"). +fn parse_chain_id(config: &Value) -> anyhow::Result { + let raw = config + .get("chainid") + .or_else(|| config.get("chainId")) + .context("config missing 'chainid' field")?; + + if let Some(s) = raw.as_str() { + let id = if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + u64::from_str_radix(hex, 16) + } else { + s.parse::() + } + .map_err(|e| anyhow::anyhow!("invalid chainid '{s}': {e}"))?; + Ok(id) + } else if let Some(n) = raw.as_u64() { + Ok(n) + } else { + anyhow::bail!("config.chainid is not a string or number: {raw}") + } +} + +/// Parse an EEST `network` string into (genesis_fork, optional transition target+time). +/// +/// Single forks: `"Cancun"`, `"Prague"`, `"Amsterdam"`, `"BPO1"`, ... +/// Transitions: `"CancunToPragueAtTime15k"`, `"OsakaToBPO1AtTime15k"`, ... +/// → returns `(Cancun, Some((Prague, 15_000)))`. +fn parse_network( + s: &str, +) -> anyhow::Result<( + ethrex_common::types::Fork, + Option<(ethrex_common::types::Fork, u64)>, +)> { + if let Some(at_idx) = s.find("AtTime") { + let head = &s[..at_idx]; + let tail = &s[at_idx + "AtTime".len()..]; + let secs: u64 = if let Some(k) = tail.strip_suffix('k') { + k.parse::() + .map_err(|e| anyhow::anyhow!("bad time '{tail}': {e}"))? + * 1000 + } else { + tail.parse::() + .map_err(|e| anyhow::anyhow!("bad time '{tail}': {e}"))? + }; + let to_idx = head + .find("To") + .ok_or_else(|| anyhow::anyhow!("missing 'To' in transition network: {s}"))?; + let from = single_fork(&head[..to_idx])?; + let to = single_fork(&head[to_idx + 2..])?; + return Ok((from, Some((to, secs)))); + } + Ok((single_fork(s)?, None)) +} + +fn single_fork(s: &str) -> anyhow::Result { + use ethrex_common::types::Fork; + let f = match s { + "Frontier" => Fork::Frontier, + "Homestead" => Fork::Homestead, + "EIP150" | "Tangerine" => Fork::Tangerine, + "EIP158" | "SpuriousDragon" => Fork::SpuriousDragon, + "Byzantium" => Fork::Byzantium, + "Constantinople" => Fork::Constantinople, + "ConstantinopleFix" | "Petersburg" => Fork::Petersburg, + "Istanbul" => Fork::Istanbul, + "MuirGlacier" => Fork::MuirGlacier, + "Berlin" => Fork::Berlin, + "London" => Fork::London, + "ArrowGlacier" => Fork::ArrowGlacier, + "GrayGlacier" => Fork::GrayGlacier, + "Paris" | "Merge" => Fork::Paris, + "Shanghai" => Fork::Shanghai, + "Cancun" => Fork::Cancun, + "Prague" => Fork::Prague, + "Osaka" => Fork::Osaka, + "BPO1" => Fork::BPO1, + "BPO2" => Fork::BPO2, + "BPO3" => Fork::BPO3, + "BPO4" => Fork::BPO4, + "BPO5" => Fork::BPO5, + "Amsterdam" => Fork::Amsterdam, + other => anyhow::bail!("Unknown network: {other}"), + }; + Ok(f) +} + +/// Build a Geth-style chain config. All pre-Paris block-numbered forks activate at block 0. +/// Time-based forks activate at 0 for everything `<=` the target fork, except the transition +/// target (when set) which activates at `transition.1`. Later forks are not set. +fn build_chain_config_json( + genesis_fork: ethrex_common::types::Fork, + transition: Option<(ethrex_common::types::Fork, u64)>, + chain_id: u64, +) -> Value { + use ethrex_common::types::Fork; + + let mut cfg = serde_json::json!({ + "chainId": chain_id, + "homesteadBlock": 0, + "daoForkBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "mergeNetsplitBlock": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + // Required by ChainConfig even on pre-Prague chains; use the canonical EIP-6110 address. + "depositContractAddress": "0x00000000219ab540356cBB839Cbe05303d7705Fa", + }); + + let target_fork = transition.map(|(to, _)| to).unwrap_or(genesis_fork); + let obj = cfg.as_object_mut().expect("json object"); + + const TIME_FORKS: &[(Fork, &str)] = &[ + (Fork::Shanghai, "shanghaiTime"), + (Fork::Cancun, "cancunTime"), + (Fork::Prague, "pragueTime"), + (Fork::Osaka, "osakaTime"), + (Fork::BPO1, "bpo1Time"), + (Fork::BPO2, "bpo2Time"), + (Fork::BPO3, "bpo3Time"), + (Fork::BPO4, "bpo4Time"), + (Fork::BPO5, "bpo5Time"), + (Fork::Amsterdam, "amsterdamTime"), + ]; + + for &(fork, field) in TIME_FORKS { + if fork > target_fork { + continue; + } + let time = match transition { + Some((to, t)) if to == fork => t, + _ => 0, + }; + obj.insert(field.into(), time.into()); + } + + cfg +} + +/// Convert EEST's blob-schedule shape (fork-name keys, hex-string values) into ethrex's +/// `ChainConfig::blob_schedule` shape (camelCase keys, numeric values). +fn convert_blob_schedule(eest: &Value) -> Value { + let mut out = serde_json::Map::new(); + let Some(obj) = eest.as_object() else { + return Value::Object(out); + }; + for (k, v) in obj { + out.insert(k.to_lowercase(), convert_blob_entry(v)); + } + Value::Object(out) +} + +fn convert_blob_entry(entry: &Value) -> Value { + let Some(obj) = entry.as_object() else { + return entry.clone(); + }; + serde_json::json!({ + "target": hex_or_num(obj.get("target")), + "max": hex_or_num(obj.get("max")), + "baseFeeUpdateFraction": hex_or_num(obj.get("baseFeeUpdateFraction")), + }) +} + +fn hex_or_num(v: Option<&Value>) -> u64 { + match v { + Some(Value::String(s)) => s + .strip_prefix("0x") + .or_else(|| s.strip_prefix("0X")) + .map(|h| u64::from_str_radix(h, 16).unwrap_or(0)) + .unwrap_or_else(|| s.parse().unwrap_or(0)), + Some(Value::Number(n)) => n.as_u64().unwrap_or(0), + _ => 0, + } +} + +/// Assemble the Geth-style genesis JSON from the fixture header, pre alloc, and chain config. +fn build_genesis_json(header: &FixtureHeader, alloc: &Value, config: Value) -> Value { + let mut genesis = serde_json::json!({ + "config": config, + "alloc": alloc, + "coinbase": header.coinbase, + "difficulty": header.difficulty, + "gasLimit": header.gas_limit, + "nonce": header.nonce, + "mixHash": header.mix_hash, + "timestamp": header.timestamp, + }); + + let obj = genesis.as_object_mut().expect("json object"); + + // Optional header fields — include only when present. + // JSON keys must match Genesis serde camelCase field names. + macro_rules! insert_opt { + ($key:expr, $field:expr) => { + if let Some(ref v) = $field { + obj.insert($key.into(), v.clone().into()); + } + }; + } + insert_opt!("extraData", header.extra_data); + insert_opt!("baseFeePerGas", header.base_fee_per_gas); + insert_opt!("withdrawalsRoot", header.withdrawals_root); + insert_opt!("blobGasUsed", header.blob_gas_used); + insert_opt!("excessBlobGas", header.excess_blob_gas); + insert_opt!("parentBeaconBlockRoot", header.parent_beacon_block_root); + insert_opt!("requestsHash", header.requests_hash); + + genesis +} + +/// Returns `true` when `fork` predates the Engine API (before Paris). +pub fn is_pre_paris(fork: ethrex_common::types::Fork) -> bool { + fork < ethrex_common::types::Fork::Paris +} + +// ─── Tests ──────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pre_paris_fork_skipped() { + let raw = serde_json::json!({ + "test_london": { + "network": "London", + "lastblockhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "genesisBlockHeader": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "number": "0x00", + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x00", + "gasLimit": "0x1000", + "nonce": "0x0000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00" + }, + "pre": {}, + "config": { "chainid": "0x01" }, + "engineNewPayloads": [] + } + }) + .to_string(); + let fixtures: EngineFixtureFile = serde_json::from_str(&raw).unwrap(); + let (_, fixture) = fixtures.iter().next().unwrap(); + let fork = fixture.fork().expect("fork() must succeed for London"); + assert_eq!(fork, ethrex_common::types::Fork::London); + assert!(is_pre_paris(fork), "London is pre-Paris"); + } +} diff --git a/ef_tests/engine/src/harness.rs b/ef_tests/engine/src/harness.rs new file mode 100644 index 0000000..19e4765 --- /dev/null +++ b/ef_tests/engine/src/harness.rs @@ -0,0 +1,280 @@ +// Backend bench measurements (`bench_backends_100`): +// +// InMemory: 3 ms/fixture mean over 100 iters +// RocksDB: 10 ms/fixture mean over 100 iters (tmpfs) +// Host: Linux debian 6.12.74+deb13+1-amd64 #1 SMP PREEMPT_DYNAMIC x86_64 +// +// Targets: <5 ms InMemory, <50 ms RocksDB on tmpfs. + +#[cfg(feature = "rocksdb")] +use std::path::PathBuf; + +use ethrex_common::{H256, types::Genesis}; +use ethrex_rpc::{ + RpcApiContext, + rpc::{map_engine_requests, map_http_requests, rpc_response}, + utils::{RpcNamespace, RpcRequest, RpcRequestId}, +}; + +use crate::engine_ctx::engine_only_context; +use ethrex_storage::{EngineType, Store}; +use serde_json::Value; + +/// Which storage backend to use when constructing a harness. +#[derive(Debug, Clone, Copy)] +pub enum Backend { + /// Fully in-memory; fast, no disk I/O, no cleanup needed. + InMemory, + /// RocksDB on a temporary directory; cleaned up on `Drop` via `_tempdir`. + /// Requires the `rocksdb` feature. + #[cfg(feature = "rocksdb")] + RocksDB, +} + +/// A self-contained in-process harness for exercising the engine API against a +/// single test fixture. Each harness owns its own `Store` initialised from the +/// fixture's genesis; the shared `SyncManager` / `PeerHandler` scaffold is +/// allocated once per process (see `engine_only_context`). +pub struct EngineApiHarness { + pub ctx: RpcApiContext, + /// Keeps the RocksDB temp directory alive for the lifetime of the harness. + /// `None` for `Backend::InMemory`. + pub _tempdir: Option, +} + +/// Returns `/dev/shm` if it exists and is writable (tmpfs, good for RocksDB +/// benchmarks), otherwise falls back to `std::env::temp_dir()`. +#[cfg(feature = "rocksdb")] +fn prefer_tmpfs_dir() -> PathBuf { + let shm = PathBuf::from("/dev/shm"); + if shm.is_dir() + && std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(false) + .open(shm.join(".hive_write_probe")) + .map(|_| std::fs::remove_file(shm.join(".hive_write_probe")).is_ok()) + .unwrap_or(false) + { + shm + } else { + std::env::temp_dir() + } +} + +impl EngineApiHarness { + /// Build a harness from a typed `Genesis`. Hot path: callers should use this + /// when they already hold a parsed `Genesis` to skip a round-trip through JSON. + pub async fn from_genesis(genesis: Genesis, backend: Backend) -> anyhow::Result { + let (store, tempdir) = match backend { + Backend::InMemory => { + let store = Store::new("", EngineType::InMemory)?; + (store, None) + } + #[cfg(feature = "rocksdb")] + Backend::RocksDB => { + let dir = tempfile::TempDir::new_in(prefer_tmpfs_dir())?; + let store = Store::new(dir.path(), EngineType::RocksDB)?; + (store, Some(dir)) + } + }; + + let mut store = store; + store.add_initial_state(genesis).await?; + let ctx = engine_only_context(store).await; + Ok(Self { + ctx, + _tempdir: tempdir, + }) + } + + /// Convenience for callers that hold a JSON-encoded genesis (mostly tests). + pub async fn from_genesis_json(genesis_json: &str, backend: Backend) -> anyhow::Result { + let genesis: Genesis = serde_json::from_str(genesis_json)?; + Self::from_genesis(genesis, backend).await + } + + /// Dispatch a pre-built `RpcRequest` directly. Skips the JSON envelope + /// round-trip — the per-method `serde_json::from_value::(params)` inside + /// each handler still exercises the serde path that matters for coverage. + async fn dispatch(&self, req: RpcRequest) -> anyhow::Result { + let res = match req.namespace() { + Ok(RpcNamespace::Engine) => map_engine_requests(&req, self.ctx.clone()).await, + Ok(_) => map_http_requests(&req, self.ctx.clone()).await, + Err(e) => Err(e), + }; + Ok(rpc_response(req.id.clone(), res)?) + } + + /// Round-trip a JSON-RPC request body through the in-process dispatcher. + /// Exercises the full envelope-parse path; used by external callers / tests. + pub async fn call_raw(&self, body: &str) -> anyhow::Result { + let req: RpcRequest = serde_json::from_str(body)?; + self.dispatch(req).await + } + + /// Build an RpcRequest and dispatch it directly (no envelope round-trip). + async fn call(&self, method: &str, params: Vec) -> anyhow::Result { + let req = RpcRequest { + id: RpcRequestId::Number(1), + jsonrpc: "2.0".to_string(), + method: method.to_string(), + params: Some(params), + }; + self.dispatch(req).await + } + + /// Call `engine_forkchoiceUpdatedVx` with `head` as head, safe, and finalized hash. + /// `version` must be 1–4; passes no payload attributes. + pub async fn fcu(&self, version: u8, head: H256) -> anyhow::Result { + let fcs = serde_json::json!({ + "headBlockHash": format!("{head:#x}"), + "safeBlockHash": format!("{head:#x}"), + "finalizedBlockHash": format!("{head:#x}"), + }); + self.call(&format!("engine_forkchoiceUpdatedV{version}"), vec![fcs]) + .await + } + + /// Call `engine_newPayloadVx`. `params` is the EEST fixture's pre-built params array. + pub async fn new_payload(&self, version: u8, params: &[Value]) -> anyhow::Result { + self.call(&format!("engine_newPayloadV{version}"), params.to_vec()) + .await + } + + /// Call `eth_getBlockByNumber("0x0", false)` and return the raw JSON response. + pub async fn get_block_by_number_zero(&self) -> anyhow::Result { + self.call( + "eth_getBlockByNumber", + vec![Value::String("0x0".to_string()), Value::Bool(false)], + ) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const GENESIS: &str = include_str!("../../../../fixtures/genesis/l1.json"); + + /// Verify harness construction completes in reasonable time and the + /// resulting context has a live syncer. + #[tokio::test] + async fn harness_builds_from_genesis() { + let h = EngineApiHarness::from_genesis_json(GENESIS, Backend::InMemory) + .await + .expect("harness construction failed"); + assert!(h.ctx.syncer.is_some(), "syncer must be Some"); + assert!(h.ctx.peer_handler.is_none(), "peer_handler must be None"); + } + + /// Shared syncer: building two harnesses should reuse the same Arc. + #[tokio::test] + async fn shared_syncer_is_same_arc() { + use std::sync::Arc; + let h1 = EngineApiHarness::from_genesis_json(GENESIS, Backend::InMemory) + .await + .expect("first harness"); + let h2 = EngineApiHarness::from_genesis_json(GENESIS, Backend::InMemory) + .await + .expect("second harness"); + let p1 = Arc::as_ptr(h1.ctx.syncer.as_ref().unwrap()); + let p2 = Arc::as_ptr(h2.ctx.syncer.as_ref().unwrap()); + assert_eq!(p1, p2, "both harnesses must share the same SyncManager Arc"); + } + + /// Smoke test: RocksDB harness constructs and drops cleanly, tempdir is set. + /// + /// Requires the `rocksdb` feature: `cargo test -p ef_tests-engine --features rocksdb`. + #[cfg(feature = "rocksdb")] + #[tokio::test] + async fn rocksdb_harness_builds_and_tempdir_is_some() { + let h = EngineApiHarness::from_genesis_json(GENESIS, Backend::RocksDB) + .await + .expect("RocksDB harness construction failed"); + assert!(h._tempdir.is_some(), "_tempdir must be Some for RocksDB"); + drop(h); // TempDir RAII cleanup + } + + /// Bench guard: mean per-fixture harness construction must stay below the + /// stated targets for both backends over 100 iterations. + /// + /// Run with: + /// `cargo test -p ef_tests-engine --release --features rocksdb \ + /// -- --include-ignored bench_backends_100` + #[ignore = "timing guard; run with --release --include-ignored"] + #[tokio::test] + async fn bench_backends_100() { + const ITERATIONS: u32 = 100; + const INMEM_LIMIT_MS: u128 = 5; + #[cfg(feature = "rocksdb")] + const ROCKSDB_LIMIT_MS: u128 = 50; + + // InMemory + let t0 = std::time::Instant::now(); + for _ in 0..ITERATIONS { + let _h = EngineApiHarness::from_genesis_json(GENESIS, Backend::InMemory) + .await + .expect("harness construction failed in bench"); + } + let inmem_mean_ms = t0.elapsed().as_millis() / u128::from(ITERATIONS); + + // RocksDB (tmpfs when available) + #[cfg(feature = "rocksdb")] + let rocksdb_mean_ms = { + let t1 = std::time::Instant::now(); + for _ in 0..ITERATIONS { + let _h = EngineApiHarness::from_genesis_json(GENESIS, Backend::RocksDB) + .await + .expect("RocksDB harness construction failed in bench"); + } + t1.elapsed().as_millis() / u128::from(ITERATIONS) + }; + #[cfg(not(feature = "rocksdb"))] + let rocksdb_mean_ms: u128 = 0; + + eprintln!( + "bench_backends_100: InMemory={inmem_mean_ms} ms/iter RocksDB={rocksdb_mean_ms} ms/iter ratio={:.1}x", + rocksdb_mean_ms as f64 / inmem_mean_ms.max(1) as f64, + ); + + assert!( + inmem_mean_ms <= INMEM_LIMIT_MS, + "InMemory mean ({inmem_mean_ms} ms) exceeded {INMEM_LIMIT_MS} ms limit" + ); + #[cfg(feature = "rocksdb")] + assert!( + rocksdb_mean_ms <= ROCKSDB_LIMIT_MS, + "RocksDB mean ({rocksdb_mean_ms} ms) exceeded {ROCKSDB_LIMIT_MS} ms limit" + ); + } + + /// Verify `eth_getBlockByNumber("0x0", false)` succeeds and returns the genesis block. + #[tokio::test] + async fn eth_get_block_by_number_zero_returns_genesis() { + let h = EngineApiHarness::from_genesis_json(GENESIS, Backend::InMemory) + .await + .expect("harness construction failed"); + + let resp = h + .get_block_by_number_zero() + .await + .expect("get_block_by_number_zero must succeed"); + + // Must have no error field. + assert!( + resp.get("error").is_none(), + "response must not have error: {resp}" + ); + + // result.hash must be a non-zero H256. + let hash_str = resp["result"]["hash"] + .as_str() + .expect("result.hash must be a string"); + let hash: H256 = hash_str + .parse() + .expect("result.hash must be a valid H256 hex"); + assert_ne!(hash, H256::zero(), "genesis block hash must be non-zero"); + } +} diff --git a/ef_tests/engine/src/lib.rs b/ef_tests/engine/src/lib.rs new file mode 100644 index 0000000..25293ca --- /dev/null +++ b/ef_tests/engine/src/lib.rs @@ -0,0 +1,11 @@ +pub mod engine_ctx; +pub mod exception_mapper; +pub mod fixture; +pub mod harness; +pub mod report; +pub mod runner; + +pub use fixture::{EngineFixture, EngineFixtureFile}; +pub use harness::{Backend, EngineApiHarness}; +pub use report::{render_failures, render_summary}; +pub use runner::{FixtureFailure, RunOptions, run_fixture}; diff --git a/ef_tests/engine/src/report.rs b/ef_tests/engine/src/report.rs new file mode 100644 index 0000000..9e202fe --- /dev/null +++ b/ef_tests/engine/src/report.rs @@ -0,0 +1,14 @@ +use crate::runner::FixtureFailure; +use std::fmt::Write as _; + +pub fn render_failures(failures: &[(String, FixtureFailure)]) -> String { + let mut out = String::new(); + for (name, failure) in failures { + writeln!(out, "FAIL {name}: {failure}").expect("write to String is infallible"); + } + out +} + +pub fn render_summary(total: usize, passed: usize, skipped: usize, failed: usize) -> String { + format!("=== {total} fixtures: {passed} passed, {skipped} skipped, {failed} failed ===",) +} diff --git a/ef_tests/engine/src/runner.rs b/ef_tests/engine/src/runner.rs new file mode 100644 index 0000000..82d4d20 --- /dev/null +++ b/ef_tests/engine/src/runner.rs @@ -0,0 +1,456 @@ +use std::fmt; + +use ethrex_common::H256; +use serde_json::Value; + +use crate::fixture::{EngineFixture, FixturePayload, ValidationError, is_pre_paris}; +use crate::harness::{Backend, EngineApiHarness}; + +// ─── Public types ───────────────────────────────────────────────────────────── + +#[derive(Debug)] +pub struct RunOptions { + pub backend: Backend, + pub strict_exceptions: bool, +} + +impl RunOptions { + pub fn from_env() -> Self { + Self { + backend: parse_backend_env(), + strict_exceptions: std::env::var("ETHREX_ENGINE_STRICT_EXCEPTIONS") + .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) + .unwrap_or(false), + } + } +} + +fn parse_backend_env() -> Backend { + match std::env::var("ETHREX_ENGINE_BACKEND").as_deref() { + Ok("inmemory") | Err(_) => Backend::InMemory, + #[cfg(feature = "rocksdb")] + Ok("rocksdb") => Backend::RocksDB, + Ok(other) => panic!( + "ETHREX_ENGINE_BACKEND='{other}' invalid; valid: inmemory{}", + if cfg!(feature = "rocksdb") { + ", rocksdb" + } else { + "" + } + ), + } +} + +#[derive(Debug)] +pub enum FixtureFailure { + SkippedPreParis, + FixtureParse(String), + HarnessSetup(String), + EmptyPayloads, + InitialFcu(String), + GenesisRpc(String), + GenesisMismatch { + expected: H256, + got: H256, + }, + PayloadRpc { + index: usize, + msg: String, + }, + PayloadParse { + index: usize, + msg: String, + }, + WrongStatus { + index: usize, + expected: String, + got: String, + validation_error: Option, + }, + WrongErrorCode { + index: usize, + want: i32, + got: i32, + msg: String, + }, + UnexpectedJsonRpcError { + index: usize, + code: i32, + msg: String, + }, + MissingErrorCode { + index: usize, + want: i32, + }, + MissingValidationError { + index: usize, + }, + ValidationErrorMismatch { + index: usize, + expected: Vec, + got: String, + strict: bool, + }, + MalformedResponse { + index: usize, + detail: String, + }, + FollowupFcu { + index: usize, + msg: String, + }, +} + +impl FixtureFailure { + pub fn is_skip(&self) -> bool { + matches!(self, FixtureFailure::SkippedPreParis) + } +} + +impl fmt::Display for FixtureFailure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FixtureFailure::SkippedPreParis => write!(f, "skipped reason=pre-Paris fork"), + FixtureFailure::FixtureParse(e) => write!(f, "fixture_parse error={e}"), + FixtureFailure::HarnessSetup(e) => write!(f, "harness_setup error={e}"), + FixtureFailure::EmptyPayloads => write!(f, "empty_payloads expected=at least 1"), + FixtureFailure::InitialFcu(e) => write!(f, "initial_fcu error={e}"), + FixtureFailure::GenesisRpc(e) => write!(f, "genesis_rpc error={e}"), + FixtureFailure::GenesisMismatch { expected, got } => { + write!(f, "genesis_mismatch expected={expected:#x} got={got:#x}") + } + FixtureFailure::PayloadRpc { index, msg } => { + write!(f, "payload_rpc[{index}] error={msg}") + } + FixtureFailure::PayloadParse { index, msg } => { + write!(f, "payload_parse[{index}] error={msg}") + } + FixtureFailure::WrongStatus { + index, + expected, + got, + validation_error, + } => match validation_error { + Some(ve) => write!( + f, + "wrong_status[{index}] expected={expected} got={got} validationError={ve}" + ), + None => write!(f, "wrong_status[{index}] expected={expected} got={got}"), + }, + FixtureFailure::WrongErrorCode { + index, + want, + got, + msg, + } => write!( + f, + "wrong_error_code[{index}] expected={want} got={got} msg={msg}" + ), + FixtureFailure::UnexpectedJsonRpcError { index, code, msg } => { + write!( + f, + "unexpected_jsonrpc_error[{index}] code={code} msg={msg}" + ) + } + FixtureFailure::MissingErrorCode { index, want } => { + write!(f, "missing_error_code[{index}] expected_code={want}") + } + FixtureFailure::MissingValidationError { index } => { + write!( + f, + "missing_validation_error[{index}] expected=non-null validationError" + ) + } + FixtureFailure::ValidationErrorMismatch { + index, + expected, + got, + strict, + } => write!( + f, + "validation_error_mismatch[{index}] expected={expected:?} got={got:?} strict={strict}" + ), + FixtureFailure::MalformedResponse { index, detail } => { + write!(f, "malformed_response[{index}] detail={detail}") + } + FixtureFailure::FollowupFcu { index, msg } => { + write!(f, "followup_fcu[{index}] error={msg}") + } + } + } +} + +// ─── Main entry point ───────────────────────────────────────────────────────── + +pub async fn run_fixture( + name: &str, + fix: &EngineFixture, + opts: &RunOptions, +) -> Result<(), FixtureFailure> { + // 1. Pre-Paris skip + let fork = fix + .fork() + .map_err(|e| FixtureFailure::FixtureParse(e.to_string()))?; + if is_pre_paris(fork) { + return Err(FixtureFailure::SkippedPreParis); + } + + // 2. Build harness + let genesis = fix + .build_genesis() + .map_err(|e| FixtureFailure::FixtureParse(e.to_string()))?; + let harness = Box::pin(EngineApiHarness::from_genesis(genesis, opts.backend)) + .await + .map_err(|e| FixtureFailure::HarnessSetup(e.to_string()))?; + + // 3. Initial FCU to genesis (mirrors test_via_engine.py:80–105) + let first = fix + .engine_new_payloads + .first() + .ok_or(FixtureFailure::EmptyPayloads)?; + let resp = Box::pin(harness.fcu( + first.forkchoice_updated_version, + fix.genesis_block_header.block_hash, + )) + .await + .map_err(|e| FixtureFailure::InitialFcu(e.to_string()))?; + assert_fcu_valid(&resp).map_err(FixtureFailure::InitialFcu)?; + + // 4. Genesis hash check (mirrors test_via_engine.py:107–122) + let block = Box::pin(harness.get_block_by_number_zero()) + .await + .map_err(|e| FixtureFailure::GenesisRpc(e.to_string()))?; + let got_hash = parse_block_hash(&block).map_err(FixtureFailure::GenesisRpc)?; + if got_hash != fix.genesis_block_header.block_hash { + return Err(FixtureFailure::GenesisMismatch { + expected: fix.genesis_block_header.block_hash, + got: got_hash, + }); + } + + // 5. Per-payload loop (mirrors test_via_engine.py:124–240) + for (i, payload) in fix.engine_new_payloads.iter().enumerate() { + let resp = Box::pin(harness.new_payload(payload.new_payload_version, &payload.params)) + .await + .map_err(|e| FixtureFailure::PayloadRpc { + index: i, + msg: e.to_string(), + })?; + check_payload_response(&resp, payload, i, opts.strict_exceptions, name)?; + if payload.valid() { + let head = payload + .head_block_hash() + .map_err(|e| FixtureFailure::PayloadParse { + index: i, + msg: e.to_string(), + })?; + let fcu_resp = Box::pin(harness.fcu(payload.forkchoice_updated_version, head)) + .await + .map_err(|e| FixtureFailure::FollowupFcu { + index: i, + msg: e.to_string(), + })?; + assert_fcu_valid(&fcu_resp) + .map_err(|e| FixtureFailure::FollowupFcu { index: i, msg: e })?; + } + } + + Ok(()) +} + +// ─── Private helpers ────────────────────────────────────────────────────────── + +fn check_payload_response( + resp: &Value, + payload: &FixturePayload, + index: usize, + strict: bool, + name: &str, +) -> Result<(), FixtureFailure> { + // JSON-RPC error path + if let Some(err) = resp.get("error") { + let got_code = err.get("code").and_then(|v| v.as_i64()).unwrap_or(0) as i32; + let got_msg = err + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string(); + match payload.error_code { + Some(want) if want == got_code => return Ok(()), + Some(want) => { + return Err(FixtureFailure::WrongErrorCode { + index, + want, + got: got_code, + msg: got_msg, + }); + } + None => { + return Err(FixtureFailure::UnexpectedJsonRpcError { + index, + code: got_code, + msg: got_msg, + }); + } + } + } + + // Success path — extract status + let result = resp + .get("result") + .ok_or_else(|| FixtureFailure::MalformedResponse { + index, + detail: "missing result".into(), + })?; + let status = result + .get("status") + .and_then(|v| v.as_str()) + .ok_or_else(|| FixtureFailure::MalformedResponse { + index, + detail: "missing status".into(), + })? + .to_string(); + + let expected = if payload.valid() { "VALID" } else { "INVALID" }; + if status != expected { + let validation_error = result + .get("validationError") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + return Err(FixtureFailure::WrongStatus { + index, + expected: expected.into(), + got: status, + validation_error, + }); + } + + // Expected error code but got success response + if let Some(want) = payload.error_code { + return Err(FixtureFailure::MissingErrorCode { index, want }); + } + + // INVALID payloads: check validationError only when the fixture expects one + if status == "INVALID" { + let got_ve = result + .get("validationError") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()); + match (&payload.validation_error, got_ve) { + (None, _) => return Ok(()), + (Some(_), None) => return Err(FixtureFailure::MissingValidationError { index }), + (Some(expected_ve), Some(got)) => { + let candidates: Vec<&str> = match expected_ve { + ValidationError::Single(s) => vec![s.as_str()], + ValidationError::List(v) => v.iter().map(|s| s.as_str()).collect(), + }; + // Use the ported EthrexExceptionMapper: try the canonical-exception lookup + // first; fall back to literal substring for forward-compatibility with + // exceptions the mapper hasn't been taught yet. + let matches = candidates + .iter() + .any(|c| crate::exception_mapper::matches(c, &got) || got.contains(c)); + if !matches { + let f = FixtureFailure::ValidationErrorMismatch { + index, + expected: candidates.iter().map(|s| s.to_string()).collect(), + got, + strict, + }; + if strict { + return Err(f); + } else { + eprintln!("warn [{name}]: {f}"); + } + } + } + } + } + + Ok(()) +} + +/// Verify an FCU response contains `payloadStatus.status == "VALID"`. +fn assert_fcu_valid(resp: &Value) -> Result<(), String> { + if let Some(err) = resp.get("error") { + let msg = err + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + return Err(format!("FCU returned JSON-RPC error: {msg}")); + } + let status = resp + .get("result") + .and_then(|r| r.get("payloadStatus")) + .and_then(|ps| ps.get("status")) + .and_then(|s| s.as_str()) + .unwrap_or(""); + if status != "VALID" { + return Err(format!("FCU status expected=VALID got={status}")); + } + Ok(()) +} + +/// Extract `result.hash` from an `eth_getBlockByNumber` response and parse it. +fn parse_block_hash(block_resp: &Value) -> Result { + let hash_str = block_resp + .get("result") + .and_then(|r| r.get("hash")) + .and_then(|v| v.as_str()) + .ok_or_else(|| "result.hash missing or not a string".to_string())?; + hash_str + .parse::() + .map_err(|e| format!("invalid block hash hex '{hash_str}': {e}")) +} + +// ─── Tests ──────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use crate::fixture::EngineFixtureFile; + + fn inmem_opts() -> RunOptions { + RunOptions { + backend: Backend::InMemory, + strict_exceptions: false, + } + } + + /// Pre-Paris fixtures must return SkippedPreParis (is_skip() == true). + #[tokio::test] + async fn pre_paris_returns_skip() { + let raw = serde_json::json!({ + "test_london_skip": { + "network": "London", + "lastblockhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "genesisBlockHeader": { + "hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "number": "0x00", + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x00", + "gasLimit": "0x1000", + "nonce": "0x0000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00" + }, + "pre": {}, + "config": { "chainid": "0x01" }, + "engineNewPayloads": [{ + "params": [{}], + "newPayloadVersion": "1", + "forkchoiceUpdatedVersion": "1" + }] + } + }) + .to_string(); + let fixtures: EngineFixtureFile = serde_json::from_str(&raw).unwrap(); + let opts = inmem_opts(); + let (name, fixture) = fixtures.iter().next().unwrap(); + let err = run_fixture(name, fixture, &opts) + .await + .expect_err("London fixture must be skipped"); + assert!(err.is_skip(), "expected SkippedPreParis, got: {err}"); + } +} diff --git a/ef_tests/engine/tests/all.rs b/ef_tests/engine/tests/all.rs new file mode 100644 index 0000000..87eab37 --- /dev/null +++ b/ef_tests/engine/tests/all.rs @@ -0,0 +1,143 @@ +// Parallelism strategy: each JSON file holds many fixtures (typically dozens to +// hundreds). We iterate fixtures serially within the test fn; datatest-stable +// parallelises across files using libtest. Tune concurrency with RUST_TEST_THREADS. + +use std::path::Path; +use std::sync::OnceLock; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use ef_tests_engine::{ + EngineFixtureFile, FixtureFailure, RunOptions, render_failures, render_summary, run_fixture, +}; +use regex::Regex; + +// Aggregate fixture counters across all `engine_runner` calls. libtest reports +// one test per JSON file, but each file holds many fixtures; these atomics +// surface the inner totals in a final line printed at process exit. +static F_PASSED: AtomicUsize = AtomicUsize::new(0); +static F_FAILED: AtomicUsize = AtomicUsize::new(0); +static F_SKIPPED: AtomicUsize = AtomicUsize::new(0); +static F_FILTERED: AtomicUsize = AtomicUsize::new(0); + +#[ctor::dtor] +fn print_fixture_summary() { + let p = F_PASSED.load(Ordering::Relaxed); + let f = F_FAILED.load(Ordering::Relaxed); + let s = F_SKIPPED.load(Ordering::Relaxed); + let fi = F_FILTERED.load(Ordering::Relaxed); + let total = p + f + s + fi; + if total == 0 { + return; + } + + // Mirror libtest's coloring rules: respect NO_COLOR, CARGO_TERM_COLOR, and TTY. + let color = { + use std::io::IsTerminal; + let force = std::env::var("CARGO_TERM_COLOR").ok(); + match force.as_deref() { + Some("always") => true, + Some("never") => false, + _ => std::env::var_os("NO_COLOR").is_none() && std::io::stderr().is_terminal(), + } + }; + let (g, r, y, c, b, z) = if color { + ( + "\x1b[32m", "\x1b[31m", "\x1b[33m", "\x1b[36m", "\x1b[1m", "\x1b[0m", + ) + } else { + ("", "", "", "", "", "") + }; + let verdict = if f > 0 { + format!("{r}{b}FAILED{z}") + } else { + format!("{g}{b}ok{z}") + }; + eprintln!( + "\nfixture result: {verdict}. {p} {g}passed{z}; {f} {r}failed{z}; \ + {s} {y}skipped{z}; {fi} {c}filtered{z}; {total} total", + ); +} + +// Path patterns: if any pattern is contained in the file path, the file is skipped +// entirely. Entries are filled in once real failures are classified (Task 4.8). +const SKIP_PATTERNS: &[&str] = &[]; + +static RT: OnceLock = OnceLock::new(); + +fn runtime() -> &'static tokio::runtime::Runtime { + RT.get_or_init(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("tokio runtime init failed") + }) +} + +/// Optional fixture-name regex (mirrors hive's `--sim.limit`). +/// Set via `ETHREX_ENGINE_LIMIT=` to run only fixtures whose name matches. +static LIMIT: OnceLock> = OnceLock::new(); + +fn limit() -> Option<&'static Regex> { + LIMIT + .get_or_init(|| { + std::env::var("ETHREX_ENGINE_LIMIT").ok().map(|pat| { + Regex::new(&pat) + .unwrap_or_else(|e| panic!("ETHREX_ENGINE_LIMIT='{pat}' invalid regex: {e}")) + }) + }) + .as_ref() +} + +fn engine_runner(path: &Path) -> datatest_stable::Result<()> { + // Skip entire file if path matches any skip pattern. + let path_str = path.to_string_lossy(); + for pat in SKIP_PATTERNS { + if path_str.contains(pat) { + return Ok(()); + } + } + + let raw = std::fs::read_to_string(path)?; + let fixtures: EngineFixtureFile = serde_json::from_str(&raw)?; + + let opts = RunOptions::from_env(); + let limit = limit(); + + let mut total = 0usize; + let mut passed = 0usize; + let mut skipped = 0usize; + let mut filtered = 0usize; + let mut failures: Vec<(String, FixtureFailure)> = Vec::new(); + + for (name, fixture) in &fixtures { + if let Some(re) = limit + && !re.is_match(name) + { + filtered += 1; + continue; + } + total += 1; + let result = runtime().block_on(run_fixture(name, fixture, &opts)); + match result { + Ok(()) => passed += 1, + Err(e) if e.is_skip() => skipped += 1, + Err(e) => failures.push((name.clone(), e)), + } + } + + F_PASSED.fetch_add(passed, Ordering::Relaxed); + F_SKIPPED.fetch_add(skipped, Ordering::Relaxed); + F_FILTERED.fetch_add(filtered, Ordering::Relaxed); + F_FAILED.fetch_add(failures.len(), Ordering::Relaxed); + + if failures.is_empty() { + return Ok(()); + } + + let mut report = render_failures(&failures); + report.push('\n'); + report.push_str(&render_summary(total, passed, skipped, failures.len())); + Err(report.into()) +} + +datatest_stable::harness!(engine_runner, "vectors/eest/", r".*\.json$"); diff --git a/ef_tests/state/.fixtures_url_amsterdam b/ef_tests/state/.fixtures_url_amsterdam index 2290401..566881c 100644 --- a/ef_tests/state/.fixtures_url_amsterdam +++ b/ef_tests/state/.fixtures_url_amsterdam @@ -1 +1 @@ -https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.6.1/fixtures_bal.tar.gz +https://github.com/ethereum/execution-specs/releases/download/tests-bal%40v7.2.0/fixtures_bal.tar.gz diff --git a/ef_tests/state/Makefile b/ef_tests/state/Makefile index 336e532..be69ead 100644 --- a/ef_tests/state/Makefile +++ b/ef_tests/state/Makefile @@ -31,9 +31,11 @@ $(VECTORS_DIR): $(STATETEST_ARTIFACT) $(AMSTERDAM_ARTIFACT): $(AMSTERDAM_FIXTURES_FILE) curl -L -o $(AMSTERDAM_ARTIFACT) $(AMSTERDAM_URL) -amsterdam-vectors: $(AMSTERDAM_ARTIFACT) $(VECTORS_DIR) +$(VECTORS_DIR)/state_tests/for_amsterdam: $(AMSTERDAM_ARTIFACT) $(VECTORS_DIR) tar -xzf $(AMSTERDAM_ARTIFACT) --strip-components=2 -C $(VECTORS_DIR)/state_tests fixtures/state_tests/for_amsterdam +amsterdam-vectors: $(VECTORS_DIR)/state_tests/for_amsterdam + help: ## 📚 Show help for each of the Makefile recipes @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @@ -61,21 +63,21 @@ refresh-evm-ef-tests: clean-evm-ef-tests download-evm-ef-tests ## Cleans and re- run-evm-ef-tests: ## 🏃‍♂️ Run EF Tests if [ "$(QUIET)" = "true" ]; then \ - time cargo test --quiet --test all --profile release-with-debug -- $(flags) --summary;\ + time cargo test --quiet --test all --profile release-fast -- $(flags) --summary;\ elif [ "$(DEBUG)" = "true" ]; then \ time cargo test --test all -- $(flags);\ else \ - time cargo test --test all --profile release-with-debug -- $(flags);\ + time cargo test --test all --profile release-fast -- $(flags);\ fi run-evm-ef-tests-ci: $(VECTORS_DIR) ## 🏃‍♂️ Run EF Tests only with LEVM and without spinner, for CI. - time cargo test -p ef_tests-state --test all --profile release-with-debug -- --summary + time cargo test -p ef_tests-state --test all --profile release-fast -- --summary test-levm: $(VECTORS_DIR) $(MAKE) run-evm-ef-tests flags="--summary" test-levm-nostd-crypto: $(VECTORS_DIR) ## 🧪 Run EF state tests with no_std crypto fallbacks - cargo test --test all --profile release-with-debug --no-default-features --features nostd-crypto -- --summary + cargo test --test all --profile release-fast --no-default-features --features nostd-crypto -- --summary test-revm: $(VECTORS_DIR) $(MAKE) run-evm-ef-tests flags="--revm" @@ -114,14 +116,14 @@ samply-run-ef-tests-revm: @for dir in $(SUBDIRS); do\ CARGO_PROFILE_RELEASE_DEBUG=true samply record --save-only \ -o levm_perfgraphs/samply/ef_tests/revm/prof_$$dir.json \ - cargo test --profile release-with-debug -p ef_tests-state --test all -- --summary --revm --tests $$dir;\ + cargo test --profile release-fast -p ef_tests-state --test all -- --summary --revm --tests $$dir;\ done samply-run-ef-tests-levm: @for dir in $(SUBDIRS); do\ CARGO_PROFILE_RELEASE_DEBUG=true samply record --save-only \ -o levm_perfgraphs/samply/ef_tests/state/prof_$$dir.json \ - cargo test --profile release-with-debug -p ef_tests-state --test all -- --summary --tests $$dir;\ + cargo test --profile release-fast -p ef_tests-state --test all -- --summary --tests $$dir;\ done ################ diff --git a/ef_tests/state/runner/levm_runner.rs b/ef_tests/state/runner/levm_runner.rs index 4990484..20c4d15 100644 --- a/ef_tests/state/runner/levm_runner.rs +++ b/ef_tests/state/runner/levm_runner.rs @@ -230,6 +230,7 @@ pub fn prepare_vm_for_tx<'a>( is_privileged: false, fee_token: None, disable_balance_check: false, + is_system_call: false, }, db, &tx, diff --git a/ef_tests/state_v2/Makefile b/ef_tests/state_v2/Makefile index e1107ed..b82c68b 100644 --- a/ef_tests/state_v2/Makefile +++ b/ef_tests/state_v2/Makefile @@ -4,7 +4,7 @@ help: ## 📚 Show help for each of the Makefile recipes @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' run-tests: ## 🧪 Run all tests with optional flags. Usage: make run-tests flags="--flag1 --flag2" - cargo test --package ef_tests-statev2 --test all --release -- $(flags) + cargo test --package ef_tests-statev2 --test all --profile release-fast -- $(flags) clean-reports: ## 🗑️ Delete all generated test reports rm -rf ./reports diff --git a/ef_tests/state_v2/src/main.rs b/ef_tests/state_v2/src/main.rs index 100aba5..6b484aa 100644 --- a/ef_tests/state_v2/src/main.rs +++ b/ef_tests/state_v2/src/main.rs @@ -1,14 +1,57 @@ #![allow(clippy::all)] -use clap::Parser; +use std::process::ExitCode; + +use clap::{Parser, Subcommand}; use ef_tests_statev2::modules::{ error::RunnerError, parser::{RunnerOptions, parse_tests}, + statetest::{self, StatetestOptions}, }; +#[derive(Parser, Debug)] +#[command(name = "ef-tests-state-v2")] +struct Cli { + #[command(subcommand)] + command: Option, + + /// Default (no subcommand): bulk-run the EF state-test suite. + #[command(flatten)] + runner: RunnerOptions, +} + +#[derive(Subcommand, Debug)] +enum Command { + /// Run a single EF state-test fixture and emit EIP-3155 trace + stateRoot to + /// stderr. Designed for goevmlab differential fuzzing. + Statetest(StatetestOptions), +} + #[tokio::main] -pub async fn main() -> Result<(), RunnerError> { - let mut runner_options = RunnerOptions::parse(); +pub async fn main() -> ExitCode { + let cli = Cli::parse(); + + // Errors from a subcommand map to exit code 2 so that goevmlab can distinguish + // a state-root mismatch (deliberate exit 1) from an actual internal failure. + match cli.command { + Some(Command::Statetest(opts)) => match statetest::run(opts).await { + Ok(code) => code, + Err(e) => { + eprintln!("statetest error: {e:?}"); + ExitCode::from(2) + } + }, + None => match run_bulk(cli.runner).await { + Ok(()) => ExitCode::SUCCESS, + Err(e) => { + eprintln!("error: {e:?}"); + ExitCode::from(2) + } + }, + } +} + +async fn run_bulk(mut runner_options: RunnerOptions) -> Result<(), RunnerError> { println!("Runner options: {:#?}", runner_options); println!("\nParsing test files..."); diff --git a/ef_tests/state_v2/src/modules/block_runner.rs b/ef_tests/state_v2/src/modules/block_runner.rs index f0c58c2..c752123 100644 --- a/ef_tests/state_v2/src/modules/block_runner.rs +++ b/ef_tests/state_v2/src/modules/block_runner.rs @@ -13,6 +13,20 @@ use ethrex_levm::{ vm::{VM, VMType}, }; use std::str::FromStr; +use std::sync::Arc; + +thread_local! { + /// Per-OS-thread merkleization pool, lazily built on first use. See the + /// matching helper in `tooling/ef_tests/blockchain/test_runner.rs` for the + /// reasoning; the merkle protocol requires exclusive ownership of its pool + /// per concurrent caller, and keying by `thread_local!` provides that. + static MERKLE_POOL: std::cell::OnceCell> = + const { std::cell::OnceCell::new() }; +} + +fn merkle_pool() -> Arc { + MERKLE_POOL.with(|cell| cell.get_or_init(Blockchain::build_merkle_pool).clone()) +} use crate::modules::types::TestCase; use crate::modules::{ @@ -23,7 +37,26 @@ use crate::modules::{ }; pub async fn run_tests(tests: Vec) -> Result<(), RunnerError> { + // Fusaka EIPs that block-mode supports; mirrors the allowlist in runner.rs. + // TODO: drop once all Fusaka EIPs land. + let fusaka_eips_to_test: Vec<&str> = + vec!["eip-7594", "eip-7939", "eip-7918", "eip-7892", "eip-7883"]; + for test in &tests { + // Apply the same gating runner.rs uses so we don't unconditionally run + // every Osaka fixture in block mode. Fixtures without `_info` (e.g. + // goevmlab-generated) bypass the filter — we can't read the EIP list, + // so silently dropping them would be wrong. + if test.path.to_str().unwrap().contains("osaka") + && let Some(spec) = test + ._info + .as_ref() + .and_then(|info| info.reference_spec.as_deref()) + && !fusaka_eips_to_test.iter().any(|eip| spec.contains(eip)) + { + continue; + } + println!("Running test group: {}", test.name); for test_case in &test.test_cases { let res = run_test(test, test_case).await; @@ -43,7 +76,7 @@ pub async fn run_test(test: &Test, test_case: &TestCase) -> Result<(), RunnerErr let tracer = LevmCallTracer::disabled(); let (mut db, initial_block_hash, store, _genesis) = - load_initial_state(test, &test_case.fork).await; + load_initial_state(test, &test_case.fork, false).await; let mut vm = VM::new(env.clone(), &mut db, &tx, tracer, VMType::L1, &NativeCrypto) .map_err(RunnerError::VMError)?; let execution_result = vm.execute(); @@ -81,7 +114,7 @@ pub async fn run_test(test: &Test, test_case: &TestCase) -> Result<(), RunnerErr // So they could be specified in the test but if the fork is e.g. Paris we should set them to None despite that. // Otherwise it will fail block header validations let (excess_blob_gas, blob_gas_used, parent_beacon_block_root, requests_hash) = match fork { - Fork::Prague | Fork::Cancun => { + Fork::Cancun | Fork::Prague | Fork::Osaka => { let blob_gas_used = match tx { Transaction::EIP4844Transaction(blob_tx) => { Some(get_total_blob_gas(&blob_tx) as u64) @@ -97,10 +130,10 @@ pub async fn run_test(test: &Test, test_case: &TestCase) -> Result<(), RunnerErr .unwrap(), ); let parent_beacon_block_root = Some(H256::zero()); - let requests_hash = if fork == Fork::Prague { - Some(*DEFAULT_REQUESTS_HASH) - } else { - None + // Prague added requests; Osaka inherits the same mechanism. + let requests_hash = match fork { + Fork::Prague | Fork::Osaka => Some(*DEFAULT_REQUESTS_HASH), + _ => None, }; ( excess_blob_gas, @@ -148,7 +181,7 @@ pub async fn run_test(test: &Test, test_case: &TestCase) -> Result<(), RunnerErr // 3. Create Blockchain and add block. - let blockchain = Blockchain::new(store, ethrex_blockchain::BlockchainOptions::default()); + let blockchain = Blockchain::default_with_store_and_pool(store, merkle_pool()); let result = blockchain.add_block_pipeline(block, None); diff --git a/ef_tests/state_v2/src/modules/deserialize.rs b/ef_tests/state_v2/src/modules/deserialize.rs index d8d6145..ffa4668 100644 --- a/ef_tests/state_v2/src/modules/deserialize.rs +++ b/ef_tests/state_v2/src/modules/deserialize.rs @@ -138,9 +138,16 @@ where let post_deserialized = HashMap::>::deserialize(deserializer)?; let mut post_parsed = HashMap::new(); for (fork_str, values) in post_deserialized { + // Keep names in sync with the `Fork` enum in `crates/common/types/genesis.rs`. + // An unknown fork name is a hard error so that newly-emitted fixture forks + // surface as a build break (forcing a deserializer/Fork update), rather than + // silently dropping test coverage. let fork = match fork_str.as_str() { "Frontier" => Fork::Frontier, "Homestead" => Fork::Homestead, + "EIP150" => Fork::Tangerine, + "EIP158" => Fork::SpuriousDragon, + "Byzantium" => Fork::Byzantium, "Constantinople" => Fork::Constantinople, "ConstantinopleFix" | "Petersburg" => Fork::Petersburg, "Istanbul" => Fork::Istanbul, @@ -150,9 +157,13 @@ where "Shanghai" => Fork::Shanghai, "Cancun" => Fork::Cancun, "Prague" => Fork::Prague, - "Byzantium" => Fork::Byzantium, - "EIP158" => Fork::SpuriousDragon, - "EIP150" => Fork::Tangerine, + "Osaka" => Fork::Osaka, + "BPO1" => Fork::BPO1, + "BPO2" => Fork::BPO2, + "BPO3" => Fork::BPO3, + "BPO4" => Fork::BPO4, + "BPO5" => Fork::BPO5, + "Amsterdam" => Fork::Amsterdam, other => { return Err(serde::de::Error::custom(format!( "Unknown fork name: {other}", diff --git a/ef_tests/state_v2/src/modules/error.rs b/ef_tests/state_v2/src/modules/error.rs index 68a3bdc..ba20247 100644 --- a/ef_tests/state_v2/src/modules/error.rs +++ b/ef_tests/state_v2/src/modules/error.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use ethrex_levm::errors::VMError; #[derive(Debug)] @@ -6,5 +8,11 @@ pub enum RunnerError { VMError(VMError), EIP7702ShouldNotBeCreateType, FailedToGetIndexValue(String), + /// Wraps an I/O or serde error encountered while parsing a fixture. + /// Holds the offending path and the underlying error message. + ParseFixture { + path: PathBuf, + source: String, + }, Custom(String), } diff --git a/ef_tests/state_v2/src/modules/mod.rs b/ef_tests/state_v2/src/modules/mod.rs index a5b40ca..35b6b6c 100644 --- a/ef_tests/state_v2/src/modules/mod.rs +++ b/ef_tests/state_v2/src/modules/mod.rs @@ -5,5 +5,6 @@ pub mod parser; pub mod report; pub mod result_check; pub mod runner; +pub mod statetest; pub mod types; pub mod utils; diff --git a/ef_tests/state_v2/src/modules/parser.rs b/ef_tests/state_v2/src/modules/parser.rs index b3aba48..dcf7a0d 100644 --- a/ef_tests/state_v2/src/modules/parser.rs +++ b/ef_tests/state_v2/src/modules/parser.rs @@ -52,8 +52,15 @@ pub fn parse_file(path: &PathBuf, log_parse_file: bool) -> Result, Run if log_parse_file { println!("Parsing file: {:?}", path); } - let test_file = std::fs::File::open(path.clone()).unwrap(); - let mut tests: Tests = serde_json::from_reader(test_file).unwrap(); + let test_file = std::fs::File::open(path).map_err(|e| RunnerError::ParseFixture { + path: path.clone(), + source: format!("open: {e}"), + })?; + let mut tests: Tests = + serde_json::from_reader(test_file).map_err(|e| RunnerError::ParseFixture { + path: path.clone(), + source: format!("deserialize: {e}"), + })?; for test in tests.0.iter_mut() { test.path = path.clone(); } @@ -71,14 +78,23 @@ pub fn parse_dir( if log_parse_dir { println!("Parsing test directory: {:?}", path); } - let dir_entries: Vec<_> = std::fs::read_dir(path.clone()).unwrap().flatten().collect(); + let dir_entries: Vec<_> = std::fs::read_dir(path) + .map_err(|e| RunnerError::ParseFixture { + path: path.clone(), + source: format!("read_dir: {e}"), + })? + .flatten() + .collect(); // Process directory entries in parallel let directory_tests_results: Vec<_> = dir_entries .into_par_iter() .map(|entry| -> Result>, RunnerError> { // Check entry type - let entry_type = entry.file_type().unwrap(); + let entry_type = entry.file_type().map_err(|e| RunnerError::ParseFixture { + path: entry.path(), + source: format!("file_type: {e}"), + })?; if entry_type.is_dir() { let dir_tests = parse_dir( &entry.path(), diff --git a/ef_tests/state_v2/src/modules/report.rs b/ef_tests/state_v2/src/modules/report.rs index 9a892e2..61da6da 100644 --- a/ef_tests/state_v2/src/modules/report.rs +++ b/ef_tests/state_v2/src/modules/report.rs @@ -75,26 +75,23 @@ pub fn write_failing_test_to_report(test: &Test, failing_test_cases: Vec) -> Result<(), RunnerError> { vec!["eip-7594", "eip-7939", "eip-7918", "eip-7892", "eip-7883"]; for test in tests { - let test_eip = test._info.clone().reference_spec.unwrap_or_default(); - + // Fusaka EIP allowlist only applies when `_info.reference_spec` is + // present. Fixtures without it (e.g. goevmlab-generated) bypass the + // filter so they aren't silently dropped just because we can't read + // the EIP list. if test.path.to_str().unwrap().contains("osaka") - && !fusaka_eips_to_test.iter().any(|eip| test_eip.contains(eip)) + && let Some(spec) = test + ._info + .as_ref() + .and_then(|info| info.reference_spec.as_deref()) + && !fusaka_eips_to_test.iter().any(|eip| spec.contains(eip)) { continue; } @@ -66,7 +72,7 @@ pub async fn run_test( for test_case in &test.test_cases { // Setup VM for transaction. let (mut db, initial_block_hash, storage, genesis) = - load_initial_state(test, &test_case.fork).await; + load_initial_state(test, &test_case.fork, true).await; let env = get_vm_env_for_test(test.env, test_case)?; let tx = get_tx_from_test_case(test_case).await?; let tracer = LevmCallTracer::disabled(); @@ -150,6 +156,7 @@ pub fn get_vm_env_for_test( is_privileged: false, fee_token: None, disable_balance_check: false, + is_system_call: false, }) } diff --git a/ef_tests/state_v2/src/modules/statetest.rs b/ef_tests/state_v2/src/modules/statetest.rs new file mode 100644 index 0000000..9e19549 --- /dev/null +++ b/ef_tests/state_v2/src/modules/statetest.rs @@ -0,0 +1,244 @@ +//! `statetest` subcommand: single-fixture runner for goevmlab differential fuzzing. +//! +//! Takes one EF state-test JSON file and runs every `(fork, post-index)` case through +//! LEVM. For each case, emits EIP-3155 JSONL steps and a final `stateRoot` line to +//! **stderr** (stdout is reserved for crash diagnostics, matching geth/revm convention). +//! +//! Exit status: +//! - `0`: all cases produced the expected post-state root +//! - `1`: at least one case had a post-state root mismatch (tolerated by goevmlab) +//! - other: actual crash (panic, parse error, etc.) + +use std::path::PathBuf; +use std::process::ExitCode; + +use clap::Args; +use ethrex_common::tracing::Eip3155Step; +use ethrex_crypto::NativeCrypto; +use ethrex_levm::{ + opcode_tracer::{LevmOpcodeTracer, OpcodeTracerConfig}, + tracing::LevmCallTracer, + vm::{VM, VMType}, +}; +use ethrex_vm::backends; + +use crate::modules::{ + error::RunnerError, + parser::parse_file, + result_check::post_state_root, + runner::{get_tx_from_test_case, get_vm_env_for_test}, + utils::load_initial_state, +}; + +#[derive(Args, Debug)] +#[group(required = true, multiple = false)] +pub struct StatetestOptions { + /// Emit full EIP-3155 JSONL trace + stateRoot line for the given fixture. + #[arg(long, value_name = "PATH", group = "mode")] + pub json: Option, + /// Emit only the stateRoot line for the given fixture (no per-opcode trace). + #[arg(long, value_name = "PATH", group = "mode")] + pub json_outcome: Option, +} + +impl StatetestOptions { + /// Returns `(path, emit_trace)`. The clap `ArgGroup` guarantees exactly one is set. + fn fixture_path(&self) -> (&PathBuf, bool) { + match (&self.json, &self.json_outcome) { + (Some(p), None) => (p, true), + (None, Some(p)) => (p, false), + _ => unreachable!("clap ArgGroup enforces exactly one of --json / --json-outcome"), + } + } +} + +pub async fn run(opts: StatetestOptions) -> Result { + let (path, emit_trace) = opts.fixture_path(); + let tests = parse_file(path, false)?; + + // `Tests::from` filters out forks not in `DEFAULT_FORKS` (types.rs). A fixture + // whose `post` map contains only unsupported forks would therefore parse fine + // but produce zero `test_cases`, and we'd silently exit 0 with no `stateRoot` + // emitted — a false-green that goevmlab can't detect. Surface it as an error. + if tests.iter().all(|t| t.test_cases.is_empty()) { + return Err(RunnerError::Custom(format!( + "no runnable test cases in {}: none of the post-state forks are in the runnable allow-list", + path.display(), + ))); + } + + let mut any_mismatch = false; + for test in &tests { + for test_case in &test.test_cases { + any_mismatch |= run_case(test, test_case, emit_trace).await?; + } + } + + Ok(if any_mismatch { + ExitCode::from(1) + } else { + ExitCode::SUCCESS + }) +} + +/// Runs a single `(fork, post-index)` test case. Emits per-opcode JSONL when +/// `emit_trace` is true, then emits the final `stateRoot` line. Returns `true` +/// when the computed root differs from the fixture's expected root. +async fn run_case( + test: &crate::modules::types::Test, + test_case: &crate::modules::types::TestCase, + emit_trace: bool, +) -> Result { + let (mut db, initial_block_hash, storage, _genesis) = + load_initial_state(test, &test_case.fork, true).await; + let env = get_vm_env_for_test(test.env, test_case)?; + let tx = get_tx_from_test_case(test_case).await?; + + let mut vm = VM::new( + env, + &mut db, + &tx, + LevmCallTracer::disabled(), + VMType::L1, + &NativeCrypto, + ) + .map_err(RunnerError::VMError)?; + + if emit_trace { + vm.opcode_tracer = LevmOpcodeTracer::new(OpcodeTracerConfig::default()); + } + + // Execution errors here are not necessarily fatal — a state test can expect + // a tx to fail. The post-state root check is what determines pass/fail. + let _ = vm.execute(); + + if emit_trace { + // Wrap each step in `Eip3155Step` so the serializer emits the strict + // EIP-3155 wire shape (numeric `op` + separate `opName`, hex + // `gas`/`gasCost`/`refund`, `stack: []` when disabled) — what goevmlab's + // opLog unmarshaler expects, not the geth-RPC structLogger shape. + for step in &vm.opcode_tracer.logs { + let line = serde_json::to_string(&Eip3155Step(step)) + .map_err(|e| RunnerError::Custom(format!("failed to serialize trace step: {e}")))?; + eprintln!("{line}"); + } + } + + let account_updates = backends::levm::LEVM::get_state_transitions(&mut vm.db.clone()) + .map_err(|e| RunnerError::FailedToGetAccountsUpdates(e.to_string()))?; + let computed_root = post_state_root(&account_updates, initial_block_hash, storage); + + eprintln!("{}", stateroot_line(&computed_root)); + + Ok(computed_root != test_case.post.hash) +} + +/// Formats a state root as the literal line goevmlab's adapter scans for in +/// each client's stderr stream: the substring `"stateRoot":"0x<64 lowercase hex>"`. +/// +/// Extracted so the regression test below can pin the exact wire format without +/// reaching into `eprintln!`. Surrounding JSON shape is flexible per the goevmlab +/// spec — only the literal substring matters — but emitting it as a valid one-key +/// JSON object keeps the line parseable too. +fn stateroot_line(root: ðrex_common::H256) -> String { + format!("{{\"stateRoot\":\"0x{root:x}\"}}") +} + +#[cfg(test)] +mod tests { + //! Regression tests for the wire-format contract that goevmlab consumes. + //! + //! Two invariants matter end-to-end: + //! 1. Each opcode trace line is JSON parseable by goevmlab's `opLog` + //! unmarshaler (`evms/gen_oplog.go`). That means `op` is a number + //! (cast to `vm.OpCode`), `gas`/`gasCost` are decimal-or-hex numbers, + //! `stack` is a non-null array. We rely on `Eip3155Step`'s serializer + //! to emit this shape — see `crates/common/tracing.rs`. + //! 2. The final stateRoot line contains the exact literal substring + //! `"stateRoot":"0x<64 hex chars>"` so goevmlab can scan for it by + //! raw byte search (see [revm.go](https://github.com/holiman/goevmlab/blob/master/evms/revm.go)). + + use super::stateroot_line; + use ethrex_common::{H256, U256, tracing::Eip3155Step, tracing::OpcodeStep}; + use serde_json::Value; + + /// Builds a minimal `OpcodeStep` for `PUSH1` (opcode 0x60) with one stack entry. + fn sample_step() -> OpcodeStep { + OpcodeStep { + pc: 0, + op: 0x60, + gas: 21_000, + gas_cost: 3, + mem_size: 0, + depth: 1, + return_data: bytes::Bytes::new(), + refund: 0, + stack: Some(vec![U256::from(0x42)]), + memory: None, + storage: None, + error: None, + } + } + + #[test] + fn eip3155_step_matches_goevmlab_oplog_shape() { + let line = serde_json::to_string(&Eip3155Step(&sample_step())).expect("serialize"); + let v: Value = serde_json::from_str(&line).expect("valid JSON"); + + // EIP-3155 spec types, mirroring the fields goevmlab's gen_oplog.go + // expects to unmarshal into uint64/vm.OpCode/uint256.Int/etc. + assert!(v["pc"].is_number(), "pc must be a JSON number"); + assert!( + v["op"].is_number(), + "op must be a NUMERIC opcode byte (goevmlab casts to vm.OpCode); got: {}", + v["op"] + ); + assert_eq!(v["op"].as_u64(), Some(0x60)); + assert_eq!(v["opName"].as_str(), Some("PUSH1")); + + let gas = v["gas"].as_str().expect("gas must be a hex string"); + assert!( + gas.starts_with("0x"), + "gas must be `\"0x...\"` form per EIP-3155 Hex-Number; got: {gas}" + ); + let gas_cost = v["gasCost"].as_str().expect("gasCost must be a hex string"); + assert!(gas_cost.starts_with("0x")); + + // EIP-3155: `stack` MUST be `[]`, never null. + assert!(v["stack"].is_array(), "stack must be an array, never null"); + assert_eq!(v["stack"][0].as_str(), Some("0x42")); + } + + #[test] + fn eip3155_step_stack_disabled_renders_as_empty_array() { + let mut step = sample_step(); + step.stack = None; + let line = serde_json::to_string(&Eip3155Step(&step)).expect("serialize"); + let v: Value = serde_json::from_str(&line).expect("valid JSON"); + assert_eq!( + v["stack"], + Value::Array(vec![]), + "EIP-3155: stack must be `[]` when disabled, not null", + ); + } + + #[test] + fn stateroot_line_pins_literal_goevmlab_scan_pattern() { + let root = H256::repeat_byte(0xab); + let line = stateroot_line(&root); + + // The literal substring `"stateRoot":"0x<64 hex>"` is what goevmlab byte- + // scans for; surrounding JSON shape is flexible. Pin both halves. + let expected_hex = format!("0x{}", "ab".repeat(32)); + assert_eq!(expected_hex.len(), 66, "64 hex chars + 0x prefix"); + assert!( + line.contains(&format!("\"stateRoot\":\"{expected_hex}\"")), + "missing goevmlab scan pattern; line={line}" + ); + + // Sanity: H256's LowerHex zero-pads to 64 chars even for low-value roots. + let small = H256::from_low_u64_be(1); + let line_small = stateroot_line(&small); + assert!(line_small.contains(&format!("\"0x{:0>64}\"", "1"))); + } +} diff --git a/ef_tests/state_v2/src/modules/types.rs b/ef_tests/state_v2/src/modules/types.rs index 1fa0339..c9f7b21 100644 --- a/ef_tests/state_v2/src/modules/types.rs +++ b/ef_tests/state_v2/src/modules/types.rs @@ -27,7 +27,14 @@ use std::{ path::PathBuf, }; -const DEFAULT_FORKS: [&str; 5] = ["Merge", "Shanghai", "Cancun", "Prague", "Amsterdam"]; +const DEFAULT_FORKS: [&str; 6] = [ + "Merge", + "Shanghai", + "Cancun", + "Prague", + "Osaka", + "Amsterdam", +]; /// `Tests` structure is the result of parsing a whole `.json` file from the EF tests. This file includes at /// least one general test enviroment and different test cases inside each enviroment. @@ -117,17 +124,19 @@ impl Tests { test_data: &HashMap, test_cases: Vec, ) -> Result { - // Obtain the value of the `info` field in the JSON. - let info_field = test_data - .get("_info") - .ok_or(serde::de::Error::missing_field("_info"))?; - // Parse the field value as `Info`. - let test_info = serde_json::from_value(info_field.clone()).map_err(|err| { - serde::de::Error::custom(format!( - "Failed to deserialize `info` field in test {}. Serde error: {}", - test_name, err - )) - })?; + // The `_info` field is optional — EF fixtures populate it but + // goevmlab-generated fixtures may omit it. + let test_info = match test_data.get("_info") { + Some(info_field) => { + Some(serde_json::from_value(info_field.clone()).map_err(|err| { + serde::de::Error::custom(format!( + "Failed to deserialize `info` field in test {}. Serde error: {}", + test_name, err + )) + })?) + } + None => None, + }; // Obtain the value of the `env` field in the JSON. let env_field = test_data .get("env") @@ -226,8 +235,10 @@ impl Tests { pub struct Test { pub name: String, // The name of the test object inside the .json file. pub path: PathBuf, // The path of the .json file the Test can be found at. - pub _info: Info, // General information about the test. - pub env: Env, // The block enviroment before the test transaction happens. + /// General information about the test (optional — present in EF fixtures, + /// may be absent in goevmlab-generated ones). + pub _info: Option, + pub env: Env, // The block enviroment before the test transaction happens. pub pre: HashMap, // The accounts state previous to the test transaction. pub test_cases: Vec, // A vector of specific cases to be tested under these conditions (transactions). } @@ -295,6 +306,8 @@ pub fn genesis_from_test_and_fork(test: &Test, fork: &Fork) -> Genesis { schedule.cancun.target } else if *fork == Fork::Prague { schedule.prague.target + } else if *fork == Fork::Osaka { + schedule.osaka.target } else { 0 }; @@ -560,3 +573,79 @@ pub struct RawTransaction { #[serde(default, deserialize_with = "deserialize_authorization_lists")] pub authorization_list: Option>, } + +#[cfg(test)] +mod tests { + use super::*; + + /// Minimal fixture body — Tests::deserialize parses every test object as a + /// (test_name -> raw fields) map, so the inner fields just need to be + /// shape-correct enough for the per-field parsers downstream. + fn fixture_json(with_info: bool) -> String { + let info = if with_info { + r#""_info": { "comment": "goevmlab-generated" },"# + } else { + "" + }; + format!( + r#"{{ + "blockhash_divergence": {{ + {info} + "env": {{ + "currentCoinbase": "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x200000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000200000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x1", + "currentTimestamp": "0x3e8", + "previousHash": "0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", + "currentBaseFee": "0x10" + }}, + "pre": {{}}, + "transaction": {{ + "gasPrice": "0x10", + "nonce": "0x0", + "to": "0x00000000000000000000000000000000000000f1", + "data": ["0x"], + "gasLimit": ["0x5f5e100"], + "value": ["0x0"], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + }}, + "post": {{ + "Prague": [ + {{ + "hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "logs": "0x0000000000000000000000000000000000000000000000000000000000000000", + "indexes": {{ "data": 0, "gas": 0, "value": 0 }} + }} + ] + }} + }} + }}"# + ) + } + + #[test] + fn fixture_without_info_parses() { + let json = fixture_json(false); + let tests: Tests = serde_json::from_str(&json).expect("must parse without _info"); + assert_eq!(tests.0.len(), 1); + assert!( + tests.0[0]._info.is_none(), + "_info should be None when absent" + ); + } + + #[test] + fn fixture_with_info_still_parses() { + let json = fixture_json(true); + let tests: Tests = serde_json::from_str(&json).expect("must parse with _info"); + assert_eq!(tests.0.len(), 1); + let info = tests.0[0] + ._info + .as_ref() + .expect("_info should be Some when present"); + assert_eq!(info.comment.as_deref(), Some("goevmlab-generated")); + } +} diff --git a/ef_tests/state_v2/src/modules/utils.rs b/ef_tests/state_v2/src/modules/utils.rs index 1b05c26..34d0541 100644 --- a/ef_tests/state_v2/src/modules/utils.rs +++ b/ef_tests/state_v2/src/modules/utils.rs @@ -1,10 +1,13 @@ use ethrex_blockchain::vm::StoreVmDatabase; use ethrex_common::H256; use ethrex_common::{ - U256, - types::{Fork, Genesis}, + Address, U256, + types::{AccountState, ChainConfig, Code, CodeMetadata, Fork, Genesis}, + utils::keccak, }; +use ethrex_levm::db::Database as LevmDatabase; use ethrex_levm::db::gen_db::GeneralizedDatabase; +use ethrex_levm::errors::DatabaseError; use ethrex_storage::{EngineType, Store}; use ethrex_vm::DynVmDatabase; @@ -15,6 +18,52 @@ use crate::modules::{ types::{Env, Test, TestCase, genesis_from_test_and_fork}, }; +/// Wraps an inner levm `Database` to enforce the EF state-test convention for +/// `BLOCKHASH(n)` = `keccak256(decimal_string(n))`, independent of underlying +/// storage. Matches geth's `vmTestBlockHash` in `tests/state_test_util.go`. +/// +/// Without this override BLOCKHASH(0) at block 1 returns the genesis hash that +/// ethrex derives from the test's pre-state, which doesn't match the hash +/// fixtures put in `env.previousHash` (the EF convention). That trips +/// differential fuzzers like goevmlab on the very first block-hash lookup. +/// +/// Scoped to single-pass executions (`statetest` CLI + `runner.rs` EF runner). +/// `block_runner.rs` deliberately does NOT use this shim — its phase-3 real +/// import goes through `add_block_pipeline` which would not honor the override, +/// so applying the shim only to its phase-1 pre-exec would make the two phases +/// disagree on BLOCKHASH. Closing block_runner's BLOCKHASH gap end-to-end is +/// a separate fix. +pub(crate) struct StatetestDatabase { + inner: Arc, +} + +impl StatetestDatabase { + pub(crate) fn new(inner: Arc) -> Self { + Self { inner } + } +} + +impl LevmDatabase for StatetestDatabase { + fn get_account_state(&self, address: Address) -> Result { + self.inner.get_account_state(address) + } + fn get_storage_value(&self, address: Address, key: H256) -> Result { + self.inner.get_storage_value(address, key) + } + fn get_block_hash(&self, block_number: u64) -> Result { + Ok(keccak(block_number.to_string().as_bytes())) + } + fn get_chain_config(&self) -> Result { + self.inner.get_chain_config() + } + fn get_account_code(&self, code_hash: H256) -> Result { + self.inner.get_account_code(code_hash) + } + fn get_code_metadata(&self, code_hash: H256) -> Result { + self.inner.get_code_metadata(code_hash) + } +} + /// Calculates the price of the gas based on the fields the test case has. For transaction types /// previous to EIP1559, the gas_price is explicit in the test. For later transaction types, it requires /// to be calculated based on `current_base_fee`, `priority_fee` and `max_fee_per_gas` values. @@ -35,9 +84,17 @@ pub fn effective_gas_price(test_env: &Env, test_case: &TestCase) -> Result (GeneralizedDatabase, H256, Store, Genesis) { let genesis = genesis_from_test_and_fork(test, fork); let mut storage = Store::new("./temp", EngineType::InMemory).expect("Failed to create Store"); @@ -47,12 +104,77 @@ pub async fn load_initial_state( let block_hash = genesis.get_block().hash(); let store: DynVmDatabase = Box::new(StoreVmDatabase::new(storage.clone(), genesis.get_block().header).unwrap()); + let inner: Arc = Arc::new(store); + let db: Arc = if override_blockhash { + Arc::new(StatetestDatabase::new(inner)) + } else { + inner + }; // We return some values that will be needed to calculate the post execution checks (original storage, genesis and blockhash) - ( - GeneralizedDatabase::new(Arc::new(store)), - block_hash, - storage, - genesis, - ) + (GeneralizedDatabase::new(db), block_hash, storage, genesis) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + /// Minimal LevmDatabase stub that returns a sentinel block hash, so the + /// test can assert that `StatetestDatabase` overrides BLOCKHASH and never + /// consults the inner DB for it. + struct SentinelInner; + impl LevmDatabase for SentinelInner { + fn get_account_state(&self, _: Address) -> Result { + unreachable!("not exercised by BLOCKHASH test") + } + fn get_storage_value(&self, _: Address, _: H256) -> Result { + unreachable!("not exercised by BLOCKHASH test") + } + fn get_block_hash(&self, _: u64) -> Result { + // If the wrapper ever delegates BLOCKHASH downward, this sentinel + // proves the bug — the override path is the only correct answer. + Ok(H256::repeat_byte(0xff)) + } + fn get_chain_config(&self) -> Result { + unreachable!("not exercised by BLOCKHASH test") + } + fn get_account_code(&self, _: H256) -> Result { + unreachable!("not exercised by BLOCKHASH test") + } + fn get_code_metadata(&self, _: H256) -> Result { + unreachable!("not exercised by BLOCKHASH test") + } + } + + #[test] + fn blockhash_zero_matches_ef_convention() { + let db = StatetestDatabase::new(Arc::new(SentinelInner)); + // Per geth's vmTestBlockHash: keccak256("0"). + let expected = + H256::from_str("0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d") + .unwrap(); + assert_eq!(db.get_block_hash(0).unwrap(), expected); + } + + #[test] + fn blockhash_nonzero_matches_ef_convention() { + let db = StatetestDatabase::new(Arc::new(SentinelInner)); + // keccak256("1") per geth's vmTestBlockHash. + let expected = + H256::from_str("0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6") + .unwrap(); + assert_eq!(db.get_block_hash(1).unwrap(), expected); + } + + #[test] + fn blockhash_uses_decimal_not_hex() { + // Specifically pin the decimal-string convention. For n=10, ascii "10" + // and ascii "a" would hash to different values; we must use decimal. + let db = StatetestDatabase::new(Arc::new(SentinelInner)); + let got = db.get_block_hash(10).unwrap(); + let expected = keccak(b"10"); + assert_eq!(got, expected); + assert_ne!(got, keccak(b"a"), "must hash decimal string, not hex"); + } } diff --git a/l2/dev/docker-compose.yaml b/l2/dev/docker-compose.yaml index d72449c..b0c493c 100644 --- a/l2/dev/docker-compose.yaml +++ b/l2/dev/docker-compose.yaml @@ -394,7 +394,7 @@ services: - source: ethrex-sponsor-addresses target: /usr/local/bin/sponsorable-addresses.txt command: > - l2 --dev --no-monitor --proof-coordinator.addr 0.0.0.0 --admin-server.addr 0.0.0.0 --block-producer.block-time 1000 --sponsorable-addresses sponsorable-addresses.txt + l2 --dev --no-monitor --http.addr 0.0.0.0 --http.api eth,net,web3,debug,txpool --proof-coordinator.addr 0.0.0.0 --admin-server.addr 0.0.0.0 --block-producer.block-time 1000 --sponsorable-addresses sponsorable-addresses.txt ethrex-prover: container_name: ethrex-prover diff --git a/migrations/Cargo.toml b/migrations/Cargo.toml index f249269..1ee4a67 100644 --- a/migrations/Cargo.toml +++ b/migrations/Cargo.toml @@ -16,3 +16,14 @@ ethrex-storage-libmdbx = { features = [ ], git = "https://github.com/lambdaclass/ethrex", tag = "v1.0.0", package = "ethrex-storage" } ethrex-storage = { features = ["rocksdb"], workspace = true } tokio = { features = ["full"], workspace = true } +libc = "0.2" +rocksdb.workspace = true +tracing-subscriber.workspace = true + +[[bin]] +name = "bench_migration" +path = "src/bin/bench_migration.rs" + +[[bin]] +name = "seed_migration_test" +path = "src/bin/seed_migration_test.rs" diff --git a/migrations/src/bin/bench_migration.rs b/migrations/src/bin/bench_migration.rs new file mode 100644 index 0000000..7c3d2d5 --- /dev/null +++ b/migrations/src/bin/bench_migration.rs @@ -0,0 +1,100 @@ +/// Standalone binary to benchmark the v1→v2 RECEIPTS migration. +/// +/// Usage: bench_migration +/// +/// Opens the RocksDB database, runs the two-CF migration (receipts → receipts_v2), +/// and reports wall-clock time and peak RSS. +/// +/// Prerequisites: +/// 1. Run `seed_migration_test ` to seed 150M old-format entries +/// 2. Ensure metadata.json has {"schema_version": 1} (or just don't create one) +/// 3. Run this binary +use std::time::Instant; + +fn get_rss_mb() -> Option { + #[cfg(target_os = "macos")] + { + use std::mem; + let mut info: libc::rusage = unsafe { mem::zeroed() }; + let ret = unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut info) }; + if ret == 0 { + // macOS reports maxrss in bytes + Some(info.ru_maxrss as f64 / (1024.0 * 1024.0)) + } else { + None + } + } + #[cfg(target_os = "linux")] + { + use std::mem; + let mut info: libc::rusage = unsafe { mem::zeroed() }; + let ret = unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut info) }; + if ret == 0 { + // Linux reports maxrss in kilobytes + Some(info.ru_maxrss as f64 / 1024.0) + } else { + None + } + } + #[cfg(not(any(target_os = "macos", target_os = "linux")))] + { + None + } +} + +fn main() { + // Initialize tracing so migration progress logs are visible + tracing_subscriber::fmt() + .with_target(false) + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .init(); + + let args: Vec = std::env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + let db_path = &args[1]; + + println!("Opening database at: {db_path}"); + let rss_before = get_rss_mb(); + + let backend = ethrex_storage::backend::rocksdb::RocksDBBackend::open(db_path) + .expect("Failed to open RocksDB"); + + let rss_after_open = get_rss_mb(); + println!( + "Database opened. RSS after open: {:.1} MB", + rss_after_open.unwrap_or(0.0) + ); + + // Run the migration + println!("Starting migration v1→v2 (two-CF: receipts → receipts_v2)..."); + let start = Instant::now(); + + ethrex_storage::migrations::run_pending_migrations( + &backend, + std::path::Path::new(db_path), + 1, // pretend we're at v1 + ) + .expect("Migration failed"); + + let elapsed = start.elapsed(); + let rss_after = get_rss_mb(); + + println!("\n=== Migration Benchmark Results ==="); + println!("Wall-clock time: {:.1}s", elapsed.as_secs_f64()); + if let Some(before) = rss_before { + println!("RSS before open: {:.1} MB", before); + } + if let Some(after_open) = rss_after_open { + println!("RSS after open: {:.1} MB", after_open); + } + if let Some(after) = rss_after { + println!("Peak RSS (maxrss): {:.1} MB", after); + } + println!("==================================="); +} diff --git a/migrations/src/bin/seed_migration_test.rs b/migrations/src/bin/seed_migration_test.rs new file mode 100644 index 0000000..71df425 --- /dev/null +++ b/migrations/src/bin/seed_migration_test.rs @@ -0,0 +1,219 @@ +/// Standalone binary to seed ~150M old-format (RLP-encoded) RECEIPTS keys +/// into an existing RocksDB database for migration benchmarking. +/// +/// Usage: seed_migration_test +/// +/// This opens the database, writes 150M entries with RLP-encoded (H256, u64) +/// keys and small synthetic receipt values into the "receipts" column family, +/// then exits. After running, reset metadata.json to {"schema_version": 1} +/// and start ethrex to trigger the migration. +use ethrex_storage::api::tables::{RECEIPTS, TABLES}; +use rocksdb::{ + BlockBasedOptions, Cache, ColumnFamilyDescriptor, DBWithThreadMode, MultiThreaded, Options, + WriteBatch, +}; +use std::collections::HashSet; +use std::time::Instant; + +/// RLP-encode a (H256, u64) tuple the same way ethrex_rlp does. +/// Layout: RLP list header + 32-byte hash (with RLP string header) + u64 (with RLP string header) +fn rlp_encode_receipt_key(block_hash: &[u8; 32], index: u64) -> Vec { + // RLP-encode the H256 (32 bytes): 0xa0 prefix + 32 bytes = 33 bytes + // RLP-encode the u64: variable length + // Then wrap in a list + + let mut hash_encoded = Vec::with_capacity(33); + hash_encoded.push(0x80 + 32); // string header for 32 bytes + hash_encoded.extend_from_slice(block_hash); + + let idx_encoded = rlp_encode_u64(index); + + let payload_len = hash_encoded.len() + idx_encoded.len(); + let mut out = Vec::with_capacity(payload_len + 3); + + // List header + if payload_len < 56 { + out.push(0xc0 + payload_len as u8); + } else { + let len_bytes = minimal_be_bytes(payload_len as u64); + out.push(0xf7 + len_bytes.len() as u8); + out.extend_from_slice(&len_bytes); + } + out.extend_from_slice(&hash_encoded); + out.extend_from_slice(&idx_encoded); + out +} + +fn rlp_encode_u64(val: u64) -> Vec { + if val == 0 { + return vec![0x80]; // empty string = 0 + } + if val < 128 { + return vec![val as u8]; // single byte + } + let bytes = minimal_be_bytes(val); + let mut out = Vec::with_capacity(1 + bytes.len()); + out.push(0x80 + bytes.len() as u8); + out.extend_from_slice(&bytes); + out +} + +fn minimal_be_bytes(val: u64) -> Vec { + let bytes = val.to_be_bytes(); + let start = bytes.iter().position(|&b| b != 0).unwrap_or(7); + bytes[start..].to_vec() +} + +/// Create a minimal synthetic receipt value (RLP-encoded). +/// Receipt: [tx_type(0), succeeded(true), cumulative_gas(21000), bloom(256 zeros), logs(empty)] +fn synthetic_receipt_value() -> Vec { + // A minimal Legacy receipt: RLP([1, cumgas, bloom, []]) + // succeeded = 0x01 (single byte) + // cumulative_gas_used = 21000 = 0x5208 + // bloom = 256 zero bytes + // logs = empty list + + let succeeded = vec![0x01]; // RLP single byte + let cumgas = vec![0x82, 0x52, 0x08]; // RLP string: 2 bytes, 0x5208 + // bloom: 256 zero bytes -> string header 0xb9 0x01 0x00 + 256 zeros + let mut bloom = Vec::with_capacity(259); + bloom.push(0xb9); + bloom.push(0x01); + bloom.push(0x00); + bloom.extend_from_slice(&[0u8; 256]); + let logs = vec![0xc0]; // empty list + + let payload_len = succeeded.len() + cumgas.len() + bloom.len() + logs.len(); + let mut out = Vec::with_capacity(payload_len + 4); + + // List header for the receipt + if payload_len < 56 { + out.push(0xc0 + payload_len as u8); + } else { + let len_bytes = minimal_be_bytes(payload_len as u64); + out.push(0xf7 + len_bytes.len() as u8); + out.extend_from_slice(&len_bytes); + } + out.extend_from_slice(&succeeded); + out.extend_from_slice(&cumgas); + out.extend_from_slice(&bloom); + out.extend_from_slice(&logs); + out +} + +fn main() { + let args: Vec = std::env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + let db_path = &args[1]; + + println!("Opening database at: {db_path}"); + + // DB options matching ethrex's RocksDBBackend::open() + let mut opts = Options::default(); + opts.create_if_missing(true); + opts.create_missing_column_families(true); + opts.set_max_open_files(512); + opts.set_max_file_opening_threads(16); + opts.set_max_background_jobs(8); + opts.set_compression_type(rocksdb::DBCompressionType::None); + + let block_cache = Cache::new_lru_cache(2 * 1024 * 1024 * 1024); // 2GB for seeding + + // Build CF list from the crate's TABLES constant, plus the legacy RECEIPTS CF + // that we need to seed old-format entries into. + let existing_cfs = DBWithThreadMode::::list_cf(&opts, db_path) + .unwrap_or_else(|_| vec!["default".to_string()]); + + let mut all_cfs: HashSet = existing_cfs.into_iter().collect(); + all_cfs.extend(TABLES.iter().map(|t| t.to_string())); + all_cfs.insert(RECEIPTS.to_string()); + all_cfs.insert("default".to_string()); + + let cf_descriptors: Vec = all_cfs + .iter() + .map(|cf_name| { + let mut cf_opts = Options::default(); + cf_opts.set_write_buffer_size(128 * 1024 * 1024); + cf_opts.set_max_write_buffer_number(3); + cf_opts.set_target_file_size_base(256 * 1024 * 1024); + + let mut block_opts = BlockBasedOptions::default(); + block_opts.set_block_size(32 * 1024); + block_opts.set_block_cache(&block_cache); + cf_opts.set_block_based_table_factory(&block_opts); + + ColumnFamilyDescriptor::new(cf_name.clone(), cf_opts) + }) + .collect(); + + let db = DBWithThreadMode::::open_cf_descriptors(&opts, db_path, cf_descriptors) + .expect("Failed to open database"); + + let cf = db + .cf_handle("receipts") + .expect("receipts column family not found"); + + let receipt_value = synthetic_receipt_value(); + println!("Synthetic receipt value: {} bytes", receipt_value.len()); + + const TOTAL: u64 = 150_000_000; + const BATCH_SIZE: u64 = 50_000; + const RECEIPTS_PER_BLOCK: u64 = 256; + + let start = Instant::now(); + let mut batch = WriteBatch::default(); + let mut count: u64 = 0; + + println!("Seeding {TOTAL} old-format RLP RECEIPTS entries..."); + + for i in 0..TOTAL { + // Generate a deterministic "block hash" from the block index + let block_idx = i / RECEIPTS_PER_BLOCK; + let receipt_idx = i % RECEIPTS_PER_BLOCK; + let mut block_hash = [0u8; 32]; + // Use a prefix that won't collide with real block hashes (starts with 0xFF) + block_hash[0] = 0xFF; + block_hash[1] = 0xFE; + // Encode block_idx into bytes 24..31 + block_hash[24..32].copy_from_slice(&block_idx.to_be_bytes()); + + let key = rlp_encode_receipt_key(&block_hash, receipt_idx); + batch.put_cf(&cf, &key, &receipt_value); + count += 1; + + if count.is_multiple_of(BATCH_SIZE) { + db.write(batch).expect("Failed to write batch"); + batch = WriteBatch::default(); + + if count.is_multiple_of(5_000_000) { + let elapsed = start.elapsed().as_secs_f64(); + let rate = count as f64 / elapsed; + println!( + " {count}/{TOTAL} ({:.1}%) — {:.0} entries/sec — {:.1}s elapsed", + count as f64 / TOTAL as f64 * 100.0, + rate, + elapsed + ); + } + } + } + + // Final batch + if !count.is_multiple_of(BATCH_SIZE) { + db.write(batch).expect("Failed to write final batch"); + } + + let elapsed = start.elapsed().as_secs_f64(); + println!( + "Done! Seeded {count} entries in {elapsed:.1}s ({:.0} entries/sec)", + count as f64 / elapsed + ); + println!("Now reset metadata.json to {{\"schema_version\": 1}} and start ethrex."); + println!("Migration will copy entries from 'receipts' to 'receipts_v2' (two-CF approach)."); + println!( + "The old 'receipts' CF will be dropped automatically on the next startup after migration." + ); +} diff --git a/repl/src/commands/admin.rs b/repl/src/commands/admin.rs index f1ae2c7..e6a79a9 100644 --- a/repl/src/commands/admin.rs +++ b/repl/src/commands/admin.rs @@ -48,5 +48,19 @@ pub fn commands() -> Vec { params: ENODE, description: "Adds a peer by enode URL", }, + CommandDef { + namespace: "admin", + name: "peerScores", + rpc_method: "admin_peerScores", + params: NO_PARAMS, + description: "Returns peer diagnostics: scores, inflight requests, eligibility", + }, + CommandDef { + namespace: "admin", + name: "syncStatus", + rpc_method: "admin_syncStatus", + params: NO_PARAMS, + description: "Returns sync diagnostics: phase, pivot, staleness, recent events", + }, ] } diff --git a/repl/src/commands/debug.rs b/repl/src/commands/debug.rs index 0c3cef6..57be94b 100644 --- a/repl/src/commands/debug.rs +++ b/repl/src/commands/debug.rs @@ -87,13 +87,6 @@ pub fn commands() -> Vec { params: BLOCK_ONLY, description: "Returns the execution witness for a block", }, - CommandDef { - namespace: "debug", - name: "getBlockAccessList", - rpc_method: "debug_getBlockAccessList", - params: BLOCK_ONLY, - description: "Returns the access list for a block", - }, CommandDef { namespace: "debug", name: "traceTransaction", diff --git a/repl/src/commands/eth.rs b/repl/src/commands/eth.rs index 43155d2..8c62e6e 100644 --- a/repl/src/commands/eth.rs +++ b/repl/src/commands/eth.rs @@ -436,5 +436,12 @@ pub fn commands() -> Vec { params: GET_PROOF, description: "Returns the Merkle proof for an account", }, + CommandDef { + namespace: "eth", + name: "getBlockAccessList", + rpc_method: "eth_getBlockAccessList", + params: BLOCK_ONLY, + description: "Returns the access list for a block", + }, ] } diff --git a/repl/src/formatter.rs b/repl/src/formatter.rs index b2975a6..e343a27 100644 --- a/repl/src/formatter.rs +++ b/repl/src/formatter.rs @@ -70,48 +70,64 @@ fn format_object_box(map: &serde_json::Map, title: &str) -> Strin let rows = flatten_object(map, ""); - let key_w = rows.iter().map(|(k, _)| k.len()).max().unwrap_or(0); - let val_w = rows - .iter() - .map(|(_, v)| v.len()) - .max() - .unwrap_or(0) - .min(MAX_VALUE_DISPLAY_LEN); - let content_w = key_w + 3 + val_w; - let box_w = content_w + 4; // "│ " + content + " │" + // Separate scalar rows from table sections (arrays of objects rendered below the box) + let mut scalar_rows = Vec::new(); + let mut table_sections: Vec<(String, String)> = Vec::new(); + for (key, value) in rows { + if value.starts_with('\n') && value.contains("items)") { + table_sections.push((key, value)); + } else { + scalar_rows.push((key, value)); + } + } let mut out = String::new(); - // Top border - if title.is_empty() { - out.push_str(&format!("┌{}┐\n", "─".repeat(box_w - 2))); - } else { - let fill = (box_w - 2).saturating_sub(title.len() + 1); - out.push_str(&format!("┌─{}{}┐\n", title.bold(), "─".repeat(fill))); + if !scalar_rows.is_empty() { + let key_w = scalar_rows.iter().map(|(k, _)| k.len()).max().unwrap_or(0); + let val_w = scalar_rows + .iter() + .map(|(_, v)| v.len()) + .max() + .unwrap_or(0) + .min(MAX_VALUE_DISPLAY_LEN); + let content_w = key_w + 3 + val_w; + let box_w = content_w + 4; + + if title.is_empty() { + out.push_str(&format!("┌{}┐\n", "─".repeat(box_w - 2))); + } else { + let fill = (box_w - 2).saturating_sub(title.len() + 1); + out.push_str(&format!("┌─{}{}┐\n", title.bold(), "─".repeat(fill))); + } + + for (key, value) in &scalar_rows { + let display_val = truncate_middle(value, val_w); + let key_pad = " ".repeat(key_w.saturating_sub(key.len())); + let val_pad = " ".repeat(val_w.saturating_sub(display_val.len())); + out.push_str(&format!( + "│ {}{} {}{} │\n", + key_pad, + key.cyan(), + colorize_inline(&display_val), + val_pad, + )); + } + + out.push_str(&format!("└{}┘", "─".repeat(box_w - 2))); } - // Rows - for (key, value) in &rows { - let display_val = truncate_middle(value, val_w); - let key_pad = " ".repeat(key_w.saturating_sub(key.len())); - let val_pad = " ".repeat(val_w.saturating_sub(display_val.len())); - out.push_str(&format!( - "│ {}{} {}{} │\n", - key_pad, - key.cyan(), - colorize_inline(&display_val), - val_pad, - )); - } - - // Bottom border - out.push_str(&format!("└{}┘", "─".repeat(box_w - 2))); + // Render table sections below the box + for (key, table) in &table_sections { + out.push_str(&format!("\n {}:{}", key.cyan(), table)); + } out } /// Flatten a JSON object into (key, plain-text-value) pairs. /// Nested objects are expanded with dot-separated keys. +/// Arrays of objects are rendered as inline tables. fn flatten_object(map: &serde_json::Map, prefix: &str) -> Vec<(String, String)> { let mut rows = Vec::new(); for (key, value) in map { @@ -124,6 +140,10 @@ fn flatten_object(map: &serde_json::Map, prefix: &str) -> Vec<(St Value::Object(nested) if !nested.is_empty() => { rows.extend(flatten_object(nested, &full_key)); } + Value::Array(arr) if !arr.is_empty() && arr.iter().all(|v| v.is_object()) => { + // Render array of objects as a table + rows.push((full_key, format_object_array_table(arr))); + } Value::Array(arr) => { let items: Vec = arr.iter().map(inline_value).collect(); rows.push((full_key, items.join(", "))); @@ -136,6 +156,84 @@ fn flatten_object(map: &serde_json::Map, prefix: &str) -> Vec<(St rows } +/// Render an array of objects as a compact table with headers. +fn format_object_array_table(arr: &[Value]) -> String { + if arr.is_empty() { + return "[]".to_string(); + } + + // Collect all keys from all objects to build columns + let mut columns: Vec = Vec::new(); + for item in arr { + if let Value::Object(map) = item { + for key in map.keys() { + if !columns.contains(key) { + columns.push(key.clone()); + } + } + } + } + + if columns.is_empty() { + return "[]".to_string(); + } + + // Compute column widths + let col_values: Vec> = arr + .iter() + .map(|item| { + columns + .iter() + .map(|col| item.get(col).map(inline_value).unwrap_or_default()) + .collect() + }) + .collect(); + + let col_widths: Vec = columns + .iter() + .enumerate() + .map(|(i, header)| { + let max_val = col_values.iter().map(|row| row[i].len()).max().unwrap_or(0); + header.len().max(max_val).min(30) + }) + .collect(); + + let mut out = String::new(); + + // Header + out.push('\n'); + let header_parts: Vec = columns + .iter() + .zip(&col_widths) + .map(|(h, w)| format!("{:>width$}", h, width = *w)) + .collect(); + out.push_str(&format!(" {}", header_parts.join(" "))); + + // Separator + let sep_parts: Vec = col_widths.iter().map(|w| "─".repeat(*w)).collect(); + out.push_str(&format!("\n {}", sep_parts.join("──"))); + + // Rows + for row in &col_values { + let parts: Vec = row + .iter() + .zip(&col_widths) + .map(|(val, w)| { + let truncated = if val.len() > *w { + format!("{}…", &val[..*w - 1]) + } else { + val.clone() + }; + format!("{:>width$}", truncated, width = *w) + }) + .collect(); + out.push_str(&format!("\n {}", parts.join(" "))); + } + + out.push_str(&format!("\n ({} items)", arr.len())); + out +} + /// Convert a Value to a plain-text string for table cells. fn inline_value(value: &Value) -> String { match value { diff --git a/sync/Makefile b/sync/Makefile index 0343373..3b3c419 100644 --- a/sync/Makefile +++ b/sync/Makefile @@ -185,6 +185,7 @@ start-ethrex: ## Start ethrex for the network given by NETWORK. @echo $(RUSTFLAGS) cd $(ETHREX_DIR) && RUST_LOG=3 cargo run $(PROFILING_CFG) --release --features "rocksdb sync-test metrics" --bin ethrex -- \ --http.addr 0.0.0.0 \ + --http.api eth,net,web3,admin \ --http.port 8545 \ --authrpc.port 8551 \ --p2p.port 30303\ @@ -232,7 +233,7 @@ server-sync: sleep 0.2 - tmux new-window -t sync:2 -n ethrex "cd ../.. && ulimit -n 1000000 && rm -rf ~/.local/share/ethrex && RUST_LOG=info,ethrex_p2p::sync=debug $(if $(DEBUG_ASSERT),RUSTFLAGS='-C debug-assertions=yes') $(if $(HEALING),SKIP_START_SNAP_SYNC=1) cargo run $(PROFILING_CFG) --release --bin ethrex --features rocksdb -- --http.addr 0.0.0.0 --metrics --metrics.port 3701 --network $(SERVER_SYNC_NETWORK) $(if $(MEMORY),--datadir memory) --authrpc.jwtsecret ~/secrets/jwt.hex $(if $(or $(FULL_SYNC),$(HEALING)),--syncmode full) 2>&1 | tee $(LOGS_FILE)" + tmux new-window -t sync:2 -n ethrex "cd ../.. && ulimit -n 1000000 && rm -rf ~/.local/share/ethrex && RUST_LOG=info,ethrex_p2p::sync=debug $(if $(DEBUG_ASSERT),RUSTFLAGS='-C debug-assertions=yes') $(if $(HEALING),SKIP_START_SNAP_SYNC=1) cargo run $(PROFILING_CFG) --release --bin ethrex --features rocksdb -- --http.addr 0.0.0.0 --http.api eth,net,web3,admin --metrics --metrics.port 3701 --network $(SERVER_SYNC_NETWORK) $(if $(MEMORY),--datadir memory) --authrpc.jwtsecret ~/secrets/jwt.hex $(if $(or $(FULL_SYNC),$(HEALING)),--syncmode full) 2>&1 | tee $(LOGS_FILE)" # ============================================================================== # Docker Compose Multi-Network Snapsync @@ -251,6 +252,9 @@ MULTISYNC_BUILD_PROFILE ?= release-with-debug-assertions MULTISYNC_LOCAL_IMAGE ?= ethrex-local:multisync # Branch to track for auto-update mode (defaults to current branch if not set) MULTISYNC_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) +# Sync phases that trigger TRACE logging and fast polling in the monitor. +# Empty by default (no extra tracing). Example: MULTISYNC_WATCHED_PHASES=healing +MULTISYNC_WATCHED_PHASES ?= multisync-up: ## Start all networks specified in MULTISYNC_NETWORKS via Docker Compose. $(MULTISYNC_COMPOSE) up -d $(MULTISYNC_SERVICES) @@ -341,4 +345,5 @@ multisync-loop-auto: ## Continuous loop with auto-update: pull latest, build, an --branch "$(MULTISYNC_BRANCH)" \ --build-profile "$(MULTISYNC_BUILD_PROFILE)" \ --image-tag "$(MULTISYNC_LOCAL_IMAGE)" \ - --ethrex-dir "$(ETHREX_DIR)" + --ethrex-dir "$(ETHREX_DIR)" \ + $(if $(MULTISYNC_WATCHED_PHASES),--watched-phases "$(MULTISYNC_WATCHED_PHASES)") diff --git a/sync/docker-compose.multisync.yaml b/sync/docker-compose.multisync.yaml index 200f913..d7bc192 100644 --- a/sync/docker-compose.multisync.yaml +++ b/sync/docker-compose.multisync.yaml @@ -28,9 +28,13 @@ x-ethrex-common: ðrex-common image: "${ETHREX_IMAGE:-ghcr.io/lambdaclass/ethrex:main}" pull_policy: "${ETHREX_PULL_POLICY:-always}" + environment: + RUST_LOG: "${RUST_LOG:-info,ethrex_p2p::sync=debug}" ulimits: nofile: 1000000 restart: unless-stopped + # Metrics are exposed via --metrics in each container's command. + # Only mainnet maps to host port 3701 (scraped by centralized Prometheus). x-consensus-common: &consensus-common image: sigp/lighthouse:v8.0.1 @@ -58,7 +62,7 @@ services: --http --http-address 0.0.0.0 --execution-endpoint http://ethrex-hoodi:8551 --execution-jwt /secrets/jwt.hex - --checkpoint-sync-url https://hoodi-checkpoint-sync.stakely.io/ + --checkpoint-sync-url https://checkpoint-sync.hoodi.ethpandaops.io --checkpoint-sync-url-timeout 600 --purge-db depends_on: @@ -70,16 +74,19 @@ services: container_name: ethrex-hoodi ports: - "8545:8545" # RPC + - "3702:3701" # Metrics volumes: - secrets-hoodi:/secrets - ethrex-hoodi:/data command: > --http.addr 0.0.0.0 + --http.api eth,net,web3,admin --network hoodi --authrpc.addr 0.0.0.0 --authrpc.jwtsecret /secrets/jwt.hex --syncmode snap --datadir /data + --metrics --metrics.addr 0.0.0.0 --metrics.port 3701 depends_on: setup-jwt-hoodi: condition: service_completed_successfully @@ -117,16 +124,19 @@ services: container_name: ethrex-sepolia ports: - "8546:8545" # RPC on different host port + - "3703:3701" # Metrics volumes: - secrets-sepolia:/secrets - ethrex-sepolia:/data command: > --http.addr 0.0.0.0 + --http.api eth,net,web3,admin --network sepolia --authrpc.addr 0.0.0.0 --authrpc.jwtsecret /secrets/jwt.hex --syncmode snap --datadir /data + --metrics --metrics.addr 0.0.0.0 --metrics.port 3701 depends_on: setup-jwt-sepolia: condition: service_completed_successfully @@ -164,16 +174,19 @@ services: container_name: ethrex-mainnet ports: - "8547:8545" # RPC on different host port + - "3701:3701" # Metrics (scraped by centralized Prometheus) volumes: - secrets-mainnet:/secrets - ethrex-mainnet:/data command: > --http.addr 0.0.0.0 + --http.api eth,net,web3,admin --network mainnet --authrpc.addr 0.0.0.0 --authrpc.jwtsecret /secrets/jwt.hex --syncmode snap --datadir /data + --metrics --metrics.addr 0.0.0.0 --metrics.port 3701 depends_on: setup-jwt-mainnet: condition: service_completed_successfully @@ -199,7 +212,7 @@ services: --http --http-address 0.0.0.0 --execution-endpoint http://ethrex-hoodi-2:8551 --execution-jwt /secrets/jwt.hex - --checkpoint-sync-url https://hoodi-checkpoint-sync.stakely.io/ + --checkpoint-sync-url https://checkpoint-sync.hoodi.ethpandaops.io --checkpoint-sync-url-timeout 600 --purge-db depends_on: @@ -216,11 +229,13 @@ services: - ethrex-hoodi-2:/data command: > --http.addr 0.0.0.0 + --http.api eth,net,web3,admin --network hoodi --authrpc.addr 0.0.0.0 --authrpc.jwtsecret /secrets/jwt.hex --syncmode snap --datadir /data + --metrics --metrics.addr 0.0.0.0 --metrics.port 3701 depends_on: setup-jwt-hoodi-2: condition: service_completed_successfully diff --git a/sync/docker-compose.yml b/sync/docker-compose.yml index 674b7d0..bcfcf78 100644 --- a/sync/docker-compose.yml +++ b/sync/docker-compose.yml @@ -43,6 +43,7 @@ services: - RUST_LOG=ethrex_p2p::rlpx::eth::blocks=off,ethrex_p2p::sync=debug,ethrex_p2p::network=info,spawned_concurrency::tasks::gen_server=off command: > --http.addr 0.0.0.0 + --http.api eth,net,web3,admin --authrpc.addr 0.0.0.0 --network hoodi --authrpc.jwtsecret /secrets/jwt.hex diff --git a/sync/docker_monitor.py b/sync/docker_monitor.py index 0ab1c42..fc4be68 100644 --- a/sync/docker_monitor.py +++ b/sync/docker_monitor.py @@ -61,6 +61,249 @@ } +# Diagnostics polling configuration +DIAGNOSTICS_NORMAL_INTERVAL = 30 # seconds between polls during normal operation +DIAGNOSTICS_DEGRADED_INTERVAL = 5 # seconds between polls during degradation +DIAGNOSTICS_NORMAL_BUFFER_SIZE = 20 # snapshots kept in normal mode +DIAGNOSTICS_DEGRADED_BUFFER_SIZE = 60 # snapshots kept in degraded mode +DEGRADATION_ELIGIBLE_PEERS_THRESHOLD = 5 # trigger if eligible peers below this +DEGRADATION_STALENESS_RATIO = 0.8 # trigger if pivot age > 80% of threshold +DEGRADATION_RECOVERY_TIMEOUT = 60 # seconds of health before leaving degraded mode +# Watched phases: sync phases that warrant closer monitoring. +# When the node enters a watched phase, the monitor bumps the log level to +# TRACE (via admin_setLogLevel) and switches to fast polling (5s intervals). +# This is useful for investigating specific sync stages — e.g. "healing" is +# where pivot-update failures tend to occur. +# +# Default: empty (no phases watched). Set via --watched-phases CLI flag: +# --watched-phases "healing" +# --watched-phases "healing,storage_insertion" +WATCHED_PHASES: set[str] = set() +LOG_LEVEL_NORMAL = "info,ethrex_p2p::sync=debug" +LOG_LEVEL_DEGRADED = "info,ethrex_p2p=trace" + + +class DiagnosticsTracker: + """Polls admin_peerScores and admin_syncStatus, keeps rolling buffer, dumps on degradation.""" + + def __init__(self, instances: list): + self.instances = instances + self.buffers: dict[str, list[dict]] = {inst.name: [] for inst in instances} + self.degraded: dict[str, bool] = {inst.name: False for inst in instances} + self.degraded_since: dict[str, float] = {inst.name: 0 for inst in instances} + self.healthy_since: dict[str, float] = {inst.name: 0 for inst in instances} + self.last_poll: dict[str, float] = {inst.name: 0 for inst in instances} + self.events: list[dict] = [] # degradation events across all networks + self.dumped_for_run: dict[str, bool] = {inst.name: False for inst in instances} + self._last_progress: dict[str, Optional[str]] = {inst.name: None for inst in instances} + self.last_reasons: dict[str, frozenset] = {inst.name: frozenset() for inst in instances} + + def poll_interval(self, name: str) -> float: + return DIAGNOSTICS_DEGRADED_INTERVAL if self.degraded[name] else DIAGNOSTICS_NORMAL_INTERVAL + + def buffer_limit(self, name: str) -> int: + return DIAGNOSTICS_DEGRADED_BUFFER_SIZE if self.degraded[name] else DIAGNOSTICS_NORMAL_BUFFER_SIZE + + def should_poll(self, name: str) -> bool: + return (time.time() - self.last_poll[name]) >= self.poll_interval(name) + + def poll(self, inst, force: bool = False) -> None: + """Poll diagnostics RPC endpoints for a single instance.""" + if not force and inst.status in ("success", "failed", "waiting"): + return + if not force and not self.should_poll(inst.name): + return + + self.last_poll[inst.name] = time.time() + peer_scores = rpc_call(inst.rpc_url, "admin_peerScores") + sync_status = rpc_call(inst.rpc_url, "admin_syncStatus") + + if peer_scores is None and sync_status is None: + return # node not reachable, skip + + snapshot = { + "timestamp": datetime.utcnow().isoformat() + "Z", + "epoch": time.time(), + "peer_scores": peer_scores, + "sync_status": sync_status, + } + + buf = self.buffers[inst.name] + buf.append(snapshot) + # Trim buffer to limit + limit = self.buffer_limit(inst.name) + while len(buf) > limit: + buf.pop(0) + + self._check_alert_conditions(inst, snapshot) + + def _check_alert_conditions(self, inst, snapshot: dict) -> None: + """Check for degradation conditions and trigger dump if needed.""" + now = time.time() + name = inst.name + reasons = [] + + # Check eligible peers + if snapshot.get("peer_scores") and isinstance(snapshot["peer_scores"], dict): + summary = snapshot["peer_scores"].get("summary", {}) + eligible = summary.get("eligible_peers", 999) + if eligible < DEGRADATION_ELIGIBLE_PEERS_THRESHOLD: + reasons.append(f"eligible_peers={eligible}") + + # Check sync progress stall + if snapshot.get("sync_status") and isinstance(snapshot["sync_status"], dict): + phase = snapshot["sync_status"].get("current_phase", "idle") + progress_key = str(snapshot["sync_status"].get("phase_progress", {})) + if phase not in ("idle", ""): + if self._last_progress[name] is not None and self._last_progress[name] == progress_key: + pass # stall detection not yet implemented + self._last_progress[name] = progress_key + + # Check staleness ratio + pivot_age = snapshot["sync_status"].get("pivot_age_seconds") + threshold = snapshot["sync_status"].get("staleness_threshold_seconds", 0) + if pivot_age and threshold and threshold > 0: + ratio = pivot_age / threshold + if ratio > DEGRADATION_STALENESS_RATIO: + reasons.append(f"staleness_ratio={ratio:.2f}") + + # Healing phase is high-risk for pivot failures — increase polling + if phase in WATCHED_PHASES: + reasons.append(f"watched_phase:{phase}") + + reasons_set = frozenset(reasons) + if reasons: + newly_degraded = not self.degraded[name] + reasons_changed = reasons_set != self.last_reasons.get(name, frozenset()) + if newly_degraded: + self.degraded[name] = True + self.degraded_since[name] = now + self.healthy_since[name] = 0 + # Distinguish intentional tracing (watched phase) from real issues + only_watched = all(r.startswith("watched_phase:") for r in reasons) + event = { + "timestamp": datetime.utcnow().isoformat() + "Z", + "network": name, + "event_type": "watched_phase_start" if only_watched else "degradation_start", + "reasons": reasons, + "eligible_peers": snapshot.get("peer_scores", {}).get("summary", {}).get("eligible_peers"), + "phase": snapshot.get("sync_status", {}).get("current_phase"), + } + self.events.append(event) + if only_watched: + print(f"👁️ [{name}] Watched phase active: {', '.join(reasons)} — increasing poll frequency") + else: + print(f"⚠️ [{name}] Degradation detected: {', '.join(reasons)} — increasing poll frequency") + # Bump log level to TRACE for detailed peer comms + if rpc_set_log_level(inst.rpc_url, LOG_LEVEL_DEGRADED): + print(f"🔍 [{name}] Log level bumped to TRACE for peer diagnostics") + else: + print(f"⚠️ [{name}] Failed to bump log level") + elif reasons_changed: + # Already degraded but reasons changed — record and log + event = { + "timestamp": datetime.utcnow().isoformat() + "Z", + "network": name, + "event_type": "reasons_changed", + "reasons": reasons, + "phase": snapshot.get("sync_status", {}).get("current_phase"), + } + self.events.append(event) + print(f"🔄 [{name}] Monitor reasons changed: {', '.join(reasons)}") + # Dump snapshots on degradation / watched phase + self._dump_snapshots(name) + else: + # Healthy — check if we can exit degraded mode + if self.degraded[name]: + if self.healthy_since[name] == 0: + self.healthy_since[name] = now + elif (now - self.healthy_since[name]) >= DEGRADATION_RECOVERY_TIMEOUT: + self.degraded[name] = False + self.healthy_since[name] = 0 + event = { + "timestamp": datetime.utcnow().isoformat() + "Z", + "network": name, + "event_type": "monitoring_normal", + } + self.events.append(event) + print(f"✅ [{name}] Monitoring back to normal — resuming default poll frequency") + # Restore log level to normal + if rpc_set_log_level(inst.rpc_url, LOG_LEVEL_NORMAL): + print(f"📝 [{name}] Log level restored to DEBUG") + else: + print(f"⚠️ [{name}] Failed to restore log level") + self.last_reasons[name] = reasons_set + + def on_failure(self, inst, name: str) -> None: + """Called when a network fails — do a final poll and dump snapshots.""" + # Do one last poll to capture the state at failure time + self.poll(inst, force=True) + # Bump log level to capture any post-failure details + rpc_set_log_level(inst.rpc_url, LOG_LEVEL_DEGRADED) + # Always dump on failure, even if previously dumped for degradation + self._dump_snapshots(name, force=True) + event = { + "timestamp": datetime.utcnow().isoformat() + "Z", + "network": name, + "event_type": "failure", + } + self.events.append(event) + + def set_run_id(self, run_id: str) -> None: + """Set the current run ID so snapshots go to the right directory.""" + self.run_id = run_id + # Ensure the directory exists now, not at the end of the run + run_dir = LOGS_DIR / f"run_{run_id}" + run_dir.mkdir(parents=True, exist_ok=True) + + def _dump_snapshots(self, name: str, force: bool = False) -> None: + """Dump the rolling buffer to disk.""" + if not force and self.dumped_for_run.get(name): + return + self.dumped_for_run[name] = True + buf = self.buffers[name] + if not buf: + return + if not hasattr(self, 'run_id') or not self.run_id: + return + run_dir = LOGS_DIR / f"run_{self.run_id}" + run_dir.mkdir(parents=True, exist_ok=True) + out_path = run_dir / f"{name}_peer_snapshots.json" + try: + import json + out_path.write_text(json.dumps(buf, indent=2, default=str)) + print(f"📸 [{name}] Dumped {len(buf)} diagnostic snapshots to {out_path}") + except Exception as e: + print(f"⚠️ [{name}] Failed to dump snapshots: {e}") + + def format_degradation_events(self) -> str: + """Format monitor events for the summary.txt.""" + if not self.events: + return "" + lines = ["\n Monitor Events:"] + for ev in self.events: + ts = ev["timestamp"] + net = ev.get("network", "?") + evt = ev.get("event_type", "?") + reasons = ev.get("reasons", []) + detail = f" ({', '.join(reasons)})" if reasons else "" + lines.append(f" {ts} [{net}] {evt}{detail}") + return "\n".join(lines) + + def reset(self) -> None: + """Reset state for a new run.""" + for name in self.buffers: + self.buffers[name] = [] + self.degraded[name] = False + self.degraded_since[name] = 0 + self.healthy_since[name] = 0 + self.last_poll[name] = 0 + self.dumped_for_run[name] = False + self._last_progress[name] = None + self.last_reasons[name] = frozenset() + self.events = [] + + @dataclass class Instance: name: str @@ -155,6 +398,19 @@ def build_docker_image(profile: str, image_tag: str, ethrex_dir: str) -> bool: check=True ) print("✅ Docker image built successfully") + # Prune build cache to prevent unbounded disk growth across runs + try: + result = subprocess.run( + ["docker", "builder", "prune", "-f"], + capture_output=True, text=True, timeout=120 + ) + if result.returncode == 0: + reclaimed = result.stdout.strip().split("\n")[-1] if result.stdout.strip() else "" + print(f"🧹 Build cache pruned. {reclaimed}") + else: + print(f"⚠️ Build cache prune failed: {result.stderr.strip()}") + except Exception as e: + print(f"⚠️ Build cache prune error: {e}") return True except subprocess.CalledProcessError as e: print(f"❌ Failed to build Docker image: {e}") @@ -274,6 +530,15 @@ def rpc_call(url: str, method: str) -> Optional[Any]: return None +def rpc_set_log_level(url: str, level: str) -> bool: + """Set the node's log level via admin_setLogLevel RPC.""" + try: + resp = requests.post(url, json={"jsonrpc": "2.0", "method": "admin_setLogLevel", "params": [level], "id": 1}, timeout=5).json() + return resp.get("result") is not None and "error" not in resp + except Exception: + return False + + def parse_phase_timings(run_id: str, container: str) -> list[tuple[str, str, str]]: """Parse phase completion times from saved container logs. @@ -410,18 +675,18 @@ def save_container_logs(container: str, run_id: str, suffix: str = ""): def save_all_logs(instances: list[Instance], run_id: str, compose_file: str): """Save logs for all containers (ethrex + consensus).""" print(f"\n📁 Saving logs for run {run_id}...") - + for inst in instances: # Save ethrex logs save_container_logs(inst.container, run_id) # Save consensus logs (convention: consensus-{network}) consensus_container = inst.container.replace("ethrex-", "consensus-") save_container_logs(consensus_container, run_id) - + print(f"📁 Logs saved to {LOGS_DIR}/run_{run_id}/\n") -def log_run_result(run_id: str, run_count: int, instances: list[Instance], hostname: str, branch: str, commit: str, build_profile: str = ""): +def log_run_result(run_id: str, run_count: int, instances: list[Instance], hostname: str, branch: str, commit: str, build_profile: str = "", diagnostics_tracker: Optional['DiagnosticsTracker'] = None): """Append run result to the persistent log file.""" ensure_logs_dir() all_success = all(i.status == "success" for i in instances) @@ -477,15 +742,27 @@ def log_run_result(run_id: str, run_count: int, instances: list[Instance], hostn for name, count, duration in phases: lines.append(f" {name:<{max_name_len}} {duration} ({count})") + # Include degradation events if any + if diagnostics_tracker: + degradation_text = diagnostics_tracker.format_degradation_events() + if degradation_text: + lines.append(degradation_text) + lines.append("") # Append to log file - with open(RUN_LOG_FILE, "a") as f: - f.write("\n".join(lines) + "\n") - print(f"📝 Run logged to {RUN_LOG_FILE}") - # Also write summary to the run folder - summary_file = LOGS_DIR / f"run_{run_id}" / "summary.txt" - summary_file.parent.mkdir(parents=True, exist_ok=True) - summary_file.write_text("\n".join(lines)) + text = "\n".join(lines) + "\n" + try: + with open(RUN_LOG_FILE, "a") as f: + f.write(text) + print(f"📝 Run logged to {RUN_LOG_FILE}") + # Also write summary to the run folder + summary_file = LOGS_DIR / f"run_{run_id}" / "summary.txt" + summary_file.parent.mkdir(parents=True, exist_ok=True) + summary_file.write_text(text) + except OSError as e: + print(f"⚠️ Failed to write run log (disk full?): {e}", flush=True) + # Print to stdout so the result isn't lost entirely + print(text, flush=True) def generate_run_id() -> str: @@ -667,6 +944,7 @@ def update_instance(inst: Instance, timeout_min: int) -> bool: def main(): + global WATCHED_PHASES p = argparse.ArgumentParser(description="Monitor Docker snapsync instances") p.add_argument("--networks", default="hoodi,sepolia,mainnet") p.add_argument("--timeout", type=int, default=SYNC_TIMEOUT) @@ -685,8 +963,13 @@ def main(): help="Docker image tag to build") p.add_argument("--ethrex-dir", default=os.environ.get("ETHREX_DIR", "../.."), help="Path to ethrex repository root") + p.add_argument("--watched-phases", default=",".join(WATCHED_PHASES), + help="Comma-separated sync phases that trigger TRACE logging and fast polling (default: healing)") args = p.parse_args() + # Apply CLI override for watched phases + WATCHED_PHASES = {ph.strip() for ph in args.watched_phases.split(",") if ph.strip()} + # Resolve ethrex directory to absolute path ethrex_dir = os.path.abspath(args.ethrex_dir) @@ -699,7 +982,8 @@ def main(): containers = [f"ethrex-{n}" for n in names] instances = [Instance(n, p, c) for n, p, c in zip(names, ports, containers)] - + tracker = DiagnosticsTracker(instances) + # Detect state of already-running containers for inst in instances: if t := container_start_time(inst.container): @@ -731,6 +1015,7 @@ def main(): # Get run count from existing logs (persists across restarts) run_count = get_next_run_count() run_id = generate_run_id() + tracker.set_run_id(run_id) print(f"📁 Logs will be saved to {LOGS_DIR.absolute()}") print(f"📝 Run history: {RUN_LOG_FILE.absolute()}") @@ -763,6 +1048,7 @@ def main(): # Reset instances since we restarted for inst in instances: reset_instance(inst) + tracker.reset() time.sleep(30) # Wait for containers to start print(f"{'='*60}\n") @@ -770,6 +1056,12 @@ def main(): last_print = 0 while True: changed = any(update_instance(i, args.timeout) for i in instances) + # Poll diagnostics endpoints + for inst in instances: + tracker.poll(inst) + # Trigger dump on failure + if inst.status == "failed" and changed: + tracker.on_failure(inst, inst.name) if changed or (time.time() - last_print) > STATUS_PRINT_INTERVAL: print_status(instances) last_print = time.time() @@ -779,7 +1071,7 @@ def main(): time.sleep(CHECK_INTERVAL) # Log the run result and save container logs BEFORE any restart save_all_logs(instances, run_id, args.compose_file) - log_run_result(run_id, run_count, instances, hostname, branch, commit, args.build_profile) + log_run_result(run_id, run_count, instances, hostname, branch, commit, args.build_profile, tracker) # Send a single Slack summary notification for the run if not args.no_slack: slack_notify(run_id, run_count, instances, hostname, branch, commit, args.build_profile) @@ -791,6 +1083,7 @@ def main(): # Prepare for another run run_count += 1 run_id = generate_run_id() # New run ID for the new cycle + tracker.set_run_id(run_id) # If auto-update is enabled, the loop will pull/build/restart # Otherwise, just restart containers now diff --git a/sync/peer_top.py b/sync/peer_top.py new file mode 100644 index 0000000..48a1b49 --- /dev/null +++ b/sync/peer_top.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +"""Live peer table viewer — like top for ethrex peers.""" +import os, signal, sys, time +import requests as req + +ENDPOINT = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:18547" +INTERVAL = float(sys.argv[2]) if len(sys.argv) > 2 else 1.0 + +# ANSI colors +RED = "\033[31m" +GREEN = "\033[32m" +YELLOW = "\033[33m" +CYAN = "\033[36m" +DIM = "\033[2m" +BOLD = "\033[1m" +RESET = "\033[0m" + +# Track previous scores for delta coloring +prev_scores: dict[str, int] = {} + + +def fetch(method): + try: + r = req.post( + ENDPOINT, + json={"jsonrpc": "2.0", "method": method, "params": [], "id": 1}, + timeout=3, + ) + return r.json().get("result") + except Exception: + return None + + +start_time = time.time() + + +def color_score(peer_id: str, score: int) -> str: + """Color the score based on value and delta from previous tick.""" + prev = prev_scores.get(peer_id) + if score <= -30: + color = RED + elif score <= 0: + color = YELLOW + else: + color = GREEN + + if prev is not None and prev != score: + if score > prev: + # Score went up — bright green arrow + return f"{GREEN}{BOLD}{score:>4} \u2191{RESET}" + else: + # Score went down — bright red arrow + return f"{RED}{BOLD}{score:>4} \u2193{RESET}" + return f"{color}{score:>4} {RESET}" + + +def trim_client(client: str, width: int) -> str: + """Trim client/version string to width. When very tight, show just the client name.""" + if len(client) <= width: + return client + if width < 10: + # Space is tight — drop version info, keep the client name + name = client.split("/")[0] + return name[:width] + return client[: width - 1] + "\u2026" + + +def render(term_cols: int): + global prev_scores + lines = [] + elapsed = int(time.time() - start_time) + h, m, s = elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60 + now_str = time.strftime("%H:%M:%S") + lines.append( + f"{BOLD}peer_top{RESET} {DIM}— {now_str} — up {h:02d}:{m:02d}:{s:02d} — {ENDPOINT}{RESET}" + ) + lines.append("") + sync = fetch("admin_syncStatus") + data = fetch("admin_peerScores") + + if sync: + phase = sync.get("current_phase") or "idle" + pivot = sync.get("pivot_block_number") or "?" + age = sync.get("pivot_age_seconds") + threshold = sync.get("staleness_threshold_seconds", 0) + progress = sync.get("phase_progress", {}) + age_str = f"{age}s" if age else "?" + + # Color staleness margin + if age and threshold: + margin_secs = threshold - age + if margin_secs < 0: + margin_color = RED + elif margin_secs < 300: + margin_color = YELLOW + else: + margin_color = GREEN + margin = f"{margin_color}({margin_secs}s to stale){RESET}" + else: + margin = "" + + lines.append( + f"{BOLD}Phase:{RESET} {CYAN}{phase}{RESET} " + f"{BOLD}Pivot:{RESET} {pivot} " + f"{BOLD}Age:{RESET} {age_str} {margin}" + ) + if progress: + parts = [f"{k}={v:,}" for k, v in progress.items()] + lines.append(f"{DIM}Progress: {', '.join(parts)}{RESET}") + + # Pivot update history + pivot_changes = sync.get("recent_pivot_changes", []) + if pivot_changes: + lines.append("") + lines.append(f"{BOLD}Pivot History:{RESET} (last {len(pivot_changes)})") + for pc in pivot_changes[-5:]: # show last 5 + ts = pc.get("timestamp", 0) + ts_str = time.strftime("%H:%M:%S", time.localtime(ts)) if ts else "?" + old_n = pc.get("old_pivot_number", "?") + new_n = pc.get("new_pivot_number", "?") + outcome = pc.get("outcome", "?") + reason = pc.get("failure_reason", "") + if outcome == "success": + icon = f"{GREEN}\u2713{RESET}" + else: + icon = f"{RED}\u2717{RESET}" + if reason: + reason = f" {RED}{reason}{RESET}" + lines.append( + f" {icon} {DIM}{ts_str}{RESET} {old_n} \u2192 {new_n} [{outcome}]{reason}" + ) + + # Recent errors + errors = sync.get("recent_errors", []) + if errors: + lines.append("") + lines.append(f"{BOLD}Recent Errors:{RESET} (last {len(errors)})") + for err in errors[-3:]: # show last 3 + ts = err.get("timestamp", 0) + ts_str = time.strftime("%H:%M:%S", time.localtime(ts)) if ts else "?" + msg = err.get("error_message", "?")[:60] + recov = f"{GREEN}recoverable{RESET}" if err.get("recoverable") else f"{RED}irrecoverable{RESET}" + lines.append(f" {DIM}{ts_str}{RESET} {msg} [{recov}]") + + lines.append("") + + if not data: + lines.append(f"{RED}Node not reachable{RESET}") + return lines + + s = data["summary"] + peers = data["peers"] + + # Color eligible count + elig_count = s["eligible_peers"] + if elig_count < 5: + elig_color = RED + elif elig_count < 20: + elig_color = YELLOW + else: + elig_color = GREEN + + lines.append( + f"{BOLD}Peers:{RESET} {s['total_peers']} " + f"{BOLD}Eligible:{RESET} {elig_color}{elig_count}{RESET} " + f"{BOLD}Avg Score:{RESET} {s['average_score']} " + f"{BOLD}Inflight:{RESET} {s['total_inflight_requests']}" + ) + lines.append("") + + # Column widths — fixed columns + dynamic Capabilities / Client + # Layout: PID Score Reqs Elig Caps Dir Client + W_PID, W_SCORE, W_REQS, W_ELIG, W_DIR = 14, 6, 5, 4, 4 + SEPARATORS = 6 # one space between each of the 7 columns + fixed = W_PID + W_SCORE + W_REQS + W_ELIG + W_DIR + SEPARATORS # = 39 + # Budget for Caps + Client. Leave 1 char right-margin. + budget = max(20, term_cols - fixed - 1) + W_CAPS = max(12, min(22, budget - 10)) # caps capped at 22, min 12 + W_CLIENT = max(8, budget - W_CAPS) + + lines.append( + f"{DIM}{'Peer ID':>{W_PID}} {'Score':>{W_SCORE}} {'Reqs':>{W_REQS}}" + f" {'Elig':>{W_ELIG}} {'Capabilities':<{W_CAPS}} {'Dir':>{W_DIR}}" + f" {'Client':<{W_CLIENT}}{RESET}" + ) + lines.append(f"{DIM}{'-' * (fixed + W_CAPS + W_CLIENT)}{RESET}") + + new_scores = {} + for p in sorted(peers, key=lambda x: x["score"], reverse=True): + pid_full = p["peer_id"] + pid = pid_full[:6] + ".." + pid_full[-4:] + score = p["score"] + new_scores[pid_full] = score + + score_str = color_score(pid_full, score) + + # Group capabilities by protocol + by_proto = {} + for c in p["capabilities"]: + parts = c.split("/") + proto = parts[0] + ver = parts[1] if len(parts) > 1 else "?" + by_proto.setdefault(proto, []).append(ver) + caps = " ".join(f"{k}/{','.join(vs)}" for k, vs in by_proto.items()) + if len(caps) > W_CAPS: + caps = caps[: W_CAPS - 1] + "\u2026" + client = trim_client(p["client_version"], W_CLIENT) + d = p["connection_direction"][:3] + + elig_char = "\u2713" if p["eligible"] else "\u2717" + elig_col = GREEN if p["eligible"] else RED + # Visible-width 1, right-aligned in W_ELIG column + elig_str = f"{' ' * (W_ELIG - 1)}{elig_col}{elig_char}{RESET}" + + reqs = p["inflight_requests"] + reqs_str = f"{YELLOW}{reqs:>{W_REQS}}{RESET}" if reqs > 0 else f"{reqs:>{W_REQS}}" + + lines.append( + f"{pid:>{W_PID}} {score_str} {reqs_str}" + f" {elig_str} {caps:<{W_CAPS}} {d:>{W_DIR}}" + f" {DIM}{client:<{W_CLIENT}}{RESET}" + ) + + prev_scores = new_scores + return lines + + +def cleanup(*_): + sys.stdout.write("\033[?1049l\033[?25h") + sys.stdout.flush() + sys.exit(0) + + +def main(): + signal.signal(signal.SIGINT, cleanup) + signal.signal(signal.SIGTERM, cleanup) + + sys.stdout.write("\033[?1049h\033[?25l\033[2J") + sys.stdout.flush() + + try: + prev_line_count = 0 + while True: + try: + size = os.get_terminal_size() + term_rows, term_cols = size.lines, size.columns + except OSError: + term_rows, term_cols = 40, 120 + lines = render(term_cols) + if len(lines) > term_rows - 2: + hidden = len(lines) - term_rows + 3 + lines = lines[: term_rows - 3] + lines.append( + f" {DIM}... {hidden} more peers (resize terminal to see all){RESET}" + ) + buf = "\033[H" + for line in lines: + buf += f"{line}\033[K\n" + for _ in range(max(0, prev_line_count - len(lines))): + buf += "\033[K\n" + sys.stdout.write(buf) + sys.stdout.flush() + prev_line_count = len(lines) + time.sleep(INTERVAL) + except Exception: + cleanup() + + +if __name__ == "__main__": + main() diff --git a/trace_compare/README.md b/trace_compare/README.md new file mode 100644 index 0000000..55121fb --- /dev/null +++ b/trace_compare/README.md @@ -0,0 +1,53 @@ +# trace_compare + +Spot-checks ethrex's `debug_traceTransaction` output against the other EL clients +running side-by-side in a kurtosis enclave. Useful for sanity-checking +[`OpcodeStep`](../../crates/common/tracing.rs) wire-format changes against geth and besu +without leaving the local machine. + +## Prereqs + +- Docker (OrbStack on Mac, or Docker Desktop) +- `kurtosis` CLI (`brew install kurtosis-tech/tap/kurtosis-cli`) +- `curl`, `jq` + +## Usage + +```bash +# 1. Start a multi-client enclave (~5 min on first run, builds the ethrex image) +make localnet + +# 2. Once the chain is producing blocks, compare a tx across every EL +tooling/trace_compare/compare.sh +# auto-discovers el-* services in the `lambdanet` enclave, auto-picks a tx +# from `latest`, traces it on every client, prints suggested diffs. + +# Trace a specific tx +tooling/trace_compare/compare.sh --tx 0xabcd... +``` + +## What it does + +1. Parses `kurtosis enclave inspect lambdanet` to find every `el-*` service and + its host-mapped RPC port (`kurtosis port print … rpc` / `… http`). +2. Picks the first tx from the latest block (via the first discovered client) if + `--tx` wasn't supplied. +3. Calls `debug_traceTransaction` against each client's RPC and saves the + responses to `trace-compare-/.json`. +4. Prints suggested pairwise `diff` commands for `structLogs` only (the + wrapper fields can introduce noise that's not interesting for tracer work). + +## What divergences mean + +After [EIP-3155 alignment](https://eips.ethereum.org/EIPS/eip-3155) for +`OpcodeStep` (commit `dc11a20e1` on `feat/eip-3155-tracer`), per-step output +should match geth byte-for-byte on the fields it has in common +(`pc`, `op`, `gas`, `gasCost`, `depth`, `stack`, `memSize`, `returnData`, +`refund`, `opName`). Besu emits the same shape but sometimes with extra fields. + +Diffs in the wrapper (`failed` / `gas` / `returnValue` / `structLogs`) are +expected to match — all three clients emit the geth structLogger wrapper. + +Step-count differences usually point at fused-opcode handling (cf. the +[JUMPDEST regression test](../../test/tests/levm/opcode_tracer_tests.rs)) +or at a real divergence in execution. diff --git a/trace_compare/compare.sh b/trace_compare/compare.sh new file mode 100755 index 0000000..98e663d --- /dev/null +++ b/trace_compare/compare.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# Compare `debug_traceTransaction` output across every EL client in a kurtosis enclave. +# +# Prereqs: +# - `make localnet` already running (or any kurtosis enclave with at least one EL service) +# - `kurtosis`, `curl`, `jq` on $PATH +# +# Usage: +# tooling/trace_compare/compare.sh [--enclave NAME] [--tx 0xHASH] [--out DIR] +# +# Defaults: +# --enclave lambdanet (matches the `make localnet` enclave name) +# --tx +# --out ./trace-compare- +# +# The script: +# 1. Discovers every `el-*` service in the enclave and its host-mapped RPC port. +# 2. If --tx wasn't given, picks the first tx from the latest block (using the +# first discovered client's RPC). +# 3. Calls `debug_traceTransaction` against every client and saves each response +# to `/.json`. +# 4. Prints suggested `diff` commands for every pair. +# +# Why this exists: spot-checking that ethrex's `OpcodeStep` wire shape (and any +# future tracer changes) match the other major clients on the same execution. + +set -euo pipefail + +ENCLAVE="lambdanet" +TX_HASH="" +OUT_DIR="" + +usage() { + sed -n '2,/^set -euo pipefail/p' "$0" | sed -e 's/^# \{0,1\}//' -e '$d' + exit "${1:-0}" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --enclave) ENCLAVE="$2"; shift 2 ;; + --tx) TX_HASH="$2"; shift 2 ;; + --out) OUT_DIR="$2"; shift 2 ;; + -h|--help) usage 0 ;; + *) echo "unknown arg: $1" >&2; usage 1 ;; + esac +done + +if [[ -z "$OUT_DIR" ]]; then + OUT_DIR="./trace-compare-$(date +%Y%m%d-%H%M%S)" +fi +mkdir -p "$OUT_DIR" + +for cmd in kurtosis curl jq; do + command -v "$cmd" >/dev/null 2>&1 || { echo "error: '$cmd' not on \$PATH" >&2; exit 1; } +done + +if ! kurtosis enclave inspect "$ENCLAVE" >/dev/null 2>&1; then + echo "error: kurtosis enclave '$ENCLAVE' not found. Did you run \`make localnet\`?" >&2 + exit 1 +fi + +# Discover EL services. Kurtosis names them `el-N--`, e.g. `el-1-geth-lighthouse`. +# In `kurtosis enclave inspect` the service name is column 2 (after the UUID). +# Avoid `mapfile` because macOS still ships bash 3.2. +SERVICES=() +while IFS= read -r svc; do + [[ -n "$svc" ]] && SERVICES+=("$svc") +done < <( + kurtosis enclave inspect "$ENCLAVE" 2>/dev/null \ + | awk '$2 ~ /^el-/ {print $2}' \ + | sort -u +) + +if [[ ${#SERVICES[@]} -eq 0 ]]; then + echo "error: no EL services found in enclave '$ENCLAVE'" >&2 + exit 1 +fi + +# Parallel arrays instead of `declare -A` (associative arrays are bash 4+). +# `RPC_NAMES[i]` is the service name, `RPC_URLS[i]` its rpc/http endpoint. +RPC_NAMES=() +RPC_URLS=() +for svc in "${SERVICES[@]}"; do + # Try `rpc` first (geth/ethrex/reth), then `http` (besu/nethermind sometimes). + url=$(kurtosis port print "$ENCLAVE" "$svc" rpc 2>/dev/null || true) + if [[ -z "$url" ]]; then + url=$(kurtosis port print "$ENCLAVE" "$svc" http 2>/dev/null || true) + fi + if [[ -z "$url" ]]; then + echo "warn: no rpc/http port found for $svc, skipping" >&2 + continue + fi + RPC_NAMES+=("$svc") + RPC_URLS+=("$url") + echo "$svc -> $url" +done + +if [[ ${#RPC_NAMES[@]} -eq 0 ]]; then + echo "error: no usable RPC URLs discovered" >&2 + exit 1 +fi + +# Pick a tx if not specified. +if [[ -z "$TX_HASH" ]]; then + some_svc="${RPC_NAMES[0]}" + some_url="${RPC_URLS[0]}" + TX_HASH=$( + curl -s "$some_url" -H 'content-type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]}' \ + | jq -r '.result.transactions[0] // empty' + ) + if [[ -z "$TX_HASH" ]]; then + echo "error: 'latest' has no transactions on $some_svc. Specify --tx ." >&2 + exit 1 + fi + echo "auto-picked tx: $TX_HASH (from $some_svc)" +fi + +echo "tracing $TX_HASH across ${#RPC_NAMES[@]} clients..." +for i in "${!RPC_NAMES[@]}"; do + svc="${RPC_NAMES[$i]}" + url="${RPC_URLS[$i]}" + out="$OUT_DIR/${svc}.json" + + # Per-client tracer config: + # geth/besu/reth/erigon default to the structLogger (opcode-level) tracer when + # no `tracer` is set in params. ethrex's RPC default is `callTracer` instead + # (call-frame level), so we have to opt into the opcode tracer explicitly. + # The named tracer "opcodeTracer" exists only on ethrex; passing it to geth + # would error with "unknown tracer". Hence the conditional. + if [[ "$svc" == *"-ethrex-"* ]]; then + tracer_cfg='{"tracer":"opcodeTracer"}' + else + tracer_cfg='{}' + fi + + curl -s "$url" -H 'content-type: application/json' \ + -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"debug_traceTransaction\",\"params\":[\"$TX_HASH\",$tracer_cfg]}" \ + > "$out" + if jq -e '.error' "$out" >/dev/null 2>&1; then + echo " $svc -> $out (ERROR: $(jq -r '.error.message' "$out"))" + else + n=$(jq -r '.result.structLogs | length // 0' "$out") + echo " $svc -> $out ($n structLogs)" + fi +done + +echo "" +echo "saved under: $OUT_DIR" +echo "" + +# Print pairwise diff commands. structLogs comparison is the interesting bit; +# wrappers and per-step extras can introduce noise. +echo "compare with (structLogs only):" +for ((i=0; i<${#RPC_NAMES[@]}; i++)); do + for ((j=i+1; j<${#RPC_NAMES[@]}; j++)); do + a="${RPC_NAMES[i]}"; b="${RPC_NAMES[j]}" + echo " diff <(jq '.result.structLogs' $OUT_DIR/$a.json) <(jq '.result.structLogs' $OUT_DIR/$b.json)" + done +done