From 998ec717f6ed019268ef0e23d61e3f1ca868c66a Mon Sep 17 00:00:00 2001 From: greged93 <82421016+greged93@users.noreply.github.com> Date: Sun, 21 Apr 2024 17:41:42 +0200 Subject: [PATCH] feat: trace_block (#979) * wip tracing * wip tracing * cleaning * refactor tracing * add testing * fix lint * fix comments * add builder + macro for testing * clean test helper function * clean tracing + update test * add comments * fix comments --- .trunk/trunk.yaml | 2 +- Cargo.lock | 250 +++++++++++++------------ Cargo.toml | 4 +- src/eth_provider/error.rs | 4 + src/eth_rpc/api/mod.rs | 1 + src/eth_rpc/api/trace_api.rs | 13 ++ src/eth_rpc/rpc.rs | 7 +- src/eth_rpc/servers/debug_rpc.rs | 6 +- src/eth_rpc/servers/mod.rs | 1 + src/eth_rpc/servers/trace_rpc.rs | 32 ++++ src/lib.rs | 1 + src/models/block.rs | 10 +- src/models/transaction.rs | 310 ++++++++++++++++++++----------- src/test_utils/eoa.rs | 42 +++-- src/test_utils/evm_contract.rs | 93 ++++++++-- src/test_utils/fixtures.rs | 22 +++ src/test_utils/katana/mod.rs | 61 +++++- src/tracing/database.rs | 111 +++++++++++ src/tracing/mod.rs | 137 ++++++++++++++ tests/debug_api.rs | 4 +- tests/trace_api.rs | 155 ++++++++++++++++ 21 files changed, 982 insertions(+), 284 deletions(-) create mode 100644 src/eth_rpc/api/trace_api.rs create mode 100644 src/eth_rpc/servers/trace_rpc.rs create mode 100644 src/tracing/database.rs create mode 100644 src/tracing/mod.rs create mode 100644 tests/trace_api.rs diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index c06f183ba..ff2e2eb32 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -22,7 +22,7 @@ lint: - git-diff-check - hadolint@2.12.0 - markdownlint@0.39.0 - - osv-scanner@1.7.0 + - osv-scanner@1.7.2 - oxipng@9.0.0 - prettier@3.2.5 - rustfmt@1.65.0 diff --git a/Cargo.lock b/Cargo.lock index 213755983..cdaf25d7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1407,15 +1407,6 @@ dependencies = [ "serde", ] -[[package]] -name = "btoi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" -dependencies = [ - "num-traits 0.2.18", -] - [[package]] name = "build-utils" version = "0.1.0" @@ -4704,9 +4695,6 @@ name = "faster-hex" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" -dependencies = [ - "serde", -] [[package]] name = "fastrand" @@ -5220,9 +5208,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gix" -version = "0.58.0" +version = "0.62.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31887c304d9a935f3e5494fb5d6a0106c34e965168ec0db9b457424eedd0c741" +checksum = "5631c64fb4cd48eee767bf98a3cbc5c9318ef3bb71074d4c099a2371510282b6" dependencies = [ "gix-actor", "gix-archive", @@ -5233,6 +5221,7 @@ dependencies = [ "gix-credentials", "gix-date", "gix-diff", + "gix-dir", "gix-discover", "gix-features", "gix-filter", @@ -5278,23 +5267,23 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.30.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7bb9fad6125c81372987c06469601d37e1a2d421511adb69971b9083517a8a" +checksum = "45c3a3bde455ad2ee8ba8a195745241ce0b770a8a26faae59fcf409d01b28c46" dependencies = [ "bstr", - "btoi", "gix-date", + "gix-utils", "itoa", "thiserror", - "winnow 0.5.36", + "winnow 0.6.5", ] [[package]] name = "gix-archive" -version = "0.9.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4d28c5375dd1a8a433ce0bb6d2a462182d853861d625cb43f0a821365ac020" +checksum = "62f28b5481bbe35de9f2eacbd8dbc61da7b8d763eaecd667018602aa805e2e2e" dependencies = [ "bstr", "gix-date", @@ -5305,9 +5294,9 @@ dependencies = [ [[package]] name = "gix-attributes" -version = "0.22.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214ee3792e504ee1ce206b36dcafa4f328ca313d1e2ac0b41433d68ef4e14260" +checksum = "eefb48f42eac136a4a0023f49a54ec31be1c7a9589ed762c45dcb9b953f7ecc8" dependencies = [ "bstr", "gix-glob", @@ -5322,27 +5311,27 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b6cd0f246180034ddafac9b00a112f19178135b21eb031b3f79355891f7325" +checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae" dependencies = [ "thiserror", ] [[package]] name = "gix-chunk" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003ec6deacf68076a0c157271a127e0bb2c031c1a41f7168cbe5d248d9b85c78" +checksum = "45c8751169961ba7640b513c3b24af61aa962c967aaf04116734975cd5af0c52" dependencies = [ "thiserror", ] [[package]] name = "gix-command" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1ffc7db3fb50b7dae6ecd937a3527cb725f444614df2ad8988d81806f13f09" +checksum = "f90009020dc4b3de47beed28e1334706e0a330ddd17f5cfeb097df3b15a54b77" dependencies = [ "bstr", "gix-path", @@ -5352,9 +5341,9 @@ dependencies = [ [[package]] name = "gix-commitgraph" -version = "0.24.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82dbd7fb959862e3df2583331f0ad032ac93533e8a52f1b0694bc517f5d292bc" +checksum = "f7b102311085da4af18823413b5176d7c500fb2272eaf391cfa8635d8bcb12c4" dependencies = [ "bstr", "gix-chunk", @@ -5366,9 +5355,9 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62bf2073b6ce3921ffa6d8326f645f30eec5fc4a8e8a4bc0fcb721a2f3f69dc" +checksum = "7580e05996e893347ad04e1eaceb92e1c0e6a3ffe517171af99bf6b6df0ca6e5" dependencies = [ "bstr", "gix-config-value", @@ -5382,14 +5371,14 @@ dependencies = [ "smallvec", "thiserror", "unicode-bom", - "winnow 0.5.36", + "winnow 0.6.5", ] [[package]] name = "gix-config-value" -version = "0.14.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e7bfb37a46ed0b8468db37a6d8a0a61d56bdbe4603ae492cb322e5f3958" +checksum = "fbd06203b1a9b33a78c88252a625031b094d9e1b647260070c25b09910c0a804" dependencies = [ "bitflags 2.5.0", "bstr", @@ -5400,9 +5389,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.24.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206ede3fe433abba3c8b0174179d5bbac65ae3f0d9187e2ea96c0494db6a139f" +checksum = "5c70146183bd3c7119329a3c7392d1aa0e0adbe48d727f4df31828fe6d8fdaa1" dependencies = [ "bstr", "gix-command", @@ -5417,9 +5406,9 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7f3dfb72bebe3449b5e642be64e3c6ccbe9821c8b8f19f487cf5bfbbf4067e" +checksum = "180b130a4a41870edfbd36ce4169c7090bca70e195da783dea088dd973daa59c" dependencies = [ "bstr", "itoa", @@ -5429,9 +5418,9 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.40.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdcb5e49c4b9729dd1c361040ae5c3cd7c497b2260b18c954f62db3a63e98cf" +checksum = "a5fbc24115b957346cd23fb0f47d830eb799c46c89cdcf2f5acc9bf2938c2d01" dependencies = [ "bstr", "gix-command", @@ -5447,11 +5436,31 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gix-dir" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fcd56ffa1133f35525af890226ad0d3b2e607b4490360c94b1869e278eba3" +dependencies = [ + "bstr", + "gix-discover", + "gix-fs", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-trace", + "gix-utils", + "gix-worktree", + "thiserror", +] + [[package]] name = "gix-discover" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4669218f3ec0cbbf8f16857b32200890f8ca585f36f5817242e4115fe4551af" +checksum = "64bab49087ed3710caf77e473dc0efc54ca33d8ccc6441359725f121211482b1" dependencies = [ "bstr", "dunce", @@ -5465,9 +5474,9 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.38.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "184f7f7d4e45db0e2a362aeaf12c06c5e84817d0ef91d08e8e90170dad9f0b07" +checksum = "db4254037d20a247a0367aa79333750146a369719f0c6617fec4f5752cc62b37" dependencies = [ "bytes", "bytesize", @@ -5489,9 +5498,9 @@ dependencies = [ [[package]] name = "gix-filter" -version = "0.9.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9240862840fb740d209422937195e129e4ed3da49af212383260134bea8f6c1a" +checksum = "5c0d1f01af62bfd2fb3dd291acc2b29d4ab3e96ad52a679174626508ce98ef12" dependencies = [ "bstr", "encoding_rs", @@ -5510,9 +5519,9 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4436e883d5769f9fb18677b8712b49228357815f9e4104174a6fc2d8461a437b" +checksum = "e2184c40e7910529677831c8b481acf788ffd92427ed21fad65b6aa637e631b8" dependencies = [ "gix-features", "gix-utils", @@ -5520,9 +5529,9 @@ dependencies = [ [[package]] name = "gix-glob" -version = "0.16.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4965a1d06d0ab84a29d4a67697a97352ab14ae1da821084e5afb1fd6d8191ca0" +checksum = "682bdc43cb3c00dbedfcc366de2a849b582efd8d886215dbad2ea662ec156bb5" dependencies = [ "bitflags 2.5.0", "bstr", @@ -5532,9 +5541,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0ed89cdc1dce26685c80271c4287077901de3c3dd90234d5fa47c22b2268653" +checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" dependencies = [ "faster-hex", "thiserror", @@ -5542,9 +5551,9 @@ dependencies = [ [[package]] name = "gix-hashtable" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe47d8c0887f82355e2e9e16b6cecaa4d5e5346a7a474ca78ff94de1db35a5b" +checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" dependencies = [ "gix-hash", "hashbrown 0.14.3", @@ -5553,9 +5562,9 @@ dependencies = [ [[package]] name = "gix-ignore" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7069aaca4a05784c4cb44e392f0eaf627c6e57e05d3100c0e2386a37a682f0" +checksum = "640dbeb4f5829f9fc14d31f654a34a0350e43a24e32d551ad130d99bf01f63f1" dependencies = [ "bstr", "gix-glob", @@ -5566,14 +5575,14 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.29.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7152181ba8f0a3addc5075dd612cea31fc3e252b29c8be8c45f4892bf87426" +checksum = "881ab3b1fa57f497601a5add8289e72a7ae09471fc0b9bbe483b628ae8e418a1" dependencies = [ "bitflags 2.5.0", "bstr", - "btoi", "filetime", + "fnv", "gix-bitmap", "gix-features", "gix-fs", @@ -5581,6 +5590,8 @@ dependencies = [ "gix-lock", "gix-object", "gix-traverse", + "gix-utils", + "hashbrown 0.14.3", "itoa", "libc", "memmap2 0.9.4", @@ -5602,9 +5613,9 @@ dependencies = [ [[package]] name = "gix-macros" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75e7ab728059f595f6ddc1ad8771b8d6a231971ae493d9d5948ecad366ee8bb" +checksum = "1dff438f14e67e7713ab9332f5fd18c8f20eb7eb249494f6c2bf170522224032" dependencies = [ "proc-macro2", "quote", @@ -5613,9 +5624,9 @@ dependencies = [ [[package]] name = "gix-mailmap" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8755c637aeeee7205fb605b4cfe5f8ff0ff48ad3d19b3ac98de7d0422538575" +checksum = "28a62c86c08a65f99002013d58dd3312b2987705547436bdb90c507dcd9a41b1" dependencies = [ "bstr", "gix-actor", @@ -5625,9 +5636,9 @@ dependencies = [ [[package]] name = "gix-negotiate" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a163adb84149e522e991cbe27250a6e01de56f98cd05b174614ce3f8a4e8b140" +checksum = "54ba98f8c8c06870dfc167d192ca38a38261867b836cb89ac80bc9176dba975e" dependencies = [ "bitflags 2.5.0", "gix-commitgraph", @@ -5641,28 +5652,28 @@ dependencies = [ [[package]] name = "gix-object" -version = "0.41.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693ce9d30741506cb082ef2d8b797415b48e032cce0ab23eff894c19a7e4777b" +checksum = "3d4f8efae72030df1c4a81d02dbe2348e748d9b9a11e108ed6efbd846326e051" dependencies = [ "bstr", - "btoi", "gix-actor", "gix-date", "gix-features", "gix-hash", + "gix-utils", "gix-validate", "itoa", "smallvec", "thiserror", - "winnow 0.5.36", + "winnow 0.6.5", ] [[package]] name = "gix-odb" -version = "0.57.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba2fa9e81f2461b78b4d81a807867667326c84cdab48e0aed7b73a593aa1be4" +checksum = "e8bbb43d2fefdc4701ffdf9224844d05b136ae1b9a73c2f90710c8dd27a93503" dependencies = [ "arc-swap", "gix-date", @@ -5680,9 +5691,9 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.47.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da5f3e78c96b76c4e6fe5e8e06b76221e4a0ee9a255aa935ed1fdf68988dfd8" +checksum = "b58bad27c7677fa6b587aab3a1aca0b6c97373bd371a0a4290677c838c9bcaf1" dependencies = [ "clru", "gix-chunk", @@ -5701,9 +5712,9 @@ dependencies = [ [[package]] name = "gix-packetline-blocking" -version = "0.17.3" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8ef6dd3ea50e26f3bf572e90c034d033c804d340cd1eb386392f184a9ba2f7" +checksum = "c31d42378a3d284732e4d589979930d0d253360eccf7ec7a80332e5ccb77e14a" dependencies = [ "bstr", "faster-hex", @@ -5713,9 +5724,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97e9ad649bf5e109562d6acba657ca428661ec08e77eaf3a755d8fa55485be9c" +checksum = "23623cf0f475691a6d943f898c4d0b89f5c1a2a64d0f92bce0e0322ee6528783" dependencies = [ "bstr", "gix-trace", @@ -5726,9 +5737,9 @@ dependencies = [ [[package]] name = "gix-pathspec" -version = "0.6.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cbd49750edb26b0a691e5246fc635fa554d344da825cd20fa9ee0da9c1b761f" +checksum = "ea9f934a111e0efdf93ae06e3648427e60e783099fbebd6a53a7a2ffb10a1e65" dependencies = [ "bitflags 2.5.0", "bstr", @@ -5741,9 +5752,9 @@ dependencies = [ [[package]] name = "gix-prompt" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd89d058258e53e0fd6c57f13ee16c5673a83066a68e11f88626fc8cfda5f6" +checksum = "f5325eb17ce7b5e5d25dec5c2315d642a09d55b9888b3bf46b7d72e1621a55d8" dependencies = [ "gix-command", "gix-config-value", @@ -5754,20 +5765,20 @@ dependencies = [ [[package]] name = "gix-quote" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7dc10303d73a960d10fb82f81188b036ac3e6b11b5795b20b1a60b51d1321f" +checksum = "cbff4f9b9ea3fa7a25a70ee62f545143abef624ac6aa5884344e70c8b0a1d9ff" dependencies = [ "bstr", - "btoi", + "gix-utils", "thiserror", ] [[package]] name = "gix-ref" -version = "0.41.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5818958994ad7879fa566f5441ebcc48f0926aa027b28948e6fbf6578894dc31" +checksum = "fd4aba68b925101cb45d6df328979af0681364579db889098a0de75b36c77b65" dependencies = [ "gix-actor", "gix-date", @@ -5782,14 +5793,14 @@ dependencies = [ "gix-validate", "memmap2 0.9.4", "thiserror", - "winnow 0.5.36", + "winnow 0.6.5", ] [[package]] name = "gix-refspec" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613aa4d93034c5791d13bdc635e530f4ddab1412ddfb4a8215f76213177b61c7" +checksum = "dde848865834a54fe4d9b4573f15d0e9a68eaf3d061b42d3ed52b4b8acf880b2" dependencies = [ "bstr", "gix-hash", @@ -5801,9 +5812,9 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "288f6549d7666db74dc3f169a9a333694fc28ecd2f5aa7b2c979c89eb556751a" +checksum = "9e34196e1969bd5d36e2fbc4467d893999132219d503e23474a8ad2b221cb1e8" dependencies = [ "bstr", "gix-date", @@ -5817,9 +5828,9 @@ dependencies = [ [[package]] name = "gix-revwalk" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9b4d91dfc5c14fee61a28c65113ded720403b65a0f46169c0460f731a5d03c" +checksum = "e0a7d393ae814eeaae41a333c0ff684b243121cc61ccdc5bbe9897094588047d" dependencies = [ "gix-commitgraph", "gix-date", @@ -5832,9 +5843,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d9bf462feaf05f2121cba7399dbc6c34d88a9cad58fc1e95027791d6a3c6d2" +checksum = "fddc27984a643b20dd03e97790555804f98cf07404e0e552c0ad8133266a79a1" dependencies = [ "bitflags 2.5.0", "gix-path", @@ -5844,12 +5855,14 @@ dependencies = [ [[package]] name = "gix-status" -version = "0.5.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324d6b9ad77d9e1b73611ce274f4fc1145bf29bc893e18c9e378981d0dc4ae80" +checksum = "50c413bfd2952e4ee92e48438dac3c696f3555e586a34d184a427f6bedd1e4f9" dependencies = [ "bstr", "filetime", + "gix-diff", + "gix-dir", "gix-features", "gix-filter", "gix-fs", @@ -5857,15 +5870,16 @@ dependencies = [ "gix-index", "gix-object", "gix-path", + "gix-pathspec", "gix-worktree", "thiserror", ] [[package]] name = "gix-submodule" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73182f6c1f5ed1ed94ba16581ac62593d5e29cd1c028b2af618f836283b8f8d4" +checksum = "4fb7ea05666362472fecd44c1fc35fe48a5b9b841b431cc4f85b95e6f20c23ec" dependencies = [ "bstr", "gix-config", @@ -5894,16 +5908,17 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b202d766a7fefc596e2cc6a89cda8ad8ad733aed82da635ac120691112a9b1" +checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" [[package]] name = "gix-traverse" -version = "0.37.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc30c5b5e4e838683b59e1b0574ce6bc1c35916df9709aaab32bb7751daf08b" +checksum = "f4029ec209b0cc480d209da3837a42c63801dd8548f09c1f4502c60accb62aeb" dependencies = [ + "bitflags 2.5.0", "gix-commitgraph", "gix-date", "gix-hash", @@ -5916,9 +5931,9 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.27.0" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f1981ecc700f4fd73ae62b9ca2da7c8816c8fd267f0185e3f8c21e967984ac" +checksum = "0db829ebdca6180fbe32be7aed393591df6db4a72dbbc0b8369162390954d1cf" dependencies = [ "bstr", "gix-features", @@ -5930,19 +5945,20 @@ dependencies = [ [[package]] name = "gix-utils" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e839f3d0798b296411263da6bee780a176ef8008a5dfc31287f7eda9266ab8" +checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" dependencies = [ + "bstr", "fastrand", "unicode-normalization", ] [[package]] name = "gix-validate" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac7cc36f496bd5d96cdca0f9289bb684480725d40db60f48194aa7723b883854" +checksum = "e39fc6e06044985eac19dd34d474909e517307582e462b2eb4c8fa51b6241545" dependencies = [ "bstr", "thiserror", @@ -5950,9 +5966,9 @@ dependencies = [ [[package]] name = "gix-worktree" -version = "0.30.0" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca36bb3dc54038c66507dc75c4d8edbee2d6d5cc45227b4eb508ad13dd60a006" +checksum = "f06ca5dd164678914fc9280ba9d1ffeb66499ccc16ab1278c513828beee88401" dependencies = [ "bstr", "gix-attributes", @@ -5968,9 +5984,9 @@ dependencies = [ [[package]] name = "gix-worktree-state" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae178614b70bdb0c7f6f21b8c9fb711ab78bd7e8e1866f565fcf28876747f1d" +checksum = "70b4bcac42d5b3197d38e3f15f6eb277c5e6d6a1669c7beabed8f666dba1c9b8" dependencies = [ "bstr", "gix-features", @@ -5988,9 +6004,9 @@ dependencies = [ [[package]] name = "gix-worktree-stream" -version = "0.9.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f5330e8c071ac47d27ad8e666a58bdf95ff47a8f7fc8ccc2d1dc95fd381d57d" +checksum = "6e73f7f8c1354516339a244f61f0fe3080b4cf25ddcbcfefd2d56661d51e88d4" dependencies = [ "gix-attributes", "gix-features", @@ -7405,8 +7421,10 @@ dependencies = [ "rayon", "reqwest 0.12.3", "reth-primitives", + "reth-revm", "reth-rpc-types", "reth-rpc-types-compat", + "revm-inspectors", "rstest", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 161240e5b..056f41333 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,7 @@ starknet-crypto = { version = "0.6.1", default-features = false } starknet_api = { version = "0.7.0-dev.0", default-features = false } # Ethereum dependencies +alloy-primitives = "0.6.4" alloy-rlp = { version = "0.3.4", default-features = false } ethers = { version = "2.0.9", default-features = false } ethers-solc = { version = "2.0.9", default-features = false } @@ -105,7 +106,8 @@ reth-primitives = { git = "https://github.com/paradigmxyz/reth.git", tag = "v0.2 ] } reth-rpc-types = { git = "https://github.com/paradigmxyz/reth.git", tag = "v0.2.0-beta.5", default-features = false } reth-rpc-types-compat = { git = "https://github.com/paradigmxyz/reth.git", tag = "v0.2.0-beta.5", default-features = false } -alloy-primitives = "0.6.4" +reth-revm = { git = "https://github.com/paradigmxyz/reth.git", tag = "v0.2.0-beta.5", default-features = false } +revm-inspectors = { git = "https://github.com/paradigmxyz/evm-inspectors", rev = "0ad0338" } # Serde serde = { version = "1.0.197", default-features = false, features = ["derive"] } diff --git a/src/eth_provider/error.rs b/src/eth_provider/error.rs index 5be06c459..597fdbfde 100644 --- a/src/eth_provider/error.rs +++ b/src/eth_provider/error.rs @@ -220,6 +220,9 @@ pub enum TransactionError { /// BlockTransactions::FullTransactions variant. #[error("expected full transactions")] ExpectedFullTransactions, + /// Thrown if the tracing fails + #[error("tracing error: {0}")] + Tracing(Box), } impl From for EthRpcErrorCode { @@ -228,6 +231,7 @@ impl From for EthRpcErrorCode { TransactionError::InvalidChainId => EthRpcErrorCode::InvalidInput, TransactionError::GasOverflow => EthRpcErrorCode::TransactionRejected, TransactionError::ExpectedFullTransactions => EthRpcErrorCode::InternalError, + TransactionError::Tracing(_) => EthRpcErrorCode::InternalError, } } } diff --git a/src/eth_rpc/api/mod.rs b/src/eth_rpc/api/mod.rs index 42b42ca23..24f3b45ab 100644 --- a/src/eth_rpc/api/mod.rs +++ b/src/eth_rpc/api/mod.rs @@ -2,4 +2,5 @@ pub mod alchemy_api; pub mod debug_api; pub mod eth_api; pub mod net_api; +pub mod trace_api; pub mod web3_api; diff --git a/src/eth_rpc/api/trace_api.rs b/src/eth_rpc/api/trace_api.rs new file mode 100644 index 000000000..41156accd --- /dev/null +++ b/src/eth_rpc/api/trace_api.rs @@ -0,0 +1,13 @@ +use jsonrpsee::core::RpcResult as Result; +use jsonrpsee::proc_macros::rpc; +use reth_rpc_types::trace::parity::LocalizedTransactionTrace; +use reth_rpc_types::BlockId; + +/// Trace API +#[rpc(server, namespace = "trace")] +#[async_trait] +pub trait TraceApi { + /// Returns the traces for the given block. + #[method(name = "block")] + async fn trace_block(&self, block_id: BlockId) -> Result>>; +} diff --git a/src/eth_rpc/rpc.rs b/src/eth_rpc/rpc.rs index d4f5fc160..0f56ed6f6 100644 --- a/src/eth_rpc/rpc.rs +++ b/src/eth_rpc/rpc.rs @@ -10,11 +10,13 @@ use crate::eth_rpc::api::alchemy_api::AlchemyApiServer; use crate::eth_rpc::api::debug_api::DebugApiServer; use crate::eth_rpc::api::eth_api::EthApiServer; use crate::eth_rpc::api::net_api::NetApiServer; +use crate::eth_rpc::api::trace_api::TraceApiServer; use crate::eth_rpc::api::web3_api::Web3ApiServer; use crate::eth_rpc::servers::alchemy_rpc::AlchemyRpc; use crate::eth_rpc::servers::debug_rpc::DebugRpc; use crate::eth_rpc::servers::eth_rpc::KakarotEthRpc; use crate::eth_rpc::servers::net_rpc::NetRpc; +use crate::eth_rpc::servers::trace_rpc::TraceRpc; use crate::eth_rpc::servers::web3_rpc::Web3Rpc; /// Represents RPC modules that are supported by reth @@ -25,6 +27,7 @@ pub enum KakarotRpcModule { Web3, Net, Debug, + Trace, } #[derive(Debug)] @@ -46,7 +49,8 @@ where let alchemy_rpc_module = AlchemyRpc::new(eth_provider.clone()).into_rpc(); let web3_rpc_module = Web3Rpc::default().into_rpc(); let net_rpc_module = NetRpc::new(eth_provider.clone()).into_rpc(); - let debug_rpc_module = DebugRpc::new(eth_provider).into_rpc(); + let debug_rpc_module = DebugRpc::new(eth_provider.clone()).into_rpc(); + let trace_rpc_module = TraceRpc::new(eth_provider).into_rpc(); let mut modules = HashMap::new(); @@ -55,6 +59,7 @@ where modules.insert(KakarotRpcModule::Web3, web3_rpc_module.into()); modules.insert(KakarotRpcModule::Net, net_rpc_module.into()); modules.insert(KakarotRpcModule::Debug, debug_rpc_module.into()); + modules.insert(KakarotRpcModule::Trace, trace_rpc_module.into()); Self { modules, _phantom: PhantomData } } diff --git a/src/eth_rpc/servers/debug_rpc.rs b/src/eth_rpc/servers/debug_rpc.rs index dff707fc8..72009d6c6 100644 --- a/src/eth_rpc/servers/debug_rpc.rs +++ b/src/eth_rpc/servers/debug_rpc.rs @@ -2,7 +2,7 @@ use crate::eth_provider::error::{EthApiError, EthereumDataFormatError, Signature use crate::eth_rpc::api::debug_api::DebugApiServer; use crate::models::block::rpc_to_primitive_block; use crate::models::block::rpc_to_primitive_header; -use crate::{eth_provider::provider::EthereumProvider, models::transaction::rpc_transaction_to_primitive}; +use crate::{eth_provider::provider::EthereumProvider, models::transaction::rpc_to_primitive_transaction}; use alloy_rlp::Encodable; use jsonrpsee::core::{async_trait, RpcResult as Result}; use reth_primitives::{Bytes, Log, Receipt, ReceiptWithBloom, TransactionSigned, B256}; @@ -61,7 +61,7 @@ impl DebugApiServer for DebugRpc

if let Some(tx) = transaction { let signature = tx.signature.ok_or_else(|| EthApiError::from(SignatureError::MissingSignature))?; - let tx = rpc_transaction_to_primitive(tx).map_err(EthApiError::from)?; + let tx = rpc_to_primitive_transaction(tx).map_err(EthApiError::from)?; let bytes = TransactionSigned::from_transaction_and_signature( tx, reth_primitives::Signature { @@ -84,7 +84,7 @@ impl DebugApiServer for DebugRpc

for t in transactions { let signature = t.signature.ok_or_else(|| EthApiError::from(SignatureError::MissingSignature))?; - let tx = rpc_transaction_to_primitive(t).map_err(EthApiError::from)?; + let tx = rpc_to_primitive_transaction(t).map_err(EthApiError::from)?; let bytes = TransactionSigned::from_transaction_and_signature( tx, reth_primitives::Signature { diff --git a/src/eth_rpc/servers/mod.rs b/src/eth_rpc/servers/mod.rs index a8f6fe0ec..c34ce7ca9 100644 --- a/src/eth_rpc/servers/mod.rs +++ b/src/eth_rpc/servers/mod.rs @@ -2,4 +2,5 @@ pub mod alchemy_rpc; pub mod debug_rpc; pub mod eth_rpc; pub mod net_rpc; +pub mod trace_rpc; pub mod web3_rpc; diff --git a/src/eth_rpc/servers/trace_rpc.rs b/src/eth_rpc/servers/trace_rpc.rs new file mode 100644 index 000000000..e9b15c20f --- /dev/null +++ b/src/eth_rpc/servers/trace_rpc.rs @@ -0,0 +1,32 @@ +use std::sync::Arc; + +use crate::eth_provider::provider::EthereumProvider; +use crate::eth_rpc::api::trace_api::TraceApiServer; +use crate::tracing::Tracer; +use jsonrpsee::core::{async_trait, RpcResult as Result}; +use reth_rpc_types::trace::parity::LocalizedTransactionTrace; +use reth_rpc_types::BlockId; + +/// The RPC module for implementing the Trace api +#[derive(Debug)] +pub struct TraceRpc { + eth_provider: P, +} + +impl TraceRpc

{ + pub const fn new(eth_provider: P) -> Self { + Self { eth_provider } + } +} + +#[async_trait] +impl TraceApiServer for TraceRpc

{ + /// Returns the traces for the given block. + async fn trace_block(&self, block_id: BlockId) -> Result>> { + let provider = Arc::new(&self.eth_provider); + let tracer = Tracer::new(provider).await?; + + let traces = tracer.trace_block(block_id).await?; + Ok(traces) + } +} diff --git a/src/lib.rs b/src/lib.rs index a4458af08..21e840077 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ pub mod models; pub mod prometheus_handler; #[cfg(feature = "testing")] pub mod test_utils; +pub mod tracing; diff --git a/src/models/block.rs b/src/models/block.rs index 882490a20..af20ed5fc 100644 --- a/src/models/block.rs +++ b/src/models/block.rs @@ -1,4 +1,4 @@ -use super::transaction::rpc_transaction_to_primitive; +use super::transaction::rpc_to_primitive_transaction; use crate::eth_provider::constant::STARKNET_MODULUS; use crate::{eth_provider::error::EthereumDataFormatError, into_via_try_wrapper}; use reth_primitives::{BlockId as EthereumBlockId, BlockNumberOrTag, TransactionSigned, Withdrawals, U256}; @@ -110,7 +110,7 @@ pub fn rpc_to_primitive_block(block: reth_rpc_types::Block) -> Result Result { match rpc_transaction @@ -84,135 +86,217 @@ pub fn rpc_transaction_to_primitive( } } +pub fn rpc_to_ec_recovered_transaction( + transaction: reth_rpc_types::Transaction, +) -> Result { + let signature = transaction.signature.ok_or(SignatureError::MissingSignature)?; + let transaction = rpc_to_primitive_transaction(transaction)?; + + let tx_signed = TransactionSigned::from_transaction_and_signature( + transaction, + Signature { + r: signature.r, + s: signature.s, + odd_y_parity: signature.y_parity.ok_or(SignatureError::RecoveryError)?.0, + }, + ); + + let tx_ec_recovered = tx_signed.try_into_ecrecovered().map_err(|_| SignatureError::RecoveryError)?; + Ok(tx_ec_recovered) +} + #[cfg(test)] mod tests { + use super::*; + use reth_primitives::{Address, Bytes, U256, U8}; + use reth_rpc_types::AccessListItem as RpcAccessListItem; use std::str::FromStr; - use reth_primitives::{Address, Bytes, B256, U256, U8}; - use reth_rpc_types::AccessListItem as RpcAccessListItem; + struct RpcTxBuilder { + tx: reth_rpc_types::Transaction, + } - use super::*; + impl RpcTxBuilder { + fn new() -> Self { + Self { + tx: reth_rpc_types::Transaction { + nonce: 1, + from: Address::from_str("0x0000000000000000000000000000000000000001").unwrap(), + to: Some(Address::from_str("0x0000000000000000000000000000000000000002").unwrap()), + value: U256::from(100), + gas_price: Some(U256::from(20)), + gas: U256::from(21000), + input: Bytes::from("1234"), + signature: Some(reth_rpc_types::Signature { + r: U256::from(1), + s: U256::from(2), + v: U256::from(37), + y_parity: Some(reth_rpc_types::Parity(true)), + }), + chain_id: Some(1), + transaction_type: Some(U8::from(0)), + ..Default::default() + }, + } + } - // Helper to create a common base for RPC transactions - fn base_rpc_transaction() -> reth_rpc_types::Transaction { - reth_rpc_types::Transaction { - hash: B256::default(), - nonce: 1, - block_hash: None, - block_number: None, - transaction_index: None, - from: Address::from_str("0x0000000000000000000000000000000000000001").unwrap(), - to: Some(Address::from_str("0x0000000000000000000000000000000000000002").unwrap()), - value: U256::from(100), - gas_price: Some(U256::from(20)), - gas: U256::from(21000), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - max_fee_per_blob_gas: None, - input: Bytes::from("1234"), - signature: None, - chain_id: Some(1), - blob_versioned_hashes: Some(vec![]), - access_list: None, - transaction_type: None, - other: serde_json::from_str("{}").unwrap(), + fn with_transaction_type(mut self, tx_type: TxType) -> Self { + self.tx.transaction_type = Some(U8::from(tx_type as u8)); + self } - } - #[test] - fn test_legacy_transaction_conversion() { - let mut rpc_tx = base_rpc_transaction(); - rpc_tx.transaction_type = Some(U8::from(0)); - - let result = rpc_transaction_to_primitive(rpc_tx); - assert!(matches!(result, Ok(reth_primitives::Transaction::Legacy(_)))); - if let Ok(reth_primitives::Transaction::Legacy(tx)) = result { - assert_eq!(tx.nonce, 1); - assert_eq!(tx.gas_price, 20); - assert_eq!(tx.gas_limit, 21000); - assert_eq!(tx.value, U256::from(100)); - assert_eq!(tx.input, Bytes::from("1234")); - assert_eq!(tx.chain_id, Some(1)); - assert_eq!( - tx.to, - TransactionKind::Call(Address::from_str("0x0000000000000000000000000000000000000002").unwrap()) - ); + fn with_access_list(mut self) -> Self { + self.tx.access_list = Some(reth_rpc_types::AccessList(vec![RpcAccessListItem { + address: Address::from_str("0x0000000000000000000000000000000000000003").unwrap(), + storage_keys: vec![U256::from(123).into(), U256::from(456).into()], + }])); + self } - } - #[test] - fn test_eip2930_transaction_conversion() { - let mut rpc_tx = base_rpc_transaction(); - rpc_tx.transaction_type = Some(U8::from(1)); - rpc_tx.access_list = Some(reth_rpc_types::AccessList(vec![RpcAccessListItem { - address: Address::from_str("0x0000000000000000000000000000000000000003").unwrap(), - storage_keys: vec![U256::from(123).into(), U256::from(456).into()], - }])); - - let result = rpc_transaction_to_primitive(rpc_tx); - assert!(matches!(result, Ok(reth_primitives::Transaction::Eip2930(_)))); - if let Ok(reth_primitives::Transaction::Eip2930(tx)) = result { - assert_eq!(tx.chain_id, 1); - assert_eq!(tx.nonce, 1); - assert_eq!(tx.gas_price, 20); - assert_eq!(tx.gas_limit, 21000); - assert_eq!(tx.value, U256::from(100)); - assert_eq!(tx.input, Bytes::from("1234")); - assert_eq!( - tx.to, - TransactionKind::Call(Address::from_str("0x0000000000000000000000000000000000000002").unwrap()) - ); - assert_eq!( - tx.access_list, - AccessList(vec![AccessListItem { - address: Address::from_str("0x0000000000000000000000000000000000000003").unwrap(), - storage_keys: vec![U256::from(123).into(), U256::from(456).into()] - }]) - ) + fn with_fee_market(mut self) -> Self { + self.tx.max_fee_per_gas = Some(U256::from(30)); + self.tx.max_priority_fee_per_gas = Some(U256::from(10)); + self } - } - #[test] - fn test_eip1559_transaction_conversion() { - let mut rpc_tx = base_rpc_transaction(); - rpc_tx.transaction_type = Some(U8::from(2)); - rpc_tx.max_fee_per_gas = Some(U256::from(30)); - rpc_tx.max_priority_fee_per_gas = Some(U256::from(10)); - rpc_tx.access_list = Some(reth_rpc_types::AccessList(vec![RpcAccessListItem { - address: Address::from_str("0x0000000000000000000000000000000000000003").unwrap(), - storage_keys: vec![U256::from(123).into(), U256::from(456).into()], - }])); - - let result = rpc_transaction_to_primitive(rpc_tx); - assert!(matches!(result, Ok(reth_primitives::Transaction::Eip1559(_)))); - if let Ok(reth_primitives::Transaction::Eip1559(tx)) = result { - assert_eq!(tx.chain_id, 1); - assert_eq!(tx.nonce, 1); - assert_eq!(tx.max_fee_per_gas, 30); - assert_eq!(tx.max_priority_fee_per_gas, 10); - assert_eq!(tx.gas_limit, 21000); - assert_eq!(tx.value, U256::from(100)); - assert_eq!(tx.input, Bytes::from("1234")); - assert_eq!( - tx.to, - TransactionKind::Call(Address::from_str("0x0000000000000000000000000000000000000002").unwrap()) - ); - assert_eq!( - tx.access_list, - AccessList(vec![AccessListItem { - address: Address::from_str("0x0000000000000000000000000000000000000003").unwrap(), - storage_keys: vec![U256::from(123).into(), U256::from(456).into()] - }]) - ) + fn build(self) -> reth_rpc_types::Transaction { + self.tx } } + // Helper to create a legacy transaction + fn legacy_rpc_transaction() -> reth_rpc_types::Transaction { + RpcTxBuilder::new().with_transaction_type(TxType::Legacy).build() + } + + // Helper to create an EIP-2930 transaction + fn eip2930_rpc_transaction() -> reth_rpc_types::Transaction { + RpcTxBuilder::new().with_transaction_type(TxType::Eip2930).with_access_list().build() + } + + // Helper to create an EIP-1559 transaction + fn eip1559_rpc_transaction() -> reth_rpc_types::Transaction { + RpcTxBuilder::new().with_transaction_type(TxType::Eip1559).with_access_list().with_fee_market().build() + } + + macro_rules! assert_common_fields { + ($tx: expr, $rpc_tx: expr, $gas_price_field: ident, $has_access_list: expr) => { + assert_eq!($tx.chain_id(), $rpc_tx.chain_id); + assert_eq!($tx.nonce(), $rpc_tx.nonce); + assert_eq!($tx.max_fee_per_gas(), $rpc_tx.$gas_price_field.unwrap().to()); + assert_eq!($tx.max_priority_fee_per_gas(), $rpc_tx.max_priority_fee_per_gas.map(|fee| fee.to())); + assert_eq!($tx.gas_limit(), $rpc_tx.gas.to::()); + assert_eq!($tx.value(), $rpc_tx.value); + assert_eq!($tx.input().clone(), $rpc_tx.input); + assert_eq!($tx.to().unwrap(), $rpc_tx.to.unwrap()); + if $has_access_list { + assert_eq!( + $tx.access_list().cloned().unwrap(), + AccessList( + $rpc_tx + .access_list + .unwrap() + .0 + .into_iter() + .map(|access_list| AccessListItem { + address: access_list.address, + storage_keys: access_list.storage_keys, + }) + .collect() + ) + ); + } + }; + } + + // Macro to create the tests for rpc to primitive transaction conversion + macro_rules! test_rpc_to_primitive_conversion { + ($test_name: ident, $tx_initializer: ident, $gas_price_field: ident, $has_access_list: expr) => { + #[test] + fn $test_name() { + // Given + let rpc_tx = $tx_initializer(); + + // When + let tx = rpc_to_primitive_transaction(rpc_tx.clone()) + .expect("Failed to convert RPC transaction to ec recovered"); + + // Then + assert_common_fields!(tx, rpc_tx, $gas_price_field, $has_access_list); + } + }; + } + + // Macro to create the tests for rpc to ec recovered transaction conversion + macro_rules! test_rpc_to_ec_recovered_conversion { + ($test_name: ident, $tx_initializer: ident, $gas_price_field: ident, $has_access_list: expr) => { + #[test] + fn $test_name() { + // Given + let rpc_tx = $tx_initializer(); + + // When + let tx = rpc_to_ec_recovered_transaction(rpc_tx.clone()) + .expect("Failed to convert RPC transaction to primitive"); + + // Then + assert_common_fields!(tx, rpc_tx, $gas_price_field, $has_access_list); + assert_eq!( + tx.signature, + rpc_tx + .signature + .map(|sig| Signature { r: sig.r, s: sig.s, odd_y_parity: sig.y_parity.unwrap().0 }) + .unwrap() + ); + } + }; + } + + test_rpc_to_primitive_conversion!( + test_legacy_transaction_conversion_to_primitive, + legacy_rpc_transaction, + gas_price, + false + ); + test_rpc_to_ec_recovered_conversion!( + test_legacy_transaction_conversion_to_ec_recovered, + legacy_rpc_transaction, + gas_price, + false + ); + + test_rpc_to_primitive_conversion!( + test_eip2930_transaction_conversion_to_primitive, + eip2930_rpc_transaction, + gas_price, + true + ); + test_rpc_to_ec_recovered_conversion!( + test_eip2930_transaction_conversion_to_ec_recovered, + eip2930_rpc_transaction, + gas_price, + true + ); + + test_rpc_to_primitive_conversion!( + test_eip1559_transaction_conversion_to_primitive, + eip1559_rpc_transaction, + max_fee_per_gas, + true + ); + test_rpc_to_ec_recovered_conversion!( + test_eip1559_transaction_conversion_to_ec_recovered, + eip1559_rpc_transaction, + max_fee_per_gas, + true + ); + #[test] #[should_panic(expected = "PrimitiveError")] fn test_invalid_transaction_type() { - let mut rpc_tx = base_rpc_transaction(); + let mut rpc_tx = RpcTxBuilder::new().build(); rpc_tx.transaction_type = Some(U8::from(99)); // Invalid type - let _ = rpc_transaction_to_primitive(rpc_tx).unwrap(); + let _ = rpc_to_primitive_transaction(rpc_tx).unwrap(); } } diff --git a/src/test_utils/eoa.rs b/src/test_utils/eoa.rs index fdfb90771..cfdbb94b4 100644 --- a/src/test_utils/eoa.rs +++ b/src/test_utils/eoa.rs @@ -13,8 +13,9 @@ use starknet_crypto::FieldElement; use crate::eth_provider::provider::{EthDataProvider, EthereumProvider}; use crate::eth_provider::starknet::kakarot_core::starknet_address; use crate::models::felt::Felt252Wrapper; -use crate::test_utils::evm_contract::EvmContract; -use crate::test_utils::evm_contract::KakarotEvmContract; +use crate::test_utils::evm_contract::{ + EvmContract, KakarotEvmContract, TransactionInfo, TxCommonInfo, TxFeeMarketInfo, +}; use crate::test_utils::tx_waiter::watch_tx; pub const TX_GAS_LIMIT: u64 = 5_000_000; @@ -80,6 +81,8 @@ impl KakarotEOA

{ self.eth_provider.starknet_provider() } + /// Deploys an EVM contract given a contract name and constructor arguments + /// Returns a KakarotEvmContract instance pub async fn deploy_evm_contract( &self, contract_name: Option<&str>, @@ -113,8 +116,7 @@ impl KakarotEOA

{ ::prepare_create_transaction( &bytecode, constructor_args, - nonce, - chain_id.try_into()?, + &TxCommonInfo { nonce, chain_id: chain_id.try_into()?, ..Default::default() }, )? }; let tx_signed = self.sign_transaction(tx)?; @@ -155,22 +157,25 @@ impl KakarotEOA

{ /// Calls a KakarotEvmContract function and returns the Starknet transaction hash /// The transaction is signed and sent by the EOA /// The transaction is waited for until it is confirmed - /// - /// allow(dead_code) is used because this function is used in tests, - /// and each test is compiled separately, so the compiler thinks this function is unused - #[allow(dead_code)] pub async fn call_evm_contract( &self, contract: &KakarotEvmContract, function: &str, args: T, value: u128, - ) -> Result { - let nonce: u64 = self.nonce().await?.try_into()?; - let chain_id = self.eth_provider.chain_id().await?.unwrap_or_default(); - - let tx = contract.prepare_call_transaction(function, args, nonce, value, chain_id.try_into()?)?; - let tx_signed = self.sign_transaction(tx)?; + ) -> Result { + let nonce = self.nonce().await?.try_into()?; + let chain_id = self.eth_provider.chain_id().await?.unwrap_or_default().to(); + + let tx = contract.prepare_call_transaction( + function, + args, + &TransactionInfo::FeeMarketInfo(TxFeeMarketInfo { + common: TxCommonInfo { chain_id, nonce, value }, + ..Default::default() + }), + )?; + let tx_signed = self.sign_transaction(tx.clone())?; let tx_hash = self.send_transaction(tx_signed).await?; let bytes = tx_hash.0; @@ -180,12 +185,12 @@ impl KakarotEOA

{ .await .expect("Tx polling failed"); - Ok(starknet_tx_hash) + Ok(tx) } /// Transfers value to the given address /// The transaction is signed and sent by the EOA - pub async fn transfer(&self, to: Address, value: u128) -> Result { + pub async fn transfer(&self, to: Address, value: u128) -> Result { let tx = Transaction::Eip1559(TxEip1559 { chain_id: self.eth_provider.chain_id().await?.unwrap_or_default().try_into()?, nonce: self.nonce().await?.try_into()?, @@ -195,8 +200,9 @@ impl KakarotEOA

{ ..Default::default() }); - let tx_signed = self.sign_transaction(tx)?; + let tx_signed = self.sign_transaction(tx.clone())?; - self.send_transaction(tx_signed).await + let _ = self.send_transaction(tx_signed).await; + Ok(tx) } } diff --git a/src/test_utils/evm_contract.rs b/src/test_utils/evm_contract.rs index a56df4052..801e3a735 100644 --- a/src/test_utils/evm_contract.rs +++ b/src/test_utils/evm_contract.rs @@ -4,7 +4,7 @@ use std::path::Path; use ethers::abi::Tokenize; use ethers_solc::artifacts::CompactContractBytecode; use foundry_config::{find_project_root_path, load_config}; -use reth_primitives::{Transaction, TransactionKind, TxEip1559, U256}; +use reth_primitives::{Transaction, TransactionKind, TxEip1559, TxLegacy, U256}; use starknet_crypto::FieldElement; use crate::models::felt::Felt252Wrapper; @@ -12,6 +12,48 @@ use crate::root_project_path; use super::eoa::TX_GAS_LIMIT; +#[derive(Clone, Debug)] +pub enum TransactionInfo { + FeeMarketInfo(TxFeeMarketInfo), + LegacyInfo(TxLegacyInfo), +} + +macro_rules! impl_common_info { + ($field: ident, $type: ty) => { + pub fn $field(&self) -> $type { + match self { + TransactionInfo::FeeMarketInfo(info) => info.common.$field, + TransactionInfo::LegacyInfo(info) => info.common.$field, + } + } + }; +} +impl TransactionInfo { + impl_common_info!(chain_id, u64); + impl_common_info!(nonce, u64); + impl_common_info!(value, u128); +} + +#[derive(Clone, Debug, Default)] +pub struct TxCommonInfo { + pub chain_id: u64, + pub nonce: u64, + pub value: u128, +} + +#[derive(Clone, Debug, Default)] +pub struct TxFeeMarketInfo { + pub common: TxCommonInfo, + pub max_fee_per_gas: u128, + pub max_priority_fee_per_gas: u128, +} + +#[derive(Clone, Debug, Default)] +pub struct TxLegacyInfo { + pub common: TxCommonInfo, + pub gas_price: u128, +} + pub trait EvmContract { fn load_contract_bytecode(contract_name: &str) -> Result { let dot_sol = format!("{contract_name}.sol"); @@ -28,8 +70,7 @@ pub trait EvmContract { fn prepare_create_transaction( contract_bytecode: &CompactContractBytecode, constructor_args: T, - nonce: u64, - chain_id: u64, + tx_info: &TxCommonInfo, ) -> Result { let abi = contract_bytecode.abi.as_ref().ok_or_else(|| eyre::eyre!("No ABI found"))?; let bytecode = contract_bytecode @@ -48,21 +89,20 @@ pub trait EvmContract { }; Ok(Transaction::Eip1559(TxEip1559 { - chain_id, - nonce, + chain_id: tx_info.chain_id, + nonce: tx_info.nonce, gas_limit: TX_GAS_LIMIT, input: deploy_data.into(), ..Default::default() })) } + #[allow(clippy::too_many_arguments)] fn prepare_call_transaction( &self, selector: &str, constructor_args: T, - nonce: u64, - value: u128, - chain_id: u64, + tx_info: &TransactionInfo, ) -> Result; } @@ -88,9 +128,7 @@ impl EvmContract for KakarotEvmContract { &self, selector: &str, args: T, - nonce: u64, - value: u128, - chain_id: u64, + tx_info: &TransactionInfo, ) -> Result { let abi = self.bytecode.abi.as_ref().ok_or_else(|| eyre::eyre!("No ABI found"))?; let params = args.into_tokens(); @@ -98,14 +136,29 @@ impl EvmContract for KakarotEvmContract { let data = abi.function(selector).and_then(|function| function.encode_input(¶ms))?; let evm_address: Felt252Wrapper = self.evm_address.into(); - Ok(Transaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit: TX_GAS_LIMIT, - to: TransactionKind::Call(evm_address.try_into()?), - value: U256::from(value), - input: data.into(), - ..Default::default() - })) + + let tx = match tx_info { + TransactionInfo::FeeMarketInfo(fee_market) => Transaction::Eip1559(TxEip1559 { + chain_id: tx_info.chain_id(), + nonce: tx_info.nonce(), + gas_limit: TX_GAS_LIMIT, + to: TransactionKind::Call(evm_address.try_into()?), + value: U256::from(tx_info.value()), + input: data.into(), + max_fee_per_gas: fee_market.max_fee_per_gas, + max_priority_fee_per_gas: fee_market.max_priority_fee_per_gas, + ..Default::default() + }), + TransactionInfo::LegacyInfo(legacy) => Transaction::Legacy(TxLegacy { + chain_id: Some(tx_info.chain_id()), + nonce: tx_info.nonce(), + gas_limit: TX_GAS_LIMIT, + to: TransactionKind::Call(evm_address.try_into()?), + value: U256::from(tx_info.value()), + input: data.into(), + gas_price: legacy.gas_price, + }), + }; + Ok(tx) } } diff --git a/src/test_utils/fixtures.rs b/src/test_utils/fixtures.rs index 984843232..4bf925bd1 100644 --- a/src/test_utils/fixtures.rs +++ b/src/test_utils/fixtures.rs @@ -43,6 +43,28 @@ pub async fn erc20(#[future] katana: Katana) -> (Katana, KakarotEvmContract) { (katana, contract) } +#[cfg(any(test, feature = "arbitrary", feature = "testing"))] +#[fixture] +#[awt] +pub async fn plain_opcodes(#[future] counter: (Katana, KakarotEvmContract)) -> (Katana, KakarotEvmContract) { + use ethers::abi::Address; + + let katana = counter.0; + let counter = counter.1; + let eoa = katana.eoa(); + let counter_address = Address::from_slice(&counter.evm_address.to_bytes_be()[12..]); + let contract = eoa + .deploy_evm_contract( + Some("PlainOpcodes"), + ( + Token::Address(counter_address), // counter address + ), + ) + .await + .expect("Failed to deploy ERC20 contract"); + (katana, contract) +} + /// This fixture creates a new test environment on Katana. #[cfg(any(test, feature = "arbitrary", feature = "testing"))] #[fixture] diff --git a/src/test_utils/katana/mod.rs b/src/test_utils/katana/mod.rs index c6ed04119..e933a76de 100644 --- a/src/test_utils/katana/mod.rs +++ b/src/test_utils/katana/mod.rs @@ -1,5 +1,6 @@ pub mod genesis; +use std::collections::HashMap; use std::path::Path; use std::sync::Arc; @@ -8,12 +9,18 @@ use katana_primitives::block::GasPrices; use katana_primitives::chain::ChainId; use katana_primitives::genesis::json::GenesisJson; use katana_primitives::genesis::Genesis; +use mongodb::bson::doc; +use mongodb::options::{UpdateModifications, UpdateOptions}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; -use crate::eth_provider::provider::EthDataProvider; +use crate::eth_provider::database::types::{header::StoredHeader, transaction::StoredTransaction}; +use crate::eth_provider::utils::{format_hex, into_filter}; +use crate::eth_provider::{ + constant::{HASH_PADDING, U64_PADDING}, + provider::EthDataProvider, +}; use crate::test_utils::eoa::KakarotEOA; -use std::collections::HashMap; #[cfg(any(test, feature = "arbitrary", feature = "testing"))] use { @@ -141,13 +148,59 @@ impl<'a> Katana { self.eoa.clone() } - /// allow(dead_code) is used because this function is used in tests, - /// and each test is compiled separately, so the compiler thinks this function is unused #[allow(dead_code)] pub const fn sequencer(&self) -> &TestSequencer { &self.sequencer } + /// Adds transactions to the database along with a corresponding header. + pub async fn add_transactions_with_header_to_database(&self, txs: Vec, header: Header) { + let provider = self.eth_provider(); + let database = provider.database(); + let Header { number, .. } = header; + let block_number = number.expect("Failed to get block number").to::(); + + // Add the transactions to the database. + let tx_collection = database.collection::(); + for tx in txs { + let filter = into_filter("tx.hash", &tx.hash, HASH_PADDING); + database + .update_one::(tx.into(), filter, true) + .await + .expect("Failed to update transaction in database"); + } + + // We use the unpadded block number to filter the transactions in the database and + // the padded block number to update the block number in the database. + let unpadded_block_number = format_hex(block_number, 0); + let padded_block_number = format_hex(block_number, U64_PADDING); + + // The transactions get added in the database with the unpadded block number (due to U256 serialization using `human_readable`). + // We need to update the block number to the padded version. + tx_collection + .update_many( + doc! {"tx.blockNumber": &unpadded_block_number}, + UpdateModifications::Document(doc! {"$set": {"tx.blockNumber": &padded_block_number}}), + UpdateOptions::builder().upsert(true).build(), + ) + .await + .expect("Failed to update block number"); + + // Same issue as the transactions, we need to update the block number to the padded version once added + // to the database. + let header_collection = database.collection::(); + let filter = into_filter("header.number", &block_number, U64_PADDING); + database.update_one(StoredHeader { header }, filter, true).await.expect("Failed to update header in database"); + header_collection + .update_one( + doc! {"header.number": unpadded_block_number}, + UpdateModifications::Document(doc! {"$set": {"header.number": padded_block_number}}), + UpdateOptions::builder().upsert(true).build(), + ) + .await + .expect("Failed to update block number"); + } + /// Retrieves the first stored transaction pub fn first_transaction(&self) -> Option { self.mock_data diff --git a/src/tracing/database.rs b/src/tracing/database.rs new file mode 100644 index 000000000..e3a3a1888 --- /dev/null +++ b/src/tracing/database.rs @@ -0,0 +1,111 @@ +use std::collections::HashMap; + +use crate::eth_provider::{ + error::{EthApiError, EthereumDataFormatError}, + provider::EthereumProvider, +}; +use reth_primitives::{Address, B256, U256}; +use reth_revm::{ + db::{AccountState, CacheDB, DbAccount}, + primitives::{Account, AccountInfo, Bytecode}, + Database, DatabaseCommit, +}; +use reth_rpc_types::{serde_helpers::JsonStorageKey, BlockId, BlockNumberOrTag}; +use tokio::runtime::Handle; + +pub(crate) struct EthDatabaseSnapshot { + cache: CacheDB

, + block_id: BlockId, +} + +impl EthDatabaseSnapshot

{ + pub(crate) fn new(provider: P, block_id: BlockId) -> Self { + Self { cache: CacheDB::new(provider), block_id } + } +} + +impl Database for EthDatabaseSnapshot

{ + type Error = EthApiError; + + fn basic(&mut self, address: Address) -> Result, Self::Error> { + let cache = &self.cache; + if let Some(account) = cache.accounts.get(&address) { + return Ok(Some(account.info.clone())); + } + + let account_info = Handle::current().block_on(async { + let bytecode = cache.db.get_code(address, Some(self.block_id)).await?; + let bytecode = Bytecode::new_raw(bytecode); + let code_hash = bytecode.hash_slow(); + + let nonce = cache.db.transaction_count(address, Some(self.block_id)).await?.to(); + let balance = cache.db.balance(address, Some(self.block_id)).await?; + + Result::<_, EthApiError>::Ok(AccountInfo { nonce, balance, code: Some(bytecode), code_hash }) + })?; + + self.cache.insert_account_info(address, account_info.clone()); + Ok(Some(account_info)) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + Ok(self.cache.contracts.get(&code_hash).cloned().unwrap_or_default()) + } + + fn storage(&mut self, address: Address, index: U256) -> Result { + let cache = &self.cache; + if let Some(account) = cache.accounts.get(&address) { + if let Some(storage) = account.storage.get(&index) { + return Ok(*storage); + } + } + + let storage = Handle::current().block_on(async { + let value = cache + .db + .storage_at(address, JsonStorageKey(B256::from_slice(&index.to_be_bytes::<32>())), Some(self.block_id)) + .await?; + Result::<_, EthApiError>::Ok(value) + })?; + let storage = U256::from_be_bytes(storage.0); + + self.cache.accounts.entry(address).or_default().storage.insert(index, storage); + Ok(storage) + } + + fn block_hash(&mut self, number: U256) -> Result { + let cache = &self.cache; + if let Some(hash) = cache.block_hashes.get(&number) { + return Ok(*hash); + } + + let block_number = number.try_into().map_err(|_| EthereumDataFormatError::PrimitiveError)?; + let hash = Handle::current().block_on(async { + let hash = cache + .db + .block_by_number(BlockNumberOrTag::Number(block_number), false) + .await? + .ok_or(EthApiError::UnknownBlock)? + .header + .hash + .unwrap_or_default(); + Result::<_, EthApiError>::Ok(hash) + })?; + + self.cache.block_hashes.insert(number, hash); + Ok(hash) + } +} + +impl DatabaseCommit for EthDatabaseSnapshot

{ + fn commit(&mut self, changes: HashMap) { + changes.into_iter().for_each(|(address, account)| { + let db_account = DbAccount { + info: account.info.clone(), + storage: account.storage.into_iter().map(|(k, v)| (k, v.present_value)).collect(), + account_state: AccountState::None, + }; + self.cache.accounts.insert(address, db_account); + }); + } +} diff --git a/src/tracing/mod.rs b/src/tracing/mod.rs new file mode 100644 index 000000000..743c5290e --- /dev/null +++ b/src/tracing/mod.rs @@ -0,0 +1,137 @@ +mod database; + +use crate::{ + eth_provider::{ + error::{EthApiError, EthereumDataFormatError, TransactionError}, + provider::EthereumProvider, + }, + models::transaction::rpc_to_ec_recovered_transaction, +}; +use reth_primitives::{ + revm::env::tx_env_with_recovered, + revm_primitives::{BlockEnv, CfgEnv, Env, EnvWithHandlerCfg, SpecId}, + TxType, B256, +}; +use reth_revm::tracing::{TracingInspector, TracingInspectorConfig}; +use reth_revm::{primitives::HandlerCfg, DatabaseCommit}; +use reth_rpc_types::{trace::parity::LocalizedTransactionTrace, BlockId, BlockTransactions, Header, TransactionInfo}; + +pub type TracerResult = Result; + +#[derive(Debug)] +pub struct Tracer { + eth_provider: P, + env: Env, + inspector: TracingInspector, +} + +impl Tracer

{ + pub async fn new(eth_provider: P) -> TracerResult { + let mut cfg = CfgEnv::default(); + cfg.chain_id = eth_provider.chain_id().await?.unwrap_or_default().to(); + + let env = Env { cfg, ..Default::default() }; + + Ok(Self { eth_provider, env, inspector: TracingInspector::new(TracingInspectorConfig::default_parity()) }) + } + + pub async fn trace_block(&self, block_id: BlockId) -> TracerResult>> { + let maybe_block = match block_id { + BlockId::Hash(hash) => self.eth_provider.block_by_hash(hash.block_hash, true).await?, + BlockId::Number(number) => self.eth_provider.block_by_number(number, true).await?, + }; + + if maybe_block.is_none() { + return Ok(None); + } + + let block = maybe_block.unwrap(); + if block.header.hash.unwrap_or_default().is_zero() { + return Err(EthApiError::UnknownBlock); + } + + // DB should use the state of the parent block + let parent_block_hash = block.header.parent_hash; + let db = database::EthDatabaseSnapshot::new(self.eth_provider.clone(), BlockId::Hash(parent_block_hash.into())); + + let env = Box::new(self.setup_env(block.header.clone()).await); + let ctx = reth_revm::Context::new(reth_revm::EvmContext::new_with_env(db, env.clone()), self.inspector.clone()); + + let env = EnvWithHandlerCfg::new(env, HandlerCfg::new(SpecId::CANCUN)); + let mut handler = reth_revm::Handler::new(env.handler_cfg); + handler.append_handler_register_plain(reth_revm::inspector_handle_register); + let mut evm = reth_revm::Evm::new(ctx, handler); + + let traces = tokio::task::block_in_place(move || { + let transactions = match &block.transactions { + BlockTransactions::Full(transactions) => transactions, + _ => return Err(TransactionError::ExpectedFullTransactions.into()), + }; + let mut transactions = transactions.iter().peekable(); + let mut traces = Vec::with_capacity(block.transactions.len()); + + while let Some(tx) = transactions.next() { + // Convert the transaction to an ec recovered transaction and update the evm env with it + let tx_ec_recovered = rpc_to_ec_recovered_transaction(tx.clone())?; + let tx_env = tx_env_with_recovered(&tx_ec_recovered); + evm = evm.modify().modify_tx_env(|env| *env = tx_env).build(); + + // Transact the transaction + let result = evm.transact().map_err(|err| TransactionError::Tracing(err.into()))?; + + // Convert the traces to a parity trace and accumulate them + let parity_builder = evm.context.external.clone().into_parity_builder(); + + // Extract the base fee from the transaction based on the transaction type + let base_fee = match tx.transaction_type.and_then(|typ| typ.to::().try_into().ok()) { + Some(TxType::Legacy) | Some(TxType::Eip2930) => tx.gas_price, + Some(TxType::Eip1559) => tx + .max_fee_per_gas + .map(|fee| fee.saturating_sub(tx.max_priority_fee_per_gas.unwrap_or_default())), + _ => return Err(EthereumDataFormatError::TransactionConversionError.into()), + } + .map(|fee| fee.try_into()) + .transpose() + .map_err(|_| EthereumDataFormatError::PrimitiveError)?; + + let transaction_info = TransactionInfo { + hash: Some(tx.hash), + index: tx.transaction_index.map(|i| i.to()), + block_hash: tx.block_hash, + block_number: tx.block_number.map(|bn| bn.to()), + base_fee, + }; + traces.extend(parity_builder.into_localized_transaction_traces(transaction_info)); + + // Only commit to the database if there are more transactions to process. + if transactions.peek().is_some() { + evm.context.evm.inner.db.commit(result.state); + } + + // Update the evm env with a new inspector + evm = evm.modify().modify_external_context(|insp| *insp = self.inspector.clone()).build(); + } + + Result::<_, EthApiError>::Ok(traces) + })?; + + Ok(Some(traces)) + } + + async fn setup_env(&self, block_header: Header) -> Env { + let mut env = self.env.clone(); + + let Header { number, timestamp, gas_limit, miner, base_fee_per_gas, difficulty, .. } = block_header; + let block_env = BlockEnv { + number: number.unwrap_or_default(), + timestamp, + gas_limit, + coinbase: miner, + basefee: base_fee_per_gas.unwrap_or_default(), + prevrandao: Some(B256::from_slice(&difficulty.to_be_bytes::<32>()[..])), + ..Default::default() + }; + env.block = block_env; + env + } +} diff --git a/tests/debug_api.rs b/tests/debug_api.rs index 7b2be3d86..176b4d4af 100644 --- a/tests/debug_api.rs +++ b/tests/debug_api.rs @@ -2,7 +2,7 @@ use alloy_rlp::{Decodable, Encodable}; use kakarot_rpc::eth_provider::provider::EthereumProvider; use kakarot_rpc::models::block::rpc_to_primitive_block; -use kakarot_rpc::models::transaction::rpc_transaction_to_primitive; +use kakarot_rpc::models::transaction::rpc_to_primitive_transaction; use kakarot_rpc::test_utils::fixtures::{katana, setup}; use kakarot_rpc::test_utils::katana::Katana; use kakarot_rpc::test_utils::mongo::{BLOCK_HASH, BLOCK_NUMBER, EIP1599_TX_HASH, EIP2930_TX_HASH, LEGACY_TX_HASH}; @@ -284,7 +284,7 @@ async fn test_raw_transactions(#[future] katana: Katana, _setup: ()) { // Convert the transaction to a primitives transactions and encode it. let rlp_bytes = TransactionSigned::from_transaction_and_signature( - rpc_transaction_to_primitive(tx).unwrap(), + rpc_to_primitive_transaction(tx).unwrap(), reth_primitives::Signature { r: signature.r, s: signature.s, diff --git a/tests/trace_api.rs b/tests/trace_api.rs new file mode 100644 index 000000000..47eeae522 --- /dev/null +++ b/tests/trace_api.rs @@ -0,0 +1,155 @@ +#![cfg(feature = "testing")] +use kakarot_rpc::eth_provider::provider::EthereumProvider; +use kakarot_rpc::test_utils::eoa::Eoa; +use kakarot_rpc::test_utils::evm_contract::{ + EvmContract, KakarotEvmContract, TransactionInfo, TxCommonInfo, TxFeeMarketInfo, +}; +use kakarot_rpc::test_utils::fixtures::{plain_opcodes, setup}; +use kakarot_rpc::test_utils::katana::Katana; +use kakarot_rpc::test_utils::rpc::start_kakarot_rpc_server; +use reth_primitives::{B256, U256, U8}; +use reth_rpc_types::trace::parity::LocalizedTransactionTrace; +use reth_rpc_types::Signature; +use rstest::*; +use serde_json::{json, Value}; +use starknet::core::types::MaybePendingBlockWithTxHashes; +use starknet::providers::Provider; + +const TRACING_BLOCK_NUMBER: u64 = 0x3; +const TRANSACTIONS_COUNT: usize = 5; + +fn header(block_number: u64, hash: B256, parent_hash: B256, base_fee: u128) -> reth_rpc_types::Header { + reth_rpc_types::Header { + number: Some(U256::from(block_number)), + hash: Some(hash), + parent_hash, + nonce: Default::default(), + logs_bloom: Default::default(), + transactions_root: Default::default(), + state_root: Default::default(), + receipts_root: Default::default(), + difficulty: Default::default(), + total_difficulty: Default::default(), + extra_data: Default::default(), + gas_limit: U256::MAX, + gas_used: Default::default(), + timestamp: Default::default(), + uncles_hash: Default::default(), + miner: Default::default(), + mix_hash: Default::default(), + base_fee_per_gas: Some(U256::from(base_fee)), + withdrawals_root: Default::default(), + excess_blob_gas: Default::default(), + parent_beacon_block_root: Default::default(), + blob_gas_used: Default::default(), + } +} + +#[rstest] +#[awt] +#[tokio::test(flavor = "multi_thread")] +async fn test_trace_block(#[future] plain_opcodes: (Katana, KakarotEvmContract), _setup: ()) { + // Setup the Kakarot RPC server. + let katana = plain_opcodes.0; + let plain_opcodes = plain_opcodes.1; + let (server_addr, server_handle) = + start_kakarot_rpc_server(&katana).await.expect("Error setting up Kakarot RPC server"); + + // Get the EOA address, nonce, and chain id. + let eoa = katana.eoa(); + let eoa_address = eoa.evm_address().expect("Failed to get eoa address"); + let nonce: u64 = eoa.nonce().await.expect("Failed to get nonce").to(); + let chain_id = eoa.eth_provider().chain_id().await.expect("Failed to get chain id").unwrap_or_default().to(); + + // Push 10 RPC transactions into the database. + let mut txs = Vec::with_capacity(TRANSACTIONS_COUNT); + let max_fee_per_gas = 10; + let max_priority_fee_per_gas = 1; + for i in 0..TRANSACTIONS_COUNT { + // We want to trace the "createCounterAndInvoke" which does a CREATE followed by a CALL. + let tx = plain_opcodes + .prepare_call_transaction( + "createCounterAndInvoke", + (), + &TransactionInfo::FeeMarketInfo(TxFeeMarketInfo { + common: TxCommonInfo { nonce: nonce + i as u64, value: 0, chain_id }, + max_fee_per_gas, + max_priority_fee_per_gas, + }), + ) + .expect("Failed to prepare call transaction"); + // Sign the transaction and convert it to a RPC transaction. + let tx_signed = eoa.sign_transaction(tx.clone()).expect("Failed to sign transaction"); + let tx = reth_rpc_types::Transaction { + transaction_type: Some(U8::from(2)), + nonce: tx.nonce(), + hash: tx_signed.hash(), + to: tx.to(), + from: eoa_address, + block_number: Some(U256::from(TRACING_BLOCK_NUMBER)), + chain_id: tx.chain_id(), + gas: U256::from(tx.gas_limit()), + input: tx.input().clone(), + signature: Some(Signature { + r: tx_signed.signature().r, + s: tx_signed.signature().s, + v: U256::from(tx_signed.signature().v(Some(chain_id))), + y_parity: Some(reth_rpc_types::Parity(tx_signed.signature().odd_y_parity)), + }), + max_fee_per_gas: Some(U256::from(max_fee_per_gas)), + gas_price: Some(U256::from(max_fee_per_gas)), + max_priority_fee_per_gas: Some(U256::from(max_priority_fee_per_gas)), + value: tx.value(), + ..Default::default() + }; + txs.push(tx); + } + + // Add a block header pointing to a parent hash header in the database for these transactions. + // This is required since tracing will start on the previous block. + let maybe_parent_block = katana + .eth_provider() + .starknet_provider() + .get_block_with_tx_hashes(starknet::core::types::BlockId::Number(TRACING_BLOCK_NUMBER - 1)) + .await + .expect("Failed to get block"); + let parent_block_hash = match maybe_parent_block { + MaybePendingBlockWithTxHashes::PendingBlock(_) => panic!("Pending block found"), + MaybePendingBlockWithTxHashes::Block(block) => block.block_hash, + }; + let parent_block_hash = B256::from_slice(&parent_block_hash.to_bytes_be()[..]); + + let parent_header = header(TRACING_BLOCK_NUMBER - 1, parent_block_hash, B256::random(), max_fee_per_gas); + let header = header(TRACING_BLOCK_NUMBER, B256::random(), parent_block_hash, max_fee_per_gas); + katana.add_transactions_with_header_to_database(vec![], parent_header).await; + katana.add_transactions_with_header_to_database(txs, header).await; + + // Send the trace_block RPC request. + let reqwest_client = reqwest::Client::new(); + let res = reqwest_client + .post(format!("http://localhost:{}", server_addr.port())) + .header("Content-Type", "application/json") + .body( + json!( + { + "jsonrpc":"2.0", + "method":"trace_block", + "params":[format!("0x{:016x}", TRACING_BLOCK_NUMBER)], + "id":1, + } + ) + .to_string(), + ) + .send() + .await + .expect("Failed to call Debug RPC"); + let response = res.text().await.expect("Failed to get response body"); + let raw: Value = serde_json::from_str(&response).expect("Failed to deserialize response body"); + let traces: Option> = + serde_json::from_value(raw["result"].clone()).expect("Failed to deserialize result"); + + assert!(traces.is_some()); + // We expect 3 traces per transaction: CALL, CREATE, and CALL. + assert!(traces.unwrap().len() == TRANSACTIONS_COUNT * 3); + drop(server_handle); +}