diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 538605e..7b225cb 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 7e52838..c185543 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -88,22 +88,22 @@ 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 = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4ff99651d46cef43767b5e8262ea228cd05287409ccb0c947cc25e70a952f9" +checksum = "7f16daaf7e1f95f62c6c3bf8a3fc3d78b08ae9777810c0bb5e94966c7cd57ef0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -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,9 +129,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0701b0eda8051a2398591113e7862f807ccdd3315d0b441f06c2a0865a379b" +checksum = "118998d9015332ab1b4720ae1f1e3009491966a0349938a1f43ff45a8a4c6299" dependencies = [ "alloy-consensus", "alloy-eips", @@ -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,38 +207,28 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926b2c0d34e641cf8b17bf54ce50fda16715b9f68ad878fa6128bae410c6f890" -dependencies = [ - "alloy-primitives", - "alloy-rlp", - "serde", -] - -[[package]] -name = "alloy-eip7928" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +checksum = "407510740da514b694fecb44d8b3cebdc60d448f70cc5d24743e8ba273448a6e" dependencies = [ "alloy-primitives", "alloy-rlp", "arbitrary", "borsh", + "once_cell", "serde", ] [[package]] name = "alloy-eips" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def1626eea28d48c6cc0a6f16f34d4af0001906e4f889df6c660b39c86fd044d" +checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", - "alloy-eip7928 0.3.3", + "alloy-eip7928", "alloy-primitives", "alloy-rlp", "alloy-serde", @@ -248,19 +238,18 @@ dependencies = [ "c-kzg", "derive_more", "either", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", "serde", "serde_with", "sha2", - "thiserror 2.0.18", ] [[package]] name = "alloy-evm" -version = "0.25.3" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc719f3501f4b6761c0da9e1c657adc7ac37739dea51a373d5849efbe4f55e52" +checksum = "e13146597a586a4166ac31b192883e08c044272d6b8c43de231ee1f43dd9a115" dependencies = [ "alloy-consensus", "alloy-eips", @@ -271,17 +260,16 @@ 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 = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d9d1aba3f914f0e8db9e4616ae37f3d811426d95bdccf44e47d0605ab202f6" +checksum = "bbf9480307b09d22876efb67d30cadd9013134c21f3a17ec9f93fd7536d38024" dependencies = [ "alloy-eips", "alloy-primitives", @@ -320,9 +308,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57586581f2008933241d16c3e3f633168b3a5d2738c5c42ea5246ec5e0ef17a" +checksum = "422d110f1c40f1f8d0e5562b0b649c35f345fccb7093d9f02729943dcd1eef71" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -335,9 +323,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b36c2a0ed74e48851f78415ca5b465211bd678891ba11e88fee09eac534bab1" +checksum = "7197a66d94c4de1591cdc16a9bcea5f8cccd0da81b865b49aef97b1b4016e0fa" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -361,9 +349,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "636c8051da58802e757b76c3b65af610b95799f72423dc955737dec73de234fd" +checksum = "eb82711d59a43fdfd79727c99f270b974c784ec4eb5728a0d0d22f26716c87ef" dependencies = [ "alloy-consensus", "alloy-eips", @@ -387,14 +375,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,9 +392,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3dd56e2eafe8b1803e325867ac2c8a4c73c9fb5f341ffd8347f9344458c5922" +checksum = "bf6b18b929ef1d078b834c3631e9c925177f3b23ddc6fa08a722d13047205876" dependencies = [ "alloy-chains", "alloy-consensus", @@ -429,14 +417,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.2", "serde", "serde_json", "thiserror 2.0.18", @@ -448,9 +436,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eebf54983d4fccea08053c218ee5c288adf2e660095a243d0532a8070b43955" +checksum = "5ad54073131e7292d4e03e1aa2287730f737280eb160d8b579fb31939f558c11" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -470,9 +458,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 +469,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 +480,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91577235d341a1bdbee30a463655d08504408a4d51e9f72edbfc5a622829f402" +checksum = "94fcc9604042ca80bd37aa5e232ea1cd851f337e31e2babbbb345bc0b1c30de3" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -505,7 +493,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest", + "reqwest 0.13.2", "serde", "serde_json", "tokio", @@ -518,9 +506,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cff039bf01a17d76c0aace3a3a773d5f895eb4c68baaae729ec9da9e86c99c" +checksum = "4faad925d3a669ffc15f43b3deec7fbdf2adeb28a4d6f9cf4bc661698c0f8f4b" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -531,9 +519,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "564afceae126df73b95f78c81eb46e2ef689a45ace0fcdaf5c9a178693a5ccca" +checksum = "b38080c2b01ad1bacbd3583cf7f6f800e5e0ffc11eaddaad7321225733a2d818" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -543,9 +531,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22250cf438b6a3926de67683c08163bfa1fd1efa47ee9512cbcd631b6b0243c" +checksum = "47df51bedb3e6062cb9981187a51e86d0d64a4de66eb0855e9efe6574b044ddf" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -555,9 +543,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73234a141ecce14e2989748c04fcac23deee67a445e2c4c167cfb42d4dacd1b6" +checksum = "3823026d1ed239a40f12364fac50726c8daf1b6ab8077a97212c5123910429ed" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -566,16 +554,16 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625af0c3ebd3c31322edb1fb6b8e3e518acc39e164ed07e422eaff05310ff2fa" +checksum = "f526dbd7bb039327cfd0ccf18c8a29ffd7402616b0c7a0239512bf8417d544c7" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "derive_more", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", "serde", "serde_json", "serde_with", @@ -586,9 +574,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779f70ab16a77e305571881b07a9bc6b0068ae6f962497baf5762825c5b839fb" +checksum = "2145138f3214928f08cd13da3cb51ef7482b5920d8ac5a02ecd4e38d1a8f6d1e" dependencies = [ "alloy-primitives", "derive_more", @@ -598,9 +586,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10620d600cc46538f613c561ac9a923843c6c74c61f054828dcdb8dd18c72ec4" +checksum = "bb9b97b6e7965679ad22df297dda809b11cebc13405c1b537e5cffecc95834fa" dependencies = [ "alloy-consensus", "alloy-eips", @@ -608,19 +596,19 @@ dependencies = [ "alloy-rlp", "alloy-serde", "derive_more", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.9.1", + "ethereum_ssz_derive 0.9.1", "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 = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "010e101dbebe0c678248907a2545b574a87d078d82c2f6f5d0e8e7c9a6149a10" +checksum = "59c095f92c4e1ff4981d89e9aa02d5f98c762a1980ab66bec49c44be11349da2" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -640,9 +628,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375e4bf001135fe4f344db6197fafed8c2b61e99fa14d3597f44cd413f79e45b" +checksum = "8eae9c65ff60dcc262247b6ebb5ad391ddf36d09029802c1768c5723e0cfa2f4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -655,9 +643,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be096f74d85e1f927580b398bf7bc5b4aa62326f149680ec0867e3c040c9aced" +checksum = "2e5a4d010f86cd4e01e5205ec273911e538e1738e76d8bafe9ecd245910ea5a3" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -669,9 +657,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ab75189fbc29c5dd6f0bc1529bccef7b00773b458763f4d9d81a77ae4a1a2d" +checksum = "942d26a2ca8891b26de4a8529d21091e21c1093e27eb99698f1a86405c76b1ff" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -681,9 +669,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6d631f8b975229361d8af7b2c749af31c73b3cf1352f90e144ddb06227105e" +checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" dependencies = [ "alloy-primitives", "arbitrary", @@ -693,9 +681,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97f40010b5e8f79b70bf163b38cd15f529b18ca88c4427c0e43441ee54e4ed82" +checksum = "43f447aefab0f1c0649f71edc33f590992d4e122bc35fb9cdbbf67d4421ace85" dependencies = [ "alloy-primitives", "async-trait", @@ -708,9 +696,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c4ec1cc27473819399a3f0da83bc1cef0ceaac8c1c93997696e46dc74377a58" +checksum = "f721f4bf2e4812e5505aaf5de16ef3065a8e26b9139ac885862d00b5a55a659a" dependencies = [ "alloy-consensus", "alloy-network", @@ -720,7 +708,7 @@ dependencies = [ "coins-bip32", "coins-bip39", "k256", - "rand 0.8.5", + "rand 0.8.6", "thiserror 2.0.18", "zeroize", ] @@ -748,7 +736,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 +768,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -797,9 +785,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a03bb3f02b9a7ab23dacd1822fa7f69aa5c8eefcdcf57fad085e0b8d76fb4334" +checksum = "8098f965442a9feb620965ba4b4be5e2b320f4ec5a3fff6bfa9e1ff7ef42bed1" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -820,14 +808,14 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce599598ef8ebe067f3627509358d9faaa1ef94f77f834a7783cd44209ef55c" +checksum = "e8597d36d546e1dab822345ad563243ec3920e199322cb554ce56c8ef1a1e2e7" dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest", + "reqwest 0.13.2", "serde_json", "tower", "tracing", @@ -836,9 +824,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49963a2561ebd439549915ea61efb70a7b13b97500ec16ca507721c9d9957d07" +checksum = "a1bd98c3870b8a44b79091dde5216a81d58ffbc1fd8ed61b776f9fee0f3bdf20" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -856,18 +844,20 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed38ea573c6658e0c2745af9d1f1773b1ed83aa59fbd9c286358ad469c3233a" +checksum = "ec3ab7a72b180992881acc112628b7668337a19ce15293ee974600ea7b693691" dependencies = [ "alloy-pubsub", "alloy-transport", "futures", "http", + "rustls", "serde_json", "tokio", "tokio-tungstenite", "tracing", + "url", "ws_stream_wasm", ] @@ -893,11 +883,11 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.6.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397406cf04b11ca2a48e6f81804c70af3f40a36abf648e11dc7416043eb0834d" +checksum = "d69722eddcdf1ce096c3ab66cf8116999363f734eb36fe94a148f4f71c85da84" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -914,9 +904,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 +919,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", ] @@ -1258,7 +1248,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 +1258,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 +1268,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 +1294,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 +1382,26 @@ 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", + "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 +1474,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 2.11.1", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1543,9 +1531,9 @@ 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", ] @@ -1563,6 +1551,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 +1597,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 +1671,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 +1692,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" @@ -1720,15 +1727,6 @@ dependencies = [ "serde_core", ] -[[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" @@ -1739,19 +1737,6 @@ dependencies = [ "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 +1744,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 +1762,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -1831,7 +1810,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1857,9 +1836,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 +1846,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 +1858,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 +1870,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 +1910,7 @@ dependencies = [ "hmac", "once_cell", "pbkdf2", - "rand 0.8.5", + "rand 0.8.6", "sha2", "thiserror 1.0.69", ] @@ -1948,9 +1936,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 +1956,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 +1977,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 +1991,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" @@ -2023,7 +2011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "proptest", "serde_core", ] @@ -2042,11 +2030,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 +2049,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" @@ -2086,19 +2081,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "core2" -version = "0.4.0" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "memchr", + "libc", ] [[package]] name = "cpufeatures" -version = "0.2.17" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ "libc", ] @@ -2114,9 +2109,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" @@ -2162,26 +2157,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" @@ -2189,11 +2177,15 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "crossterm_winapi", + "derive_more", "document-features", + "mio", "parking_lot", - "rustix 1.1.4", + "rustix", + "signal-hook", + "signal-hook-mio", "winapi", ] @@ -2251,7 +2243,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", @@ -2281,16 +2273,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" @@ -2315,21 +2297,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" @@ -2339,6 +2306,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", "syn 2.0.117", ] @@ -2354,17 +2322,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" @@ -2376,44 +2333,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", @@ -2421,9 +2367,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 2.0.117", @@ -2479,9 +2425,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", @@ -2580,15 +2526,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" @@ -2599,18 +2536,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" @@ -2618,15 +2543,15 @@ 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" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f170f4f6ed0e1df52bf43b403899f0081917ecf1500bfe312505cc3b515a8899" +checksum = "4c7999df38d0bd8f688212e1a4fae31fd2fea6d218649b9cd7c40bf3ec1318fc" dependencies = [ "aes", "aes-gcm", @@ -2642,13 +2567,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", @@ -2668,9 +2592,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" @@ -2787,7 +2711,7 @@ dependencies = [ "hex", "k256", "log", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "serde", "sha3", @@ -2842,22 +2766,13 @@ dependencies = [ "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", -] - [[package]] name = "ethereum_hashing" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c853bd72c9e5787f8aafc3df2907c2ed03cff3150c3acd94e2e53a98ab70a8ab" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "ring", "sha2", ] @@ -2891,18 +2806,45 @@ dependencies = [ ] [[package]] -name = "ethereum_ssz_derive" -version = "0.9.1" +name = "ethereum_ssz" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" +checksum = "368a4a4e4273b0135111fe9464e35465067766a8f664615b5a86338b73864407" dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.14.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cd82c68120c89361e1a457245cf212f7d9f541bffaffed530c8f2d54a160b2" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2914,9 +2856,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" @@ -2983,6 +2925,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" @@ -2990,7 +2943,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", ] @@ -3053,6 +3006,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" @@ -3239,7 +3198,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "libgit2-sys", "log", @@ -3298,16 +3257,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" @@ -3331,7 +3280,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -3361,9 +3310,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" @@ -3372,7 +3318,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", - "equivalent", "foldhash 0.1.5", ] @@ -3389,13 +3334,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]] @@ -3451,7 +3402,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.2", + "rand 0.9.4", "ring", "serde", "thiserror 2.0.18", @@ -3474,7 +3425,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "resolv-conf", "serde", "smallvec", @@ -3576,9 +3527,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", @@ -3591,7 +3542,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -3599,21 +3549,18 @@ 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", "hyper-util", "log", "rustls", - "rustls-native-certs", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.6", ] [[package]] @@ -3646,7 +3593,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2", "tokio", "tower-service", "tracing", @@ -3678,12 +3625,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", @@ -3691,9 +3639,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", @@ -3704,9 +3652,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", @@ -3718,15 +3666,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", @@ -3738,15 +3686,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", @@ -3858,13 +3806,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", ] @@ -3884,7 +3832,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "inotify-sys", "libc", ] @@ -3910,9 +3858,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", @@ -3923,9 +3871,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", @@ -3933,19 +3881,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]] @@ -3956,9 +3905,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -3999,9 +3948,26 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jemalloc_pprof" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8a0d44c349cfe2654897fadcb9de4f0bfbf48288ec344f700b2bd59f152dd209" +dependencies = [ + "anyhow", + "libc", + "mappings", + "once_cell", + "pprof_util", + "tempfile", + "tikv-jemalloc-ctl", + "tokio", + "tracing", +] [[package]] name = "jni" @@ -4012,7 +3978,7 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", @@ -4021,9 +3987,31 @@ dependencies = [ [[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" @@ -4037,10 +4025,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -4078,7 +4068,7 @@ dependencies = [ "pin-project", "rustls", "rustls-pki-types", - "rustls-platform-verifier", + "rustls-platform-verifier 0.5.3", "soketto", "thiserror 2.0.18", "tokio", @@ -4104,7 +4094,7 @@ dependencies = [ "jsonrpsee-types", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.4", "rustc-hash", "serde", "serde_json", @@ -4130,7 +4120,7 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "rustls", - "rustls-platform-verifier", + "rustls-platform-verifier 0.5.3", "serde", "serde_json", "thiserror 2.0.18", @@ -4247,25 +4237,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" @@ -4300,9 +4316,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[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" @@ -4323,7 +4339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -4357,28 +4373,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 2.11.1", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", +] + +[[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", @@ -4386,6 +4417,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 2.11.1", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -4402,12 +4442,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" @@ -4416,9 +4450,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" @@ -4444,18 +4478,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] - -[[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", ] @@ -4487,9 +4512,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" @@ -4517,6 +4542,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" @@ -4580,7 +4618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda" dependencies = [ "base64 0.22.1", - "indexmap 2.13.0", + "indexmap 2.14.0", "metrics", "metrics-util", "quanta", @@ -4598,7 +4636,7 @@ dependencies = [ "mach2 0.6.0", "metrics", "once_cell", - "procfs 0.18.0", + "procfs", "rlimit", "windows 0.62.2", ] @@ -4614,7 +4652,7 @@ dependencies = [ "hashbrown 0.16.1", "metrics", "quanta", - "rand 0.9.2", + "rand 0.9.4", "rand_xoshiro", "sketches-ddsketch", ] @@ -4635,21 +4673,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" @@ -4668,9 +4691,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", @@ -4680,9 +4703,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", @@ -4690,20 +4713,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", @@ -4775,13 +4798,14 @@ dependencies = [ "async-trait", "auto_impl", "jsonrpsee", + "metrics", "morph-chainspec", "morph-payload-types", "morph-primitives", "parking_lot", + "reth-metrics", "reth-node-api", "reth-payload-builder", - "reth-payload-primitives", "reth-primitives-traits", "reth-provider", "reth-rpc-api", @@ -4790,6 +4814,52 @@ dependencies = [ "tracing", ] +[[package]] +name = "morph-engine-tree-ext" +version = "0.2.2" +dependencies = [ + "alloy-consensus", + "alloy-eip7928", + "alloy-eips", + "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.2.2" @@ -4831,12 +4901,13 @@ dependencies = [ "alloy-signer", "alloy-signer-local", "clap", - "dashmap 6.1.0", + "dashmap", "eyre", "jsonrpsee", "morph-chainspec", "morph-consensus", "morph-engine-api", + "morph-engine-tree-ext", "morph-evm", "morph-payload-builder", "morph-payload-types", @@ -4864,6 +4935,7 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "reth-trie", + "reth-trie-db", "serde", "serde_json", "tokio", @@ -4880,6 +4952,7 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", + "metrics", "morph-chainspec", "morph-evm", "morph-payload-types", @@ -4887,7 +4960,9 @@ dependencies = [ "reth-basic-payload-builder", "reth-chainspec", "reth-evm", + "reth-execution-cache", "reth-execution-types", + "reth-metrics", "reth-payload-builder", "reth-payload-primitives", "reth-payload-util", @@ -4895,6 +4970,7 @@ dependencies = [ "reth-revm", "reth-storage-api", "reth-transaction-pool", + "reth-trie-parallel", "revm", "thiserror 2.0.18", "tracing", @@ -4911,9 +4987,8 @@ dependencies = [ "alloy-rpc-types-engine", "alloy-serde", "morph-primitives", - "rand 0.8.5", + "rand 0.8.6", "reth-ethereum-primitives", - "reth-payload-builder", "reth-payload-primitives", "reth-primitives-traits", "serde", @@ -4988,6 +5063,7 @@ version = "0.2.2" dependencies = [ "alloy-consensus", "alloy-eips", + "alloy-evm", "alloy-network", "alloy-primitives", "alloy-rpc-types-eth", @@ -5034,10 +5110,13 @@ dependencies = [ "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", @@ -5079,14 +5158,23 @@ dependencies = [ [[package]] name = "multihash" -version = "0.19.3" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +checksum = "89ace881e3f514092ce9efbcb8f413d0ad9763860b828981c2de51ddc666936c" dependencies = [ - "core2", + "no_std_io2", "unsigned-varint", ] +[[package]] +name = "no_std_io2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3564ce7035b1e4778d8cb6cacebb5d766b5e8fe5a75b9e441e33fb61a872c6" +dependencies = [ + "memchr", +] + [[package]] name = "nom" version = "7.1.3" @@ -5103,7 +5191,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "fsevent-sys", "inotify", "kqueue", @@ -5121,7 +5209,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -5177,9 +5265,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" @@ -5234,9 +5322,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", @@ -5244,9 +5332,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", @@ -5279,136 +5367,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 2.11.1", ] [[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" @@ -5446,14 +5437,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", @@ -5461,7 +5452,7 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest", + "reqwest 0.12.28", "thiserror 2.0.18", "tokio", "tonic", @@ -5498,16 +5489,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" @@ -5536,7 +5521,6 @@ version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ - "arbitrary", "arrayvec", "bitvec", "byte-slice-cast", @@ -5580,7 +5564,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5722,9 +5706,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" @@ -5748,7 +5732,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", ] @@ -5761,9 +5745,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", ] @@ -5774,6 +5758,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" @@ -5829,7 +5826,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]] @@ -5863,40 +5860,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 2.11.1", "chrono", - "hex", + "flate2", + "procfs-core", + "rustix", ] [[package]] @@ -5905,21 +5879,22 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", + "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 2.11.1", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -5972,17 +5947,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" @@ -6026,7 +5990,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2", "thiserror 2.0.18", "tokio", "tracing", @@ -6039,10 +6003,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", @@ -6063,7 +6028,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.60.2", ] @@ -6097,9 +6062,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", @@ -6109,9 +6074,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", @@ -6181,29 +6146,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 = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +dependencies = [ + "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 = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ - "bitflags 2.11.0", - "cassowary", + "bitflags 2.11.1", "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 2.11.1", + "hashbrown 0.16.1", + "indoc", + "instability", + "itertools 0.14.0", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width", ] [[package]] @@ -6212,14 +6219,14 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[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", @@ -6247,16 +6254,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -6270,17 +6277,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" @@ -6335,6 +6331,40 @@ name = "reqwest" version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", @@ -6353,8 +6383,8 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-native-certs", "rustls-pki-types", + "rustls-platform-verifier 0.6.2", "serde", "serde_json", "serde_urlencoded", @@ -6370,7 +6400,6 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.6", ] [[package]] @@ -6381,8 +6410,8 @@ 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6391,6 +6420,7 @@ dependencies = [ "futures-util", "metrics", "reth-chain-state", + "reth-execution-cache", "reth-metrics", "reth-payload-builder", "reth-payload-builder-primitives", @@ -6399,14 +6429,16 @@ 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6417,7 +6449,8 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.4", + "rayon", "reth-chainspec", "reth-errors", "reth-ethereum-primitives", @@ -6436,8 +6469,8 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-chains", "alloy-consensus", @@ -6456,8 +6489,8 @@ dependencies = [ [[package]] name = "reth-cli" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-genesis", "clap", @@ -6465,13 +6498,12 @@ 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-chains", "alloy-consensus", @@ -6479,9 +6511,10 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "backon", + "blake3", "clap", "comfy-table", - "crossterm 0.28.1", + "crossterm", "eyre", "fdlimit", "futures", @@ -6490,8 +6523,10 @@ dependencies = [ "itertools 0.14.0", "lz4", "metrics", + "parking_lot", "ratatui", - "reqwest", + "rayon", + "reqwest 0.13.2", "reth-chainspec", "reth-cli", "reth-cli-runner", @@ -6526,13 +6561,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", @@ -6548,8 +6585,8 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "reth-tasks", "tokio", @@ -6558,25 +6595,28 @@ dependencies = [ [[package]] name = "reth-cli-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "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.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96e584e01478c951911946a7864f18e967c1cd90965e136e2d1b51aa3da9126" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6586,7 +6626,7 @@ dependencies = [ "arbitrary", "bytes", "modular-bitfield", - "op-alloy-consensus", + "parity-scale-codec", "reth-codecs-derive", "reth-zstd-compressors", "serde", @@ -6595,8 +6635,9 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c342ae46f5a886b8bf506205b9501b1032b896defd0f4f156edb423007fef880" dependencies = [ "proc-macro2", "quote", @@ -6605,8 +6646,8 @@ dependencies = [ [[package]] name = "reth-config" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "eyre", "humantime-serde", @@ -6621,8 +6662,8 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6634,11 +6675,12 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", + "alloy-primitives", "reth-chainspec", "reth-consensus", "reth-primitives-traits", @@ -6646,8 +6688,8 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6660,7 +6702,7 @@ dependencies = [ "derive_more", "eyre", "futures", - "reqwest", + "reqwest 0.13.2", "reth-node-api", "reth-primitives-traits", "reth-tracing", @@ -6672,8 +6714,8 @@ dependencies = [ [[package]] name = "reth-db" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "derive_more", @@ -6681,6 +6723,7 @@ dependencies = [ "metrics", "page_size", "parking_lot", + "quanta", "reth-db-api", "reth-fs-util", "reth-libmdbx", @@ -6690,31 +6733,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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", @@ -6726,8 +6768,8 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -6756,8 +6798,8 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "alloy-primitives", @@ -6771,8 +6813,8 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6780,7 +6822,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", @@ -6796,8 +6838,8 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -6807,7 +6849,7 @@ dependencies = [ "futures", "itertools 0.14.0", "metrics", - "rand 0.9.2", + "rand 0.9.4", "reth-chainspec", "reth-ethereum-forks", "reth-metrics", @@ -6820,15 +6862,15 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", + "dashmap", "data-encoding", "enr", "hickory-resolver", "linked_hash_set", - "parking_lot", "reth-ethereum-forks", "reth-network-peers", "reth-tokio-util", @@ -6844,8 +6886,8 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6879,8 +6921,8 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6915,7 +6957,6 @@ dependencies = [ "reth-payload-builder", "reth-payload-builder-primitives", "reth-payload-primitives", - "reth-primitives", "reth-primitives-traits", "reth-provider", "reth-rpc-api", @@ -6937,8 +6978,8 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "aes", "alloy-primitives", @@ -6952,7 +6993,7 @@ dependencies = [ "futures", "hmac", "pin-project", - "rand 0.8.5", + "rand 0.8.6", "reth-network-peers", "secp256k1 0.30.0", "sha2", @@ -6965,8 +7006,8 @@ dependencies = [ [[package]] name = "reth-engine-local" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6988,8 +7029,8 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7011,46 +7052,22 @@ 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", - "alloy-eip7928 0.1.0", + "alloy-eip7928", "alloy-eips", "alloy-evm", "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", "crossbeam-channel", - "dashmap 6.1.0", "derive_more", "futures", "metrics", - "mini-moka", "moka", "parking_lot", "rayon", @@ -7062,6 +7079,7 @@ dependencies = [ "reth-errors", "reth-ethereum-primitives", "reth-evm", + "reth-execution-cache", "reth-execution-types", "reth-metrics", "reth-network-p2p", @@ -7078,13 +7096,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", @@ -7092,8 +7110,8 @@ dependencies = [ [[package]] name = "reth-engine-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -7120,29 +7138,29 @@ dependencies = [ [[package]] name = "reth-era" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-rlp", - "ethereum_ssz", - "ethereum_ssz_derive", + "ethereum_ssz 0.10.3", + "ethereum_ssz_derive 0.10.3", "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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "bytes", "eyre", "futures-util", - "reqwest", + "reqwest 0.13.2", "reth-era", "reth-fs-util", "sha2", @@ -7151,8 +7169,8 @@ dependencies = [ [[package]] name = "reth-era-utils" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7173,8 +7191,8 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -7184,8 +7202,8 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7212,8 +7230,8 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-chains", "alloy-consensus", @@ -7233,8 +7251,8 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "clap", "eyre", @@ -7249,14 +7267,15 @@ 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7271,26 +7290,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -7302,8 +7319,8 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7317,6 +7334,7 @@ dependencies = [ "reth-ethereum-primitives", "reth-evm", "reth-evm-ethereum", + "reth-execution-cache", "reth-payload-builder", "reth-payload-builder-primitives", "reth-payload-primitives", @@ -7331,28 +7349,22 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", "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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "rayon", "reth-db-api", @@ -7361,8 +7373,8 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7385,15 +7397,14 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", "alloy-primitives", "alloy-rpc-types-engine", - "derive_more", "reth-chainspec", "reth-ethereum-forks", "reth-ethereum-primitives", @@ -7404,10 +7415,28 @@ dependencies = [ "revm", ] +[[package]] +name = "reth-execution-cache" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" +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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-evm", "alloy-primitives", @@ -7419,13 +7448,14 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", "alloy-primitives", + "alloy-rlp", "derive_more", "reth-ethereum-primitives", "reth-primitives-traits", @@ -7437,8 +7467,8 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7475,8 +7505,8 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7489,8 +7519,8 @@ dependencies = [ [[package]] name = "reth-fs-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "serde", "serde_json", @@ -7499,8 +7529,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7527,8 +7557,8 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "bytes", "futures", @@ -7547,12 +7577,13 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", - "dashmap 6.1.0", + "crossbeam-queue", + "dashmap", "derive_more", "parking_lot", "reth-mdbx-sys", @@ -7563,17 +7594,17 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "futures", "metrics", @@ -7584,8 +7615,8 @@ dependencies = [ [[package]] name = "reth-net-banlist" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "ipnet", @@ -7593,12 +7624,12 @@ dependencies = [ [[package]] name = "reth-net-nat" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "futures-util", "if-addrs", - "reqwest", + "reqwest 0.13.2", "serde_with", "thiserror 2.0.18", "tokio", @@ -7607,8 +7638,8 @@ dependencies = [ [[package]] name = "reth-network" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7624,8 +7655,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", @@ -7637,6 +7668,7 @@ dependencies = [ "reth-eth-wire-types", "reth-ethereum-forks", "reth-ethereum-primitives", + "reth-evm-ethereum", "reth-fs-util", "reth-metrics", "reth-net-banlist", @@ -7663,8 +7695,8 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7688,8 +7720,8 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7711,8 +7743,8 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7726,8 +7758,8 @@ dependencies = [ [[package]] name = "reth-network-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -7740,8 +7772,8 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "anyhow", "bincode", @@ -7757,8 +7789,8 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -7781,8 +7813,8 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7809,7 +7841,6 @@ dependencies = [ "reth-downloaders", "reth-engine-local", "reth-engine-primitives", - "reth-engine-service", "reth-engine-tree", "reth-engine-util", "reth-evm", @@ -7840,6 +7871,7 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "reth-transaction-pool", + "reth-trie-db", "secp256k1 0.30.0", "serde_json", "tokio", @@ -7849,8 +7881,8 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7863,7 +7895,7 @@ dependencies = [ "futures", "humantime", "ipnet", - "rand 0.9.2", + "rand 0.9.4", "reth-chainspec", "reth-cli-util", "reth-config", @@ -7880,7 +7912,6 @@ dependencies = [ "reth-network-p2p", "reth-network-peers", "reth-primitives-traits", - "reth-provider", "reth-prune-types", "reth-rpc-convert", "reth-rpc-eth-types", @@ -7888,13 +7919,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", @@ -7905,8 +7936,8 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "alloy-network", @@ -7943,8 +7974,8 @@ dependencies = [ [[package]] name = "reth-node-ethstats" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7967,8 +7998,8 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7991,22 +8022,28 @@ dependencies = [ [[package]] name = "reth-node-metrics" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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.2", + "reth-fs-util", "reth-metrics", "reth-tasks", + "tempfile", + "tikv-jemalloc-ctl", "tokio", "tower", "tracing", @@ -8014,8 +8051,8 @@ dependencies = [ [[package]] name = "reth-node-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "reth-chainspec", "reth-db-api", @@ -8024,37 +8061,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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", @@ -8062,8 +8087,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "pin-project", "reth-payload-primitives", @@ -8074,16 +8099,16 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "auto_impl", "either", - "op-alloy-rpc-types-engine", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -8091,14 +8116,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8107,31 +8133,19 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03650bb740d1bca0d974c007248177ae7a7e38c50c9f46eb02292c5d9bc01252" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8141,15 +8155,15 @@ dependencies = [ "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", @@ -8157,20 +8171,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", + "alloy-genesis", "alloy-primitives", "alloy-rpc-types-engine", - "dashmap 6.1.0", "eyre", "itertools 0.14.0", "metrics", @@ -8186,6 +8199,7 @@ dependencies = [ "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-execution-types", + "reth-fs-util", "reth-metrics", "reth-nippy-jar", "reth-node-types", @@ -8195,19 +8209,21 @@ 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8225,6 +8241,7 @@ dependencies = [ "reth-prune-types", "reth-stages-types", "reth-static-file-types", + "reth-storage-api", "reth-tokio-util", "rustc-hash", "thiserror 2.0.18", @@ -8234,8 +8251,8 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "arbitrary", @@ -8243,16 +8260,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-debug", "reth-primitives-traits", "reth-storage-api", "reth-storage-errors", @@ -8262,12 +8282,12 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eip7928 0.1.0", + "alloy-eip7928", "alloy-eips", "alloy-evm", "alloy-genesis", @@ -8291,13 +8311,9 @@ dependencies = [ "derive_more", "dyn-clone", "futures", - "http", - "http-body", - "hyper", "itertools 0.14.0", "jsonrpsee", "jsonrpsee-types", - "jsonwebtoken", "parking_lot", "pin-project", "reth-chain-state", @@ -8326,6 +8342,7 @@ dependencies = [ "reth-rpc-server-types", "reth-storage-api", "reth-tasks", + "reth-tracing", "reth-transaction-pool", "reth-trie-common", "revm", @@ -8337,17 +8354,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ - "alloy-eip7928 0.1.0", + "alloy-eip7928", "alloy-eips", "alloy-genesis", "alloy-json-rpc", @@ -8375,8 +8391,8 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-network", "alloy-provider", @@ -8394,9 +8410,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", @@ -8416,8 +8434,8 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-evm", @@ -8425,23 +8443,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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "async-trait", "jsonrpsee-core", @@ -8467,11 +8485,12 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-dyn-abi", + "alloy-eip7928", "alloy-eips", "alloy-evm", "alloy-json-rpc", @@ -8505,14 +8524,15 @@ 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8529,8 +8549,8 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "metrics", - "rand 0.9.2", - "reqwest", + "rand 0.9.4", + "reqwest 0.13.2", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -8559,8 +8579,8 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-rpc-types-engine", "http", @@ -8573,8 +8593,8 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8584,24 +8604,40 @@ dependencies = [ "reth-errors", "reth-network-api", "serde", - "strum 0.27.2", + "strum", +] + +[[package]] +name = "reth-rpc-traits" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9230acfd70f7f27bc52da3f397e1896432ce160f9bd460d9788f1a28d61588c" +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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", - "bincode", + "alloy-rlp", "eyre", "futures-util", "itertools 0.14.0", "num-traits", + "page_size", "rayon", - "reqwest", + "reqwest 0.13.2", "reth-chainspec", "reth-codecs", "reth-config", @@ -8617,6 +8653,7 @@ dependencies = [ "reth-execution-types", "reth-exex", "reth-fs-util", + "reth-libmdbx", "reth-network-p2p", "reth-primitives-traits", "reth-provider", @@ -8627,6 +8664,7 @@ dependencies = [ "reth-static-file-types", "reth-storage-api", "reth-storage-errors", + "reth-tasks", "reth-testing-utils", "reth-trie", "reth-trie-db", @@ -8638,8 +8676,8 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8647,6 +8685,7 @@ dependencies = [ "auto_impl", "futures-util", "metrics", + "reth-codecs", "reth-consensus", "reth-errors", "reth-metrics", @@ -8665,8 +8704,8 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "arbitrary", @@ -8679,8 +8718,8 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "parking_lot", @@ -8699,21 +8738,23 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8736,33 +8777,38 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-eips", "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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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", @@ -8770,15 +8816,15 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", "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", @@ -8786,8 +8832,8 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "tokio", "tokio-stream", @@ -8796,8 +8842,8 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "clap", "eyre", @@ -8813,8 +8859,8 @@ dependencies = [ [[package]] name = "reth-tracing-otlp" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "clap", "eyre", @@ -8830,8 +8876,8 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8839,23 +8885,26 @@ dependencies = [ "alloy-rlp", "aquamarine", "auto_impl", - "bitflags 2.11.0", + "bitflags 2.11.1", "futures-util", "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", @@ -8871,8 +8920,8 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8897,8 +8946,8 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8924,48 +8973,56 @@ dependencies = [ [[package]] name = "reth-trie-db" -version = "1.10.0" -source = "git+https://github.com/morph-l2/reth?rev=1b0702546633c259306017717b2938f14adfe329#1b0702546633c259306017717b2938f14adfe329" +version = "2.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" 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.0.0" +source = "git+https://github.com/paradigmxyz/reth?tag=v2.0.0#eb4c15e5e36d8776d46629beae4c0a69af7ab04f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8977,41 +9034,27 @@ dependencies = [ "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.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a621aef55fe4da8935abede9d1d105f227bcb673f212b3575a748a6a2f8f688e" dependencies = [ "zstd", ] [[package]] name = "revm" -version = "33.1.0" +version = "36.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c85ed0028f043f87b3c88d4a4cb6f0a76440085523b6a8afe5ff003cf418054" +checksum = "b0abc15d09cd211e9e73410ada10134069c794d4bcdb787dfc16a1bf0939849c" dependencies = [ "revm-bytecode", "revm-context", @@ -9028,9 +9071,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "7.1.1" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c6b5e6e8dd1e28a4a60e5f46615d4ef0809111c9e63208e55b5c7058200fb0" +checksum = "e86e468df3cf5cf59fa7ef71a3e9ccabb76bb336401ea2c0674f563104cf3c5e" dependencies = [ "bitvec", "phf", @@ -9040,9 +9083,9 @@ dependencies = [ [[package]] name = "revm-context" -version = "12.1.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f038f0c9c723393ac897a5df9140b21cfa98f5753a2cb7d0f28fa430c4118abf" +checksum = "9eb1f0a76b14d684a444fc52f7bf6b7564bf882599d91ee62e76d602e7a187c7" dependencies = [ "bitvec", "cfg-if", @@ -9057,9 +9100,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "13.1.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431c9a14e4ef1be41ae503708fd02d974f80ef1f2b6b23b5e402e8d854d1b225" +checksum = "fc256b27743e2912ca16899568e6652a372eb5d1d573e6edb16c7836b16cf487" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -9073,9 +9116,9 @@ dependencies = [ [[package]] name = "revm-database" -version = "9.0.6" +version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980d8d6bba78c5dd35b83abbb6585b0b902eb25ea4448ed7bfba6283b0337191" +checksum = "2c0a7d6da41061f2c50f99a2632571026b23684b5449ff319914151f4449b6c8" dependencies = [ "alloy-eips", "revm-bytecode", @@ -9087,22 +9130,23 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "8.0.5" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cce03e3780287b07abe58faf4a7f5d8be7e81321f93ccf3343c8f7755602bae" +checksum = "bd497a38a79057b94a049552cb1f925ad15078bc1a479c132aeeebd1d2ccc768" dependencies = [ "auto_impl", "either", "revm-primitives", "revm-state", "serde", + "thiserror 2.0.18", ] [[package]] name = "revm-handler" -version = "14.1.0" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d44f8f6dbeec3fecf9fe55f78ef0a758bdd92ea46cd4f1ca6e2a946b32c367f3" +checksum = "9f1eed729ca9b228ae98688f352235871e9b8be3d568d488e4070f64c56e9d3d" dependencies = [ "auto_impl", "derive-where", @@ -9119,9 +9163,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "14.1.0" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5617e49216ce1ca6c8826bcead0386bc84f49359ef67cde6d189961735659f93" +checksum = "cbf5102391706513689f91cb3cb3d97b5f13a02e8647e6e9cb7620877ef84847" dependencies = [ "auto_impl", "either", @@ -9137,9 +9181,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.33.2" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01def7351cd9af844150b8e88980bcd11304f33ce23c3d7c25f2a8dab87c1345" +checksum = "9487362b728f80dd2033ef5f4d0688453435bbe7caa721fa7e3b8fa25d89242b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -9155,9 +9199,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "31.1.0" +version = "34.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec36405f7477b9dccdc6caa3be19adf5662a7a0dffa6270cdb13a090c077e5" +checksum = "cf22f80612bb8f58fd1f578750281f2afadb6c93835b14ae6a4d6b75ca26f445" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -9168,9 +9212,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "31.0.0" +version = "32.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a62958af953cc4043e93b5be9b8497df84cc3bd612b865c49a7a7dfa26a84e2" +checksum = "e2ec11f45deec71e4945e1809736bb20d454285f9167ab53c5159dae1deb603f" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -9186,16 +9230,15 @@ dependencies = [ "p256", "revm-primitives", "ripemd", - "rug", "secp256k1 0.31.1", "sha2", ] [[package]] name = "revm-primitives" -version = "21.0.2" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e161db429d465c09ba9cbff0df49e31049fe6b549e28eb0b7bd642fcbd4412" +checksum = "4bcfb5ce6cf18b118932bcdb7da05cd9c250f2cb9f64131396b55f3fe3537c35" dependencies = [ "alloy-primitives", "num_enum", @@ -9205,11 +9248,12 @@ dependencies = [ [[package]] name = "revm-state" -version = "8.1.1" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8be953b7e374dbdea0773cf360debed8df394ea8d82a8b240a6b5da37592fc" +checksum = "d29404707763da607e5d6e4771cb203998c28159279c2f64cc32de08d2814651" dependencies = [ - "bitflags 2.11.0", + "alloy-eip7928", + "bitflags 2.11.1", "revm-bytecode", "revm-primitives", "serde", @@ -9241,9 +9285,9 @@ dependencies = [ [[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" @@ -9294,14 +9338,24 @@ dependencies = [ [[package]] name = "roaring" -version = "0.10.12" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e8d2cfa184d94d0726d650a9f4a1be7f9b76ac9fdb954219878dc00c1c1e7b" +checksum = "8ba9ce64a8f45d7fc86358410bb1a82e8c987504c0d4900e9141d69a9f26c885" 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" @@ -9317,18 +9371,6 @@ 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" @@ -9349,8 +9391,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", @@ -9366,11 +9408,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]] @@ -9394,20 +9436,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]] @@ -9416,19 +9445,20 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -9452,9 +9482,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", @@ -9481,6 +9511,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "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.61.2", +] + [[package]] name = "rustls-platform-verifier-android" version = "0.1.1" @@ -9489,10 +9540,11 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -9603,7 +9655,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", ] @@ -9615,7 +9667,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", ] @@ -9643,7 +9695,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "core-foundation-sys", "libc", @@ -9671,9 +9723,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", @@ -9736,7 +9788,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", @@ -9746,11 +9798,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]] @@ -9767,15 +9819,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" 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", @@ -9786,11 +9838,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.17.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.117", @@ -9813,7 +9865,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", ] @@ -9824,15 +9876,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", @@ -9840,9 +9892,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", @@ -9850,20 +9902,11 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shellexpand" -version = "3.1.2" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "dirs", + "lazy_static", ] [[package]] @@ -9915,9 +9958,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simple_asn1" @@ -9937,21 +9980,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" -[[package]] -name = "skeptic" -version = "0.13.7" -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", -] - [[package]] name = "sketches-ddsketch" version = "0.3.1" @@ -9964,6 +9992,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" @@ -9980,16 +10017,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" @@ -10012,7 +10039,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand 0.8.6", "sha1", ] @@ -10044,35 +10071,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]] @@ -10093,6 +10098,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" @@ -10149,15 +10160,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]] @@ -10192,7 +10204,7 @@ dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -10236,6 +10248,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 2.11.1", + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.61.3", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -10254,6 +10280,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" @@ -10289,9 +10346,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", @@ -10299,9 +10356,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", ] @@ -10314,9 +10371,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -10324,16 +10381,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", @@ -10364,9 +10421,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", @@ -10396,74 +10453,63 @@ 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" @@ -10511,7 +10557,7 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap 2.13.0", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -10530,7 +10576,7 @@ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", "base64 0.22.1", - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-core", "futures-util", @@ -10579,11 +10625,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", @@ -10732,7 +10779,7 @@ checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" dependencies = [ "alloy-primitives", "ethereum_hashing", - "ethereum_ssz", + "ethereum_ssz 0.9.1", "smallvec", "typenum", ] @@ -10759,12 +10806,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" @@ -10773,16 +10814,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", @@ -10790,11 +10831,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" @@ -10846,32 +10893,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" @@ -10934,9 +10975,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", @@ -10962,7 +11003,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", @@ -11049,11 +11090,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]] @@ -11062,14 +11103,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.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", @@ -11080,23 +11121,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" 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.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11104,9 +11141,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", @@ -11117,9 +11154,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] @@ -11141,16 +11178,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", @@ -11165,10 +11202,10 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver 1.0.27", + "indexmap 2.14.0", + "semver 1.0.28", ] [[package]] @@ -11187,9 +11224,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" dependencies = [ "js-sys", "wasm-bindgen", @@ -11211,14 +11248,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", ] @@ -11229,14 +11266,14 @@ 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", ] @@ -11280,12 +11317,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]] @@ -11294,10 +11334,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]] @@ -11311,14 +11360,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]] @@ -11327,33 +11377,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]] @@ -11369,9 +11419,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", @@ -11379,15 +11429,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" @@ -11395,6 +11440,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" @@ -11402,16 +11457,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]] @@ -11420,34 +11486,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]] @@ -11483,7 +11549,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]] @@ -11501,21 +11567,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" @@ -11538,7 +11589,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", @@ -11549,13 +11600,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]] @@ -11564,12 +11624,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" @@ -11588,12 +11642,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" @@ -11612,12 +11660,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" @@ -11648,12 +11690,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" @@ -11672,12 +11708,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" @@ -11696,12 +11726,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" @@ -11720,12 +11744,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" @@ -11748,13 +11766,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]] @@ -11766,6 +11783,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" @@ -11785,7 +11808,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -11815,8 +11838,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -11835,9 +11858,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", @@ -11847,9 +11870,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" @@ -11886,7 +11909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.4", + "rustix", ] [[package]] @@ -11897,9 +11920,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", @@ -11908,9 +11931,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", @@ -11920,18 +11943,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", @@ -11940,18 +11963,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", @@ -11981,9 +12004,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", @@ -11992,9 +12015,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", @@ -12003,9 +12026,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 901cfcb..7357611 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace.package] version = "0.2.2" 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", @@ -45,6 +46,7 @@ all = "warn" 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 } @@ -54,86 +56,93 @@ 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-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.0.0" } +reth-chain-state = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-cli-util = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-codecs = { version = "0.1.0", default-features = false } +reth-codecs-derive = "0.1.0" +reth-consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-db-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-e2e-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-engine-local = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-engine-tree = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-eth-wire-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-ethereum-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-ethereum-consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-ethereum-engine-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0", default-features = false } +reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-metrics = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-node-core = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-node-metrics = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-payload-util = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-primitives-traits = { version = "=0.1.0", default-features = false } +reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-tracing = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-trie = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-zstd-compressors = { version = "0.1.0", default-features = false } +reth-execution-cache = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-trie-db = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-trie-parallel = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.0" } +reth-trie-sparse = { git = "https://github.com/paradigmxyz/reth", tag = "v2.0.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.0.0", features = [ "std", "optional-checks", ] } -revm = { version = "33.1.0", features = [ +revm = { version = "36.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 = { version = "1.8.2", default-features = false } +alloy-consensus = { version = "1.8.2", default-features = false } +alloy-contract = { version = "1.8.2", default-features = false } +alloy-eip7928 = { version = "0.3.0", default-features = false } +alloy-eips = { version = "1.8.2", default-features = false } +alloy-evm = { version = "0.30.0", default-features = false } +alloy-genesis = "1.8.2" 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-network = { version = "1.8.2", default-features = false } +alloy-primitives = { version = "1.5.6", default-features = false } +alloy-provider = { version = "1.8.2", default-features = false } +alloy-rlp = "0.3.13" +alloy-rpc-types-engine = "1.8.2" +alloy-rpc-types-eth = { version = "1.8.2" } +alloy-serde = "1.8.2" +alloy-signer = "1.8.2" +alloy-signer-local = "1.8.2" +alloy-sol-types = { version = "1.5.6", default-features = false } +alloy-transport = "1.8.2" +alloy-chains = { version = "0.2.33", default-features = false } +crossbeam-channel = "0.5.13" +revm-primitives = { version = "22.1.0", default-features = false } arbitrary = { version = "1.3", features = ["derive"] } async-lock = "3.4.1" async-trait = "0.1" diff --git a/Dockerfile b/Dockerfile index 856f8bb..799b396 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,8 +19,10 @@ COPY --from=planner /app/recipe.json recipe.json ARG BUILD_PROFILE=release ENV BUILD_PROFILE=$BUILD_PROFILE -# Extra Cargo flags -ARG RUSTFLAGS="" +# Extra Cargo flags. Default target-cpu=x86-64-v3 matches upstream reth +# (Haswell / Excavator+) for AVX2 / BMI2 / POPCNT on trie/keccak hot paths. +# Override with `--build-arg RUSTFLAGS=""` for older CPUs. +ARG RUSTFLAGS="-C target-cpu=x86-64-v3" ENV RUSTFLAGS="$RUSTFLAGS" # Build dependencies (cached layer) diff --git a/README.md b/README.md index 911aaef..82b850f 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 4512cf2..c9d5efe 100644 --- a/bin/morph-reth/Cargo.toml +++ b/bin/morph-reth/Cargo.toml @@ -32,3 +32,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 624da7f..ad2c5f9 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/consensus/src/validation.rs b/crates/consensus/src/validation.rs index e2c7803..8623cad 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, }; @@ -346,6 +346,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 +367,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(()) } @@ -624,6 +636,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 +1930,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 d47aaf4..e52b121 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 9e40c84..7966b14 100644 --- a/crates/engine-api/src/builder.rs +++ b/crates/engine-api/src/builder.rs @@ -10,21 +10,21 @@ 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 parking_lot::RwLock; -use reth_payload_builder::PayloadBuilderHandle; -use reth_payload_primitives::{EngineApiMessageVersion, PayloadBuilderAttributes}; +use reth_node_api::ConsensusEngineEvent; +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 @@ -94,7 +94,20 @@ struct BlockTagCache { } impl EngineStateTracker { - /// Records a canonical head hint from a locally successful FCU call. + /// Unconditionally writes the canonical head hint. + /// + /// Called from two sources with different trust levels: + /// 1. The local FCU sync path, immediately after `fork_choice_updated` + /// returns Ok — the caller knows the new head was just committed and + /// must overwrite the tracker so the next engine-API call sees it + /// before reth's asynchronous event has propagated. + /// 2. [`record_canonical_event_if_authoritative`], after the engine + /// event has been validated against the provider's canonical head. + /// + /// Stale events from reth's unbounded event channel never call into this + /// directly — they go through the helper above so the tracker cannot + /// silently regress when an old event arrives after a newer optimistic + /// write. pub fn record_local_head(&self, number: u64, hash: B256, timestamp: u64) { *self.head.write() = Some(InMemoryHead { number, @@ -103,18 +116,6 @@ impl EngineStateTracker { }); } - /// 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() } @@ -141,6 +142,57 @@ impl EngineStateTracker { } } +/// Apply a [`ConsensusEngineEvent::CanonicalChainCommitted`] event to the +/// tracker only if it still describes the provider's current canonical head. +/// +/// reth's engine event stream is FIFO but unbounded +/// (`UnboundedSender`). Under high import throughput or +/// transient backpressure the listener task can lag, and a stale event from a +/// prior canonical head can arrive after the FCU sync path has already +/// recorded a newer head via [`EngineStateTracker::record_local_head`]. +/// Treating every event as authoritative would let the tracker silently +/// regress and cause the next `engine_assembleL2Block` to fail with +/// `DiscontinuousBlockNumber` until the next event arrives. +/// +/// We resolve the race by treating the provider's `CanonicalInMemoryState` +/// as the single authority. reth synchronously updates that state inside +/// `on_canonical_chain_update` *before* emitting the event, so a fresh event +/// always matches the provider's current canonical head; a mismatch proves +/// the event is stale (or reth has reorged elsewhere) and must be ignored. +/// This stays compatible with future reorg-capable `engine_newL2BlockV2` +/// flows where head numbers can legitimately decrease — the local FCU path +/// still writes [`EngineStateTracker::record_local_head`] unconditionally. +/// +/// `canonical_head` returns `Some((number, hash))` for the provider's current +/// canonical tip, or `None` if it cannot be resolved (e.g. before the chain +/// has been initialized) — in which case the event is dropped. +pub fn record_canonical_event_if_authoritative( + tracker: &EngineStateTracker, + event: &ConsensusEngineEvent, + canonical_head: F, +) where + F: FnOnce() -> Option<(u64, B256)>, +{ + let ConsensusEngineEvent::CanonicalChainCommitted(header, _) = event else { + return; + }; + let Some((canonical_num, canonical_hash)) = canonical_head() else { + return; + }; + if header.number() != canonical_num || canonical_hash != header.hash() { + tracing::trace!( + target: "morph::engine", + event_number = header.number(), + event_hash = %header.hash(), + canonical_number = canonical_num, + canonical_hash = %canonical_hash, + "skipping stale canonical chain event" + ); + return; + } + tracker.record_local_head(header.number(), header.hash(), header.timestamp()); +} + impl RealMorphL2EngineApi { /// Creates a new [`RealMorphL2EngineApi`]. pub fn new( @@ -318,11 +370,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,18 +588,27 @@ 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. @@ -662,17 +719,17 @@ impl RealMorphL2EngineApi { 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 +776,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. @@ -766,21 +823,35 @@ 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")?; + ensure_payload_status_valid(&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); + // + // Order matters for race-safety with the engine-event listener task + // (`record_canonical_event_if_authoritative`): + // 1. provider.set_canonical_head(N) first — once the listener sees + // provider canonical = N, any in-flight stale CanonicalChainCommitted(N-1) + // it processes after this point will fail the provider check and be + // dropped. + // 2. tracker.record_local_head(N) second — if a stale event is already + // mid-processing in the (set_canonical_head, record_local_head) + // window, it can race-write tracker = N-1, but our subsequent local + // write here unconditionally overwrites it back to N. + // Reversing the order leaves a window where the listener writes N-1 *after* + // record_local_head(N) but *before* set_canonical_head(N), regressing the + // tracker until the next event arrives. self.provider .set_canonical_head(SealedHeader::new(header.clone(), data.hash)); + self.engine_state_tracker + .record_local_head(data.number, data.hash, data.timestamp); tracing::info!( target: "morph::engine", @@ -913,31 +984,6 @@ 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 where Provider: HeaderProvider + BlockNumReader, @@ -967,6 +1013,30 @@ impl RealMorphL2EngineApi { } } +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}" + ))) + } + } +} + #[cfg(test)] fn apply_executable_data_overrides( recovered_block: RecoveredBlock, @@ -1031,6 +1101,7 @@ 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; @@ -1041,29 +1112,147 @@ mod tests { RecoveredBlock::new_unhashed(block, Vec::new()) } + fn payload_status(status: PayloadStatusEnum) -> PayloadStatus { + PayloadStatus::from_status(status) + } + #[test] - fn test_engine_state_tracker_updates_head_on_canonical_chain_commit() { - let tracker = EngineStateTracker::default(); - assert!(tracker.current_head().is_none()); + 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(), + } + ))); + } - let header = MorphHeader { + #[test] + fn test_ensure_payload_status_valid_rejects_accepted() { + let err = + ensure_payload_status_valid(&payload_status(PayloadStatusEnum::Accepted), "newPayload") + .unwrap_err(); + + match err { + MorphEngineApiError::ExecutionFailed(msg) => { + assert!(msg.contains("newPayload returned ACCEPTED")); + } + other => panic!("unexpected error: {other}"), + } + } + + /// Build a sealed `MorphHeader` for use as a `CanonicalChainCommitted` payload. + fn sealed_header_at(number: u64, timestamp: u64) -> SealedHeader { + SealedHeader::seal_slow(MorphHeader { inner: Header { - number: 42, - timestamp: 1_700_000_042, + number, + timestamp, ..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()); + }) + } + + /// Wrap a sealed header into a `CanonicalChainCommitted` event. + fn canonical_event( + sealed: &SealedHeader, + ) -> ConsensusEngineEvent { + ConsensusEngineEvent::CanonicalChainCommitted(Box::new(sealed.clone()), Duration::ZERO) + } + + #[test] + fn test_record_canonical_event_writes_when_provider_matches() { + let tracker = EngineStateTracker::default(); + let sealed = sealed_header_at(42, 1_700_000_042); + let event = canonical_event(&sealed); + + record_canonical_event_if_authoritative(&tracker, &event, || { + Some((sealed.number(), sealed.hash())) + }); + + let head = tracker.current_head().expect("head should be set"); + assert_eq!(head.number, sealed.number()); + assert_eq!(head.hash, sealed.hash()); + assert_eq!(head.timestamp, sealed.timestamp()); + } + + #[test] + fn test_record_canonical_event_skips_when_provider_hash_differs() { + // Stale event scenario: tracker already advanced (via local FCU) past the + // event's number, provider canonical now has a different hash. The event + // must not regress the tracked head. + let tracker = EngineStateTracker::default(); + tracker.record_local_head(43, B256::from([0xAA; 32]), 1_700_000_043); + + let stale = sealed_header_at(42, 1_700_000_042); + let event = canonical_event(&stale); + + // Provider still reports the newer head (different hash + number). + record_canonical_event_if_authoritative(&tracker, &event, || { + Some((43, B256::from([0xAA; 32]))) + }); + + let head = tracker.current_head().expect("head must remain"); + assert_eq!(head.number, 43); + assert_eq!(head.hash, B256::from([0xAA; 32])); + } + + #[test] + fn test_record_canonical_event_skips_when_number_matches_but_hash_differs() { + // Reorg-style stale: same number, different hash. Provider has already + // moved on to a different canonical block at the same height. + let tracker = EngineStateTracker::default(); + let new_hash = B256::from([0xBB; 32]); + tracker.record_local_head(7, new_hash, 1_700_000_007); + + let stale = sealed_header_at(7, 1_700_000_007); + // `stale.hash()` is whatever seal_slow computes — guaranteed != 0xBB + // because seal_slow hashes the header bytes. + assert_ne!(stale.hash(), new_hash, "test setup invariant"); + + let event = canonical_event(&stale); + record_canonical_event_if_authoritative(&tracker, &event, || Some((7, new_hash))); + + let head = tracker.current_head().expect("head must remain"); + assert_eq!(head.hash, new_hash); + } + + #[test] + fn test_record_canonical_event_skips_when_provider_unavailable() { + // Provider returns None (e.g. before chain initialization). Event is + // dropped without touching tracker. + let tracker = EngineStateTracker::default(); + let sealed = sealed_header_at(1, 100); + let event = canonical_event(&sealed); + + record_canonical_event_if_authoritative(&tracker, &event, || None); + + assert!(tracker.current_head().is_none(), "tracker must stay empty"); + } + + #[test] + fn test_record_canonical_event_ignores_non_canonical_variants() { + // Only CanonicalChainCommitted updates the tracker; other event variants + // (BlockReceived, ForkchoiceUpdated, ...) leave it untouched even when + // they would otherwise pass the provider check. + let tracker = EngineStateTracker::default(); + let event = ConsensusEngineEvent::BlockReceived(alloy_eips::BlockNumHash { + number: 99, + hash: B256::from([0xCC; 32]), + }); + + record_canonical_event_if_authoritative(&tracker, &event, || { + Some((99, B256::from([0xCC; 32]))) + }); + + assert!(tracker.current_head().is_none()); } #[test] @@ -1287,38 +1476,6 @@ mod tests { assert_eq!(head.timestamp, 200); } - #[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 diff --git a/crates/engine-api/src/lib.rs b/crates/engine-api/src/lib.rs index 931cb27..a83a3d3 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,10 @@ mod builder; mod error; mod metrics; mod rpc; -mod validator; pub use api::MorphL2EngineApi; -pub use builder::{EngineStateTracker, RealMorphL2EngineApi}; +pub use builder::{ + EngineStateTracker, RealMorphL2EngineApi, record_canonical_event_if_authoritative, +}; 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 e56a637..0000000 --- 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 0000000..255e6c8 --- /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 0000000..e3dbd9d --- /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 0000000..fc27722 --- /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 0000000..5b3612e --- /dev/null +++ b/crates/engine-tree-ext/src/payload_validator.rs @@ -0,0 +1,2133 @@ +//! 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::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()), + }; + + // 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)?; + + // Extract the BAL, if valid and available + let block_access_list = ensure_ok!( + input + .block_access_list() + .transpose() + // Eventually gets converted to a `InsertBlockErrorKind::Other` + .map_err(Box::::from) + ) + .map(Arc::new); + + // 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) + let overlay_factory = + OverlayStateProviderFactory::new(self.provider.clone(), self.changeset_cache.clone()) + .with_block_hash(Some(anchor_hash)) + .with_lazy_overlay(lazy_overlay); + + // 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, + block_access_list, + )); + + // 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, + block_access_list: Option>, + ) -> 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(); + + // Use the pre-computed overlay factory for multiproofs + let handle = self.payload_processor.spawn( + env, + txs, + provider_builder, + overlay_factory, + &self.config, + block_access_list, + ); + + // 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, + block_access_list, + ); + + // 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" + ); + + // Extract deferred trie data handles (non-blocking) + let handles: Vec = blocks.iter().map(|b| b.trie_data_handle()).collect(); + + (Some(LazyOverlay::new(anchor_hash, handles)), 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_factory = + OverlayStateProviderFactory::new(self.provider.clone(), self.changeset_cache.clone()) + .with_block_hash(Some(anchor_hash)) + .with_lazy_overlay(lazy_overlay); + + 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 0000000..3ac7b52 --- /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 0000000..7f5d846 --- /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 846fd4e..a9f9cf1 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/block/curie.rs b/crates/evm/src/block/curie.rs index 36c7358..ac9a525 100644 --- a/crates/evm/src/block/curie.rs +++ b/crates/evm/src/block/curie.rs @@ -20,8 +20,8 @@ use morph_revm::{CURIE_L1_GAS_PRICE_ORACLE_STORAGE, L1_GAS_PRICE_ORACLE_ADDRESS}; use revm::{ - Database, - database::{State, states::StorageSlot}, + Database, DatabaseCommit, + state::{Account, EvmStorageSlot}, }; /// Applies the Morph Curie hard fork to the state. @@ -33,36 +33,37 @@ use revm::{ /// - Sets `isCurie` slot to 1 (true) /// /// This function should only be called once at the Curie transition block. -pub(crate) fn apply_curie_hard_fork(state: &mut State) -> Result<(), DB::Error> { +pub(crate) fn apply_curie_hard_fork(db: &mut DB) -> Result<(), DB::Error> +where + DB: Database + DatabaseCommit, +{ tracing::info!(target: "morph::evm", "Applying Curie hard fork"); - let oracle = state.load_cache_account(L1_GAS_PRICE_ORACLE_ADDRESS)?; - - // Create storage updates - let new_storage = CURIE_L1_GAS_PRICE_ORACLE_STORAGE - .into_iter() - .map(|(slot, present_value)| { - ( - slot, - StorageSlot { - present_value, - previous_or_original_value: oracle.storage_slot(slot).unwrap_or_default(), - }, - ) - }) - .collect(); - - // Get existing account info or use default - let oracle_info = oracle.account_info().unwrap_or_default(); - - // Create transition for oracle storage update - let transition = oracle.change(oracle_info, new_storage); - - // Add transition to state - if let Some(s) = state.transition_state.as_mut() { - s.add_transitions(vec![(L1_GAS_PRICE_ORACLE_ADDRESS, transition)]); + // Load existing account info (may be None if not yet in state) + let account_info = db.basic(L1_GAS_PRICE_ORACLE_ADDRESS)?.unwrap_or_default(); + + // Build the account state to commit using Account::from(info) to get correct + // field defaults (original_info, transaction_id, hasher), then populate storage. + let mut account = Account::from(account_info); + // Mark as Touched so the commit is persisted. + account.mark_touch(); + + // Insert Curie initialization storage slots. These are new slots being set for + // the first time at the Curie fork, so original_value = 0 is correct. + for (slot, new_value) in CURIE_L1_GAS_PRICE_ORACLE_STORAGE { + account.storage.insert( + slot, + EvmStorageSlot::new_changed(Default::default(), new_value, 0), + ); } + // Commit the storage updates via DatabaseCommit. + db.commit( + [(L1_GAS_PRICE_ORACLE_ADDRESS, account)] + .into_iter() + .collect(), + ); + Ok(()) } @@ -91,7 +92,6 @@ mod tests { let mut state = State::builder() .with_database(db) .with_bundle_update() - .without_state_clear() .build(); // oracle pre fork state @@ -126,7 +126,7 @@ mod tests { .storage .into_iter() .collect::>(); - storage.sort_by(|(a, _), (b, _)| a.cmp(b)); + storage.sort_by_key(|(a, _)| *a); let expected_storage = [ (GPO_L1_BLOB_BASE_FEE_SLOT, INITIAL_L1_BLOB_BASE_FEE), diff --git a/crates/evm/src/block/factory.rs b/crates/evm/src/block/factory.rs index 969a35b..b9b2e08 100644 --- a/crates/evm/src/block/factory.rs +++ b/crates/evm/src/block/factory.rs @@ -11,26 +11,17 @@ use alloy_evm::{ Database, block::{BlockExecutorFactory, BlockExecutorFor}, eth::EthBlockExecutionCtx, - revm::{Inspector, database::State}, + revm::Inspector, }; use morph_chainspec::MorphChainSpec; use morph_primitives::{MorphReceipt, MorphTxEnvelope}; use morph_revm::evm::MorphContext; +use reth_revm::DatabaseCommit; 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 @@ -74,13 +65,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> where - DB: Database + 'a, - I: Inspector>> + 'a, + DB: Database + DatabaseCommit + 'a, + I: Inspector> + 'a, { - 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 e45beaf..4877389 100644 --- a/crates/evm/src/block/mod.rs +++ b/crates/evm/src/block/mod.rs @@ -19,21 +19,50 @@ 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, + StateChangeSource, TxResult, }, }; -use alloy_primitives::{Address, U256}; +use alloy_primitives::{Address, Log, U256}; use curie::apply_curie_hard_fork; 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(crate) 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 @@ -60,13 +89,13 @@ use revm::context::Block; /// 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(crate) 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 @@ -79,10 +108,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`]. /// @@ -91,9 +120,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, @@ -106,51 +135,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); } @@ -162,13 +157,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, @@ -181,8 +173,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 => { @@ -205,56 +195,32 @@ 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 - /// account cache to optimize L1 fee calculations for all transactions - /// - /// 3. **Curie Hardfork**: At the exact Curie activation block, applies the - /// hardfork state changes (updates to L1 Gas Price Oracle contract) - /// - /// # Errors - /// Returns error if: - /// - L1 Gas Price Oracle account cannot be loaded - /// - Curie hardfork application fails at transition block 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::()); self.hardfork = hardfork; - // 3. Apply Curie hardfork at the transition block - // Only executes once at the exact block where Curie activates + // Apply Curie hardfork at the transition block if self .spec .morph_fork_activation(MorphHardfork::Curie) @@ -269,97 +235,70 @@ 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) -> Result { + 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(); self.gas_used += gas_used; + // Get MorphTx-specific fields using the recovered transaction. + let (tx, signer) = recovered.into_parts(); + let morph_tx_fields = self.get_morph_tx_fields(&tx, signer, self.hardfork)?; + // 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, @@ -375,17 +314,6 @@ where Ok(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> { @@ -404,13 +332,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 9d38cb1..bc32906 100644 --- a/crates/evm/src/block/receipt.rs +++ b/crates/evm/src/block/receipt.rs @@ -245,8 +245,7 @@ mod tests { fn make_success_result(gas_used: u64) -> ExecutionResult { ExecutionResult::Success { reason: revm::context::result::SuccessReason::Stop, - gas_used, - gas_refunded: 0, + gas: revm::context::result::ResultGas::new(gas_used, gas_used, 0, 0, 0), logs: vec![], output: revm::context::result::Output::Call(alloy_primitives::Bytes::new()), } @@ -258,8 +257,7 @@ mod tests { ) -> ExecutionResult { ExecutionResult::Success { reason: revm::context::result::SuccessReason::Stop, - gas_used, - gas_refunded: 0, + gas: revm::context::result::ResultGas::new(gas_used, gas_used, 0, 0, 0), logs, output: revm::context::result::Output::Call(alloy_primitives::Bytes::new()), } @@ -267,7 +265,8 @@ mod tests { fn make_revert_result(gas_used: u64) -> ExecutionResult { ExecutionResult::Revert { - gas_used, + gas: revm::context::result::ResultGas::new(gas_used, gas_used, 0, 0, 0), + logs: vec![], output: alloy_primitives::Bytes::new(), } } diff --git a/crates/evm/src/config.rs b/crates/evm/src/config.rs index 6ab605e..70ef660 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,13 @@ 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()), }) } @@ -145,8 +152,9 @@ 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, }) } } diff --git a/crates/evm/src/context.rs b/crates/evm/src/context.rs index 5dc98db..083a030 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 f17f50f..8ed9e58 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/lib.rs b/crates/evm/src/lib.rs index 3f8012a..85e71a5 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -77,10 +77,11 @@ use alloy_evm::{ Database, block::{BlockExecutorFactory, BlockExecutorFor}, eth::EthBlockExecutionCtx, - revm::{Inspector, database::State}, + revm::Inspector, }; pub use evm::MorphEvmFactory; use morph_primitives::{MorphReceipt, MorphTxEnvelope}; +use reth_revm::DatabaseCommit; use crate::{block::MorphBlockExecutorFactory, evm::MorphEvm}; use morph_chainspec::MorphChainSpec; @@ -166,12 +167,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> where - DB: Database + 'a, - I: Inspector>> + 'a, + DB: Database + DatabaseCommit + 'a, + I: Inspector> + 'a, { self.executor_factory.create_executor(evm, ctx) } diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 93842b5..eae6d09 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 @@ -40,6 +41,7 @@ reth-rpc-eth-api.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 diff --git a/crates/node/src/add_ons.rs b/crates/node/src/add_ons.rs index e2b84b2..cd50d6b 100644 --- a/crates/node/src/add_ons.rs +++ b/crates/node/src/add_ons.rs @@ -16,7 +16,8 @@ use reth_node_builder::{ }, }; use reth_provider::{ - BlockWriter, CanonChainTracker, ChainSpecProvider, DBProvider, DatabaseProviderFactory, + BlockNumReader, BlockWriter, CanonChainTracker, ChainSpecProvider, DBProvider, + DatabaseProviderFactory, }; use reth_rpc_builder::Identity; use reth_rpc_eth_api::RpcNodeCore; @@ -108,11 +109,23 @@ where MorphEthConfigHandler::new(ctx.node.provider().clone(), ctx.node.evm_config().clone()); // Keep a local view of canonical head/forkchoice from reth engine events. + // Stale events are filtered against the provider's current canonical head; + // local FCU successes still write the tracker unconditionally on the + // import path. See `record_canonical_event_if_authoritative` for the + // race-safety rationale. let tracker_for_events = engine_state_tracker.clone(); - task_executor.spawn_critical("morph engine state tracker", async move { + let provider_for_events = provider.clone(); + task_executor.spawn_critical_task("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); + morph_engine_api::record_canonical_event_if_authoritative( + &tracker_for_events, + &event, + || { + let info = provider_for_events.chain_info().ok()?; + Some((info.best_number, info.best_hash)) + }, + ); } }); diff --git a/crates/node/src/components/pool.rs b/crates/node/src/components/pool.rs index 8aad6a0..47a196d 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/test_utils.rs b/crates/node/src/test_utils.rs index 1856087..1d1494e 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 @@ -345,13 +359,15 @@ 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), + }, + 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 6931ac4..bc8ccc8 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(()); @@ -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 { @@ -551,33 +628,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 7ae89ab..739fa14 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 07c5a46..b365f4c 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 627dd6d..22812f0 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?; @@ -59,7 +59,7 @@ async fn state_root_validation_skipped_pre_jade() -> eyre::Result<()> { async fn new_l2_block_imports_assembled_block_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(); @@ -97,7 +97,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 +123,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 +134,28 @@ 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), }, - 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 efb4b99..27f03b6 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 3ed5fe1..cef8669 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 ecd8349..3e994c5 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; @@ -54,13 +53,15 @@ pub(crate) async fn advance_block_with_l1_messages( 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}"))?; @@ -125,13 +126,15 @@ pub(crate) async fn build_block_no_submit( 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}"))?; @@ -230,13 +233,15 @@ pub(crate) async fn expect_payload_build_failure( 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 027bf74..0be68e3 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 567d47c..e00d387 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/rpc.rs b/crates/node/tests/it/rpc.rs index 2666922..32808c2 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); @@ -402,7 +401,7 @@ async fn transaction_receipt_exposes_morph_fields_over_rpc() -> eyre::Result<()> 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 +444,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 +477,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 +492,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 +584,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 5ebac50..a10f1ba 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 c994576..acd3e31 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 ab42ef7..bef78a2 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 5de02b8..b7a4c32 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::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,63 @@ 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 { + state_provider = Box::new(CachedStateProvider::new( + state_provider, + execution_cache.cache().clone(), + execution_cache.metrics().clone(), + )); + } + 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 +235,7 @@ where Client: StateProviderFactory + ChainSpecProvider + Clone, Txs: MorphPayloadTransactions, { - type Attributes = MorphPayloadBuilderAttributes; + type Attributes = MorphPayloadAttributes; type BuiltPayload = MorphBuiltPayload; fn try_build( @@ -225,6 +263,8 @@ where let args = BuildArguments { config, cached_reads: Default::default(), + execution_cache: None, + trie_handle: None, cancel: Default::default(), best_payload: None, }; @@ -634,9 +674,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,12 +702,12 @@ 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(), }, base_fee_per_gas: attributes.base_fee_per_gas, @@ -678,6 +719,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 +789,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 +870,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 +1094,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 bed39aa..7a618b5 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 b270e3f..70f7cc9 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 6b1f1d0..9776a46 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 ef84713..4abcd12 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 } @@ -65,14 +71,54 @@ impl reth_payload_primitives::PayloadAttributes for MorphPayloadAttributes { } } +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 +127,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 +141,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 +168,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 +176,70 @@ 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 } } @@ -489,10 +547,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 +582,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 3b51724..468a9f2 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, BatchSignature, GenericResponse}; @@ -124,7 +126,6 @@ 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 8e5f979..d0c9a8b 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 90ac3ed..0ce624c 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -152,9 +152,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 @@ -196,7 +193,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) } diff --git a/crates/primitives/src/receipt/mod.rs b/crates/primitives/src/receipt/mod.rs index a04e4e6..a74fd36 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 6c874be..4e334a0 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/revm/src/evm.rs b/crates/revm/src/evm.rs index c42a69c..18e04dc 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,163 @@ 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_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_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_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 +284,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 c96c08b..e1aaf69 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 4c2f144..425e494 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,9 +74,10 @@ 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)) } @@ -169,7 +170,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 +223,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,9 +246,12 @@ 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 spec = (*evm.ctx_ref().cfg().spec()).into(); let disable_eip7623 = evm.ctx_ref().cfg().is_eip7623_disabled(); // For L1 message transactions, handle intrinsic gas specially @@ -316,7 +317,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 +342,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 +468,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 +478,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 +493,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 +943,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 +1216,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/token_fee.rs b/crates/revm/src/token_fee.rs index fee48b6..b6198e1 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 60753e3..d92c5fe 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 0d2feb6..0398ce4 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -35,6 +35,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 4f23d07..5af9ada 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 82b72dc..f4a5e7f 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 0845bfb..96a08d7 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/transaction.rs b/crates/rpc/src/eth/transaction.rs index 9b4795b..ab388a7 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; @@ -151,10 +151,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 +189,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 +319,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 +341,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 +460,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 +483,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, diff --git a/crates/txpool/Cargo.toml b/crates/txpool/Cargo.toml index 77e6e13..ce50efe 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 bac2d0c..52ca426 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 fdc533e..21411db 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 1fdc73e..c75ecd8 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 9000cea..817f531 100644 --- a/deny.toml +++ b/deny.toml @@ -8,16 +8,10 @@ ignore = [ "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2025-0141 bincode is unmaintained "RUSTSEC-2025-0141", - # https://rustsec.org/advisories/RUSTSEC-2026-0002 lru 0.12.x unsound IterMut - # pinned by reth fork at 0.12.5, fix requires 0.16.3 (semver-incompatible) - "RUSTSEC-2026-0002", # 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", - # https://rustsec.org/advisories/RUSTSEC-2026-0099 rustls-webpki wildcard name constraints - "RUSTSEC-2026-0099", ] # This section is considered when running `cargo deny check bans`. @@ -86,4 +80,5 @@ unknown-registry = "warn" unknown-git = "deny" allow-git = [ "https://github.com/morph-l2/reth", + "https://github.com/paradigmxyz/reth", ] diff --git a/etc/docker-compose.yml b/etc/docker-compose.yml index b11f236..570aeaf 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 39ebd69..b6c8f39 100644 --- a/local-test/README.md +++ b/local-test/README.md @@ -98,3 +98,12 @@ To wipe chain data and start syncing from scratch: ``` This removes `reth-data/db`, `reth-data/static_files`, and `node-data/data/` for the specified network. Config files (genesis, keys) 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/reth-start.sh b/local-test/reth-start.sh index 3c6c755..3634f57 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