diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 538605e2..7b225cb1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ Thanks for your interest in contributing to Morph Reth! This document provides g ### Prerequisites -- Rust 1.88 or later +- Rust 1.95 or later - Cargo ### Build diff --git a/Cargo.lock b/Cargo.lock index 322d0bd2..c3afd5e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -88,27 +88,27 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.31" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" dependencies = [ "alloy-primitives", "alloy-rlp", "num_enum", "serde", - "strum 0.27.2", + "strum", ] [[package]] name = "alloy-consensus" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4ff99651d46cef43767b5e8262ea228cd05287409ccb0c947cc25e70a952f9" +checksum = "e3d64da86c616b5092ea64eea648f311bbd58630a0b384c42d699175d6f9122b" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-trie", "alloy-tx-macros", "arbitrary", @@ -119,7 +119,7 @@ dependencies = [ "either", "k256", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "serde", "serde_json", @@ -129,15 +129,15 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0701b0eda8051a2398591113e7862f807ccdd3315d0b441f06c2a0865a379b" +checksum = "8fd98696ca3617d3a9ba1a6f2011880cbfd5618228dab6400c9f8bca457859a8" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "arbitrary", "serde", ] @@ -156,7 +156,7 @@ dependencies = [ "itoa", "serde", "serde_json", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -169,7 +169,7 @@ dependencies = [ "alloy-rlp", "arbitrary", "crc", - "rand 0.8.5", + "rand 0.8.6", "serde", "thiserror 2.0.18", ] @@ -184,7 +184,7 @@ dependencies = [ "alloy-rlp", "arbitrary", "borsh", - "rand 0.8.5", + "rand 0.8.6", "serde", ] @@ -199,7 +199,7 @@ dependencies = [ "arbitrary", "borsh", "k256", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_with", "thiserror 2.0.18", @@ -207,41 +207,55 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.1.0" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926b2c0d34e641cf8b17bf54ce50fda16715b9f68ad878fa6128bae410c6f890" +checksum = "72bc95fd73591644f0022065bef3c027d1a5ed875a53cb19a898d71a00818c1d" dependencies = [ "alloy-primitives", "alloy-rlp", + "arbitrary", + "borsh", + "once_cell", "serde", + "thiserror 2.0.18", ] [[package]] -name = "alloy-eip7928" -version = "0.3.3" +name = "alloy-eips" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "arbitrary", + "alloy-serde 1.8.3", + "auto_impl", "borsh", + "c-kzg", + "derive_more", + "either", "serde", + "serde_with", + "sha2", ] [[package]] name = "alloy-eips" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def1626eea28d48c6cc0a6f16f34d4af0001906e4f889df6c660b39c86fd044d" +checksum = "64c0456f5f7a4497e9342d20f528e30f5288ddfa0d6a012bd5044afee46cd8a0" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", - "alloy-eip7928 0.3.3", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "arbitrary", "auto_impl", "borsh", @@ -253,17 +267,16 @@ dependencies = [ "serde", "serde_with", "sha2", - "thiserror 2.0.18", ] [[package]] name = "alloy-evm" -version = "0.25.3" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc719f3501f4b6761c0da9e1c657adc7ac37739dea51a373d5849efbe4f55e52" +checksum = "c1ceeea6dcbbcd4e546b27700763a6f6c3b3fee30054209884f521078b6fda4f" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-hardforks", "alloy-primitives", "alloy-rpc-types-engine", @@ -271,21 +284,20 @@ dependencies = [ "alloy-sol-types", "auto_impl", "derive_more", - "op-alloy", - "op-revm", "revm", "thiserror 2.0.18", + "tracing", ] [[package]] name = "alloy-genesis" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d9d1aba3f914f0e8db9e4616ae37f3d811426d95bdccf44e47d0605ab202f6" +checksum = "a71ff8b55d2b8aa05259f474cae7dea0e4991724dc18936b81cb23ec492a0c2a" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-trie", "borsh", "serde", @@ -320,9 +332,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57586581f2008933241d16c3e3f633168b3a5d2738c5c42ea5246ec5e0ef17a" +checksum = "19e352478b756bad5d7203148e4b461861282ea2ded3da406ba24868b52cd098" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -335,19 +347,19 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b36c2a0ed74e48851f78415ca5b465211bd678891ba11e88fee09eac534bab1" +checksum = "ed08ae169869e08370ed121612e0d3dadac33d1a256e9f2465926b23f0bd7d95" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", "alloy-rpc-types-any", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-signer", "alloy-sol-types", "async-trait", @@ -361,14 +373,14 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "636c8051da58802e757b76c3b65af610b95799f72423dc955737dec73de234fd" +checksum = "02e6c7ad28afe348a9a9c5624b67ee5b3607b8de98d5816b3056ecdfa6fa2697" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-serde", + "alloy-serde 2.0.4", "serde", ] @@ -387,14 +399,14 @@ dependencies = [ "foldhash 0.2.0", "getrandom 0.4.2", "hashbrown 0.16.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", "proptest-derive", - "rand 0.9.2", + "rand 0.9.4", "rapidhash", "ruint", "rustc-hash", @@ -404,13 +416,13 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3dd56e2eafe8b1803e325867ac2c8a4c73c9fb5f341ffd8347f9344458c5922" +checksum = "93a7c17472b55482d4734154c2f5ed13f72e03f6752cebb927f6a2d8b52e646c" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-network", "alloy-network-primitives", @@ -429,14 +441,14 @@ dependencies = [ "async-stream", "async-trait", "auto_impl", - "dashmap 6.1.0", + "dashmap", "either", "futures", "futures-utils-wasm", - "lru 0.16.3", + "lru", "parking_lot", "pin-project", - "reqwest", + "reqwest 0.13.3", "serde", "serde_json", "thiserror 2.0.18", @@ -448,9 +460,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eebf54983d4fccea08053c218ee5c288adf2e660095a243d0532a8070b43955" +checksum = "a8d86958b02bca85103d64fa60d7b364a8b017c6e40f2b02c3f50ca22964a738" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -470,9 +482,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -481,9 +493,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" dependencies = [ "proc-macro2", "quote", @@ -492,9 +504,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91577235d341a1bdbee30a463655d08504408a4d51e9f72edbfc5a622829f402" +checksum = "5beb5c2fe6b960c8e8b038e69fd502a90a2e930afa4770efb748b163b0767729" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -505,7 +517,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest", + "reqwest 0.13.3", "serde", "serde_json", "tokio", @@ -518,22 +530,22 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cff039bf01a17d76c0aace3a3a773d5f895eb4c68baaae729ec9da9e86c99c" +checksum = "4ee1257a278f6d293e05c5162c5940a1561b1aa85ded0028b464c81de37ebfa5" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-admin" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "564afceae126df73b95f78c81eb46e2ef689a45ace0fcdaf5c9a178693a5ccca" +checksum = "e2144d5b2866e651796eac0a997d3b5a056449c12e0d91be3184129e0c722885" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -543,34 +555,38 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22250cf438b6a3926de67683c08163bfa1fd1efa47ee9512cbcd631b6b0243c" +checksum = "df32156f085e74eac942b6103744be49b817c302341aaa8cb0c1c88dc29228d9" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "serde", ] [[package]] name = "alloy-rpc-types-any" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73234a141ecce14e2989748c04fcac23deee67a445e2c4c167cfb42d4dacd1b6" +checksum = "6a234bfbdf7a76c3d13808f729af5321852de3dedcaa6fc6d5f54787aaf54c6a" dependencies = [ "alloy-consensus-any", + "alloy-network-primitives", + "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", + "serde", + "serde_json", ] [[package]] name = "alloy-rpc-types-beacon" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625af0c3ebd3c31322edb1fb6b8e3e518acc39e164ed07e422eaff05310ff2fa" +checksum = "296450f5e76bece0116c939b9437b0421a5da9c5d40031bf4cf9b38d3d94e475" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -586,11 +602,12 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779f70ab16a77e305571881b07a9bc6b0068ae6f962497baf5762825c5b839fb" +checksum = "0ab075ac1c25bcf697f133b7cd92e2fb26afe213e872ef79fdf77f0d7bcb3793" dependencies = [ "alloy-primitives", + "alloy-rlp", "derive_more", "serde", "serde_with", @@ -598,37 +615,37 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10620d600cc46538f613c561ac9a923843c6c74c61f054828dcdb8dd18c72ec4" +checksum = "73b12366c96f4013e1aeebc96c6b56e5f33f07853c42ea2f485045c0c157a4a1" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "derive_more", "ethereum_ssz", "ethereum_ssz_derive", "jsonwebtoken", - "rand 0.8.5", + "rand 0.8.6", "serde", - "strum 0.27.2", + "strum", ] [[package]] name = "alloy-rpc-types-eth" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "010e101dbebe0c678248907a2545b574a87d078d82c2f6f5d0e8e7c9a6149a10" +checksum = "56a282daf869eeb7383d3d5c2deb35b0b3fb45ecb329513af4090fc61245ee18" dependencies = [ "alloy-consensus", "alloy-consensus-any", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-sol-types", "arbitrary", "itertools 0.14.0", @@ -640,28 +657,28 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375e4bf001135fe4f344db6197fafed8c2b61e99fa14d3597f44cd413f79e45b" +checksum = "7adc1243d55744a66b3a6cbbbba96436e8df5d248f2ee8186bef4238ef704ec7" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "serde", "serde_json", ] [[package]] name = "alloy-rpc-types-trace" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be096f74d85e1f927580b398bf7bc5b4aa62326f149680ec0867e3c040c9aced" +checksum = "6184b5d14152b68b0bb8beb621339d94f0b761a37958bb365fbf7c00922125c2" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "serde", "serde_json", "thiserror 2.0.18", @@ -669,21 +686,32 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ab75189fbc29c5dd6f0bc1529bccef7b00773b458763f4d9d81a77ae4a1a2d" +checksum = "f00b631c361e7c7baaf4f1f5a9877730f3507fed2acb9d4b34841b8184b2ec28" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", + "serde", +] + +[[package]] +name = "alloy-serde" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" +dependencies = [ + "alloy-primitives", "serde", + "serde_json", ] [[package]] name = "alloy-serde" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6d631f8b975229361d8af7b2c749af31c73b3cf1352f90e144ddb06227105e" +checksum = "a0eada2558e921b39dfcead33c487364df9b31374f5733c1c9d2c891c4529933" dependencies = [ "alloy-primitives", "arbitrary", @@ -693,9 +721,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97f40010b5e8f79b70bf163b38cd15f529b18ca88c4427c0e43441ee54e4ed82" +checksum = "41eb29f7a8adcd8941fbb8e134022a133e6f8dfd345f2e3b7109599f8a7dca08" dependencies = [ "alloy-primitives", "async-trait", @@ -708,9 +736,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c4ec1cc27473819399a3f0da83bc1cef0ceaac8c1c93997696e46dc74377a58" +checksum = "bef839e7ce9b59aa60fa9a175e97986c6145c888d643b0f1fb0a3e7b8e56a2e2" dependencies = [ "alloy-consensus", "alloy-network", @@ -720,7 +748,7 @@ dependencies = [ "coins-bip32", "coins-bip39", "k256", - "rand 0.8.5", + "rand 0.8.6", "thiserror 2.0.18", "zeroize", ] @@ -748,7 +776,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "proc-macro-error2", "proc-macro2", "quote", @@ -780,7 +808,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -797,9 +825,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a03bb3f02b9a7ab23dacd1822fa7f69aa5c8eefcdcf57fad085e0b8d76fb4334" +checksum = "3ac7a80c0bac3e44559d53d002e34c461dc2f23262b42cafec019bc70551abbe" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -820,14 +848,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce599598ef8ebe067f3627509358d9faaa1ef94f77f834a7783cd44209ef55c" +checksum = "eed3ed3300a998f88639ed619fdbbd88bd82865e00c6a8ecb796c99eb12358f6" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest", + "reqwest 0.13.3", "serde_json", "tower", "tracing", @@ -836,9 +864,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49963a2561ebd439549915ea61efb70a7b13b97500ec16ca507721c9d9957d07" +checksum = "1075d9d30fd4d71e50000fd4afb19ed2664ceab20c2a29f3889a6e988329e02d" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -856,18 +884,20 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed38ea573c6658e0c2745af9d1f1773b1ed83aa59fbd9c286358ad469c3233a" +checksum = "0e3bff84b2b2a46eb34cc522dc3f889a2867c70be90a377421429b662b3ec4ce" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", "http", + "rustls", "serde_json", "tokio", "tokio-tungstenite", "tracing", + "url", "ws_stream_wasm", ] @@ -893,11 +923,11 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.6.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397406cf04b11ca2a48e6f81804c70af3f40a36abf648e11dc7416043eb0834d" +checksum = "99fce0350197dcd4ba4e9a7dd43915d908c0eb0e7352755791709a705e1c76b6" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -914,9 +944,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -929,15 +959,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -948,7 +978,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -959,7 +989,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -991,6 +1021,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" + [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -1258,7 +1294,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]] @@ -1268,7 +1304,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]] @@ -1278,7 +1314,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]] @@ -1304,9 +1340,9 @@ checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" [[package]] name = "async-compression" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -1392,10 +1428,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "az" -version = "1.3.0" +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] [[package]] name = "backon" @@ -1468,31 +1521,13 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.71.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" -dependencies = [ - "bitflags 2.11.0", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.117", -] - [[package]] name = "bindgen" version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.0", + "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1537,19 +1572,19 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] +[[package]] +name = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + [[package]] name = "bitvec" version = "1.0.1" @@ -1563,6 +1598,20 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1595,19 +1644,20 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" dependencies = [ "borsh-derive", + "bytes", "cfg_aliases", ] [[package]] name = "borsh-derive" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" dependencies = [ "once_cell", "proc-macro-crate", @@ -1668,12 +1718,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" -[[package]] -name = "bytecount" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" - [[package]] name = "bytemuck" version = "1.25.0" @@ -1695,6 +1739,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "c-kzg" version = "2.1.7" @@ -1722,36 +1776,14 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" dependencies = [ "serde", "serde_core", ] -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform 0.1.9", - "semver 1.0.27", - "serde", - "serde_json", -] - [[package]] name = "cargo_metadata" version = "0.23.1" @@ -1759,19 +1791,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", - "cargo-platform 0.3.2", - "semver 1.0.27", + "cargo-platform", + "semver 1.0.28", "serde", "serde_json", "thiserror 2.0.18", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "castaway" version = "0.2.4" @@ -1783,9 +1809,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -1831,7 +1857,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1857,9 +1883,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -1867,9 +1893,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -1879,9 +1905,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -1891,9 +1917,18 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] [[package]] name = "coins-bip32" @@ -1922,7 +1957,7 @@ dependencies = [ "hmac", "once_cell", "pbkdf2", - "rand 0.8.5", + "rand 0.8.6", "sha2", "thiserror 1.0.69", ] @@ -1948,9 +1983,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" @@ -1968,16 +2003,16 @@ version = "7.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" dependencies = [ - "crossterm 0.29.0", + "crossterm", "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width", ] [[package]] name = "compact_str" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -1989,9 +2024,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "brotli", "compression-core", @@ -2003,9 +2038,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "concat-kdf" @@ -2018,12 +2053,12 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "proptest", "serde_core", ] @@ -2042,11 +2077,12 @@ checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" [[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]] @@ -2060,6 +2096,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.10.0" @@ -2094,6 +2136,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -2105,9 +2156,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" @@ -2153,26 +2204,19 @@ dependencies = [ ] [[package]] -name = "crossbeam-utils" -version = "0.8.21" +name = "crossbeam-queue" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "crossterm" -version = "0.28.1" +name = "crossbeam-utils" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags 2.11.0", - "crossterm_winapi", - "mio", - "parking_lot", - "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", - "winapi", -] +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" @@ -2180,11 +2224,15 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags", "crossterm_winapi", + "derive_more", "document-features", + "mio", "parking_lot", - "rustix 1.1.4", + "rustix", + "signal-hook", + "signal-hook-mio", "winapi", ] @@ -2242,7 +2290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", @@ -2272,16 +2320,6 @@ dependencies = [ "darling_macro 0.20.11", ] -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - [[package]] name = "darling" version = "0.23.0" @@ -2306,21 +2344,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "serde", - "strsim", - "syn 2.0.117", -] - [[package]] name = "darling_core" version = "0.23.0" @@ -2330,6 +2353,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", "syn 2.0.117", ] @@ -2345,17 +2369,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", - "quote", - "syn 2.0.117", -] - [[package]] name = "darling_macro" version = "0.23.0" @@ -2367,44 +2380,33 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "dashmap" version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ + "arbitrary", "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", + "serde", ] [[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 = "data-encoding-macro" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -2412,9 +2414,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", "syn 1.0.109", @@ -2470,9 +2472,9 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", @@ -2571,15 +2573,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -2590,18 +2583,6 @@ dependencies = [ "dirs-sys-next", ] -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.5.2", - "windows-sys 0.61.2", -] - [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -2609,15 +2590,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users 0.4.6", + "redox_users", "winapi", ] [[package]] name = "discv5" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f170f4f6ed0e1df52bf43b403899f0081917ecf1500bfe312505cc3b515a8899" +version = "0.10.4" +source = "git+https://github.com/sigp/discv5?rev=7663c00#7663c00ee0837ea98547caaedede95d9d6736f4d" dependencies = [ "aes", "aes-gcm", @@ -2633,13 +2613,12 @@ dependencies = [ "hkdf", "lazy_static", "libp2p-identity", - "lru 0.12.5", "more-asserts", "multiaddr", "parking_lot", - "rand 0.8.5", + "rand 0.8.6", "smallvec", - "socket2 0.5.10", + "socket2", "tokio", "tracing", "uint 0.10.0", @@ -2659,9 +2638,9 @@ dependencies = [ [[package]] name = "doctest-file" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" +checksum = "c2db04e74f0a9a93103b50e90b96024c9b2bdca8bce6a632ec71b88736d3d359" [[package]] name = "document-features" @@ -2778,7 +2757,7 @@ dependencies = [ "hex", "k256", "log", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "serde", "sha3", @@ -2830,25 +2809,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", + "windows-sys 0.52.0", ] [[package]] name = "ethereum_hashing" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" +checksum = "5aa93f58bb1eb3d1e556e4f408ef1dac130bad01ac37db4e7ade45de40d1c86a" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "ring", "sha2", ] @@ -2868,13 +2838,13 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.9.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" +checksum = "368a4a4e4273b0135111fe9464e35465067766a8f664615b5a86338b73864407" dependencies = [ "alloy-primitives", "ethereum_serde_utils", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_derive", "smallvec", @@ -2883,16 +2853,27 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.9.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" +checksum = "f2cd82c68120c89361e1a457245cf212f7d9f541bffaffed530c8f2d54a160b2" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "evmap" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8" +dependencies = [ + "hashbag", + "left-right", + "smallvec", +] + [[package]] name = "eyre" version = "0.6.12" @@ -2905,9 +2886,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fastrlp" @@ -2974,6 +2955,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixed-cache" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41c7aa69c00ebccf06c3fa7ffe2a6cf26a58b5fe4deabfe646285ff48136a8f" +dependencies = [ + "equivalent", + "rapidhash", + "typeid", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2981,7 +2973,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", ] @@ -3044,6 +3036,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -3163,6 +3161,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link 0.1.3", + "windows-result 0.3.4", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3230,7 +3243,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.11.0", + "bitflags", "libc", "libgit2-sys", "log", @@ -3289,16 +3302,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gmp-mpfr-sys" -version = "1.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - [[package]] name = "group" version = "0.13.0" @@ -3312,9 +3315,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", @@ -3322,7 +3325,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -3335,6 +3338,12 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" +[[package]] +name = "hashbag" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064" + [[package]] name = "hashbrown" version = "0.12.3" @@ -3352,9 +3361,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" @@ -3363,7 +3369,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", - "equivalent", "foldhash 0.1.5", ] @@ -3380,13 +3385,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "hashlink" -version = "0.9.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.16.1", ] [[package]] @@ -3442,7 +3453,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.2", + "rand 0.9.4", "ring", "serde", "thiserror 2.0.18", @@ -3465,7 +3476,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "resolv-conf", "serde", "smallvec", @@ -3567,9 +3578,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -3582,7 +3593,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -3590,9 +3600,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", @@ -3600,11 +3610,9 @@ dependencies = [ "log", "rustls", "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", ] [[package]] @@ -3637,7 +3645,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2", "tokio", "tower-service", "tracing", @@ -3655,7 +3663,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -3669,12 +3677,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -3682,9 +3691,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -3695,9 +3704,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -3709,15 +3718,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -3729,15 +3738,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -3773,9 +3782,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", @@ -3791,6 +3800,31 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "imbl" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e525189e5f603908d0c6e0d402cb5de9c4b2c8866151fabc4ebd771ed2630a2e" +dependencies = [ + "archery", + "bitmaps", + "imbl-sized-chunks", + "rand_core 0.9.5", + "rand_xoshiro", + "serde_core", + "version_check", + "wide", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -3849,13 +3883,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -3875,7 +3909,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.11.0", + "bitflags", "inotify-sys", "libc", ] @@ -3901,9 +3935,9 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling 0.23.0", "indoc", @@ -3914,9 +3948,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" +checksum = "069323743400cb7ab06a8fe5c1ed911d36b6919ec531661d034c89083629595b" dependencies = [ "doctest-file", "futures-core", @@ -3924,19 +3958,20 @@ dependencies = [ "recvmsg", "tokio", "widestring", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.5.10", + "socket2", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", ] [[package]] @@ -3945,16 +3980,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -3990,9 +4015,26 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jemalloc_pprof" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d44c349cfe2654897fadcb9de4f0bfbf48288ec344f700b2bd59f152dd209" +dependencies = [ + "anyhow", + "libc", + "mappings", + "once_cell", + "pprof_util", + "tempfile", + "tikv-jemalloc-ctl", + "tokio", + "tracing", +] [[package]] name = "jni" @@ -4003,18 +4045,70 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.117", +] + [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] [[package]] name = "jobserver" @@ -4028,10 +4122,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -4069,7 +4165,7 @@ dependencies = [ "pin-project", "rustls", "rustls-pki-types", - "rustls-platform-verifier", + "rustls-platform-verifier 0.5.3", "soketto", "thiserror 2.0.18", "tokio", @@ -4095,7 +4191,7 @@ dependencies = [ "jsonrpsee-types", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.4", "rustc-hash", "serde", "serde_json", @@ -4121,7 +4217,7 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "rustls", - "rustls-platform-verifier", + "rustls-platform-verifier 0.5.3", "serde", "serde_json", "thiserror 2.0.18", @@ -4210,16 +4306,18 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.1" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ + "aws-lc-rs", "base64 0.22.1", + "getrandom 0.2.17", "js-sys", "pem", - "ring", "serde", "serde_json", + "signature", "simple_asn1", ] @@ -4238,25 +4336,51 @@ dependencies = [ "signature", ] +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + [[package]] name = "keccak" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] name = "keccak-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" 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 = "kqueue" version = "1.1.1" @@ -4269,11 +4393,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "a7b65860415f949f23fa882e669f2dbd4a0f0eeb1acdd56790b30494afd7da2f" dependencies = [ - "bitflags 1.3.2", + "bitflags", "libc", ] @@ -4289,17 +4413,28 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "left-right" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0c21e4c8ff95f487fb34e6f9182875f42c84cef966d29216bf115d9bba835a" +dependencies = [ + "crossbeam-utils", + "loom", + "slab", +] + [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +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", @@ -4314,7 +4449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4348,28 +4483,43 @@ version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a54ad7278b8bc5301d5ffd2a94251c004feb971feba96c971ea4063645990757" dependencies = [ - "bindgen 0.72.1", + "bindgen", "errno", "libc", ] [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", + "bitflags", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.5", +] + +[[package]] +name = "librocksdb-sys" +version = "0.17.3+10.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef2a00ee60fe526157c9023edab23943fae1ce2ab6f4abb2a807c1746835de9" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "libc", + "libz-sys", + "lz4-sys", + "zstd-sys", ] [[package]] name = "libz-sys" -version = "1.1.25" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -4377,6 +4527,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -4393,12 +4552,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -4407,9 +4560,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" @@ -4434,19 +4587,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "lru" -version = "0.12.5" +name = "loom" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ - "hashbrown 0.15.5", -] - + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber 0.3.23", +] + [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] @@ -4478,9 +4635,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.11.6" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" +checksum = "98c23545df7ecf1b16c303910a69b079e8e251d60f7dd2cc9b4177f2afaf1746" [[package]] name = "mach2" @@ -4508,6 +4665,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "mappings" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bab1e61a4b76757edb59cd81fcaa7f3ba9018d43b527d9abfad877b4c6c60f2" +dependencies = [ + "anyhow", + "libc", + "once_cell", + "pprof_util", + "tracing", +] + [[package]] name = "match-lookup" version = "0.1.2" @@ -4545,12 +4715,12 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "ff56c2e7dce6bd462e3b8919986a617027481b1dcc703175b58cf9dd98a2f071" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", ] [[package]] @@ -4566,12 +4736,13 @@ dependencies = [ [[package]] name = "metrics-exporter-prometheus" -version = "0.18.1" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda" +checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108" dependencies = [ "base64 0.22.1", - "indexmap 2.13.0", + "evmap", + "indexmap 2.14.0", "metrics", "metrics-util", "quanta", @@ -4589,24 +4760,25 @@ dependencies = [ "mach2 0.6.0", "metrics", "once_cell", - "procfs 0.18.0", + "procfs", "rlimit", "windows 0.62.2", ] [[package]] name = "metrics-util" -version = "0.20.1" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdfb1365fea27e6dd9dc1dbc19f570198bc86914533ad639dae939635f096be4" +checksum = "9e56997f084e57b045edf17c3ed8ba7f9f779c670df8206dfd1c736f4c02dc4a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.16.1", "metrics", "quanta", - "rand 0.9.2", + "rand 0.9.4", "rand_xoshiro", + "rapidhash", "sketches-ddsketch", ] @@ -4626,21 +4798,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "mini-moka" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" -dependencies = [ - "crossbeam-channel", - "crossbeam-utils", - "dashmap 5.5.3", - "skeptic", - "smallvec", - "tagptr", - "triomphe", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4659,9 +4816,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -4671,9 +4828,9 @@ dependencies = [ [[package]] name = "modular-bitfield" -version = "0.11.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a" dependencies = [ "modular-bitfield-impl", "static_assertions", @@ -4681,20 +4838,20 @@ dependencies = [ [[package]] name = "modular-bitfield-impl" -version = "0.11.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] name = "moka" -version = "0.12.14" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -4719,12 +4876,12 @@ version = "0.3.0" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-hardforks", "alloy-primitives", - "alloy-serde", + "alloy-serde 2.0.4", "auto_impl", "eyre", "morph-primitives", @@ -4759,7 +4916,7 @@ name = "morph-engine-api" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-rpc-types-engine", @@ -4774,7 +4931,6 @@ dependencies = [ "reth-metrics", "reth-node-api", "reth-payload-builder", - "reth-payload-primitives", "reth-primitives-traits", "reth-provider", "reth-rpc-api", @@ -4783,6 +4939,52 @@ dependencies = [ "tracing", ] +[[package]] +name = "morph-engine-tree-ext" +version = "0.3.0" +dependencies = [ + "alloy-consensus", + "alloy-eip7928", + "alloy-eips 2.0.4", + "alloy-evm", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "crossbeam-channel", + "derive_more", + "eyre", + "morph-chainspec", + "morph-node", + "morph-payload-types", + "morph-primitives", + "reth-chain-state", + "reth-consensus", + "reth-db", + "reth-engine-primitives", + "reth-engine-tree", + "reth-errors", + "reth-evm", + "reth-execution-cache", + "reth-node-api", + "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-tasks", + "reth-tracing", + "reth-trie", + "reth-trie-db", + "reth-trie-parallel", + "reth-trie-sparse", + "revm-primitives", + "serde_json", + "tokio", + "tracing", +] + [[package]] name = "morph-evm" version = "0.3.0" @@ -4814,7 +5016,7 @@ name = "morph-node" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-hardforks", "alloy-primitives", @@ -4824,12 +5026,14 @@ dependencies = [ "alloy-signer", "alloy-signer-local", "clap", - "dashmap 6.1.0", + "dashmap", "eyre", + "futures", "jsonrpsee", "morph-chainspec", "morph-consensus", "morph-engine-api", + "morph-engine-tree-ext", "morph-evm", "morph-payload-builder", "morph-payload-types", @@ -4861,6 +5065,7 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "reth-trie", + "reth-trie-db", "serde", "serde_json", "tempfile", @@ -4876,7 +5081,7 @@ name = "morph-payload-builder" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "metrics", @@ -4887,6 +5092,7 @@ dependencies = [ "reth-basic-payload-builder", "reth-chainspec", "reth-evm", + "reth-execution-cache", "reth-execution-types", "reth-metrics", "reth-payload-builder", @@ -4896,6 +5102,7 @@ dependencies = [ "reth-revm", "reth-storage-api", "reth-transaction-pool", + "reth-trie-parallel", "revm", "thiserror 2.0.18", "tracing", @@ -4906,15 +5113,14 @@ name = "morph-payload-types" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "alloy-serde", + "alloy-serde 2.0.4", "morph-primitives", - "rand 0.8.5", + "rand 0.8.6", "reth-ethereum-primitives", - "reth-payload-builder", "reth-payload-primitives", "reth-primitives-traits", "serde", @@ -4927,10 +5133,10 @@ name = "morph-primitives" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 2.0.4", "bytes", "modular-bitfield", "reth-codecs", @@ -4992,7 +5198,7 @@ name = "morph-revm" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-sol-types", @@ -5015,11 +5221,12 @@ name = "morph-rpc" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", + "alloy-evm", "alloy-network", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "derive_more", "eyre", "jsonrpsee", @@ -5056,17 +5263,20 @@ name = "morph-txpool" version = "0.3.0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "c-kzg", "derive_more", "futures", "morph-chainspec", + "morph-evm", "morph-primitives", "morph-revm", "parking_lot", "reth-chainspec", + "reth-evm", + "reth-evm-ethereum", "reth-primitives-traits", "reth-provider", "reth-revm", @@ -5131,7 +5341,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags", "fsevent-sys", "inotify", "kqueue", @@ -5149,7 +5359,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -5167,7 +5377,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5205,9 +5415,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -5262,9 +5472,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -5272,9 +5482,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -5307,136 +5517,39 @@ dependencies = [ ] [[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -dependencies = [ - "critical-section", - "portable-atomic", -] - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "op-alloy" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b8fee21003dd4f076563de9b9d26f8c97840157ef78593cd7f262c5ca99848" -dependencies = [ - "op-alloy-consensus", - "op-alloy-network", - "op-alloy-provider", - "op-alloy-rpc-types", - "op-alloy-rpc-types-engine", -] - -[[package]] -name = "op-alloy-consensus" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736381a95471d23e267263cfcee9e1d96d30b9754a94a2819148f83379de8a86" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-network", - "alloy-primitives", - "alloy-rlp", - "alloy-rpc-types-eth", - "alloy-serde", - "arbitrary", - "derive_more", - "serde", - "serde_with", - "thiserror 2.0.18", -] - -[[package]] -name = "op-alloy-network" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4034183dca6bff6632e7c24c92e75ff5f0eabb58144edb4d8241814851334d47" -dependencies = [ - "alloy-consensus", - "alloy-network", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-types-eth", - "alloy-signer", - "op-alloy-consensus", - "op-alloy-rpc-types", -] - -[[package]] -name = "op-alloy-provider" -version = "0.23.1" +name = "objc2-core-foundation" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6753d90efbaa8ea8bcb89c1737408ca85fa60d7adb875049d3f382c063666f86" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "alloy-network", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-types-engine", - "alloy-transport", - "async-trait", - "op-alloy-rpc-types-engine", + "bitflags", ] [[package]] -name = "op-alloy-rpc-types" -version = "0.23.1" +name = "objc2-io-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd87c6b9e5b6eee8d6b76f41b04368dca0e9f38d83338e5b00e730c282098a4" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-network-primitives", - "alloy-primitives", - "alloy-rpc-types-eth", - "alloy-serde", - "derive_more", - "op-alloy-consensus", - "serde", - "serde_json", - "thiserror 2.0.18", + "libc", + "objc2-core-foundation", ] [[package]] -name = "op-alloy-rpc-types-engine" -version = "0.23.1" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727699310a18cdeed32da3928c709e2704043b6584ed416397d5da65694efc" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-primitives", - "alloy-rlp", - "alloy-rpc-types-engine", - "alloy-serde", - "derive_more", - "ethereum_ssz", - "ethereum_ssz_derive", - "op-alloy-consensus", - "serde", - "sha2", - "snap", - "thiserror 2.0.18", + "critical-section", + "portable-atomic", ] [[package]] -name = "op-revm" -version = "14.1.0" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1475a779c73999fc803778524042319691b31f3d6699d2b560c4ed8be1db802a" -dependencies = [ - "auto_impl", - "revm", - "serde", -] +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "opaque-debug" @@ -5474,14 +5587,14 @@ dependencies = [ "bytes", "http", "opentelemetry", - "reqwest", + "reqwest 0.12.28", ] [[package]] name = "opentelemetry-otlp" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" +checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" dependencies = [ "http", "opentelemetry", @@ -5489,7 +5602,7 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest", + "reqwest 0.12.28", "thiserror 2.0.18", "tokio", "tonic", @@ -5526,16 +5639,10 @@ dependencies = [ "futures-util", "opentelemetry", "percent-encoding", - "rand 0.9.2", + "rand 0.9.4", "thiserror 2.0.18", ] -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "p256" version = "0.13.2" @@ -5564,7 +5671,6 @@ version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ - "arbitrary", "arrayvec", "bitvec", "byte-slice-cast", @@ -5608,7 +5714,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5708,18 +5814,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" dependencies = [ "proc-macro2", "quote", @@ -5750,9 +5856,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -5776,7 +5882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -5789,9 +5895,9 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -5802,6 +5908,19 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "pprof_util" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea0cc524de808a6d98d192a3d99fe95617031ad4a52ec0a0f987ef4432e8fe1" +dependencies = [ + "anyhow", + "flate2", + "num", + "paste", + "prost", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -5857,7 +5976,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.4+spec-1.1.0", + "toml_edit", ] [[package]] @@ -5891,40 +6010,17 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "procfs" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" -dependencies = [ - "bitflags 2.11.0", - "chrono", - "flate2", - "hex", - "procfs-core 0.17.0", - "rustix 0.38.44", -] - [[package]] name = "procfs" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags 2.11.0", - "procfs-core 0.18.0", - "rustix 1.1.4", -] - -[[package]] -name = "procfs-core" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" -dependencies = [ - "bitflags 2.11.0", + "bitflags", "chrono", - "hex", + "flate2", + "procfs-core", + "rustix", ] [[package]] @@ -5933,21 +6029,22 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.11.0", + "bitflags", + "chrono", "hex", ] [[package]] name = "proptest" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.11.0", + "bitflags", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -6000,17 +6097,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "pulldown-cmark" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" -dependencies = [ - "bitflags 2.11.0", - "memchr", - "unicase", -] - [[package]] name = "quanta" version = "0.12.6" @@ -6054,7 +6140,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2", "thiserror 2.0.18", "tokio", "tracing", @@ -6067,10 +6153,11 @@ version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", @@ -6091,7 +6178,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.60.2", ] @@ -6125,9 +6212,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[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", @@ -6137,9 +6224,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -6209,29 +6296,71 @@ version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" dependencies = [ - "rand 0.9.2", + "rand 0.9.4", "rustversion", ] [[package]] name = "ratatui" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" dependencies = [ - "bitflags 2.11.0", - "cassowary", + "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-widgets", +] + +[[package]] +name = "ratatui-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" +dependencies = [ + "bitflags", "compact_str", - "crossterm 0.28.1", + "hashbrown 0.16.1", "indoc", - "instability", - "itertools 0.13.0", - "lru 0.12.5", - "paste", - "strum 0.26.3", + "itertools 0.14.0", + "kasuari", + "lru", + "strum", + "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.2.0", + "unicode-width", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" +dependencies = [ + "bitflags", + "hashbrown 0.16.1", + "indoc", + "instability", + "itertools 0.14.0", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width", ] [[package]] @@ -6240,14 +6369,14 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -6275,16 +6404,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -6298,17 +6427,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - [[package]] name = "ref-cast" version = "1.0.25" @@ -6389,6 +6507,46 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "reqwest" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier 0.7.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -6398,7 +6556,6 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.6", ] [[package]] @@ -6409,16 +6566,17 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "reth-basic-payload-builder" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "futures-core", "futures-util", "metrics", "reth-chain-state", + "reth-execution-cache", "reth-metrics", "reth-payload-builder", "reth-payload-builder-primitives", @@ -6427,17 +6585,19 @@ dependencies = [ "reth-revm", "reth-storage-api", "reth-tasks", + "reth-trie-parallel", + "serde", "tokio", "tracing", ] [[package]] name = "reth-chain-state" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-signer", "alloy-signer-local", @@ -6445,7 +6605,8 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.4", + "rayon", "reth-chainspec", "reth-errors", "reth-ethereum-primitives", @@ -6464,12 +6625,12 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-primitives", @@ -6484,8 +6645,8 @@ dependencies = [ [[package]] name = "reth-cli" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-genesis", "clap", @@ -6493,23 +6654,23 @@ dependencies = [ "reth-cli-runner", "reth-db", "serde_json", - "shellexpand", ] [[package]] name = "reth-cli-commands" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "backon", + "blake3", "clap", "comfy-table", - "crossterm 0.28.1", + "crossterm", "eyre", "fdlimit", "futures", @@ -6518,8 +6679,10 @@ dependencies = [ "itertools 0.14.0", "lz4", "metrics", + "parking_lot", "ratatui", - "reqwest", + "rayon", + "reqwest 0.13.3", "reth-chainspec", "reth-cli", "reth-cli-runner", @@ -6554,13 +6717,15 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-prune", + "reth-prune-types", "reth-revm", "reth-stages", + "reth-stages-types", "reth-static-file", "reth-static-file-types", + "reth-storage-api", "reth-tasks", "reth-trie", - "reth-trie-common", "reth-trie-db", "secp256k1 0.30.0", "serde", @@ -6576,8 +6741,8 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "reth-tasks", "tokio", @@ -6586,35 +6751,38 @@ dependencies = [ [[package]] name = "reth-cli-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "cfg-if", "eyre", "libc", - "rand 0.8.5", + "rand 0.8.6", "reth-fs-util", "secp256k1 0.30.0", "serde", "thiserror 2.0.18", + "tikv-jemalloc-sys", + "tikv-jemallocator", ] [[package]] name = "reth-codecs" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce542a96bf888f31854803e80b3340bc233927743aa580838014e8a88fe0d66" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-trie", "arbitrary", "bytes", "modular-bitfield", - "op-alloy-consensus", + "parity-scale-codec", "reth-codecs-derive", "reth-zstd-compressors", "serde", @@ -6623,8 +6791,9 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634c90f1cc0f9887680ca785b0b21aa961070b9465917bf65afaec56a6d005bb" dependencies = [ "proc-macro2", "quote", @@ -6633,8 +6802,8 @@ dependencies = [ [[package]] name = "reth-config" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "eyre", "humantime-serde", @@ -6649,8 +6818,8 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6662,11 +6831,12 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", + "alloy-primitives", "reth-chainspec", "reth-consensus", "reth-primitives-traits", @@ -6674,11 +6844,11 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-json-rpc", "alloy-primitives", "alloy-provider", @@ -6688,7 +6858,7 @@ dependencies = [ "derive_more", "eyre", "futures", - "reqwest", + "reqwest 0.13.3", "reth-node-api", "reth-primitives-traits", "reth-tracing", @@ -6700,15 +6870,17 @@ dependencies = [ [[package]] name = "reth-db" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "derive_more", "eyre", + "libc", "metrics", "page_size", "parking_lot", + "quanta", "reth-db-api", "reth-fs-util", "reth-libmdbx", @@ -6718,31 +6890,30 @@ dependencies = [ "reth-storage-errors", "reth-tracing", "rustc-hash", - "strum 0.27.2", + "strum", "sysinfo", "tempfile", "thiserror 2.0.18", + "tracing", ] [[package]] name = "reth-db-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-genesis", "alloy-primitives", "arbitrary", + "arrayvec", "bytes", "derive_more", "metrics", "modular-bitfield", - "parity-scale-codec", "proptest", "reth-codecs", "reth-db-models", "reth-ethereum-primitives", - "reth-optimism-primitives", "reth-primitives-traits", "reth-prune-types", "reth-stages-types", @@ -6754,8 +6925,8 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -6784,10 +6955,10 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "arbitrary", "bytes", @@ -6799,8 +6970,8 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6808,7 +6979,7 @@ dependencies = [ "enr", "itertools 0.14.0", "parking_lot", - "rand 0.8.5", + "rand 0.8.6", "reth-ethereum-forks", "reth-net-banlist", "reth-net-nat", @@ -6824,8 +6995,8 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6835,7 +7006,7 @@ dependencies = [ "futures", "itertools 0.14.0", "metrics", - "rand 0.9.2", + "rand 0.9.4", "reth-chainspec", "reth-ethereum-forks", "reth-metrics", @@ -6848,15 +7019,15 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", + "dashmap", "data-encoding", "enr", "hickory-resolver", "linked_hash_set", - "parking_lot", "reth-ethereum-forks", "reth-network-peers", "reth-tokio-util", @@ -6872,11 +7043,11 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "async-compression", @@ -6907,11 +7078,11 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-network", "alloy-primitives", "alloy-provider", @@ -6943,7 +7114,6 @@ dependencies = [ "reth-payload-builder", "reth-payload-builder-primitives", "reth-payload-primitives", - "reth-primitives", "reth-primitives-traits", "reth-provider", "reth-rpc-api", @@ -6965,8 +7135,8 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "aes", "alloy-primitives", @@ -6980,7 +7150,7 @@ dependencies = [ "futures", "hmac", "pin-project", - "rand 0.8.5", + "rand 0.8.6", "reth-network-peers", "secp256k1 0.30.0", "sha2", @@ -6993,8 +7163,8 @@ dependencies = [ [[package]] name = "reth-engine-local" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7016,11 +7186,11 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -7039,46 +7209,23 @@ dependencies = [ "tokio", ] -[[package]] -name = "reth-engine-service" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" -dependencies = [ - "futures", - "pin-project", - "reth-chainspec", - "reth-consensus", - "reth-engine-primitives", - "reth-engine-tree", - "reth-ethereum-primitives", - "reth-evm", - "reth-network-p2p", - "reth-node-types", - "reth-payload-builder", - "reth-provider", - "reth-prune", - "reth-stages-api", - "reth-tasks", -] - [[package]] name = "reth-engine-tree" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eip7928 0.1.0", - "alloy-eips", + "alloy-eip7928", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", "crossbeam-channel", - "dashmap 6.1.0", "derive_more", "futures", + "indexmap 2.14.0", "metrics", - "mini-moka", "moka", "parking_lot", "rayon", @@ -7090,6 +7237,7 @@ dependencies = [ "reth-errors", "reth-ethereum-primitives", "reth-evm", + "reth-execution-cache", "reth-execution-types", "reth-metrics", "reth-network-p2p", @@ -7106,13 +7254,13 @@ dependencies = [ "reth-tasks", "reth-tracing", "reth-trie", + "reth-trie-common", + "reth-trie-db", "reth-trie-parallel", "reth-trie-sparse", - "reth-trie-sparse-parallel", "revm", "revm-primitives", "schnellru", - "smallvec", "thiserror 2.0.18", "tokio", "tracing", @@ -7120,8 +7268,8 @@ dependencies = [ [[package]] name = "reth-engine-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -7148,29 +7296,30 @@ dependencies = [ [[package]] name = "reth-era" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "ethereum_ssz", "ethereum_ssz_derive", + "sha2", "snap", "thiserror 2.0.18", ] [[package]] name = "reth-era-downloader" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "bytes", "eyre", "futures-util", - "reqwest", + "reqwest 0.13.3", "reth-era", "reth-fs-util", "sha2", @@ -7179,8 +7328,8 @@ dependencies = [ [[package]] name = "reth-era-utils" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7201,8 +7350,8 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -7212,8 +7361,8 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7240,12 +7389,13 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-chains", "alloy-consensus", - "alloy-eips", + "alloy-eip7928", + "alloy-eips 2.0.4", "alloy-hardforks", "alloy-primitives", "alloy-rlp", @@ -7261,8 +7411,8 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "clap", "eyre", @@ -7277,17 +7427,18 @@ dependencies = [ "reth-node-ethereum", "reth-node-metrics", "reth-rpc-server-types", + "reth-tasks", "reth-tracing", "tracing", ] [[package]] name = "reth-ethereum-consensus" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chainspec", "reth-consensus", @@ -7299,26 +7450,24 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-rlp", "alloy-rpc-types-engine", "reth-engine-primitives", "reth-ethereum-primitives", "reth-payload-primitives", "reth-primitives-traits", "serde", - "sha2", "thiserror 2.0.18", ] [[package]] name = "reth-ethereum-forks" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -7330,11 +7479,11 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", @@ -7345,6 +7494,7 @@ dependencies = [ "reth-ethereum-primitives", "reth-evm", "reth-evm-ethereum", + "reth-execution-cache", "reth-payload-builder", "reth-payload-builder-primitives", "reth-payload-primitives", @@ -7359,28 +7509,22 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", - "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde", - "arbitrary", - "modular-bitfield", "reth-codecs", "reth-primitives-traits", - "reth-zstd-compressors", "serde", - "serde_with", ] [[package]] name = "reth-etl" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "rayon", "reth-db-api", @@ -7389,11 +7533,11 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "auto_impl", @@ -7413,15 +7557,14 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", - "derive_more", "reth-chainspec", "reth-ethereum-forks", "reth-ethereum-primitives", @@ -7432,10 +7575,28 @@ dependencies = [ "revm", ] +[[package]] +name = "reth-execution-cache" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" +dependencies = [ + "alloy-primitives", + "fixed-cache", + "metrics", + "parking_lot", + "reth-errors", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-trie", + "tracing", +] + [[package]] name = "reth-execution-errors" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-evm", "alloy-primitives", @@ -7447,13 +7608,14 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-primitives", + "alloy-rlp", "derive_more", "reth-ethereum-primitives", "reth-primitives-traits", @@ -7465,11 +7627,11 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "eyre", "futures", @@ -7503,10 +7665,10 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "reth-chain-state", "reth-execution-types", @@ -7517,8 +7679,8 @@ dependencies = [ [[package]] name = "reth-fs-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "serde", "serde_json", @@ -7527,8 +7689,8 @@ dependencies = [ [[package]] name = "reth-invalid-block-hooks" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7555,8 +7717,8 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "bytes", "futures", @@ -7575,12 +7737,13 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "bitflags 2.11.0", + "bitflags", "byteorder", - "dashmap 6.1.0", + "crossbeam-queue", + "dashmap", "derive_more", "parking_lot", "reth-mdbx-sys", @@ -7591,29 +7754,30 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "bindgen 0.71.1", + "bindgen", "cc", ] [[package]] name = "reth-metrics" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "futures", "metrics", "metrics-derive", + "reth-primitives-traits", "tokio", "tokio-util", ] [[package]] name = "reth-net-banlist" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "ipnet", @@ -7621,12 +7785,12 @@ dependencies = [ [[package]] name = "reth-net-nat" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "futures-util", "if-addrs", - "reqwest", + "reqwest 0.13.3", "serde_with", "thiserror 2.0.18", "tokio", @@ -7635,11 +7799,11 @@ dependencies = [ [[package]] name = "reth-network" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "aquamarine", @@ -7652,8 +7816,8 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "rayon", "reth-chainspec", "reth-consensus", @@ -7665,6 +7829,7 @@ dependencies = [ "reth-eth-wire-types", "reth-ethereum-forks", "reth-ethereum-primitives", + "reth-evm-ethereum", "reth-fs-util", "reth-metrics", "reth-net-banlist", @@ -7682,6 +7847,7 @@ dependencies = [ "secp256k1 0.30.0", "serde", "smallvec", + "socket2", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -7691,8 +7857,8 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7716,11 +7882,11 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "auto_impl", "derive_more", @@ -7739,8 +7905,8 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7754,8 +7920,8 @@ dependencies = [ [[package]] name = "reth-network-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -7768,8 +7934,8 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "anyhow", "bincode", @@ -7785,8 +7951,8 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -7809,11 +7975,11 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-provider", "alloy-rpc-types", @@ -7837,7 +8003,6 @@ dependencies = [ "reth-downloaders", "reth-engine-local", "reth-engine-primitives", - "reth-engine-service", "reth-engine-tree", "reth-engine-util", "reth-evm", @@ -7868,6 +8033,7 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "reth-transaction-pool", + "reth-trie-db", "secp256k1 0.30.0", "serde_json", "tokio", @@ -7877,11 +8043,11 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "clap", @@ -7891,7 +8057,7 @@ dependencies = [ "futures", "humantime", "ipnet", - "rand 0.9.2", + "rand 0.9.4", "reth-chainspec", "reth-cli-util", "reth-config", @@ -7908,7 +8074,6 @@ dependencies = [ "reth-network-p2p", "reth-network-peers", "reth-primitives-traits", - "reth-provider", "reth-prune-types", "reth-rpc-convert", "reth-rpc-eth-types", @@ -7916,13 +8081,13 @@ dependencies = [ "reth-stages-types", "reth-storage-api", "reth-storage-errors", + "reth-tasks", "reth-tracing", "reth-tracing-otlp", "reth-transaction-pool", "secp256k1 0.30.0", "serde", - "shellexpand", - "strum 0.27.2", + "strum", "thiserror 2.0.18", "toml", "tracing", @@ -7933,10 +8098,10 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-network", "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -7971,8 +8136,8 @@ dependencies = [ [[package]] name = "reth-node-ethstats" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7995,11 +8160,11 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", @@ -8019,22 +8184,28 @@ dependencies = [ [[package]] name = "reth-node-metrics" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "bytes", "eyre", "http", "http-body-util", + "jemalloc_pprof", "jsonrpsee-server", + "mappings", "metrics", "metrics-exporter-prometheus", "metrics-process", "metrics-util", - "procfs 0.17.0", - "reqwest", + "pprof_util", + "procfs", + "reqwest 0.13.3", + "reth-fs-util", "reth-metrics", "reth-tasks", + "tempfile", + "tikv-jemalloc-ctl", "tokio", "tower", "tracing", @@ -8042,8 +8213,8 @@ dependencies = [ [[package]] name = "reth-node-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "reth-chainspec", "reth-db-api", @@ -8052,37 +8223,25 @@ dependencies = [ "reth-primitives-traits", ] -[[package]] -name = "reth-optimism-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-primitives", - "alloy-rlp", - "op-alloy-consensus", - "reth-primitives-traits", - "serde", - "serde_with", -] - [[package]] name = "reth-payload-builder" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rpc-types", + "derive_more", "futures-util", "metrics", "reth-chain-state", "reth-ethereum-engine-primitives", + "reth-execution-cache", "reth-metrics", "reth-payload-builder-primitives", "reth-payload-primitives", "reth-primitives-traits", + "reth-trie-parallel", "tokio", "tokio-stream", "tracing", @@ -8090,8 +8249,8 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "pin-project", "reth-payload-primitives", @@ -8102,16 +8261,16 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "auto_impl", "either", - "op-alloy-rpc-types-engine", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -8119,14 +8278,15 @@ dependencies = [ "reth-primitives-traits", "reth-trie-common", "serde", + "sha2", "thiserror 2.0.18", "tokio", ] [[package]] name = "reth-payload-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8135,49 +8295,37 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", "reth-primitives-traits", ] -[[package]] -name = "reth-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" -dependencies = [ - "alloy-consensus", - "once_cell", - "reth-ethereum-forks", - "reth-ethereum-primitives", - "reth-primitives-traits", - "reth-static-file-types", -] - [[package]] name = "reth-primitives-traits" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee12e304adbacbb32248c9806ebafbe1e2811fbfefe53c5e5b710a8438b7ec0" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", "alloy-trie", "arbitrary", - "auto_impl", "byteorder", "bytes", + "dashmap", "derive_more", "modular-bitfield", "once_cell", - "op-alloy-consensus", "proptest", "proptest-arbitrary-interop", + "quanta", "rayon", "reth-codecs", "revm-bytecode", @@ -8185,20 +8333,19 @@ dependencies = [ "revm-state", "secp256k1 0.30.0", "serde", - "serde_with", "thiserror 2.0.18", ] [[package]] name = "reth-provider" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", + "alloy-genesis", "alloy-primitives", "alloy-rpc-types-engine", - "dashmap 6.1.0", "eyre", "itertools 0.14.0", "metrics", @@ -8214,6 +8361,7 @@ dependencies = [ "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-execution-types", + "reth-fs-util", "reth-metrics", "reth-nippy-jar", "reth-node-types", @@ -8223,22 +8371,24 @@ dependencies = [ "reth-static-file-types", "reth-storage-api", "reth-storage-errors", + "reth-tasks", "reth-trie", "reth-trie-db", "revm-database", "revm-state", - "strum 0.27.2", + "rocksdb", + "strum", "tokio", "tracing", ] [[package]] name = "reth-prune" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "itertools 0.14.0", "metrics", @@ -8253,6 +8403,7 @@ dependencies = [ "reth-prune-types", "reth-stages-types", "reth-static-file-types", + "reth-storage-api", "reth-tokio-util", "rustc-hash", "thiserror 2.0.18", @@ -8262,8 +8413,8 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "arbitrary", @@ -8271,16 +8422,19 @@ dependencies = [ "modular-bitfield", "reth-codecs", "serde", - "strum 0.27.2", + "strum", "thiserror 2.0.18", + "tracing", ] [[package]] name = "reth-revm" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-debug", "reth-primitives-traits", "reth-storage-api", "reth-storage-errors", @@ -8290,13 +8444,12 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eip7928 0.1.0", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-genesis", "alloy-network", @@ -8312,20 +8465,16 @@ dependencies = [ "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-signer", "alloy-signer-local", "async-trait", "derive_more", "dyn-clone", "futures", - "http", - "http-body", - "hyper", "itertools 0.14.0", "jsonrpsee", "jsonrpsee-types", - "jsonwebtoken", "parking_lot", "pin-project", "reth-chain-state", @@ -8354,6 +8503,7 @@ dependencies = [ "reth-rpc-server-types", "reth-storage-api", "reth-tasks", + "reth-tracing", "reth-transaction-pool", "reth-trie-common", "revm", @@ -8365,18 +8515,16 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-stream", - "tower", "tracing", "tracing-futures", ] [[package]] name = "reth-rpc-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eip7928 0.1.0", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-json-rpc", "alloy-primitives", @@ -8390,7 +8538,7 @@ dependencies = [ "alloy-rpc-types-mev", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde", + "alloy-serde 2.0.4", "jsonrpsee", "reth-chain-state", "reth-engine-primitives", @@ -8403,8 +8551,8 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-network", "alloy-provider", @@ -8422,9 +8570,11 @@ dependencies = [ "reth-metrics", "reth-network-api", "reth-node-core", + "reth-payload-primitives", "reth-primitives-traits", "reth-rpc", "reth-rpc-api", + "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", "reth-rpc-layer", @@ -8444,8 +8594,8 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-evm", @@ -8453,23 +8603,23 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-rpc-types-eth", - "alloy-signer", "auto_impl", "dyn-clone", "jsonrpsee-types", - "reth-ethereum-primitives", "reth-evm", "reth-primitives-traits", + "reth-rpc-traits", "thiserror 2.0.18", ] [[package]] name = "reth-rpc-engine-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "async-trait", "jsonrpsee-core", @@ -8495,12 +8645,13 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eips", + "alloy-eip7928", + "alloy-eips 2.0.4", "alloy-evm", "alloy-json-rpc", "alloy-network", @@ -8508,7 +8659,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "alloy-rpc-types-mev", - "alloy-serde", + "alloy-serde 2.0.4", "async-trait", "auto_impl", "dyn-clone", @@ -8533,17 +8684,18 @@ dependencies = [ "reth-trie-common", "revm", "revm-inspectors", + "serde_json", "tokio", "tracing", ] [[package]] name = "reth-rpc-eth-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-evm", "alloy-network", "alloy-primitives", @@ -8557,8 +8709,8 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "metrics", - "rand 0.9.2", - "reqwest", + "rand 0.9.4", + "reqwest 0.13.3", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -8587,8 +8739,8 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-rpc-types-engine", "http", @@ -8601,10 +8753,10 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "jsonrpsee-core", @@ -8612,24 +8764,40 @@ dependencies = [ "reth-errors", "reth-network-api", "serde", - "strum 0.27.2", + "strum", +] + +[[package]] +name = "reth-rpc-traits" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "860fe223501a76ff14aa3bf164f739f31008c2a2905ac85708bfd88f042e6151" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-signer", + "reth-primitives-traits", + "thiserror 2.0.18", ] [[package]] name = "reth-stages" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", - "bincode", + "alloy-rlp", "eyre", "futures-util", "itertools 0.14.0", "num-traits", + "page_size", "rayon", - "reqwest", + "reqwest 0.13.3", "reth-chainspec", "reth-codecs", "reth-config", @@ -8645,6 +8813,7 @@ dependencies = [ "reth-execution-types", "reth-exex", "reth-fs-util", + "reth-libmdbx", "reth-network-p2p", "reth-primitives-traits", "reth-provider", @@ -8655,6 +8824,7 @@ dependencies = [ "reth-static-file-types", "reth-storage-api", "reth-storage-errors", + "reth-tasks", "reth-testing-utils", "reth-trie", "reth-trie-db", @@ -8666,15 +8836,16 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "aquamarine", "auto_impl", "futures-util", "metrics", + "reth-codecs", "reth-consensus", "reth-errors", "reth-metrics", @@ -8693,8 +8864,8 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "arbitrary", @@ -8707,8 +8878,8 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "parking_lot", @@ -8727,24 +8898,26 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "clap", "derive_more", "fixed-map", + "reth-stages-types", "serde", - "strum 0.27.2", + "strum", + "tracing", ] [[package]] name = "reth-storage-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", @@ -8764,33 +8937,38 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "derive_more", + "reth-codecs", "reth-primitives-traits", "reth-prune-types", "reth-static-file-types", "revm-database-interface", + "revm-state", "thiserror 2.0.18", ] [[package]] name = "reth-tasks" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ - "auto_impl", - "dyn-clone", + "crossbeam-utils", + "dashmap", "futures-util", + "libc", "metrics", + "parking_lot", "pin-project", "rayon", "reth-metrics", "thiserror 2.0.18", + "thread-priority", "tokio", "tracing", "tracing-futures", @@ -8798,15 +8976,15 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-genesis", "alloy-primitives", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "reth-ethereum-primitives", "reth-primitives-traits", "secp256k1 0.30.0", @@ -8814,8 +8992,8 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "tokio", "tokio-stream", @@ -8824,8 +9002,8 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "clap", "eyre", @@ -8841,8 +9019,8 @@ dependencies = [ [[package]] name = "reth-tracing-otlp" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "clap", "eyre", @@ -8858,32 +9036,36 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "aquamarine", "auto_impl", - "bitflags 2.11.0", + "bitflags", "futures-util", + "imbl", "metrics", "parking_lot", "paste", "pin-project", - "rand 0.9.2", + "rand 0.9.4", "reth-chain-state", "reth-chainspec", "reth-eth-wire-types", "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", "reth-execution-types", "reth-fs-util", "reth-metrics", "reth-primitives-traits", "reth-storage-api", "reth-tasks", + "revm", "revm-interpreter", "revm-primitives", "rustc-hash", @@ -8899,11 +9081,11 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", - "alloy-eips", + "alloy-eips 2.0.4", "alloy-primitives", "alloy-rlp", "alloy-trie", @@ -8925,14 +9107,14 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-consensus", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-eth", - "alloy-serde", + "alloy-serde 2.0.4", "alloy-trie", "arbitrary", "arrayvec", @@ -8952,94 +9134,87 @@ dependencies = [ [[package]] name = "reth-trie-db" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", + "metrics", + "parking_lot", "reth-db-api", "reth-execution-errors", + "reth-metrics", "reth-primitives-traits", + "reth-stages-types", "reth-storage-api", "reth-storage-errors", "reth-trie", + "reth-trie-common", "tracing", ] [[package]] name = "reth-trie-parallel" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ + "alloy-eip7928", + "alloy-evm", "alloy-primitives", "alloy-rlp", "crossbeam-channel", - "dashmap 6.1.0", + "crossbeam-utils", "derive_more", "itertools 0.14.0", "metrics", "rayon", "reth-execution-errors", "reth-metrics", + "reth-primitives-traits", "reth-provider", "reth-storage-errors", + "reth-tasks", "reth-trie", - "reth-trie-common", "reth-trie-sparse", + "revm-state", "thiserror 2.0.18", - "tokio", "tracing", ] [[package]] name = "reth-trie-sparse" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.2.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.2.0#88505c7fcbfdebfd3b56d88c86b62e950043c6c4" dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-trie", - "auto_impl", "metrics", "rayon", "reth-execution-errors", "reth-metrics", "reth-primitives-traits", "reth-trie-common", - "smallvec", - "tracing", -] - -[[package]] -name = "reth-trie-sparse-parallel" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" -dependencies = [ - "alloy-primitives", - "alloy-rlp", - "alloy-trie", - "metrics", - "rayon", - "reth-execution-errors", - "reth-metrics", - "reth-trie-common", - "reth-trie-sparse", + "serde", + "serde_json", + "slotmap", "smallvec", "tracing", ] [[package]] name = "reth-zstd-compressors" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c12fafa33d2f420a9d39249a3e0357b1928d09429f30758b85280409092873b2" dependencies = [ "zstd", ] [[package]] name = "revm" -version = "33.1.0" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c85ed0028f043f87b3c88d4a4cb6f0a76440085523b6a8afe5ff003cf418054" +checksum = "91202d39dbe8e8d10e9e8f2b76c30da68ecd1d25be69ba6d853ad0d03a3a398a" dependencies = [ "revm-bytecode", "revm-context", @@ -9056,9 +9231,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "7.1.1" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c6b5e6e8dd1e28a4a60e5f46615d4ef0809111c9e63208e55b5c7058200fb0" +checksum = "bdbb3a3d735efa94c91f2ef6bf20a35f99a77bc78f3e25bd758336901bdf9661" dependencies = [ "bitvec", "phf", @@ -9068,9 +9243,9 @@ dependencies = [ [[package]] name = "revm-context" -version = "12.1.0" +version = "16.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f038f0c9c723393ac897a5df9140b21cfa98f5753a2cb7d0f28fa430c4118abf" +checksum = "c5f68d928d8b228e0faeb1c6ed75c4fde7d124f1ddf9119b67e7a0ad4041237d" dependencies = [ "bitvec", "cfg-if", @@ -9085,9 +9260,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "13.1.0" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431c9a14e4ef1be41ae503708fd02d974f80ef1f2b6b23b5e402e8d854d1b225" +checksum = "1f3758e6167c4ba7a59a689c519a047edaefcd4c37d74f279b93ed87bc8aece4" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -9101,11 +9276,11 @@ dependencies = [ [[package]] name = "revm-database" -version = "9.0.6" +version = "13.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980d8d6bba78c5dd35b83abbb6585b0b902eb25ea4448ed7bfba6283b0337191" +checksum = "c281a1f11d3bcb8c0bba1199ed6bcb001d1aeb3d4fb366819e14f88723989a4e" dependencies = [ - "alloy-eips", + "alloy-eips 1.8.3", "revm-bytecode", "revm-database-interface", "revm-primitives", @@ -9115,22 +9290,23 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "8.0.5" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cce03e3780287b07abe58faf4a7f5d8be7e81321f93ccf3343c8f7755602bae" +checksum = "d89efb9832a4e3742bb4ded5f7fe5bf905e8860e69427d4dfec153484fc6d304" dependencies = [ "auto_impl", "either", "revm-primitives", "revm-state", "serde", + "thiserror 2.0.18", ] [[package]] name = "revm-handler" -version = "14.1.0" +version = "18.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44f8f6dbeec3fecf9fe55f78ef0a758bdd92ea46cd4f1ca6e2a946b32c367f3" +checksum = "783e903d6922b7f5f9a940d1bb229530502d2924b1aed9d5ca5a94ebf065d460" dependencies = [ "auto_impl", "derive-where", @@ -9147,9 +9323,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "14.1.0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5617e49216ce1ca6c8826bcead0386bc84f49359ef67cde6d189961735659f93" +checksum = "8216ad58422090d0daa9eb430e0a081f7ad07e7fd30681dee71f8420c99624e0" dependencies = [ "auto_impl", "either", @@ -9165,9 +9341,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.33.2" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01def7351cd9af844150b8e88980bcd11304f33ce23c3d7c25f2a8dab87c1345" +checksum = "731b682530a732ef9c189ef831589128e2ce34d4a306c956322ae2dffe009715" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -9183,9 +9359,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "31.1.0" +version = "35.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec36405f7477b9dccdc6caa3be19adf5662a7a0dffa6270cdb13a090c077e5" +checksum = "1ece9f41b69658c15d748288a4dbdfc06a63f3ce93d983af440de3f1631dce6a" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -9196,9 +9372,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "31.0.0" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a62958af953cc4043e93b5be9b8497df84cc3bd612b865c49a7a7dfa26a84e2" +checksum = "a346a8cc6c8c39bd65306641c692191299c0a7b63d38810e39e8fe9b92378660" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -9207,23 +9383,24 @@ dependencies = [ "ark-serialize 0.5.0", "arrayref", "aurora-engine-modexp", + "aws-lc-rs", "blst", "c-kzg", "cfg-if", "k256", "p256", + "revm-context-interface", "revm-primitives", "ripemd", - "rug", "secp256k1 0.31.1", "sha2", ] [[package]] name = "revm-primitives" -version = "21.0.2" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e161db429d465c09ba9cbff0df49e31049fe6b549e28eb0b7bd642fcbd4412" +checksum = "0c99bda77d9661521ba0b4bc04558c6692074f01e65dd420fa3b893033d9b8a2" dependencies = [ "alloy-primitives", "num_enum", @@ -9233,11 +9410,12 @@ dependencies = [ [[package]] name = "revm-state" -version = "8.1.1" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8be953b7e374dbdea0773cf360debed8df394ea8d82a8b240a6b5da37592fc" +checksum = "c32490ed687dba31c3c882beb8c20408bdd30ef96690d8f145b0ee9a87040bfe" dependencies = [ - "bitflags 2.11.0", + "alloy-eip7928", + "bitflags", "revm-bytecode", "revm-primitives", "serde", @@ -9263,15 +9441,15 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] [[package]] name = "ringbuffer" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" +checksum = "57b0b88a509053cbfd535726dcaaceee631313cef981266119527a1d110f6d2b" [[package]] name = "ripemd" @@ -9322,14 +9500,24 @@ dependencies = [ [[package]] name = "roaring" -version = "0.10.12" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e8d2cfa184d94d0726d650a9f4a1be7f9b76ac9fdb954219878dc00c1c1e7b" +checksum = "1dedc5658c6ecb3bdb5ef5f3295bb9253f42dcf3fd1402c03f6b1f7659c3c4a9" dependencies = [ "bytemuck", "byteorder", ] +[[package]] +name = "rocksdb" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb7af00d2b17dbd07d82c0063e25411959748ff03e8d4f96134c2ff41fce34f" +dependencies = [ + "libc", + "librocksdb-sys", +] + [[package]] name = "rolling-file" version = "0.2.0" @@ -9345,23 +9533,11 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" -[[package]] -name = "rug" -version = "1.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de190ec858987c79cad4da30e19e546139b3339331282832af004d0ea7829639" -dependencies = [ - "az", - "gmp-mpfr-sys", - "libc", - "libm", -] - [[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", "arbitrary", @@ -9377,8 +9553,8 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "rlp", "ruint-macro", "serde_core", @@ -9394,11 +9570,11 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" dependencies = [ - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -9422,20 +9598,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.27", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "semver 1.0.28", ] [[package]] @@ -9444,19 +9607,20 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -9480,9 +9644,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", @@ -9496,7 +9660,7 @@ checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ "core-foundation", "core-foundation-sys", - "jni", + "jni 0.21.1", "log", "once_cell", "rustls", @@ -9509,6 +9673,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs 1.0.7", + "windows-sys 0.52.0", +] + [[package]] name = "rustls-platform-verifier-android" version = "0.1.1" @@ -9521,9 +9706,10 @@ version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -9550,6 +9736,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -9603,6 +9798,12 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -9631,7 +9832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", + "rand 0.8.6", "secp256k1-sys 0.10.1", "serde", ] @@ -9643,7 +9844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.2", + "rand 0.9.4", "secp256k1-sys 0.11.0", ] @@ -9671,7 +9872,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -9699,9 +9900,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -9764,7 +9965,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -9774,11 +9975,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.9" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -9795,15 +9996,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -9814,11 +10015,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -9841,7 +10042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -9852,15 +10053,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] [[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", @@ -9868,9 +10069,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" +checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" dependencies = [ "cc", "cfg-if", @@ -9885,15 +10086,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shellexpand" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" -dependencies = [ - "dirs", -] - [[package]] name = "shlex" version = "1.3.0" @@ -9943,9 +10135,25 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple_asn1" @@ -9961,24 +10169,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[package]] -name = "skeptic" -version = "0.13.7" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" -dependencies = [ - "bytecount", - "cargo_metadata 0.14.2", - "error-chain", - "glob", - "pulldown-cmark", - "tempfile", - "walkdir", -] +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "sketches-ddsketch" @@ -9992,6 +10185,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -10008,16 +10210,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.3" @@ -10025,7 +10217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -10040,7 +10232,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand 0.8.6", "sha1", ] @@ -10072,35 +10264,13 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros 0.26.4", -] - [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros 0.27.2", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.117", + "strum_macros", ] [[package]] @@ -10121,6 +10291,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" @@ -10177,15 +10353,16 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.33.1" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", - "windows 0.57.0", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.62.2", ] [[package]] @@ -10220,8 +10397,8 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix 1.1.4", - "windows-sys 0.61.2", + "rustix", + "windows-sys 0.52.0", ] [[package]] @@ -10264,6 +10441,20 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "thread-priority" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210811179577da3d54eb69ab0b50490ee40491a25d95b8c6011ba40771cb721" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.61.3", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -10282,6 +10473,37 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tikv-jemalloc-ctl" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "661f1f6a57b3a36dc9174a2c10f19513b4866816e13425d3e418b11cc37bc24c" +dependencies = [ + "libc", + "paste", + "tikv-jemalloc-sys", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" version = "0.3.47" @@ -10317,9 +10539,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -10327,9 +10549,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -10342,9 +10564,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -10352,16 +10574,16 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -10392,9 +10614,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", @@ -10424,80 +10646,69 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.23" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "serde", + "indexmap 2.14.0", + "serde_core", "serde_spanned", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", ] [[package]] name = "toml_datetime" -version = "0.6.11" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.13.0", - "serde", - "serde_spanned", - "toml_datetime 0.6.11", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.0.0+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.2", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow", + "winnow 1.0.2", ] [[package]] -name = "toml_write" -version = "0.1.2" +name = "toml_writer" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tonic" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "base64 0.22.1", @@ -10521,9 +10732,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost", @@ -10539,7 +10750,7 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap 2.13.0", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -10552,13 +10763,13 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" dependencies = [ "async-compression", "base64 0.22.1", - "bitflags 2.11.0", + "bitflags", "bytes", "futures-core", "futures-util", @@ -10567,7 +10778,6 @@ dependencies = [ "http-body-util", "http-range-header", "httpdate", - "iri-string", "mime", "mime_guess", "percent-encoding", @@ -10578,6 +10788,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "url", "uuid", ] @@ -10607,11 +10818,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", @@ -10746,17 +10958,19 @@ dependencies = [ "serde", "serde_json", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", + "tracing-log", "tracing-serde", ] [[package]] name = "tree_hash" -version = "0.10.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" +checksum = "f7fd51aa83d2eb83b04570808430808b5d24fdbf479a4d5ac5dee4a2e2dd2be4" dependencies = [ "alloy-primitives", "ethereum_hashing", @@ -10767,11 +10981,11 @@ dependencies = [ [[package]] name = "tree_hash_derive" -version = "0.10.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bee2ea1551f90040ab0e34b6fb7f2fa3bad8acc925837ac654f2c78a13e3089" +checksum = "8840ad4d852e325d3afa7fde8a50b2412f89dce47d7eb291c0cc7f87cd040f38" dependencies = [ - "darling 0.20.11", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -10787,12 +11001,6 @@ dependencies = [ "rlp", ] -[[package]] -name = "triomphe" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" - [[package]] name = "try-lock" version = "0.2.5" @@ -10801,16 +11009,16 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", "http", "httparse", "log", - "rand 0.9.2", + "rand 0.9.4", "rustls", "rustls-pki-types", "sha1", @@ -10818,11 +11026,17 @@ dependencies = [ "utf-8", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[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" @@ -10874,32 +11088,26 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-truncate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ - "itertools 0.13.0", + "itertools 0.14.0", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width", ] [[package]] name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -10923,6 +11131,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -10962,9 +11176,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -10990,7 +11204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" dependencies = [ "anyhow", - "cargo_metadata 0.23.1", + "cargo_metadata", "derive_builder", "regex", "rustversion", @@ -11077,11 +11291,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]] @@ -11090,14 +11304,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.114" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -11108,23 +11322,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11132,9 +11342,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -11145,9 +11355,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -11169,16 +11379,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -11193,10 +11403,10 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags", "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver 1.0.27", + "indexmap 2.14.0", + "semver 1.0.28", ] [[package]] @@ -11215,9 +11425,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", @@ -11239,14 +11449,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.6", + "webpki-root-certs 1.0.7", ] [[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", ] @@ -11257,18 +11467,28 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.6", + "webpki-roots 1.0.7", ] [[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", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.2.1" @@ -11297,7 +11517,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -11308,12 +11528,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.57.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", ] [[package]] @@ -11322,10 +11545,19 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections", + "windows-collections 0.3.2", "windows-core 0.62.2", - "windows-future", - "windows-numerics", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -11339,14 +11571,15 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.57.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -11355,33 +11588,33 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", - "windows-link", + "windows-implement", + "windows-interface", + "windows-link 0.2.1", "windows-result 0.4.1", - "windows-strings", + "windows-strings 0.5.1", ] [[package]] name = "windows-future" -version = "0.3.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.62.2", - "windows-link", - "windows-threading", + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", ] [[package]] -name = "windows-implement" -version = "0.57.0" +name = "windows-future" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -11397,9 +11630,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.57.0" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -11407,15 +11640,10 @@ dependencies = [ ] [[package]] -name = "windows-interface" -version = "0.59.3" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" @@ -11423,6 +11651,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + [[package]] name = "windows-numerics" version = "0.3.1" @@ -11430,16 +11668,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core 0.62.2", - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] name = "windows-result" -version = "0.1.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link 0.1.3", ] [[package]] @@ -11448,34 +11697,34 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] name = "windows-strings" -version = "0.5.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.42.2", + "windows-link 0.2.1", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.42.2", ] [[package]] @@ -11511,7 +11760,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -11529,21 +11778,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -11566,7 +11800,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -11577,13 +11811,22 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -11592,12 +11835,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -11616,12 +11853,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -11640,12 +11871,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -11676,12 +11901,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -11700,12 +11919,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -11724,12 +11937,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -11748,12 +11955,6 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -11776,13 +11977,12 @@ dependencies = [ ] [[package]] -name = "winreg" -version = "0.50.0" +name = "winnow" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "memchr", ] [[package]] @@ -11794,6 +11994,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" @@ -11813,7 +12019,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -11843,8 +12049,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -11863,9 +12069,9 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", - "semver 1.0.27", + "semver 1.0.28", "serde", "serde_derive", "serde_json", @@ -11875,9 +12081,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "ws_stream_wasm" @@ -11914,7 +12120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.4", + "rustix", ] [[package]] @@ -11925,9 +12131,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -11936,9 +12142,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -11948,18 +12154,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -11968,18 +12174,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -12009,9 +12215,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -12020,9 +12226,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -12031,9 +12237,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 58b07be6..539b1cb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace.package] version = "0.3.0" edition = "2024" -rust-version = "1.88" +rust-version = "1.95" license = "MIT OR Apache-2.0" publish = false @@ -12,6 +12,7 @@ members = [ "crates/chainspec", "crates/consensus", "crates/engine-api", + "crates/engine-tree-ext", "crates/evm", "crates/node", "crates/rpc", @@ -88,6 +89,7 @@ codegen-units = 1 morph-chainspec = { path = "crates/chainspec", default-features = false } morph-consensus = { path = "crates/consensus", default-features = false } morph-engine-api = { path = "crates/engine-api", default-features = false } +morph-engine-tree-ext = { path = "crates/engine-tree-ext", default-features = false } morph-evm = { path = "crates/evm", default-features = false } morph-node = { path = "crates/node"} morph-payload-builder = { path = "crates/payload/builder", default-features = false } @@ -98,89 +100,96 @@ morph-rpc = { path = "crates/rpc" } morph-revm = { path = "crates/revm", default-features = false } morph-txpool = { path = "crates/txpool", default-features = false } -reth-basic-payload-builder = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-chain-state = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-chainspec = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-cli = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-cli-commands = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-cli-util = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-codecs = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-codecs-derive = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-consensus = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-consensus-common = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-db = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-db-api = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-e2e-test-utils = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-engine-local = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-engine-primitives = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-engine-tree = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-errors = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-eth-wire-types = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-exex = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-exex-test-utils = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-exex-types = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-ethereum = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-ethereum-cli = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-ethereum-consensus = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-ethereum-engine-primitives = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-ethereum-primitives = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329", default-features = false } -reth-evm = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-evm-ethereum = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-execution-types = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-metrics = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-network-peers = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-node-api = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-node-builder = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-node-core = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-node-ethereum = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-node-metrics = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-payload-builder = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-payload-primitives = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-payload-util = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-primitives-traits = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329", default-features = false } -reth-provider = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-rpc = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-rpc-api = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-rpc-builder = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-rpc-convert = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-rpc-eth-api = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-rpc-eth-types = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-rpc-server-types = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-storage-api = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-tasks = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-tracing = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-trie = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-transaction-pool = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329" } -reth-zstd-compressors = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329", default-features = false } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-chain-state = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-cli-util = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-codecs = { version = "0.3.1", default-features = false } +reth-codecs-derive = "0.3.1" +reth-consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-db-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-e2e-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-engine-local = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-engine-tree = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-eth-wire-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-exex = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-exex-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-exex-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-ethereum-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-ethereum-engine-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0", default-features = false } +reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-metrics = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-node-core = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-node-metrics = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-payload-util = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-primitives-traits = { version = "0.3.1", default-features = false } +reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-tracing = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-trie = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-zstd-compressors = { version = "0.3.1", default-features = false } +reth-execution-cache = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-trie-db = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-trie-parallel = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0" } +reth-trie-sparse = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0", default-features = false } -reth-revm = { git = "https://github.com/morph-l2/reth", rev = "1b0702546633c259306017717b2938f14adfe329", features = [ +reth-revm = { git = "https://github.com/paradigmxyz/reth", tag = "v2.2.0", features = [ "std", "optional-checks", ] } -revm = { version = "33.1.0", features = [ +revm = { version = "38.0.0", features = [ "optional_fee_charge", "optional_eip7623", ], default-features = false } -alloy = { version = "1.4.3", default-features = false } -alloy-consensus = { version = "1.4.3", default-features = false } -alloy-contract = { version = "1.4.3", default-features = false } -alloy-eips = { version = "1.4.3", default-features = false } -alloy-evm = "0.25.1" -alloy-genesis = "1.4.3" -alloy-hardforks = "0.4.5" -alloy-network = { version = "1.4.3", default-features = false } -alloy-primitives = { version = "1.5.0", default-features = false } -alloy-provider = { version = "1.4.3", default-features = false } -alloy-rlp = "0.3.10" -alloy-rpc-types-engine = "1.4.3" -alloy-rpc-types-eth = { version = "1.4.3" } -alloy-serde = "1.4.3" -alloy-signer = "1.4.3" -alloy-signer-local = "1.4.3" -alloy-sol-types = "1.5.0" -alloy-transport = "1.4.3" -alloy-chains = { version = "0.2.5", default-features = false } +alloy = { version = "2.0.4", default-features = false } +alloy-consensus = { version = "2.0.4", default-features = false } +alloy-contract = { version = "2.0.4", default-features = false } +alloy-eip7928 = { version = "0.3.4", default-features = false } +alloy-eips = { version = "2.0.4", default-features = false } +alloy-evm = { version = "0.34.0", default-features = false } +alloy-genesis = "2.0.4" +alloy-hardforks = "0.4.7" +alloy-network = { version = "2.0.4", default-features = false } +alloy-primitives = { version = "1.5.6", default-features = false } +alloy-provider = { version = "2.0.4", default-features = false } +alloy-rlp = "0.3.13" +alloy-rpc-types-engine = "2.0.4" +alloy-rpc-types-eth = { version = "2.0.4" } +alloy-serde = "2.0.4" +alloy-signer = "2.0.4" +alloy-signer-local = "2.0.4" +alloy-sol-types = { version = "1.5.6", default-features = false } +alloy-transport = "2.0.4" +alloy-chains = { version = "0.2.33", default-features = false } +crossbeam-channel = "0.5.13" +revm-primitives = { version = "23.0.0", default-features = false } arbitrary = { version = "1.3", features = ["derive"] } async-lock = "3.4.1" async-trait = "0.1" diff --git a/README.md b/README.md index b0a216fc..a2239452 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ morph-reth/ ### Prerequisites -- Rust 1.88 or later +- Rust 1.95 or later - Cargo ### Building from Source diff --git a/bin/morph-reth/Cargo.toml b/bin/morph-reth/Cargo.toml index cf187412..dce6ce87 100644 --- a/bin/morph-reth/Cargo.toml +++ b/bin/morph-reth/Cargo.toml @@ -34,3 +34,16 @@ reth-rpc-server-types.workspace = true clap.workspace = true eyre.workspace = true tracing.workspace = true + +[features] +default = ["jemalloc"] + +jemalloc = [ + "reth-cli-util/jemalloc", + "reth-ethereum-cli/jemalloc", +] +jemalloc-prof = [ + "jemalloc", + "reth-cli-util/jemalloc-prof", + "reth-ethereum-cli/jemalloc-prof", +] diff --git a/bin/morph-reth/src/main.rs b/bin/morph-reth/src/main.rs index b99e4184..31b8cf7f 100644 --- a/bin/morph-reth/src/main.rs +++ b/bin/morph-reth/src/main.rs @@ -3,6 +3,18 @@ //! This is the main entry point for the Morph L2 execution layer client. //! It extends reth with Morph-specific functionality. +#[global_allocator] +static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); + +// Required for `override_allocator_on_supported_platforms` — ensures the linker +// pulls in tikv_jemalloc_sys symbols so jemalloc takes over malloc/free. +#[cfg(all(feature = "jemalloc", unix))] +use reth_cli_util::allocator::tikv_jemalloc_sys as _; + +#[cfg(all(feature = "jemalloc-prof", unix))] +#[unsafe(export_name = "malloc_conf")] +static MALLOC_CONF: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0"; + use clap::Parser; use morph_chainspec::{MorphChainSpec, MorphChainSpecParser}; use morph_consensus::MorphConsensus; diff --git a/crates/chainspec/src/hardfork.rs b/crates/chainspec/src/hardfork.rs index ee2745f9..42d75185 100644 --- a/crates/chainspec/src/hardfork.rs +++ b/crates/chainspec/src/hardfork.rs @@ -202,84 +202,7 @@ impl From for MorphHardfork { #[cfg(test)] mod tests { use super::*; - use reth_chainspec::Hardfork; - - #[test] - fn test_morph203_hardfork_name() { - let fork = MorphHardfork::Morph203; - assert_eq!(fork.name(), "Morph203"); - } - - #[test] - fn test_hardfork_trait_implementation() { - let fork = MorphHardfork::Morph203; - // Should implement Hardfork trait - let _name: &str = Hardfork::name(&fork); - } - - #[test] - #[cfg(feature = "serde")] - fn test_morph_hardfork_serde() { - let fork = MorphHardfork::Morph203; - - // Serialize to JSON - let json = serde_json::to_string(&fork).unwrap(); - assert_eq!(json, "\"Morph203\""); - - // Deserialize from JSON - let deserialized: MorphHardfork = serde_json::from_str(&json).unwrap(); - assert_eq!(deserialized, fork); - } - - #[test] - fn test_is_curie() { - assert!(!MorphHardfork::Bernoulli.is_curie()); - assert!(MorphHardfork::Curie.is_curie()); - assert!(MorphHardfork::Morph203.is_curie()); - assert!(MorphHardfork::Viridian.is_curie()); - assert!(MorphHardfork::Emerald.is_curie()); - assert!(MorphHardfork::Jade.is_curie()); - } - - #[test] - fn test_is_morph203() { - assert!(!MorphHardfork::Bernoulli.is_morph203()); - assert!(!MorphHardfork::Curie.is_morph203()); - assert!(MorphHardfork::Morph203.is_morph203()); - assert!(MorphHardfork::Viridian.is_morph203()); - assert!(MorphHardfork::Emerald.is_morph203()); - assert!(MorphHardfork::Jade.is_morph203()); - } - - #[test] - fn test_is_viridian() { - assert!(!MorphHardfork::Bernoulli.is_viridian()); - assert!(!MorphHardfork::Curie.is_viridian()); - assert!(!MorphHardfork::Morph203.is_viridian()); - assert!(MorphHardfork::Viridian.is_viridian()); - assert!(MorphHardfork::Emerald.is_viridian()); - assert!(MorphHardfork::Jade.is_viridian()); - } - - #[test] - fn test_is_emerald() { - assert!(!MorphHardfork::Bernoulli.is_emerald()); - assert!(!MorphHardfork::Curie.is_emerald()); - assert!(!MorphHardfork::Morph203.is_emerald()); - assert!(!MorphHardfork::Viridian.is_emerald()); - assert!(MorphHardfork::Emerald.is_emerald()); - assert!(MorphHardfork::Jade.is_emerald()); - } - - #[test] - fn test_is_jade() { - assert!(!MorphHardfork::Bernoulli.is_jade()); - assert!(!MorphHardfork::Curie.is_jade()); - assert!(!MorphHardfork::Morph203.is_jade()); - assert!(!MorphHardfork::Viridian.is_jade()); - assert!(!MorphHardfork::Emerald.is_jade()); - assert!(MorphHardfork::Jade.is_jade()); - } + use alloy_evm::revm::context_interface::cfg::gas_params::GasParams; #[test] fn test_morph_hardfork_to_specid_mapping() { @@ -291,6 +214,51 @@ mod tests { assert_eq!(SpecId::from(MorphHardfork::Jade), SpecId::OSAKA); } + #[test] + fn test_morph_hardforks_do_not_enable_amsterdam_state_gas() { + let forks = [ + MorphHardfork::Bernoulli, + MorphHardfork::Curie, + MorphHardfork::Morph203, + MorphHardfork::Viridian, + MorphHardfork::Emerald, + MorphHardfork::Jade, + ]; + + for fork in forks { + let spec = SpecId::from(fork); + assert!( + spec < SpecId::AMSTERDAM, + "MorphHardfork {fork:?} maps to SpecId {spec:?}, which would enable Amsterdam-era gas semantics" + ); + + let params = GasParams::new_spec(spec); + assert_eq!( + params.tx_eip7702_per_auth_state_gas(), + 0, + "MorphHardfork {fork:?} must not enable EIP-8037 state gas" + ); + } + } + + #[test] + fn test_eip7702_refund_stays_regular_for_morph_specs() { + for spec in [SpecId::CANCUN, SpecId::PRAGUE, SpecId::OSAKA] { + let params = GasParams::new_spec(spec); + let total_refund = 25_000; + let (state_refund, regular_refund) = params.split_eip7702_refund(total_refund); + + assert_eq!( + state_refund, 0, + "spec={spec:?}: Morph must not route EIP-7702 refunds to state gas" + ); + assert_eq!( + regular_refund, total_refund, + "spec={spec:?}: Morph EIP-7702 refunds must remain subject to the regular refund cap" + ); + } + } + #[test] fn test_specid_to_morph_hardfork_mapping() { assert_eq!(MorphHardfork::from(SpecId::CANCUN), MorphHardfork::Morph203); @@ -298,16 +266,6 @@ mod tests { assert_eq!(MorphHardfork::from(SpecId::OSAKA), MorphHardfork::Jade); } - #[test] - fn test_is_bernoulli() { - assert!(MorphHardfork::Bernoulli.is_bernoulli()); - assert!(MorphHardfork::Curie.is_bernoulli()); - assert!(MorphHardfork::Morph203.is_bernoulli()); - assert!(MorphHardfork::Viridian.is_bernoulli()); - assert!(MorphHardfork::Emerald.is_bernoulli()); - assert!(MorphHardfork::Jade.is_bernoulli()); - } - /// SpecIds below CANCUN should map to Morph203 (the latest CANCUN-level hardfork). #[test] fn test_specid_below_cancun_maps_to_morph203() { @@ -333,18 +291,4 @@ mod tests { let spec = SpecId::from(MorphHardfork::Emerald); assert_eq!(MorphHardfork::from(spec), MorphHardfork::Jade); } - - #[test] - fn test_default_hardfork_is_jade() { - assert_eq!(MorphHardfork::default(), MorphHardfork::Jade); - } - - #[test] - fn test_hardfork_ordering() { - assert!(MorphHardfork::Bernoulli < MorphHardfork::Curie); - assert!(MorphHardfork::Curie < MorphHardfork::Morph203); - assert!(MorphHardfork::Morph203 < MorphHardfork::Viridian); - assert!(MorphHardfork::Viridian < MorphHardfork::Emerald); - assert!(MorphHardfork::Emerald < MorphHardfork::Jade); - } } diff --git a/crates/consensus/src/validation.rs b/crates/consensus/src/validation.rs index e2c7803e..53e13f8f 100644 --- a/crates/consensus/src/validation.rs +++ b/crates/consensus/src/validation.rs @@ -43,7 +43,7 @@ use morph_primitives::{ Block, BlockBody, MorphHeader, MorphReceipt, MorphTxEnvelope, transaction::morph_transaction::MORPH_TX_VERSION_1, }; -use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator}; +use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator, ReceiptRootBloom}; use reth_consensus_common::validation::{ validate_against_parent_hash_number, validate_body_against_header, }; @@ -159,9 +159,9 @@ impl HeaderValidator for MorphConsensus { if self.chain_spec.is_fee_vault_enabled() && header.beneficiary() != alloy_primitives::Address::ZERO { - return Err(ConsensusError::Other( - MorphConsensusError::InvalidCoinbase(header.beneficiary()).to_string(), - )); + return Err(ConsensusError::other(MorphConsensusError::InvalidCoinbase( + header.beneficiary(), + ))); } // Check timestamp is not in the future @@ -197,8 +197,8 @@ impl HeaderValidator for MorphConsensus { .base_fee_per_gas() .ok_or(ConsensusError::BaseFeeMissing)?; if base_fee > MORPH_MAXIMUM_BASE_FEE { - return Err(ConsensusError::Other( - MorphConsensusError::BaseFeeOverLimit(base_fee).to_string(), + return Err(ConsensusError::other( + MorphConsensusError::BaseFeeOverLimit(base_fee), )); } Ok(()) @@ -233,12 +233,11 @@ impl HeaderValidator for MorphConsensus { // decrease across blocks. This is the header-only half of L1 message // validation; the body-level half is in validate_block_pre_execution. if header.next_l1_msg_index < parent.next_l1_msg_index { - return Err(ConsensusError::Other( + return Err(ConsensusError::other( MorphConsensusError::InvalidNextL1MessageIndex { expected: parent.next_l1_msg_index, actual: header.next_l1_msg_index, - } - .to_string(), + }, )); } @@ -278,7 +277,7 @@ impl Consensus for MorphConsensus { // Check no uncles allowed (Morph L2 has no uncle blocks) let ommers_len = block.body().ommers().map(|o| o.len()).unwrap_or_default(); if ommers_len > 0 { - return Err(ConsensusError::Other("uncles not allowed".to_string())); + return Err(ConsensusError::msg("uncles not allowed")); } // Check ommers hash must be empty root hash @@ -299,8 +298,8 @@ impl Consensus for MorphConsensus { // Check withdrawals are empty if block.body().withdrawals().is_some() { - return Err(ConsensusError::Other( - MorphConsensusError::WithdrawalsNonEmpty.to_string(), + return Err(ConsensusError::other( + MorphConsensusError::WithdrawalsNonEmpty, )); } @@ -346,6 +345,7 @@ impl FullConsensus for MorphConsensus { &self, block: &RecoveredBlock, result: &BlockExecutionResult, + receipt_root_bloom: Option, ) -> Result<(), ConsensusError> { // Verify the block gas used let cumulative_gas_used = result @@ -366,8 +366,19 @@ impl FullConsensus for MorphConsensus { }); } - // Verify the receipts logs bloom and root - verify_receipts(block.receipts_root(), block.logs_bloom(), &result.receipts)?; + // Verify the receipts logs bloom and root. + // Use pre-computed (root, bloom) from the executor when available to avoid + // redundant hashing; fall back to computing from receipts otherwise. + if let Some((receipts_root, logs_bloom)) = receipt_root_bloom { + verify_receipts_precomputed( + block.receipts_root(), + block.logs_bloom(), + receipts_root, + logs_bloom, + )?; + } else { + verify_receipts(block.receipts_root(), block.logs_bloom(), &result.receipts)?; + } Ok(()) } @@ -499,34 +510,30 @@ fn validate_l1_messages_in_block( if tx.is_l1_msg() { // Check L1 messages are only at the start of the block (before any L2 tx) if saw_l2_transaction { - return Err(ConsensusError::Other( - MorphConsensusError::InvalidL1MessageOrder.to_string(), + return Err(ConsensusError::other( + MorphConsensusError::InvalidL1MessageOrder, )); } - let tx_queue_index = tx.queue_index().ok_or_else(|| { - ConsensusError::Other(MorphConsensusError::MalformedL1Message.to_string()) - })?; + let tx_queue_index = tx + .queue_index() + .ok_or_else(|| ConsensusError::other(MorphConsensusError::MalformedL1Message))?; // Check queue indices are strictly sequential (each = previous + 1). // Use checked_add to prevent overflow at u64::MAX. if let Some(prev) = prev_queue_index { let expected = prev.checked_add(1).ok_or_else(|| { - ConsensusError::Other( - MorphConsensusError::L1MessagesNotInOrder { - expected: u64::MAX, - actual: tx_queue_index, - } - .to_string(), - ) + ConsensusError::other(MorphConsensusError::L1MessagesNotInOrder { + expected: u64::MAX, + actual: tx_queue_index, + }) })?; if tx_queue_index != expected { - return Err(ConsensusError::Other( + return Err(ConsensusError::other( MorphConsensusError::L1MessagesNotInOrder { expected, actual: tx_queue_index, - } - .to_string(), + }, )); } } @@ -549,26 +556,20 @@ fn validate_l1_messages_in_block( // monotonicity check in validate_header_against_parent handles that case. if l1_msg_count > 0 { let last_queue_index = prev_queue_index.ok_or_else(|| { - ConsensusError::Other( - "internal error: l1_msg_count > 0 but prev_queue_index is None".to_string(), - ) + ConsensusError::msg("internal error: l1_msg_count > 0 but prev_queue_index is None") })?; let min_expected = last_queue_index.checked_add(1).ok_or_else(|| { - ConsensusError::Other( - MorphConsensusError::InvalidNextL1MessageIndex { - expected: u64::MAX, - actual: header_next_l1_msg_index, - } - .to_string(), - ) + ConsensusError::other(MorphConsensusError::InvalidNextL1MessageIndex { + expected: u64::MAX, + actual: header_next_l1_msg_index, + }) })?; if header_next_l1_msg_index < min_expected { - return Err(ConsensusError::Other( + return Err(ConsensusError::other( MorphConsensusError::InvalidNextL1MessageIndex { expected: min_expected, actual: header_next_l1_msg_index, - } - .to_string(), + }, )); } } @@ -593,20 +594,17 @@ fn validate_morph_txs(txs: &[MorphTxEnvelope], is_jade: bool) -> Result<(), Cons // Reject MorphTx V1 before Jade fork (hardfork-gated, consensus-only check). if !is_jade && morph_tx.version == MORPH_TX_VERSION_1 { - return Err(ConsensusError::Other( - MorphConsensusError::InvalidBody( - "MorphTx version 1 is not yet active (jade fork not reached)".into(), - ) - .to_string(), - )); + return Err(ConsensusError::other(MorphConsensusError::InvalidBody( + "MorphTx version 1 is not yet active (jade fork not reached)".into(), + ))); } // Reuse primitive-layer validation (version, fee_token_id, reference, // memo length, fee_limit constraints, gas price ordering). if let Err(reason) = morph_tx.validate() { - return Err(ConsensusError::Other( - MorphConsensusError::InvalidBody(reason.to_string()).to_string(), - )); + return Err(ConsensusError::other(MorphConsensusError::InvalidBody( + reason.to_string(), + ))); } } @@ -624,6 +622,33 @@ fn validate_morph_txs(txs: &[MorphTxEnvelope], is_jade: bool) -> Result<(), Cons /// 2. Calculates the logs bloom by combining all receipt blooms /// 3. Compares both against the expected values from the block header #[inline] +fn verify_receipts_precomputed( + expected_receipts_root: B256, + expected_logs_bloom: Bloom, + receipts_root: B256, + logs_bloom: Bloom, +) -> Result<(), ConsensusError> { + if receipts_root != expected_receipts_root { + return Err(ConsensusError::BodyReceiptRootDiff( + GotExpected { + got: receipts_root, + expected: expected_receipts_root, + } + .into(), + )); + } + if logs_bloom != expected_logs_bloom { + return Err(ConsensusError::BodyBloomLogDiff( + GotExpected { + got: logs_bloom, + expected: expected_logs_bloom, + } + .into(), + )); + } + Ok(()) +} + fn verify_receipts( expected_receipts_root: B256, expected_logs_bloom: Bloom, @@ -1891,7 +1916,7 @@ mod tests { let recovered = reth_primitives_traits::RecoveredBlock::new_unhashed(block, vec![Address::ZERO]); - let post_result = consensus.validate_block_post_execution(&recovered, &result); + let post_result = consensus.validate_block_post_execution(&recovered, &result, None); assert!(matches!( post_result, Err(ConsensusError::BlockGasUsed { .. }) diff --git a/crates/engine-api/Cargo.toml b/crates/engine-api/Cargo.toml index d47aaf47..e52b121d 100644 --- a/crates/engine-api/Cargo.toml +++ b/crates/engine-api/Cargo.toml @@ -20,7 +20,6 @@ morph-primitives = { workspace = true, features = ["reth-codec"] } reth-metrics.workspace = true reth-node-api.workspace = true reth-payload-builder.workspace = true -reth-payload-primitives.workspace = true reth-primitives-traits.workspace = true reth-provider.workspace = true reth-rpc-api.workspace = true diff --git a/crates/engine-api/src/builder.rs b/crates/engine-api/src/builder.rs index 9e40c84d..a07dfe48 100644 --- a/crates/engine-api/src/builder.rs +++ b/crates/engine-api/src/builder.rs @@ -10,21 +10,20 @@ use alloy_consensus::{ }; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{Address, B64, B256, Sealable}; -use alloy_rpc_types_engine::PayloadAttributes; +use alloy_rpc_types_engine::{PayloadAttributes, PayloadStatus, PayloadStatusEnum}; use morph_chainspec::MorphChainSpec; use morph_payload_types::{ AssembleL2BlockParams, ExecutableL2Data, GenericResponse, MorphBuiltPayload, - MorphExecutionData, MorphPayloadBuilderAttributes, MorphPayloadTypes, SafeL2Data, + MorphExecutionData, MorphPayloadTypes, SafeL2Data, }; -use morph_primitives::{Block, BlockBody, MorphHeader, MorphPrimitives, MorphTxEnvelope}; +use morph_primitives::{Block, BlockBody, MorphHeader, MorphTxEnvelope}; use parking_lot::RwLock; -use reth_payload_builder::PayloadBuilderHandle; -use reth_payload_primitives::{EngineApiMessageVersion, PayloadBuilderAttributes}; +use reth_payload_builder::{BuildNewPayload, PayloadBuilderHandle}; #[cfg(test)] use reth_primitives_traits::RecoveredBlock; -use reth_primitives_traits::{SealedBlock, SealedHeader}; +use reth_primitives_traits::{FastInstant as Instant, SealedBlock, SealedHeader}; use reth_provider::{BlockIdReader, BlockNumReader, CanonChainTracker, HeaderProvider}; -use std::{sync::Arc, time::Instant}; +use std::sync::Arc; // ============================================================================= // Real Implementation @@ -48,16 +47,15 @@ pub struct RealMorphL2EngineApi { /// Handle to the running reth engine tree pipeline. engine_handle: reth_node_api::ConsensusEngineHandle, - /// Engine-state tracker updated from consensus engine events (authoritative) and local FCU - /// success hints (fast path). - engine_state_tracker: Arc, + /// Tracks L1-derived safe/finalized block tags for FCU updates. + block_tag_tracker: Arc, /// Prometheus metrics for custom Morph L2 Engine API endpoints and chain head health. metrics: MorphEngineApiMetrics, } #[derive(Debug, Clone, Copy, PartialEq)] -struct InMemoryHead { +struct CanonicalHead { number: u64, hash: B256, timestamp: u64, @@ -69,17 +67,11 @@ struct InMemoryHead { /// wait for Morph node's real `set_block_tags` updates instead. const FCU_TAG_FALLBACK_MAX_AGE_SECS: u64 = 60; -/// Tracks engine-visible canonical head for the custom Morph engine API. -/// -/// Updated from `CanonicalChainCommitted` consensus engine events and optimistically -/// on successful local FCU calls to reduce latency before event delivery. -/// -/// Also caches L1-based safe/finalized block hashes from `set_block_tags` so that -/// the FCU can pass them to the engine tree, keeping both memory cleanup and +/// Tracks L1-based safe/finalized block hashes from `set_block_tags` so that +/// FCU calls can pass them to the engine tree, keeping both memory cleanup and /// RPC-visible tags consistent. #[derive(Debug, Default)] -pub struct EngineStateTracker { - head: RwLock>, +pub struct BlockTagTracker { /// Last L1-based safe/finalized hashes from `set_block_tags`. /// `None` means `set_block_tags` has not yet provided a value (e.g. during /// historical sync when all batches are already finalized on L1). @@ -93,32 +85,7 @@ struct BlockTagCache { finalized_hash: Option, } -impl EngineStateTracker { - /// Records a canonical head hint from a locally successful FCU call. - pub fn record_local_head(&self, number: u64, hash: B256, timestamp: u64) { - *self.head.write() = Some(InMemoryHead { - number, - hash, - timestamp, - }); - } - - /// Updates the tracker from a consensus engine event stream item. - pub fn on_consensus_engine_event( - &self, - event: &reth_node_api::ConsensusEngineEvent, - ) { - use reth_node_api::ConsensusEngineEvent; - - if let ConsensusEngineEvent::CanonicalChainCommitted(header, _) = event { - self.record_local_head(header.number(), header.hash(), header.timestamp()); - } - } - - fn current_head(&self) -> Option { - *self.head.read() - } - +impl BlockTagTracker { /// Caches L1-based block tag hashes from a successful `set_block_tags` call. pub fn record_block_tags(&self, safe_hash: Option, finalized_hash: Option) { let mut tags = self.block_tags.write(); @@ -148,14 +115,14 @@ impl RealMorphL2EngineApi { payload_builder: PayloadBuilderHandle, chain_spec: Arc, engine_handle: reth_node_api::ConsensusEngineHandle, - engine_state_tracker: Arc, + block_tag_tracker: Arc, ) -> Self { Self { provider, payload_builder, chain_spec, engine_handle, - engine_state_tracker, + block_tag_tracker, metrics: MorphEngineApiMetrics::default(), } } @@ -318,11 +285,7 @@ where "validate_l2_block returned engine payload status" ); - let success = matches!( - status.status, - alloy_rpc_types_engine::PayloadStatusEnum::Valid - | alloy_rpc_types_engine::PayloadStatusEnum::Accepted - ); + let success = payload_status_is_validated(&status); tracing::info!( target: "morph::engine", block_number = data.number, @@ -540,22 +503,31 @@ where // set_safe on the provider directly, skipping zero hashes. This avoids a full // FCU round-trip through the async engine pipeline for what is purely a tag // update, and correctly skips the update when the caller passes B256::ZERO. - if finalized_block_hash != B256::ZERO { - self.update_block_tag(finalized_block_hash, "finalized", |sealed| { - self.provider.set_finalized(sealed); - })?; - } - + // + // Order matters: set safe FIRST, then finalized. The Ethereum invariant + // `finalized.number <= safe.number` must hold at every observable point + // for an RPC reader. Updating finalized first and then safe leaves a + // window between the two writes where `eth_getBlockByNumber("finalized")` + // returns the new value but `eth_getBlockByNumber("safe")` returns the + // stale older value — a transient `finalized > safe` violation. Updating + // safe first keeps the invariant satisfied throughout (finalized stays + // at its older, smaller value while safe advances). if safe_block_hash != B256::ZERO { self.update_block_tag(safe_block_hash, "safe", |sealed| { self.provider.set_safe(sealed); })?; } + if finalized_block_hash != B256::ZERO { + self.update_block_tag(finalized_block_hash, "finalized", |sealed| { + self.provider.set_finalized(sealed); + })?; + } + // Cache the L1-based hashes so subsequent FCU calls use them instead of // falling back to head. This keeps engine-tree finalization and // RPC-visible tags aligned with the actual L1 finalization status. - self.engine_state_tracker.record_block_tags( + self.block_tag_tracker.record_block_tags( if safe_block_hash != B256::ZERO { Some(safe_block_hash) } else { @@ -656,23 +628,25 @@ impl RealMorphL2EngineApi { suggested_fee_recipient: Address::ZERO, withdrawals: Some(Vec::new()), parent_beacon_block_root: None, + // Morph L2 has no PoS slot semantics; introduced in alloy 2.0. + slot_number: None, }, transactions: Some(params.transactions), gas_limit: gas_limit_override, base_fee_per_gas: base_fee_override, }; - let builder_attrs = MorphPayloadBuilderAttributes::try_new(parent_hash, rpc_attributes, 1) - .map_err(|e| { - MorphEngineApiError::BlockBuildError(format!( - "failed to create builder attributes: {e}", - )) - })?; - let payload_id = builder_attrs.payload_id(); + let payload_id = rpc_attributes.morph_payload_id(&parent_hash); + let build_input = BuildNewPayload { + attributes: rpc_attributes, + parent_hash, + cache: None, + trie_handle: None, + }; let _ = self .payload_builder - .send_new_payload(builder_attrs) + .send_new_payload(build_input) .await .map_err(|_| { MorphEngineApiError::BlockBuildError("failed to send build request".to_string()) @@ -719,7 +693,7 @@ impl RealMorphL2EngineApi { .await .map_err(|e| MorphEngineApiError::ExecutionFailed(e.to_string()))?; let new_payload_elapsed = new_payload_started.elapsed(); - self.ensure_payload_status_acceptable(&payload_status, "newPayload")?; + ensure_payload_status_valid(&payload_status, "newPayload")?; // Morph uses Tendermint consensus with instant finality — every committed // block is final and no reorgs are possible. @@ -744,13 +718,13 @@ impl RealMorphL2EngineApi { .unwrap_or_default() .as_secs(); let finalized_hash = resolve_fcu_block_tag_hash( - self.engine_state_tracker.l1_finalized_hash(), + self.block_tag_tracker.l1_finalized_hash(), data.hash, data.timestamp, now_timestamp, ); let safe_hash = resolve_fcu_block_tag_hash( - self.engine_state_tracker.l1_safe_hash(), + self.block_tag_tracker.l1_safe_hash(), data.hash, data.timestamp, now_timestamp, @@ -766,21 +740,11 @@ impl RealMorphL2EngineApi { let fcu_started = Instant::now(); let fcu_result = self .engine_handle - .fork_choice_updated(forkchoice, None, Self::engine_api_version()) + .fork_choice_updated(forkchoice, None) .await .map_err(|e| MorphEngineApiError::ExecutionFailed(e.to_string()))?; let fcu_elapsed = fcu_started.elapsed(); - self.ensure_payload_status_acceptable(&fcu_result.payload_status, "forkchoiceUpdated")?; - - // Synchronously update the canonical head so that eth_blockNumber immediately - // reflects the new block. The background write pipeline updates - // canonical_in_memory_state asynchronously; without this call, morph-node - // would see eth_blockNumber return the old block number and reject the next - // block as ErrWrongBlockNumber. - self.engine_state_tracker - .record_local_head(data.number, data.hash, data.timestamp); - self.provider - .set_canonical_head(SealedHeader::new(header.clone(), data.hash)); + ensure_payload_status_valid(&fcu_result.payload_status, "forkchoiceUpdated")?; tracing::info!( target: "morph::engine", @@ -873,6 +837,10 @@ impl RealMorphL2EngineApi { blob_gas_used: None, excess_blob_gas: None, requests_hash: None, + // Pre-Amsterdam Morph blocks do not carry a block-access-list hash, + // and there is no PoS slot number. + block_access_list_hash: None, + slot_number: None, }, }; let body = BlockBody { @@ -913,57 +881,54 @@ impl RealMorphL2EngineApi { )) } - fn ensure_payload_status_acceptable( - &self, - status: &alloy_rpc_types_engine::PayloadStatus, - context: &'static str, - ) -> EngineApiResult<()> { - match &status.status { - alloy_rpc_types_engine::PayloadStatusEnum::Valid - | alloy_rpc_types_engine::PayloadStatusEnum::Accepted => Ok(()), - alloy_rpc_types_engine::PayloadStatusEnum::Syncing => { - Err(MorphEngineApiError::ExecutionFailed(format!( - "{context} returned SYNCING for payload" - ))) - } - alloy_rpc_types_engine::PayloadStatusEnum::Invalid { validation_error } => { - Err(MorphEngineApiError::ValidationFailed(format!( - "{context} returned INVALID: {validation_error}" - ))) - } - } - } - - const fn engine_api_version() -> EngineApiMessageVersion { - EngineApiMessageVersion::V1 - } - - fn current_head(&self) -> EngineApiResult + fn current_head(&self) -> EngineApiResult where Provider: HeaderProvider + BlockNumReader, { - if let Some(head) = self.engine_state_tracker.current_head() { - return Ok(head); - } - - let number = self + let info = self .provider - .last_block_number() + .chain_info() .map_err(|e| MorphEngineApiError::Database(e.to_string()))?; let header = self .provider - .sealed_header(number) + .sealed_header_by_hash(info.best_hash) .map_err(|e| MorphEngineApiError::Database(e.to_string()))? - .ok_or_else(|| MorphEngineApiError::Internal(format!("header {number} not found")))?; + .ok_or_else(|| { + MorphEngineApiError::Internal(format!( + "canonical head header {} ({}) not found", + info.best_number, info.best_hash + )) + })?; - let head = InMemoryHead { - number, - hash: header.hash(), + Ok(CanonicalHead { + number: info.best_number, + hash: info.best_hash, timestamp: header.timestamp(), - }; - self.engine_state_tracker - .record_local_head(head.number, head.hash, head.timestamp); - Ok(head) + }) + } +} + +fn payload_status_is_validated(status: &PayloadStatus) -> bool { + matches!(status.status, PayloadStatusEnum::Valid) +} + +fn ensure_payload_status_valid( + status: &PayloadStatus, + context: &'static str, +) -> EngineApiResult<()> { + match &status.status { + PayloadStatusEnum::Valid => Ok(()), + PayloadStatusEnum::Accepted => Err(MorphEngineApiError::ExecutionFailed(format!( + "{context} returned ACCEPTED before payload was validated" + ))), + PayloadStatusEnum::Syncing => Err(MorphEngineApiError::ExecutionFailed(format!( + "{context} returned SYNCING for payload" + ))), + PayloadStatusEnum::Invalid { validation_error } => { + Err(MorphEngineApiError::ValidationFailed(format!( + "{context} returned INVALID: {validation_error}" + ))) + } } } @@ -1031,39 +996,48 @@ mod tests { use super::*; use alloy_consensus::Header; use alloy_primitives::{Address, Bloom, Bytes}; + use alloy_rpc_types_engine::{PayloadStatus, PayloadStatusEnum}; use morph_primitives::BlockBody; - use reth_node_api::ConsensusEngineEvent; - use reth_primitives_traits::SealedHeader; - use std::time::Duration; fn recovered_with_header(header: MorphHeader) -> RecoveredBlock { let block = Block::new(header, BlockBody::default()); RecoveredBlock::new_unhashed(block, Vec::new()) } + fn payload_status(status: PayloadStatusEnum) -> PayloadStatus { + PayloadStatus::from_status(status) + } + + #[test] + fn test_validation_success_requires_valid_payload_status() { + assert!(payload_status_is_validated(&payload_status( + PayloadStatusEnum::Valid + ))); + assert!(!payload_status_is_validated(&payload_status( + PayloadStatusEnum::Accepted + ))); + assert!(!payload_status_is_validated(&payload_status( + PayloadStatusEnum::Syncing + ))); + assert!(!payload_status_is_validated(&payload_status( + PayloadStatusEnum::Invalid { + validation_error: "bad payload".to_string(), + } + ))); + } + #[test] - fn test_engine_state_tracker_updates_head_on_canonical_chain_commit() { - let tracker = EngineStateTracker::default(); - assert!(tracker.current_head().is_none()); + fn test_ensure_payload_status_valid_rejects_accepted() { + let err = + ensure_payload_status_valid(&payload_status(PayloadStatusEnum::Accepted), "newPayload") + .unwrap_err(); - let header = MorphHeader { - inner: Header { - number: 42, - timestamp: 1_700_000_042, - ..Default::default() - }, - ..Default::default() - }; - let sealed_header = SealedHeader::seal_slow(header); - tracker.on_consensus_engine_event(&ConsensusEngineEvent::CanonicalChainCommitted( - Box::new(sealed_header.clone()), - Duration::ZERO, - )); - - let current_head = tracker.current_head().expect("head should be updated"); - assert_eq!(current_head.number, sealed_header.number()); - assert_eq!(current_head.hash, sealed_header.hash()); - assert_eq!(current_head.timestamp, sealed_header.timestamp()); + match err { + MorphEngineApiError::ExecutionFailed(msg) => { + assert!(msg.contains("newPayload returned ACCEPTED")); + } + other => panic!("unexpected error: {other}"), + } } #[test] @@ -1253,82 +1227,16 @@ mod tests { ); } - // ========================================================================= - // EngineStateTracker tests - // ========================================================================= - - #[test] - fn test_engine_state_tracker_default_is_none() { - let tracker = EngineStateTracker::default(); - assert!(tracker.current_head().is_none()); - } - #[test] - fn test_engine_state_tracker_record_local_head() { - let tracker = EngineStateTracker::default(); - let hash = B256::from([0x42; 32]); - tracker.record_local_head(10, hash, 1_700_000_010); - - let head = tracker.current_head().expect("head should be set"); - assert_eq!(head.number, 10); - assert_eq!(head.hash, hash); - assert_eq!(head.timestamp, 1_700_000_010); - } + fn test_block_tag_tracker_records_l1_block_tags() { + let tracker = BlockTagTracker::default(); + let safe_hash = B256::from([0x11; 32]); + let finalized_hash = B256::from([0x22; 32]); - #[test] - fn test_engine_state_tracker_overwrites_on_update() { - let tracker = EngineStateTracker::default(); - tracker.record_local_head(10, B256::from([0x01; 32]), 100); - tracker.record_local_head(20, B256::from([0x02; 32]), 200); - - let head = tracker.current_head().expect("head should be set"); - assert_eq!(head.number, 20); - assert_eq!(head.hash, B256::from([0x02; 32])); - assert_eq!(head.timestamp, 200); - } + tracker.record_block_tags(Some(safe_hash), Some(finalized_hash)); - #[test] - fn test_engine_state_tracker_ignores_non_canonical_events() { - let tracker = EngineStateTracker::default(); - - // LiveSyncProgress events should not update the head - // (only CanonicalChainCommitted updates it) - // We can only test CanonicalChainCommitted since other variants - // require complex types. Verify the tracker remains None when no - // CanonicalChainCommitted event is sent. - assert!(tracker.current_head().is_none()); - - // Now send a CanonicalChainCommitted event - let header = MorphHeader { - inner: Header { - number: 5, - timestamp: 500, - ..Default::default() - }, - ..Default::default() - }; - let sealed_header = SealedHeader::seal_slow(header); - tracker.on_consensus_engine_event(&ConsensusEngineEvent::CanonicalChainCommitted( - Box::new(sealed_header), - Duration::ZERO, - )); - - let head = tracker - .current_head() - .expect("head should be set after event"); - assert_eq!(head.number, 5); - } - - #[test] - fn test_engine_state_tracker_concurrent_reads() { - // Verify parking_lot::RwLock allows concurrent reads without panic - let tracker = EngineStateTracker::default(); - tracker.record_local_head(1, B256::ZERO, 100); - - // Multiple reads should not block or panic - let head1 = tracker.current_head(); - let head2 = tracker.current_head(); - assert_eq!(head1, head2); + assert_eq!(tracker.l1_safe_hash(), Some(safe_hash)); + assert_eq!(tracker.l1_finalized_hash(), Some(finalized_hash)); } // ========================================================================= diff --git a/crates/engine-api/src/lib.rs b/crates/engine-api/src/lib.rs index 931cb275..8d740723 100644 --- a/crates/engine-api/src/lib.rs +++ b/crates/engine-api/src/lib.rs @@ -4,7 +4,6 @@ //! //! - [`MorphL2EngineApi`]: The L2 Engine API trait for block building and validation //! - [`MorphL2EngineRpcServer`]: The JSON-RPC server implementation -//! - [`MorphValidationContext`]: Validation context with Jade hardfork support //! //! # L2 Engine API //! @@ -25,10 +24,8 @@ mod builder; mod error; mod metrics; mod rpc; -mod validator; pub use api::MorphL2EngineApi; -pub use builder::{EngineStateTracker, RealMorphL2EngineApi}; +pub use builder::{BlockTagTracker, RealMorphL2EngineApi}; pub use error::{EngineApiResult, MorphEngineApiError}; pub use rpc::{MorphL2EngineRpcHandler, MorphL2EngineRpcServer, into_rpc_result}; -pub use validator::{MorphValidationContext, should_validate_state_root}; diff --git a/crates/engine-api/src/validator.rs b/crates/engine-api/src/validator.rs deleted file mode 100644 index e56a637d..00000000 --- a/crates/engine-api/src/validator.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! Morph Engine Validator utilities. -//! -//! This module provides utilities for state root validation according to -//! the Jade hardfork rules. -//! -//! **Important**: Morph skips state root validation before the Jade hardfork, -//! before Jade, Morph uses ZK-trie, and state root verification happens in the -//! ZK proof instead. - -use morph_chainspec::{MorphChainSpec, MorphHardforks}; -use std::sync::Arc; - -/// Determines if state root validation should be performed for a given timestamp. -/// -/// Before the Jade hardfork, state root validation is skipped because Morph -/// uses ZK-trie before Jade, and the state root verification happens in the -/// ZK proof instead. -/// -/// # Arguments -/// -/// * `chain_spec` - The chain specification -/// * `timestamp` - The block timestamp to check -/// -/// # Returns -/// -/// Returns `true` if state root validation should be performed (Jade is active), -/// `false` if Jade is not active (validation skipped, using ZK-trie). -pub fn should_validate_state_root(chain_spec: &MorphChainSpec, timestamp: u64) -> bool { - chain_spec.is_jade_active_at_timestamp(timestamp) -} - -/// Helper struct to hold chain spec for validation decisions. -#[derive(Debug, Clone)] -pub struct MorphValidationContext { - chain_spec: Arc, -} - -impl MorphValidationContext { - /// Creates a new validation context. - pub const fn new(chain_spec: Arc) -> Self { - Self { chain_spec } - } - - /// Returns whether state root validation should be performed at the given timestamp. - pub fn should_validate_state_root(&self, timestamp: u64) -> bool { - should_validate_state_root(&self.chain_spec, timestamp) - } - - /// Returns the chain spec. - pub fn chain_spec(&self) -> &MorphChainSpec { - &self.chain_spec - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_genesis::Genesis; - use serde_json::json; - - fn create_test_chainspec(jade_time: Option) -> Arc { - let mut genesis_json = json!({ - "config": { - "chainId": 1337, - "bernoulliBlock": 0, - "curieBlock": 0, - "morph": {} - }, - "alloc": {} - }); - - if let Some(time) = jade_time { - genesis_json["config"]["jadeForkTime"] = json!(time); - } - - let genesis: Genesis = serde_json::from_value(genesis_json).unwrap(); - Arc::new(MorphChainSpec::from(genesis)) - } - - #[test] - fn test_should_validate_state_root_before_jade() { - let chain_spec = create_test_chainspec(Some(1000)); - - // Before Jade: should skip validation (return false, using ZK-trie) - assert!(!should_validate_state_root(&chain_spec, 0)); - assert!(!should_validate_state_root(&chain_spec, 500)); - assert!(!should_validate_state_root(&chain_spec, 999)); - } - - #[test] - fn test_should_validate_state_root_after_jade() { - let chain_spec = create_test_chainspec(Some(1000)); - - // After Jade: should validate (return true, using MPT) - assert!(should_validate_state_root(&chain_spec, 1000)); - assert!(should_validate_state_root(&chain_spec, 2000)); - } - - #[test] - fn test_should_validate_state_root_no_jade() { - // If Jade is not set, should always return false - let chain_spec = create_test_chainspec(None); - - assert!(!should_validate_state_root(&chain_spec, 0)); - assert!(!should_validate_state_root(&chain_spec, 1000)); - } - - #[test] - fn test_validation_context() { - let chain_spec = create_test_chainspec(Some(1000)); - let ctx = MorphValidationContext::new(chain_spec); - - // Before Jade: should skip validation (using ZK-trie) - assert!(!ctx.should_validate_state_root(500)); - // After Jade: should validate (using MPT) - assert!(ctx.should_validate_state_root(1000)); - } - - #[test] - fn test_validation_context_chain_spec_accessor() { - let chain_spec = create_test_chainspec(Some(1000)); - let ctx = MorphValidationContext::new(chain_spec); - - // Verify the chain_spec accessor returns a valid chain spec - // by checking a hardfork method on it - assert!(ctx.chain_spec().is_jade_active_at_timestamp(1000)); - assert!(!ctx.chain_spec().is_jade_active_at_timestamp(999)); - } - - #[test] - fn test_should_validate_state_root_at_jade_boundary() { - let chain_spec = create_test_chainspec(Some(1000)); - - // Exactly at Jade timestamp: should validate (active) - assert!(should_validate_state_root(&chain_spec, 1000)); - - // One second before: should NOT validate - assert!(!should_validate_state_root(&chain_spec, 999)); - - // One second after: should validate - assert!(should_validate_state_root(&chain_spec, 1001)); - } - - #[test] - fn test_should_validate_state_root_jade_at_zero() { - // Jade active from genesis (timestamp 0) - let chain_spec = create_test_chainspec(Some(0)); - - // Should always validate when Jade is at timestamp 0 - assert!(should_validate_state_root(&chain_spec, 0)); - assert!(should_validate_state_root(&chain_spec, 1)); - assert!(should_validate_state_root(&chain_spec, u64::MAX)); - } - - #[test] - fn test_should_validate_state_root_jade_at_max_timestamp() { - let chain_spec = create_test_chainspec(Some(u64::MAX)); - - // Only u64::MAX should trigger validation - assert!(!should_validate_state_root(&chain_spec, 0)); - assert!(!should_validate_state_root(&chain_spec, u64::MAX - 1)); - assert!(should_validate_state_root(&chain_spec, u64::MAX)); - } -} diff --git a/crates/engine-tree-ext/Cargo.toml b/crates/engine-tree-ext/Cargo.toml new file mode 100644 index 00000000..255e6c88 --- /dev/null +++ b/crates/engine-tree-ext/Cargo.toml @@ -0,0 +1,80 @@ +[package] +name = "morph-engine-tree-ext" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +publish.workspace = true + +[lints] +workspace = true + +[dependencies] +# morph local crates +morph-chainspec = { workspace = true } +# morph-primitives brought in transitively via morph-chainspec; direct dep here +# enables the reth-codec feature so cargo test -p morph-engine-tree-ext satisfies +# NodePrimitives::Receipt: FullReceipt (requires Compact impl). +morph-primitives = { workspace = true, features = ["reth-codec"] } + +# reth (workspace = true after Task 4 flips root Cargo.toml to paradigmxyz/reth v2.0.0) +reth-chain-state = { workspace = true } +reth-consensus = { workspace = true } +reth-db = { workspace = true } +reth-engine-primitives = { workspace = true } +reth-engine-tree = { workspace = true } +reth-errors = { workspace = true } +reth-evm = { workspace = true } +reth-execution-cache = { workspace = true } +reth-payload-primitives = { workspace = true } +reth-primitives-traits = { workspace = true } +reth-provider = { workspace = true } +reth-revm = { workspace = true } +reth-tasks = { workspace = true } +reth-trie = { workspace = true } +reth-trie-db = { workspace = true } +reth-trie-parallel = { workspace = true } + +# alloy / revm — workspace versions from reth v2.0.0 +alloy-consensus = { workspace = true } +alloy-eip7928 = { workspace = true } +alloy-eips = { workspace = true } +alloy-evm = { workspace = true } +alloy-primitives = { workspace = true } +alloy-rlp = { workspace = true } +revm-primitives = { workspace = true } + +# utilities +crossbeam-channel = { workspace = true } +derive_more = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } + +[features] +default = ["std"] +std = [] +trie-debug = ["dep:reth-trie-sparse"] +# Forwards `morph-node`'s `test-utils` feature, which provides `MorphTestNode`, +# `TestNodeBuilder`, `HardforkSchedule`, etc. used by the Jade-boundary +# integration test. +test-utils = ["morph-node/test-utils"] + +[dependencies.reth-trie-sparse] +workspace = true +default-features = false +optional = true + +[dev-dependencies] +alloy-genesis = { workspace = true } +serde_json = { workspace = true } +# Jade boundary integration test — exercises MorphBasicEngineValidator through +# the real MorphNode stack via morph-node's test-utils feature. +morph-node = { workspace = true, features = ["test-utils"] } +morph-payload-types = { workspace = true } +reth-node-api = { workspace = true } +reth-payload-builder = { workspace = true } +reth-tracing = { workspace = true } +alloy-rpc-types-engine = { workspace = true } +alloy-rpc-types-eth = { workspace = true } +eyre = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/crates/engine-tree-ext/src/gate.rs b/crates/engine-tree-ext/src/gate.rs new file mode 100644 index 00000000..e3dbd9db --- /dev/null +++ b/crates/engine-tree-ext/src/gate.rs @@ -0,0 +1,67 @@ +//! Decides whether strict state-root equality must be enforced for a block. +//! +//! Pre-Jade Morph blocks store ZK-trie roots in `header.state_root`, while reth +//! computes MPT roots. Skipping the strict check pre-Jade is safe because the +//! first post-Jade block's successful strict check retroactively anchors the +//! MPT state accumulated across the pre-Jade range (transitivity of EVM +//! execution; see the design spec). + +use morph_chainspec::{MorphChainSpec, MorphHardforks}; + +/// Returns `true` iff reth must enforce the strict +/// `computed_state_root == header.state_root()` check at the given block +/// timestamp. Post-Jade: true. Pre-Jade: false. +pub fn state_root_enforced_at(chain_spec: &MorphChainSpec, timestamp: u64) -> bool { + chain_spec.is_jade_active_at_timestamp(timestamp) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_genesis::Genesis; + use serde_json::json; + use std::sync::Arc; + + fn chain_spec_with_jade_at(jade_time: u64) -> Arc { + let genesis: Genesis = serde_json::from_value(json!({ + "config": { + "chainId": 1337, + "bernoulliBlock": 0, + "curieBlock": 0, + "morph": {}, + "jadeForkTime": jade_time + }, + "alloc": {} + })) + .expect("valid test genesis"); + Arc::new(MorphChainSpec::from(genesis)) + } + + #[test] + fn skipped_before_jade() { + let cs = chain_spec_with_jade_at(1_000); + assert!(!state_root_enforced_at(&cs, 0)); + assert!(!state_root_enforced_at(&cs, 500)); + assert!(!state_root_enforced_at(&cs, 999)); + } + + #[test] + fn strict_at_and_after_jade() { + let cs = chain_spec_with_jade_at(1_000); + assert!(state_root_enforced_at(&cs, 1_000)); + assert!(state_root_enforced_at(&cs, 1_001)); + assert!(state_root_enforced_at(&cs, u64::MAX)); + } + + #[test] + fn skipped_when_jade_unset() { + let genesis: Genesis = serde_json::from_value(json!({ + "config": { "chainId": 1337, "bernoulliBlock": 0, "curieBlock": 0, "morph": {} }, + "alloc": {} + })) + .unwrap(); + let cs = Arc::new(MorphChainSpec::from(genesis)); + assert!(!state_root_enforced_at(&cs, 0)); + assert!(!state_root_enforced_at(&cs, 1_000_000)); + } +} diff --git a/crates/engine-tree-ext/src/lib.rs b/crates/engine-tree-ext/src/lib.rs new file mode 100644 index 00000000..fc277227 --- /dev/null +++ b/crates/engine-tree-ext/src/lib.rs @@ -0,0 +1,13 @@ +//! Morph extension of reth's engine tree. +//! +//! Verbatim copy of `reth_engine_tree::tree::payload_validator` from reth v2.0.0, +//! used as the foundation for a Jade-gated state-root skip. +//! +//! `trie_updates` is a verbatim copy of the private sibling module from the same +//! source tree, required by `payload_validator::compare_trie_updates_with_serial`. + +pub mod gate; +pub mod payload_validator; +pub(crate) mod trie_updates; + +pub use payload_validator::MorphBasicEngineValidator; diff --git a/crates/engine-tree-ext/src/payload_validator.rs b/crates/engine-tree-ext/src/payload_validator.rs new file mode 100644 index 00000000..a0455e01 --- /dev/null +++ b/crates/engine-tree-ext/src/payload_validator.rs @@ -0,0 +1,2126 @@ +//! Types and traits for validating blocks and payloads. +//! +//! # Validation pipeline +//! +//! When the engine processes a new payload (`engine_newPayload`), validation happens in phases: +//! +//! ## Phase 1 – Payload conversion +//! [`PayloadValidator::convert_payload_to_block`] decodes the execution payload (RLP, hashing) +//! into a [`SealedBlock`]. This runs on a background thread concurrently with state setup. +//! +//! ## Phase 2 – Pre-execution consensus +//! - [`HeaderValidator::validate_header`] — standalone header checks (hash, gas, base fee, +//! fork-specific fields) +//! - [`Consensus::validate_block_pre_execution`] — body vs header (tx root, ommer hash, withdrawals +//! root) +//! - [`HeaderValidator::validate_header_against_parent`] — sequential checks (block number, +//! timestamp, gas limit, base fee vs parent) +//! +//! ## Phase 3 – Execution +//! Block transactions are executed via the EVM. Receipt roots are computed incrementally. +//! +//! ## Phase 4 – Post-execution consensus +//! - [`FullConsensus::validate_block_post_execution`] — gas used, receipt root, logs bloom, +//! requests hash +//! - [`PayloadValidator::validate_block_post_execution_with_hashed_state`] — network-specific +//! (no-op on L1, used by OP Stack) +//! +//! ## Payload attributes validation (`engine_forkchoiceUpdated`) +//! When the CL provides payload attributes to start building a block: +//! - [`PayloadValidator::validate_payload_attributes_against_header`] — ensures timestamp ordering +//! +//! If validation passes, a payload build job is started. If it fails, +//! `INVALID_PAYLOAD_ATTRIBUTES` is returned without rolling back the forkchoice update. +//! +//! [`HeaderValidator::validate_header`]: reth_consensus::HeaderValidator::validate_header +//! [`Consensus::validate_block_pre_execution`]: reth_consensus::Consensus::validate_block_pre_execution +//! [`HeaderValidator::validate_header_against_parent`]: reth_consensus::HeaderValidator::validate_header_against_parent +//! [`FullConsensus::validate_block_post_execution`]: reth_consensus::FullConsensus::validate_block_post_execution +//! [`SealedBlock`]: reth_primitives_traits::SealedBlock + +use alloy_consensus::transaction::{Either, TxHashRef}; +use alloy_eip7928::BlockAccessList; +use alloy_eips::{NumHash, eip1898::BlockWithParent, eip4895::Withdrawal}; +use alloy_evm::Evm; +use alloy_primitives::{B256, map::B256Set}; +use reth_engine_tree::tree::{ + CacheWaitDurations, CachedStateProvider, EngineApiMetrics, EngineApiTreeState, EngineValidator, + ExecutionEnv, PayloadHandle, StateProviderBuilder, TreeConfig, WaitForCaches, + error::{InsertBlockError, InsertBlockErrorKind, InsertPayloadError}, + instrumented_state::{InstrumentedStateProvider, StateProviderStats}, + payload_processor::{ + PayloadProcessor, + multiproof::{StateRootComputeOutcome, StateRootHandle}, + }, + payload_validator::TreeCtx, + precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap}, +}; +use reth_revm::database::StateProviderDatabase; +#[cfg(feature = "trie-debug")] +use reth_trie_sparse::debug_recorder::TrieDebugRecorder; + +use crate::gate::state_root_enforced_at; +use morph_chainspec::MorphChainSpec; +use reth_chain_state::{DeferredTrieData, ExecutedBlock, ExecutionTimingStats, LazyOverlay}; +use reth_consensus::{ConsensusError, FullConsensus, ReceiptRootBloom}; +use reth_engine_primitives::{ + ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator, +}; +use reth_engine_tree::tree::payload_processor::receipt_root_task::{ + IndexedReceipt, ReceiptRootTaskHandle, +}; +use reth_errors::{BlockExecutionError, ProviderResult}; +use reth_evm::{ + ConfigureEvm, EvmEnvFor, ExecutionCtxFor, OnStateHook, SpecFor, block::BlockExecutor, + execute::ExecutableTxFor, +}; +use reth_execution_cache::{CacheStats, SavedCache}; +use reth_payload_primitives::{ + BuiltPayload, InvalidPayloadAttributesError, NewPayloadError, PayloadTypes, +}; +use reth_primitives_traits::{ + AlloyBlockHeader, BlockBody, BlockTy, FastInstant as Instant, GotExpected, NodePrimitives, + RecoveredBlock, SealedBlock, SealedHeader, SignerRecoverable, +}; +use reth_provider::{ + BlockExecutionOutput, BlockNumReader, BlockReader, ChangeSetReader, DatabaseProviderFactory, + DatabaseProviderROFactory, HashedPostStateProvider, ProviderError, PruneCheckpointReader, + StageCheckpointReader, StateProvider, StateProviderFactory, StateReader, + StorageChangeSetReader, StorageSettingsCache, + providers::{OverlayBuilder, OverlayStateProviderFactory}, +}; +use reth_revm::db::{BundleAccount, State, states::bundle_state::BundleRetention}; +use reth_trie::{HashedPostState, StateRoot, trie_cursor::TrieCursorFactory, updates::TrieUpdates}; +use reth_trie_db::ChangesetCache; +use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError}; +use revm_primitives::{Address, KECCAK_EMPTY}; +use std::{ + collections::HashMap, + panic::{self, AssertUnwindSafe}, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + mpsc::RecvTimeoutError, + }, + time::Duration, +}; +use tracing::{Span, debug, debug_span, error, info, instrument, trace, warn}; + +/// Output of block or payload validation. +pub type ValidationOutcome>> = + Result<(ExecutedBlock, Option>), E>; + +/// Handle to a [`HashedPostState`] computed on a background thread. +type LazyHashedPostState = reth_tasks::LazyHandle; + +/// Result type for block validation with optional timing stats. +type InsertPayloadResult = Result< + (ExecutedBlock, Option>), + InsertPayloadError<::Block>, +>; + +/// Context providing access to tree state during validation. +/// +/// A helper type that provides reusable payload validation logic for network-specific validators. +/// +/// This type satisfies [`EngineValidator`] and is responsible for executing blocks/payloads. +/// +/// This type contains common validation, execution, and state root computation logic that can be +/// used by network-specific payload validators (e.g., Ethereum, Optimism). It is not meant to be +/// used as a standalone component, but rather as a building block for concrete implementations. +#[derive(derive_more::Debug)] +pub struct MorphBasicEngineValidator +where + Evm: ConfigureEvm, +{ + /// Provider for database access. + provider: P, + /// Consensus implementation for validation. + consensus: Arc>, + /// EVM configuration. + evm_config: Evm, + /// Configuration for the tree. + config: TreeConfig, + /// Payload processor for state root computation. + payload_processor: PayloadProcessor, + /// Precompile cache map. + precompile_cache_map: PrecompileCacheMap>, + /// Precompile cache metrics. + precompile_cache_metrics: HashMap, + /// Hook to call when invalid blocks are encountered. + #[debug(skip)] + invalid_block_hook: Box>, + /// Metrics for the engine api. + metrics: EngineApiMetrics, + /// Validator for the payload. + validator: V, + /// Changeset cache for in-memory trie changesets + changeset_cache: ChangesetCache, + /// Task runtime for spawning parallel work. + runtime: reth_tasks::Runtime, + /// Chain spec, used to gate pre-Jade state-root skipping. + chain_spec: Arc, +} + +impl MorphBasicEngineValidator +where + N: NodePrimitives, + P: DatabaseProviderFactory< + Provider: BlockReader + + StageCheckpointReader + + PruneCheckpointReader + + ChangeSetReader + + StorageChangeSetReader + + BlockNumReader + + StorageSettingsCache, + > + BlockReader
+ + ChangeSetReader + + BlockNumReader + + StateProviderFactory + + StateReader + + HashedPostStateProvider + + Clone + + 'static, + Evm: ConfigureEvm + 'static, +{ + /// Creates a new `TreePayloadValidator`. + #[expect(clippy::too_many_arguments)] + pub fn new( + provider: P, + consensus: Arc>, + evm_config: Evm, + validator: V, + config: TreeConfig, + invalid_block_hook: Box>, + changeset_cache: ChangesetCache, + runtime: reth_tasks::Runtime, + chain_spec: Arc, + ) -> Self { + let precompile_cache_map = PrecompileCacheMap::default(); + let payload_processor = PayloadProcessor::new( + runtime.clone(), + evm_config.clone(), + &config, + precompile_cache_map.clone(), + ); + Self { + provider, + consensus, + evm_config, + payload_processor, + precompile_cache_map, + precompile_cache_metrics: HashMap::new(), + config, + invalid_block_hook, + metrics: EngineApiMetrics::default(), + validator, + changeset_cache, + runtime, + chain_spec, + } + } + + /// Converts a [`BlockOrPayload`] to a recovered block. + #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)] + pub fn convert_to_block>>( + &self, + input: BlockOrPayload, + ) -> Result, NewPayloadError> + where + V: PayloadValidator, + { + match input { + BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload), + BlockOrPayload::Block(block) => Ok(block), + } + } + + /// Returns EVM environment for the given payload or block. + pub fn evm_env_for>>( + &self, + input: &BlockOrPayload, + ) -> Result, Evm::Error> + where + V: PayloadValidator, + Evm: ConfigureEngineEvm, + { + match input { + BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?), + BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?), + } + } + + /// Returns [`ExecutableTxIterator`] for the given payload or block. + pub fn tx_iterator_for<'a, T: PayloadTypes>>( + &'a self, + input: &'a BlockOrPayload, + ) -> Result, NewPayloadError> + where + V: PayloadValidator, + Evm: ConfigureEngineEvm, + { + Ok(match input { + BlockOrPayload::Payload(payload) => { + let iter = self + .evm_config + .tx_iterator_for_payload(payload) + .map_err(NewPayloadError::other)?; + Either::Left(iter) + } + BlockOrPayload::Block(block) => { + let txs = block.body().clone_transactions(); + let convert = |tx: N::SignedTx| tx.try_into_recovered(); + Either::Right((txs, convert)) + } + }) + } + + /// Returns a [`ExecutionCtxFor`] for the given payload or block. + pub fn execution_ctx_for<'a, T: PayloadTypes>>( + &self, + input: &'a BlockOrPayload, + ) -> Result, Evm::Error> + where + V: PayloadValidator, + Evm: ConfigureEngineEvm, + { + match input { + BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)?), + BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?), + } + } + + /// Handles execution errors by checking if header validation errors should take precedence. + /// + /// When an execution error occurs, this function checks if there are any header validation + /// errors that should be reported instead, as header validation errors have higher priority. + fn handle_execution_error>>( + &self, + input: BlockOrPayload, + execution_err: InsertBlockErrorKind, + parent_block: &SealedHeader, + ) -> InsertPayloadResult + where + V: PayloadValidator, + { + debug!( + target: "engine::tree::payload_validator", + ?execution_err, + block = ?input.num_hash(), + "Block execution failed, checking for header validation errors" + ); + + // If execution failed, we should first check if there are any header validation + // errors that take precedence over the execution error + let block = self.convert_to_block(input)?; + + // Validate block consensus rules which includes header validation + if let Err(consensus_err) = self.validate_block_inner(&block, None) { + // Header validation error takes precedence over execution error + return Err(InsertBlockError::new(block, consensus_err.into()).into()); + } + + // Also validate against the parent + if let Err(consensus_err) = self + .consensus + .validate_header_against_parent(block.sealed_header(), parent_block) + { + // Parent validation error takes precedence over execution error + return Err(InsertBlockError::new(block, consensus_err.into()).into()); + } + + // No header validation errors, return the original execution error + Err(InsertBlockError::new(block, execution_err).into()) + } + + /// Validates a block that has already been converted from a payload. + /// + /// This method performs: + /// - Consensus validation + /// - Block execution + /// - State root computation + /// - Fork detection + #[instrument( + level = "debug", + target = "engine::tree::payload_validator", + skip_all, + fields( + parent = ?input.parent_hash(), + type_name = ?input.type_name(), + ) + )] + pub fn validate_block_with_state>>( + &mut self, + input: BlockOrPayload, + mut ctx: TreeCtx<'_, N>, + ) -> InsertPayloadResult + where + V: PayloadValidator + Clone, + Evm: ConfigureEngineEvm, + { + // Spawn payload conversion on a background thread so it runs concurrently with the + // rest of the function (setup + execution). For payloads this overlaps the cost of + // RLP decoding + header hashing. + let is_payload = matches!(&input, BlockOrPayload::Payload(_)); + let convert_to_block = match &input { + BlockOrPayload::Payload(_) => { + let payload_clone = input.clone(); + let validator = self.validator.clone(); + let handle = self.payload_processor.executor().spawn_blocking_named( + "payload-convert", + move || { + let BlockOrPayload::Payload(payload) = payload_clone else { + unreachable!() + }; + validator.convert_payload_to_block(payload) + }, + ); + Either::Left(handle) + } + BlockOrPayload::Block(_) => Either::Right(()), + }; + + // Returns the sealed block, either by awaiting the background conversion task (for + // payloads) or by extracting the already-converted block directly. + let convert_to_block = + move |input: BlockOrPayload| -> Result, NewPayloadError> { + match convert_to_block { + Either::Left(handle) => handle.try_into_inner().expect("sole handle"), + Either::Right(()) => { + let BlockOrPayload::Block(block) = input else { + unreachable!() + }; + Ok(block) + } + } + }; + + /// A helper macro that returns the block in case there was an error + /// This macro is used for early returns before block conversion + macro_rules! ensure_ok { + ($expr:expr) => { + match $expr { + Ok(val) => val, + Err(e) => { + let block = convert_to_block(input)?; + return Err(InsertBlockError::new(block, e.into()).into()); + } + } + }; + } + + /// A helper macro for handling errors after the input has been converted to a block + macro_rules! ensure_ok_post_block { + ($expr:expr, $block:expr) => { + match $expr { + Ok(val) => val, + Err(e) => { + return Err( + InsertBlockError::new($block.into_sealed_block(), e.into()).into() + ) + } + } + }; + } + + let parent_hash = input.parent_hash(); + + trace!(target: "engine::tree::payload_validator", "Fetching block state provider"); + let _enter = + debug_span!(target: "engine::tree::payload_validator", "state_provider").entered(); + let Some(provider_builder) = + ensure_ok!(self.state_provider_builder(parent_hash, ctx.state())) + else { + // this is pre-validated in the tree + return Err(InsertBlockError::new( + convert_to_block(input)?, + ProviderError::HeaderNotFound(parent_hash.into()).into(), + ) + .into()); + }; + let mut state_provider = ensure_ok!(provider_builder.build()); + drop(_enter); + + // Fetch parent block. This goes to memory most of the time unless the parent block is + // beyond the in-memory buffer. + let Some(parent_block) = ensure_ok!(self.sealed_header_by_hash(parent_hash, ctx.state())) + else { + return Err(InsertBlockError::new( + convert_to_block(input)?, + ProviderError::HeaderNotFound(parent_hash.into()).into(), + ) + .into()); + }; + + let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm_env") + .in_scope(|| self.evm_env_for(&input)) + .map_err(NewPayloadError::other)?; + + let env = ExecutionEnv { + evm_env, + hash: input.hash(), + parent_hash: input.parent_hash(), + parent_state_root: parent_block.state_root(), + transaction_count: input.transaction_count(), + gas_used: input.gas_used(), + withdrawals: input.withdrawals().map(|w| w.to_vec()), + // Morph blocks (pre-Amsterdam) carry no BAL. Skip BAL-driven prewarming + // and let the payload processor install its own state hook for prewarm + // updates instead. + decoded_bal: None, + }; + + // Plan the strategy used for state root computation. + let strategy = self.plan_state_root_computation(); + + debug!( + target: "engine::tree::payload_validator", + ?strategy, + "Decided which state root algorithm to run" + ); + + // Get an iterator over the transactions in the payload + let txs = self.tx_iterator_for(&input)?; + + // Morph blocks (pre-Amsterdam) carry no BAL, so we deliberately drop the + // upstream BAL extraction. The header validator returns `None` for + // `block_access_list_hash()`, so any BAL bytes carried on the wire are + // ignored. + + // Create lazy overlay from ancestors - this doesn't block, allowing execution to start + // before the trie data is ready. The overlay will be computed on first access. + let (lazy_overlay, anchor_hash) = Self::get_parent_lazy_overlay(parent_hash, ctx.state()); + + // Create overlay factory for payload processor (StateRootTask path needs it for + // multiproofs). v2.2.0 moved `with_block_hash` / `with_lazy_overlay` onto + // `OverlayBuilder`, so we configure the builder first and pass it to the factory. + let overlay_builder = OverlayBuilder::::new(anchor_hash, self.changeset_cache.clone()) + .with_lazy_overlay(lazy_overlay); + let overlay_factory = + OverlayStateProviderFactory::new(self.provider.clone(), overlay_builder); + + // Spawn the appropriate processor based on strategy + let mut handle = ensure_ok!(self.spawn_payload_processor( + env.clone(), + txs, + provider_builder, + overlay_factory.clone(), + strategy, + )); + + // Create optional cache stats for detailed block logging + let slow_block_enabled = self.config.slow_block_threshold().is_some(); + let cache_stats = slow_block_enabled.then(|| Arc::new(CacheStats::default())); + + // Use cached state provider before executing, used in execution after prewarming threads + // complete + if let Some((caches, cache_metrics)) = handle.caches().zip(handle.cache_metrics()) { + state_provider = Box::new( + CachedStateProvider::new(state_provider, caches, cache_metrics) + .with_cache_stats(cache_stats.clone()), + ); + }; + + let state_provider_stats = if slow_block_enabled || self.config.state_provider_metrics() { + let instrumented_state_provider = + InstrumentedStateProvider::new(state_provider, "engine"); + let stats = slow_block_enabled.then(|| instrumented_state_provider.stats()); + state_provider = Box::new(instrumented_state_provider); + stats + } else { + None + }; + + // Execute the block and handle any execution errors. + // The receipt root task is spawned before execution and receives receipts incrementally + // as transactions complete, allowing parallel computation during execution. + let execute_block_start = Instant::now(); + let (output, senders, receipt_root_rx) = + match self.execute_block(state_provider, env, &input, &mut handle) { + Ok(output) => output, + Err(err) => return self.handle_execution_error(input, err, &parent_block), + }; + let execution_duration = execute_block_start.elapsed(); + + // After executing the block we can stop prewarming transactions + handle.stop_prewarming_execution(); + + // Create ExecutionOutcome early so we can terminate caching before validation and state + // root computation. Using Arc allows sharing with both the caching task and the deferred + // trie task without cloning the expensive BundleState. + let output = Arc::new(output); + + // Terminate caching task early since execution is complete and caching is no longer + // needed. This frees up resources while state root computation continues. + let valid_block_tx = handle.terminate_caching(Some(output.clone())); + + // Spawn hashed post state computation in background so it runs concurrently with + // block conversion and receipt root computation. This is a pure CPU-bound task + // (keccak256 hashing of all changed addresses and storage slots). + let hashed_state_output = output.clone(); + let hashed_state_provider = self.provider.clone(); + let hashed_state: LazyHashedPostState = self + .payload_processor + .executor() + .spawn_blocking_named("hash-post-state", move || { + let _span = debug_span!( + target: "engine::tree::payload_validator", + "hashed_post_state", + ) + .entered(); + hashed_state_provider.hashed_post_state(&hashed_state_output.state) + }); + + let block = convert_to_block(input)?; + let transaction_root = is_payload.then(|| { + let body = block.body().clone(); + let parent_span = Span::current(); + let num_hash = block.num_hash(); + self.payload_processor.executor().spawn_blocking_named("payload-tx-root", move || { + let _span = + debug_span!(target: "engine::tree::payload_validator", parent: parent_span, "payload_tx_root", block = ?num_hash) + .entered(); + body.calculate_tx_root() + }) + }); + let block = block.with_senders(senders); + + // Wait for the receipt root computation to complete. + let receipt_root_bloom = { + let _enter = debug_span!( + target: "engine::tree::payload_validator", + "wait_receipt_root", + ) + .entered(); + + receipt_root_rx + .blocking_recv() + .inspect_err(|_| { + tracing::error!( + target: "engine::tree::payload_validator", + "Receipt root task dropped sender without result, receipt root calculation likely aborted" + ); + }) + .ok() + }; + let transaction_root = transaction_root.map(|handle| { + let _span = + debug_span!(target: "engine::tree::payload_validator", "wait_payload_tx_root") + .entered(); + handle.try_into_inner().expect("sole handle") + }); + + let hashed_state = ensure_ok_post_block!( + self.validate_post_execution( + &block, + &parent_block, + &output, + &mut ctx, + transaction_root, + receipt_root_bloom, + hashed_state, + ), + block + ); + + let root_time = Instant::now(); + let mut maybe_state_root = None; + let mut state_root_task_failed = false; + #[cfg(feature = "trie-debug")] + let mut trie_debug_recorders = Vec::new(); + + match strategy { + StateRootStrategy::StateRootTask => { + debug!(target: "engine::tree::payload_validator", "Using sparse trie state root algorithm"); + + let task_result = ensure_ok_post_block!( + self.await_state_root_with_timeout( + &mut handle, + overlay_factory.clone(), + &hashed_state, + ), + block + ); + + match task_result { + Ok(StateRootComputeOutcome { + state_root, + trie_updates, + #[cfg(feature = "trie-debug")] + debug_recorders, + }) => { + let elapsed = root_time.elapsed(); + info!(target: "engine::tree::payload_validator", ?state_root, ?elapsed, "State root task finished"); + + #[cfg(feature = "trie-debug")] + { + trie_debug_recorders = debug_recorders; + } + + // Compare trie updates with serial computation if configured + if self.config.always_compare_trie_updates() { + let _has_diff = self.compare_trie_updates_with_serial( + overlay_factory.clone(), + &hashed_state, + trie_updates.as_ref().clone(), + ); + #[cfg(feature = "trie-debug")] + if _has_diff { + Self::write_trie_debug_recorders( + block.header().number(), + &trie_debug_recorders, + ); + } + } + + // Pre-Jade Morph blocks store a ZK-trie root in header.state_root; + // reth computes an MPT root which will never match. The strict check + // is only enforced post-Jade. See gate::state_root_enforced_at and + // the design spec for transitivity of EVM execution. + let strict_enforced = + state_root_enforced_at(&self.chain_spec, block.header().timestamp()); + + // we double check the state root here for good measure + if !strict_enforced || state_root == block.header().state_root() { + maybe_state_root = Some((state_root, trie_updates, elapsed)) + } else { + warn!( + target: "engine::tree::payload_validator", + ?state_root, + block_state_root = ?block.header().state_root(), + "State root task returned incorrect state root" + ); + #[cfg(feature = "trie-debug")] + Self::write_trie_debug_recorders( + block.header().number(), + &trie_debug_recorders, + ); + state_root_task_failed = true; + } + } + Err(error) => { + debug!(target: "engine::tree::payload_validator", %error, "State root task failed"); + state_root_task_failed = true; + } + } + } + StateRootStrategy::Parallel => { + debug!(target: "engine::tree::payload_validator", "Using parallel state root algorithm"); + match self.compute_state_root_parallel(overlay_factory.clone(), &hashed_state) { + Ok(result) => { + let elapsed = root_time.elapsed(); + info!( + target: "engine::tree::payload_validator", + regular_state_root = ?result.0, + ?elapsed, + "Regular root task finished" + ); + maybe_state_root = Some((result.0, Arc::new(result.1), elapsed)); + } + Err(error) => { + debug!(target: "engine::tree::payload_validator", %error, "Parallel state root computation failed"); + } + } + } + StateRootStrategy::Synchronous => {} + } + + // Determine the state root. + // If the state root was computed in parallel, we use it. + // Otherwise, we fall back to computing it synchronously. + let (state_root, trie_output, root_elapsed) = if let Some(maybe_state_root) = + maybe_state_root + { + maybe_state_root + } else { + // fallback is to compute the state root regularly in sync + if self.config.state_root_fallback() { + debug!(target: "engine::tree::payload_validator", "Using state root fallback for testing"); + } else { + warn!(target: "engine::tree::payload_validator", "Failed to compute state root in parallel"); + self.metrics + .block_validation + .state_root_parallel_fallback_total + .increment(1); + } + + let (root, updates) = ensure_ok_post_block!( + Self::compute_state_root_serial(overlay_factory.clone(), &hashed_state), + block + ); + + if state_root_task_failed { + self.metrics + .block_validation + .state_root_task_fallback_success_total + .increment(1); + } + + (root, Arc::new(updates), root_time.elapsed()) + }; + + self.metrics + .block_validation + .record_state_root(&trie_output, root_elapsed.as_secs_f64()); + self.metrics + .record_state_root_gas_bucket(block.header().gas_used(), root_elapsed.as_secs_f64()); + debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root"); + + // ensure state root matches — post-Jade only. Pre-Jade blocks carry a + // ZK-trie root in the header which will never equal reth's MPT root. + // Correctness is proven transitively by the first post-Jade block's + // successful strict check (see design spec). + let strict_enforced = state_root_enforced_at(&self.chain_spec, block.header().timestamp()); + + if strict_enforced && state_root != block.header().state_root() { + #[cfg(feature = "trie-debug")] + Self::write_trie_debug_recorders(block.header().number(), &trie_debug_recorders); + + // call post-block hook + self.on_invalid_block( + &parent_block, + &block, + &output, + Some((&trie_output, state_root)), + ctx.state_mut(), + ); + let block_state_root = block.header().state_root(); + return Err(InsertBlockError::new( + block.into_sealed_block(), + ConsensusError::BodyStateRootDiff( + GotExpected { + got: state_root, + expected: block_state_root, + } + .into(), + ) + .into(), + ) + .into()); + } + + let timing_stats = state_provider_stats.map(|stats| { + self.calculate_timing_stats( + &block, + stats, + cache_stats, + &output, + execution_duration, + root_elapsed, + ) + }); + + if let Some(valid_block_tx) = valid_block_tx { + let _ = valid_block_tx.send(()); + } + + // Create the overlay provider NOW, while we're on the engine loop thread and trie changeset + // eviction cannot race with us. If we deferred this to the background task, persistence + // could advance and evict changeset cache entries between factory creation and the task + // actually running, causing expensive DB fallback computations when building the overlay. + let changeset_provider = + ensure_ok_post_block!(overlay_factory.database_provider_ro(), block); + + let executed_block = self.spawn_deferred_trie_task( + block, + output, + &ctx, + hashed_state, + trie_output, + changeset_provider, + ); + Ok((executed_block, timing_stats)) + } + + /// Return sealed block header from database or in-memory state by hash. + fn sealed_header_by_hash( + &self, + hash: B256, + state: &EngineApiTreeState, + ) -> ProviderResult>> { + // check memory first + let header = state.tree_state().sealed_header_by_hash(&hash); + + if header.is_some() { + Ok(header) + } else { + self.provider.sealed_header_by_hash(hash) + } + } + + /// Validate if block is correct and satisfies all the consensus rules that concern the header + /// and block body itself. + #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)] + fn validate_block_inner( + &self, + block: &SealedBlock, + transaction_root: Option, + ) -> Result<(), ConsensusError> { + if let Err(e) = self.consensus.validate_header(block.sealed_header()) { + error!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {}: {e}", block.hash()); + return Err(e); + } + + if let Err(e) = self + .consensus + .validate_block_pre_execution_with_tx_root(block, transaction_root) + { + error!(target: "engine::tree::payload_validator", ?block, "Failed to validate block {}: {e}", block.hash()); + return Err(e); + } + + Ok(()) + } + + /// Executes a block with the given state provider. + /// + /// This method orchestrates block execution: + /// 1. Sets up the EVM with state database and precompile caching + /// 2. Spawns a background task for incremental receipt root computation + /// 3. Executes transactions with metrics collection via state hooks + /// 4. Merges state transitions and records execution metrics + #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)] + #[expect(clippy::type_complexity)] + fn execute_block( + &mut self, + state_provider: S, + env: ExecutionEnv, + input: &BlockOrPayload, + handle: &mut PayloadHandle, Err, N::Receipt>, + ) -> Result< + ( + BlockExecutionOutput, + Vec
, + tokio::sync::oneshot::Receiver<(B256, alloy_primitives::Bloom)>, + ), + InsertBlockErrorKind, + > + where + S: StateProvider + Send, + Err: core::error::Error + Send + Sync + 'static, + V: PayloadValidator, + T: PayloadTypes>, + Evm: ConfigureEngineEvm, + { + debug!(target: "engine::tree::payload_validator", "Executing block"); + + let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| { + State::builder() + .with_database(StateProviderDatabase::new(state_provider)) + .with_bundle_update() + .build() + }); + + let (spec_id, mut executor) = { + let _span = debug_span!(target: "engine::tree", "create_evm").entered(); + let spec_id = *env.evm_env.spec_id(); + let evm = self.evm_config.evm_with_env(&mut db, env.evm_env); + let ctx = self + .execution_ctx_for(input) + .map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?; + let executor = self.evm_config.create_executor(evm, ctx); + (spec_id, executor) + }; + + if !self.config.precompile_cache_disabled() { + let _span = debug_span!(target: "engine::tree", "setup_precompile_cache").entered(); + executor + .evm_mut() + .precompiles_mut() + .map_cacheable_precompiles(|address, precompile| { + let metrics = self + .precompile_cache_metrics + .entry(*address) + .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address)) + .clone(); + CachedPrecompile::wrap( + precompile, + self.precompile_cache_map.cache_for_address(*address), + spec_id, + Some(metrics), + ) + }); + } + + // Spawn background task to compute receipt root and logs bloom incrementally. + // Unbounded channel is used since tx count bounds capacity anyway (max ~30k txs per block). + let receipts_len = input.transaction_count(); + let (receipt_tx, receipt_rx) = crossbeam_channel::unbounded(); + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + let task_handle = ReceiptRootTaskHandle::new(receipt_rx, result_tx); + self.payload_processor + .executor() + .spawn_blocking_named("receipt-root", move || task_handle.run(receipts_len)); + + let transaction_count = input.transaction_count(); + let executed_tx_index = Arc::clone(handle.executed_tx_index()); + let executor = executor.with_state_hook( + handle + .state_hook() + .map(|hook| Box::new(hook) as Box), + ); + + let execution_start = Instant::now(); + + // Execute all transactions and finalize + let (executor, senders) = self.execute_transactions( + executor, + transaction_count, + handle.iter_transactions(), + &receipt_tx, + &executed_tx_index, + )?; + drop(receipt_tx); + + // Finish execution and get the result + let post_exec_start = Instant::now(); + let (_evm, result) = debug_span!(target: "engine::tree", "BlockExecutor::finish") + .in_scope(|| executor.finish()) + .map(|(evm, result)| (evm.into_db(), result))?; + self.metrics + .record_post_execution(post_exec_start.elapsed()); + + // Merge transitions into bundle state + debug_span!(target: "engine::tree", "merge_transitions") + .in_scope(|| db.merge_transitions(BundleRetention::Reverts)); + + let output = BlockExecutionOutput { + result, + state: db.take_bundle(), + }; + + let execution_duration = execution_start.elapsed(); + self.metrics + .record_block_execution(&output, execution_duration); + self.metrics + .record_block_execution_gas_bucket(output.result.gas_used, execution_duration); + debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block"); + + Ok((output, senders, result_rx)) + } + + /// Executes transactions and collects senders, streaming receipts to a background task. + /// + /// This method handles: + /// - Applying pre-execution changes (e.g., beacon root updates) + /// - Executing each transaction with timing metrics + /// - Streaming receipts to the receipt root computation task + /// - Collecting transaction senders for later use + /// + /// Returns the executor (for finalization) and the collected senders. + fn execute_transactions( + &self, + mut executor: E, + transaction_count: usize, + transactions: impl Iterator>, + receipt_tx: &crossbeam_channel::Sender>, + executed_tx_index: &AtomicUsize, + ) -> Result<(E, Vec
), BlockExecutionError> + where + E: BlockExecutor, + Tx: alloy_evm::block::ExecutableTx + alloy_evm::RecoveredTx, + InnerTx: TxHashRef, + Err: core::error::Error + Send + Sync + 'static, + { + let mut senders = Vec::with_capacity(transaction_count); + + // Apply pre-execution changes (e.g., beacon root update) + let pre_exec_start = Instant::now(); + debug_span!(target: "engine::tree", "pre_execution") + .in_scope(|| executor.apply_pre_execution_changes())?; + self.metrics.record_pre_execution(pre_exec_start.elapsed()); + + // Execute transactions + let exec_span = debug_span!(target: "engine::tree", "execution").entered(); + let mut transactions = transactions.into_iter(); + // Some executors may execute transactions that do not append receipts during the + // main loop (e.g., system transactions whose receipts are added during finalization). + // In that case, invoking the callback on every transaction would resend the previous + // receipt with the same index and can panic the ordered root builder. + let mut last_sent_len = 0usize; + loop { + // Measure time spent waiting for next transaction from iterator + // (e.g., parallel signature recovery) + let wait_start = Instant::now(); + let Some(tx_result) = transactions.next() else { + break; + }; + self.metrics.record_transaction_wait(wait_start.elapsed()); + + let tx = tx_result.map_err(BlockExecutionError::other)?; + let tx_signer = *>::signer(&tx); + + senders.push(tx_signer); + + let _enter = debug_span!( + target: "engine::tree", + "execute tx", + tx_index = senders.len() - 1, + ) + .entered(); + trace!(target: "engine::tree", "Executing transaction"); + + let tx_start = Instant::now(); + executor.execute_transaction(tx)?; + self.metrics + .record_transaction_execution(tx_start.elapsed()); + + // advance the shared counter so prewarm workers skip already-executed txs + executed_tx_index.store(senders.len(), Ordering::Relaxed); + + let current_len = executor.receipts().len(); + if current_len > last_sent_len { + last_sent_len = current_len; + // Send the latest receipt to the background task for incremental root computation. + if let Some(receipt) = executor.receipts().last() { + let tx_index = current_len - 1; + let _ = receipt_tx.send(IndexedReceipt::new(tx_index, receipt.clone())); + } + } + } + drop(exec_span); + + Ok((executor, senders)) + } + + /// Compute state root for the given hashed post state in parallel. + /// + /// Uses an overlay factory which provides the state of the parent block, along with the + /// [`HashedPostState`] containing the changes of this block, to compute the state root and + /// trie updates for this block. + /// + /// # Returns + /// + /// Returns `Ok(_)` if computed successfully. + /// Returns `Err(_)` if error was encountered during computation. + #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)] + fn compute_state_root_parallel( + &self, + overlay_factory: OverlayStateProviderFactory, + hashed_state: &LazyHashedPostState, + ) -> Result<(B256, TrieUpdates), ParallelStateRootError> { + let hashed_state = hashed_state.get(); + // The `hashed_state` argument will be taken into account as part of the overlay, but we + // need to use the prefix sets which were generated from it to indicate to the + // ParallelStateRoot which parts of the trie need to be recomputed. + let prefix_sets = hashed_state.construct_prefix_sets().freeze(); + let overlay_factory = + overlay_factory.with_extended_hashed_state_overlay(hashed_state.clone_into_sorted()); + ParallelStateRoot::new(overlay_factory, prefix_sets, self.runtime.clone()) + .incremental_root_with_updates() + } + + /// Compute state root for the given hashed post state in serial. + /// + /// Uses an overlay factory which provides the state of the parent block, along with the + /// [`HashedPostState`] containing the changes of this block, to compute the state root and + /// trie updates for this block. + fn compute_state_root_serial( + overlay_factory: OverlayStateProviderFactory, + hashed_state: &LazyHashedPostState, + ) -> ProviderResult<(B256, TrieUpdates)> { + let hashed_state = hashed_state.get(); + // The `hashed_state` argument will be taken into account as part of the overlay, but we + // need to use the prefix sets which were generated from it to indicate to the + // StateRoot which parts of the trie need to be recomputed. + let prefix_sets = hashed_state.construct_prefix_sets().freeze(); + let overlay_factory = + overlay_factory.with_extended_hashed_state_overlay(hashed_state.clone_into_sorted()); + + let provider = overlay_factory.database_provider_ro()?; + + Ok(StateRoot::new(&provider, &provider) + .with_prefix_sets(prefix_sets) + .root_with_updates()?) + } + + /// Awaits the state root from the background task, with an optional timeout fallback. + /// + /// If a timeout is configured (`state_root_task_timeout`), this method first waits for the + /// state root task up to the timeout duration. If the task doesn't complete in time, a + /// sequential state root computation is spawned via `spawn_blocking`. Both computations + /// then race: the main thread polls the task receiver and the sequential result channel + /// in a loop, returning whichever finishes first. + /// + /// If no timeout is configured, this simply awaits the state root task without any fallback. + /// + /// Returns `ProviderResult>` where the outer `ProviderResult` captures + /// unrecoverable errors from the sequential fallback (e.g. DB errors), while the inner + /// `Result` captures parallel state root task errors that can still fall back to serial. + #[instrument( + level = "debug", + target = "engine::tree::payload_validator", + name = "await_state_root", + skip_all + )] + fn await_state_root_with_timeout( + &self, + handle: &mut PayloadHandle, + overlay_factory: OverlayStateProviderFactory, + hashed_state: &LazyHashedPostState, + ) -> ProviderResult> { + let Some(timeout) = self.config.state_root_task_timeout() else { + return Ok(handle.state_root()); + }; + + let task_rx = handle.take_state_root_rx(); + + match task_rx.recv_timeout(timeout) { + Ok(result) => Ok(result), + Err(RecvTimeoutError::Disconnected) => Ok(Err(ParallelStateRootError::Other( + "sparse trie task dropped".to_string(), + ))), + Err(RecvTimeoutError::Timeout) => { + warn!( + target: "engine::tree::payload_validator", + ?timeout, + "State root task timed out, spawning sequential fallback" + ); + self.metrics + .block_validation + .state_root_task_timeout_total + .increment(1); + + let (seq_tx, seq_rx) = + std::sync::mpsc::channel::>(); + + let seq_overlay = overlay_factory; + let seq_hashed_state = hashed_state.clone(); + self.payload_processor + .executor() + .spawn_blocking_named("serial-root", move || { + let result = + Self::compute_state_root_serial(seq_overlay, &seq_hashed_state); + let _ = seq_tx.send(result); + }); + + const POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(10); + + loop { + match task_rx.recv_timeout(POLL_INTERVAL) { + Ok(result) => { + debug!( + target: "engine::tree::payload_validator", + source = "task", + "State root timeout race won" + ); + return Ok(result); + } + Err(RecvTimeoutError::Disconnected) => { + debug!( + target: "engine::tree::payload_validator", + "State root task dropped, waiting for sequential fallback" + ); + let result = seq_rx.recv().map_err(|_| { + ProviderError::other(std::io::Error::other( + "both state root computations failed", + )) + })?; + let (state_root, trie_updates) = result?; + return Ok(Ok(StateRootComputeOutcome { + state_root, + trie_updates: Arc::new(trie_updates), + #[cfg(feature = "trie-debug")] + debug_recorders: Vec::new(), + })); + } + Err(RecvTimeoutError::Timeout) => {} + } + + if let Ok(result) = seq_rx.try_recv() { + debug!( + target: "engine::tree::payload_validator", + source = "sequential", + "State root timeout race won" + ); + let (state_root, trie_updates) = result?; + return Ok(Ok(StateRootComputeOutcome { + state_root, + trie_updates: Arc::new(trie_updates), + #[cfg(feature = "trie-debug")] + debug_recorders: Vec::new(), + })); + } + } + } + } + } + + /// Compares trie updates from the state root task with serial state root computation. + /// + /// This is used for debugging and validating the correctness of the parallel state root + /// task implementation. When enabled via `--engine.state-root-task-compare-updates`, this + /// method runs a separate serial state root computation and compares the resulting trie + /// updates. + fn compare_trie_updates_with_serial( + &self, + overlay_factory: OverlayStateProviderFactory, + hashed_state: &LazyHashedPostState, + task_trie_updates: TrieUpdates, + ) -> bool { + debug!(target: "engine::tree::payload_validator", "Comparing trie updates with serial computation"); + + match Self::compute_state_root_serial(overlay_factory.clone(), hashed_state) { + Ok((serial_root, serial_trie_updates)) => { + debug!( + target: "engine::tree::payload_validator", + ?serial_root, + "Serial state root computation finished for comparison" + ); + + // Get a database provider to use as trie cursor factory + match overlay_factory.database_provider_ro() { + Ok(provider) => { + match crate::trie_updates::compare_trie_updates( + &provider, + task_trie_updates, + serial_trie_updates, + ) { + Ok(has_diff) => return has_diff, + Err(err) => { + warn!( + target: "engine::tree::payload_validator", + %err, + "Error comparing trie updates" + ); + return true; + } + } + } + Err(err) => { + warn!( + target: "engine::tree::payload_validator", + %err, + "Failed to get database provider for trie update comparison" + ); + } + } + } + Err(err) => { + warn!( + target: "engine::tree::payload_validator", + %err, + "Failed to compute serial state root for comparison" + ); + } + } + false + } + + /// Writes trie debug recorders to a JSON file for the given block number. + /// + /// The file is written to the current working directory as + /// `trie_debug_block_{block_number}.json`. + #[cfg(feature = "trie-debug")] + fn write_trie_debug_recorders( + block_number: u64, + recorders: &[(Option, TrieDebugRecorder)], + ) { + let path = format!("trie_debug_block_{block_number}.json"); + match serde_json::to_string_pretty(recorders) { + Ok(json) => match std::fs::write(&path, json) { + Ok(()) => { + warn!( + target: "engine::tree::payload_validator", + %path, + "Wrote trie debug recorders to file" + ); + } + Err(err) => { + warn!( + target: "engine::tree::payload_validator", + %err, + %path, + "Failed to write trie debug recorders" + ); + } + }, + Err(err) => { + warn!( + target: "engine::tree::payload_validator", + %err, + "Failed to serialize trie debug recorders" + ); + } + } + } + + /// Validates the block after execution. + /// + /// This performs: + /// - parent header validation + /// - post-execution consensus validation + /// - state-root based post-execution validation + /// + /// If `receipt_root_bloom` is provided, it will be used instead of computing the receipt root + /// and logs bloom from the receipts. + /// + /// The `hashed_state` handle wraps the background hashed post state computation. + #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)] + #[expect(clippy::too_many_arguments)] + fn validate_post_execution>>( + &self, + block: &RecoveredBlock, + parent_block: &SealedHeader, + output: &BlockExecutionOutput, + ctx: &mut TreeCtx<'_, N>, + transaction_root: Option, + receipt_root_bloom: Option, + hashed_state: LazyHashedPostState, + ) -> Result + where + V: PayloadValidator, + { + let start = Instant::now(); + + trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus"); + // validate block consensus rules + if let Err(e) = self.validate_block_inner(block, transaction_root) { + return Err(e.into()); + } + + // now validate against the parent + let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered(); + if let Err(e) = self + .consensus + .validate_header_against_parent(block.sealed_header(), parent_block) + { + warn!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {} against parent: {e}", block.hash()); + return Err(e.into()); + } + drop(_enter); + + // Validate block post-execution rules + let _enter = + debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution") + .entered(); + if let Err(err) = + self.consensus + .validate_block_post_execution(block, output, receipt_root_bloom) + { + // call post-block hook + self.on_invalid_block(parent_block, block, output, None, ctx.state_mut()); + return Err(err.into()); + } + drop(_enter); + + // Wait for the background keccak256 hashing task to complete. This blocks until + // all changed addresses and storage slots have been hashed. + let hashed_state_ref = + debug_span!(target: "engine::tree::payload_validator", "wait_hashed_post_state") + .in_scope(|| hashed_state.get()); + + let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution_with_hashed_state").entered(); + if let Err(err) = self + .validator + .validate_block_post_execution_with_hashed_state(hashed_state_ref, block) + { + // call post-block hook + self.on_invalid_block(parent_block, block, output, None, ctx.state_mut()); + return Err(err.into()); + } + + // record post-execution validation duration + self.metrics + .block_validation + .post_execution_validation_duration + .record(start.elapsed().as_secs_f64()); + + Ok(hashed_state) + } + + /// Spawns a payload processor task based on the state root strategy. + /// + /// This method determines how to execute the block and compute its state root based on + /// the selected strategy: + /// - `StateRootTask`: Uses a dedicated task for state root computation with proof generation + /// - `Parallel`: Computes state root in parallel with block execution + /// - `Synchronous`: Falls back to sequential execution and state root computation + /// + /// The method handles strategy fallbacks if the preferred approach fails, ensuring + /// block execution always completes with a valid state root. + /// + /// # Arguments + /// + /// * `overlay_factory` - Pre-computed overlay factory for multiproof generation + /// (`StateRootTask`) + #[instrument( + level = "debug", + target = "engine::tree::payload_validator", + skip_all, + fields(?strategy) + )] + fn spawn_payload_processor>( + &mut self, + env: ExecutionEnv, + txs: T, + provider_builder: StateProviderBuilder, + overlay_factory: OverlayStateProviderFactory, + strategy: StateRootStrategy, + ) -> Result< + PayloadHandle< + impl ExecutableTxFor + use, + impl core::error::Error + Send + Sync + 'static + use, + N::Receipt, + >, + InsertBlockErrorKind, + > { + match strategy { + StateRootStrategy::StateRootTask => { + let spawn_start = Instant::now(); + + let handle = self.payload_processor.spawn( + env, + txs, + provider_builder, + overlay_factory, + &self.config, + ); + + // record prewarming initialization duration + self.metrics + .block_validation + .spawn_payload_processor + .record(spawn_start.elapsed().as_secs_f64()); + + Ok(handle) + } + StateRootStrategy::Parallel | StateRootStrategy::Synchronous => { + let start = Instant::now(); + let handle = + self.payload_processor + .spawn_cache_exclusive(env, txs, provider_builder); + + // Record prewarming initialization duration + self.metrics + .block_validation + .spawn_payload_processor + .record(start.elapsed().as_secs_f64()); + + Ok(handle) + } + } + } + + /// Creates a `StateProviderBuilder` for the given parent hash. + /// + /// This method checks if the parent is in the tree state (in-memory) or persisted to disk, + /// and creates the appropriate provider builder. + fn state_provider_builder( + &self, + hash: B256, + state: &EngineApiTreeState, + ) -> ProviderResult>> { + if let Some((historical, blocks)) = state.tree_state().blocks_by_hash(hash) { + debug!(target: "engine::tree::payload_validator", %hash, %historical, "found canonical state for block in memory, creating provider builder"); + // the block leads back to the canonical chain + return Ok(Some(StateProviderBuilder::new( + self.provider.clone(), + historical, + Some(blocks), + ))); + } + + // Check if the block is persisted + if let Some(header) = self.provider.header(hash)? { + debug!(target: "engine::tree::payload_validator", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder"); + // For persisted blocks, we create a builder that will fetch state directly from the + // database + return Ok(Some(StateProviderBuilder::new( + self.provider.clone(), + hash, + None, + ))); + } + + debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block"); + Ok(None) + } + + /// Determines the state root computation strategy based on configuration. + /// + /// Note: Use state root task only if prefix sets are empty, otherwise proof generation is + /// too expensive because it requires walking all paths in every proof. + const fn plan_state_root_computation(&self) -> StateRootStrategy { + if self.config.state_root_fallback() { + StateRootStrategy::Synchronous + } else if self.config.use_state_root_task() { + StateRootStrategy::StateRootTask + } else { + StateRootStrategy::Parallel + } + } + + /// Called when an invalid block is encountered during validation. + fn on_invalid_block( + &self, + parent_header: &SealedHeader, + block: &RecoveredBlock, + output: &BlockExecutionOutput, + trie_updates: Option<(&TrieUpdates, B256)>, + state: &mut EngineApiTreeState, + ) { + if state.has_invalid_header(&block.hash()) { + // we already marked this block as invalid + return; + } + self.invalid_block_hook + .on_invalid_block(parent_header, block, output, trie_updates); + } + + /// Creates a [`LazyOverlay`] for the parent block without blocking. + /// + /// Returns a lazy overlay that will compute the trie input on first access, and the anchor + /// block hash (the highest persisted ancestor). This allows execution to start immediately + /// while the trie input computation is deferred until the overlay is actually needed. + /// + /// If parent is on disk (no in-memory blocks), returns `None` for the lazy overlay. + /// + /// Uses a cached overlay if available for the canonical head (the common case). + fn get_parent_lazy_overlay( + parent_hash: B256, + state: &EngineApiTreeState, + ) -> (Option>, B256) { + // Get blocks leading to the parent to determine the anchor + let (anchor_hash, blocks) = state + .tree_state() + .blocks_by_hash(parent_hash) + .unwrap_or_else(|| (parent_hash, vec![])); + + if blocks.is_empty() { + debug!(target: "engine::tree::payload_validator", "Parent found on disk, no lazy overlay needed"); + return (None, anchor_hash); + } + + // Try to use the cached overlay if it matches both parent hash and anchor + if let Some(cached) = state + .tree_state() + .get_cached_overlay(parent_hash, anchor_hash) + { + debug!( + target: "engine::tree::payload_validator", + %parent_hash, + %anchor_hash, + "Using cached canonical overlay" + ); + return (Some(cached.overlay.clone()), cached.anchor_hash); + } + + debug!( + target: "engine::tree::payload_validator", + %anchor_hash, + num_blocks = blocks.len(), + "Creating lazy overlay for in-memory blocks" + ); + + // v2.2.0 ingests the executed blocks themselves; per-block + // `DeferredTrieData` extraction is no longer required. + (Some(LazyOverlay::new(blocks)), anchor_hash) + } + + /// Spawns a background task to compute and sort trie data for the executed block. + /// + /// This function creates a [`DeferredTrieData`] handle with fallback inputs and spawns a + /// blocking task that calls `wait_cloned()` to: + /// 1. Sort the block's hashed state and trie updates + /// 2. Merge ancestor overlays and extend with the sorted data + /// 3. Create an [`AnchoredTrieInput`](reth_chain_state::AnchoredTrieInput) for efficient future + /// trie computations + /// 4. Cache the result so subsequent calls return immediately + /// + /// If the background task hasn't completed when `trie_data()` is called, `wait_cloned()` + /// computes from the stored inputs, eliminating deadlock risk and duplicate computation. + /// + /// The validation hot path can return immediately after state root verification, + /// while consumers (DB writes, overlay providers, proofs) get trie data either + /// from the completed task or via fallback computation. + fn spawn_deferred_trie_task( + &self, + block: RecoveredBlock, + execution_outcome: Arc>, + ctx: &TreeCtx<'_, N>, + hashed_state: LazyHashedPostState, + trie_output: Arc, + changeset_provider: impl TrieCursorFactory + Send + 'static, + ) -> ExecutedBlock { + // Capture parent hash and ancestor overlays for deferred trie input construction. + let (anchor_hash, overlay_blocks) = ctx + .state() + .tree_state() + .blocks_by_hash(block.parent_hash()) + .unwrap_or_else(|| (block.parent_hash(), Vec::new())); + + // Collect lightweight ancestor trie data handles. We don't call trie_data() here; + // the merge and any fallback sorting happens in the compute_trie_input_task. + let ancestors: Vec = overlay_blocks + .iter() + .rev() + .map(|b| b.trie_data_handle()) + .collect(); + + // Create deferred handle with fallback inputs in case the background task hasn't completed. + // Resolve the lazy handle into Arc. By this point the hashed state has + // already been computed and used for state root verification, so .get() returns instantly. + let hashed_state = match hashed_state.try_into_inner() { + Ok(state) => Arc::new(state), + Err(handle) => Arc::new(handle.get().clone()), + }; + let deferred_trie_data = + DeferredTrieData::pending(hashed_state, trie_output, anchor_hash, ancestors); + let deferred_handle_task = deferred_trie_data.clone(); + let block_validation_metrics = self.metrics.block_validation.clone(); + + // Capture block info and cache handle for changeset computation + let block_hash = block.hash(); + let block_number = block.number(); + + // Register a pending changeset entry so that concurrent readers will wait for + // this computation to finish rather than falling back to the expensive DB path. + // The guard ensures the pending entry is cancelled if the task panics. + let pending_changeset_guard = self.changeset_cache.register_pending(block_hash); + + // Spawn background task to compute trie data. Calling `wait_cloned` will compute from + // the stored inputs and cache the result, so subsequent calls return immediately. + let compute_trie_input_task = move || { + let _span = debug_span!( + target: "engine::tree::payload_validator", + "compute_trie_input_task", + block_number + ) + .entered(); + + let result = panic::catch_unwind(AssertUnwindSafe(|| { + let compute_start = Instant::now(); + let computed = deferred_handle_task.wait_cloned(); + block_validation_metrics + .deferred_trie_compute_duration + .record(compute_start.elapsed().as_secs_f64()); + + // Record sizes of the computed trie data + block_validation_metrics + .hashed_post_state_size + .record(computed.hashed_state.total_len() as f64); + block_validation_metrics + .trie_updates_sorted_size + .record(computed.trie_updates.total_len() as f64); + if let Some(anchored) = &computed.anchored_trie_input { + block_validation_metrics + .anchored_overlay_trie_updates_size + .record(anchored.trie_input.nodes.total_len() as f64); + block_validation_metrics + .anchored_overlay_hashed_state_size + .record(anchored.trie_input.state.total_len() as f64); + } + + // Compute and cache changesets using the computed trie_updates. + // Use the pre-created provider to avoid races with changeset cache + // eviction that can happen between task spawn and execution. + let changeset_start = Instant::now(); + + match reth_trie::changesets::compute_trie_changesets( + &changeset_provider, + &computed.trie_updates, + ) { + Ok(changesets) => { + debug!( + target: "engine::tree::changeset", + ?block_number, + elapsed = ?changeset_start.elapsed(), + "Computed and caching changesets" + ); + + pending_changeset_guard.resolve(block_number, Arc::new(changesets)); + } + Err(e) => { + warn!( + target: "engine::tree::changeset", + ?block_number, + ?e, + "Failed to compute changesets in deferred trie task" + ); + } + } + })); + + if result.is_err() { + error!( + target: "engine::tree::payload_validator", + "Deferred trie task panicked; fallback computation will be used when trie data is accessed" + ); + } + }; + + // Spawn task that computes trie data asynchronously. + self.payload_processor + .executor() + .spawn_blocking_named("trie-input", compute_trie_input_task); + + ExecutedBlock::with_deferred_trie_data( + Arc::new(block), + execution_outcome, + deferred_trie_data, + ) + } + + fn calculate_timing_stats( + &self, + block: &RecoveredBlock, + provider_stats: Arc, + cache_stats: Option>, + output: &BlockExecutionOutput, + execution_duration: Duration, + state_hash_duration: Duration, + ) -> Box { + let accounts_read = provider_stats.total_account_fetches(); + let storage_read = provider_stats.total_storage_fetches(); + let code_read = provider_stats.total_code_fetches(); + let code_bytes_read = provider_stats.total_code_fetched_bytes(); + + // Write stats from BundleState (final state changes) + let accounts_changed = output.state.state.len(); + let accounts_deleted = output + .state + .state + .values() + .filter(|acc| acc.was_destroyed()) + .count(); + let storage_slots_changed = output + .state + .state + .values() + .map(|account| account.storage.len()) + .sum::(); + let storage_slots_deleted = output + .state + .state + .values() + .flat_map(|account| account.storage.values()) + .filter(|slot| { + slot.present_value.is_zero() && !slot.previous_or_original_value.is_zero() + }) + .count(); + + // Helper: check if account represents a new contract deployment + let is_new_deployment = |acc: &BundleAccount| -> bool { + let has_code_now = acc + .info + .as_ref() + .is_some_and(|info| info.code_hash != KECCAK_EMPTY); + let had_no_code_before = acc + .original_info + .as_ref() + .map(|info| info.code_hash == KECCAK_EMPTY) + .unwrap_or(true); + has_code_now && had_no_code_before + }; + + let bytecodes_changed = output + .state + .state + .values() + .filter(|acc| is_new_deployment(acc)) + .count(); + + // Unique new code hashes to count actual bytes persisted (deduplicated) + let unique_new_code_hashes: B256Set = output + .state + .state + .values() + .filter(|acc| is_new_deployment(acc)) + .filter_map(|acc| acc.info.as_ref().map(|info| info.code_hash)) + .collect(); + let code_bytes_written: usize = unique_new_code_hashes + .iter() + .filter_map(|hash| { + output + .state + .contracts + .get(hash) + .map(|bytecode| bytecode.original_bytes().len()) + }) + .sum(); + + // Total time spent fetching state during execution + let state_read_duration = provider_stats.total_account_fetch_latency() + + provider_stats.total_storage_fetch_latency() + + provider_stats.total_code_fetch_latency(); + + // EIP-7702 delegation tracking from bytecode changes + // Count new EIP-7702 bytecodes as delegations set + let eip7702_delegations_set = output + .state + .contracts + .values() + .filter(|bytecode| bytecode.is_eip7702()) + .count(); + // Delegations cleared: accounts where bytecode changed FROM EIP-7702 TO empty + // This detects when an EIP-7702 delegation is removed by setting code to empty + // Note: Clearing a delegation does NOT destroy the account - it just empties the + // bytecode + let eip7702_delegations_cleared = output + .state + .state + .values() + .filter(|acc| { + // Check if original bytecode was EIP-7702 + let original_was_eip7702 = acc + .original_info + .as_ref() + .and_then(|info| info.code.as_ref()) + .map(|bytecode| bytecode.is_eip7702()) + .unwrap_or(false); + + // Check if current code is empty (delegation cleared) + let code_now_empty = acc + .info + .as_ref() + .map(|info| info.code_hash == KECCAK_EMPTY) + .unwrap_or(false); + + original_was_eip7702 && code_now_empty + }) + .count(); + + // Get cache statistics for detailed block logging + let (account_cache_hits, account_cache_misses) = cache_stats + .as_ref() + .map(|s| (s.account_hits(), s.account_misses())) + .unwrap_or_default(); + let (storage_cache_hits, storage_cache_misses) = cache_stats + .as_ref() + .map(|s| (s.storage_hits(), s.storage_misses())) + .unwrap_or_default(); + let (code_cache_hits, code_cache_misses) = cache_stats + .as_ref() + .map(|s| (s.code_hits(), s.code_misses())) + .unwrap_or_default(); + + // Build execution timing stats for detailed block logging + Box::new(ExecutionTimingStats { + block_number: block.number(), + block_hash: block.hash(), + gas_used: output.result.gas_used, + tx_count: block.transaction_count(), + execution_duration, + state_read_duration, + state_hash_duration, + accounts_read, + storage_read, + code_read, + code_bytes_read, + accounts_changed, + accounts_deleted, + storage_slots_changed, + storage_slots_deleted, + bytecodes_changed, + code_bytes_written, + eip7702_delegations_set, + eip7702_delegations_cleared, + account_cache_hits, + account_cache_misses, + storage_cache_hits, + storage_cache_misses, + code_cache_hits, + code_cache_misses, + }) + } +} + +/// Strategy describing how to compute the state root. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum StateRootStrategy { + /// Use the state root task (background sparse trie computation). + StateRootTask, + /// Run the parallel state root computation on the calling thread. + Parallel, + /// Fall back to synchronous computation via the state provider. + Synchronous, +} + +impl EngineValidator for MorphBasicEngineValidator +where + P: DatabaseProviderFactory< + Provider: BlockReader + + StageCheckpointReader + + PruneCheckpointReader + + ChangeSetReader + + StorageChangeSetReader + + BlockNumReader + + StorageSettingsCache, + > + BlockReader
+ + StateProviderFactory + + StateReader + + ChangeSetReader + + BlockNumReader + + HashedPostStateProvider + + Clone + + 'static, + N: NodePrimitives, + V: PayloadValidator + Clone, + Evm: ConfigureEngineEvm + 'static, + Types: PayloadTypes>, +{ + fn validate_payload_attributes_against_header( + &self, + attr: &Types::PayloadAttributes, + header: &N::BlockHeader, + ) -> Result<(), InvalidPayloadAttributesError> { + self.validator + .validate_payload_attributes_against_header(attr, header) + } + + fn convert_payload_to_block( + &self, + payload: Types::ExecutionData, + ) -> Result, NewPayloadError> { + let block = self.validator.convert_payload_to_block(payload)?; + Ok(block) + } + + fn validate_payload( + &mut self, + payload: Types::ExecutionData, + ctx: TreeCtx<'_, N>, + ) -> ValidationOutcome { + self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx) + } + + fn validate_block( + &mut self, + block: SealedBlock, + ctx: TreeCtx<'_, N>, + ) -> ValidationOutcome { + self.validate_block_with_state(BlockOrPayload::Block(block), ctx) + } + + fn on_inserted_executed_block(&self, block: ExecutedBlock) { + self.payload_processor.on_inserted_executed_block( + block.recovered_block.block_with_parent(), + &block.execution_output.state, + ); + } + + fn cache_for(&self, block_hash: B256) -> Option { + Some(self.payload_processor.cache_for(block_hash)) + } + + fn sparse_trie_handle_for( + &self, + parent_hash: B256, + parent_state_root: B256, + state: &EngineApiTreeState, + ) -> Option { + let (lazy_overlay, anchor_hash) = Self::get_parent_lazy_overlay(parent_hash, state); + let overlay_builder = OverlayBuilder::::new(anchor_hash, self.changeset_cache.clone()) + .with_lazy_overlay(lazy_overlay); + let overlay_factory = + OverlayStateProviderFactory::new(self.provider.clone(), overlay_builder); + + Some(self.payload_processor.spawn_state_root( + overlay_factory, + parent_state_root, + // Full proof workers — tx count unknown at FCU time (block built incrementally) + false, + &self.config, + )) + } +} + +impl WaitForCaches for MorphBasicEngineValidator +where + Evm: ConfigureEvm, +{ + fn wait_for_caches(&self) -> CacheWaitDurations { + self.payload_processor.wait_for_caches() + } +} + +/// Enum representing either block or payload being validated. +#[derive(Debug, Clone)] +pub enum BlockOrPayload { + /// Payload. + Payload(T::ExecutionData), + /// Block. + Block(SealedBlock::Primitives>>), +} + +impl BlockOrPayload { + /// Returns the hash of the block. + pub fn hash(&self) -> B256 { + match self { + Self::Payload(payload) => payload.block_hash(), + Self::Block(block) => block.hash(), + } + } + + /// Returns the number and hash of the block. + pub fn num_hash(&self) -> NumHash { + match self { + Self::Payload(payload) => payload.num_hash(), + Self::Block(block) => block.num_hash(), + } + } + + /// Returns the parent hash of the block. + pub fn parent_hash(&self) -> B256 { + match self { + Self::Payload(payload) => payload.parent_hash(), + Self::Block(block) => block.parent_hash(), + } + } + + /// Returns [`BlockWithParent`] for the block. + pub fn block_with_parent(&self) -> BlockWithParent { + match self { + Self::Payload(payload) => payload.block_with_parent(), + Self::Block(block) => block.block_with_parent(), + } + } + + /// Returns a string showing whether or not this is a block or payload. + pub const fn type_name(&self) -> &'static str { + match self { + Self::Payload(_) => "payload", + Self::Block(_) => "block", + } + } + + /// Returns the block access list if available. + pub const fn block_access_list(&self) -> Option> { + // TODO decode and return `BlockAccessList` + None + } + + /// Returns the number of transactions in the payload or block. + pub fn transaction_count(&self) -> usize + where + T::ExecutionData: ExecutionPayload, + { + match self { + Self::Payload(payload) => payload.transaction_count(), + Self::Block(block) => block.transaction_count(), + } + } + + /// Returns the withdrawals from the payload or block. + pub fn withdrawals(&self) -> Option<&[Withdrawal]> + where + T::ExecutionData: ExecutionPayload, + { + match self { + Self::Payload(payload) => payload.withdrawals().map(|w| w.as_slice()), + Self::Block(block) => block.body().withdrawals().map(|w| w.as_slice()), + } + } + + /// Returns the total gas used by the block. + pub fn gas_used(&self) -> u64 + where + T::ExecutionData: ExecutionPayload, + { + match self { + Self::Payload(payload) => payload.gas_used(), + Self::Block(block) => block.gas_used(), + } + } +} diff --git a/crates/engine-tree-ext/src/trie_updates.rs b/crates/engine-tree-ext/src/trie_updates.rs new file mode 100644 index 00000000..3ac7b523 --- /dev/null +++ b/crates/engine-tree-ext/src/trie_updates.rs @@ -0,0 +1,357 @@ +use alloy_primitives::{ + B256, + map::{B256Map, HashMap}, +}; +use reth_db::DatabaseError; +use reth_trie::{ + BranchNodeCompact, Nibbles, + trie_cursor::{TrieCursor, TrieCursorFactory}, + updates::{StorageTrieUpdates, TrieUpdates}, +}; +use std::collections::BTreeSet; +use tracing::warn; + +#[derive(Debug)] +struct EntryDiff { + task: T, + regular: T, + database: T, +} + +#[derive(Debug, Default)] +struct TrieUpdatesDiff { + account_nodes: HashMap>>, + removed_nodes: HashMap>, + storage_tries: B256Map, +} + +impl TrieUpdatesDiff { + fn has_differences(&self) -> bool { + !self.account_nodes.is_empty() + || !self.removed_nodes.is_empty() + || !self.storage_tries.is_empty() + } + + pub(super) fn log_differences(mut self) { + if self.has_differences() { + for ( + path, + EntryDiff { + task, + regular, + database, + }, + ) in &mut self.account_nodes + { + warn!(target: "engine::tree", ?path, ?task, ?regular, ?database, "Difference in account trie updates"); + } + + for ( + path, + EntryDiff { + task: task_removed, + regular: regular_removed, + database: database_not_exists, + }, + ) in &self.removed_nodes + { + warn!(target: "engine::tree", ?path, ?task_removed, ?regular_removed, ?database_not_exists, "Difference in removed account trie nodes"); + } + + for (address, storage_diff) in self.storage_tries { + storage_diff.log_differences(address); + } + } + } +} + +#[derive(Debug, Default)] +struct StorageTrieUpdatesDiff { + is_deleted: Option>, + storage_nodes: HashMap>>, + removed_nodes: HashMap>, +} + +impl StorageTrieUpdatesDiff { + fn has_differences(&self) -> bool { + self.is_deleted.is_some() + || !self.storage_nodes.is_empty() + || !self.removed_nodes.is_empty() + } + + fn log_differences(&self, address: B256) { + if let Some(EntryDiff { + task: task_deleted, + regular: regular_deleted, + database: database_not_exists, + }) = self.is_deleted + { + warn!(target: "engine::tree", ?address, ?task_deleted, ?regular_deleted, ?database_not_exists, "Difference in storage trie deletion"); + } + + for ( + path, + EntryDiff { + task, + regular, + database, + }, + ) in &self.storage_nodes + { + warn!(target: "engine::tree", ?address, ?path, ?task, ?regular, ?database, "Difference in storage trie updates"); + } + + for ( + path, + EntryDiff { + task: task_removed, + regular: regular_removed, + database: database_not_exists, + }, + ) in &self.removed_nodes + { + warn!(target: "engine::tree", ?address, ?path, ?task_removed, ?regular_removed, ?database_not_exists, "Difference in removed storage trie nodes"); + } + } +} + +/// Compares the trie updates from state root task, regular state root calculation and database, +/// and logs the differences if there's any. +/// +/// Returns `true` if there are differences. +pub(crate) fn compare_trie_updates( + trie_cursor_factory: impl TrieCursorFactory, + task: TrieUpdates, + regular: TrieUpdates, +) -> Result { + let mut task = adjust_trie_updates(task); + let mut regular = adjust_trie_updates(regular); + + let mut diff = TrieUpdatesDiff::default(); + + // compare account nodes + let mut account_trie_cursor = trie_cursor_factory.account_trie_cursor()?; + for key in task + .account_nodes + .keys() + .chain(regular.account_nodes.keys()) + .copied() + .collect::>() + { + let (task, regular) = ( + task.account_nodes.remove(&key), + regular.account_nodes.remove(&key), + ); + let database = account_trie_cursor.seek_exact(key)?.map(|x| x.1); + + if !branch_nodes_equal(task.as_ref(), regular.as_ref(), database.as_ref())? { + diff.account_nodes.insert( + key, + EntryDiff { + task, + regular, + database, + }, + ); + } + } + + // compare removed nodes + let mut account_trie_cursor = trie_cursor_factory.account_trie_cursor()?; + for key in task + .removed_nodes + .iter() + .chain(regular.removed_nodes.iter()) + .copied() + .collect::>() + { + let (task_removed, regular_removed) = ( + task.removed_nodes.contains(&key), + regular.removed_nodes.contains(&key), + ); + let database_not_exists = account_trie_cursor.seek_exact(key)?.is_none(); + // If the deletion is a no-op, meaning that the entry is not in the + // database, do not add it to the diff. + if task_removed != regular_removed && !database_not_exists { + diff.removed_nodes.insert( + key, + EntryDiff { + task: task_removed, + regular: regular_removed, + database: database_not_exists, + }, + ); + } + } + + // compare storage tries + for key in task + .storage_tries + .keys() + .chain(regular.storage_tries.keys()) + .copied() + .collect::>() + { + let (mut task, mut regular) = ( + task.storage_tries.remove(&key), + regular.storage_tries.remove(&key), + ); + if task != regular { + #[expect(clippy::or_fun_call)] + let storage_diff = compare_storage_trie_updates( + || trie_cursor_factory.storage_trie_cursor(key), + // Compare non-existent storage tries as empty. + task.as_mut().unwrap_or(&mut Default::default()), + regular.as_mut().unwrap_or(&mut Default::default()), + )?; + if storage_diff.has_differences() { + diff.storage_tries.insert(key, storage_diff); + } + } + } + + // log differences + let has_differences = diff.has_differences(); + diff.log_differences(); + + Ok(has_differences) +} + +fn compare_storage_trie_updates( + trie_cursor: impl Fn() -> Result, + task: &mut StorageTrieUpdates, + regular: &mut StorageTrieUpdates, +) -> Result { + // Check if the storage trie exists by seeking to the first entry + let database_not_exists = trie_cursor()?.seek(Nibbles::default())?.is_none(); + let mut diff = StorageTrieUpdatesDiff { + // If the deletion is a no-op, meaning that the entry is not in the + // database, do not add it to the diff. + is_deleted: (task.is_deleted != regular.is_deleted && !database_not_exists).then_some( + EntryDiff { + task: task.is_deleted, + regular: regular.is_deleted, + database: database_not_exists, + }, + ), + ..Default::default() + }; + + // compare storage nodes + let mut storage_trie_cursor = trie_cursor()?; + for key in task + .storage_nodes + .keys() + .chain(regular.storage_nodes.keys()) + .copied() + .collect::>() + { + let (task, regular) = ( + task.storage_nodes.remove(&key), + regular.storage_nodes.remove(&key), + ); + let database = storage_trie_cursor.seek_exact(key)?.map(|x| x.1); + if !branch_nodes_equal(task.as_ref(), regular.as_ref(), database.as_ref())? { + diff.storage_nodes.insert( + key, + EntryDiff { + task, + regular, + database, + }, + ); + } + } + + // compare removed nodes + let mut storage_trie_cursor = trie_cursor()?; + for key in task + .removed_nodes + .iter() + .chain(regular.removed_nodes.iter()) + .collect::>() + { + let (task_removed, regular_removed) = ( + task.removed_nodes.contains(key), + regular.removed_nodes.contains(key), + ); + if task_removed == regular_removed { + continue; + } + let database_not_exists = storage_trie_cursor.seek_exact(*key)?.map(|x| x.1).is_none(); + // If the deletion is a no-op, meaning that the entry is not in the + // database, do not add it to the diff. + if !database_not_exists { + diff.removed_nodes.insert( + *key, + EntryDiff { + task: task_removed, + regular: regular_removed, + database: database_not_exists, + }, + ); + } + } + + Ok(diff) +} + +/// Filters the removed nodes of both account trie updates and storage trie updates, so that they +/// don't include those nodes that were also updated. +fn adjust_trie_updates(trie_updates: TrieUpdates) -> TrieUpdates { + TrieUpdates { + removed_nodes: trie_updates + .removed_nodes + .into_iter() + .filter(|key| !trie_updates.account_nodes.contains_key(key)) + .collect(), + storage_tries: trie_updates + .storage_tries + .into_iter() + .map(|(address, updates)| { + ( + address, + StorageTrieUpdates { + removed_nodes: updates + .removed_nodes + .into_iter() + .filter(|key| !updates.storage_nodes.contains_key(key)) + .collect(), + ..updates + }, + ) + }) + .collect(), + ..trie_updates + } +} + +/// Compares the branch nodes from state root task and regular state root calculation. +/// +/// If one of the branch nodes is [`None`], it means it's not updated and the other is compared to +/// the branch node from the database. +/// +/// Returns `true` if they are equal. +fn branch_nodes_equal( + task: Option<&BranchNodeCompact>, + regular: Option<&BranchNodeCompact>, + database: Option<&BranchNodeCompact>, +) -> Result { + Ok(match (task, regular) { + (Some(task), Some(regular)) => { + task.state_mask == regular.state_mask + && task.tree_mask == regular.tree_mask + && task.hash_mask == regular.hash_mask + && task.hashes == regular.hashes + && task.root_hash == regular.root_hash + } + (None, None) => true, + _ => { + if task.is_some() { + task == database + } else { + regular == database + } + } + }) +} diff --git a/crates/engine-tree-ext/tests/jade_boundary.rs b/crates/engine-tree-ext/tests/jade_boundary.rs new file mode 100644 index 00000000..7f5d8469 --- /dev/null +++ b/crates/engine-tree-ext/tests/jade_boundary.rs @@ -0,0 +1,212 @@ +//! End-to-end verification of retroactive trust across the Jade hardfork +//! boundary. +//! +//! These tests exercise the `MorphBasicEngineValidator` through the real +//! `MorphNode` stack: they spin up an ephemeral node, build a valid block, +//! tamper with the header's `state_root`, and re-import via the Engine API. +//! +//! Retroactive-trust invariants: +//! * Pre-Jade: header state root is NOT compared against the MPT root the +//! validator computes — a mismatched `state_root` must still import. +//! * Post-Jade: the validator enforces strict MPT equality — the same +//! tampered `state_root` must be rejected as INVALID. +//! +//! Two existing sibling tests verify these invariants at the crate-of-use +//! level (`crates/node/tests/it/engine.rs::state_root_validation_skipped_pre_jade` +//! and `crates/node/tests/it/consensus.rs::post_jade_state_root_mismatch_is_rejected`). +//! The tests in this file pin the contract to the engine-tree-ext crate: if +//! someone tweaks `MorphBasicEngineValidator` in a way that breaks the +//! boundary, `cargo test -p morph-engine-tree-ext` is expected to catch it. + +#![cfg(feature = "test-utils")] + +use alloy_consensus::{BlockHeader, proofs}; +use alloy_primitives::{Address, B256}; +use alloy_rpc_types_engine::PayloadAttributes; +use morph_node::test_utils::{HardforkSchedule, MorphTestNode, TestNodeBuilder}; +use morph_payload_types::{MorphBuiltPayload, MorphPayloadAttributes, MorphPayloadTypes}; +use reth_node_api::PayloadTypes; +use reth_payload_builder::BuildNewPayload; +use reth_payload_primitives::BuiltPayload; +use reth_primitives_traits::SealedBlock; +use reth_provider::BlockReaderIdExt; + +/// Build an empty block through the payload builder without submitting it. +/// +/// `node.advance_block()` would time out waiting for a non-empty payload since +/// the pool is empty — instead, drive the builder directly with empty L1 +/// messages and resolve the payload synchronously via +/// `PayloadKind::WaitForPending`. This avoids fixed sleeps + polling, which +/// were flake-prone on loaded CI runners. +async fn build_candidate_block(node: &mut MorphTestNode) -> eyre::Result { + const BUILD_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); + + let head = node + .inner + .provider + .sealed_header_by_number_or_tag(alloy_rpc_types_eth::BlockNumberOrTag::Latest)?; + let (head_hash, head_ts) = head + .map(|h| (h.hash(), h.timestamp())) + .unwrap_or((B256::ZERO, 0)); + + let rpc_attrs = MorphPayloadAttributes { + inner: PayloadAttributes { + timestamp: head_ts + 1, + prev_randao: B256::ZERO, + suggested_fee_recipient: Address::ZERO, + withdrawals: Some(vec![]), + parent_beacon_block_root: Some(B256::ZERO), + }, + transactions: Some(vec![]), + gas_limit: None, + base_fee_per_gas: None, + }; + + let payload_id = node + .inner + .payload_builder_handle + .send_new_payload(BuildNewPayload { + attributes: rpc_attrs, + parent_hash: head_hash, + cache: None, + trie_handle: None, + }) + .await? + .map_err(|e| eyre::eyre!("payload build failed: {e}"))?; + + tokio::time::timeout( + BUILD_TIMEOUT, + node.inner + .payload_builder_handle + .resolve_kind(payload_id, reth_node_api::PayloadKind::WaitForPending), + ) + .await + .map_err(|_| eyre::eyre!("payload build timed out after {:?}", BUILD_TIMEOUT))? + .ok_or_else(|| eyre::eyre!("no payload response for id {payload_id:?}"))? + .map_err(|e| eyre::eyre!("payload build error: {e}")) +} + +/// Tamper with a payload's header and ask the engine to import the result. +/// +/// Returns `true` if the engine accepted the block (VALID), `false` otherwise. +async fn try_import_with_tampered_state_root( + node: &mut MorphTestNode, + base: &MorphBuiltPayload, + bogus_state_root: B256, +) -> eyre::Result { + let sealed = base.block(); + let morph_header: morph_primitives::MorphHeader = sealed.header().inner.clone().into(); + let body = sealed.body().clone(); + let mut block = morph_primitives::Block::new(morph_header, body); + + block.header.inner.state_root = bogus_state_root; + block.header.inner.transactions_root = + proofs::calculate_transaction_root(&block.body.transactions); + + let modified_sealed = SealedBlock::seal_slow(block); + let execution_data = MorphPayloadTypes::block_to_payload(modified_sealed); + let status = node + .inner + .add_ons_handle + .beacon_engine_handle + .new_payload(execution_data) + .await?; + + Ok(status.is_valid()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn pre_jade_block_with_tampered_state_root_imports() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut nodes, _wallet) = TestNodeBuilder::new() + .with_schedule(HardforkSchedule::PreJade) + .build() + .await?; + let mut node = nodes.pop().unwrap(); + + let base_payload = build_candidate_block(&mut node).await?; + let accepted = + try_import_with_tampered_state_root(&mut node, &base_payload, B256::from([0xFF; 32])) + .await?; + + assert!( + accepted, + "pre-Jade block with tampered state_root must be accepted — retroactive trust skips \ + state-root validation before Jade" + ); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn post_jade_block_with_tampered_state_root_is_rejected() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut nodes, _wallet) = TestNodeBuilder::new() + .with_schedule(HardforkSchedule::AllActive) + .build() + .await?; + let mut node = nodes.pop().unwrap(); + + let base_payload = build_candidate_block(&mut node).await?; + let accepted = + try_import_with_tampered_state_root(&mut node, &base_payload, B256::from([0xFF; 32])) + .await?; + + assert!( + !accepted, + "post-Jade block with tampered state_root must be rejected — MorphBasicEngineValidator \ + enforces strict MPT root equality after Jade" + ); + Ok(()) +} + +/// Regression test: P2P-downloaded blocks enter the engine tree via the +/// Block-input path (`insert_block`), which never invokes +/// `convert_payload_to_block` and therefore registers no withdraw-trie-root +/// expectation. The validator must treat the missing entry as +/// `SkipValidation` so the downloaded block is accepted; otherwise sync +/// stalls indefinitely with `"missing withdraw trie root expectation +/// cache entry"`. +/// +/// The test runs two interconnected nodes: node[0] builds and imports a +/// block via the Engine API (Payload path → expectation registered on +/// node[0] only), then node[1] points its forkchoice at the new head so +/// reth's downloader fetches the block from node[0] over P2P. The download +/// lands in node[1]'s engine tree as a `Block` input. +#[tokio::test(flavor = "multi_thread")] +async fn p2p_downloaded_block_imports_without_registered_expectation() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut nodes, _wallet) = TestNodeBuilder::new() + .with_schedule(HardforkSchedule::AllActive) + .with_num_nodes(2) + .build() + .await?; + let node1 = nodes.pop().expect("two nodes requested"); + let mut node0 = nodes.pop().expect("two nodes requested"); + + let payload = build_candidate_block(&mut node0).await?; + let head_hash = payload.block().hash(); + + let exec_data = MorphPayloadTypes::block_to_payload(payload.block().clone()); + let status = node0 + .inner + .add_ons_handle + .beacon_engine_handle + .new_payload(exec_data) + .await?; + assert!( + status.is_valid(), + "node[0] must accept its own freshly-built block, got {status:?}" + ); + node0.update_forkchoice(head_hash, head_hash).await?; + + // sync_to keeps issuing FCU until node[1]'s head matches `head_hash`. + // Since node[1] does not have the block, reth's engine tree will trigger + // its block downloader against the connected peer (node[0]) — downloaded + // blocks enter via the Block input path, exercising the bug. + node1.sync_to(head_hash).await?; + + Ok(()) +} diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index 846fd4e4..a9f9cf1a 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -19,7 +19,7 @@ morph-payload-types.workspace = true reth-chainspec.workspace = true reth-evm.workspace = true -reth-ethereum-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"] } +reth-ethereum-primitives = { workspace = true, features = ["serde"] } reth-revm.workspace = true reth-primitives-traits.workspace = true reth-rpc-eth-api = { workspace = true, optional = true } diff --git a/crates/evm/src/assemble.rs b/crates/evm/src/assemble.rs index 7b840d96..afa775fe 100644 --- a/crates/evm/src/assemble.rs +++ b/crates/evm/src/assemble.rs @@ -114,6 +114,10 @@ impl BlockAssembler for MorphBlockAssembler { blob_gas_used: None, excess_blob_gas: None, requests_hash: None, + // Pre-Amsterdam Morph blocks do not carry a block-access-list hash, + // and have no PoS slot number. + block_access_list_hash: None, + slot_number: None, }; // Wrap in MorphHeader with L2-specific fields diff --git a/crates/evm/src/block/factory.rs b/crates/evm/src/block/factory.rs index 969a35b6..0ba66897 100644 --- a/crates/evm/src/block/factory.rs +++ b/crates/evm/src/block/factory.rs @@ -4,14 +4,13 @@ //! that can execute Morph L2 blocks with proper L1 fee calculation and receipt building. use crate::{ - block::{DefaultMorphReceiptBuilder, MorphBlockExecutor}, + block::{DefaultMorphReceiptBuilder, MorphBlockExecutor, MorphTxResult}, evm::MorphEvm, }; use alloy_evm::{ - Database, - block::{BlockExecutorFactory, BlockExecutorFor}, + block::{BlockExecutorFactory, StateDB}, eth::EthBlockExecutionCtx, - revm::{Inspector, database::State}, + revm::Inspector, }; use morph_chainspec::MorphChainSpec; use morph_primitives::{MorphReceipt, MorphTxEnvelope}; @@ -21,16 +20,6 @@ use std::sync::Arc; use crate::evm::MorphEvmFactory; /// Block executor factory for Morph. -/// -/// This factory creates [`MorphBlockExecutor`] instances that handle Morph-specific -/// block execution logic including: -/// - L1 fee calculation for transactions -/// - Token fee information extraction for MorphTx (0x7F) transactions -/// - Curie hardfork application -/// -/// Unlike using `EthBlockExecutorFactory`, this factory uses the custom -/// `MorphReceiptBuilder` trait which includes `l1_fee` in its context, -/// ensuring receipts are built with complete information. #[derive(Debug, Clone)] pub(crate) struct MorphBlockExecutorFactory { /// Receipt builder @@ -67,6 +56,10 @@ impl BlockExecutorFactory for MorphBlockExecutorFactory { type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; type Transaction = MorphTxEnvelope; type Receipt = MorphReceipt; + type TxExecutionResult = MorphTxResult; + // The Morph executor owns its EVM and receipt builder by value rather than + // borrowing them from the factory, so the lifetime parameter is unused. + type Executor<'a, DB: StateDB, I: Inspector>> = MorphBlockExecutor; fn evm_factory(&self) -> &Self::EvmFactory { &self.evm_factory @@ -74,13 +67,13 @@ impl BlockExecutorFactory for MorphBlockExecutorFactory { fn create_executor<'a, DB, I>( &'a self, - evm: MorphEvm<&'a mut State, I>, + evm: MorphEvm, _ctx: Self::ExecutionCtx<'a>, - ) -> impl BlockExecutorFor<'a, Self, DB, I> + ) -> Self::Executor<'a, DB, I> where - DB: Database + 'a, - I: Inspector>> + 'a, + DB: StateDB, + I: Inspector>, { - MorphBlockExecutor::new(evm, &self.spec, &self.receipt_builder) + MorphBlockExecutor::new(evm, self.spec.clone(), self.receipt_builder) } } diff --git a/crates/evm/src/block/mod.rs b/crates/evm/src/block/mod.rs index e6de9bd8..6292f88a 100644 --- a/crates/evm/src/block/mod.rs +++ b/crates/evm/src/block/mod.rs @@ -17,20 +17,49 @@ use crate::evm::MorphEvm; use alloy_consensus::Transaction; use alloy_consensus::transaction::TxHashRef; use alloy_evm::{ - Database, Evm, + Database, Evm, RecoveredTx, block::{ - BlockExecutionError, BlockExecutionResult, BlockExecutor, ExecutableTx, OnStateHook, - StateChangeSource, + BlockExecutionError, BlockExecutionResult, BlockExecutor, ExecutableTx, GasOutput, + OnStateHook, StateChangeSource, TxResult, }, }; -use alloy_primitives::{Address, U256}; +use alloy_primitives::{Address, Log, U256}; use morph_chainspec::{MorphChainSpec, MorphHardfork, MorphHardforks}; use morph_primitives::{MorphReceipt, MorphTxEnvelope}; use morph_revm::{L1_GAS_PRICE_ORACLE_ADDRESS, MorphHaltReason, TokenFeeInfo, evm::MorphContext}; -use reth_chainspec::EthereumHardforks; -use reth_revm::{DatabaseCommit, Inspector, State, context::result::ResultAndState}; +use reth_primitives_traits::Recovered; +use reth_revm::{DatabaseCommit, Inspector, context::result::ResultAndState}; use revm::context::Block; +/// The result of executing a Morph transaction. +/// +/// Carries the EVM result together with the recovered transaction and cached fee +/// information that are needed during [`MorphBlockExecutor::commit_transaction`]. +pub struct MorphTxResult { + /// The raw EVM execution result and state diff. + pub result: ResultAndState, + /// Recovered transaction (consensus tx + signer). + pub recovered: Recovered, + /// L1 data fee read from the handler cache immediately after execution. + pub l1_fee: U256, + /// Token-fee deduction Transfer logs (survive main-tx revert). + pub pre_fee_logs: Vec, + /// Token-fee reimbursement Transfer logs (survive main-tx revert). + pub post_fee_logs: Vec, +} + +impl TxResult for MorphTxResult { + type HaltReason = MorphHaltReason; + + fn result(&self) -> &ResultAndState { + &self.result + } + + fn into_result(self) -> ResultAndState { + self.result + } +} + /// Block executor for Morph L2 blocks. /// /// This executor handles Morph-specific block execution logic, differing from @@ -48,22 +77,18 @@ use revm::context::Block; /// The executor extracts token fee information from the L2TokenRegistry contract, /// including exchange rate and scale factor. /// -/// ## Hardfork Application -/// The executor applies hardfork-specific state changes at transition blocks, -/// such as the Curie hardfork which updates the L1 Gas Price Oracle contract. -/// /// ## Execution Flow -/// 1. `apply_pre_execution_changes`: Set up state, load contracts, apply hardforks +/// 1. `apply_pre_execution_changes`: Set up state and load contracts /// 2. `execute_transaction_without_commit`: Execute transaction in EVM /// 3. `commit_transaction`: Calculate fees, build receipt, commit state /// 4. `finish`: Return final execution result with all receipts -pub(crate) struct MorphBlockExecutor<'a, DB: Database, I> { - /// The EVM used by executor - evm: MorphEvm<&'a mut State, I>, +pub struct MorphBlockExecutor { + /// The EVM used by executor (owned, not a reference) + evm: MorphEvm, /// Chain specification - spec: &'a MorphChainSpec, + spec: std::sync::Arc, /// Receipt builder - receipt_builder: &'a DefaultMorphReceiptBuilder, + receipt_builder: DefaultMorphReceiptBuilder, /// Receipts of executed transactions receipts: Vec, /// Total gas used by executed transactions @@ -76,10 +101,10 @@ pub(crate) struct MorphBlockExecutor<'a, DB: Database, I> { state_hook: Option>, } -impl<'a, DB, I> MorphBlockExecutor<'a, DB, I> +impl MorphBlockExecutor where DB: Database, - I: Inspector>>, + I: Inspector>, { /// Creates a new [`MorphBlockExecutor`]. /// @@ -88,9 +113,9 @@ where /// * `spec` - Chain specification containing hardfork information /// * `receipt_builder` - Builder for constructing transaction receipts pub(crate) fn new( - evm: MorphEvm<&'a mut State, I>, - spec: &'a MorphChainSpec, - receipt_builder: &'a DefaultMorphReceiptBuilder, + evm: MorphEvm, + spec: std::sync::Arc, + receipt_builder: DefaultMorphReceiptBuilder, ) -> Self { Self { evm, @@ -103,51 +128,17 @@ where } } - /// Returns the L1 data fee for the most recently executed transaction. - /// - /// Reads from the handler's per-transaction cache (set during - /// `validate_and_deduct_eth_fee` / `validate_and_deduct_token_fee`), - /// avoiding re-encoding the full transaction RLP. - /// For L1 messages (which skip handler fee logic) the cache is ZERO. - #[inline] - fn cached_l1_fee(&self) -> U256 { - self.evm.cached_l1_data_fee() - } - /// Extract MorphTx-specific fields for MorphTx (0x7F) transactions. /// /// MorphTx transactions include: /// - Token fee information (when using ERC20 for gas payment) /// - Transaction metadata (version, reference, memo) - /// - /// # How MorphTx Token Fees Work - /// 1. User specifies a `fee_token_id` (registered ERC20 token) - /// 2. User specifies a `fee_limit` (max tokens willing to pay) - /// 3. System fetches token exchange rate from L2TokenRegistry - /// 4. System converts ETH fee to token amount using: `token_fee = eth_fee * fee_rate / token_scale` - /// 5. System validates user has sufficient token balance - /// - /// # Arguments - /// * `tx` - The transaction to extract fields from - /// * `sender` - Transaction sender (used for token registry balance queries) - /// * `hardfork` - The current Morph hardfork (affects token registry behavior) - /// - /// # Returns - /// - `Ok(None)` for non-MorphTx transactions - /// - `Ok(Some(fields))` for MorphTx with valid fields - /// - `Err` if MorphTx is missing required fields or token info cannot be fetched - /// - /// # Errors - /// Returns error if: - /// - MorphTx is missing `fee_token_id` or `fee_limit` - /// - L2TokenRegistry contract cannot be queried fn get_morph_tx_fields( &mut self, tx: &MorphTxEnvelope, sender: Address, hardfork: MorphHardfork, ) -> Result, BlockExecutionError> { - // Only MorphTx transactions have these fields if !tx.is_morph_tx() { return Ok(None); } @@ -159,13 +150,10 @@ where .fee_limit() .ok_or_else(|| BlockExecutionError::msg("MorphTx missing fee_limit"))?; - // Extract version, reference, and memo from the transaction let version = tx.version().unwrap_or(0); let reference = tx.reference(); let memo = tx.memo().cloned(); - // For fee_token_id==0 (ETH fee MorphTx, V1 only), no token registry lookup needed. - // Still preserve version/reference/memo in the receipt. if fee_token_id == 0 { return Ok(Some(MorphReceiptTxFields { version, @@ -178,8 +166,6 @@ where })); } - // Reuse cached token fee info from handler validation to avoid redundant DB reads. - // Falls back to DB read if cache is empty (e.g., in test scenarios). let token_info = match self.evm.cached_token_fee_info() { Some(info) => Some(info), None => { @@ -202,45 +188,36 @@ where } } -impl<'a, DB, I> BlockExecutor for MorphBlockExecutor<'a, DB, I> +impl BlockExecutor for MorphBlockExecutor where - DB: Database, - I: Inspector>>, + DB: Database + DatabaseCommit, + I: Inspector>, { type Transaction = MorphTxEnvelope; type Receipt = MorphReceipt; - type Evm = MorphEvm<&'a mut State, I>; + type Evm = MorphEvm; + type Result = MorphTxResult; /// Applies pre-execution state changes before processing transactions. /// /// This method performs initialization required before executing any transactions: /// - /// 1. **State Clear Flag**: Sets the flag that enables EIP-161 state trie clearing - /// if the Spurious Dragon hardfork is active - /// - /// 2. **L1 Gas Oracle Cache**: Loads the L1 Gas Price Oracle contract into the + /// 1. **L1 Gas Oracle Cache**: Loads the L1 Gas Price Oracle contract into the /// account cache to optimize L1 fee calculations for all transactions /// /// # Errors /// Returns error if: /// - L1 Gas Price Oracle account cannot be loaded fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { - // 1. Set state clear flag if the block is after the Spurious Dragon hardfork - let block_number: u64 = self.evm.block().number.to(); - let state_clear_flag = self.spec.is_spurious_dragon_active_at_block(block_number); - self.evm.db_mut().set_state_clear_flag(state_clear_flag); - - // 2. Load L1 gas oracle contract into cache so that subsequent per-tx - // L1BlockInfo reads in the handler are fast (avoid cold DB hits). - // NOTE: We do NOT cache L1BlockInfo here because the oracle can be - // updated by a regular transaction (from the external gas-oracle service) - // within the same block. The handler reads it per-tx instead. + // Pre-warm the L1 gas oracle contract in the underlying DB cache so that + // subsequent per-tx L1BlockInfo reads in the handler are fast. let _ = self .evm .db_mut() - .load_cache_account(L1_GAS_PRICE_ORACLE_ADDRESS) + .basic(L1_GAS_PRICE_ORACLE_ADDRESS) .map_err(BlockExecutionError::other)?; + let block_number: u64 = self.evm.block().number.to(); let hardfork = self .spec .morph_hardfork_at(block_number, self.evm.block().timestamp.to::()); @@ -249,97 +226,84 @@ where Ok(()) } - /// Executes a transaction without committing state changes. - /// - /// This method validates the transaction can fit in the remaining block gas, - /// then executes it in the EVM. The state changes are returned but not yet - /// committed to the database. - /// - /// # Gas Validation - /// Before execution, validates that: - /// ```text - /// tx.gas_limit + cumulative_gas_used <= block.gas_limit - /// ``` - /// - /// # Returns - /// Returns the execution result and state changes that can be committed later. - /// - /// # Errors - /// Returns error if: - /// - Transaction gas limit exceeds available block gas - /// - EVM execution fails (reverts, halts, out of gas, etc.) fn execute_transaction_without_commit( &mut self, tx: impl ExecutableTx, - ) -> Result, BlockExecutionError> { - // The sum of the transaction's gas limit and the gas utilized in this block prior, - // must be no greater than the block's gasLimit. + ) -> Result { + let (tx_env, recovered) = tx.into_parts(); + + // Validate gas limit fits in remaining block gas. let block_available_gas = self.evm.block().gas_limit() - self.gas_used; - if tx.tx().gas_limit() > block_available_gas { + if recovered.tx().gas_limit() > block_available_gas { return Err(BlockExecutionError::msg(format!( "transaction gas limit {} exceeds block available gas {}", - tx.tx().gas_limit(), + recovered.tx().gas_limit(), block_available_gas ))); } + // Clone the consensus tx and signer BEFORE transact (since we can't move out later). + let consensus_tx = recovered.tx().clone(); + let signer = *recovered.signer(); + // Execute the transaction - self.evm - .transact(&tx) - .map_err(|err| BlockExecutionError::evm(err, *tx.tx().tx_hash())) - } + let result = self + .evm + .transact(tx_env) + .map_err(|err| BlockExecutionError::evm(err, *consensus_tx.tx_hash()))?; - /// Commits a transaction's execution result and builds its receipt. - /// - /// This method performs post-execution processing for a transaction: - /// - /// 1. **L1 Fee Calculation**: Calculates the L1 data fee for the transaction - /// 2. **Token Fee Info**: For MorphTx, extracts token fee information - /// 3. **Gas Accounting**: Updates cumulative gas used for the block - /// 4. **Receipt Building**: Constructs receipt with all Morph-specific fields - /// 5. **State Commit**: Commits the EVM state changes to the database - /// - /// # Arguments - /// * `output` - The execution result from `execute_transaction_without_commit` - /// * `tx` - The original transaction - /// - /// # Returns - /// The gas used by this transaction. - /// - /// # Errors - /// Returns error if L1 fee calculation or token fee info extraction fails. - #[inline] - fn commit_transaction( - &mut self, - output: ResultAndState, - tx: impl ExecutableTx, - ) -> Result { - let ResultAndState { result, state } = output; + // Read caches from the EVM immediately after execution, before the next tx resets them. + let l1_fee = self.evm.cached_l1_data_fee(); + let pre_fee_logs = self.evm.take_pre_fee_logs(); + let post_fee_logs = self.evm.take_post_fee_logs(); - // Read L1 fee from handler cache (set during validate_and_deduct_*). - let l1_fee = self.cached_l1_fee(); + Ok(MorphTxResult { + result, + recovered: Recovered::new_unchecked(consensus_tx, signer), + l1_fee, + pre_fee_logs, + post_fee_logs, + }) + } - // Get MorphTx-specific fields for MorphTx transactions. - // Uses the hardfork cached in apply_pre_execution_changes (constant per block). - let morph_tx_fields = self.get_morph_tx_fields(tx.tx(), *tx.signer(), self.hardfork)?; + fn commit_transaction(&mut self, output: Self::Result) -> GasOutput { + let MorphTxResult { + result: ResultAndState { result, state }, + recovered, + l1_fee, + pre_fee_logs, + post_fee_logs, + } = output; - // Notify the state hook (e.g. StateRootTask) BEFORE committing, - // so the sparse trie can be updated incrementally per transaction. + // Notify the state hook (e.g. StateRootTask) BEFORE committing. if let Some(hook) = &mut self.state_hook { hook.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); } - // Update cumulative gas used - let gas_used = result.gas_used(); + // EIP-8037 separates regular and state gas; pre-Amsterdam morph treats + // them as a single number, so use the unified `tx_gas_used` getter. + let gas_used = result.gas().tx_gas_used(); self.gas_used += gas_used; + // Get MorphTx-specific fields using the recovered transaction. Errors here + // are tracing-only — the trait API no longer permits us to surface errors + // from `commit_transaction`. + let (tx, signer) = recovered.into_parts(); + let morph_tx_fields = match self.get_morph_tx_fields(&tx, signer, self.hardfork) { + Ok(fields) => fields, + Err(err) => { + tracing::error!( + target: "morph::evm", + %err, + "failed to load MorphTx receipt fields; emitting receipt without them" + ); + None + } + }; + // Build receipt. - // Fee Transfer logs are cached separately by the handler (pre_fee_logs / - // post_fee_logs) so they survive main tx revert. - let pre_fee_logs = self.evm.take_pre_fee_logs(); - let post_fee_logs = self.evm.take_post_fee_logs(); let ctx: MorphReceiptBuilderCtx<'_, Self::Evm> = MorphReceiptBuilderCtx { - tx: tx.tx(), + tx: &tx, result, cumulative_gas_used: self.gas_used, l1_fee, @@ -352,20 +316,10 @@ where // Commit state changes self.evm.db_mut().commit(state); - Ok(gas_used) + // Morph is pre-EIP-8037, so all gas is regular gas (no state-gas tracking). + GasOutput::new(gas_used) } - /// Finalizes block execution and returns the results. - /// - /// Consumes the executor and returns the EVM instance along with the - /// complete execution results including all transaction receipts. - /// - /// # Returns - /// A tuple containing: - /// - The EVM instance (for potential reuse or state access) - /// - Block execution result with receipts, gas used, and empty requests - /// - /// Note: `blob_gas_used` is always 0 as Morph doesn't support EIP-4844 blobs. fn finish( self, ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { @@ -384,13 +338,15 @@ where self.state_hook = hook; } - /// Returns a mutable reference to the EVM instance. fn evm_mut(&mut self) -> &mut Self::Evm { &mut self.evm } - /// Returns a reference to the EVM instance. fn evm(&self) -> &Self::Evm { &self.evm } + + fn receipts(&self) -> &[Self::Receipt] { + &self.receipts + } } diff --git a/crates/evm/src/block/receipt.rs b/crates/evm/src/block/receipt.rs index 9d38cb1a..00c9a426 100644 --- a/crates/evm/src/block/receipt.rs +++ b/crates/evm/src/block/receipt.rs @@ -242,11 +242,20 @@ mod tests { // Since build_receipt only uses E::HaltReason, we can use any concrete Evm type. type TestEvm = crate::evm::MorphEvm; + // revm v38 reshaped `ResultGas`: the previous 5-arg constructor is gone, + // and the new 3-arg `new(total_gas_spent, refunded, floor_gas)` is itself + // deprecated in favor of `with_*` builders. Our receipt-builder tests only + // observe `gas_used()` (`= total_gas_spent`), so set just that field via + // the non-deprecated builder. State gas (EIP-8037) and refunds aren't + // exercised pre-Amsterdam. + fn result_gas(gas_used: u64) -> revm::context::result::ResultGas { + revm::context::result::ResultGas::default().with_total_gas_spent(gas_used) + } + fn make_success_result(gas_used: u64) -> ExecutionResult { ExecutionResult::Success { reason: revm::context::result::SuccessReason::Stop, - gas_used, - gas_refunded: 0, + gas: result_gas(gas_used), logs: vec![], output: revm::context::result::Output::Call(alloy_primitives::Bytes::new()), } @@ -258,8 +267,7 @@ mod tests { ) -> ExecutionResult { ExecutionResult::Success { reason: revm::context::result::SuccessReason::Stop, - gas_used, - gas_refunded: 0, + gas: result_gas(gas_used), logs, output: revm::context::result::Output::Call(alloy_primitives::Bytes::new()), } @@ -267,7 +275,8 @@ mod tests { fn make_revert_result(gas_used: u64) -> ExecutionResult { ExecutionResult::Revert { - gas_used, + gas: result_gas(gas_used), + logs: vec![], output: alloy_primitives::Bytes::new(), } } diff --git a/crates/evm/src/config.rs b/crates/evm/src/config.rs index 6ab605e8..5eaf28fd 100644 --- a/crates/evm/src/config.rs +++ b/crates/evm/src/config.rs @@ -35,7 +35,7 @@ impl ConfigureEvm for MorphEvmConfig { let mut cfg_env = CfgEnv::::default() .with_chain_id(self.chain_spec().chain().id()) - .with_spec(spec); + .with_spec_and_mainnet_gas_params(spec); cfg_env.disable_eip7623 = true; // Morph does not enforce EIP-7825 transaction gas limit cap. Historical mainnet // transactions (e.g. block 20459477, gas_limit=21,165,068) exceed the EIP-7825 @@ -65,6 +65,7 @@ impl ConfigureEvm for MorphEvmConfig { excess_blob_gas: 0, blob_gasprice: 1, // minimum blob gas price }), + slot_num: 0, }; Ok(EvmEnv { @@ -85,7 +86,7 @@ impl ConfigureEvm for MorphEvmConfig { let mut cfg_env = CfgEnv::::default() .with_chain_id(self.chain_spec().chain().id()) - .with_spec(spec); + .with_spec_and_mainnet_gas_params(spec); cfg_env.disable_eip7623 = true; // Morph does not enforce EIP-7825 transaction gas limit cap — see evm_env() above. cfg_env.tx_gas_limit_cap = Some(attributes.gas_limit); @@ -115,6 +116,7 @@ impl ConfigureEvm for MorphEvmConfig { excess_blob_gas: 0, blob_gasprice: 1, // minimum blob gas price }), + slot_num: 0, }; Ok(EvmEnv { @@ -131,8 +133,15 @@ impl ConfigureEvm for MorphEvmConfig { parent_hash: block.header().parent_hash(), parent_beacon_block_root: block.header().parent_beacon_block_root(), ommers: &[], - withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed), + withdrawals: block + .body() + .withdrawals + .as_ref() + .map(|w| Cow::Borrowed(w.0.as_slice())), extra_data: block.extra_data().clone(), + tx_count_hint: Some(block.body().transactions.len()), + // Morph L2 has no PoS slot semantics; introduced upstream in alloy 2.0. + slot_number: None, }) } @@ -145,8 +154,11 @@ impl ConfigureEvm for MorphEvmConfig { parent_hash: parent.hash(), parent_beacon_block_root: attributes.parent_beacon_block_root, ommers: &[], - withdrawals: attributes.inner.withdrawals.map(Cow::Owned), + withdrawals: attributes.inner.withdrawals.map(|w| Cow::Owned(w.0)), extra_data: attributes.inner.extra_data, + tx_count_hint: None, + // Morph L2 has no PoS slot semantics; introduced upstream in alloy 2.0. + slot_number: None, }) } } @@ -269,6 +281,7 @@ mod tests { parent_beacon_block_root: None, withdrawals: None, extra_data: Bytes::new(), + slot_number: None, }, base_fee_per_gas: Some(500_000), }; @@ -318,6 +331,7 @@ mod tests { parent_beacon_block_root: None, withdrawals: None, extra_data: Bytes::new(), + slot_number: None, }, base_fee_per_gas: None, }; diff --git a/crates/evm/src/context.rs b/crates/evm/src/context.rs index 5dc98db5..083a0302 100644 --- a/crates/evm/src/context.rs +++ b/crates/evm/src/context.rs @@ -1,6 +1,4 @@ use reth_evm::NextBlockEnvAttributes; -#[cfg(feature = "rpc")] -use reth_primitives_traits::SealedHeader; /// Context required for next block environment. #[derive(Debug, Clone, derive_more::Deref)] @@ -17,7 +15,9 @@ pub struct MorphNextBlockEnvAttributes { impl reth_rpc_eth_api::helpers::pending_block::BuildPendingEnv for MorphNextBlockEnvAttributes { - fn build_pending_env(parent: &SealedHeader) -> Self { + fn build_pending_env( + parent: &reth_primitives_traits::SealedHeader, + ) -> Self { Self { inner: NextBlockEnvAttributes::build_pending_env(parent), base_fee_per_gas: None, diff --git a/crates/evm/src/engine.rs b/crates/evm/src/engine.rs index f17f50fb..8ed9e581 100644 --- a/crates/evm/src/engine.rs +++ b/crates/evm/src/engine.rs @@ -5,6 +5,7 @@ use crate::MorphEvmConfig; use alloy_consensus::crypto::RecoveryError; +use alloy_evm::block::ExecutableTxParts; use alloy_primitives::Address; use morph_payload_types::MorphExecutionData; use morph_primitives::{Block, MorphTxEnvelope}; @@ -87,6 +88,15 @@ impl ToTxEnv for RecoveredInBlock { } } +impl ExecutableTxParts for RecoveredInBlock { + type Recovered = Self; + + fn into_parts(self) -> (MorphTxEnv, Self) { + let tx_env = MorphTxEnv::from_recovered_tx(self.tx(), *self.signer()); + (tx_env, self) + } +} + #[cfg(test)] mod tests { use super::*; @@ -95,7 +105,7 @@ mod tests { use morph_chainspec::MorphChainSpec; use morph_primitives::{BlockBody, MorphHeader}; use rayon::prelude::*; - use reth_evm::ConfigureEngineEvm; + use reth_evm::{ConfigureEngineEvm, ConvertTx, ExecutableTxTuple}; fn create_legacy_tx() -> MorphTxEnvelope { let tx = TxLegacy { @@ -180,7 +190,7 @@ mod tests { assert!(result.is_ok()); let tuple = result.unwrap(); - let (iter, recover_fn): (_, _) = tuple.into(); + let (iter, recover_fn) = tuple.into_parts(); // Collect items and verify we have 2 transactions let items: Vec<_> = iter.into_par_iter().collect(); @@ -188,7 +198,7 @@ mod tests { // Test the recovery function works on all items for item in items { - let recovered = recover_fn(item); + let recovered = recover_fn.convert(item); assert!(recovered.is_ok()); } } diff --git a/crates/evm/src/evm.rs b/crates/evm/src/evm.rs index c9c0f0f7..96005018 100644 --- a/crates/evm/src/evm.rs +++ b/crates/evm/src/evm.rs @@ -3,7 +3,10 @@ use alloy_evm::{ precompiles::PrecompilesMap, revm::{ Context, ExecuteEvm, InspectEvm, Inspector, SystemCallEvm, - context::result::{EVMError, ResultAndState}, + context::{ + CfgEnv, + result::{EVMError, ResultAndState}, + }, inspector::NoOpInspector, }, }; @@ -178,6 +181,10 @@ where &self.block } + fn cfg_env(&self) -> &CfgEnv { + &self.inner.inner.ctx.cfg + } + fn chain_id(&self) -> u64 { self.cfg.chain_id } diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index 3f8012a1..5becb7f9 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -74,14 +74,16 @@ pub mod evm; use std::sync::Arc; use alloy_evm::{ - Database, - block::{BlockExecutorFactory, BlockExecutorFor}, + block::{BlockExecutorFactory, StateDB}, eth::EthBlockExecutionCtx, - revm::{Inspector, database::State}, + revm::Inspector, }; pub use evm::MorphEvmFactory; use morph_primitives::{MorphReceipt, MorphTxEnvelope}; +// Re-export types that surface in `BlockExecutorFactory` associated types so +// downstream crates can name them. They are constructed via the factory. +pub use crate::block::{MorphBlockExecutor, MorphTxResult}; use crate::{block::MorphBlockExecutorFactory, evm::MorphEvm}; use morph_chainspec::MorphChainSpec; use morph_revm::evm::MorphContext; @@ -159,6 +161,8 @@ impl BlockExecutorFactory for MorphEvmConfig { type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; type Transaction = MorphTxEnvelope; type Receipt = MorphReceipt; + type TxExecutionResult = MorphTxResult; + type Executor<'a, DB: StateDB, I: Inspector>> = MorphBlockExecutor; fn evm_factory(&self) -> &Self::EvmFactory { self.executor_factory.evm_factory() @@ -166,12 +170,12 @@ impl BlockExecutorFactory for MorphEvmConfig { fn create_executor<'a, DB, I>( &'a self, - evm: MorphEvm<&'a mut State, I>, + evm: MorphEvm, ctx: Self::ExecutionCtx<'a>, - ) -> impl BlockExecutorFor<'a, Self, DB, I> + ) -> Self::Executor<'a, DB, I> where - DB: Database + 'a, - I: Inspector>> + 'a, + DB: StateDB, + I: Inspector>, { self.executor_factory.create_executor(evm, ctx) } diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index e976f71f..3c6eca37 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -14,6 +14,7 @@ workspace = true morph-chainspec.workspace = true morph-consensus.workspace = true morph-engine-api.workspace = true +morph-engine-tree-ext.workspace = true morph-evm.workspace = true morph-payload-builder.workspace = true morph-payload-types.workspace = true @@ -45,6 +46,7 @@ reth-tasks.workspace = true reth-transaction-pool.workspace = true reth-tracing.workspace = true reth-trie.workspace = true +reth-trie-db.workspace = true # Alloy alloy-consensus.workspace = true @@ -59,6 +61,7 @@ alloy-rpc-types-eth.workspace = true eyre.workspace = true clap.workspace = true dashmap.workspace = true +futures.workspace = true parking_lot.workspace = true tokio = { workspace = true, features = ["sync", "rt"] } tokio-stream.workspace = true diff --git a/crates/node/src/add_ons.rs b/crates/node/src/add_ons.rs index dc05e190..2a1583bf 100644 --- a/crates/node/src/add_ons.rs +++ b/crates/node/src/add_ons.rs @@ -26,7 +26,6 @@ use reth_provider::{ use reth_rpc_builder::Identity; use reth_rpc_eth_api::RpcNodeCore; use reth_tracing::tracing; -use tokio_stream::StreamExt; /// Morph node add-ons for RPC and Engine API. /// @@ -41,9 +40,10 @@ pub struct MorphAddOns< PVB = MorphEngineValidatorBuilder, EVB = MorphTreeEngineValidatorBuilder, RpcMiddleware = Identity, + AuthHttpMiddleware = Identity, > { /// Inner RPC add-ons from reth. - inner: RpcAddOns, + inner: RpcAddOns, /// Optional reference-index control injected by `main.rs`. When present /// the add-on spawns startup indexing on launch and registers the /// `morph_` RPC namespace. @@ -67,6 +67,7 @@ where NoopEngineApiBuilder::default(), MorphTreeEngineValidatorBuilder::new(pvb), Identity::default(), + Identity::default(), ), reference_index: None, } @@ -115,31 +116,20 @@ where let payload_builder = ctx.node.payload_builder_handle().clone(); let chain_spec = ctx.node.provider().chain_spec(); let beacon_engine_handle = ctx.beacon_engine_handle.clone(); - let engine_events = ctx.engine_events.clone(); let task_executor = ctx.node.task_executor().clone(); - let engine_state_tracker = - std::sync::Arc::new(morph_engine_api::EngineStateTracker::default()); + let block_tag_tracker = std::sync::Arc::new(morph_engine_api::BlockTagTracker::default()); // Create Morph eth_config handler (EIP-7910 + morph extension) let eth_config_handler = MorphEthConfigHandler::new(ctx.node.provider().clone(), ctx.node.evm_config().clone()); - // Keep a local view of canonical head/forkchoice from reth engine events. - let tracker_for_events = engine_state_tracker.clone(); - task_executor.spawn_critical("morph engine state tracker", async move { - let mut listener = engine_events.new_listener(); - while let Some(event) = listener.next().await { - tracker_for_events.on_consensus_engine_event(&event); - } - }); - // Spawn reference index startup indexing (Task A) if configured. let reference_rpc_handler = if let Some(control) = self.reference_index { let startup_control = control.clone(); let startup_node = ctx.node.clone(); - // spawn_critical causes node shutdown on panic/error, matching the spec + // spawn_critical_task causes node shutdown on panic/error, matching the spec // requirement that reference index startup failures are fatal. - task_executor.spawn_critical("morph reference index startup", async move { + task_executor.spawn_critical_task("morph reference index startup", async move { let result = tokio::task::spawn_blocking(move || { crate::exex::run_startup_indexing(&startup_node, &startup_control) }) @@ -149,7 +139,7 @@ where match result { Ok(()) => {} Err(err) => { - // Propagate to spawn_critical which will shut down the node. + // Propagate to spawn_critical_task which will shut down the node. panic!("reference index startup failed: {err:?}"); } } @@ -200,7 +190,7 @@ where payload_builder, chain_spec, beacon_engine_handle, - engine_state_tracker, + block_tag_tracker, ); // Create the RPC handler diff --git a/crates/node/src/components/pool.rs b/crates/node/src/components/pool.rs index 8aad6a06..47a196dc 100644 --- a/crates/node/src/components/pool.rs +++ b/crates/node/src/components/pool.rs @@ -1,6 +1,7 @@ //! Morph transaction pool builder. use crate::MorphNode; +use morph_evm::MorphEvmConfig; use morph_primitives; use morph_txpool::MorphTransactionValidator; use reth_node_api::FullNodeTypes; @@ -18,42 +19,58 @@ use reth_transaction_pool::{TransactionValidationTaskExecutor, blobstore::InMemo #[non_exhaustive] pub struct MorphPoolBuilder; -impl PoolBuilder for MorphPoolBuilder +impl PoolBuilder for MorphPoolBuilder where Node: FullNodeTypes, + Evm: Send, { - type Pool = morph_txpool::MorphTransactionPool; - - async fn build_pool(self, ctx: &BuilderContext) -> eyre::Result { + type Pool = morph_txpool::MorphTransactionPool< + Node::Provider, + InMemoryBlobStore, + morph_txpool::MorphPooledTransaction, + MorphEvmConfig, + >; + + async fn build_pool( + self, + ctx: &BuilderContext, + _evm_config: Evm, + ) -> eyre::Result { let pool_config = ctx.pool_config(); // Use in-memory blob store (Morph doesn't support EIP-4844 blobs) let blob_store = InMemoryBlobStore::default(); + // Build the Morph-specific EVM config for the validator + let morph_evm_config = + MorphEvmConfig::new(ctx.chain_spec(), morph_evm::MorphEvmFactory::default()); + // Build the transaction validator with Morph-specific checks - let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone()) - .with_head_timestamp(ctx.head().timestamp) - .with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes) - .with_local_transactions_config(pool_config.local_transactions_config.clone()) - .set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap) - .with_max_tx_gas_limit(ctx.config().txpool.max_tx_gas_limit) - .set_block_gas_limit(ctx.chain_spec().inner.genesis().gas_limit) - .with_minimum_priority_fee(ctx.config().txpool.minimum_priority_fee) - .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) - // Register MorphTx (0x7F) type for ERC20 gas payment - .with_custom_tx_type(morph_primitives::MORPH_TX_TYPE_ID) - // Disable the inner EthTransactionValidator's balance check. - // MorphTx (fee_token_id > 0) users may have zero ETH but pay gas in ERC20 tokens. - // Without this, the inner validator rejects them before reaching MorphTransactionValidator's - // token fee validation. The MorphTransactionValidator already performs its own balance - // checks for all tx types (including L1 data fee), so this is safe. - .disable_balance_check() - // Note: L1Message (0x7E) is NOT registered - it will be rejected by - // EthTransactionValidator as TxTypeNotSupported, which is correct since - // L1 messages should only be included by the sequencer during block building - // Disable EIP-4844 blob transactions - .no_eip4844() - .build_with_tasks(ctx.task_executor().clone(), blob_store.clone()); + let validator = TransactionValidationTaskExecutor::eth_builder( + ctx.provider().clone(), + morph_evm_config, + ) + .with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes) + .with_local_transactions_config(pool_config.local_transactions_config.clone()) + .set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap) + .with_max_tx_gas_limit(ctx.config().txpool.max_tx_gas_limit) + .set_block_gas_limit(ctx.chain_spec().inner.genesis().gas_limit) + .with_minimum_priority_fee(ctx.config().txpool.minimum_priority_fee) + .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) + // Register MorphTx (0x7F) type for ERC20 gas payment + .with_custom_tx_type(morph_primitives::MORPH_TX_TYPE_ID) + // Disable the inner EthTransactionValidator's balance check. + // MorphTx (fee_token_id > 0) users may have zero ETH but pay gas in ERC20 tokens. + // Without this, the inner validator rejects them before reaching MorphTransactionValidator's + // token fee validation. The MorphTransactionValidator already performs its own balance + // checks for all tx types (including L1 data fee), so this is safe. + .disable_balance_check() + // Note: L1Message (0x7E) is NOT registered - it will be rejected by + // EthTransactionValidator as TxTypeNotSupported, which is correct since + // L1 messages should only be included by the sequencer during block building + // Disable EIP-4844 blob transactions + .no_eip4844() + .build_with_tasks(ctx.task_executor().clone(), blob_store.clone()); // Wrap with Morph-specific validator let validator = validator.map(MorphTransactionValidator::new); @@ -69,7 +86,7 @@ where // Spawn Morph-specific maintenance task for MorphTx (0x7F) revalidation // This handles ERC20 token balance changes that reth's standard maintenance // cannot track (reth only tracks ETH balance via SenderInfo) - ctx.task_executor().spawn_critical( + ctx.task_executor().spawn_critical_task( "txpool maintenance - morph pool", morph_txpool::maintain_morph_pool(pool.clone(), ctx.provider().clone()), ); diff --git a/crates/node/src/exex/reference_index.rs b/crates/node/src/exex/reference_index.rs index 9067d330..dc25835b 100644 --- a/crates/node/src/exex/reference_index.rs +++ b/crates/node/src/exex/reference_index.rs @@ -13,6 +13,7 @@ use alloy_consensus::BlockHeader; use alloy_eips::BlockNumHash; +use futures::FutureExt; use morph_chainspec::spec::MorphChainSpec; use morph_primitives::MorphPrimitives; use morph_reference_index::{ @@ -21,7 +22,7 @@ use morph_reference_index::{ reconcile::run_startup_reconcile, writer::{delete_block, update_indexed_to, write_block}, }; -use reth_db_api::transaction::DbTx; +use reth_db_api::transaction::{DbTx, DbTxMut}; use reth_exex::{ExExContext, ExExEvent, ExExNotification}; use reth_node_api::{FullNodeComponents, NodeTypes}; use reth_provider::{ @@ -29,10 +30,11 @@ use reth_provider::{ }; use reth_storage_api::TransactionVariant; use tokio::sync::watch; -use tokio_stream::StreamExt; +use tokio_stream::{Stream, StreamExt}; use tracing::{debug, error, info}; const TARGET: &str = "morph::reference_index"; +const MAX_LIVE_NOTIFICATION_BATCH: usize = 1024; // ── shared control ──────────────────────────────────────────────────────────── @@ -169,7 +171,8 @@ where } } - match handle_notification(&ctx.events, &control.db, notification, &mut last_finished) { + let notifications = drain_ready_notifications(&mut ctx.notifications, notification)?; + match handle_notifications_batch(&ctx.events, &control.db, notifications, &mut last_finished) { Ok(()) => {} Err(e) => { error!(target: TARGET, ?e, "error processing notification"); @@ -225,66 +228,123 @@ where Ok(()) } -/// Process one ExEx notification: commit or revert three tables atomically. -/// -/// Updates `last_finished` when a FinishedHeight is sent so the caller can -/// keep progress monotonic across the startup-watch and notification arms. -fn handle_notification( +/// Drain already-ready notifications so fast catch-up can be indexed in one DB transaction. +fn drain_ready_notifications( + notifications: &mut S, + first: ExExNotification, +) -> eyre::Result>> +where + S: Stream>> + Unpin, +{ + let mut batch = Vec::with_capacity(32); + batch.push(first); + + while batch.len() < MAX_LIVE_NOTIFICATION_BATCH { + match notifications.try_next().now_or_never() { + Some(Ok(Some(notification))) => batch.push(notification), + Some(Ok(None)) | None => break, + Some(Err(err)) => return Err(err), + } + } + + Ok(batch) +} + +/// Process ready ExEx notifications in one atomic commit. +fn handle_notifications_batch( events: &tokio::sync::mpsc::UnboundedSender, db: &ReferenceIndexDb, - notification: ExExNotification, + notifications: Vec>, last_finished: &mut Option, ) -> eyre::Result<()> { + if notifications.is_empty() { + return Ok(()); + } + + let tx = db.tx_mut()?; + let mut finished_height = None; + let mut allow_regression = false; + for notification in notifications { + let effect = apply_notification_to_tx(&tx, notification)?; + allow_regression |= effect.allow_regression; + finished_height = effect.finished_height; + } + tx.commit()?; + + if let Some(block) = finished_height { + if allow_regression { + events.send(ExExEvent::FinishedHeight(block))?; + *last_finished = Some(block); + } else { + send_finished_height_monotonic(events, block, last_finished)?; + } + } + + Ok(()) +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +struct NotificationEffect { + finished_height: Option, + allow_regression: bool, +} + +fn apply_notification_to_tx( + tx: &Tx, + notification: ExExNotification, +) -> eyre::Result { match notification { ExExNotification::ChainCommitted { new } => { - let tx = db.tx_mut()?; for block in new.blocks_iter() { write_block( - &tx, + tx, block.number(), block.hash(), block.timestamp(), &block.body().transactions, )?; } - update_indexed_to(&tx, new.tip().number())?; - tx.commit()?; - send_finished_height_monotonic(events, new.tip().num_hash(), last_finished)?; + update_indexed_to(tx, new.tip().number())?; + Ok(NotificationEffect { + finished_height: Some(new.tip().num_hash()), + allow_regression: false, + }) } ExExNotification::ChainReverted { old } => { let parent = old.first().number().saturating_sub(1); - let tx = db.tx_mut()?; for block in old.blocks_iter() { - delete_block(&tx, block.number())?; + delete_block(tx, block.number())?; } - update_indexed_to(&tx, parent)?; - tx.commit()?; + update_indexed_to(tx, parent)?; // FinishedHeight not sent on revert per spec. + Ok(NotificationEffect { + finished_height: None, + allow_regression: true, + }) } ExExNotification::ChainReorged { old, new } => { - let tx = db.tx_mut()?; for block in old.blocks_iter() { - delete_block(&tx, block.number())?; + delete_block(tx, block.number())?; } for block in new.blocks_iter() { write_block( - &tx, + tx, block.number(), block.hash(), block.timestamp(), &block.body().transactions, )?; } - update_indexed_to(&tx, new.tip().number())?; - tx.commit()?; + update_indexed_to(tx, new.tip().number())?; // On reorg to a shorter chain the tip may go down; that is the one // case we ALLOW FinishedHeight to regress (reth's ExExEvent doc // explicitly permits "on reorgs, height may go down"). - events.send(ExExEvent::FinishedHeight(new.tip().num_hash()))?; - *last_finished = Some(new.tip().num_hash()); + Ok(NotificationEffect { + finished_height: Some(new.tip().num_hash()), + allow_regression: true, + }) } } - Ok(()) } /// Send a FinishedHeight only if it strictly advances `last_finished`. @@ -368,3 +428,142 @@ where Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::Header; + use alloy_primitives::B256; + use morph_primitives::{Block, BlockBody, MorphHeader, MorphPrimitives}; + use reth_primitives_traits::SealedBlock; + use reth_provider::{Chain, ExecutionOutcome}; + use std::{collections::BTreeMap, sync::Arc}; + + #[test] + fn batched_commits_emit_finished_height_once_at_tip() -> eyre::Result<()> { + let dir = tempfile::tempdir()?; + let db = ReferenceIndexDb::open(dir.path(), 2910, B256::ZERO)?; + let (events, mut events_rx) = tokio::sync::mpsc::unbounded_channel(); + let mut last_finished = None; + + let block1 = recovered_block(1, B256::ZERO); + let block1_hash = block1.hash(); + let block2 = recovered_block(2, block1_hash); + let block2_num_hash = block2.num_hash(); + + handle_notifications_batch( + &events, + &db, + vec![ + committed_notification(vec![block1]), + committed_notification(vec![block2]), + ], + &mut last_finished, + )?; + + assert_eq!(db.indexed_to()?, 2); + assert!(db.indexed_block_hash(1)?.is_some()); + assert!(db.indexed_block_hash(2)?.is_some()); + assert_eq!(last_finished, Some(block2_num_hash)); + assert_eq!( + events_rx.try_recv()?, + ExExEvent::FinishedHeight(block2_num_hash) + ); + assert!(events_rx.try_recv().is_err()); + + Ok(()) + } + + #[test] + fn batch_with_reorg_allows_final_finished_height_to_regress() -> eyre::Result<()> { + let dir = tempfile::tempdir()?; + let db = ReferenceIndexDb::open(dir.path(), 2910, B256::ZERO)?; + let (events, mut events_rx) = tokio::sync::mpsc::unbounded_channel(); + let mut last_finished = Some(BlockNumHash { + number: 12, + hash: B256::repeat_byte(0x12), + }); + + let old10 = recovered_block(10, B256::ZERO); + let old11 = recovered_block(11, old10.hash()); + let old12 = recovered_block(12, old11.hash()); + let new10 = recovered_block(10, B256::repeat_byte(0x99)); + let new11 = recovered_block(11, new10.hash()); + let new11_num_hash = new11.num_hash(); + + handle_notifications_batch( + &events, + &db, + vec![ + reorg_notification(vec![old10, old11, old12], vec![new10]), + committed_notification(vec![new11]), + ], + &mut last_finished, + )?; + + assert_eq!(db.indexed_to()?, 11); + assert_eq!(last_finished, Some(new11_num_hash)); + assert_eq!( + events_rx.try_recv()?, + ExExEvent::FinishedHeight(new11_num_hash) + ); + assert!(events_rx.try_recv().is_err()); + + Ok(()) + } + + fn committed_notification( + blocks: Vec>, + ) -> ExExNotification { + ExExNotification::ChainCommitted { + new: Arc::new(Chain::new( + blocks, + ExecutionOutcome::default(), + BTreeMap::new(), + )), + } + } + + fn reorg_notification( + old: Vec>, + new: Vec>, + ) -> ExExNotification { + ExExNotification::ChainReorged { + old: Arc::new(Chain::new( + old, + ExecutionOutcome::default(), + BTreeMap::new(), + )), + new: Arc::new(Chain::new( + new, + ExecutionOutcome::default(), + BTreeMap::new(), + )), + } + } + + fn recovered_block( + number: u64, + parent_hash: B256, + ) -> reth_primitives_traits::RecoveredBlock { + let header = MorphHeader { + inner: Header { + number, + parent_hash, + timestamp: 1_000 + number, + gas_limit: 30_000_000, + parent_beacon_block_root: Some(B256::ZERO), + ..Default::default() + }, + next_l1_msg_index: 0, + }; + let body = BlockBody { + transactions: vec![], + ommers: vec![], + withdrawals: None, + }; + SealedBlock::seal_slow(Block { header, body }) + .try_recover() + .expect("empty test block should recover") + } +} diff --git a/crates/node/src/node.rs b/crates/node/src/node.rs index 03cd40b6..5256862c 100644 --- a/crates/node/src/node.rs +++ b/crates/node/src/node.rs @@ -211,6 +211,8 @@ impl PayloadAttributesBuilder .chain_spec .is_cancun_active_at_timestamp(timestamp) .then(B256::random), + // Morph L2 has no PoS slot semantics; field added in alloy 2.0. + slot_number: None, }, // No L1 transactions in local mining mode transactions: None, diff --git a/crates/node/src/test_utils.rs b/crates/node/src/test_utils.rs index 18560877..d8f50d00 100644 --- a/crates/node/src/test_utils.rs +++ b/crates/node/src/test_utils.rs @@ -24,7 +24,7 @@ use alloy_primitives::{Address, B256, Bytes, TxKind, U256}; use alloy_rpc_types_engine::PayloadAttributes; use alloy_rpc_types_eth::TransactionRequest; use alloy_signer_local::PrivateKeySigner; -use morph_payload_types::{MorphBuiltPayload, MorphPayloadBuilderAttributes}; +use morph_payload_types::MorphBuiltPayload; use morph_primitives::{ MorphTxEnvelope, TxL1Msg, TxMorph, transaction::l1_transaction::L1_TX_TYPE_ID, }; @@ -32,9 +32,8 @@ use reth_e2e_test_utils::{ NodeHelperType, TmpDB, transaction::TransactionTestContext, wallet::Wallet, }; use reth_node_api::NodeTypesWithDBAdapter; -use reth_payload_builder::EthPayloadBuilderAttributes; +use reth_payload_builder::BuildNewPayload; use reth_provider::providers::BlockchainProvider; -use reth_tasks::TaskManager; use std::sync::Arc; use tokio::sync::Mutex; @@ -229,6 +228,24 @@ impl TestNodeBuilder { self } + /// Override an account's runtime bytecode in the test genesis. + pub fn with_account_code(mut self, address: Address, code: impl Into) -> Self { + let alloc = self + .genesis_json + .get_mut("alloc") + .and_then(serde_json::Value::as_object_mut) + .expect("test genesis alloc must be an object"); + let address = address.to_string().to_ascii_lowercase(); + let entry = alloc + .entry(address) + .or_insert_with(|| serde_json::json!({ "balance": "0x0" })); + let entry = entry + .as_object_mut() + .expect("test genesis account entry must be an object"); + entry.insert("code".to_string(), serde_json::json!(code.into())); + self + } + /// Set the number of nodes to start. /// /// When `num_nodes > 1`, all nodes are interconnected via a simulated P2P network. @@ -245,9 +262,9 @@ impl TestNodeBuilder { /// Build and launch the configured nodes. /// - /// Returns the node handles, the task manager, and a wallet derived from + /// Returns the node handles and a wallet derived from /// the standard test mnemonic (`test test test ... junk`). - pub async fn build(mut self) -> eyre::Result<(Vec, TaskManager, Wallet)> { + pub async fn build(mut self) -> eyre::Result<(Vec, Wallet)> { // Apply the hardfork schedule to the genesis JSON before parsing. self.schedule.apply(&mut self.genesis_json); @@ -277,10 +294,7 @@ impl TestNodeBuilder { /// # Parameters /// - `num_nodes`: number of interconnected nodes to create /// - `is_dev`: whether to enable dev mode (auto-sealing every 100ms) -pub async fn setup( - num_nodes: usize, - is_dev: bool, -) -> eyre::Result<(Vec, TaskManager, Wallet)> { +pub async fn setup(num_nodes: usize, is_dev: bool) -> eyre::Result<(Vec, Wallet)> { TestNodeBuilder::new() .with_num_nodes(num_nodes) .with_dev(is_dev) @@ -319,7 +333,7 @@ pub async fn advance_chain( pub async fn advance_empty_block(node: &mut MorphTestNode) -> eyre::Result { use alloy_consensus::BlockHeader; use reth_node_api::PayloadKind; - use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; + use reth_payload_primitives::BuiltPayload; use reth_provider::BlockReaderIdExt; let head = node @@ -339,19 +353,22 @@ pub async fn advance_empty_block(node: &mut MorphTestNode) -> eyre::Result MorphPayloadBuilderAttributes { - let attributes = PayloadAttributes { - timestamp, - prev_randao: B256::ZERO, - suggested_fee_recipient: Address::ZERO, - withdrawals: Some(vec![]), - parent_beacon_block_root: Some(B256::ZERO), - }; - - MorphPayloadBuilderAttributes::from(EthPayloadBuilderAttributes::new(B256::ZERO, attributes)) +pub fn morph_payload_attributes(timestamp: u64) -> morph_payload_types::MorphPayloadAttributes { + morph_payload_types::MorphPayloadAttributes { + inner: PayloadAttributes { + timestamp, + prev_randao: B256::ZERO, + suggested_fee_recipient: Address::ZERO, + withdrawals: Some(vec![]), + parent_beacon_block_root: Some(B256::ZERO), + slot_number: None, + }, + transactions: None, + gas_limit: None, + base_fee_per_gas: None, + } } // ============================================================================= diff --git a/crates/node/src/validator.rs b/crates/node/src/validator.rs index 6931ac4a..72c3299f 100644 --- a/crates/node/src/validator.rs +++ b/crates/node/src/validator.rs @@ -6,7 +6,6 @@ use alloy_primitives::{B256, keccak256}; use dashmap::DashMap; use morph_chainspec::{ L2_MESSAGE_QUEUE_ADDRESS, L2_MESSAGE_QUEUE_WITHDRAW_TRIE_ROOT_SLOT, MorphChainSpec, - MorphHardforks, }; use morph_payload_types::{MorphExecutionData, MorphPayloadTypes}; use morph_primitives::MorphHeader; @@ -15,14 +14,15 @@ use reth_chainspec::EthChainSpec; use reth_errors::ConsensusError; use reth_node_api::{ AddOnsContext, FullNodeComponents, InvalidPayloadAttributesError, NewPayloadError, NodeTypes, - PayloadAttributes, PayloadTypes, PayloadValidator, StateRootValidator, + PayloadAttributes, PayloadTypes, PayloadValidator, }; use reth_node_builder::{ invalid_block_hook::InvalidBlockHookExt, - rpc::{BasicEngineValidator, EngineValidatorBuilder, PayloadValidatorBuilder}, + rpc::{EngineValidatorBuilder, PayloadValidatorBuilder}, }; -use reth_primitives_traits::{GotExpected, RecoveredBlock, SealedBlock}; +use reth_primitives_traits::{RecoveredBlock, SealedBlock}; use reth_provider::ChainSpecProvider; +use reth_tracing::tracing; use std::{collections::VecDeque, sync::Arc}; /// Builder for Morph engine validator (payload validation). @@ -39,8 +39,8 @@ where { type Validator = MorphEngineValidator; - async fn build(self, ctx: &AddOnsContext<'_, Node>) -> eyre::Result { - Ok(MorphEngineValidator::new(ctx.node.provider().chain_spec())) + async fn build(self, _ctx: &AddOnsContext<'_, Node>) -> eyre::Result { + Ok(MorphEngineValidator::new()) } } @@ -78,20 +78,21 @@ where <::Payload as PayloadTypes>::ExecutionData, >, >, + Node::Provider: ChainSpecProvider, PVB: PayloadValidatorBuilder, PVB::Validator: reth_node_api::PayloadValidator< ::Payload, Block = reth_node_api::BlockTy, - > + StateRootValidator<::Primitives> - + Clone, + > + Clone, { type EngineValidator = - BasicEngineValidator; + morph_engine_tree_ext::MorphBasicEngineValidator; async fn build_tree_validator( self, ctx: &AddOnsContext<'_, Node>, tree_config: reth_node_api::TreeConfig, + changeset_cache: reth_trie_db::ChangesetCache, ) -> eyre::Result { let validator = self.payload_validator_builder.build(ctx).await?; let data_dir = ctx @@ -100,16 +101,19 @@ where .clone() .resolve_datadir(ctx.config.chain.chain()); let invalid_block_hook = ctx.create_invalid_block_hook(&data_dir).await?; + let chain_spec = ctx.node.provider().chain_spec(); - Ok(BasicEngineValidator::new( + Ok(morph_engine_tree_ext::MorphBasicEngineValidator::new( ctx.node.provider().clone(), Arc::new(ctx.node.consensus().clone()), ctx.node.evm_config().clone(), - validator.clone(), + validator, tree_config, invalid_block_hook, - ) - .with_state_root_validator(validator)) + changeset_cache, + ctx.node.task_executor().clone(), + chain_spec, + )) } } @@ -117,10 +121,9 @@ where /// /// This validator is used by the engine API to validate incoming payloads. /// For Morph, most validation is deferred to the consensus layer. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct MorphEngineValidator { - chain_spec: Arc, expected_withdraw_trie_roots: Arc>, expected_withdraw_trie_root_order: Arc>>, } @@ -135,12 +138,8 @@ impl MorphEngineValidator { const MAX_EXPECTED_WITHDRAW_TRIE_ROOTS: usize = 4096; /// Creates a new [`MorphEngineValidator`]. - pub fn new(chain_spec: Arc) -> Self { - Self { - chain_spec, - expected_withdraw_trie_roots: Arc::new(DashMap::new()), - expected_withdraw_trie_root_order: Arc::new(Mutex::new(VecDeque::new())), - } + pub fn new() -> Self { + Self::default() } fn record_withdraw_trie_root_expectation( @@ -222,10 +221,19 @@ impl PayloadValidator for MorphEngineValidator { block: &RecoveredBlock, ) -> Result<(), ConsensusError> { let Some(expectation) = self.take_withdraw_trie_root_expectation(block.hash()) else { - return Err(ConsensusError::Other(format!( - "missing withdraw trie root expectation cache entry for block {}", - block.hash() - ))); + // No CL-supplied expectation. Reachable on the Block-input path + // (P2P-downloaded blocks, pipeline backfill, and buffered blocks + // whose expectation was evicted from the bounded LRU before + // reattach) — `convert_payload_to_block` was never invoked to + // register one. Treat as SkipValidation so sync isn't stalled; + // the strict post-Jade state-root check upstream still covers + // withdraw-trie consistency through state-root equality. + tracing::debug!( + target: "morph::engine_validator", + block_hash = %block.hash(), + "no withdraw trie root expectation registered; skipping CL cross-check" + ); + return Ok(()); }; let WithdrawTrieRootExpectation::Verify(expected_withdraw_trie_root) = expectation else { return Ok(()); @@ -244,7 +252,7 @@ impl PayloadValidator for MorphEngineValidator { }; if actual_withdraw_trie_root != expected_withdraw_trie_root { - return Err(ConsensusError::Other(format!( + return Err(ConsensusError::msg(format!( "withdraw trie root mismatch: expected {expected_withdraw_trie_root}, got {actual_withdraw_trie_root}" ))); } @@ -265,45 +273,11 @@ impl PayloadValidator for MorphEngineValidator { } } -impl StateRootValidator for MorphEngineValidator { - fn validate_state_root( - &self, - block: &RecoveredBlock, - computed_state_root: B256, - ) -> Result<(), ConsensusError> { - let jade_active = self - .chain_spec - .is_jade_active_at_timestamp(block.header().timestamp()); - - // Enforce canonical state-root equality in MPT mode (post-Jade). - if jade_active { - let expected_state_root = block.header().state_root(); - if computed_state_root != expected_state_root { - return Err(ConsensusError::BodyStateRootDiff( - GotExpected { - got: computed_state_root, - expected: expected_state_root, - } - .into(), - )); - } - } - - Ok(()) - } -} - #[cfg(test)] mod tests { use super::*; use alloy_primitives::U256; - use morph_chainspec::MORPH_HOODI; use reth_trie::{HashedPostState, HashedStorage}; - use std::sync::Arc; - - fn test_chain_spec() -> Arc { - MORPH_HOODI.clone() - } #[test] fn test_extract_updated_withdraw_trie_root_from_hashed_state() { @@ -333,7 +307,7 @@ mod tests { #[test] fn test_withdraw_trie_root_expectation_cache_evicts_incrementally_not_clear_all() { - let validator = MorphEngineValidator::new(test_chain_spec()); + let validator = MorphEngineValidator::new(); let key = |n: usize| { let mut bytes = [0u8; 32]; bytes[..8].copy_from_slice(&(n as u64).to_be_bytes()); @@ -379,7 +353,7 @@ mod tests { #[test] fn test_record_and_take_expectation_roundtrip() { - let validator = MorphEngineValidator::new(test_chain_spec()); + let validator = MorphEngineValidator::new(); let hash = B256::from([0x42; 32]); let expected_root = B256::from([0xee; 32]); @@ -405,7 +379,7 @@ mod tests { #[test] fn test_record_skip_validation_expectation() { - let validator = MorphEngineValidator::new(test_chain_spec()); + let validator = MorphEngineValidator::new(); let hash = B256::from([0x99; 32]); validator.record_withdraw_trie_root_expectation( @@ -419,7 +393,7 @@ mod tests { #[test] fn test_duplicate_record_overwrites_value() { - let validator = MorphEngineValidator::new(test_chain_spec()); + let validator = MorphEngineValidator::new(); let hash = B256::from([0x11; 32]); let root1 = B256::from([0xaa; 32]); let root2 = B256::from([0xbb; 32]); @@ -439,7 +413,7 @@ mod tests { #[test] fn test_take_nonexistent_returns_none() { - let validator = MorphEngineValidator::new(test_chain_spec()); + let validator = MorphEngineValidator::new(); let hash = B256::from([0xff; 32]); assert!( validator @@ -476,13 +450,116 @@ mod tests { ); } + fn empty_recovered_block_with_hash( + hash: B256, + ) -> reth_primitives_traits::RecoveredBlock { + let header = MorphHeader::default(); + let body = morph_primitives::BlockBody::default(); + let block = morph_primitives::Block::new(header, body); + let sealed = reth_primitives_traits::SealedBlock::new_unchecked(block, hash); + reth_primitives_traits::RecoveredBlock::new_sealed(sealed, Vec::new()) + } + + /// Block-input path (P2P sync, pipeline backfill) reaches + /// `validate_block_post_execution_with_hashed_state` without calling + /// `convert_payload_to_block`, so no expectation is registered. The + /// validator must treat the missing entry as `SkipValidation` and + /// return `Ok` — otherwise sync stalls. The upstream strict state-root + /// check (post-Jade) remains the source of truth. + #[test] + fn validate_block_post_execution_skips_when_no_expectation_registered() { + let validator = MorphEngineValidator::new(); + let block = empty_recovered_block_with_hash(B256::from([0xab; 32])); + + let result = validator + .validate_block_post_execution_with_hashed_state(&HashedPostState::default(), &block); + + assert!( + result.is_ok(), + "missing expectation must be treated as SkipValidation, got {:?}", + result.err() + ); + } + + /// SkipValidation expectation (CL didn't supply a value) must be honored. + #[test] + fn validate_block_post_execution_honors_skip_validation_expectation() { + let validator = MorphEngineValidator::new(); + let hash = B256::from([0xcd; 32]); + validator.record_withdraw_trie_root_expectation( + hash, + WithdrawTrieRootExpectation::SkipValidation, + ); + let block = empty_recovered_block_with_hash(hash); + + let result = validator + .validate_block_post_execution_with_hashed_state(&HashedPostState::default(), &block); + + assert!(result.is_ok()); + // expectation must be consumed. + assert!( + validator + .take_withdraw_trie_root_expectation(hash) + .is_none() + ); + } + + /// Verify expectation: when the slot wasn't touched we trust CL (no DB read) + /// and pass through. + #[test] + fn validate_block_post_execution_passes_when_slot_unchanged() { + let validator = MorphEngineValidator::new(); + let hash = B256::from([0x33; 32]); + validator.record_withdraw_trie_root_expectation( + hash, + WithdrawTrieRootExpectation::Verify(B256::from([0xee; 32])), + ); + let block = empty_recovered_block_with_hash(hash); + + // empty hashed state → no withdraw-slot diff → skip per the doc-comment + // explanation in the validator. + let result = validator + .validate_block_post_execution_with_hashed_state(&HashedPostState::default(), &block); + + assert!(result.is_ok()); + } + + /// Verify expectation that mismatches an actually-updated slot must fail. + #[test] + fn validate_block_post_execution_rejects_mismatched_root() { + let validator = MorphEngineValidator::new(); + let hash = B256::from([0x55; 32]); + let expected = B256::from([0xee; 32]); + let actual = B256::from([0xff; 32]); + validator.record_withdraw_trie_root_expectation( + hash, + WithdrawTrieRootExpectation::Verify(expected), + ); + + let hashed_address = keccak256(L2_MESSAGE_QUEUE_ADDRESS); + let hashed_slot = keccak256(B256::from(L2_MESSAGE_QUEUE_WITHDRAW_TRIE_ROOT_SLOT)); + let state = HashedPostState::from_hashed_storage( + hashed_address, + HashedStorage::from_iter(false, [(hashed_slot, U256::from_be_bytes(actual.0))]), + ); + + let block = empty_recovered_block_with_hash(hash); + let err = validator + .validate_block_post_execution_with_hashed_state(&state, &block) + .expect_err("mismatched root must fail"); + assert!( + err.to_string().contains("withdraw trie root mismatch"), + "unexpected error: {err}" + ); + } + #[test] fn test_validate_payload_attributes_timestamp_not_in_past() { use alloy_rpc_types_engine::PayloadAttributes; use morph_payload_types::MorphPayloadAttributes; use reth_node_api::PayloadValidator; - let validator = MorphEngineValidator::new(test_chain_spec()); + let validator = MorphEngineValidator::new(); // Create a header with timestamp 100 let parent_header = MorphHeader { @@ -502,6 +579,7 @@ mod tests { suggested_fee_recipient: alloy_primitives::Address::ZERO, withdrawals: None, parent_beacon_block_root: None, + slot_number: None, }, transactions: None, gas_limit: None, @@ -521,6 +599,7 @@ mod tests { suggested_fee_recipient: alloy_primitives::Address::ZERO, withdrawals: None, parent_beacon_block_root: None, + slot_number: None, }, transactions: None, gas_limit: None, @@ -540,6 +619,7 @@ mod tests { suggested_fee_recipient: alloy_primitives::Address::ZERO, withdrawals: None, parent_beacon_block_root: None, + slot_number: None, }, transactions: None, gas_limit: None, @@ -551,33 +631,4 @@ mod tests { .is_ok() ); } - - #[test] - fn test_validate_state_root_jade_not_active_always_ok() { - // On Hoodi, Jade is not activated. validate_state_root should always - // return Ok even with mismatched state roots. - use morph_primitives::MorphHeader; - use reth_primitives_traits::{RecoveredBlock, SealedBlock}; - - let validator = MorphEngineValidator::new(test_chain_spec()); - - let header = MorphHeader { - inner: alloy_consensus::Header { - timestamp: 0, - state_root: B256::from([0xaa; 32]), - ..Default::default() - }, - ..Default::default() - }; - let block = morph_primitives::Block { - header, - body: Default::default(), - }; - let sealed = SealedBlock::seal_slow(block); - let recovered = RecoveredBlock::new_sealed(sealed, vec![]); - - // Different computed root, but Jade is not active - let result = validator.validate_state_root(&recovered, B256::from([0xbb; 32])); - assert!(result.is_ok()); - } } diff --git a/crates/node/tests/it/block_building.rs b/crates/node/tests/it/block_building.rs index 7ae89abc..739fa14a 100644 --- a/crates/node/tests/it/block_building.rs +++ b/crates/node/tests/it/block_building.rs @@ -17,7 +17,7 @@ use super::helpers::{advance_block_with_l1_messages, wallet_to_arc}; async fn empty_block_has_no_transactions() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let payload = advance_empty_block(&mut node).await?; @@ -38,7 +38,7 @@ async fn empty_block_has_no_transactions() -> eyre::Result<()> { async fn block_with_single_transfer() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -60,7 +60,7 @@ async fn block_with_single_transfer() -> eyre::Result<()> { async fn sequential_blocks_with_transfers() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -81,7 +81,7 @@ async fn sequential_blocks_with_transfers() -> eyre::Result<()> { async fn block_with_l1_message_only() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let l1_msg = L1MessageBuilder::new(0) @@ -109,7 +109,7 @@ async fn block_with_l1_message_only() -> eyre::Result<()> { async fn l1_messages_precede_l2_transactions() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Inject L2 transaction into the pool first @@ -153,7 +153,7 @@ async fn l1_messages_precede_l2_transactions() -> eyre::Result<()> { async fn multiple_l1_messages_sequential_queue_indices() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let l1_msgs = L1MessageBuilder::build_sequential(0, 3); diff --git a/crates/node/tests/it/consensus.rs b/crates/node/tests/it/consensus.rs index 07c5a466..b365f4c1 100644 --- a/crates/node/tests/it/consensus.rs +++ b/crates/node/tests/it/consensus.rs @@ -25,7 +25,7 @@ use super::helpers::{ async fn l1_message_after_l2_tx_is_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Inject an L2 transfer into the pool so that the payload builder picks it up. @@ -63,7 +63,7 @@ async fn l1_message_after_l2_tx_is_rejected() -> eyre::Result<()> { async fn l1_message_duplicate_queue_index_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Both messages claim queue index 0 — this is a protocol violation. @@ -88,7 +88,7 @@ async fn l1_message_duplicate_queue_index_rejected() -> eyre::Result<()> { async fn l1_message_gap_queue_index_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Index 0 then index 2 — index 1 is skipped. @@ -113,7 +113,7 @@ async fn l1_message_gap_queue_index_rejected() -> eyre::Result<()> { async fn post_jade_state_root_mismatch_is_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new() + let (mut nodes, _wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::AllActive) .build() .await?; @@ -140,7 +140,7 @@ async fn post_jade_state_root_mismatch_is_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn block_number_jump_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let base = build_block_no_submit(&mut node, vec![]).await?; @@ -157,7 +157,7 @@ async fn block_number_jump_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn wrong_parent_hash_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let base = build_block_no_submit(&mut node, vec![]).await?; @@ -178,7 +178,7 @@ async fn wrong_parent_hash_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn timestamp_not_greater_than_parent_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new() + let (mut nodes, _wallet) = TestNodeBuilder::new() .with_schedule(morph_node::test_utils::HardforkSchedule::PreViridian) .build() .await?; @@ -201,7 +201,7 @@ async fn timestamp_not_greater_than_parent_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn gas_used_exceeds_gas_limit_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let base = build_block_no_submit(&mut node, vec![]).await?; @@ -217,7 +217,7 @@ async fn gas_used_exceeds_gas_limit_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn gas_limit_excessive_increase_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let base = build_block_no_submit(&mut node, vec![]).await?; @@ -237,7 +237,7 @@ async fn gas_limit_excessive_increase_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn next_l1_msg_index_decreases_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Block 1: include 2 L1 messages -> next_l1_msg_index becomes 2 @@ -261,7 +261,7 @@ async fn next_l1_msg_index_decreases_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn next_l1_msg_index_insufficient_for_l1_msgs() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Build block with 2 L1 messages (queue 0,1) but don't submit @@ -281,7 +281,7 @@ async fn next_l1_msg_index_insufficient_for_l1_msgs() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn next_l1_msg_index_can_skip_past_included_messages() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Build block with queue indices 0,1 and then advance header.next_l1_msg_index to 4. diff --git a/crates/node/tests/it/engine.rs b/crates/node/tests/it/engine.rs index 627dd6d7..9952132e 100644 --- a/crates/node/tests/it/engine.rs +++ b/crates/node/tests/it/engine.rs @@ -11,9 +11,9 @@ use jsonrpsee::core::client::ClientT; use morph_node::test_utils::{HardforkSchedule, TestNodeBuilder}; use morph_payload_types::{ AssembleL2BlockParams, ExecutableL2Data, GenericResponse, MorphPayloadAttributes, - MorphPayloadBuilderAttributes, }; -use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; +use reth_payload_builder::BuildNewPayload; +use reth_payload_primitives::BuiltPayload; use reth_provider::BlockReaderIdExt; use super::helpers::{build_block_no_submit, craft_and_try_import_block}; @@ -31,7 +31,7 @@ use super::helpers::{build_block_no_submit, craft_and_try_import_block}; async fn state_root_validation_skipped_pre_jade() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new() + let (mut nodes, _wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::PreJade) .build() .await?; @@ -54,12 +54,12 @@ async fn state_root_validation_skipped_pre_jade() -> eyre::Result<()> { Ok(()) } -/// `engine_newL2Block` can import a block assembled over the authenticated RPC. +/// `engine_newL2Block` can import consecutive blocks assembled over the authenticated RPC. #[tokio::test(flavor = "multi_thread")] -async fn new_l2_block_imports_assembled_block_over_rpc() -> eyre::Result<()> { +async fn new_l2_block_imports_consecutive_assembled_blocks_over_rpc() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let auth = node.auth_server_handle(); @@ -89,6 +89,31 @@ async fn new_l2_block_imports_assembled_block_over_rpc() -> eyre::Result<()> { "imported canonical head should match the assembled block hash" ); + let mut params = AssembleL2BlockParams::empty(2); + params.timestamp = Some(latest.timestamp() + 1); + + let data: ExecutableL2Data = client.request("engine_assembleL2Block", (params,)).await?; + let expected_hash = data.hash; + + let _: () = client.request("engine_newL2Block", (data,)).await?; + + let latest = node + .inner + .provider + .sealed_header_by_number_or_tag(alloy_rpc_types_eth::BlockNumberOrTag::Latest)? + .expect("latest header must exist after importing the second block"); + + assert_eq!( + latest.number(), + 2, + "engine_newL2Block should expose the first imported block as the parent immediately" + ); + assert_eq!( + latest.hash(), + expected_hash, + "second imported canonical head should match the assembled block hash" + ); + Ok(()) } @@ -97,7 +122,7 @@ async fn new_l2_block_imports_assembled_block_over_rpc() -> eyre::Result<()> { async fn validate_l2_block_rejects_tampered_hash_over_rpc() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let auth = node.auth_server_handle(); @@ -123,7 +148,7 @@ async fn validate_l2_block_rejects_tampered_hash_over_rpc() -> eyre::Result<()> async fn payload_builder_hash_matches_block_hash_with_nonzero_prev_randao() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let head = node @@ -134,27 +159,29 @@ async fn payload_builder_hash_matches_block_hash_with_nonzero_prev_randao() -> e .map(|h| (h.hash(), h.timestamp())) .unwrap_or((B256::ZERO, 0)); - let attrs = MorphPayloadBuilderAttributes::try_new( - head_hash, - MorphPayloadAttributes { - inner: PayloadAttributes { - timestamp: head_ts + 1, - prev_randao: B256::repeat_byte(0xAA), - suggested_fee_recipient: Address::ZERO, - withdrawals: Some(vec![]), - parent_beacon_block_root: Some(B256::ZERO), - }, - transactions: Some(vec![]), - gas_limit: None, - base_fee_per_gas: None, + let rpc_attrs = MorphPayloadAttributes { + inner: PayloadAttributes { + timestamp: head_ts + 1, + prev_randao: B256::repeat_byte(0xAA), + suggested_fee_recipient: Address::ZERO, + withdrawals: Some(vec![]), + parent_beacon_block_root: Some(B256::ZERO), + slot_number: None, }, - 3, - )?; + transactions: Some(vec![]), + gas_limit: None, + base_fee_per_gas: None, + }; let payload_id = node .inner .payload_builder_handle - .send_new_payload(attrs) + .send_new_payload(BuildNewPayload { + attributes: rpc_attrs, + parent_hash: head_hash, + cache: None, + trie_handle: None, + }) .await? .map_err(|e| eyre::eyre!("payload build failed: {e}"))?; diff --git a/crates/node/tests/it/evm.rs b/crates/node/tests/it/evm.rs index efb4b99f..27f03b69 100644 --- a/crates/node/tests/it/evm.rs +++ b/crates/node/tests/it/evm.rs @@ -94,7 +94,7 @@ const ACCOUNT0: Address = alloy_primitives::address!("f39Fd6e51aad88F6F4ce6aB882 async fn contract_deploy_stores_state() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let signer = wallet_at_index(0, TEST_CHAIN_ID); @@ -130,7 +130,7 @@ async fn contract_deploy_stores_state() -> eyre::Result<()> { async fn contract_revert_receipt_status_false() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let signer = wallet_at_index(0, TEST_CHAIN_ID); @@ -171,7 +171,7 @@ async fn contract_state_persists_across_blocks() -> eyre::Result<()> { reth_tracing::init_test_tracing(); use morph_node::test_utils::advance_empty_block; - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Block 1: deploy the contract @@ -207,7 +207,7 @@ async fn contract_state_persists_across_blocks() -> eyre::Result<()> { async fn blockhash_opcode_returns_morph_custom_value() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Deploy STORE_BLOCKHASH in block 1. @@ -247,7 +247,7 @@ async fn blockhash_opcode_returns_morph_custom_value() -> eyre::Result<()> { async fn selfdestruct_opcode_disabled() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let signer = wallet_at_index(0, TEST_CHAIN_ID); @@ -301,7 +301,7 @@ async fn selfdestruct_opcode_disabled() -> eyre::Result<()> { async fn l1_fee_nonzero_for_calldata_tx() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Transaction with 100 bytes of non-zero calldata @@ -344,7 +344,7 @@ async fn l1_fee_nonzero_for_calldata_tx() -> eyre::Result<()> { async fn empty_calldata_vs_large_calldata_l1_fee_difference() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let signer = wallet_at_index(0, TEST_CHAIN_ID); diff --git a/crates/node/tests/it/hardfork.rs b/crates/node/tests/it/hardfork.rs index 3ed5fe16..cef86694 100644 --- a/crates/node/tests/it/hardfork.rs +++ b/crates/node/tests/it/hardfork.rs @@ -17,7 +17,7 @@ use super::helpers::wallet_to_arc; async fn all_active_chain_advances() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new() + let (mut nodes, wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::AllActive) .build() .await?; @@ -45,7 +45,7 @@ async fn all_active_chain_advances() -> eyre::Result<()> { async fn pre_jade_chain_advances() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new() + let (mut nodes, wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::PreJade) .build() .await?; @@ -68,7 +68,7 @@ async fn pre_jade_chain_advances() -> eyre::Result<()> { async fn pre_jade_empty_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new() + let (mut nodes, _wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::PreJade) .build() .await?; @@ -88,7 +88,7 @@ async fn pre_jade_empty_block() -> eyre::Result<()> { async fn eip7702_accepted_viridian_active() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new() + let (mut nodes, wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::AllActive) // Viridian active .build() .await?; @@ -110,7 +110,7 @@ async fn eip7702_accepted_viridian_active() -> eyre::Result<()> { async fn eip7702_rejected_viridian_inactive() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new() + let (mut nodes, wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::PreViridian) // Viridian NOT active .build() .await?; diff --git a/crates/node/tests/it/helpers.rs b/crates/node/tests/it/helpers.rs index ecd83498..808e9ed2 100644 --- a/crates/node/tests/it/helpers.rs +++ b/crates/node/tests/it/helpers.rs @@ -4,12 +4,11 @@ use alloy_consensus::BlockHeader; use alloy_primitives::{Address, B256, Bytes}; use alloy_rpc_types_engine::PayloadAttributes; use morph_node::test_utils::MorphTestNode; -use morph_payload_types::{ - MorphBuiltPayload, MorphPayloadAttributes, MorphPayloadBuilderAttributes, MorphPayloadTypes, -}; +use morph_payload_types::{MorphBuiltPayload, MorphPayloadAttributes, MorphPayloadTypes}; use reth_e2e_test_utils::wallet::Wallet; use reth_node_api::PayloadTypes; -use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; +use reth_payload_builder::BuildNewPayload; +use reth_payload_primitives::BuiltPayload; use reth_provider::BlockReaderIdExt; use std::sync::Arc; use tokio::sync::Mutex; @@ -48,19 +47,22 @@ pub(crate) async fn advance_block_with_l1_messages( suggested_fee_recipient: Address::ZERO, withdrawals: Some(vec![]), parent_beacon_block_root: Some(B256::ZERO), + slot_number: None, }, transactions: Some(l1_messages), gas_limit: None, base_fee_per_gas: None, }; - let attrs = MorphPayloadBuilderAttributes::try_new(head_hash, rpc_attrs, 3) - .map_err(|e| eyre::eyre!("failed to build payload attributes: {e}"))?; - let payload_id = node .inner .payload_builder_handle - .send_new_payload(attrs) + .send_new_payload(BuildNewPayload { + attributes: rpc_attrs, + parent_hash: head_hash, + cache: None, + trie_handle: None, + }) .await? .map_err(|e| eyre::eyre!("payload build failed: {e}"))?; @@ -119,19 +121,22 @@ pub(crate) async fn build_block_no_submit( suggested_fee_recipient: Address::ZERO, withdrawals: Some(vec![]), parent_beacon_block_root: Some(B256::ZERO), + slot_number: None, }, transactions: Some(l1_messages), gas_limit: None, base_fee_per_gas: None, }; - let attrs = MorphPayloadBuilderAttributes::try_new(head_hash, rpc_attrs, 3) - .map_err(|e| eyre::eyre!("failed to build payload attributes: {e}"))?; - let payload_id = node .inner .payload_builder_handle - .send_new_payload(attrs) + .send_new_payload(BuildNewPayload { + attributes: rpc_attrs, + parent_hash: head_hash, + cache: None, + trie_handle: None, + }) .await? .map_err(|e| eyre::eyre!("payload build failed: {e}"))?; @@ -224,19 +229,22 @@ pub(crate) async fn expect_payload_build_failure( suggested_fee_recipient: Address::ZERO, withdrawals: Some(vec![]), parent_beacon_block_root: Some(B256::ZERO), + slot_number: None, }, transactions: Some(l1_messages), gas_limit: None, base_fee_per_gas: None, }; - let attrs = MorphPayloadBuilderAttributes::try_new(head_hash, rpc_attrs, 3) - .map_err(|e| eyre::eyre!("failed to build payload attributes: {e}"))?; - let payload_id = match node .inner .payload_builder_handle - .send_new_payload(attrs) + .send_new_payload(BuildNewPayload { + attributes: rpc_attrs, + parent_hash: head_hash, + cache: None, + trie_handle: None, + }) .await? { Ok(id) => id, diff --git a/crates/node/tests/it/l1_messages.rs b/crates/node/tests/it/l1_messages.rs index 027bf74b..0be68e34 100644 --- a/crates/node/tests/it/l1_messages.rs +++ b/crates/node/tests/it/l1_messages.rs @@ -17,7 +17,7 @@ use super::helpers::advance_block_with_l1_messages; async fn single_l1_message_included() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let l1_msg = L1MessageBuilder::new(0) @@ -43,7 +43,7 @@ async fn single_l1_message_included() -> eyre::Result<()> { async fn three_sequential_l1_messages_in_one_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let l1_msgs = L1MessageBuilder::build_sequential(0, 3); @@ -76,7 +76,7 @@ async fn three_sequential_l1_messages_in_one_block() -> eyre::Result<()> { async fn l1_messages_across_blocks_continuous() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Block 1: queue indices 0, 1 @@ -102,7 +102,7 @@ async fn l1_messages_across_blocks_continuous() -> eyre::Result<()> { async fn l1_messages_resume_after_empty_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Block 1: queue indices 0, 1 @@ -134,7 +134,7 @@ async fn l1_messages_resume_after_empty_block() -> eyre::Result<()> { async fn l1_message_gas_is_tracked() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let gas_limit = 50_000u64; diff --git a/crates/node/tests/it/morph_tx.rs b/crates/node/tests/it/morph_tx.rs index 567d47ce..e00d3871 100644 --- a/crates/node/tests/it/morph_tx.rs +++ b/crates/node/tests/it/morph_tx.rs @@ -13,7 +13,7 @@ //! - Test ERC20 at `0x5300000000000000000000000000000000000022` //! with 1000 tokens pre-funded for test account 0 and 1 -use alloy_primitives::Address; +use alloy_primitives::{Address, B256, Bytes, U256}; use morph_node::test_utils::{HardforkSchedule, MorphTxBuilder, TEST_TOKEN_ID, TestNodeBuilder}; use reth_payload_primitives::BuiltPayload; @@ -31,7 +31,7 @@ use super::helpers::wallet_to_arc; async fn morph_tx_v1_eth_fee_included_in_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Build a MorphTx v1 with ETH fee @@ -66,7 +66,7 @@ async fn morph_tx_v1_eth_fee_included_in_block() -> eyre::Result<()> { async fn morph_tx_v1_multiple_in_sequence() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, mut wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, mut wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Inject 3 MorphTx v1 (ETH fee) with sequential nonces @@ -101,7 +101,7 @@ async fn morph_tx_v1_multiple_in_sequence() -> eyre::Result<()> { async fn morph_tx_v0_erc20_fee_included_in_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let raw_tx = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), 0) @@ -135,7 +135,7 @@ async fn morph_tx_v0_erc20_fee_included_in_block() -> eyre::Result<()> { async fn morph_tx_v1_erc20_fee_included_in_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let raw_tx = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), 0) @@ -169,7 +169,7 @@ async fn morph_tx_v1_rejected_before_jade() -> eyre::Result<()> { reth_tracing::init_test_tracing(); // Use PreJade schedule — Jade is NOT active - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new() + let (mut nodes, wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::PreJade) .build() .await?; @@ -196,7 +196,7 @@ async fn morph_tx_v1_rejected_before_jade() -> eyre::Result<()> { async fn morph_tx_v0_accepted_before_jade() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new() + let (mut nodes, wallet) = TestNodeBuilder::new() .with_schedule(HardforkSchedule::PreJade) .build() .await?; @@ -226,7 +226,7 @@ async fn morph_tx_v0_accepted_before_jade() -> eyre::Result<()> { async fn mixed_tx_types_in_one_block() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet_arc = wallet_to_arc(wallet); @@ -287,7 +287,7 @@ async fn mixed_tx_types_in_one_block() -> eyre::Result<()> { async fn morph_tx_invalid_token_rejected_by_pool() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let raw_tx = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), 0) @@ -311,7 +311,7 @@ async fn morph_tx_invalid_token_rejected_by_pool() -> eyre::Result<()> { async fn morph_tx_insufficient_token_balance_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); // Account 2 has ETH only, no tokens in genesis @@ -334,7 +334,7 @@ async fn morph_tx_insufficient_token_balance_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn morph_tx_v0_fee_token_id_zero_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let raw_tx = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), 0) @@ -362,7 +362,7 @@ async fn morph_tx_v0_fee_token_id_zero_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn morph_tx_memo_exceeds_64_bytes_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let raw_tx = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), 0) @@ -382,7 +382,7 @@ async fn morph_tx_memo_exceeds_64_bytes_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn morph_tx_fee_limit_zero_accepted() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let raw_tx = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), 0) @@ -413,6 +413,44 @@ fn token_balance_slot(account: Address) -> alloy_primitives::B256 { alloy_primitives::keccak256(preimage) } +/// Build calldata for ERC20 `transfer(address,uint256)`. +fn erc20_transfer_calldata(to: Address, amount: U256) -> Bytes { + let mut calldata = Vec::with_capacity(68); + calldata.extend_from_slice(&[0xa9, 0x05, 0x9c, 0xbb]); + + let mut address_word = [0u8; 32]; + address_word[12..].copy_from_slice(to.as_slice()); + calldata.extend_from_slice(&address_word); + + calldata.extend_from_slice(&amount.to_be_bytes::<32>()); + Bytes::from(calldata) +} + +fn erc20_transfer_topic() -> B256 { + alloy_primitives::keccak256("Transfer(address,address,uint256)") +} + +fn address_topic(address: Address) -> B256 { + let mut topic = [0u8; 32]; + topic[12..].copy_from_slice(address.as_slice()); + B256::from(topic) +} + +/// Optimized runtime for: +/// +/// ```solidity +/// contract Slot1Token { +/// uint256 private dummy; +/// mapping(address => uint256) public balanceOf; // slot 1 +/// event Transfer(address indexed from, address indexed to, uint256 value); +/// function transfer(address to, uint256 amount) external returns (bool) { ... } +/// } +/// ``` +/// +/// Keeping `balanceOf` at slot 1 lets the test token use the same storage layout +/// as `tests/assets/test-genesis.json` and the token registry's direct-slot path. +const SLOT1_ERC20_RUNTIME_CODE: &str = "0x608060405234801561000f575f5ffd5b5060043610610034575f3560e01c806370a0823114610038578063a9059cbb1461006a575b5f5ffd5b61005761004636600461015e565b60016020525f908152604090205481565b6040519081526020015b60405180910390f35b61007d61007836600461017e565b61008d565b6040519015158152602001610061565b335f90815260016020526040812054828110156100da5760405162461bcd60e51b815260206004820152600760248201526662616c616e636560c81b604482015260640160405180910390fd5b335f81815260016020908152604080832087860390556001600160a01b03881680845292819020805488019055518681529192917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35060019392505050565b80356001600160a01b0381168114610159575f5ffd5b919050565b5f6020828403121561016e575f5ffd5b61017782610143565b9392505050565b5f5f6040838503121561018f575f5ffd5b61019883610143565b94602093909301359350505056"; + /// After a successful MorphTx v0 with ERC20 fee, the sender's token balance /// must decrease (fee was charged from tokens, not ETH). #[tokio::test(flavor = "multi_thread")] @@ -420,7 +458,7 @@ async fn morph_tx_v0_token_balance_decreases() -> eyre::Result<()> { reth_tracing::init_test_tracing(); use reth_provider::StateProviderFactory; - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let sender = alloy_primitives::address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); @@ -459,6 +497,131 @@ async fn morph_tx_v0_token_balance_decreases() -> eyre::Result<()> { Ok(()) } +/// Regression for the mainnet block 19720219 shape: +/// +/// - tx `0xc267450129e51457a280fa82c74364d312e47885c09d15c78f6a0895844913c9` +/// - block `0xfbd17c5a73553cbd71f4654c189759a6262e6e52e76a19d760da4ab2b4e98a52` +/// - mainnet gas used: 59_335 +/// +/// The important shape is not the exact mainnet state, but that the MorphTx pays +/// fees in the same ERC20 contract it calls. Fee deduction touches the sender's +/// balance slot before the main ERC20 `transfer` SLOAD/SSTORE pair, so this +/// catches regressions in the `sload_morph`, `sstore_morph`, and reimburse +/// cold/warm-state handling. +/// +/// `EXPECTED_GAS_USED = 48_128` is the sandbox golden, NOT the mainnet +/// 59_335. The sandbox uses a minimal hand-written ERC20 with one +/// storage slot per `transfer`, while the mainnet token's compiled +/// bytecode does extra checks; initial balances and call data sizes also +/// differ. What's locked is the bug-vs-fix delta: a regression in +/// `sload_morph`/`sstore_morph` causes the main tx's SSTORE on +/// `sender.balanceOf` to be charged 2900 (SSTORE_RESET) instead of 100 +/// (dirty), pushing `cumulative_gas_used` ~2800 above the golden and +/// tripping this assertion before the change reaches mainnet. +#[tokio::test(flavor = "multi_thread")] +async fn morph_tx_v0_token_fee_transfer_to_fee_token_contract_gas_regression() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + use alloy_consensus::TxReceipt; + use alloy_consensus::transaction::TxHashRef; + use reth_provider::{ReceiptProvider, StateProviderFactory}; + + const EXPECTED_GAS_USED: u64 = 48_128; + let token_addr = morph_node::test_utils::TEST_TOKEN_ADDRESS; + let (mut nodes, wallet) = TestNodeBuilder::new() + .with_account_code(token_addr, SLOT1_ERC20_RUNTIME_CODE) + .build() + .await?; + let mut node = nodes.pop().unwrap(); + + let sender = alloy_primitives::address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let recipient = Address::with_last_byte(0x99); + let fee_vault = alloy_primitives::address!("530000000000000000000000000000000000000a"); + let amount = U256::from(100); + + let sender_slot = token_balance_slot(sender); + let recipient_slot = token_balance_slot(recipient); + let fee_vault_slot = token_balance_slot(fee_vault); + + let state_before = node.inner.provider.latest()?; + let sender_before = state_before + .storage(token_addr, sender_slot)? + .unwrap_or_default(); + let recipient_before = state_before + .storage(token_addr, recipient_slot)? + .unwrap_or_default(); + let fee_vault_before = state_before + .storage(token_addr, fee_vault_slot)? + .unwrap_or_default(); + + let raw_tx = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), wallet.inner_nonce) + .with_v0_token_fee(TEST_TOKEN_ID) + .with_to(token_addr) + .with_data(erc20_transfer_calldata(recipient, amount)) + .with_gas_limit(100_000) + .build_signed()?; + node.rpc.inject_tx(raw_tx).await?; + let payload = node.advance_block().await?; + + let tx_hash = *payload + .block() + .body() + .transactions + .first() + .expect("block should contain regression tx") + .tx_hash(); + let receipt = node + .inner + .provider + .receipt_by_hash(tx_hash)? + .expect("receipt must exist"); + + assert!(receipt.status(), "ERC20 transfer must succeed"); + + let transfer_topic = erc20_transfer_topic(); + let transfer_logs: Vec<_> = receipt + .logs() + .iter() + .filter(|log| log.address == token_addr && log.topics().first() == Some(&transfer_topic)) + .collect(); + assert_eq!( + transfer_logs.len(), + 1, + "the main ERC20 transfer should execute against the fee token contract" + ); + assert_eq!(transfer_logs[0].topics()[1], address_topic(sender)); + assert_eq!(transfer_logs[0].topics()[2], address_topic(recipient)); + + let state_after = node.inner.provider.latest()?; + let sender_after = state_after + .storage(token_addr, sender_slot)? + .unwrap_or_default(); + let recipient_after = state_after + .storage(token_addr, recipient_slot)? + .unwrap_or_default(); + let fee_vault_after = state_after + .storage(token_addr, fee_vault_slot)? + .unwrap_or_default(); + + let sender_delta = sender_before - sender_after; + let recipient_delta = recipient_after - recipient_before; + let fee_vault_delta = fee_vault_after - fee_vault_before; + + assert_eq!(recipient_delta, amount); + assert!( + fee_vault_delta > U256::ZERO, + "fee vault should keep the net charged token fee" + ); + assert_eq!( + sender_delta, + amount + fee_vault_delta, + "sender should only lose the main transfer amount plus net token fee" + ); + + assert_eq!(receipt.cumulative_gas_used(), EXPECTED_GAS_USED); + + Ok(()) +} + /// Init code that deploys a contract whose runtime always reverts. /// /// Constructor (12 bytes): CODECOPY + RETURN → deploys runtime below. @@ -495,7 +658,7 @@ async fn morph_tx_v0_token_fee_still_charged_on_revert() -> eyre::Result<()> { use morph_node::test_utils::{make_deploy_tx, wallet_at_index}; use reth_provider::{ReceiptProvider, StateProviderFactory}; - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let sender = alloy_primitives::address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); diff --git a/crates/node/tests/it/reference_index.rs b/crates/node/tests/it/reference_index.rs index a54a1c12..5a19f94f 100644 --- a/crates/node/tests/it/reference_index.rs +++ b/crates/node/tests/it/reference_index.rs @@ -52,7 +52,7 @@ where async fn reference_index_finds_single_morph_tx() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let reference = B256::with_last_byte(0x99); @@ -90,7 +90,7 @@ async fn reference_index_finds_single_morph_tx() -> eyre::Result<()> { async fn reference_index_pagination() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let reference = B256::with_last_byte(0xaa); let mut tx_hashes = Vec::new(); @@ -148,7 +148,7 @@ async fn reference_index_pagination() -> eyre::Result<()> { async fn reference_index_no_results_for_unrelated_reference() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let reference = B256::with_last_byte(0xbb); let other_reference = B256::with_last_byte(0xcc); diff --git a/crates/node/tests/it/rpc.rs b/crates/node/tests/it/rpc.rs index 2666922c..c848a667 100644 --- a/crates/node/tests/it/rpc.rs +++ b/crates/node/tests/it/rpc.rs @@ -17,7 +17,6 @@ use reth_provider::{ AccountReader, BlockReader, BlockReaderIdExt, HeaderProvider, ReceiptProvider, StateProviderFactory, TransactionsProvider, }; -use reth_tasks::TaskManager; use serde_json::Value; use super::helpers::wallet_to_arc; @@ -27,7 +26,7 @@ use super::helpers::wallet_to_arc; async fn block_number_advances_correctly() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -59,7 +58,7 @@ async fn block_number_advances_correctly() -> eyre::Result<()> { async fn block_hash_consistent_with_storage() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -91,7 +90,7 @@ async fn block_hash_consistent_with_storage() -> eyre::Result<()> { async fn block_transaction_count_correct() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -127,7 +126,7 @@ async fn block_transaction_count_correct() -> eyre::Result<()> { async fn transaction_retrievable_by_hash() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -158,7 +157,7 @@ async fn transaction_retrievable_by_hash() -> eyre::Result<()> { async fn block_gas_used_reflects_execution() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -184,7 +183,7 @@ async fn block_gas_used_reflects_execution() -> eyre::Result<()> { async fn morph_tx_receipt_contains_fee_fields() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Build and inject a MorphTx v0 with ERC20 fee payment @@ -245,7 +244,7 @@ async fn morph_tx_receipt_contains_fee_fields() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn balance_decreases_after_eth_transfer() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -276,7 +275,7 @@ async fn balance_decreases_after_eth_transfer() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn nonce_increments_after_tx() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -304,7 +303,7 @@ async fn nonce_increments_after_tx() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn l1_message_receipt_l1_fee_is_zero() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let l1_msg = L1MessageBuilder::new(0) @@ -335,7 +334,7 @@ async fn l1_message_receipt_l1_fee_is_zero() -> eyre::Result<()> { async fn transaction_receipt_exposes_morph_fields_over_rpc() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let reference = B256::with_last_byte(0x44); @@ -397,12 +396,84 @@ async fn transaction_receipt_exposes_morph_fields_over_rpc() -> eyre::Result<()> Ok(()) } +/// `eth_getBlockReceipts` uses the same Morph receipt converter as per-tx receipt RPCs. +#[tokio::test(flavor = "multi_thread")] +async fn block_receipts_expose_morph_fields_over_rpc() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; + let mut node = nodes.pop().unwrap(); + + let reference = B256::with_last_byte(0x66); + let memo = alloy_primitives::Bytes::from_static(b"block-receipts"); + let expected_reference = reference.to_string(); + let expected_memo = memo.to_string(); + + let raw_tx = MorphTxBuilder::new(wallet.chain_id, wallet.inner.clone(), 0) + .with_v1_token_fee(TEST_TOKEN_ID) + .with_reference(reference) + .with_memo(memo) + .with_data(vec![0xbb; 16]) + .build_signed()?; + node.rpc.inject_tx(raw_tx).await?; + + let payload = node.advance_block().await?; + let block_number = payload.block().number(); + let block_number_param = format!("0x{block_number:x}"); + let tx_hash = *payload + .block() + .body() + .transactions + .first() + .unwrap() + .tx_hash(); + let client = node + .rpc_client() + .ok_or_else(|| eyre::eyre!("HTTP RPC client not available"))?; + + let tx_receipt: Value = client + .request("eth_getTransactionReceipt", (tx_hash,)) + .await?; + let block_receipts: Value = client + .request("eth_getBlockReceipts", (block_number_param,)) + .await?; + let block_receipt = block_receipts + .as_array() + .and_then(|receipts| receipts.first()) + .ok_or_else(|| eyre::eyre!("expected a block receipt"))?; + + for field in [ + "type", + "version", + "feeTokenID", + "feeRate", + "tokenScale", + "feeLimit", + "reference", + "memo", + "l1Fee", + ] { + assert_eq!( + block_receipt[field], tx_receipt[field], + "block receipt field {field} must match eth_getTransactionReceipt" + ); + } + assert_eq!(block_receipt["type"].as_str(), Some("0x7f")); + assert_eq!( + block_receipt["reference"].as_str(), + Some(expected_reference.as_str()) + ); + assert_eq!(block_receipt["memo"].as_str(), Some(expected_memo.as_str())); + + Ok(()) +} + /// `eth_getTransactionByHash` exposes MorphTx reference and memo over JSON-RPC. #[tokio::test(flavor = "multi_thread")] async fn transaction_by_hash_exposes_morph_fields_over_rpc() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let reference = B256::with_last_byte(0x55); @@ -445,10 +516,9 @@ async fn transaction_by_hash_exposes_morph_fields_over_rpc() -> eyre::Result<()> } /// Produces a simple one-transaction block on the standard Jade profile and returns the -/// node, task manager, and identifiers needed by the replay-based debug / trace RPCs. -async fn build_standard_jade_block_for_debug_trace() --> eyre::Result<(MorphTestNode, TaskManager, B256, B256)> { - let (mut nodes, tasks, wallet) = TestNodeBuilder::new().build().await?; +/// node and identifiers needed by the replay-based debug / trace RPCs. +async fn build_standard_jade_block_for_debug_trace() -> eyre::Result<(MorphTestNode, B256, B256)> { + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let tx = TxLegacy { @@ -479,7 +549,7 @@ async fn build_standard_jade_block_for_debug_trace() .tx_hash(); let block_hash = payload.block().hash(); - Ok((node, tasks, tx_hash, block_hash)) + Ok((node, tx_hash, block_hash)) } /// Comprehensive test: debug + trace replay APIs on a standard Jade block with Cancun active. @@ -494,7 +564,7 @@ async fn debug_trace_replay_apis_work_for_standard_jade_block() -> eyre::Result< reth_tracing::init_test_tracing(); - let (node, _tasks, tx_hash, block_hash) = build_standard_jade_block_for_debug_trace().await?; + let (node, tx_hash, block_hash) = build_standard_jade_block_for_debug_trace().await?; // Verify parent_beacon_block_root is None (Morph L2 does not use beacon chain) let block = node @@ -586,3 +656,227 @@ async fn debug_trace_replay_apis_work_for_standard_jade_block() -> eyre::Result< Ok(()) } + +/// `eth_estimateGas` rejects a request whose sender cannot cover the L1 data fee. +/// +/// Exercises the `MorphEthApi::caller_gas_allowance` override. In reth v2.0.0 +/// the upstream `estimate_gas_with` sets `cfg_env.disable_fee_charge = true`, +/// so the EVM handler short-circuits and never checks balance — the L1 fee +/// cap must instead be enforced in the gas-allowance pre-check. +/// +/// Setup: +/// - An unfunded random sender (`balance = 0`). +/// - A zero-value transfer with a non-zero `gasPrice` so the request goes +/// through the balance-based allowance cap. +/// +/// Expected: +/// - The RPC returns an error whose message contains +/// `"insufficient funds for l1 fee"` (matches go-ethereum's error string +/// for this case). +#[tokio::test(flavor = "multi_thread")] +async fn estimate_gas_reports_insufficient_funds_for_l1_fee() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; + let mut node = nodes.pop().unwrap(); + + // Produce a block so the L1 gas oracle state (genesis alloc) is live + // and `caller_gas_allowance` can read the Curie slots. + advance_chain(1, &mut node, wallet_to_arc(wallet)).await?; + + let client = node + .rpc_client() + .ok_or_else(|| eyre::eyre!("HTTP RPC client not available"))?; + + // Unfunded random sender. + let poor_sender = Address::random(); + let recipient = Address::random(); + + let params = (serde_json::json!({ + "from": poor_sender, + "to": recipient, + "value": "0x0", + "gasPrice": "0x3b9aca00", // 1 Gwei — ensures the balance-based cap runs + }),); + + let result: Result = client.request("eth_estimateGas", params).await; + + let err = + result.expect_err("eth_estimateGas must fail for a sender that cannot cover the L1 fee"); + let err_str = err.to_string(); + assert!( + err_str.contains("insufficient funds for l1 fee"), + "expected 'insufficient funds for l1 fee', got: {err_str}" + ); + + Ok(()) +} + +/// `eth_call` does NOT reject an unfunded sender on L1-fee grounds. +/// +/// This is the companion to `estimate_gas_reports_insufficient_funds_for_l1_fee` +/// — same caller/scenario, but invoked via `eth_call` instead of +/// `eth_estimateGas`. morph-geth's `DoCall` uses +/// `ApplyMessage(..., Big0)`, so L1 fee is not deducted for this RPC +/// path; our override must stay out of `eth_call`'s way. +/// +/// Setup: +/// - An unfunded random sender (`balance = 0`). +/// - `eth_call` with non-zero `gasPrice`, without explicit `gas` — the +/// path that routes through `Call::caller_gas_allowance`. +/// +/// Expected: +/// - The call succeeds (empty output is fine; we only check that no +/// `"insufficient funds"` error is returned). +#[tokio::test(flavor = "multi_thread")] +async fn eth_call_does_not_reject_unfunded_sender_on_l1_fee() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; + let mut node = nodes.pop().unwrap(); + + advance_chain(1, &mut node, wallet_to_arc(wallet)).await?; + + let client = node + .rpc_client() + .ok_or_else(|| eyre::eyre!("HTTP RPC client not available"))?; + + let poor_sender = Address::random(); + let recipient = Address::random(); + + let params = ( + serde_json::json!({ + "from": poor_sender, + "to": recipient, + "value": "0x0", + "gasPrice": "0x3b9aca00", + }), + "latest", + ); + + let result: Result = client.request("eth_call", params).await; + + // eth_call may surface a revert error for other reasons, but it must + // never bubble up our L1-fee / transfer affordability errors. + if let Err(err) = &result { + let err_str = err.to_string(); + assert!( + !err_str.contains("insufficient funds"), + "eth_call must not reject on L1-fee grounds: {err_str}" + ); + } + + Ok(()) +} + +/// MorphTx token-fee `eth_call` does NOT reject a zero-ETH sender on +/// `(ETH_balance − value) / gas_price` grounds. +/// +/// Guards against a regression where the shared +/// `Call::caller_gas_allowance` hook short-circuits to upstream's +/// ETH-based allowance for `eth_call` / `createAccessList`. For a +/// MorphTx paying fees in a token, the caller can legitimately hold +/// zero ETH — gas and L1 fee come out of the fee token. Applying the +/// upstream ETH cap would set the allowance to 0 (or reject outright) +/// and break token-fee `eth_call` / `createAccessList`. +/// +/// Setup: +/// - An unfunded random sender (`balance = 0`). +/// - `eth_call` with `feeTokenID = 1`, non-zero `gasPrice`, no explicit +/// `gas` — the path that routes through `caller_gas_allowance`. +/// +/// Expected: +/// - The call must not surface our `insufficient funds for transfer` +/// or `insufficient funds for l1 fee` errors (it may return a +/// pass-through EVM/handler error such as "invalid fee token" if the +/// test genesis doesn't have token 1 wired up, which is out of scope). +#[tokio::test(flavor = "multi_thread")] +async fn eth_call_token_fee_does_not_reject_zero_eth_sender() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; + let mut node = nodes.pop().unwrap(); + + advance_chain(1, &mut node, wallet_to_arc(wallet)).await?; + + let client = node + .rpc_client() + .ok_or_else(|| eyre::eyre!("HTTP RPC client not available"))?; + + let poor_sender = Address::random(); + let recipient = Address::random(); + + let params = ( + serde_json::json!({ + "from": poor_sender, + "to": recipient, + "value": "0x0", + "gasPrice": "0x3b9aca00", + "feeTokenID": "0x1", + "feeLimit": "0xde0b6b3a7640000", // 1e18 token units + }), + "latest", + ); + + let result: Result = client.request("eth_call", params).await; + + if let Err(err) = &result { + let err_str = err.to_string(); + assert!( + !err_str.contains("insufficient funds for transfer") + && !err_str.contains("insufficient funds for l1 fee"), + "token-fee eth_call must not reject on ETH-balance grounds: {err_str}" + ); + } + + Ok(()) +} + +/// `eth_estimateGas` rejects a request whose sender cannot afford `tx.value`. +/// +/// Exercises the first balance check in `MorphEthApi::caller_gas_allowance` +/// — the one that fires before the L1 fee is even computed. +/// +/// Setup: +/// - An unfunded random sender (`balance = 0`). +/// - A non-zero `value`, which makes `value > balance` trivially true. +/// +/// Expected: +/// - The RPC returns an error whose message contains +/// `"insufficient funds for transfer"` (matches go-ethereum's error string +/// for this case). +#[tokio::test(flavor = "multi_thread")] +async fn estimate_gas_reports_insufficient_funds_for_transfer() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; + let mut node = nodes.pop().unwrap(); + + advance_chain(1, &mut node, wallet_to_arc(wallet)).await?; + + let client = node + .rpc_client() + .ok_or_else(|| eyre::eyre!("HTTP RPC client not available"))?; + + let poor_sender = Address::random(); + let recipient = Address::random(); + + let params = (serde_json::json!({ + "from": poor_sender, + "to": recipient, + "value": "0x64", // 100 wei > 0 balance + "gasPrice": "0x3b9aca00", + }),); + + let result: Result = client.request("eth_estimateGas", params).await; + + let err = + result.expect_err("eth_estimateGas must fail when value exceeds the sender's balance"); + let err_str = err.to_string(); + assert!( + err_str.contains("insufficient funds for transfer"), + "expected 'insufficient funds for transfer', got: {err_str}" + ); + + Ok(()) +} diff --git a/crates/node/tests/it/sync.rs b/crates/node/tests/it/sync.rs index 5ebac50c..a10f1bad 100644 --- a/crates/node/tests/it/sync.rs +++ b/crates/node/tests/it/sync.rs @@ -16,7 +16,7 @@ use tokio::sync::Mutex; async fn can_sync() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = setup(1, false).await?; + let (mut nodes, wallet) = setup(1, false).await?; let mut node = nodes.pop().unwrap(); let wallet = Arc::new(Mutex::new(wallet)); diff --git a/crates/node/tests/it/txpool.rs b/crates/node/tests/it/txpool.rs index c9945767..acd3e31d 100644 --- a/crates/node/tests/it/txpool.rs +++ b/crates/node/tests/it/txpool.rs @@ -26,7 +26,7 @@ use super::helpers::wallet_to_arc; async fn l1_message_rejected_by_pool() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, _wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, _wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let l1_msg = L1MessageBuilder::new(0) @@ -48,7 +48,7 @@ async fn l1_message_rejected_by_pool() -> eyre::Result<()> { async fn legacy_tx_accepted() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Build a legacy transaction (type 0x00) @@ -92,7 +92,7 @@ async fn legacy_tx_accepted() -> eyre::Result<()> { async fn nonce_too_low_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let wallet = wallet_to_arc(wallet); @@ -121,7 +121,7 @@ async fn nonce_too_low_rejected() -> eyre::Result<()> { async fn future_nonce_queued() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); // Submit tx with nonce=5 (account nonce is 0, so this is "future") @@ -149,7 +149,7 @@ async fn future_nonce_queued() -> eyre::Result<()> { async fn future_nonce_queued_then_promoted_after_gap_filled() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); // Submit nonce=2 — this is a future nonce; nonces 0 and 1 are missing @@ -197,7 +197,7 @@ async fn future_nonce_queued_then_promoted_after_gap_filled() -> eyre::Result<() #[tokio::test(flavor = "multi_thread")] async fn eip2930_accepted_by_pool() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let mut node = nodes.pop().unwrap(); let raw_tx = morph_node::test_utils::make_eip2930_tx(wallet.chain_id, wallet.inner.clone(), 0)?; @@ -210,7 +210,7 @@ async fn eip2930_accepted_by_pool() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn eip4844_tx_rejected_by_pool() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let blob_tx = make_eip4844_tx(wallet.chain_id, wallet.inner.clone(), 0)?; @@ -225,7 +225,7 @@ async fn eip4844_tx_rejected_by_pool() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn duplicate_tx_rejected_by_pool() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); let raw_tx = make_transfer_tx(wallet.chain_id, wallet.inner.clone(), 0).await; @@ -238,7 +238,7 @@ async fn duplicate_tx_rejected_by_pool() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn tx_gas_limit_exceeds_block_limit_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); use alloy_consensus::{SignableTransaction, TxEip1559}; @@ -273,7 +273,7 @@ async fn tx_gas_limit_exceeds_block_limit_rejected() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn tx_max_fee_below_base_fee_accepted_for_queuing() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); use alloy_consensus::{SignableTransaction, TxEip1559}; @@ -310,7 +310,7 @@ async fn tx_max_fee_below_base_fee_accepted_for_queuing() -> eyre::Result<()> { #[tokio::test(flavor = "multi_thread")] async fn morph_tx_v1_zero_eth_balance_rejected() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = TestNodeBuilder::new().build().await?; + let (mut nodes, wallet) = TestNodeBuilder::new().build().await?; let node = nodes.pop().unwrap(); use morph_node::test_utils::MorphTxBuilder; diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index ab42ef78..bef78a23 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -22,6 +22,7 @@ morph-primitives = { workspace = true, features = ["serde-bincode-compat", "reth reth-basic-payload-builder.workspace = true reth-chainspec.workspace = true reth-evm.workspace = true +reth-execution-cache.workspace = true reth-execution-types.workspace = true reth-payload-builder.workspace = true reth-payload-primitives.workspace = true @@ -29,6 +30,7 @@ reth-payload-util.workspace = true reth-primitives-traits.workspace = true reth-revm.workspace = true reth-storage-api.workspace = true +reth-trie-parallel.workspace = true reth-transaction-pool.workspace = true # Alloy diff --git a/crates/payload/builder/src/builder.rs b/crates/payload/builder/src/builder.rs index 5de02b88..2f35359d 100644 --- a/crates/payload/builder/src/builder.rs +++ b/crates/payload/builder/src/builder.rs @@ -9,7 +9,9 @@ use alloy_rlp::Encodable; use morph_chainspec::MorphChainSpec; use morph_chainspec::{L2_MESSAGE_QUEUE_ADDRESS, L2_MESSAGE_QUEUE_WITHDRAW_TRIE_ROOT_SLOT}; use morph_evm::{MorphEvmConfig, MorphNextBlockEnvAttributes}; -use morph_payload_types::{ExecutableL2Data, MorphBuiltPayload, MorphPayloadBuilderAttributes}; +use morph_payload_types::{ + ExecutableL2Data, MorphBuiltPayload, MorphPayloadAttributes, MorphPayloadBuilderAttributes, +}; use morph_primitives::{MorphHeader, MorphTxEnvelope}; use reth_basic_payload_builder::{ BuildArguments, BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour, PayloadBuilder, @@ -19,20 +21,19 @@ use reth_chainspec::ChainSpecProvider; use reth_evm::{ ConfigureEvm, Database, Evm, NextBlockEnvAttributes, block::{BlockExecutionError, BlockValidationError}, - execute::{BlockBuilder, BlockBuilderOutcome}, + execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor}, }; -use reth_execution_types::ExecutionOutcome; +use reth_execution_cache::{CachedStateMetrics, CachedStateMetricsSource, CachedStateProvider}; +use reth_execution_types::BlockExecutionOutput; use reth_payload_builder::PayloadId; -use reth_payload_primitives::{ - BuiltPayloadExecutedBlock, PayloadBuilderAttributes, PayloadBuilderError, -}; +use reth_payload_primitives::{BuiltPayloadExecutedBlock, PayloadBuilderError}; use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; -use reth_primitives_traits::{RecoveredBlock, SealedHeader}; +use reth_primitives_traits::{FastInstant as Instant, RecoveredBlock, SealedHeader}; use reth_revm::{database::StateProviderDatabase, db::State}; use reth_storage_api::{StateProvider, StateProviderFactory}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use revm::context_interface::Block as RevmBlock; -use std::{sync::Arc, time::Instant}; +use std::sync::Arc; /// Reads the withdraw trie root from the L2MessageQueue contract storage. fn read_withdraw_trie_root(db: &mut DB) -> Result { @@ -158,7 +159,7 @@ where /// Constructs a Morph payload from the transactions sent via the payload attributes. fn build_payload<'a, BestTxs>( &self, - args: BuildArguments, + args: BuildArguments, best: impl FnOnce(BestTransactionsAttributes) -> BestTxs + Send + Sync + 'a, ) -> Result, PayloadBuilderError> where @@ -167,26 +168,67 @@ where { let BuildArguments { mut cached_reads, + execution_cache, + trie_handle, config, cancel, best_payload, } = args; + // Convert RPC-level MorphPayloadAttributes to builder-level MorphPayloadBuilderAttributes + let parent_hash = config.parent_header.hash(); + let payload_id = config.payload_id; + let parent_header = config.parent_header.clone(); + let builder_attrs = MorphPayloadBuilderAttributes::try_new( + parent_hash, + config.attributes, + morph_payload_types::MORPH_PAYLOAD_BUILDER_VERSION, + ) + .map_err(|e| PayloadBuilderError::Other(e.into()))?; + let builder_config = PayloadConfig { + parent_header, + attributes: builder_attrs, + payload_id, + }; + let ctx = MorphPayloadBuilderCtx { evm_config: self.evm_config.clone(), - config, + config: builder_config, cancel, best_payload, builder_config: self.config.clone(), metrics: MorphPayloadBuilderMetrics::default(), }; - let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(&state_provider); + // When `--engine.share-execution-cache-with-payload-builder` is set, + // reth's engine provides a SavedCache snapshot associated with the parent + // block. Wrap the state provider so account/storage/code reads consult + // the cache before hitting the DB — amortizes cross-block cost when the + // payload builder and engine both touch overlapping state. + let mut state_provider: Box = + self.client.state_by_block_hash(ctx.parent().hash())?; + if let Some(execution_cache) = execution_cache { + // reth v2.2.0 dropped `SavedCache::metrics`; the canonical pattern + // (see `reth-ethereum-payload`) is to materialize a fresh zeroed + // metrics handle on every payload build — cheap because morph-reth + // builds payloads on demand rather than every 12s like upstream. + state_provider = Box::new(CachedStateProvider::new( + state_provider, + execution_cache.cache().clone(), + CachedStateMetrics::zeroed(CachedStateMetricsSource::Builder), + )); + } + let state = StateProviderDatabase::new(state_provider.as_ref()); // Reuse cached reads from previous runs for incremental payload building - build_payload_inner(cached_reads.as_db_mut(state), &state_provider, ctx, best) - .map(|out| out.with_cached_reads(cached_reads)) + build_payload_inner( + cached_reads.as_db_mut(state), + state_provider.as_ref(), + ctx, + best, + trie_handle, + ) + .map(|out| out.with_cached_reads(cached_reads)) } } @@ -197,7 +239,7 @@ where Client: StateProviderFactory + ChainSpecProvider + Clone, Txs: MorphPayloadTransactions, { - type Attributes = MorphPayloadBuilderAttributes; + type Attributes = MorphPayloadAttributes; type BuiltPayload = MorphBuiltPayload; fn try_build( @@ -225,6 +267,8 @@ where let args = BuildArguments { config, cached_reads: Default::default(), + execution_cache: None, + trie_handle: None, cancel: Default::default(), best_payload: None, }; @@ -334,8 +378,11 @@ impl MorphPayloadBuilderCtx { // Execute the transaction and record EVM execution time. let apply_started = Instant::now(); + // `BlockBuilder::execute_transaction` returns `GasOutput` from + // alloy-evm 0.34; pre-Amsterdam morph treats regular and state gas + // as a single number, so collapse to `tx_gas_used()` immediately. let gas_used = match builder.execute_transaction(recovered_tx.clone()) { - Ok(gas_used) => gas_used, + Ok(gas_output) => gas_output.tx_gas_used(), Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { error, .. @@ -508,8 +555,10 @@ impl MorphPayloadBuilderCtx { } let apply_started = Instant::now(); + // Same reasoning as the L1-message branch above: collapse `GasOutput` + // into a single u64 since we are still pre-Amsterdam. let gas_used = match builder.execute_transaction(tx.clone()) { - Ok(gas_used) => gas_used, + Ok(gas_output) => gas_output.tx_gas_used(), Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { error, .. @@ -634,9 +683,10 @@ impl ExecutionInfo { /// Builds the payload on top of the state. fn build_payload_inner<'a, DB, BestTxs>( db: DB, - state_provider: &impl StateProvider, + state_provider: &(impl StateProvider + ?Sized), ctx: MorphPayloadBuilderCtx, best: impl FnOnce(BestTransactionsAttributes) -> BestTxs + Send + Sync + 'a, + trie_handle: Option, ) -> Result, PayloadBuilderError> where DB: Database, @@ -661,13 +711,15 @@ where // Build next block env attributes let next_block_attrs = MorphNextBlockEnvAttributes { inner: NextBlockEnvAttributes { - timestamp: attributes.inner.timestamp, - suggested_fee_recipient: attributes.inner.suggested_fee_recipient, - prev_randao: attributes.inner.prev_randao, + timestamp: attributes.timestamp, + suggested_fee_recipient: attributes.suggested_fee_recipient, + prev_randao: attributes.prev_randao, gas_limit: attributes.gas_limit.unwrap_or(ctx.parent().gas_limit()), - withdrawals: Some(attributes.inner.withdrawals.clone()), - parent_beacon_block_root: attributes.inner.parent_beacon_block_root, + withdrawals: Some(attributes.withdrawals.clone()), + parent_beacon_block_root: attributes.parent_beacon_block_root, extra_data: Default::default(), + // Morph L2 has no PoS slot semantics; field added in alloy 2.0. + slot_number: None, }, base_fee_per_gas: attributes.base_fee_per_gas, }; @@ -678,6 +730,16 @@ where .builder_for_next_block(&mut db, ctx.parent(), next_block_attrs) .map_err(PayloadBuilderError::other)?; + // If the engine tree provided a sparse-trie state root handle, wire the + // state hook so per-tx state diffs stream to the background trie task + // during execution. The final `state_root()` recv() at finish time will + // return quickly since most work is done concurrently. + if let Some(ref handle) = trie_handle { + builder + .executor_mut() + .set_state_hook(Some(Box::new(handle.state_hook()))); + } + // 1. Apply pre-execution changes (system contracts, etc.) builder.apply_pre_execution_changes().map_err(|err| { tracing::warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); @@ -738,16 +800,44 @@ where // Read withdraw_trie_root from L2MessageQueue contract storage // This must be done before finish() consumes the builder - let withdraw_trie_root = read_withdraw_trie_root(builder.evm_mut().db_mut()) - .map_err(|err| PayloadBuilderError::other(MorphPayloadBuilderError::Database(err)))?; - - // 6. Finish building the block + let withdraw_trie_root = + read_withdraw_trie_root(builder.evm_mut().db_mut()).map_err(|err| { + PayloadBuilderError::other(MorphPayloadBuilderError::Storage(err.to_string())) + })?; + + // 6. Finish building the block. + // + // When `trie_handle` is provided, drop the state hook to signal FinishedStateUpdates + // to the background sparse trie task (via StateHookSender's Drop impl), then wait for + // the final root. Fall back to synchronous state root if the task fails. let BlockBuilderOutcome { execution_result, hashed_state, trie_updates, mut block, - } = builder.finish(state_provider)?; + } = if let Some(mut handle) = trie_handle { + builder.executor_mut().set_state_hook(None); + match handle.state_root() { + Ok(outcome) => builder.finish( + state_provider, + Some(( + outcome.state_root, + Arc::unwrap_or_clone(outcome.trie_updates), + )), + )?, + Err(err) => { + tracing::warn!( + target: "payload_builder", + id = %ctx.payload_id(), + %err, + "sparse trie task failed, falling back to sync state root", + ); + builder.finish(state_provider, None)? + } + } + } else { + builder.finish(state_provider, None)? + }; // Update MorphHeader with next_l1_msg_index. // Since hash_slow() only hashes the inner header, we can update the @@ -791,12 +881,10 @@ where hash: sealed_block.hash(), }; - let execution_output = ExecutionOutcome::new( - db.take_bundle(), - vec![execution_result.receipts], - header.number(), - vec![execution_result.requests], - ); + let execution_output = BlockExecutionOutput { + result: execution_result, + state: db.take_bundle(), + }; let executed = BuiltPayloadExecutedBlock { recovered_block: Arc::new(block), @@ -1017,16 +1105,19 @@ mod tests { // ========================================================================= fn test_ctx(best_payload: Option) -> MorphPayloadBuilderCtx { + let attrs = MorphPayloadBuilderAttributes::try_new( + B256::ZERO, + morph_payload_types::MorphPayloadAttributes::default(), + morph_payload_types::MORPH_PAYLOAD_BUILDER_VERSION, + ) + .unwrap(); + let payload_id = attrs.payload_id(); MorphPayloadBuilderCtx { evm_config: test_evm_config(), config: PayloadConfig::new( Arc::new(SealedHeader::seal_slow(MorphHeader::default())), - MorphPayloadBuilderAttributes::try_new( - B256::ZERO, - morph_payload_types::MorphPayloadAttributes::default(), - 1, - ) - .unwrap(), + attrs, + payload_id, ), cancel: Default::default(), best_payload, diff --git a/crates/payload/builder/src/config.rs b/crates/payload/builder/src/config.rs index bed39aa6..7a618b5b 100644 --- a/crates/payload/builder/src/config.rs +++ b/crates/payload/builder/src/config.rs @@ -2,7 +2,8 @@ use core::time::Duration; use reth_chainspec::MIN_TRANSACTION_GAS; -use std::{fmt::Debug, time::Instant}; +use reth_primitives_traits::FastInstant as Instant; +use std::fmt::Debug; /// Minimal data bytes size per transaction. /// This is a conservative estimate for the minimum encoded transaction size. diff --git a/crates/payload/builder/src/error.rs b/crates/payload/builder/src/error.rs index b270e3f0..70f7cc99 100644 --- a/crates/payload/builder/src/error.rs +++ b/crates/payload/builder/src/error.rs @@ -1,7 +1,5 @@ //! Morph payload builder error types. -use reth_evm::execute::ProviderError; - /// Errors that can occur during Morph payload building. #[derive(Debug, thiserror::Error)] pub enum MorphPayloadBuilderError { @@ -39,7 +37,7 @@ pub enum MorphPayloadBuilderError { #[error("L1 message appears after regular transaction")] L1MessageAfterRegularTx, - /// Database error when reading contract storage. - #[error("database error: {0}")] - Database(#[from] ProviderError), + /// Generic storage error (e.g. from revm EvmDatabaseError, ProviderError). + #[error("storage error: {0}")] + Storage(String), } diff --git a/crates/payload/types/Cargo.toml b/crates/payload/types/Cargo.toml index 6b1f1d04..9776a467 100644 --- a/crates/payload/types/Cargo.toml +++ b/crates/payload/types/Cargo.toml @@ -13,13 +13,12 @@ workspace = true [dependencies] # Morph -morph-primitives = { workspace = true, features = ["serde-bincode-compat"] } +morph-primitives = { workspace = true, features = ["serde-bincode-compat", "reth-codec"] } # Reth -reth-payload-builder.workspace = true reth-payload-primitives.workspace = true reth-primitives-traits.workspace = true -reth-ethereum-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"] } +reth-ethereum-primitives = { workspace = true, features = ["serde"] } # Alloy alloy-consensus.workspace = true diff --git a/crates/payload/types/src/attributes.rs b/crates/payload/types/src/attributes.rs index ef847131..3aaea3ae 100644 --- a/crates/payload/types/src/attributes.rs +++ b/crates/payload/types/src/attributes.rs @@ -5,11 +5,13 @@ use alloy_eips::eip4895::{Withdrawal, Withdrawals}; use alloy_primitives::{Address, B256, Bytes}; use alloy_rpc_types_engine::{PayloadAttributes, PayloadId}; use morph_primitives::MorphTxEnvelope; -use reth_payload_builder::EthPayloadBuilderAttributes; -use reth_payload_primitives::PayloadBuilderAttributes; use reth_primitives_traits::{Recovered, SignerRecoverable, WithEncoded}; use sha2::{Digest, Sha256}; +/// Version byte mixed into Morph payload IDs. Bumped only when the payload-attribute +/// hashing scheme materially changes; serves as a domain separator across versions. +pub const MORPH_PAYLOAD_BUILDER_VERSION: u8 = 1; + /// Morph-specific payload attributes for Engine API. /// /// This extends the standard Ethereum [`PayloadAttributes`] with L2-specific fields @@ -52,6 +54,10 @@ pub struct MorphPayloadAttributes { } impl reth_payload_primitives::PayloadAttributes for MorphPayloadAttributes { + fn payload_id(&self, parent_hash: &B256) -> PayloadId { + self.morph_payload_id(parent_hash) + } + fn timestamp(&self) -> u64 { self.inner.timestamp } @@ -63,16 +69,61 @@ impl reth_payload_primitives::PayloadAttributes for MorphPayloadAttributes { fn parent_beacon_block_root(&self) -> Option { self.inner.parent_beacon_block_root } + + fn slot_number(&self) -> Option { + // Morph L2 has no PoS slot semantics. + None + } +} + +impl MorphPayloadAttributes { + /// Computes the Morph payload ID without decoding or recovering transaction bytes. + pub fn morph_payload_id(&self, parent_hash: &B256) -> PayloadId { + payload_id_morph(parent_hash, self, MORPH_PAYLOAD_BUILDER_VERSION) + } +} + +impl From for MorphPayloadAttributes { + fn from(inner: PayloadAttributes) -> Self { + Self { + inner, + transactions: None, + gas_limit: None, + base_fee_per_gas: None, + } + } } /// Internal payload builder attributes. /// /// This is the internal representation used by the payload builder, /// with decoded L1 messages and computed payload ID. -#[derive(Debug, Clone)] +/// +/// Implements `reth_payload_primitives::PayloadAttributes` so it can serve as the +/// `type Attributes` in `PayloadBuilder` (v2.0.0 requires the builder attributes to +/// implement PayloadAttributes). The serde impls are required by the trait bound. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct MorphPayloadBuilderAttributes { - /// Inner Ethereum payload builder attributes. - pub inner: EthPayloadBuilderAttributes, + /// Computed payload ID. + pub id: PayloadId, + + /// Parent block hash. + pub parent: B256, + + /// Block timestamp. + pub timestamp: u64, + + /// Suggested fee recipient. + pub suggested_fee_recipient: Address, + + /// Previous RANDAO value. + pub prev_randao: B256, + + /// Withdrawals. + pub withdrawals: Withdrawals, + + /// Parent beacon block root. + pub parent_beacon_block_root: Option, /// Decoded L1 message transactions with original encoded bytes. /// @@ -81,6 +132,11 @@ pub struct MorphPayloadBuilderAttributes { /// /// L1 messages are decoded and recovered during construction to avoid /// repeated decoding in the payload builder. + /// + /// Skipped for serde: this is purely an internal runtime field derived from + /// `MorphPayloadAttributes::transactions` during `try_new`. It is never + /// serialised/deserialised as part of the PayloadAttributes trait contract. + #[serde(skip)] pub transactions: Vec>>, /// Optional gas limit override propagated to EVM env construction. @@ -90,15 +146,13 @@ pub struct MorphPayloadBuilderAttributes { pub base_fee_per_gas: Option, } -impl PayloadBuilderAttributes for MorphPayloadBuilderAttributes { - type RpcPayloadAttributes = MorphPayloadAttributes; - type Error = alloy_rlp::Error; - - fn try_new( +impl MorphPayloadBuilderAttributes { + /// Build from parent hash + RPC attributes + version byte, decoding L1 messages. + pub fn try_new( parent: B256, attributes: MorphPayloadAttributes, version: u8, - ) -> Result { + ) -> Result { let id = payload_id_morph(&parent, &attributes, version); // Decode and recover L1 message transactions @@ -119,8 +173,7 @@ impl PayloadBuilderAttributes for MorphPayloadBuilderAttributes { }) .collect::, alloy_rlp::Error>>()?; - // Build inner Ethereum attributes - let inner = EthPayloadBuilderAttributes { + Ok(Self { id, parent, timestamp: attributes.inner.timestamp, @@ -128,60 +181,75 @@ impl PayloadBuilderAttributes for MorphPayloadBuilderAttributes { prev_randao: attributes.inner.prev_randao, withdrawals: attributes.inner.withdrawals.unwrap_or_default().into(), parent_beacon_block_root: attributes.inner.parent_beacon_block_root, - }; - - Ok(Self { - inner, transactions, gas_limit: attributes.gas_limit, base_fee_per_gas: attributes.base_fee_per_gas, }) } - fn payload_id(&self) -> PayloadId { - self.inner.id + /// Returns the payload ID. + pub fn payload_id(&self) -> PayloadId { + self.id } - fn parent(&self) -> B256 { - self.inner.parent + /// Returns the parent block hash. + pub fn parent(&self) -> B256 { + self.parent } - fn timestamp(&self) -> u64 { - self.inner.timestamp + /// Returns the block timestamp. + pub fn timestamp(&self) -> u64 { + self.timestamp } - fn parent_beacon_block_root(&self) -> Option { - self.inner.parent_beacon_block_root + /// Returns the optional parent beacon block root. + pub fn parent_beacon_block_root(&self) -> Option { + self.parent_beacon_block_root } - fn suggested_fee_recipient(&self) -> Address { - self.inner.suggested_fee_recipient + /// Returns the suggested fee recipient. + pub fn suggested_fee_recipient(&self) -> Address { + self.suggested_fee_recipient } - fn prev_randao(&self) -> B256 { - self.inner.prev_randao + /// Returns the previous RANDAO value. + pub fn prev_randao(&self) -> B256 { + self.prev_randao } - fn withdrawals(&self) -> &Withdrawals { - &self.inner.withdrawals + /// Returns the withdrawals. + pub fn withdrawals(&self) -> &Withdrawals { + &self.withdrawals } -} -impl MorphPayloadBuilderAttributes { /// Returns true if there are L1 messages to execute. pub fn has_l1_messages(&self) -> bool { !self.transactions.is_empty() } } -impl From for MorphPayloadBuilderAttributes { - fn from(inner: EthPayloadBuilderAttributes) -> Self { - Self { - inner, - transactions: vec![], - gas_limit: None, - base_fee_per_gas: None, - } +/// `payload_id()` ignores the `parent_hash` arg and returns the pre-computed `self.id` +/// (already derived from parent + rpc-attrs during `try_new`). +impl reth_payload_primitives::PayloadAttributes for MorphPayloadBuilderAttributes { + fn payload_id(&self, _parent_hash: &B256) -> PayloadId { + self.id + } + + fn timestamp(&self) -> u64 { + self.timestamp + } + + fn withdrawals(&self) -> Option<&Vec> { + Some(self.withdrawals.as_ref()) + } + + fn parent_beacon_block_root(&self) -> Option { + self.parent_beacon_block_root + } + + fn slot_number(&self) -> Option { + // Morph L2 has no PoS slot semantics. + None } } @@ -264,6 +332,8 @@ mod tests { suggested_fee_recipient: Address::random(), withdrawals: None, parent_beacon_block_root: None, + // Morph L2 has no PoS slot semantics; field added in alloy 2.0. + slot_number: None, }, transactions: None, gas_limit: None, @@ -489,10 +559,28 @@ mod tests { ); } + #[test] + fn test_morph_payload_id_does_not_decode_transactions() { + let parent = B256::from([0x01; 32]); + let mut attrs = create_test_attributes(); + attrs.transactions = Some(vec![Bytes::from_static(b"not a valid encoded transaction")]); + + let id = attrs.morph_payload_id(&parent); + + assert_eq!( + id, + payload_id_morph(&parent, &attrs, MORPH_PAYLOAD_BUILDER_VERSION) + ); + } + #[test] fn test_builder_attributes_has_l1_messages_empty() { - let attrs = MorphPayloadBuilderAttributes::try_new(B256::ZERO, create_test_attributes(), 1) - .unwrap(); + let attrs = MorphPayloadBuilderAttributes::try_new( + B256::ZERO, + create_test_attributes(), + MORPH_PAYLOAD_BUILDER_VERSION, + ) + .unwrap(); assert!(!attrs.has_l1_messages()); } @@ -506,7 +594,12 @@ mod tests { rpc_attrs.gas_limit = Some(30_000_000); rpc_attrs.base_fee_per_gas = Some(1_000_000_000); - let attrs = MorphPayloadBuilderAttributes::try_new(parent, rpc_attrs, 1).unwrap(); + let attrs = MorphPayloadBuilderAttributes::try_new( + parent, + rpc_attrs, + MORPH_PAYLOAD_BUILDER_VERSION, + ) + .unwrap(); assert_eq!(attrs.parent(), parent); assert_eq!(attrs.timestamp(), 999); diff --git a/crates/payload/types/src/lib.rs b/crates/payload/types/src/lib.rs index a6159f88..4fea843c 100644 --- a/crates/payload/types/src/lib.rs +++ b/crates/payload/types/src/lib.rs @@ -27,12 +27,14 @@ use reth_primitives_traits::{NodePrimitives, SealedBlock}; use std::sync::Arc; // Feature unification: Ensure reth-ethereum-primitives' serde features are enabled -// for transitive dependencies (via reth-payload-builder → reth-chain-state). +// for transitive dependencies (via reth-payload-primitives → reth-chain-state). // This is required to satisfy trait bounds on EthereumReceipt in test builds. use reth_ethereum_primitives as _; // Re-export main types -pub use attributes::{MorphPayloadAttributes, MorphPayloadBuilderAttributes}; +pub use attributes::{ + MORPH_PAYLOAD_BUILDER_VERSION, MorphPayloadAttributes, MorphPayloadBuilderAttributes, +}; pub use built::MorphBuiltPayload; pub use executable_l2_data::ExecutableL2Data; pub use params::{AssembleL2BlockParams, GenericResponse}; @@ -118,13 +120,21 @@ impl ExecutionPayload for MorphExecutionData { fn transaction_count(&self) -> usize { self.block.body().transactions().count() } + + fn gas_limit(&self) -> u64 { + self.block.gas_limit() + } + + fn slot_number(&self) -> Option { + // Morph L2 has no PoS slot semantics. + None + } } impl PayloadTypes for MorphPayloadTypes { type ExecutionData = MorphExecutionData; type BuiltPayload = MorphBuiltPayload; type PayloadAttributes = MorphPayloadAttributes; - type PayloadBuilderAttributes = MorphPayloadBuilderAttributes; fn block_to_payload( block: SealedBlock< diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 8e5f979e..d0c9a8b9 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -26,7 +26,7 @@ alloy-rlp.workspace = true # Codec bytes = { workspace = true, optional = true } -modular-bitfield = { version = "0.11.2", optional = true } +modular-bitfield = { version = "0.13.1", optional = true } serde = { workspace = true, features = ["derive"], optional = true } alloy-serde = { workspace = true, optional = true } @@ -50,7 +50,6 @@ serde-bincode-compat = [ "serde", "alloy-consensus/serde-bincode-compat", "alloy-eips/serde-bincode-compat", - "reth-primitives-traits/serde-bincode-compat", ] reth-codec = [ "serde", diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index 90ac3eda..ba18558b 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -137,6 +137,16 @@ impl BlockHeader for MorphHeader { fn extra_data(&self) -> &Bytes { self.inner.extra_data() } + + fn block_access_list_hash(&self) -> Option { + // EIP-7928 / Amsterdam fork field. Pre-Amsterdam Morph blocks do not carry it. + None + } + + fn slot_number(&self) -> Option { + // PoS slot number, not part of Morph's L2 header model. + None + } } /// Sealable implementation for MorphHeader. @@ -152,9 +162,6 @@ impl Sealable for MorphHeader { } } -#[cfg(feature = "serde-bincode-compat")] -impl reth_primitives_traits::serde_bincode_compat::RlpBincode for MorphHeader {} - impl reth_primitives_traits::InMemorySize for MorphHeader { fn size(&self) -> usize { reth_primitives_traits::InMemorySize::size(&self.inner) + core::mem::size_of::() // next_l1_msg_index @@ -183,6 +190,19 @@ impl reth_primitives_traits::header::HeaderMut for MorphHeader { fn set_difficulty(&mut self, difficulty: U256) { self.inner.set_difficulty(difficulty); } + + fn set_mix_hash(&mut self, mix_hash: B256) { + self.inner.set_mix_hash(mix_hash); + } + + fn set_extra_data(&mut self, extra_data: Bytes) { + self.inner.set_extra_data(extra_data); + } + + fn set_parent_beacon_block_root(&mut self, parent_beacon_block_root: Option) { + self.inner + .set_parent_beacon_block_root(parent_beacon_block_root); + } } #[cfg(feature = "reth-codec")] @@ -196,7 +216,7 @@ impl reth_db_api::table::Compress for MorphHeader { #[cfg(feature = "reth-codec")] impl reth_db_api::table::Decompress for MorphHeader { - fn decompress(value: &[u8]) -> Result { + fn decompress(value: &[u8]) -> Result { let (obj, _) = reth_codecs::Compact::from_compact(value, value.len()); Ok(obj) } @@ -234,6 +254,11 @@ mod tests { excess_blob_gas: None, parent_beacon_block_root: None, requests_hash: None, + // Pre-Amsterdam morph headers carry no block-access-list hash and + // no PoS slot number. Both fields were introduced upstream in + // alloy 2.0 / EIP-7928. + block_access_list_hash: None, + slot_number: None, } } diff --git a/crates/primitives/src/receipt/mod.rs b/crates/primitives/src/receipt/mod.rs index a04e4e66..a74fd369 100644 --- a/crates/primitives/src/receipt/mod.rs +++ b/crates/primitives/src/receipt/mod.rs @@ -414,9 +414,6 @@ impl InMemorySize for MorphReceipt { } } -#[cfg(feature = "serde-bincode-compat")] -impl reth_primitives_traits::serde_bincode_compat::RlpBincode for MorphReceipt {} - /// Calculates the receipt root for a header. /// /// This function computes the Merkle root of receipts using the standard encoding @@ -456,8 +453,8 @@ mod compact { /// they don't implement `Compact` in reth_codecs. The conversion is lossless. #[derive(reth_codecs::CompactZstd)] #[reth_zstd( - compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR, - decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR + compressor = reth_zstd_compressors::with_receipt_compressor, + decompressor = reth_zstd_compressors::with_receipt_decompressor )] struct CompactMorphReceipt<'a> { success: bool, @@ -590,11 +587,8 @@ mod compact { #[cfg(feature = "reth-codec")] mod db_impl { use super::MorphReceipt; - use reth_codecs::Compact; - use reth_db_api::{ - DatabaseError, - table::{Compress, Decompress}, - }; + use reth_codecs::{Compact, DecompressError}; + use reth_db_api::table::{Compress, Decompress}; impl Compress for MorphReceipt { type Compressed = Vec; @@ -605,7 +599,7 @@ mod db_impl { } impl Decompress for MorphReceipt { - fn decompress(value: &[u8]) -> Result { + fn decompress(value: &[u8]) -> Result { let (obj, _) = Compact::from_compact(value, value.len()); Ok(obj) } diff --git a/crates/primitives/src/transaction/envelope.rs b/crates/primitives/src/transaction/envelope.rs index 6c874bef..4e334a06 100644 --- a/crates/primitives/src/transaction/envelope.rs +++ b/crates/primitives/src/transaction/envelope.rs @@ -233,11 +233,6 @@ impl alloy_consensus::transaction::SignerRecoverable for MorphTxEnvelope { } } -impl reth_primitives_traits::SignedTransaction for MorphTxEnvelope {} - -#[cfg(feature = "serde-bincode-compat")] -impl reth_primitives_traits::serde_bincode_compat::RlpBincode for MorphTxEnvelope {} - #[cfg(feature = "reth-codec")] mod codec { use crate::L1_TX_TYPE_ID; @@ -430,7 +425,7 @@ mod codec { } impl reth_db_api::table::Decompress for MorphTxEnvelope { - fn decompress(value: &[u8]) -> Result { + fn decompress(value: &[u8]) -> Result { let (obj, _) = Compact::from_compact(value, value.len()); Ok(obj) } diff --git a/crates/reference-index/src/tables.rs b/crates/reference-index/src/tables.rs index c8731661..43b6e586 100644 --- a/crates/reference-index/src/tables.rs +++ b/crates/reference-index/src/tables.rs @@ -1,6 +1,7 @@ //! Reference index table declarations. use alloy_primitives::B256; +use reth_codecs::DecompressError; use reth_db_api::{ DatabaseError, TableSet, TableType, TableViewer, table::{Compress, Decode, Decompress, Encode, TableInfo}, @@ -184,10 +185,10 @@ impl Compress for BlockTimestampValue { } impl Decompress for BlockTimestampValue { - fn decompress(value: &[u8]) -> Result { - Ok(Self(u64::from_be_bytes( - value.try_into().map_err(|_| DatabaseError::Decode)?, - ))) + fn decompress(value: &[u8]) -> Result { + Ok(Self(u64::from_be_bytes(value.try_into().map_err( + |_| DecompressError::new(DatabaseError::Decode), + )?))) } } @@ -209,10 +210,10 @@ macro_rules! impl_b256_value_codec { } impl Decompress for $name { - fn decompress(value: &[u8]) -> Result { - Ok(Self(B256::new( - value.try_into().map_err(|_| DatabaseError::Decode)?, - ))) + fn decompress(value: &[u8]) -> Result { + Ok(Self(B256::new(value.try_into().map_err(|_| { + DecompressError::new(DatabaseError::Decode) + })?))) } } }; @@ -234,11 +235,11 @@ impl Compress for MetaValue { } impl Decompress for MetaValue { - fn decompress(value: &[u8]) -> Result { + fn decompress(value: &[u8]) -> Result { Ok(Self(value.to_vec())) } - fn decompress_owned(value: Vec) -> Result { + fn decompress_owned(value: Vec) -> Result { Ok(Self(value)) } } diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index c42a69c1..d8ba55ba 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -8,15 +8,21 @@ use morph_chainspec::hardfork::MorphHardfork; use revm::{ Context, Inspector, context::{CfgEnv, ContextError, Evm, FrameStack, Journal}, + context_interface::host::LoadError, handler::{ EthFrame, EvmTr, FrameInitOrResult, FrameTr, ItemOrResult, instructions::EthInstructions, }, inspector::InspectorEvmTr, interpreter::{ - Host, Instruction, InstructionContext, gas::BLOCKHASH, interpreter::EthInterpreter, - interpreter_types::StackTr, + Host, Instruction, InstructionContext, InstructionResult, + gas::{BLOCKHASH, WARM_STORAGE_READ_COST}, + interpreter::EthInterpreter, + interpreter_types::{RuntimeFlag, StackTr}, + }, + primitives::{ + BLOCK_HASH_HISTORY, + hardfork::SpecId::{BERLIN, ISTANBUL}, }, - primitives::BLOCK_HASH_HISTORY, }; /// The Morph EVM context type. @@ -77,6 +83,167 @@ fn blockhash_morph( *number = morph_blockhash_result(chain_id_u64, current_number_u64, requested_number_u64); } +/// Morph custom SLOAD opcode. +/// +/// Fixes `original_value` corruption caused by revm's `mark_warm_with_transaction_id()`. +/// +/// When token fee deduction marks storage slots cold, the main tx's first SLOAD +/// triggers `mark_warm_with_transaction_id()` which resets `original_value = present_value`, +/// losing the true DB-committed original. This makes SSTORE see "clean" slots (2900 gas) +/// instead of "dirty" (100 gas), causing a 2800 gas mismatch vs go-eth. +/// +/// The DB read hits the State cache (O(1)) and only triggers on cold SLOADs. +fn sload_morph(context: InstructionContext<'_, MorphContext, EthInterpreter>) { + let Some(([], index)) = StackTr::popn_top::<0>(&mut context.interpreter.stack) else { + context.interpreter.halt_underflow(); + return; + }; + + let target = context.interpreter.input.target_address; + let key = *index; + + let additional_cold_cost = context.host.gas_params().cold_storage_additional_cost(); + let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost; + let res = context.host.sload_skip_cold_load(target, key, skip_cold); + + match res { + Ok(storage) => { + if storage.is_cold { + // Read the true committed value from DB (hits State cache, O(1)). + // This matches go-eth's GetCommittedState() returning the un-modified DB value. + let db_original = context.host.journaled_state.database.storage(target, key); + if let Ok(db_original) = db_original + && let Some(acc) = context.host.journaled_state.inner.state.get_mut(&target) + && let Some(slot) = acc.storage.get_mut(&key) + && slot.original_value != db_original + { + slot.original_value = db_original; + } + + if !context + .interpreter + .gas + .record_regular_cost(additional_cold_cost) + { + context.interpreter.halt_oog(); + return; + } + } + + *index = storage.data; + } + Err(LoadError::ColdLoadSkipped) => context.interpreter.halt_oog(), + Err(LoadError::DBError) => context.interpreter.halt_fatal(), + } +} + +/// Morph custom SSTORE opcode. +/// +/// Twin of [`sload_morph`]: revm's standard SSTORE warms a cold slot through +/// the same `mark_warm_with_transaction_id()` path as SLOAD, so forced-cold +/// token-fee slots need the same `original_value` restoration before +/// `sstore_dynamic_gas()` reads it for EIP-2200 accounting. +/// +/// Without this, a main tx that writes a fee-deducted slot WITHOUT first +/// SLOADing it sees a "clean" slot (2900 gas SSTORE_RESET, no refund) +/// instead of a "dirty" slot (100 gas SLOAD_GAS plus refund), causing the +/// same 2800-gas-per-write divergence vs go-eth that `sload_morph` fixes. +/// +/// Uses DB-direct lookup (no per-tx runtime map needed). +fn sstore_morph(context: InstructionContext<'_, MorphContext, EthInterpreter>) { + if context.interpreter.runtime_flag.is_static() { + context + .interpreter + .halt(InstructionResult::StateChangeDuringStaticCall); + return; + } + + let Some([index, value]) = StackTr::popn::<2>(&mut context.interpreter.stack) else { + context.interpreter.halt_underflow(); + return; + }; + + let target = context.interpreter.input.target_address; + let spec_id = context.interpreter.runtime_flag.spec_id(); + + if spec_id.is_enabled_in(ISTANBUL) + && context.interpreter.gas.remaining() <= context.host.gas_params().call_stipend() + { + context + .interpreter + .halt(InstructionResult::ReentrancySentryOOG); + return; + } + + if !context + .interpreter + .gas + .record_regular_cost(context.host.gas_params().sstore_static_gas()) + { + context.interpreter.halt_oog(); + return; + } + + let mut state_load = if spec_id.is_enabled_in(BERLIN) { + let additional_cold_cost = context.host.gas_params().cold_storage_additional_cost(); + let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost; + match context + .host + .sstore_skip_cold_load(target, index, value, skip_cold) + { + Ok(load) => load, + Err(LoadError::ColdLoadSkipped) => { + context.interpreter.halt_oog(); + return; + } + Err(LoadError::DBError) => { + context.interpreter.halt_fatal(); + return; + } + } + } else { + let Some(load) = context.host.sstore(target, index, value) else { + context.interpreter.halt_fatal(); + return; + }; + load + }; + + // Morph fix: on cold access, restore original_value from the DB-committed value. + // Mirrors sload_morph. Only fires on cold path; zero overhead on warm SSTOREs. + if state_load.is_cold { + let db_original = context.host.journaled_state.database.storage(target, index); + if let Ok(db_original) = db_original + && state_load.data.original_value != db_original + { + state_load.data.original_value = db_original; + if let Some(acc) = context.host.journaled_state.inner.state.get_mut(&target) + && let Some(slot) = acc.storage.get_mut(&index) + { + slot.original_value = db_original; + } + } + } + + let is_istanbul = spec_id.is_enabled_in(ISTANBUL); + let dynamic_gas = context.host.gas_params().sstore_dynamic_gas( + is_istanbul, + &state_load.data, + state_load.is_cold, + ); + if !context.interpreter.gas.record_regular_cost(dynamic_gas) { + context.interpreter.halt_oog(); + return; + } + + context.interpreter.gas.record_refund( + context + .host + .gas_params() + .sstore_refund(is_istanbul, &state_load.data), + ); +} + /// MorphEvm extends the Evm with Morph specific types and logic. #[derive(Debug, derive_more::Deref, derive_more::DerefMut)] #[expect(clippy::type_complexity)] @@ -121,10 +288,19 @@ impl MorphEvm { // Get the current hardfork spec from context and create matching precompiles let spec = ctx.cfg.spec; let precompiles = MorphPrecompiles::new_with_spec(spec); - let mut instructions = EthInstructions::new_mainnet(); + let mut instructions = EthInstructions::new_mainnet_with_spec(spec.into()); // Morph custom BLOCKHASH implementation (matches Morph geth). instructions.insert_instruction(0x40, Instruction::new(blockhash_morph::, BLOCKHASH)); + // Morph custom SLOAD: fixes original_value corruption from token fee deduction. + instructions.insert_instruction( + 0x54, + Instruction::new(sload_morph::, WARM_STORAGE_READ_COST), + ); + // Morph custom SSTORE: same original_value fix on the SSTORE cold path. + // Static gas = 0 because sstore_morph manages all gas accounting itself + // (static + dynamic + refund). + instructions.insert_instruction(0x55, Instruction::new(sstore_morph::, 0)); // SELFDESTRUCT is disabled in Morph instructions.insert_instruction(0xff, Instruction::unknown()); // BLOBHASH is disabled in Morph diff --git a/crates/revm/src/exec.rs b/crates/revm/src/exec.rs index c96c08b7..e1aaf692 100644 --- a/crates/revm/src/exec.rs +++ b/crates/revm/src/exec.rs @@ -4,8 +4,7 @@ use crate::{ evm::{MorphContext, MorphEvm}, handler::MorphEvmHandler, }; -use alloy_evm::Database; -use reth_evm::TransactionEnv; +use alloy_evm::{Database, TransactionEnvMut as _}; use revm::{ DatabaseCommit, ExecuteCommitEvm, ExecuteEvm, context::{ContextSetters, TxEnv, result::ExecResultAndState}, diff --git a/crates/revm/src/handler.rs b/crates/revm/src/handler.rs index 4c2f1440..02a78229 100644 --- a/crates/revm/src/handler.rs +++ b/crates/revm/src/handler.rs @@ -7,7 +7,7 @@ use revm::{ Cfg, ContextTr, JournalTr, Transaction, result::{EVMError, ExecutionResult, InvalidTransaction}, }, - context_interface::Block, + context_interface::{Block, journaled_state::account::JournaledAccountTr, result::ResultGas}, handler::{EvmTr, FrameTr, Handler, MainnetHandler, post_execution, pre_execution, validation}, inspector::{Inspector, InspectorHandler}, interpreter::{Gas, InitialAndFloorGas, interpreter::EthInterpreter}, @@ -74,21 +74,27 @@ where &mut self, evm: &mut Self::Evm, result: <::Frame as FrameTr>::FrameResult, + result_gas: ResultGas, ) -> Result, Self::Error> { MainnetHandler::default() - .execution_result(evm, result) + .execution_result(evm, result, result_gas) .map(|result| result.map_haltreason(Into::into)) } #[inline] - fn apply_eip7702_auth_list(&self, evm: &mut Self::Evm) -> Result { - pre_execution::apply_eip7702_auth_list(evm.ctx()) + fn apply_eip7702_auth_list( + &self, + evm: &mut Self::Evm, + init_and_floor_gas: &mut InitialAndFloorGas, + ) -> Result { + pre_execution::apply_eip7702_auth_list(evm.ctx(), init_and_floor_gas) } #[inline] fn validate_against_state_and_deduct_caller( &self, evm: &mut Self::Evm, + _init_and_floor_gas: &mut InitialAndFloorGas, ) -> Result<(), Self::Error> { // Reset per-transaction caches from the previous iteration. evm.cached_l1_data_fee = U256::ZERO; @@ -169,7 +175,7 @@ where exec_result.gas_mut().set_refund(0); return; } - let spec = evm.ctx().cfg().spec().into(); + let spec = (*evm.ctx().cfg().spec()).into(); post_execution::refund(spec, exec_result.gas_mut(), eip7702_refund); } @@ -222,12 +228,9 @@ where // which skips gas-price checks entirely. validation::validate_env::<_, Self::Error>(evm.ctx())?; - // For MorphTx V1 with ETH fee (fee_token_id == 0), gas price must be validated - // against basefee — the same rule that applies to EIP-1559 transactions. - // Token-fee MorphTx (fee_token_id > 0) intentionally skips this check because - // fees are paid in ERC20 tokens. - // Skip for simulation contexts (eth_call / eth_estimateGas) where fee charge - // is disabled, matching go-ethereum's NoBaseFee behaviour. + // MorphTx V1 ETH-fee: enforce the EIP-1559 priority-fee rule. + // Token-fee MorphTx skips it (fees paid in ERC20); simulation + // paths also skip it. if evm.ctx_ref().tx().is_morph_tx() && !evm.ctx_ref().tx().uses_token_fee() && !evm.ctx_ref().cfg().is_fee_charge_disabled() @@ -248,30 +251,43 @@ where } #[inline] - fn validate_initial_tx_gas(&self, evm: &Self::Evm) -> Result { + fn validate_initial_tx_gas( + &self, + evm: &mut Self::Evm, + ) -> Result { let tx = evm.ctx_ref().tx(); - let spec = evm.ctx_ref().cfg().spec().into(); - let disable_eip7623 = evm.ctx_ref().cfg().is_eip7623_disabled(); + let cfg = evm.ctx_ref().cfg(); + let spec = (*cfg.spec()).into(); + let disable_eip7623 = cfg.is_eip7623_disabled(); + let is_amsterdam_eip8037 = cfg.is_amsterdam_eip8037_enabled(); + let tx_gas_limit_cap = cfg.tx_gas_limit_cap(); // For L1 message transactions, handle intrinsic gas specially if tx.is_l1_msg() { - // Calculate intrinsic gas (same as normal transactions) - let initial_and_floor = validation::validate_initial_tx_gas(tx, spec, disable_eip7623) - .unwrap_or_else(|_| { - // If intrinsic gas > gas_limit, use gas_limit as intrinsic gas - // This matches go-ethereum's behavior for L1 messages - InitialAndFloorGas { - initial_gas: tx.gas_limit(), - floor_gas: 0, - } - }); + // Calculate intrinsic gas (same as normal transactions). If intrinsic gas + // > gas_limit, fall back to gas_limit (matching go-ethereum's behavior for + // L1 messages, which prepay gas on L1 and must always execute). + let initial_and_floor = validation::validate_initial_tx_gas( + tx, + spec, + disable_eip7623, + is_amsterdam_eip8037, + tx_gas_limit_cap, + ) + .unwrap_or_else(|_| InitialAndFloorGas::new(tx.gas_limit(), 0)); return Ok(initial_and_floor); } // Normal transaction validation - let initial_and_floor = validation::validate_initial_tx_gas(tx, spec, disable_eip7623) - .map_err(MorphInvalidTransaction::EthInvalidTransaction)?; + let initial_and_floor = validation::validate_initial_tx_gas( + tx, + spec, + disable_eip7623, + is_amsterdam_eip8037, + tx_gas_limit_cap, + ) + .map_err(MorphInvalidTransaction::EthInvalidTransaction)?; Ok(initial_and_floor) } @@ -316,7 +332,7 @@ where &self, evm: &mut MorphEvm, ) -> Result<(), EVMError> { - let hardfork = evm.ctx_ref().cfg().spec(); + let hardfork = *evm.ctx_ref().cfg().spec(); // Fetch L1 block info from the L1 Gas Price Oracle contract per-tx. // Must NOT use a per-block cache because the oracle can be updated by a @@ -341,7 +357,7 @@ where let mut caller = journal.load_account_with_code_mut(tx.caller())?.data; pre_execution::validate_account_nonce_and_code( - &caller.info, + &caller.account().info, tx.nonce(), cfg.is_eip3607_disabled(), cfg.is_nonce_check_disabled(), @@ -467,7 +483,7 @@ where // matching the order used in validate_and_deduct_eth_fee. let caller = journal.load_account_with_code_mut(caller_addr)?.data; pre_execution::validate_account_nonce_and_code( - &caller.info, + &caller.account().info, nonce, cfg.is_eip3607_disabled(), cfg.is_nonce_check_disabled(), @@ -477,11 +493,8 @@ where let caller_addr = evm.ctx_ref().tx().caller(); let is_call = evm.ctx_ref().tx().kind().is_call(); - // eth_call (disable_fee_charge): skip token fee deduction entirely. - // Only nonce/code validation (above) and nonce bump are needed. - // This matches the ETH path's disable_fee_charge semantics and ensures - // eth_call is a pure simulation without token registry lookups, balance - // checks, or ERC20 transfers. + // Simulation paths must not touch token balance: skip the token + // fee deduction, keep only nonce/code validation and the nonce bump. if evm.ctx_ref().cfg().is_fee_charge_disabled() { if is_call { let mut caller = evm @@ -495,7 +508,7 @@ where } let beneficiary = evm.ctx_ref().block().beneficiary(); - let hardfork = evm.ctx_ref().cfg().spec(); + let hardfork = *evm.ctx_ref().cfg().spec(); let tx_value = evm.ctx_ref().tx().value(); let rlp_bytes = evm.ctx_ref().tx().rlp_bytes.clone().unwrap_or_default(); let gas_limit = evm.ctx_ref().tx().gas_limit(); @@ -945,15 +958,19 @@ fn calculate_caller_fee_with_l1_cost( cfg: impl Cfg, l1_data_fee: U256, ) -> Result { + // Simulation paths must not consume the caller balance. + if cfg.is_fee_charge_disabled() { + return Ok(balance); + } + let basefee = block.basefee() as u128; let blob_price = block.blob_gasprice().unwrap_or_default(); let is_balance_check_disabled = cfg.is_balance_check_disabled(); - let is_fee_charge_disabled = cfg.is_fee_charge_disabled(); // Validate balance against max possible spending using max_fee_per_gas (not effective_gas_price). // go-eth's buyGas() checks: balance >= gasFeeCap * gas + value + l1DataFee. // This ensures the sender can afford the worst-case gas cost before deducting the actual cost. - if !is_balance_check_disabled && !is_fee_charge_disabled { + if !is_balance_check_disabled { let max_gas_spending = U256::from( (tx.gas_limit() as u128) .checked_mul(tx.max_fee_per_gas()) @@ -1214,4 +1231,27 @@ mod tests { .unwrap(); assert_eq!(slot_state.present_value, original_balance); } + + /// `disable_fee_charge` must leave the caller balance untouched. + #[test] + fn calculate_caller_fee_is_short_circuited_by_disable_fee_charge() { + use revm::context::{CfgEnv, TxEnv}; + + let balance = U256::from(1_000_000_000_000u128); + let mut cfg = CfgEnv::::default(); + cfg.disable_fee_charge = true; + + let tx = TxEnv { + gas_limit: 21_000, + gas_price: 1_000_000_000, + value: U256::from(42u64), + ..Default::default() + }; + let block = BlockEnv::default(); + let l1_data_fee = U256::from(1_234u64); + + let new_balance = + calculate_caller_fee_with_l1_cost(balance, tx, block, cfg, l1_data_fee).unwrap(); + assert_eq!(new_balance, balance); + } } diff --git a/crates/revm/src/precompiles.rs b/crates/revm/src/precompiles.rs index 436b12de..1e3bce90 100644 --- a/crates/revm/src/precompiles.rs +++ b/crates/revm/src/precompiles.rs @@ -40,7 +40,9 @@ use revm::{ context_interface::ContextTr, handler::{EthPrecompiles, PrecompileProvider}, interpreter::{CallInputs, InterpreterResult}, - precompile::{Precompile, PrecompileError, PrecompileId, PrecompileResult, Precompiles}, + precompile::{ + Precompile, PrecompileHalt, PrecompileId, PrecompileOutput, PrecompileResult, Precompiles, + }, primitives::{OnceLock, hardfork::SpecId}, }; use std::boxed::Box; @@ -148,21 +150,25 @@ impl Default for MorphPrecompiles { /// Disabled stub for ripemd160 (0x03) in Bernoulli/Curie hardfork. /// -/// Returns `PrecompileError` to consume all forwarded gas, matching go-ethereum's behavior -/// where a disabled precompile error causes the CALL handler to burn all remaining gas. -fn ripemd160_disabled(_input: &[u8], _gas_limit: u64) -> PrecompileResult { - Err(PrecompileError::Other( - "ripemd160 precompile disabled in Bernoulli/Curie hardfork".into(), +/// Returns a `PrecompileHalt` so the CALL handler treats the call as failure and +/// burns all forwarded gas, matching go-ethereum's behavior where a disabled +/// precompile error causes the CALL handler to consume all remaining gas. +fn ripemd160_disabled(_input: &[u8], _gas_limit: u64, reservoir: u64) -> PrecompileResult { + Ok(PrecompileOutput::halt( + PrecompileHalt::other("ripemd160 precompile disabled in Bernoulli/Curie hardfork"), + reservoir, )) } /// Disabled stub for blake2f (0x09) in Bernoulli/Curie hardfork. /// -/// Returns `PrecompileError` to consume all forwarded gas, matching go-ethereum's behavior -/// where a disabled precompile error causes the CALL handler to burn all remaining gas. -fn blake2f_disabled(_input: &[u8], _gas_limit: u64) -> PrecompileResult { - Err(PrecompileError::Other( - "blake2f precompile disabled in Bernoulli/Curie hardfork".into(), +/// Returns a `PrecompileHalt` so the CALL handler treats the call as failure and +/// burns all forwarded gas, matching go-ethereum's behavior where a disabled +/// precompile error causes the CALL handler to consume all remaining gas. +fn blake2f_disabled(_input: &[u8], _gas_limit: u64, reservoir: u64) -> PrecompileResult { + Ok(PrecompileOutput::halt( + PrecompileHalt::other("blake2f precompile disabled in Bernoulli/Curie hardfork"), + reservoir, )) } @@ -191,23 +197,25 @@ fn modexp_len_exceeds_32(data: &[u8], offset: usize) -> bool { /// that go-ethereum rejects, causing a consensus split. /// /// Ref: -fn modexp_with_32byte_limit(input: &[u8], gas_limit: u64) -> PrecompileResult { +fn modexp_with_32byte_limit(input: &[u8], gas_limit: u64, reservoir: u64) -> PrecompileResult { // The first 96 bytes of modexp input are three 32-byte big-endian length fields: // [0..32] = base_len, [32..64] = exp_len, [64..96] = mod_len if modexp_len_exceeds_32(input, 0) || modexp_len_exceeds_32(input, 32) || modexp_len_exceeds_32(input, 64) { - return Err(PrecompileError::Other( - "modexp temporarily only accepts inputs of 32 bytes (256 bits) or less".into(), + return Ok(PrecompileOutput::halt( + PrecompileHalt::other( + "modexp temporarily only accepts inputs of 32 bytes (256 bits) or less", + ), + reservoir, )); } - // Delegate to Berlin modexp (EIP-2565 gas pricing, standard computation) Precompiles::berlin() .get(&addresses::MODEXP) .expect("Berlin precompiles must include modexp") - .execute(input, gas_limit) + .execute(input, gas_limit, reservoir) } /// Wraps BN256 pairing with go-ethereum's 4-pair input length limit. @@ -219,18 +227,22 @@ fn modexp_with_32byte_limit(input: &[u8], gas_limit: u64) -> PrecompileResult { /// limits and metering become inconsistent). /// /// Ref: -fn bn256_pairing_with_4pair_limit(input: &[u8], gas_limit: u64) -> PrecompileResult { +fn bn256_pairing_with_4pair_limit( + input: &[u8], + gas_limit: u64, + reservoir: u64, +) -> PrecompileResult { if input.len() > 4 * 192 { - return Err(PrecompileError::Other( - "bad elliptic curve pairing size".into(), + return Ok(PrecompileOutput::halt( + PrecompileHalt::other("bad elliptic curve pairing size"), + reservoir, )); } - // Delegate to Berlin/Istanbul BN256 pairing Precompiles::berlin() .get(&addresses::BN256_PAIRING) .expect("Berlin precompiles must include BN256 pairing") - .execute(input, gas_limit) + .execute(input, gas_limit, reservoir) } /// Returns precompiles for Bernoulli hardfork. @@ -395,6 +407,28 @@ where #[cfg(test)] mod tests { use super::*; + use revm::precompile::PrecompileStatus; + + /// Returns true if the precompile execution result should be considered a failed + /// CALL (halt status or fatal error). Mirrors the old `result.is_err()` semantics + /// from revm 36 where rejection variants like `PrecompileError::Other` were errors. + fn is_call_failure(result: &PrecompileResult) -> bool { + match result { + Ok(output) => matches!(output.status, PrecompileStatus::Halt(_)), + Err(_) => true, + } + } + + /// Extracts a `PrecompileHalt` from a result that is expected to be a halt-on-Ok. + fn halt_reason(result: PrecompileResult) -> PrecompileHalt { + match result { + Ok(output) => match output.status { + PrecompileStatus::Halt(reason) => reason, + other => panic!("expected halt status, got: {other:?}"), + }, + Err(err) => panic!("expected halt-on-Ok, got fatal error: {err:?}"), + } + } #[test] fn test_bernoulli_precompiles() { @@ -428,16 +462,19 @@ mod tests { input[31] = 33; // base_len = 33 input[63] = 32; // exp_len = 32 input[95] = 32; // mod_len = 32 - let result = modexp.execute(&input, 100_000); + let result = modexp.execute(&input, 100_000, 0); assert!( - result.is_err(), + is_call_failure(&result), "modexp with base_len=33 should be rejected" ); // base_len=32, exp_len=32, mod_len=32 — should succeed input[31] = 32; - let result = modexp.execute(&input, 100_000); - assert!(result.is_ok(), "modexp with all lens=32 should succeed"); + let result = modexp.execute(&input, 100_000, 0); + assert!( + !is_call_failure(&result), + "modexp with all lens=32 should succeed" + ); } #[test] @@ -476,16 +513,19 @@ mod tests { // 5 pairs (960 bytes) — exceeds 4-pair limit, should be rejected let input = vec![0u8; 5 * 192]; - let result = pairing.execute(&input, 1_000_000); - assert!(result.is_err(), "pairing with 5 pairs should be rejected"); + let result = pairing.execute(&input, 1_000_000, 0); + assert!( + is_call_failure(&result), + "pairing with 5 pairs should be rejected" + ); // 4 pairs (768 bytes) — within limit, should not be rejected for size // (may still fail due to invalid curve points, but not for size) let input = vec![0u8; 4 * 192]; - let result = pairing.execute(&input, 1_000_000); + let result = pairing.execute(&input, 1_000_000, 0); // Zero-input pairing is valid and returns true assert!( - result.is_ok(), + !is_call_failure(&result), "pairing with 4 pairs should not be rejected for size" ); } @@ -538,8 +578,11 @@ mod tests { input[31] = 64; // base_len = 64 input[63] = 32; // exp_len = 32 input[95] = 64; // mod_len = 64 - let result = modexp.execute(&input, 1_000_000); - assert!(result.is_ok(), "Emerald modexp should accept base_len=64"); + let result = modexp.execute(&input, 1_000_000, 0); + assert!( + !is_call_failure(&result), + "Emerald modexp should accept base_len=64" + ); } #[test] @@ -582,18 +625,21 @@ mod tests { let precompiles = bernoulli(); let ripemd = precompiles.get(&addresses::RIPEMD160).unwrap(); - // Calling the disabled stub should return an error (consuming all forwarded gas) - let result = ripemd.execute(b"hello", 100_000); - assert!(result.is_err(), "disabled ripemd160 should return error"); - let err = result.unwrap_err(); - match err { - PrecompileError::Other(msg) => { + // Calling the disabled stub should halt (consuming all forwarded gas at the + // caller, since the CALL frame treats `Halt` as failure). + let result = ripemd.execute(b"hello", 100_000, 0); + assert!( + is_call_failure(&result), + "disabled ripemd160 should halt the call" + ); + match halt_reason(result) { + PrecompileHalt::Other(msg) => { assert!( msg.contains("ripemd160"), "error message should mention ripemd160" ); } - _ => panic!("expected PrecompileError::Other, got: {err:?}"), + other => panic!("expected PrecompileHalt::Other, got: {other:?}"), } } @@ -602,18 +648,21 @@ mod tests { let precompiles = bernoulli(); let blake2f = precompiles.get(&addresses::BLAKE2F).unwrap(); - // Calling the disabled stub should return an error (consuming all forwarded gas) - let result = blake2f.execute(b"hello", 100_000); - assert!(result.is_err(), "disabled blake2f should return error"); - let err = result.unwrap_err(); - match err { - PrecompileError::Other(msg) => { + // Calling the disabled stub should halt (consuming all forwarded gas at the + // caller, since the CALL frame treats `Halt` as failure). + let result = blake2f.execute(b"hello", 100_000, 0); + assert!( + is_call_failure(&result), + "disabled blake2f should halt the call" + ); + match halt_reason(result) { + PrecompileHalt::Other(msg) => { assert!( msg.contains("blake2f"), "error message should mention blake2f" ); } - _ => panic!("expected PrecompileError::Other, got: {err:?}"), + other => panic!("expected PrecompileHalt::Other, got: {other:?}"), } } @@ -622,10 +671,10 @@ mod tests { let precompiles = morph203(); let ripemd = precompiles.get(&addresses::RIPEMD160).unwrap(); - // In Morph203, ripemd160 is re-enabled and should work (not return disabled error) - let result = ripemd.execute(b"hello", 100_000); + // In Morph203, ripemd160 is re-enabled and should work (not return disabled halt) + let result = ripemd.execute(b"hello", 100_000, 0); assert!( - result.is_ok(), + !is_call_failure(&result), "morph203 ripemd160 should be functional: {result:?}" ); } @@ -636,10 +685,11 @@ mod tests { let blake2f = precompiles.get(&addresses::BLAKE2F).unwrap(); // blake2f requires specific input format (213 bytes), but the point is it should - // NOT return the "disabled" error. An invalid-input error is acceptable. - let result = blake2f.execute(b"hello", 100_000); - // Either Ok (valid input) or Err (invalid input format, NOT disabled error) - if let Err(PrecompileError::Other(msg)) = &result { + // NOT return the "disabled" halt. An invalid-input halt is acceptable. + let result = blake2f.execute(b"hello", 100_000, 0); + if let Ok(output) = &result + && let PrecompileStatus::Halt(PrecompileHalt::Other(msg)) = &output.status + { assert!( !msg.contains("disabled"), "morph203 blake2f should NOT be disabled: {msg}" @@ -654,10 +704,10 @@ mod tests { // 5 pairs (960 bytes) — Bernoulli uses Berlin pairing with no 4-pair limit let input = vec![0u8; 5 * 192]; - let result = pairing.execute(&input, 1_000_000); + let result = pairing.execute(&input, 1_000_000, 0); // Should succeed (zero-padded valid points), NOT rejected for size assert!( - result.is_ok(), + !is_call_failure(&result), "Bernoulli pairing should accept 5 pairs (no limit)" ); } @@ -669,15 +719,17 @@ mod tests { // Exactly 4 pairs (768 bytes) — should succeed let input = vec![0u8; 4 * 192]; + let result = pairing.execute(&input, 1_000_000, 0); assert!( - pairing.execute(&input, 1_000_000).is_ok(), + !is_call_failure(&result), "pairing with exactly 4 pairs should succeed" ); // 4 pairs + 1 byte (769 bytes) — should be rejected let input = vec![0u8; 4 * 192 + 1]; + let result = pairing.execute(&input, 1_000_000, 0); assert!( - pairing.execute(&input, 1_000_000).is_err(), + is_call_failure(&result), "pairing with 769 bytes should be rejected" ); } @@ -741,8 +793,11 @@ mod tests { 0x5a, 0x9c, 0x45, 0x49, ]); - let result = ecrecover.execute(&input, 10_000); - assert!(result.is_ok(), "valid ecrecover should succeed: {result:?}"); + let result = ecrecover.execute(&input, 10_000, 0); + assert!( + !is_call_failure(&result), + "valid ecrecover should succeed: {result:?}" + ); let output = result.unwrap().bytes; assert_eq!(output.len(), 32, "ecrecover output must be 32 bytes"); @@ -766,8 +821,11 @@ mod tests { let precompiles = bernoulli(); let sha256 = precompiles.get(&addresses::SHA256).unwrap(); - let result = sha256.execute(&[], 100_000); - assert!(result.is_ok(), "sha256 of empty input should succeed"); + let result = sha256.execute(&[], 100_000, 0); + assert!( + !is_call_failure(&result), + "sha256 of empty input should succeed" + ); // SHA256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 let expected: [u8; 32] = [ @@ -789,8 +847,8 @@ mod tests { let identity = precompiles.get(&addresses::IDENTITY).unwrap(); let input: &[u8] = &[0x12, 0x34, 0xab, 0xcd]; - let result = identity.execute(input, 100_000); - assert!(result.is_ok(), "identity should succeed"); + let result = identity.execute(input, 100_000, 0); + assert!(!is_call_failure(&result), "identity should succeed"); assert_eq!( &result.unwrap().bytes[..], input, diff --git a/crates/revm/src/token_fee.rs b/crates/revm/src/token_fee.rs index fee48b64..b6198e19 100644 --- a/crates/revm/src/token_fee.rs +++ b/crates/revm/src/token_fee.rs @@ -8,6 +8,7 @@ use alloy_evm::Database; use alloy_primitives::{Address, Bytes, U256, address, keccak256}; use morph_chainspec::hardfork::MorphHardfork; +use revm::Database as RevmDatabase; use revm::SystemCallEvm; use revm::{context_interface::result::EVMError, inspector::NoOpInspector}; @@ -94,6 +95,49 @@ impl TokenFeeInfo { })) } + /// Storage-only variant of [`Self::load_for_caller`]. + /// + /// Reads the registry entry and — when the token's `balance_slot` is + /// known — the caller's ERC20 balance directly from contract storage. + /// Unlike [`Self::load_for_caller`] this never spins up a temporary + /// `MorphEvm`, so it is callable from RPC code paths that only have + /// `revm::Database` (no `Debug` bound, no `MorphContext` setup). + /// + /// Behaviour: + /// - Returns `Ok(None)` if the token is not registered. + /// - Returns `Ok(Some(Self))` with `balance = 0` and + /// `balance_slot = None` if the token is registered but its balance + /// slot is unknown — callers are expected to skip token-balance + /// enforcement in that case (the handler re-checks the balance via + /// an EVM call during execution). + pub fn load_storage_only( + db: &mut DB, + token_id: u16, + caller: Address, + ) -> Result, DB::Error> { + let entry = match read_registry_entry(db, token_id)? { + Some(e) => e, + None => return Ok(None), + }; + + let balance = if let Some(slot) = entry.balance_slot { + read_balance_from_storage(db, entry.token_address, caller, slot)? + } else { + U256::ZERO + }; + + Ok(Some(Self { + token_address: entry.token_address, + is_active: entry.is_active, + decimals: entry.decimals, + price_ratio: entry.price_ratio, + scale: entry.scale, + caller, + balance, + balance_slot: entry.balance_slot, + })) + } + /// Calculate the token amount required for a given ETH amount. /// /// Uses the price ratio and scale to convert ETH value to token amount. @@ -118,7 +162,7 @@ impl TokenFeeInfo { } } -fn read_registry_entry( +fn read_registry_entry( db: &mut DB, token_id: u16, ) -> Result, DB::Error> { @@ -197,7 +241,7 @@ pub fn compute_mapping_slot_for_address(base_slot: U256, account: Address) -> U2 /// Load a value from a mapping in contract storage. #[inline] -fn read_mapping_value( +fn read_mapping_value( db: &mut DB, contract: Address, base_slot: U256, @@ -234,7 +278,7 @@ fn read_token_balance_with_fallback( /// Read ERC20 balance directly from storage slot. #[inline] -fn read_balance_from_storage( +fn read_balance_from_storage( db: &mut DB, token: Address, account: Address, diff --git a/crates/revm/src/tx.rs b/crates/revm/src/tx.rs index 60753e38..d92c5fef 100644 --- a/crates/revm/src/tx.rs +++ b/crates/revm/src/tx.rs @@ -10,7 +10,7 @@ use alloy_eips::eip2930::AccessList; use alloy_eips::eip7702::RecoveredAuthority; use alloy_primitives::{Address, B256, Bytes, Signature, TxKind, U256}; use morph_primitives::{L1_TX_TYPE_ID, MORPH_TX_TYPE_ID, MorphTxEnvelope, TxMorph}; -use reth_evm::{FromRecoveredTx, FromTxWithEncoded, ToTxEnv, TransactionEnv}; +use reth_evm::{FromRecoveredTx, FromTxWithEncoded, ToTxEnv, TransactionEnvMut}; use revm::context::{Transaction, TxEnv}; use revm::context_interface::transaction::{ AccessListItem, RecoveredAuthorization, SignedAuthorization, @@ -368,16 +368,11 @@ impl FromTxWithEncoded for MorphTxEnv { } } -// Implement TransactionEnv for MorphTxEnv -impl TransactionEnv for MorphTxEnv { +impl TransactionEnvMut for MorphTxEnv { fn set_gas_limit(&mut self, gas_limit: u64) { self.inner.gas_limit = gas_limit; } - fn nonce(&self) -> u64 { - self.inner.nonce - } - fn set_nonce(&mut self, nonce: u64) { self.inner.nonce = nonce; } diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 261abe3a..07885dba 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -36,6 +36,7 @@ reth-transaction-pool.workspace = true # Alloy alloy-consensus.workspace = true +alloy-evm.workspace = true alloy-primitives.workspace = true alloy-rpc-types-eth.workspace = true alloy-serde.workspace = true diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs index 4f23d077..5af9ada7 100644 --- a/crates/rpc/src/error.rs +++ b/crates/rpc/src/error.rs @@ -63,6 +63,29 @@ pub enum MorphEthApiError { /// Provider error #[error("provider error: {0}")] Provider(String), + + /// Insufficient funds for the transaction value. + /// + /// Raised by `eth_estimateGas` when the caller's balance is smaller than + /// `tx.value` before any gas / L1 fee accounting. + #[error("insufficient funds for transfer")] + InsufficientFundsForTransfer, + + /// Insufficient funds to cover the L1 data fee. + /// + /// Raised by `eth_estimateGas` when the caller's remaining balance (after + /// subtracting `tx.value`) cannot cover the L1 data fee. Mirrors + /// go-ethereum's `"insufficient funds for l1 fee"` error. + #[error("insufficient funds for l1 fee")] + InsufficientFundsForL1Fee, + + /// Invalid or inactive fee token. + /// + /// Raised by `eth_estimateGas` when a MorphTx specifies a `fee_token_id` + /// that is not registered in the L2 token registry, is inactive, or has + /// a misconfigured `price_ratio` / `scale`. + #[error("invalid fee token")] + InvalidFeeToken, } /// Converts [`MorphEthApiError`] to a JSON-RPC error object. @@ -110,6 +133,19 @@ impl From for jsonrpsee::types::ErrorObject<'static> { format!("Provider error: {msg}"), None::<()>, ), + MorphEthApiError::InsufficientFundsForTransfer => jsonrpsee::types::ErrorObject::owned( + -32000, + "insufficient funds for transfer", + None::<()>, + ), + MorphEthApiError::InsufficientFundsForL1Fee => jsonrpsee::types::ErrorObject::owned( + -32000, + "insufficient funds for l1 fee", + None::<()>, + ), + MorphEthApiError::InvalidFeeToken => { + jsonrpsee::types::ErrorObject::owned(-32000, "invalid fee token", None::<()>) + } } } } diff --git a/crates/rpc/src/eth/call.rs b/crates/rpc/src/eth/call.rs index 82b72dca..f4a5e7f2 100644 --- a/crates/rpc/src/eth/call.rs +++ b/crates/rpc/src/eth/call.rs @@ -1,11 +1,25 @@ -//! Morph `eth_call` and `eth_estimateGas` overrides. +//! Morph `eth_call` / `eth_estimateGas` overrides. +//! +//! [`Call::caller_gas_allowance`] is overridden so `eth_estimateGas` caps +//! gas by `balance − value − l1_fee` (ETH path) or the fee token balance +//! (MorphTx `fee_token_id > 0`). `eth_call` and `eth_createAccessList` +//! are detected via `cfg_env.disable_block_gas_limit = true` and fall +//! through to the upstream allowance without the L1-fee extension. use crate::MorphEthApiError; use crate::eth::{MorphEthApi, MorphNodeCore}; -use morph_chainspec::MorphChainSpec; +use alloy_evm::call::{CallError, caller_gas_allowance as upstream_caller_gas_allowance}; +use alloy_primitives::U256; +use morph_chainspec::{MorphChainSpec, MorphHardforks}; +use morph_revm::{L1BlockInfo, MorphTxExt, TokenFeeInfo}; +use reth_evm::{EvmEnvFor, TxEnvFor}; use reth_provider::ChainSpecProvider; -use reth_rpc_eth_api::helpers::{Call, EthCall, estimate::EstimateCall}; +use reth_rpc_eth_api::{ + EthApiTypes, RpcNodeCore, + helpers::{Call, EthCall, estimate::EstimateCall}, +}; use reth_rpc_eth_types::EthApiError; +use revm::{Database, context::Transaction as RevmTransaction}; impl EthCall for MorphEthApi where @@ -46,4 +60,557 @@ where fn evm_memory_limit(&self) -> u64 { self.eth_api().evm_memory_limit() } + + fn caller_gas_allowance( + &self, + mut db: impl Database>, + evm_env: &EvmEnvFor<::Evm>, + tx_env: &TxEnvFor<::Evm>, + ) -> Result::Error> { + // eth_call / eth_createAccessList: no L1-fee cap. Token-fee callers + // can have zero ETH, so defer to the handler instead of the + // upstream ETH allowance. + if evm_env.cfg_env.disable_block_gas_limit { + if tx_env.fee_token_id.is_some_and(|id| id > 0) { + return Ok(u64::MAX); + } + return upstream_caller_gas_allowance(&mut db, tx_env).map_err(|e| match e { + CallError::Database(db_err) => MorphEthApiError::Eth(db_err.into()), + CallError::InsufficientFunds(_) => MorphEthApiError::InsufficientFundsForTransfer, + }); + } + + // eth_estimateGas path. + let l1_fee = self.estimate_l1_fee(&mut db, evm_env, tx_env)?; + + if let Some(fee_token_id) = tx_env.fee_token_id.filter(|id| *id > 0) { + return self.caller_gas_allowance_with_token( + &mut db, + tx_env.caller(), + tx_env.value(), + l1_fee, + fee_token_id, + tx_env.fee_limit, + tx_env.gas_price(), + ); + } + + // allowance = (balance − value − l1_fee) / gas_price, done at wei + // precision so the remainder is not lost across the two + // subtractions. + let balance = db + .basic(tx_env.caller()) + .map_err(|e| MorphEthApiError::Eth(e.into()))? + .map(|acc| acc.balance) + .unwrap_or_default(); + let available = balance + .checked_sub(tx_env.value()) + .ok_or(MorphEthApiError::InsufficientFundsForTransfer)?; + // Reject at `l1_fee >= available` so the RPC surfaces the real + // reason rather than a downstream "gas required exceeds allowance 0". + if l1_fee >= available { + return Err(MorphEthApiError::InsufficientFundsForL1Fee); + } + Ok(gas_allowance_from_balance( + available - l1_fee, + tx_env.gas_price(), + )) + } +} + +impl MorphEthApi +where + N: MorphNodeCore, + N::Provider: ChainSpecProvider, + Rpc: + reth_rpc_convert::RpcConvert, +{ + /// Estimate the L1 data fee. Zero for L1 messages. + fn estimate_l1_fee( + &self, + db: &mut DB, + evm_env: &EvmEnvFor<::Evm>, + tx_env: &TxEnvFor<::Evm>, + ) -> Result + where + DB: Database, + DB::Error: Into, + { + if tx_env.is_l1_msg() { + return Ok(U256::ZERO); + } + + let block_number = u64::try_from(evm_env.block_env.inner.number) + .map_err(|_| EthApiError::InvalidParams("invalid block number".to_string()))?; + let timestamp = u64::try_from(evm_env.block_env.inner.timestamp) + .map_err(|_| EthApiError::InvalidParams("invalid block timestamp".to_string()))?; + let chain_spec = self.provider().chain_spec(); + let hardfork = chain_spec.morph_hardfork_at(block_number, timestamp); + + let rlp_bytes = tx_env.rlp_bytes.as_ref().ok_or_else(|| { + EthApiError::InvalidParams("missing rlp bytes for l1 fee".to_string()) + })?; + + let l1_info = L1BlockInfo::try_fetch(db, hardfork).map_err(|err| { + EthApiError::InvalidParams(format!("failed to estimate L1 data fee: {err}")) + })?; + + Ok(l1_info.calculate_tx_l1_cost(rlp_bytes, hardfork)) + } + + /// Allowance for MorphTx ERC20 fee-token callers: cap by token + /// balance, not by ETH balance. + #[allow(clippy::too_many_arguments)] + fn caller_gas_allowance_with_token( + &self, + db: &mut DB, + caller: alloy_primitives::Address, + value: U256, + l1_fee: U256, + fee_token_id: u16, + fee_limit: Option, + gas_price: u128, + ) -> Result + where + DB: revm::Database, + DB::Error: Into, + { + let token_fee_info = TokenFeeInfo::load_storage_only(db, fee_token_id, caller) + .map_err(|_| MorphEthApiError::InvalidFeeToken)? + .ok_or(MorphEthApiError::InvalidFeeToken)?; + + // ETH only needs to cover `value`; gas + L1 fee are paid in tokens. + let eth_balance = db + .basic(caller) + .map_err(|e| MorphEthApiError::Eth(e.into()))? + .map(|acc| acc.balance) + .unwrap_or_default(); + + token_gas_allowance( + eth_balance, + value, + &token_fee_info, + l1_fee, + fee_limit, + gas_price, + self.eth_api().gas_cap(), + ) + } +} + +/// Compute the gas allowance for a MorphTx fee-token caller. +/// +/// Pure function over the inputs `caller_gas_allowance_with_token` would +/// load from the database, factored out so the affordability and bounding +/// logic can be unit-tested without an EVM/DB stack. +/// +/// The `gas_cap` argument is the per-call RPC ceiling (`EthApiNodeBackend::gas_cap()`), +/// only consumed by the EVM-call-mode + no-`fee_limit` fallback to avoid +/// returning `u64::MAX`. See the in-body comment for the security rationale. +fn token_gas_allowance( + eth_balance: U256, + value: U256, + token_fee_info: &TokenFeeInfo, + l1_fee: U256, + fee_limit: Option, + gas_price: u128, + gas_cap: u64, +) -> Result { + if !token_fee_info.is_active + || token_fee_info.price_ratio.is_zero() + || token_fee_info.scale.is_zero() + { + return Err(MorphEthApiError::InvalidFeeToken); + } + + if eth_balance < value { + return Err(MorphEthApiError::InsufficientFundsForTransfer); + } + + // Determine the token-denominated affordability cap. + // + // - Slot mode (`balance_slot.is_some()`): RPC reads balance directly + // from token storage; cap by `min(balance, fee_limit)`. The + // trusted balance is the natural ceiling. + // - EVM-call mode (`balance_slot.is_none()`): RPC cannot resolve the + // balance without spinning up an EVM (the handler does that at real + // execution via `load_for_caller`). On the estimateGas path + // `disable_fee_charge=true` short-circuits the handler's check, so + // there is no natural balance ceiling — we MUST enforce `gas_cap` + // here, matching `eth_call`'s effective ceiling. Trusting a + // user-supplied `fee_limit` alone would let `fee_limit = U256::MAX` + // bypass the operator-configured `--rpc.gascap`. + let (limit, clamp_to_gas_cap) = match (token_fee_info.balance_slot, fee_limit) { + (Some(_), Some(limit)) if !limit.is_zero() => (token_fee_info.balance.min(limit), false), + (Some(_), _) => (token_fee_info.balance, false), + (None, Some(limit)) if !limit.is_zero() => (limit, true), + (None, _) => return Ok(gas_cap), + }; + + let allowance = gas_allowance_from_token_limit(limit, l1_fee, token_fee_info, gas_price)?; + Ok(if clamp_to_gas_cap { + allowance.min(gas_cap) + } else { + allowance + }) +} + +/// Convert a token-denominated affordability cap into a gas allowance. +/// +/// Subtracts the L1 fee (converted to tokens) from `limit_token`, then +/// converts the remaining token budget back to ETH and divides by +/// `gas_price`. Returns `InsufficientFundsForL1Fee` if the L1 fee swallows +/// the entire limit, matching the semantics surfaced for the ETH path. +fn gas_allowance_from_token_limit( + limit_token: U256, + l1_fee: U256, + token_info: &TokenFeeInfo, + gas_price: u128, +) -> Result { + let l1_fee_in_token = token_info.eth_to_token_amount(l1_fee); + if l1_fee_in_token >= limit_token { + return Err(MorphEthApiError::InsufficientFundsForL1Fee); + } + let available_token = limit_token - l1_fee_in_token; + let available_eth = token_amount_to_eth(available_token, token_info) + .ok_or(MorphEthApiError::InvalidFeeToken)?; + Ok(gas_allowance_from_balance(available_eth, gas_price)) +} + +/// `U256 / u128 → u64`, saturating. +fn saturating_div_u128(dividend: U256, divisor: u128) -> u64 { + if divisor == 0 { + return 0; + } + let quotient = dividend / U256::from(divisor); + if quotient > U256::from(u64::MAX) { + u64::MAX + } else { + quotient.to::() + } +} + +/// Balance → gas units. `gas_price == 0` → `u64::MAX`. +fn gas_allowance_from_balance(balance: U256, gas_price: u128) -> u64 { + if gas_price == 0 { + return u64::MAX; + } + saturating_div_u128(balance, gas_price) +} + +/// `eth = floor(token_amount * price_ratio / scale)`. `None` if +/// `price_ratio` or `scale` is zero. +/// +/// Floor (not ceil) is deliberate: this is the inverse of the +/// protocol's `eth_to_token_amount`, which ceils to protect the +/// protocol (undercharging loses revenue). An affordability budget +/// must round in the opposite direction — the largest `eth` such +/// that the corresponding token charge still fits the user's +/// balance. With non-1:1 ratios, ceiling here would over-promise a +/// gas budget the user cannot actually settle at execution time. +fn token_amount_to_eth(token_amount: U256, info: &TokenFeeInfo) -> Option { + if info.price_ratio.is_zero() || info.scale.is_zero() { + return None; + } + Some(token_amount.saturating_mul(info.price_ratio) / info.scale) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// `(balance − value − l1_fee) / gas_price`, wei precision. + /// `balance=15, value=0, l1_fee=6, gas_price=10` → 0 (not 1 from + /// floor/floor composition). + #[test] + fn gas_allowance_subtracts_l1_fee_at_wei_precision() { + let value = U256::ZERO; + let l1_fee = U256::from(6u64); + let gas_price = 10u128; + + let available = U256::from(15u64) - value - l1_fee; + assert_eq!(gas_allowance_from_balance(available, gas_price), 0); + + let available = U256::from(16u64) - value - l1_fee; + assert_eq!(gas_allowance_from_balance(available, gas_price), 1); + } + + #[test] + fn saturating_div_u128_handles_zero_divisor_and_overflow() { + assert_eq!(saturating_div_u128(U256::from(100u64), 0), 0); + assert_eq!(saturating_div_u128(U256::MAX, 1), u64::MAX); + assert_eq!(saturating_div_u128(U256::from(9u64), 10), 0); + assert_eq!(saturating_div_u128(U256::from(10u64), 10), 1); + } + + #[test] + fn gas_allowance_with_zero_gas_price_returns_u64_max() { + assert_eq!( + gas_allowance_from_balance(U256::from(1_000u64), 0), + u64::MAX + ); + } + + fn eth_path_allowance( + balance: U256, + value: U256, + l1_fee: U256, + gas_price: u128, + ) -> Result { + let available = balance + .checked_sub(value) + .ok_or(MorphEthApiError::InsufficientFundsForTransfer)?; + if l1_fee >= available { + return Err(MorphEthApiError::InsufficientFundsForL1Fee); + } + Ok(gas_allowance_from_balance(available - l1_fee, gas_price)) + } + + #[test] + fn eth_path_l1_fee_equal_to_available_errors_early() { + let err = eth_path_allowance(U256::from(10u64), U256::ZERO, U256::from(10u64), 1) + .expect_err("l1_fee == available must reject"); + assert!(matches!(err, MorphEthApiError::InsufficientFundsForL1Fee)); + } + + #[test] + fn eth_path_one_wei_above_l1_fee_yields_positive_allowance() { + let allowance = eth_path_allowance(U256::from(11u64), U256::ZERO, U256::from(10u64), 1) + .expect("l1_fee < available must succeed"); + assert_eq!(allowance, 1); + } + + #[test] + fn eth_path_value_exceeds_balance_errors_before_l1_fee_check() { + let err = eth_path_allowance(U256::from(5u64), U256::from(10u64), U256::ZERO, 1) + .expect_err("value > balance must reject"); + assert!(matches!( + err, + MorphEthApiError::InsufficientFundsForTransfer + )); + } + + /// Build an active 1:1 token (`scale == price_ratio == 1`) so token math + /// is identity and the test focuses on the limit-selection branches. + fn token_1to1(balance_slot: Option, balance: U256) -> TokenFeeInfo { + TokenFeeInfo { + token_address: alloy_primitives::Address::ZERO, + is_active: true, + decimals: 18, + price_ratio: U256::from(1u64), + scale: U256::from(1u64), + caller: alloy_primitives::Address::ZERO, + balance, + balance_slot, + } + } + + /// EVM-call mode with a user-supplied `fee_limit` that covers the L1 fee + /// must return the remaining-budget gas, never `u64::MAX`. + #[test] + fn token_evm_call_mode_with_fee_limit_uses_user_budget() { + let token = token_1to1(None, U256::ZERO); + let allowance = token_gas_allowance( + /* eth_balance */ U256::from(1u64), + /* value */ U256::ZERO, + &token, + /* l1_fee */ U256::from(10u64), + /* fee_limit */ Some(U256::from(110u64)), + /* gas_price */ 1, + /* gas_cap */ 50_000_000, + ) + .expect("fee_limit covers L1 fee with 100 wei to spare"); + // (110 - 10) / 1 = 100 gas + assert_eq!(allowance, 100); + } + + /// EVM-call mode + `fee_limit` ≤ L1 fee: surface `InsufficientFundsForL1Fee` + /// rather than silently capping at zero. + #[test] + fn token_evm_call_mode_l1_fee_swallows_limit_returns_clear_error() { + let token = token_1to1(None, U256::ZERO); + let err = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + /* l1_fee */ U256::from(100u64), + /* fee_limit */ Some(U256::from(50u64)), + 1, + 50_000_000, + ) + .expect_err("fee_limit cannot cover L1 fee"); + assert!(matches!(err, MorphEthApiError::InsufficientFundsForL1Fee)); + } + + /// EVM-call mode without a `fee_limit` must cap at the per-call `gas_cap`, + /// never `u64::MAX` (which lets estimateGas binary-search 25× the + /// block_gas_limit of free EVM work). + #[test] + fn token_evm_call_mode_without_fee_limit_falls_back_to_gas_cap() { + let token = token_1to1(None, U256::ZERO); + let cap = 5_000_000u64; + let allowance = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + /* l1_fee */ U256::from(123u64), + /* fee_limit */ None, + 1, + cap, + ) + .expect("no fee_limit must fall back to gas_cap, not u64::MAX"); + assert_eq!(allowance, cap, "must equal gas_cap, not u64::MAX"); + + // Same with explicit Some(0), which the production code treats as + // "no usable cap" via the `if !limit.is_zero()` guard. + let allowance = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + U256::from(123u64), + Some(U256::ZERO), + 1, + cap, + ) + .expect("Some(0) fee_limit must fall back to gas_cap"); + assert_eq!(allowance, cap); + } + + /// Slot-mode regression: `min(balance, fee_limit)` budget; gas_cap unused. + #[test] + fn token_slot_mode_caps_by_min_of_balance_and_fee_limit() { + let token = token_1to1( + Some(U256::from(42u64)), + /* balance */ U256::from(1_000u64), + ); + + // fee_limit < balance → fee_limit binds. + let allowance = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + /* l1_fee */ U256::from(50u64), + /* fee_limit */ Some(U256::from(500u64)), + /* gas_price */ 1, + /* gas_cap (must NOT leak in) */ 9_999, + ) + .expect("balance covers L1 fee"); + // (500 - 50) / 1 = 450 + assert_eq!(allowance, 450); + + // fee_limit > balance → balance binds. + let allowance = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + U256::from(50u64), + Some(U256::from(10_000u64)), + 1, + 9_999, + ) + .expect("balance covers L1 fee"); + // (1000 - 50) / 1 = 950 + assert_eq!(allowance, 950); + + // No fee_limit → balance binds. + let allowance = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + U256::from(50u64), + None, + 1, + 9_999, + ) + .expect("balance covers L1 fee"); + assert_eq!(allowance, 950); + } + + /// Inactive / misconfigured token returns InvalidFeeToken before any + /// limit math runs (regardless of mode). + #[test] + fn token_inactive_or_misconfigured_returns_invalid_fee_token() { + let mut token = token_1to1(None, U256::ZERO); + token.is_active = false; + let err = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + U256::ZERO, + Some(U256::from(1_000u64)), + 1, + 50_000_000, + ) + .expect_err("inactive token must reject"); + assert!(matches!(err, MorphEthApiError::InvalidFeeToken)); + + let mut token = token_1to1(Some(U256::from(1u64)), U256::from(1_000u64)); + token.price_ratio = U256::ZERO; + let err = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + U256::ZERO, + None, + 1, + 50_000_000, + ) + .expect_err("zero price_ratio must reject"); + assert!(matches!(err, MorphEthApiError::InvalidFeeToken)); + } + + /// EVM-call mode with an absurd user-supplied `fee_limit` (e.g. + /// `U256::MAX`) must clamp to `gas_cap`, never bypass the + /// operator-configured `--rpc.gascap < block_gas_limit`. + #[test] + fn token_evm_call_mode_huge_fee_limit_clamps_to_gas_cap() { + let token = token_1to1(None, U256::ZERO); + let cap = 5_000_000u64; + let allowance = token_gas_allowance( + U256::ZERO, + U256::ZERO, + &token, + /* l1_fee */ U256::ZERO, + /* fee_limit */ Some(U256::MAX), + /* gas_price */ 1, + /* gas_cap */ cap, + ) + .expect("huge fee_limit must clamp, not error"); + assert_eq!(allowance, cap, "must clamp to gas_cap, not u64::MAX"); + } + + /// `token_amount_to_eth` must floor, not ceil. Inverse of + /// `eth_to_token_amount` (which ceils for protocol safety): an + /// affordability budget must round toward the user so the returned + /// wei budget is settleable at execution time. + #[test] + fn token_amount_to_eth_floors_non_unit_ratio() { + // scale=10, price_ratio=3. eth_to_token_amount(1 wei) = ceil(10/3) = 4 + // tokens. So 1 token cannot afford even 1 wei of gas — budget = 0. + let info = TokenFeeInfo { + price_ratio: U256::from(3u64), + scale: U256::from(10u64), + ..token_1to1(None, U256::ZERO) + }; + assert_eq!( + token_amount_to_eth(U256::from(1u64), &info), + Some(U256::ZERO) + ); + assert_eq!( + token_amount_to_eth(U256::from(3u64), &info), + Some(U256::ZERO) + ); + // 4 tokens → floor(12/10) = 1 wei. Roundtrip check: + // eth_to_token_amount(1) = ceil(10/3) = 4, so 4 tokens → 1 wei is + // exactly settleable. + assert_eq!( + token_amount_to_eth(U256::from(4u64), &info), + Some(U256::from(1u64)) + ); + // Exact multiple: 10 tokens → 3 wei (floor and ceil agree). + assert_eq!( + token_amount_to_eth(U256::from(10u64), &info), + Some(U256::from(3u64)) + ); + } } diff --git a/crates/rpc/src/eth/mod.rs b/crates/rpc/src/eth/mod.rs index 0845bfba..96a08d78 100644 --- a/crates/rpc/src/eth/mod.rs +++ b/crates/rpc/src/eth/mod.rs @@ -8,12 +8,10 @@ use eyre::Result; use morph_chainspec::MorphChainSpec; use morph_evm::MorphEvmConfig; use morph_primitives::{MorphHeader, MorphPrimitives}; -use reth_evm::{Database, EvmEnvFor}; use reth_node_api::{FullNodeComponents, FullNodeTypes, NodeTypes}; use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx}; use reth_primitives_traits::RecoveredBlock; use reth_provider::{BlockReader, ChainSpecProvider}; -use reth_revm::DatabaseCommit; use reth_rpc::EthApi; use reth_rpc_convert::{RpcConvert, RpcConverter, RpcTypes}; use reth_rpc_eth_api::{ @@ -21,12 +19,14 @@ use reth_rpc_eth_api::{ helpers::{ EthApiSpec, EthBlocks, EthFees, EthState, EthTransactions, LoadBlock, LoadFee, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace, - pending_block::PendingEnvBuilder, + bal::GetBlockAccessList, pending_block::PendingEnvBuilder, }, }; -use reth_rpc_eth_types::{EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle}; +use reth_rpc_eth_types::{ + EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle, StateCacheDb, +}; use reth_tasks::{ - TaskSpawner, + Runtime, pool::{BlockingTaskGuard, BlockingTaskPool}, }; use std::{fmt, marker::PhantomData, sync::Arc, time::Duration}; @@ -221,7 +221,7 @@ where Rpc: RpcConvert, { #[inline] - fn io_task_spawner(&self) -> impl TaskSpawner { + fn io_task_spawner(&self) -> &Runtime { self.inner.eth_api.task_spawner() } @@ -322,13 +322,18 @@ where async fn send_transaction( &self, + origin: reth_transaction_pool::TransactionOrigin, tx: reth_primitives_traits::WithEncoded< reth_primitives_traits::Recovered>, >, ) -> Result { - reth_rpc_eth_api::helpers::EthTransactions::send_transaction(&self.inner.eth_api, tx) - .await - .map_err(Into::into) + reth_rpc_eth_api::helpers::EthTransactions::send_transaction( + &self.inner.eth_api, + origin, + tx, + ) + .await + .map_err(Into::into) } } @@ -367,11 +372,10 @@ where MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, Rpc: RpcConvert, { - fn apply_pre_execution_changes( + fn apply_pre_execution_changes( &self, _block: &RecoveredBlock<::Block>, - _db: &mut DB, - _evm_env: &EvmEnvFor, + _db: &mut StateCacheDb, ) -> Result<(), Self::Error> { // Morph must skip Ethereum's 4788-style pre-block system calls during replay. // Standard Morph headers omit parentBeaconBlockRoot, so the default Ethereum @@ -380,6 +384,15 @@ where } } +impl GetBlockAccessList for MorphEthApi +where + N: MorphNodeCore, + N::Provider: ChainSpecProvider, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, + Rpc: RpcConvert, +{ +} + // ===== Internal container ===== impl fmt::Debug for MorphEthApi { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/rpc/src/eth/receipt.rs b/crates/rpc/src/eth/receipt.rs index 902de3a4..def41fc2 100644 --- a/crates/rpc/src/eth/receipt.rs +++ b/crates/rpc/src/eth/receipt.rs @@ -252,4 +252,153 @@ mod tests { assert_eq!(fields.l1_fee, r.l1_fee); assert_eq!(fields.reference, r.reference); } + + /// Regression test for the `transactionReceipts` subscription wiring. + /// + /// reth v2.2.0 exposes a `transactionReceipts` pubsub topic that, in the + /// `SubscriptionKind::TransactionReceipts` arm of + /// `reth_rpc::eth::pubsub`, calls `converter.convert_receipts(inputs)` + /// against the same converter the RPC `eth_getBlockReceipts` / + /// `eth_getTransactionReceipt` endpoints use. For Morph that converter + /// is [`MorphReceiptConverter`]. + /// + /// This test exercises the converter end-to-end on a Morph-tagged + /// receipt and asserts every Morph-specific field survives the + /// conversion. If any of these assertions break in a future bump, + /// pubsub `transactionReceipts` subscribers would silently lose Morph + /// metadata. + #[test] + fn transaction_receipts_subscription_preserves_morph_fields() { + use alloy_consensus::{Signed, TxEip1559, transaction::Recovered}; + use alloy_primitives::{B256, Signature, U256, address}; + use morph_primitives::{MorphPrimitives, MorphTxEnvelope}; + use reth_primitives_traits::TransactionMeta; + use reth_rpc_convert::transaction::{ConvertReceiptInput, ReceiptConverter}; + + let signer = address!("0000000000000000000000000000000000000099"); + + let envelope = MorphTxEnvelope::Eip1559(Signed::new_unchecked( + TxEip1559 { + chain_id: 2818, + nonce: 0, + gas_limit: 21_000, + max_fee_per_gas: 2_000_000_000, + max_priority_fee_per_gas: 1_000_000, + ..Default::default() + }, + Signature::new(U256::ZERO, U256::ZERO, false), + B256::ZERO, + )); + let recovered: Recovered<&MorphTxEnvelope> = Recovered::new_unchecked(&envelope, signer); + + let receipt = MorphReceipt::Morph(make_morph_receipt_with_fields()); + + let meta = TransactionMeta { + tx_hash: B256::ZERO, + index: 0, + block_hash: b256!("1111111111111111111111111111111111111111111111111111111111111111"), + block_number: 42, + base_fee: Some(1_000_000_000), + excess_blob_gas: None, + timestamp: 1_700_000_000, + }; + + let input = ConvertReceiptInput::<'_, MorphPrimitives> { + receipt, + tx: recovered, + gas_used: 21_000, + next_log_index: 0, + meta, + }; + + let rpc_receipts = MorphReceiptConverter + .convert_receipts(vec![input]) + .expect("morph converter should not fail on a well-formed input"); + assert_eq!(rpc_receipts.len(), 1); + let rpc = &rpc_receipts[0]; + + // Morph-specific top-level RPC fields must round-trip from the + // primitive `MorphTransactionReceipt` into `MorphRpcReceipt`. + assert_eq!(rpc.l1_fee, U256::from(5000)); + assert_eq!(rpc.version, Some(1)); + assert_eq!(rpc.fee_token_id, Some(U64::from(3))); + assert_eq!(rpc.fee_rate, Some(U256::from(2_000_000))); + assert_eq!(rpc.token_scale, Some(U256::from(1_000_000))); + assert_eq!(rpc.fee_limit, Some(U256::from(999_999))); + assert_eq!( + rpc.reference, + Some(b256!( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + )) + ); + assert_eq!(rpc.memo, Some(PrimitiveBytes::from("test memo"))); + + // Standard inner fields (from `build_receipt`) must also be plumbed. + assert_eq!(rpc.inner.from, signer); + assert_eq!(rpc.inner.block_number, Some(42)); + assert_eq!(rpc.inner.transaction_index, Some(0)); + } + + /// Companion test: L1 message receipts must come back from the + /// pubsub-style converter path with default Morph fields and the + /// L1Msg envelope variant, just like `eth_getBlockReceipts`. + #[test] + fn transaction_receipts_subscription_l1_msg_carries_default_morph_fields() { + use alloy_consensus::transaction::Recovered; + use alloy_primitives::{Address, B256, Sealed, U256, address}; + use morph_primitives::transaction::TxL1Msg; + use morph_primitives::{MorphPrimitives, MorphTxEnvelope}; + use reth_primitives_traits::TransactionMeta; + use reth_rpc_convert::transaction::{ConvertReceiptInput, ReceiptConverter}; + + let l1_msg = TxL1Msg { + queue_index: 7, + gas_limit: 100_000, + sender: address!("000000000000000000000000000000000000dead"), + ..Default::default() + }; + let envelope = MorphTxEnvelope::L1Msg(Sealed::new_unchecked(l1_msg, B256::ZERO)); + let recovered: Recovered<&MorphTxEnvelope> = + Recovered::new_unchecked(&envelope, Address::ZERO); + + let receipt = MorphReceipt::L1Msg(Receipt { + status: alloy_consensus::Eip658Value::Eip658(true), + cumulative_gas_used: 50_000, + logs: vec![], + }); + + let meta = TransactionMeta { + tx_hash: B256::ZERO, + index: 0, + block_hash: B256::ZERO, + block_number: 1, + base_fee: None, + excess_blob_gas: None, + timestamp: 0, + }; + + let input = ConvertReceiptInput::<'_, MorphPrimitives> { + receipt, + tx: recovered, + gas_used: 50_000, + next_log_index: 0, + meta, + }; + + let rpc = MorphReceiptConverter + .convert_receipts(vec![input]) + .expect("morph converter should not fail on a well-formed L1 message input") + .pop() + .expect("converter must produce one receipt per input"); + + // L1 messages explicitly carry no Morph metadata. + assert_eq!(rpc.l1_fee, U256::ZERO); + assert!(rpc.version.is_none()); + assert!(rpc.fee_token_id.is_none()); + assert!(rpc.fee_rate.is_none()); + assert!(rpc.token_scale.is_none()); + assert!(rpc.fee_limit.is_none()); + assert!(rpc.reference.is_none()); + assert!(rpc.memo.is_none()); + } } diff --git a/crates/rpc/src/eth/transaction.rs b/crates/rpc/src/eth/transaction.rs index 9b4795b6..725ce418 100644 --- a/crates/rpc/src/eth/transaction.rs +++ b/crates/rpc/src/eth/transaction.rs @@ -9,7 +9,7 @@ use alloy_network::TxSigner; use alloy_primitives::{Address, Signature, TxKind, U64, U256}; use alloy_rpc_types_eth::{AccessList, Transaction as RpcTransaction, TransactionInfo}; use reth_rpc_convert::{ - SignTxRequestError, SignableTxRequest, TryIntoSimTx, TryIntoTxEnv, transaction::FromConsensusTx, + FromConsensusTx, SignTxRequestError, SignableTxRequest, TryIntoSimTx, TryIntoTxEnv, }; use reth_rpc_eth_types::EthApiError; use std::convert::Infallible; @@ -50,6 +50,11 @@ impl FromConsensusTx for MorphRpcTransaction { inner: Recovered::new_unchecked(tx, signer), block_hash: tx_info.block_hash, block_number: tx_info.block_number, + // alloy 2.0 added an explicit `block_timestamp` to RPC transactions + // so receipts/transactions returned by `eth_*` align with engine API + // semantics. `TransactionInfo` already plumbs this through from the + // block header, so we just forward it. + block_timestamp: tx_info.block_timestamp, transaction_index: tx_info.index, effective_gas_price, }; @@ -151,10 +156,10 @@ impl SignableTxRequest for MorphTransactionRequest { /// /// Also encodes the transaction for L1 fee calculation. /// All MorphTx transactions are constructed as Version 1. -impl TryIntoTxEnv for MorphTransactionRequest { +impl TryIntoTxEnv for MorphTransactionRequest { type Err = EthApiError; - fn try_into_tx_env( + fn try_into_tx_env( self, evm_env: &EvmEnv, ) -> Result { @@ -189,28 +194,9 @@ impl TryIntoTxEnv for MorphTransactionRequest { Some(morph_primitives::transaction::morph_transaction::MORPH_TX_VERSION_1); } - // L1 fee handling for different RPC methods: - // - // 1. eth_estimateGas (disable_fee_charge = false): - // - Must calculate L1 data fee to check if sender has sufficient balance - // - Matches go-ethereum behavior: available.Sub(available, l1DataFee) - // - Generate RLP bytes for L1 fee calculation - // - // 2. eth_call (disable_fee_charge = true): - // - Pure EVM simulation, no fee deduction or balance check - // - Matches go-ethereum behavior: ApplyMessage(..., l1Fee = 0) - // - Skip RLP encoding to avoid L1 fee calculation - // - // The handler layer (validate_and_deduct_eth_fee) will: - // - Calculate L1 fee based on rlp_bytes (None → empty slice → fee = 0) - // - Skip balance check when disable_fee_charge = true - if !evm_env.cfg_env.disable_fee_charge { - // eth_estimateGas: encode transaction for L1 fee calculation - tx_env.rlp_bytes = Some(tx_env.encode_for_l1_fee(evm_env.cfg_env.chain_id)); - } else { - // eth_call: skip L1 fee by not providing RLP bytes - tx_env.rlp_bytes = None; - } + // Required by `MorphEthApi::caller_gas_allowance` (eth/call.rs) to + // recover the L1 data fee when capping `eth_estimateGas` allowance. + tx_env.rlp_bytes = Some(tx_env.encode_for_l1_fee(evm_env.cfg_env.chain_id)); Ok(tx_env) } @@ -338,19 +324,20 @@ mod tests { difficulty: alloy_primitives::U256::ZERO, prevrandao: Some(B256::ZERO), blob_excess_gas_and_price: None, + slot_num: 0, }, }; EvmEnv::new(cfg, block_env) } - /// Test that eth_call (disable_fee_charge = true) skips RLP encoding for L1 fee calculation. - /// - /// This ensures that eth_call does not calculate L1 data fee, matching go-ethereum behavior - /// where ApplyMessage is called with l1Fee = 0. + /// RLP bytes are always populated — even on `eth_call` where + /// `disable_fee_charge = true`. The handler ignores them on both RPC + /// paths (see comment in `MorphTransactionRequest::try_into_tx_env`), + /// but `MorphEthApi::caller_gas_allowance` still needs them to derive + /// the L1 data fee for the `eth_estimateGas` gas-allowance cap. #[test] - fn test_eth_call_skips_l1_fee_encoding() { - // Arrange: Create a standard Ethereum transaction request + fn test_rlp_bytes_always_populated() { let request = MorphTransactionRequest { inner: create_basic_transaction_request(), fee_token_id: None, @@ -359,18 +346,18 @@ mod tests { memo: None, }; - // eth_call scenario: disable_fee_charge = true + // disable_fee_charge = true (eth_call path) let evm_env = create_evm_env(true); - - // Act: Convert to TxEnv let tx_env = request .try_into_tx_env(&evm_env) .expect("conversion should succeed"); - - // Assert: rlp_bytes should be None (no L1 fee encoding) assert!( - tx_env.rlp_bytes.is_none(), - "eth_call should not encode RLP bytes for L1 fee calculation" + tx_env.rlp_bytes.is_some(), + "rlp_bytes should be populated even when disable_fee_charge = true" + ); + assert!( + !tx_env.rlp_bytes.unwrap().is_empty(), + "RLP bytes should not be empty" ); } @@ -478,15 +465,21 @@ mod tests { ); } - /// Test that eth_call with MorphTx still skips RLP encoding. + /// MorphTx converted on the `eth_call` path still carries RLP bytes. /// - /// Even though it's a MorphTx, eth_call should not encode for L1 fee. + /// Before reth v2.0.0 this test asserted `rlp_bytes.is_none()` on the + /// assumption that `eth_call` fell through to the EVM handler, which + /// read `rlp_bytes` for L1 fee deduction. reth v2.0.0 sets + /// `cfg_env.disable_fee_charge = true` on `eth_call`, causing the + /// handler to short-circuit before reading `rlp_bytes`, and the RLP + /// is instead needed by `MorphEthApi::caller_gas_allowance` to + /// compute the L1 fee cap on `eth_estimateGas`. We now populate it + /// unconditionally. #[test] - fn test_eth_call_with_morph_tx_skips_encoding() { - // Arrange: Create a MorphTx + fn test_eth_call_with_morph_tx_keeps_rlp_and_morph_fields() { let request = MorphTransactionRequest { inner: create_basic_transaction_request(), - fee_token_id: Some(U64::from(1)), // Use U64, not U256 + fee_token_id: Some(U64::from(1)), fee_limit: Some(U256::from(1000000)), reference: Some(B256::random()), memo: Some(Bytes::from("test")), @@ -495,18 +488,20 @@ mod tests { // eth_call scenario: disable_fee_charge = true let evm_env = create_evm_env(true); - // Act: Convert to TxEnv let tx_env = request .try_into_tx_env(&evm_env) .expect("conversion should succeed"); - // Assert: Even for MorphTx, eth_call should not encode assert!( - tx_env.rlp_bytes.is_none(), - "eth_call should not encode RLP bytes even for MorphTx" + tx_env.rlp_bytes.is_some(), + "rlp_bytes should be populated even when disable_fee_charge = true" + ); + assert!( + !tx_env.rlp_bytes.unwrap().is_empty(), + "RLP bytes should not be empty" ); - // Assert: Transaction type should still be MorphTx + // Transaction type should still be MorphTx assert_eq!( tx_env.inner.tx_type, morph_primitives::MORPH_TX_TYPE_ID, @@ -753,6 +748,7 @@ mod tests { hash: Some(B256::ZERO), block_hash: Some(B256::random()), block_number: Some(10), + block_timestamp: None, index: Some(0), base_fee: Some(1000), }; @@ -795,6 +791,7 @@ mod tests { hash: Some(B256::ZERO), block_hash: Some(B256::random()), block_number: Some(100), + block_timestamp: None, index: Some(5), base_fee: Some(1_000_000_000), }; @@ -835,6 +832,7 @@ mod tests { hash: Some(B256::ZERO), block_hash: None, block_number: None, + block_timestamp: None, index: None, base_fee: Some(1_000_000_000), }; @@ -870,6 +868,7 @@ mod tests { hash: Some(B256::ZERO), block_hash: Some(B256::random()), block_number: Some(1), + block_timestamp: None, index: Some(0), base_fee: Some(base_fee), }; diff --git a/crates/txpool/Cargo.toml b/crates/txpool/Cargo.toml index 77e6e13f..ce50efe4 100644 --- a/crates/txpool/Cargo.toml +++ b/crates/txpool/Cargo.toml @@ -14,11 +14,13 @@ workspace = true [dependencies] # Morph morph-chainspec.workspace = true +morph-evm.workspace = true morph-primitives = { workspace = true, features = ["reth-codec"] } morph-revm.workspace = true # Reth reth-chainspec.workspace = true +reth-evm.workspace = true reth-primitives-traits.workspace = true reth-provider.workspace = true reth-revm.workspace = true @@ -39,4 +41,5 @@ parking_lot.workspace = true tracing.workspace = true [dev-dependencies] +reth-evm-ethereum.workspace = true reth-provider = { workspace = true, features = ["test-utils"] } diff --git a/crates/txpool/src/lib.rs b/crates/txpool/src/lib.rs index bac2d0c7..52ca426b 100644 --- a/crates/txpool/src/lib.rs +++ b/crates/txpool/src/lib.rs @@ -47,7 +47,9 @@ pub use maintain::maintain_morph_pool; mod morph_tx_validation; pub use morph_tx_validation::{MorphTxValidationInput, MorphTxValidationResult, validate_morph_tx}; -use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTaskExecutor}; +#[allow(unused_imports)] +use morph_evm as _; +use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTaskExecutor}; // for default type parameter MorphEvmConfig /// Type alias for default Morph transaction pool. /// @@ -55,8 +57,13 @@ use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTask /// - [`MorphTransactionValidator`] for transaction validation /// - [`CoinbaseTipOrdering`] for transaction ordering (by effective gas tip) /// - [`MorphPooledTransaction`] as the pooled transaction type -pub type MorphTransactionPool = Pool< - TransactionValidationTaskExecutor>, +pub type MorphTransactionPool< + Client, + S, + T = MorphPooledTransaction, + Evm = morph_evm::MorphEvmConfig, +> = Pool< + TransactionValidationTaskExecutor>, CoinbaseTipOrdering, S, >; diff --git a/crates/txpool/src/transaction.rs b/crates/txpool/src/transaction.rs index fdc533e2..21411db8 100644 --- a/crates/txpool/src/transaction.rs +++ b/crates/txpool/src/transaction.rs @@ -69,6 +69,10 @@ impl PoolTransaction for MorphPooledTransaction { self.inner.transaction().clone() } + fn consensus_ref(&self) -> Recovered<&Self::Consensus> { + self.inner.transaction().as_recovered_ref() + } + fn into_consensus(self) -> Recovered { self.inner.transaction } diff --git a/crates/txpool/src/validator.rs b/crates/txpool/src/validator.rs index 1fdc73ef..c75ecd8c 100644 --- a/crates/txpool/src/validator.rs +++ b/crates/txpool/src/validator.rs @@ -16,8 +16,9 @@ use morph_primitives::MorphTxEnvelope; use morph_revm::L1BlockInfo; use parking_lot::RwLock; use reth_chainspec::ChainSpecProvider; +use reth_evm::ConfigureEvm; use reth_primitives_traits::{ - Block, GotExpected, SealedBlock, transaction::error::InvalidTransactionError, + Block, BlockTy, GotExpected, SealedBlock, transaction::error::InvalidTransactionError, }; use reth_revm::database::StateProviderDatabase; use reth_storage_api::{BlockReaderIdExt, StateProviderFactory}; @@ -108,14 +109,14 @@ impl MorphL1BlockInfo { /// checking disabled via `disable_balance_check()`, since MorphTx users may have /// zero ETH balance but sufficient ERC20 tokens for gas payment. #[derive(Debug)] -pub struct MorphTransactionValidator { +pub struct MorphTransactionValidator { /// The type that performs the actual validation. - inner: EthTransactionValidator, + inner: EthTransactionValidator, /// Additional block info required for validation. block_info: Arc, } -impl MorphTransactionValidator { +impl MorphTransactionValidator { /// Returns the configured chain spec. pub fn chain_spec(&self) -> Arc where @@ -163,13 +164,14 @@ fn insufficient_funds_outcome( ) } -impl MorphTransactionValidator +impl MorphTransactionValidator where Client: ChainSpecProvider + StateProviderFactory + BlockReaderIdExt, Tx: EthPoolTransaction, + Evm: ConfigureEvm, { /// Create a new [`MorphTransactionValidator`]. - pub fn new(inner: EthTransactionValidator) -> Self { + pub fn new(inner: EthTransactionValidator) -> Self { let this = Self::with_block_info(inner, MorphL1BlockInfo::default()); if let Ok(Some(block)) = this .inner @@ -184,7 +186,7 @@ where /// Create a new [`MorphTransactionValidator`] with the given [`MorphL1BlockInfo`]. pub fn with_block_info( - inner: EthTransactionValidator, + inner: EthTransactionValidator, block_info: MorphL1BlockInfo, ) -> Self { Self { @@ -470,12 +472,14 @@ where } } -impl TransactionValidator for MorphTransactionValidator +impl TransactionValidator for MorphTransactionValidator where Client: ChainSpecProvider + StateProviderFactory + BlockReaderIdExt, Tx: EthPoolTransaction, + Evm: ConfigureEvm, { type Transaction = Tx; + type Block = BlockTy; async fn validate_transaction( &self, @@ -487,15 +491,13 @@ where async fn validate_transactions( &self, - transactions: Vec<(TransactionOrigin, Self::Transaction)>, + transactions: impl IntoIterator + + Send, ) -> Vec> { - self.validate_all(transactions) + self.validate_all(transactions.into_iter().collect()) } - fn on_new_head_block(&self, new_tip_block: &SealedBlock) - where - B: Block, - { + fn on_new_head_block(&self, new_tip_block: &SealedBlock) { self.inner.on_new_head_block(new_tip_block); self.update_l1_block_info(new_tip_block.header()); } @@ -514,11 +516,12 @@ fn is_morph_tx(tx: &impl Typed2718) -> bool { #[cfg(test)] mod tests { use super::*; - use alloy_consensus::{Block, Header, Signed, TxEip1559, TxLegacy}; + use alloy_consensus::{Signed, TxEip1559, TxLegacy}; use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{B256, Signature, TxKind, address}; - use morph_chainspec::MORPH_MAINNET; - use morph_primitives::{TxL1Msg, TxMorph}; + use morph_chainspec::{MORPH_MAINNET, MorphChainSpec}; + use morph_evm::MorphEvmConfig; + use morph_primitives::{MorphPrimitives, TxL1Msg, TxMorph}; use morph_revm::{ L2_TOKEN_REGISTRY_ADDRESS, compute_mapping_slot, compute_mapping_slot_for_address, }; @@ -528,6 +531,12 @@ mod tests { blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder, }; + fn new_mock_provider() -> MockEthProvider { + MockEthProvider::::new() + .with_chain_spec((**MORPH_MAINNET).clone()) + .with_genesis_block() + } + fn storage_key(slot: U256) -> B256 { B256::from(slot.to_be_bytes::<32>()) } @@ -602,12 +611,16 @@ mod tests { #[test] fn validate_l1_message_rejected() { // Create validator with mock provider - let client = MockEthProvider::default().with_chain_spec(MORPH_MAINNET.clone()); - let eth_validator: EthTransactionValidator<_, crate::MorphPooledTransaction> = - EthTransactionValidatorBuilder::new(client) - .no_shanghai() - .no_cancun() - .build::(InMemoryBlobStore::default()); + let client = new_mock_provider(); + let morph_evm_config = MorphEvmConfig::new_with_default_factory(MORPH_MAINNET.clone()); + let eth_validator: EthTransactionValidator< + _, + crate::MorphPooledTransaction, + MorphEvmConfig, + > = EthTransactionValidatorBuilder::new(client, morph_evm_config) + .no_shanghai() + .no_cancun() + .build::(InMemoryBlobStore::default()); let validator = MorphTransactionValidator::new(eth_validator); let origin = TransactionOrigin::External; @@ -643,15 +656,19 @@ mod tests { #[test] fn validate_valid_eip1559_transaction() { // Create validator with mock provider and disable balance check for simplicity - let client = MockEthProvider::default().with_chain_spec(MORPH_MAINNET.clone()); + let client = new_mock_provider(); let signer = address!("0000000000000000000000000000000000000001"); client.add_account(signer, ExtendedAccount::new(0, U256::from(10u128.pow(18)))); - let eth_validator: EthTransactionValidator<_, crate::MorphPooledTransaction> = - EthTransactionValidatorBuilder::new(client) - .no_shanghai() - .no_cancun() - .disable_balance_check() - .build::(InMemoryBlobStore::default()); + let morph_evm_config = MorphEvmConfig::new_with_default_factory(MORPH_MAINNET.clone()); + let eth_validator: EthTransactionValidator< + _, + crate::MorphPooledTransaction, + MorphEvmConfig, + > = EthTransactionValidatorBuilder::new(client, morph_evm_config) + .no_shanghai() + .no_cancun() + .disable_balance_check() + .build::(InMemoryBlobStore::default()); let validator = MorphTransactionValidator::new(eth_validator); let origin = TransactionOrigin::External; @@ -694,15 +711,19 @@ mod tests { #[test] fn validate_valid_legacy_transaction() { // Create validator with mock provider and disable balance check for simplicity - let client = MockEthProvider::default().with_chain_spec(MORPH_MAINNET.clone()); + let client = new_mock_provider(); let signer = address!("0000000000000000000000000000000000000001"); client.add_account(signer, ExtendedAccount::new(0, U256::from(10u128.pow(18)))); - let eth_validator: EthTransactionValidator<_, crate::MorphPooledTransaction> = - EthTransactionValidatorBuilder::new(client) - .no_shanghai() - .no_cancun() - .disable_balance_check() - .build::(InMemoryBlobStore::default()); + let morph_evm_config = MorphEvmConfig::new_with_default_factory(MORPH_MAINNET.clone()); + let eth_validator: EthTransactionValidator< + _, + crate::MorphPooledTransaction, + MorphEvmConfig, + > = EthTransactionValidatorBuilder::new(client, morph_evm_config) + .no_shanghai() + .no_cancun() + .disable_balance_check() + .build::(InMemoryBlobStore::default()); let validator = MorphTransactionValidator::new(eth_validator); let origin = TransactionOrigin::External; @@ -742,24 +763,11 @@ mod tests { #[test] fn validate_morph_tx_uses_effective_gas_price_for_token_fee_path() { - let client = MockEthProvider::default().with_chain_spec(MORPH_MAINNET.clone()); + let client = new_mock_provider(); let signer = address!("0000000000000000000000000000000000000001"); let token = address!("5300000000000000000000000000000000000042"); let balance_slot = U256::from(7); - client.add_block( - B256::from([0x11; 32]), - Block::new( - Header { - number: 1, - timestamp: 1, - gas_limit: 30_000_000, - base_fee_per_gas: Some(10), - ..Default::default() - }, - Default::default(), - ), - ); client.add_account(signer, ExtendedAccount::new(0, U256::ZERO)); client.add_account( L2_TOKEN_REGISTRY_ADDRESS, @@ -773,13 +781,23 @@ mod tests { )]), ); - let eth_validator: EthTransactionValidator<_, crate::MorphPooledTransaction> = - EthTransactionValidatorBuilder::new(client) - .no_shanghai() - .no_cancun() - .disable_balance_check() - .build::(InMemoryBlobStore::default()); + let morph_evm_config = MorphEvmConfig::new_with_default_factory(MORPH_MAINNET.clone()); + let eth_validator: EthTransactionValidator< + _, + crate::MorphPooledTransaction, + MorphEvmConfig, + > = EthTransactionValidatorBuilder::new(client, morph_evm_config) + .no_shanghai() + .no_cancun() + .disable_balance_check() + .build::(InMemoryBlobStore::default()); let validator = MorphTransactionValidator::new(eth_validator); + // Simulate an active chain head with base_fee_per_gas = 10 so the + // effective gas price path is taken (min(max_fee, base_fee + priority) + // = min(100, 11) = 11), yielding required_token_amount = 21_000 * 11. + validator + .block_info + .update(L1BlockInfo::default(), 0, 0, Some(10)); let tx = TxMorph { chain_id: 2818, diff --git a/deny.toml b/deny.toml index 8a1244ea..c71585ae 100644 --- a/deny.toml +++ b/deny.toml @@ -9,7 +9,8 @@ ignore = [ # https://rustsec.org/advisories/RUSTSEC-2025-0141 bincode is unmaintained "RUSTSEC-2025-0141", # https://rustsec.org/advisories/RUSTSEC-2026-0097 rand unsound with custom logger - # pinned transitively via reth; no fix available upstream yet + # pinned transitively via reth; we don't install a custom logger so this is + # a false positive in our usage, but cargo-deny can't see usage "RUSTSEC-2026-0097", # https://rustsec.org/advisories/RUSTSEC-2026-0098 rustls-webpki URI name constraints "RUSTSEC-2026-0098", @@ -88,5 +89,6 @@ license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] unknown-registry = "warn" unknown-git = "deny" allow-git = [ - "https://github.com/morph-l2/reth", + "https://github.com/paradigmxyz/reth", + "https://github.com/sigp/discv5", ] diff --git a/etc/docker-compose.yml b/etc/docker-compose.yml index b11f2366..570aeaf2 100644 --- a/etc/docker-compose.yml +++ b/etc/docker-compose.yml @@ -27,7 +27,7 @@ services: --authrpc.port 8551 --authrpc.jwtsecret /var/lib/morph-reth/jwt/jwt.hex --http --http.addr 0.0.0.0 --http.port 8545 - --http.api "eth,net,web3" + --http.api "eth,net,web3,reth" prometheus: restart: unless-stopped diff --git a/local-test/README.md b/local-test/README.md index 39ebd69b..e5e801a0 100644 --- a/local-test/README.md +++ b/local-test/README.md @@ -97,4 +97,13 @@ To wipe chain data and start syncing from scratch: ./local-test/reset.sh hoodi --yes # Reset hoodi (no confirmation) ``` -This removes `reth-data/db`, `reth-data/static_files`, and `node-data/data/` for the specified network. Config files (genesis, keys) are preserved. +This removes reth's canonical DB, RocksDB history indices, static files, ExEx state, Morph reference index state, and `node-data/data/` for the specified network. Config files (genesis, keys), `reth.toml`, discovery secrets, and logs are preserved. + +## Storage & engine tuning + +- **Storage V2 is reth's default from v2.0.0.** Hot/cold layout: MDBX for state/trie, RocksDB for history indices, static files for changesets. V1 and V2 databases are **not interchangeable** — upgrading from a V1 data directory requires a full re-sync via `reset.sh`. + +- `reth-start.sh` passes `--engine.persistence-threshold 256` / `--engine.memory-block-buffer-target 16` / `--engine.persistence-backpressure-threshold 512` (upstream defaults are 8 / 4 / 16). These batch MDBX writes so they do not compete with the morphnode Tendermint LevelDB fsyncs when both run on the same host. + + - **Backpressure semantics**: the engine stops executing new payloads once `canonical_tip - last_persisted_block` exceeds the backpressure threshold. We raised it to 512 to absorb fsync spikes; reth enforces that it must be `>` `--engine.persistence-threshold`. + - **When to revert**: if morphnode is moved to a separate host (or its fsyncs no longer contend with reth), these flags can be dropped back to defaults. diff --git a/local-test/reset.sh b/local-test/reset.sh index 6d975f4f..babc3e9d 100755 --- a/local-test/reset.sh +++ b/local-test/reset.sh @@ -17,11 +17,15 @@ echo "==========================================" echo echo "This will remove:" echo " - ${RETH_DATA_DIR}/db" +echo " - ${RETH_DATA_DIR}/rocksdb" echo " - ${RETH_DATA_DIR}/static_files" +echo " - ${RETH_DATA_DIR}/exex" +echo " - ${RETH_DATA_DIR}/morph" echo " - ${NODE_HOME}/data" echo echo "This keeps:" echo " - ${NODE_HOME}/config (genesis/keys)" +echo " - ${RETH_DATA_DIR}/discovery-secret and ${RETH_DATA_DIR}/reth.toml" echo " - log files" echo @@ -35,7 +39,13 @@ fi "${SCRIPT_DIR}/stop-all.sh" || true -rm -rf "${RETH_DATA_DIR}/db" "${RETH_DATA_DIR}/static_files" "${NODE_HOME}/data" +rm -rf \ + "${RETH_DATA_DIR}/db" \ + "${RETH_DATA_DIR}/rocksdb" \ + "${RETH_DATA_DIR}/static_files" \ + "${RETH_DATA_DIR}/exex" \ + "${RETH_DATA_DIR}/morph" \ + "${NODE_HOME}/data" mkdir -p "${RETH_DATA_DIR}" "${NODE_HOME}/data" cat > "${NODE_HOME}/data/priv_validator_state.json" <<'EOF' diff --git a/local-test/reth-start.sh b/local-test/reth-start.sh index 3c6c7554..3634f579 100755 --- a/local-test/reth-start.sh +++ b/local-test/reth-start.sh @@ -31,13 +31,20 @@ args=( --http --http.addr "${RETH_HTTP_ADDR}" --http.port "${RETH_HTTP_PORT}" - --http.api "web3,debug,eth,txpool,net,trace,admin" + --http.api "web3,debug,eth,txpool,net,trace,admin,reth" --authrpc.addr "${RETH_AUTHRPC_ADDR}" --authrpc.port "${RETH_AUTHRPC_PORT}" --authrpc.jwtsecret "${JWT_SECRET}" --log.file.directory "$(dirname "${RETH_LOG_FILE}")" --log.file.filter info --rpc.eth-proof-window 1209600 + # Local testing: explicitly avoid external IP probes such as icanhazip.com. + --nat none + # Batch MDBX writes so they don't compete with Tendermint's LevelDB fsyncs + # (v2.0.0 added persistence-backpressure-threshold, which must be > persistence-threshold) + --engine.persistence-threshold 256 + --engine.memory-block-buffer-target 16 + --engine.persistence-backpressure-threshold 512 ) # Start morph-reth with pm2