diff --git a/Cargo.lock b/Cargo.lock index 6868ccb..f6897d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -59,6 +65,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -180,9 +201,22 @@ dependencies = [ "k256", "rand 0.8.5", "serde", + "serde_with", "thiserror 2.0.17", ] +[[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", + "borsh", + "serde", +] + [[package]] name = "alloy-eips" version = "1.1.3" @@ -369,26 +403,56 @@ dependencies = [ "alloy-network", "alloy-network-primitives", "alloy-primitives", + "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-debug", "alloy-rpc-types-eth", + "alloy-rpc-types-trace", "alloy-signer", "alloy-sol-types", "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", "async-stream", "async-trait", "auto_impl", - "dashmap", + "dashmap 6.1.0", "either", "futures", "futures-utils-wasm", "lru 0.13.0", "parking_lot", "pin-project", + "reqwest", "serde", "serde_json", "thiserror 2.0.17", "tokio", "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-pubsub" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd4c64eb250a18101d22ae622357c6b505e158e9165d4c7974d59082a600c5e" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "auto_impl", + "bimap", + "futures", + "parking_lot", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", "wasmtimer", ] @@ -422,8 +486,11 @@ checksum = "d0882e72d2c1c0c79dcf4ab60a67472d3f009a949f774d4c17d0bdb669cfde05" dependencies = [ "alloy-json-rpc", "alloy-primitives", + "alloy-pubsub", "alloy-transport", "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", "futures", "pin-project", "reqwest", @@ -462,6 +529,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "alloy-rpc-types-anvil" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ce4c24e416bd0f17fceeb2f26cd8668df08fe19e1dc02f9d41c3b8ed1e93e0" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-rpc-types-any" version = "1.1.3" @@ -473,6 +552,38 @@ dependencies = [ "alloy-serde", ] +[[package]] +name = "alloy-rpc-types-beacon" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16633087e23d8d75161c3a59aa183203637b817a5a8d2f662f612ccb6d129af0" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "derive_more", + "ethereum_ssz", + "ethereum_ssz_derive", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", + "tree_hash", + "tree_hash_derive", +] + +[[package]] +name = "alloy-rpc-types-debug" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925ff0f48c2169c050f0ae7a82769bdf3f45723d6742ebb6a5efb4ed2f491b26" +dependencies = [ + "alloy-primitives", + "derive_more", + "serde", + "serde_with", +] + [[package]] name = "alloy-rpc-types-engine" version = "1.1.3" @@ -544,6 +655,18 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "alloy-rpc-types-txpool" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb5a795264a02222f9534435b8f40dcbd88de8e9d586647884aae24f389ebf2" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-serde" version = "1.1.3" @@ -582,9 +705,12 @@ dependencies = [ "alloy-primitives", "alloy-signer", "async-trait", + "coins-bip32", + "coins-bip39", "k256", "rand 0.8.5", "thiserror 2.0.17", + "zeroize", ] [[package]] @@ -665,7 +791,7 @@ checksum = "be98b07210d24acf5b793c99b759e9a696e4a2e67593aec0487ae3b3e1a2478c" dependencies = [ "alloy-json-rpc", "auto_impl", - "base64", + "base64 0.22.1", "derive_more", "futures", "futures-utils-wasm", @@ -695,6 +821,44 @@ dependencies = [ "url", ] +[[package]] +name = "alloy-transport-ipc" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8db249779ebc20dc265920c7e706ed0d31dbde8627818d1cbde60919b875bb0" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad2344a12398d7105e3722c9b7a7044ea837128e11d453604dec6e3731a86e2" +dependencies = [ + "alloy-pubsub", + "alloy-transport", + "futures", + "http", + "rustls", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "ws_stream_wasm", +] + [[package]] name = "alloy-trie" version = "0.9.1" @@ -772,7 +936,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -783,7 +947,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1126,6 +1290,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" +[[package]] +name = "async-compression" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -1159,6 +1335,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.1", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1220,6 +1407,12 @@ dependencies = [ "match-lookup", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -1232,6 +1425,18 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bincode" version = "1.3.3" @@ -1259,6 +1464,24 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.111", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -1371,12 +1594,43 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "boyer-moore-magiclen" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7441b4796eb8a7107d4cd99d829810be75f5573e1081c37faa0e8094169ea0d6" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bs58" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ + "sha2", "tinyvec", ] @@ -1392,6 +1646,12 @@ 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.24.0" @@ -1447,6 +1707,19 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.27", + "serde", + "serde_json", +] + [[package]] name = "cargo_metadata" version = "0.19.2" @@ -1574,6 +1847,57 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "coins-bip32" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2073678591747aed4000dd468b97b14d7007f7936851d3f2f01846899f5ebf08" +dependencies = [ + "bs58", + "coins-core", + "digest 0.10.7", + "hmac", + "k256", + "serde", + "sha2", + "thiserror 1.0.69", +] + +[[package]] +name = "coins-bip39" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b169b26623ff17e9db37a539fe4f15342080df39f129ef7631df7683d6d9d4" +dependencies = [ + "bitvec", + "coins-bip32", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.8.5", + "sha2", + "thiserror 1.0.69", +] + +[[package]] +name = "coins-core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b962ad8545e43a28e14e87377812ba9ae748dd4fd963f4c10e9fcc6d13475b" +dependencies = [ + "base64 0.21.7", + "bech32", + "bs58", + "const-hex", + "digest 0.10.7", + "generic-array", + "ripemd", + "serde", + "sha2", + "sha3", + "thiserror 1.0.69", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -1590,6 +1914,26 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "concat-kdf" version = "0.1.0" @@ -1701,6 +2045,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -1877,6 +2230,19 @@ dependencies = [ "syn 2.0.111", ] +[[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" @@ -1917,6 +2283,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + [[package]] name = "delay_map" version = "0.4.1" @@ -2035,6 +2407,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -2084,7 +2462,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2142,6 +2520,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "dunce" version = "1.0.5" @@ -2242,7 +2626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "851bd664a3d3a3c175cff92b2f0df02df3c541b4895d0ae307611827aae46152" dependencies = [ "alloy-rlp", - "base64", + "base64 0.22.1", "bytes", "ed25519-dalek", "hex", @@ -2300,7 +2684,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "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", + "ring", + "sha2", ] [[package]] @@ -2381,6 +2785,16 @@ dependencies = [ "bytes", ] +[[package]] +name = "fdlimit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" +dependencies = [ + "libc", + "thiserror 1.0.69", +] + [[package]] name = "ff" version = "0.13.1" @@ -2409,6 +2823,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2529,7 +2953,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", - "send_wrapper", + "send_wrapper 0.4.0", ] [[package]] @@ -2767,6 +3191,16 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + [[package]] name = "heck" version = "0.5.0" @@ -2893,6 +3327,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.10.1" @@ -2960,6 +3400,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots 1.0.5", ] [[package]] @@ -2981,7 +3422,7 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -3240,6 +3681,21 @@ dependencies = [ "generic-array", ] +[[package]] +name = "interprocess" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -3373,7 +3829,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf36eb27f8e13fa93dcb50ccb44c417e25b818cfa1a481b5470cd07b19c60b98" dependencies = [ - "base64", + "base64 0.22.1", "futures-channel", "futures-util", "gloo-net", @@ -3426,7 +3882,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790bedefcec85321e007ff3af84b4e417540d5c87b3c9779b9e247d1bcc3dab8" dependencies = [ - "base64", + "base64 0.22.1", "http-body", "hyper", "hyper-rustls", @@ -3527,7 +3983,7 @@ version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64", + "base64 0.22.1", "js-sys", "pem", "ring", @@ -3649,6 +4105,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "libproc" +version = "0.14.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54ad7278b8bc5301d5ffd2a94251c004feb971feba96c971ea4063645990757" +dependencies = [ + "bindgen 0.72.1", + "errno", + "libc", +] + [[package]] name = "libredox" version = "0.1.11" @@ -3687,6 +4154,12 @@ 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.11.0" @@ -3745,6 +4218,15 @@ version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + [[package]] name = "macro-string" version = "0.1.4" @@ -3813,12 +4295,99 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64 0.22.1", + "indexmap 2.12.1", + "metrics", + "metrics-util", + "quanta", + "thiserror 1.0.69", +] + +[[package]] +name = "metrics-process" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f615e08e049bd14a44c4425415782efb9bcd479fc1e19ddeb971509074c060d0" +dependencies = [ + "libc", + "libproc", + "mach2", + "metrics", + "once_cell", + "procfs 0.18.0", + "rlimit", + "windows 0.62.2", +] + +[[package]] +name = "metrics-util" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.5", + "metrics", + "quanta", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.1" @@ -3956,6 +4525,13 @@ dependencies = [ "tracing", ] +[[package]] +name = "morph-node" +version = "0.7.5" +dependencies = [ + "morph-rpc", +] + [[package]] name = "morph-payload-builder" version = "0.7.5" @@ -4047,6 +4623,44 @@ dependencies = [ "tracing", ] +[[package]] +name = "morph-rpc" +version = "0.7.5" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "eyre", + "jsonrpsee", + "morph-chainspec", + "morph-evm", + "morph-primitives", + "morph-revm", + "reth-chainspec", + "reth-errors", + "reth-evm", + "reth-node-api", + "reth-node-builder", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc", + "reth-rpc-convert", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-tasks", + "reth-transaction-pool", + "revm", + "serde", + "thiserror 2.0.17", + "tokio", + "tracing", +] + [[package]] name = "morph-txpool" version = "0.7.5" @@ -4161,7 +4775,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4345,6 +4959,7 @@ dependencies = [ "arbitrary", "derive_more", "serde", + "serde_with", "thiserror 2.0.17", ] @@ -4610,13 +5225,23 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + [[package]] name = "pem" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -4636,6 +5261,16 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.1", +] + [[package]] name = "phf" version = "0.13.1" @@ -4778,6 +5413,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -4838,6 +5483,52 @@ 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.10.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.10.0", + "procfs-core 0.18.0", + "rustix 1.1.2", +] + +[[package]] +name = "procfs-core" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" +dependencies = [ + "bitflags 2.10.0", + "chrono", + "hex", +] + +[[package]] +name = "procfs-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" +dependencies = [ + "bitflags 2.10.0", + "hex", +] + [[package]] name = "proptest" version = "1.9.0" @@ -4912,6 +5603,32 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +dependencies = [ + "bitflags 2.10.0", + "memchr", + "unicase", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -5075,7 +5792,16 @@ dependencies = [ ] [[package]] -name = "rapidhash" +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "rapidhash" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2988730ee014541157f48ce4dcc603940e00915edc3c7f9a8d78092256bb2493" @@ -5084,6 +5810,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "rayon" version = "1.11.0" @@ -5104,6 +5839,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.5.18" @@ -5190,7 +5931,7 @@ version = "0.12.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-core", @@ -5215,13 +5956,16 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", + "webpki-roots 1.0.5", ] [[package]] @@ -5417,6 +6161,32 @@ dependencies = [ "reth-primitives-traits", ] +[[package]] +name = "reth-consensus-debug-client" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-engine", + "alloy-transport", + "auto_impl", + "derive_more", + "eyre", + "futures", + "reqwest", + "reth-node-api", + "reth-primitives-traits", + "reth-tracing", + "ringbuffer", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "reth-db" version = "1.9.3" @@ -5471,6 +6241,36 @@ dependencies = [ "serde", ] +[[package]] +name = "reth-db-common" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-genesis", + "alloy-primitives", + "boyer-moore-magiclen", + "eyre", + "reth-chainspec", + "reth-codecs", + "reth-config", + "reth-db-api", + "reth-etl", + "reth-execution-errors", + "reth-fs-util", + "reth-node-types", + "reth-primitives-traits", + "reth-provider", + "reth-stages-types", + "reth-static-file-types", + "reth-trie", + "reth-trie-db", + "serde", + "serde_json", + "thiserror 2.0.17", + "tracing", +] + [[package]] name = "reth-db-models" version = "1.9.3" @@ -5559,6 +6359,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-downloaders" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "futures", + "futures-util", + "metrics", + "pin-project", + "rayon", + "reth-config", + "reth-consensus", + "reth-metrics", + "reth-network-p2p", + "reth-network-peers", + "reth-primitives-traits", + "reth-storage-api", + "reth-tasks", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + [[package]] name = "reth-ecies" version = "1.9.3" @@ -5636,6 +6464,160 @@ dependencies = [ "tokio", ] +[[package]] +name = "reth-engine-service" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +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.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "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", + "parking_lot", + "rayon", + "reth-chain-state", + "reth-consensus", + "reth-db", + "reth-engine-primitives", + "reth-errors", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-metrics", + "reth-network-p2p", + "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-revm", + "reth-stages-api", + "reth-tasks", + "reth-trie", + "reth-trie-parallel", + "reth-trie-sparse", + "reth-trie-sparse-parallel", + "revm", + "revm-primitives", + "schnellru", + "smallvec", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "reth-engine-util" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-rpc-types-engine", + "eyre", + "futures", + "itertools 0.14.0", + "pin-project", + "reth-chainspec", + "reth-engine-primitives", + "reth-engine-tree", + "reth-errors", + "reth-evm", + "reth-fs-util", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-revm", + "reth-storage-api", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-era" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "ethereum_ssz", + "ethereum_ssz_derive", + "snap", + "thiserror 2.0.17", +] + +[[package]] +name = "reth-era-downloader" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-primitives", + "bytes", + "eyre", + "futures-util", + "reqwest", + "reth-era", + "reth-fs-util", + "sha2", + "tokio", +] + +[[package]] +name = "reth-era-utils" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "eyre", + "futures-util", + "reth-db-api", + "reth-era", + "reth-era-downloader", + "reth-etl", + "reth-fs-util", + "reth-primitives-traits", + "reth-provider", + "reth-stages-types", + "reth-storage-api", + "tokio", + "tracing", +] + [[package]] name = "reth-errors" version = "1.9.3" @@ -5747,6 +6729,16 @@ dependencies = [ "serde_with", ] +[[package]] +name = "reth-etl" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "rayon", + "reth-db-api", + "tempfile", +] + [[package]] name = "reth-evm" version = "1.9.3" @@ -5759,8 +6751,10 @@ dependencies = [ "auto_impl", "derive_more", "futures-util", + "metrics", "reth-execution-errors", "reth-execution-types", + "reth-metrics", "reth-primitives-traits", "reth-storage-api", "reth-storage-errors", @@ -5821,26 +6815,126 @@ dependencies = [ ] [[package]] -name = "reth-fs-util" -version = "1.9.3" -source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" -dependencies = [ - "serde", - "serde_json", - "thiserror 2.0.17", -] - -[[package]] -name = "reth-libmdbx" +name = "reth-exex" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" dependencies = [ - "bitflags 2.10.0", - "byteorder", - "dashmap", - "derive_more", - "parking_lot", - "reth-mdbx-sys", + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "eyre", + "futures", + "itertools 0.14.0", + "metrics", + "parking_lot", + "reth-chain-state", + "reth-chainspec", + "reth-config", + "reth-ethereum-primitives", + "reth-evm", + "reth-exex-types", + "reth-fs-util", + "reth-metrics", + "reth-node-api", + "reth-node-core", + "reth-payload-builder", + "reth-primitives-traits", + "reth-provider", + "reth-prune-types", + "reth-revm", + "reth-stages-api", + "reth-tasks", + "reth-tracing", + "rmp-serde", + "thiserror 2.0.17", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "reth-exex-types" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "reth-chain-state", + "reth-execution-types", + "reth-primitives-traits", + "serde", + "serde_with", +] + +[[package]] +name = "reth-fs-util" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "reth-invalid-block-hooks" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-debug", + "eyre", + "futures", + "jsonrpsee", + "pretty_assertions", + "reth-engine-primitives", + "reth-evm", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc-api", + "reth-tracing", + "reth-trie", + "revm", + "revm-bytecode", + "revm-database", + "serde", + "serde_json", +] + +[[package]] +name = "reth-ipc" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "bytes", + "futures", + "futures-util", + "interprocess", + "jsonrpsee", + "pin-project", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tracing", +] + +[[package]] +name = "reth-libmdbx" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "bitflags 2.10.0", + "byteorder", + "dashmap 6.1.0", + "derive_more", + "parking_lot", + "reth-mdbx-sys", "smallvec", "thiserror 2.0.17", "tracing", @@ -5851,7 +6945,7 @@ name = "reth-mdbx-sys" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" dependencies = [ - "bindgen", + "bindgen 0.71.1", "cc", ] @@ -6062,6 +7156,73 @@ dependencies = [ "reth-transaction-pool", ] +[[package]] +name = "reth-node-builder" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-rpc-types-engine", + "aquamarine", + "eyre", + "fdlimit", + "futures", + "jsonrpsee", + "rayon", + "reth-basic-payload-builder", + "reth-chain-state", + "reth-chainspec", + "reth-config", + "reth-consensus", + "reth-consensus-debug-client", + "reth-db", + "reth-db-api", + "reth-db-common", + "reth-downloaders", + "reth-engine-local", + "reth-engine-primitives", + "reth-engine-service", + "reth-engine-tree", + "reth-engine-util", + "reth-evm", + "reth-exex", + "reth-fs-util", + "reth-invalid-block-hooks", + "reth-network", + "reth-network-api", + "reth-network-p2p", + "reth-node-api", + "reth-node-core", + "reth-node-ethstats", + "reth-node-events", + "reth-node-metrics", + "reth-payload-builder", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-engine-api", + "reth-rpc-eth-types", + "reth-rpc-layer", + "reth-stages", + "reth-static-file", + "reth-tasks", + "reth-tokio-util", + "reth-tracing", + "reth-transaction-pool", + "secp256k1 0.30.0", + "serde_json", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "reth-node-core" version = "1.9.3" @@ -6118,6 +7279,75 @@ dependencies = [ "vergen-git2", ] +[[package]] +name = "reth-node-ethstats" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "chrono", + "futures-util", + "reth-chain-state", + "reth-network-api", + "reth-primitives-traits", + "reth-storage-api", + "reth-transaction-pool", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tracing", + "url", +] + +[[package]] +name = "reth-node-events" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "derive_more", + "futures", + "humantime", + "pin-project", + "reth-engine-primitives", + "reth-network-api", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages", + "reth-static-file-types", + "reth-storage-api", + "tokio", + "tracing", +] + +[[package]] +name = "reth-node-metrics" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "eyre", + "http", + "jsonrpsee-server", + "metrics", + "metrics-exporter-prometheus", + "metrics-process", + "metrics-util", + "procfs 0.17.0", + "reqwest", + "reth-metrics", + "reth-tasks", + "tokio", + "tower", + "tracing", +] + [[package]] name = "reth-node-types" version = "1.9.3" @@ -6253,7 +7483,7 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", - "dashmap", + "dashmap 6.1.0", "eyre", "itertools 0.14.0", "metrics", @@ -6288,6 +7518,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-prune" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "itertools 0.14.0", + "metrics", + "rayon", + "reth-config", + "reth-db-api", + "reth-errors", + "reth-exex-types", + "reth-metrics", + "reth-primitives-traits", + "reth-provider", + "reth-prune-types", + "reth-stages-types", + "reth-static-file-types", + "reth-tokio-util", + "rustc-hash", + "thiserror 2.0.17", + "tokio", + "tracing", +] + [[package]] name = "reth-prune-types" version = "1.9.3" @@ -6312,9 +7570,162 @@ dependencies = [ "reth-primitives-traits", "reth-storage-api", "reth-storage-errors", + "reth-trie", "revm", ] +[[package]] +name = "reth-rpc" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-eips", + "alloy-evm", + "alloy-genesis", + "alloy-network", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-admin", + "alloy-rpc-types-beacon", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-rpc-types-mev", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "async-trait", + "derive_more", + "dyn-clone", + "futures", + "http", + "http-body", + "hyper", + "itertools 0.14.0", + "jsonrpsee", + "jsonrpsee-types", + "jsonwebtoken", + "parking_lot", + "pin-project", + "reth-chain-state", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-engine-primitives", + "reth-errors", + "reth-ethereum-engine-primitives", + "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", + "reth-execution-types", + "reth-metrics", + "reth-network-api", + "reth-network-peers", + "reth-network-types", + "reth-node-api", + "reth-primitives-traits", + "reth-revm", + "reth-rpc-api", + "reth-rpc-convert", + "reth-rpc-engine-api", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "reth-trie-common", + "revm", + "revm-inspectors", + "revm-primitives", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tower", + "tracing", + "tracing-futures", +] + +[[package]] +name = "reth-rpc-api" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-eips", + "alloy-genesis", + "alloy-json-rpc", + "alloy-primitives", + "alloy-rpc-types", + "alloy-rpc-types-admin", + "alloy-rpc-types-anvil", + "alloy-rpc-types-beacon", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-rpc-types-mev", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde", + "jsonrpsee", + "reth-chain-state", + "reth-engine-primitives", + "reth-network-peers", + "reth-rpc-eth-api", + "reth-trie-common", + "serde", + "serde_json", +] + +[[package]] +name = "reth-rpc-builder" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-network", + "alloy-provider", + "dyn-clone", + "http", + "jsonrpsee", + "metrics", + "pin-project", + "reth-chain-state", + "reth-chainspec", + "reth-consensus", + "reth-engine-primitives", + "reth-evm", + "reth-ipc", + "reth-metrics", + "reth-network-api", + "reth-node-core", + "reth-primitives-traits", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-eth-api", + "reth-rpc-eth-types", + "reth-rpc-layer", + "reth-rpc-server-types", + "reth-storage-api", + "reth-tasks", + "reth-tokio-util", + "reth-transaction-pool", + "serde", + "thiserror 2.0.17", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + [[package]] name = "reth-rpc-convert" version = "1.9.3" @@ -6336,6 +7747,35 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "reth-rpc-engine-api" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "async-trait", + "jsonrpsee-core", + "jsonrpsee-types", + "metrics", + "reth-chainspec", + "reth-engine-primitives", + "reth-metrics", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-primitives-traits", + "reth-rpc-api", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "serde", + "thiserror 2.0.17", + "tokio", + "tracing", +] + [[package]] name = "reth-rpc-eth-api" version = "1.9.3" @@ -6427,6 +7867,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-rpc-layer" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-rpc-types-engine", + "http", + "jsonrpsee-http-client", + "pin-project", + "tower", + "tower-http", + "tracing", +] + [[package]] name = "reth-rpc-server-types" version = "1.9.3" @@ -6443,6 +7897,77 @@ dependencies = [ "strum", ] +[[package]] +name = "reth-stages" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "bincode", + "eyre", + "futures-util", + "itertools 0.14.0", + "num-traits", + "rayon", + "reqwest", + "reth-codecs", + "reth-config", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-era", + "reth-era-downloader", + "reth-era-utils", + "reth-etl", + "reth-evm", + "reth-execution-types", + "reth-exex", + "reth-fs-util", + "reth-network-p2p", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-prune-types", + "reth-revm", + "reth-stages-api", + "reth-static-file-types", + "reth-storage-errors", + "reth-trie", + "reth-trie-db", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "reth-stages-api" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "aquamarine", + "auto_impl", + "futures-util", + "metrics", + "reth-consensus", + "reth-errors", + "reth-metrics", + "reth-network-p2p", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-stages-types", + "reth-static-file", + "reth-static-file-types", + "reth-tokio-util", + "thiserror 2.0.17", + "tokio", + "tracing", +] + [[package]] name = "reth-stages-types" version = "1.9.3" @@ -6457,6 +7982,26 @@ dependencies = [ "serde", ] +[[package]] +name = "reth-static-file" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-primitives", + "parking_lot", + "rayon", + "reth-codecs", + "reth-db-api", + "reth-primitives-traits", + "reth-provider", + "reth-prune-types", + "reth-stages-types", + "reth-static-file-types", + "reth-storage-errors", + "reth-tokio-util", + "tracing", +] + [[package]] name = "reth-static-file-types" version = "1.9.3" @@ -6674,6 +8219,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-trie-parallel" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crossbeam-channel", + "dashmap 6.1.0", + "derive_more", + "itertools 0.14.0", + "metrics", + "rayon", + "reth-execution-errors", + "reth-metrics", + "reth-provider", + "reth-storage-errors", + "reth-trie", + "reth-trie-common", + "reth-trie-sparse", + "thiserror 2.0.17", + "tokio", + "tracing", +] + [[package]] name = "reth-trie-sparse" version = "1.9.3" @@ -6683,14 +8253,34 @@ dependencies = [ "alloy-rlp", "alloy-trie", "auto_impl", + "metrics", "rayon", "reth-execution-errors", + "reth-metrics", "reth-primitives-traits", "reth-trie-common", "smallvec", "tracing", ] +[[package]] +name = "reth-trie-sparse-parallel" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?rev=64909d3#64909d33e6b7ab60774e37f5508fb5ad17f41897" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "metrics", + "rayon", + "reth-execution-errors", + "reth-metrics", + "reth-trie-common", + "reth-trie-sparse", + "smallvec", + "tracing", +] + [[package]] name = "reth-zstd-compressors" version = "1.9.3" @@ -6930,6 +8520,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuffer" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" + [[package]] name = "ripemd" version = "0.1.3" @@ -6939,6 +8535,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + [[package]] name = "rlp" version = "0.5.2" @@ -6949,6 +8554,25 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + [[package]] name = "roaring" version = "0.10.12" @@ -7054,6 +8678,19 @@ 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.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.2" @@ -7063,8 +8700,8 @@ dependencies = [ "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] @@ -7338,6 +8975,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.228" @@ -7409,7 +9052,7 @@ version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", @@ -7529,6 +9172,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "simple_asn1" version = "0.6.3" @@ -7547,6 +9196,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" + [[package]] name = "slab" version = "0.4.11" @@ -7595,7 +9265,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures", "http", @@ -7724,7 +9394,7 @@ dependencies = [ "libc", "memchr", "ntapi", - "windows", + "windows 0.57.0", ] [[package]] @@ -7748,8 +9418,8 @@ dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix", - "windows-sys 0.52.0", + "rustix 1.1.2", + "windows-sys 0.61.2", ] [[package]] @@ -7927,6 +9597,23 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + [[package]] name = "tokio-util" version = "0.7.17" @@ -8020,7 +9707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ "async-trait", - "base64", + "base64 0.22.1", "bytes", "http", "http-body", @@ -8058,6 +9745,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "hdrhistogram", "indexmap 2.12.1", "pin-project-lite", "slab", @@ -8075,16 +9763,29 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ + "async-compression", + "base64 0.22.1", "bitflags 2.10.0", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", + "http-range-header", + "httpdate", "iri-string", + "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", + "tracing", + "uuid", ] [[package]] @@ -8245,6 +9946,31 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tree_hash" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" +dependencies = [ + "alloy-primitives", + "ethereum_hashing", + "ethereum_ssz", + "smallvec", + "typenum", +] + +[[package]] +name = "tree_hash_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bee2ea1551f90040ab0e34b6fb7f2fa3bad8acc925837ac654f2c78a13e3089" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "triehash" version = "0.8.4" @@ -8255,12 +9981,37 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.17", + "utf-8", +] + [[package]] name = "typenum" version = "1.19.0" @@ -8303,6 +10054,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -8355,6 +10112,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -8397,7 +10160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b2bf58be11fc9414104c6d3a2e464163db5ef74b12296bda593cac37b6e4777" dependencies = [ "anyhow", - "cargo_metadata", + "cargo_metadata 0.19.2", "derive_builder", "regex", "rustversion", @@ -8549,6 +10312,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmtimer" version = "0.4.3" @@ -8601,6 +10377,24 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.5", +] + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "widestring" version = "1.2.1" @@ -8629,7 +10423,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -8648,6 +10442,27 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.57.0" @@ -8673,6 +10488,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.57.0" @@ -8723,6 +10549,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -8867,6 +10703,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -9078,6 +10923,25 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "ws_stream_wasm" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.1", + "send_wrapper 0.6.0", + "thiserror 2.0.17", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.5.1" @@ -9087,6 +10951,12 @@ dependencies = [ "tap", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 1cff19e..0caeaaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ members = [ "crates/consensus", "crates/engine-api", "crates/evm", + "crates/node", + "crates/rpc", "crates/payload/builder", "crates/payload/types", "crates/primitives", @@ -43,12 +45,11 @@ 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-evm = { path = "crates/evm", default-features = false } +morph-node = { path = "crates/node"} morph-payload-builder = { path = "crates/payload/builder", default-features = false } morph-payload-types = { path = "crates/payload/types", default-features = false } -morph-primitives = { path = "crates/primitives", default-features = false, features = [ - "serde", - "reth", -] } +morph-primitives = { path = "crates/primitives", default-features = false } +morph-rpc = { path = "crates/rpc" } morph-revm = { path = "crates/revm", default-features = false } morph-txpool = { path = "crates/txpool", default-features = false } @@ -97,6 +98,7 @@ reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3 reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" } reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" } reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" } reth-tracing = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" } reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" } reth-zstd-compressors = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3", default-features = false } diff --git a/crates/chainspec/Cargo.toml b/crates/chainspec/Cargo.toml index a7fccd9..460858c 100644 --- a/crates/chainspec/Cargo.toml +++ b/crates/chainspec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "morph-chainspec" -description = "TODO:" +description = "Morph L2 chainspec implementation" version.workspace = true edition.workspace = true diff --git a/crates/consensus/Cargo.toml b/crates/consensus/Cargo.toml index b913e5f..880abd2 100644 --- a/crates/consensus/Cargo.toml +++ b/crates/consensus/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] # Morph morph-chainspec.workspace = true -morph-primitives = { workspace = true, features = ["reth-codec"] } +morph-primitives = { workspace = true, features = ["serde-bincode-compat"] } # Reth reth-consensus.workspace = true diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index e2754da..97d6884 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "morph-evm" -description = "TODO:" +description = "Morph EVM implementation" version.workspace = true edition.workspace = true @@ -16,7 +16,7 @@ morph-chainspec.workspace = true morph-primitives.workspace = true morph-revm.workspace = true -reth-chainspec = { workspace = true, optional = true } +reth-chainspec.workspace = true reth-evm.workspace = true reth-evm-ethereum.workspace = true reth-revm.workspace = true @@ -38,8 +38,7 @@ alloy-genesis.workspace = true [features] default = [] -reth-codec = [ - "morph-primitives/reth-codec", - "dep:reth-chainspec", +serde-bincode-compat = [ + "morph-primitives/serde-bincode-compat", ] rpc = ["dep:reth-rpc-eth-api", "morph-revm/rpc"] diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index f554657..ae73572 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -31,13 +31,12 @@ //! //! # Features //! -//! - `reth-codec`: Enable `ConfigureEvm` implementation for reth integration +//! - `serde-bincode-compat`: Enable `ConfigureEvm` implementation for reth integration //! - `engine`: Enable engine API types #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] -#[cfg(feature = "reth-codec")] mod config; mod assemble; diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml new file mode 100644 index 0000000..a854578 --- /dev/null +++ b/crates/node/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "morph-node" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +publish.workspace = true + +[lints] +workspace = true + +[dependencies] +morph-rpc.workspace = true + +[features] +default = [] diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs new file mode 100644 index 0000000..f75e0a9 --- /dev/null +++ b/crates/node/src/lib.rs @@ -0,0 +1,7 @@ +//! Morph node implementation +//! +//! This crate provides the Morph node types and RPC implementation. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub use morph_rpc as rpc; diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index 44d9e25..c4fb922 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -14,9 +14,9 @@ workspace = true [dependencies] # Morph morph-chainspec.workspace = true -morph-evm = { workspace = true, features = ["reth-codec"] } +morph-evm = { workspace = true, features = ["serde-bincode-compat"] } morph-payload-types.workspace = true -morph-primitives.workspace = true +morph-primitives = { workspace = true, features = ["serde-bincode-compat", "reth-codec"] } # Reth reth-basic-payload-builder.workspace = true diff --git a/crates/payload/types/Cargo.toml b/crates/payload/types/Cargo.toml index 7ef640a..5ce3355 100644 --- a/crates/payload/types/Cargo.toml +++ b/crates/payload/types/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] # Morph -morph-primitives = { workspace = true, features = ["serde", "reth-codec"] } +morph-primitives = { workspace = true, features = ["serde-bincode-compat"] } # Reth reth-payload-builder.workspace = true @@ -29,7 +29,7 @@ alloy-rpc-types-engine = { workspace = true, features = ["serde"] } alloy-serde.workspace = true # Utils -serde = { workspace = true, features = ["derive"] } +serde = { workspace = true, features = ["derive"], optional = true } sha2.workspace = true [dev-dependencies] @@ -38,4 +38,4 @@ rand.workspace = true alloy-primitives = { workspace = true, features = ["rand"] } [features] -default = [] +default = ["serde"] diff --git a/crates/payload/types/src/attributes.rs b/crates/payload/types/src/attributes.rs index 2871227..674438c 100644 --- a/crates/payload/types/src/attributes.rs +++ b/crates/payload/types/src/attributes.rs @@ -8,15 +8,15 @@ use morph_primitives::MorphTxEnvelope; use reth_payload_builder::EthPayloadBuilderAttributes; use reth_payload_primitives::PayloadBuilderAttributes; use reth_primitives_traits::{Recovered, SignerRecoverable, WithEncoded}; -use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; /// Morph-specific payload attributes for Engine API. /// /// This extends the standard Ethereum [`PayloadAttributes`] with L2-specific fields /// for forced transaction inclusion (L1 messages). -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct MorphPayloadAttributes { /// Standard Ethereum payload attributes. #[serde(flatten)] @@ -26,7 +26,10 @@ pub struct MorphPayloadAttributes { /// /// This includes L1 messages that must be processed in order. /// These transactions are not in the mempool and must be explicitly provided. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none") + )] pub transactions: Option>, } diff --git a/crates/payload/types/src/executable_l2_data.rs b/crates/payload/types/src/executable_l2_data.rs index 9530df1..4df7cce 100644 --- a/crates/payload/types/src/executable_l2_data.rs +++ b/crates/payload/types/src/executable_l2_data.rs @@ -1,7 +1,6 @@ //! ExecutableL2Data type definition. use alloy_primitives::{Address, B256, Bytes}; -use serde::{Deserialize, Serialize}; /// L2 block data used for AssembleL2Block/ValidateL2Block/NewL2Block. /// @@ -13,8 +12,9 @@ use serde::{Deserialize, Serialize}; /// 1. **BLS message fields**: Fields that affect state calculation and need BLS signing /// 2. **Execution results**: Fields computed after block execution /// 3. **Metadata**: Additional L2-specific fields -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct ExecutableL2Data { // === BLS message fields (need to be signed) === /// Parent block hash. @@ -24,27 +24,30 @@ pub struct ExecutableL2Data { pub miner: Address, /// Block number. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub number: u64, /// Gas limit for this block. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub gas_limit: u64, /// Base fee per gas (EIP-1559). - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) )] pub base_fee_per_gas: Option, /// Block timestamp. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub timestamp: u64, /// RLP-encoded transactions. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub transactions: Vec, // === Execution results === @@ -52,7 +55,7 @@ pub struct ExecutableL2Data { pub state_root: B256, /// Gas used by all transactions. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub gas_used: u64, /// Receipts root. @@ -66,7 +69,7 @@ pub struct ExecutableL2Data { // === Metadata === /// Next L1 message queue index after this block. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub next_l1_message_index: u64, /// Cached block hash. diff --git a/crates/payload/types/src/lib.rs b/crates/payload/types/src/lib.rs index bcbaa0a..347349d 100644 --- a/crates/payload/types/src/lib.rs +++ b/crates/payload/types/src/lib.rs @@ -24,7 +24,6 @@ use alloy_primitives::{B256, Bytes}; use morph_primitives::Block; use reth_payload_primitives::{BuiltPayload, ExecutionPayload, PayloadTypes}; use reth_primitives_traits::{NodePrimitives, SealedBlock}; -use serde::{Deserialize, Serialize}; use std::sync::Arc; // Re-export main types @@ -47,7 +46,8 @@ pub use safe_l2_data::SafeL2Data; pub struct MorphPayloadTypes; /// Execution data for Morph node. Simply wraps a sealed block. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MorphExecutionData { /// The built block. pub block: Arc>, diff --git a/crates/payload/types/src/params.rs b/crates/payload/types/src/params.rs index 470c1f8..0dab0a3 100644 --- a/crates/payload/types/src/params.rs +++ b/crates/payload/types/src/params.rs @@ -1,21 +1,21 @@ //! Request/response types for L2 Engine API methods. use alloy_primitives::Bytes; -use serde::{Deserialize, Serialize}; /// Parameters for engine_assembleL2Block. /// /// This struct contains the input parameters for building a new L2 block. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct AssembleL2BlockParams { /// Block number to build. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub number: u64, /// Transactions to include in the block. /// These are RLP-encoded transaction bytes. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub transactions: Vec, } @@ -41,7 +41,8 @@ impl AssembleL2BlockParams { /// /// This is used by methods like engine_validateL2Block that return /// a simple success/failure status. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GenericResponse { /// Whether the operation was successful. pub success: bool, diff --git a/crates/payload/types/src/safe_l2_data.rs b/crates/payload/types/src/safe_l2_data.rs index 24e66da..1c8df49 100644 --- a/crates/payload/types/src/safe_l2_data.rs +++ b/crates/payload/types/src/safe_l2_data.rs @@ -3,7 +3,6 @@ //! This type is used for NewSafeL2Block in the derivation pipeline. use alloy_primitives::{B256, Bytes}; -use serde::{Deserialize, Serialize}; /// Safe L2 block data, used for NewSafeL2Block (derivation). /// @@ -13,35 +12,42 @@ use serde::{Deserialize, Serialize}; /// during execution rather than provided upfront. /// /// [`ExecutableL2Data`]: super::ExecutableL2Data -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct SafeL2Data { /// Block number. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub number: u64, /// Gas limit. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub gas_limit: u64, /// Base fee per gas (EIP-1559). - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "alloy_serde::quantity::opt" + #[cfg_attr( + feature = "serde", + serde( + default, + skip_serializing_if = "Option::is_none", + with = "alloy_serde::quantity::opt" + ) )] pub base_fee_per_gas: Option, /// Block timestamp. - #[serde(with = "alloy_serde::quantity")] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub timestamp: u64, /// RLP-encoded transactions. - #[serde(default)] + #[cfg_attr(feature = "serde", serde(default))] pub transactions: Vec, /// Optional batch hash for batch association. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[cfg_attr( + feature = "serde", + serde(default, skip_serializing_if = "Option::is_none") + )] pub batch_hash: Option, } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 2180863..62d2232 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -12,50 +12,57 @@ workspace = true [dependencies] # Reth -reth-db-api = { workspace = true, optional = true } -reth-ethereum-primitives = { workspace = true, optional = true } reth-primitives-traits.workspace = true reth-codecs = { workspace = true, optional = true } +reth-db-api = { workspace = true, optional = true } reth-zstd-compressors = { workspace = true, optional = true } +reth-ethereum-primitives = { workspace = true, optional = true } # Alloy alloy-consensus.workspace = true alloy-eips.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true -alloy-serde = { workspace = true, optional = true } -# Utils -bytes.workspace = true -serde = { workspace = true, features = ["derive"], optional = true } +# Codec +bytes = { workspace = true, optional = true } modular-bitfield = { version = "0.11.2", optional = true } - +serde = { workspace = true, features = ["derive"], optional = true } +alloy-serde = { workspace = true, optional = true } [dev-dependencies] serde_json.workspace = true [features] -default = ["serde", "reth"] +default = ["serde"] serde = [ "dep:serde", "dep:alloy-serde", - "alloy-primitives/serde", - "alloy-eips/serde", "alloy-consensus/serde", + "alloy-eips/serde", + "alloy-primitives/serde", "reth-primitives-traits/serde", "reth-ethereum-primitives?/serde", ] -reth = [ - "dep:reth-ethereum-primitives", + +serde-bincode-compat = [ + "serde", + "alloy-consensus/serde-bincode-compat", + "alloy-eips/serde-bincode-compat", + "reth-primitives-traits/serde-bincode-compat", + "dep:reth-ethereum-primitives", + "reth-ethereum-primitives/serde", + "reth-ethereum-primitives/serde-bincode-compat", ] reth-codec = [ - "reth", - "serde", - "dep:reth-codecs", - "dep:reth-db-api", - "dep:modular-bitfield", - "dep:reth-zstd-compressors", - "reth-ethereum-primitives/reth-codec", - "reth-codecs/alloy", - "reth-primitives-traits/reth-codec", + "serde", + "dep:reth-codecs", + "dep:reth-db-api", + "dep:modular-bitfield", + "dep:reth-zstd-compressors", + "dep:reth-ethereum-primitives", + "reth-ethereum-primitives/reth-codec", + "reth-codecs/alloy", + "dep:bytes", + "reth-primitives-traits/reth-codec", ] diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index bb6af1e..51d2742 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -168,7 +168,9 @@ impl Sealable for MorphHeader { } } -#[cfg(feature = "reth")] +#[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) @@ -177,10 +179,8 @@ impl reth_primitives_traits::InMemorySize for MorphHeader { } } -#[cfg(feature = "reth")] impl reth_primitives_traits::BlockHeader for MorphHeader {} -#[cfg(feature = "reth")] impl reth_primitives_traits::header::HeaderMut for MorphHeader { fn set_parent_hash(&mut self, hash: B256) { self.inner.set_parent_hash(hash); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index d40f384..7b0b95c 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -26,22 +26,12 @@ //! //! [`MorphPrimitives`] implements reth's `NodePrimitives` trait, providing //! all the type bindings needed for a Morph node. -//! -//! Note: `NodePrimitives` implementation requires the `reth-codec` feature. #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg), allow(unexpected_cfgs))] -// Suppress unused_crate_dependencies warnings for dependencies used in submodules -use alloy_consensus as _; -use alloy_eips as _; -use alloy_primitives as _; -use alloy_rlp as _; -use bytes as _; -#[cfg(feature = "reth")] +#[cfg(feature = "serde-bincode-compat")] use reth_ethereum_primitives as _; -#[cfg(feature = "reth-codec")] -use reth_zstd_compressors as _; pub mod header; pub mod receipt; @@ -57,7 +47,9 @@ pub type Block = alloy_consensus::Block; pub type BlockBody = alloy_consensus::BlockBody; // Re-export receipt types -pub use receipt::{MorphReceipt, MorphReceiptWithBloom, MorphTransactionReceipt}; +pub use receipt::{ + MorphReceipt, MorphReceiptEnvelope, MorphReceiptWithBloom, MorphTransactionReceipt, +}; // Re-export transaction types pub use transaction::{ @@ -65,11 +57,13 @@ pub use transaction::{ }; /// A [`NodePrimitives`] implementation for Morph. +/// +/// This implementation is only available when the `serde-bincode-compat` feature is enabled. #[derive(Debug, Clone, Default, Eq, PartialEq)] #[non_exhaustive] pub struct MorphPrimitives; -#[cfg(feature = "reth-codec")] +#[cfg(feature = "serde-bincode-compat")] impl reth_primitives_traits::NodePrimitives for MorphPrimitives { type Block = Block; type BlockHeader = MorphHeader; diff --git a/crates/primitives/src/receipt/envelope.rs b/crates/primitives/src/receipt/envelope.rs new file mode 100644 index 0000000..a1bd41d --- /dev/null +++ b/crates/primitives/src/receipt/envelope.rs @@ -0,0 +1,312 @@ +//! Receipt envelope types for Morph. + +use crate::transaction::envelope::MorphTxType; +use std::vec::Vec; + +use alloy_consensus::{Eip658Value, Receipt, ReceiptWithBloom, TxReceipt}; +use alloy_eips::{ + Typed2718, + eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}, +}; +use alloy_primitives::{Bloom, Log, logs_bloom}; +use alloy_rlp::{BufMut, Decodable, Encodable, length_of_length}; + +/// Receipt envelope, as defined in [EIP-2718], modified for Morph chains. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(tag = "type"))] +#[non_exhaustive] +pub enum MorphReceiptEnvelope { + /// Receipt envelope with no type flag. + #[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))] + Legacy(ReceiptWithBloom>), + /// Receipt envelope with type flag 1, containing a [EIP-2930] receipt. + #[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))] + Eip2930(ReceiptWithBloom>), + /// Receipt envelope with type flag 2, containing a [EIP-1559] receipt. + #[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))] + Eip1559(ReceiptWithBloom>), + /// Receipt envelope with type flag 4, containing a [EIP-7702] receipt. + #[cfg_attr(feature = "serde", serde(rename = "0x4", alias = "0x04"))] + Eip7702(ReceiptWithBloom>), + /// Receipt envelope with type flag 126, containing a Morph L1 message receipt. + #[cfg_attr(feature = "serde", serde(rename = "0x7e", alias = "0x7E"))] + L1Message(ReceiptWithBloom>), + /// Receipt envelope with type flag 127, containing a Morph transaction receipt. + #[cfg_attr(feature = "serde", serde(rename = "0x7f", alias = "0x7F"))] + Morph(ReceiptWithBloom>), +} + +impl MorphReceiptEnvelope { + /// Creates a new [`MorphReceiptEnvelope`] from the given parts. + pub fn from_parts<'a>( + status: bool, + cumulative_gas_used: u64, + logs: impl IntoIterator, + tx_type: MorphTxType, + ) -> Self { + let logs = logs.into_iter().cloned().collect::>(); + let logs_bloom = logs_bloom(&logs); + let inner_receipt = Receipt { + status: Eip658Value::Eip658(status), + cumulative_gas_used, + logs, + }; + let with_bloom = ReceiptWithBloom { + receipt: inner_receipt, + logs_bloom, + }; + match tx_type { + MorphTxType::Legacy => Self::Legacy(with_bloom), + MorphTxType::Eip2930 => Self::Eip2930(with_bloom), + MorphTxType::Eip1559 => Self::Eip1559(with_bloom), + MorphTxType::Eip7702 => Self::Eip7702(with_bloom), + MorphTxType::L1Msg => Self::L1Message(with_bloom), + MorphTxType::Morph => Self::Morph(with_bloom), + } + } +} + +impl MorphReceiptEnvelope { + /// Return the [`MorphTxType`] of the inner receipt. + pub const fn tx_type(&self) -> MorphTxType { + match self { + Self::Legacy(_) => MorphTxType::Legacy, + Self::Eip2930(_) => MorphTxType::Eip2930, + Self::Eip1559(_) => MorphTxType::Eip1559, + Self::Eip7702(_) => MorphTxType::Eip7702, + Self::L1Message(_) => MorphTxType::L1Msg, + Self::Morph(_) => MorphTxType::Morph, + } + } + + /// Returns the success status of the receipt's transaction. + pub const fn status(&self) -> bool { + self.as_receipt().unwrap().status.coerce_status() + } + + /// Return true if the transaction was successful. + pub const fn is_success(&self) -> bool { + self.status() + } + + /// Returns the cumulative gas used at this receipt. + pub const fn cumulative_gas_used(&self) -> u64 { + self.as_receipt().unwrap().cumulative_gas_used + } + + /// Return the receipt logs. + pub fn logs(&self) -> &[T] { + &self.as_receipt().unwrap().logs + } + + /// Return the receipt's bloom. + pub const fn logs_bloom(&self) -> &Bloom { + match self { + Self::Legacy(t) + | Self::Eip2930(t) + | Self::Eip1559(t) + | Self::Eip7702(t) + | Self::L1Message(t) + | Self::Morph(t) => &t.logs_bloom, + } + } + + /// Returns the L1 message receipt if it is a deposit receipt. + pub const fn as_l1_message_receipt_with_bloom(&self) -> Option<&ReceiptWithBloom>> { + match self { + Self::L1Message(t) => Some(t), + _ => None, + } + } + + /// Returns the L1 message receipt if it is a deposit receipt. + pub const fn as_l1_message_receipt(&self) -> Option<&Receipt> { + match self { + Self::L1Message(t) => Some(&t.receipt), + _ => None, + } + } + + /// Return the inner receipt. Currently this is infallible, however, future + /// receipt types may be added. + pub const fn as_receipt(&self) -> Option<&Receipt> { + match self { + Self::Legacy(t) + | Self::Eip2930(t) + | Self::Eip1559(t) + | Self::Eip7702(t) + | Self::L1Message(t) + | Self::Morph(t) => Some(&t.receipt), + } + } +} + +impl MorphReceiptEnvelope { + /// Get the length of the inner receipt in the 2718 encoding. + pub fn inner_length(&self) -> usize { + match self { + Self::Legacy(t) + | Self::Eip2930(t) + | Self::Eip1559(t) + | Self::Eip7702(t) + | Self::L1Message(t) + | Self::Morph(t) => t.length(), + } + } + + /// Calculate the length of the rlp payload of the network encoded receipt. + pub fn rlp_payload_length(&self) -> usize { + let length = self.inner_length(); + match self { + Self::Legacy(_) => length, + _ => length + 1, + } + } +} + +impl TxReceipt for MorphReceiptEnvelope +where + T: Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync, +{ + type Log = T; + + fn status_or_post_state(&self) -> Eip658Value { + self.as_receipt().unwrap().status + } + + fn status(&self) -> bool { + self.as_receipt().unwrap().status.coerce_status() + } + + fn bloom(&self) -> Bloom { + *self.logs_bloom() + } + + fn bloom_cheap(&self) -> Option { + Some(self.bloom()) + } + + fn cumulative_gas_used(&self) -> u64 { + self.as_receipt().unwrap().cumulative_gas_used + } + + fn logs(&self) -> &[T] { + &self.as_receipt().unwrap().logs + } +} + +impl Encodable for MorphReceiptEnvelope { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + self.network_encode(out) + } + + fn length(&self) -> usize { + let mut payload_length = self.rlp_payload_length(); + if !self.is_legacy() { + payload_length += length_of_length(payload_length); + } + payload_length + } +} + +impl Decodable for MorphReceiptEnvelope { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::network_decode(buf) + .map_or_else(|_| Err(alloy_rlp::Error::Custom("Unexpected type")), Ok) + } +} + +impl Encodable2718 for MorphReceiptEnvelope { + fn type_flag(&self) -> Option { + match self { + Self::Legacy(_) => None, + Self::Eip2930(_) => Some(MorphTxType::Eip2930 as u8), + Self::Eip1559(_) => Some(MorphTxType::Eip1559 as u8), + Self::Eip7702(_) => Some(MorphTxType::Eip7702 as u8), + Self::L1Message(_) => Some(MorphTxType::L1Msg as u8), + Self::Morph(_) => Some(MorphTxType::Morph as u8), + } + } + + fn encode_2718_len(&self) -> usize { + self.inner_length() + !self.is_legacy() as usize + } + + fn encode_2718(&self, out: &mut dyn BufMut) { + if let Some(ty) = self.type_flag() { + out.put_u8(ty); + } + match self { + Self::Legacy(t) + | Self::Eip2930(t) + | Self::Eip1559(t) + | Self::Eip7702(t) + | Self::L1Message(t) + | Self::Morph(t) => t.encode(out), + } + } +} + +impl Typed2718 for MorphReceiptEnvelope { + fn ty(&self) -> u8 { + let ty = match self { + Self::Legacy(_) => MorphTxType::Legacy, + Self::Eip2930(_) => MorphTxType::Eip2930, + Self::Eip1559(_) => MorphTxType::Eip1559, + Self::Eip7702(_) => MorphTxType::Eip7702, + Self::L1Message(_) => MorphTxType::L1Msg, + Self::Morph(_) => MorphTxType::Morph, + }; + ty as u8 + } +} + +impl Decodable2718 for MorphReceiptEnvelope { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result { + match ty + .try_into() + .map_err(|_| Eip2718Error::UnexpectedType(ty))? + { + MorphTxType::Legacy => { + Err(alloy_rlp::Error::Custom("type-0 eip2718 receipts are not supported").into()) + } + MorphTxType::Eip2930 => Ok(Self::Eip2930(Decodable::decode(buf)?)), + MorphTxType::Eip1559 => Ok(Self::Eip1559(Decodable::decode(buf)?)), + MorphTxType::Eip7702 => Ok(Self::Eip7702(Decodable::decode(buf)?)), + MorphTxType::L1Msg => Ok(Self::L1Message(Decodable::decode(buf)?)), + MorphTxType::Morph => Ok(Self::Morph(Decodable::decode(buf)?)), + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result { + Ok(Self::Legacy(Decodable::decode(buf)?)) + } +} + +impl From for MorphReceiptEnvelope { + fn from(value: crate::receipt::MorphReceipt) -> Self { + let (tx_type, inner) = match value { + crate::receipt::MorphReceipt::Legacy(receipt) => (MorphTxType::Legacy, receipt.inner), + crate::receipt::MorphReceipt::Eip2930(receipt) => (MorphTxType::Eip2930, receipt.inner), + crate::receipt::MorphReceipt::Eip1559(receipt) => (MorphTxType::Eip1559, receipt.inner), + crate::receipt::MorphReceipt::Eip7702(receipt) => (MorphTxType::Eip7702, receipt.inner), + crate::receipt::MorphReceipt::Morph(receipt) => (MorphTxType::Morph, receipt.inner), + crate::receipt::MorphReceipt::L1Msg(receipt) => (MorphTxType::L1Msg, receipt), + }; + + let logs_bloom = logs_bloom(&inner.logs); + let with_bloom = ReceiptWithBloom { + receipt: inner, + logs_bloom, + }; + match tx_type { + MorphTxType::Legacy => Self::Legacy(with_bloom), + MorphTxType::Eip2930 => Self::Eip2930(with_bloom), + MorphTxType::Eip1559 => Self::Eip1559(with_bloom), + MorphTxType::Eip7702 => Self::Eip7702(with_bloom), + MorphTxType::L1Msg => Self::L1Message(with_bloom), + MorphTxType::Morph => Self::Morph(with_bloom), + } + } +} diff --git a/crates/primitives/src/receipt/mod.rs b/crates/primitives/src/receipt/mod.rs index bad6ebf..ea37841 100644 --- a/crates/primitives/src/receipt/mod.rs +++ b/crates/primitives/src/receipt/mod.rs @@ -4,8 +4,10 @@ //! - [`MorphTransactionReceipt`]: Receipt with L1 fee and Morph transaction fields //! - [`MorphReceipt`]: Typed receipt enum for different transaction types +mod envelope; #[allow(clippy::module_inception)] mod receipt; +pub use envelope::MorphReceiptEnvelope; pub use receipt::{MorphReceiptWithBloom, MorphTransactionReceipt}; use crate::transaction::envelope::MorphTxType; @@ -71,15 +73,15 @@ impl MorphReceipt { } } - /// Returns the L1 fee if present. - pub fn l1_fee(&self) -> Option { + /// Returns the L1 fee for the receipt. + pub fn l1_fee(&self) -> alloy_primitives::U256 { match self { Self::Legacy(r) | Self::Eip2930(r) | Self::Eip1559(r) | Self::Eip7702(r) | Self::Morph(r) => r.l1_fee, - Self::L1Msg(_) => None, + Self::L1Msg(_) => alloy_primitives::U256::ZERO, } } @@ -412,6 +414,9 @@ 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 @@ -477,7 +482,7 @@ mod compact { | MorphReceipt::Eip1559(r) | MorphReceipt::Eip7702(r) | MorphReceipt::Morph(r) => ( - r.l1_fee, + (r.l1_fee != U256::ZERO).then_some(r.l1_fee), r.fee_token_id.map(u64::from), r.fee_rate, r.token_scale, @@ -527,7 +532,7 @@ mod compact { let morph_receipt = MorphTransactionReceipt { inner, - l1_fee, + l1_fee: l1_fee.unwrap_or_default(), fee_token_id: fee_token_id.map(|id| id as u16), fee_rate, token_scale, @@ -648,10 +653,10 @@ mod tests { #[test] fn test_receipt_l1_fee() { let receipt = create_test_receipt(); - assert_eq!(receipt.l1_fee(), Some(U256::from(1000))); + assert_eq!(receipt.l1_fee(), U256::from(1000)); let l1_msg = MorphReceipt::L1Msg(Receipt::default()); - assert_eq!(l1_msg.l1_fee(), None); + assert_eq!(l1_msg.l1_fee(), U256::ZERO); } #[test] diff --git a/crates/primitives/src/receipt/receipt.rs b/crates/primitives/src/receipt/receipt.rs index fd5751e..1e4bc54 100644 --- a/crates/primitives/src/receipt/receipt.rs +++ b/crates/primitives/src/receipt/receipt.rs @@ -28,9 +28,9 @@ pub struct MorphTransactionReceipt { /// This is the cost of posting the transaction data to L1. #[cfg_attr( feature = "serde", - serde(default, skip_serializing_if = "Option::is_none") + serde(default, skip_serializing_if = "U256::is_zero") )] - pub l1_fee: Option, + pub l1_fee: U256, /// The ERC20 token ID used for fee payment (TxMorph feature). /// Only present for TxMorph. @@ -70,7 +70,7 @@ impl MorphTransactionReceipt { pub const fn new(inner: Receipt) -> Self { Self { inner, - l1_fee: None, + l1_fee: U256::ZERO, fee_token_id: None, fee_rate: None, token_scale: None, @@ -82,7 +82,7 @@ impl MorphTransactionReceipt { pub const fn with_l1_fee(inner: Receipt, l1_fee: U256) -> Self { Self { inner, - l1_fee: Some(l1_fee), + l1_fee, fee_token_id: None, fee_rate: None, token_scale: None, @@ -101,7 +101,7 @@ impl MorphTransactionReceipt { ) -> Self { Self { inner, - l1_fee: Some(l1_fee), + l1_fee, fee_token_id: Some(fee_token_id), fee_rate: Some(fee_rate), token_scale: Some(token_scale), @@ -114,9 +114,9 @@ impl MorphTransactionReceipt { self.fee_token_id.is_some() } - /// Returns the L1 fee, defaulting to zero if not set. - pub fn l1_fee_or_zero(&self) -> U256 { - self.l1_fee.unwrap_or(U256::ZERO) + /// Returns the L1 fee. + pub const fn l1_fee(&self) -> U256 { + self.l1_fee } } @@ -249,7 +249,7 @@ mod tests { assert!(receipt.status()); assert_eq!(receipt.cumulative_gas_used(), 21000); - assert!(receipt.l1_fee.is_none()); + assert_eq!(receipt.l1_fee, U256::ZERO); assert!(receipt.fee_token_id.is_none()); assert!(!receipt.is_morph_tx()); } @@ -264,8 +264,8 @@ mod tests { let l1_fee = U256::from(1000000); let receipt = MorphTransactionReceipt::with_l1_fee(inner, l1_fee); - assert_eq!(receipt.l1_fee, Some(l1_fee)); - assert_eq!(receipt.l1_fee_or_zero(), l1_fee); + assert_eq!(receipt.l1_fee, l1_fee); + assert_eq!(receipt.l1_fee(), l1_fee); assert!(!receipt.is_morph_tx()); } diff --git a/crates/primitives/src/transaction/envelope.rs b/crates/primitives/src/transaction/envelope.rs index 759e5ad..d6d0aa2 100644 --- a/crates/primitives/src/transaction/envelope.rs +++ b/crates/primitives/src/transaction/envelope.rs @@ -204,6 +204,9 @@ 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; diff --git a/crates/revm/src/handler.rs b/crates/revm/src/handler.rs index 1fa80ae..f03f89a 100644 --- a/crates/revm/src/handler.rs +++ b/crates/revm/src/handler.rs @@ -18,7 +18,7 @@ use crate::{ error::MorphHaltReason, evm::MorphContext, l1block::L1BlockInfo, - token_fee::{TokenFeeInfo, get_mapping_account_slot}, + token_fee::{TokenFeeInfo, mapping_slot_for}, tx::MorphTxExt, }; @@ -329,9 +329,8 @@ where // Fetch token fee info from Token Registry let spec = evm.ctx_ref().cfg().spec(); - let token_fee_info = - TokenFeeInfo::try_fetch(evm.ctx_mut().db_mut(), token_id, caller, spec)? - .ok_or(MorphInvalidTransaction::TokenNotRegistered(token_id))?; + let token_fee_info = TokenFeeInfo::fetch(evm.ctx_mut().db_mut(), token_id, caller, spec)? + .ok_or(MorphInvalidTransaction::TokenNotRegistered(token_id))?; // Check if token is active if !token_fee_info.is_active { @@ -339,7 +338,7 @@ where } // Calculate token amount required for total fee - let token_amount_required = token_fee_info.calculate_token_amount(reimburse_eth); + let token_amount_required = token_fee_info.eth_to_token_amount(reimburse_eth); // Get mutable access to journal components let journal = evm.ctx().journal_mut(); @@ -391,7 +390,7 @@ where // Fetch token fee info from Token Registry let token_fee_info = - TokenFeeInfo::try_fetch(journal.db_mut(), token_id, caller_addr, hardfork)? + TokenFeeInfo::fetch(journal.db_mut(), token_id, caller_addr, hardfork)? .ok_or(MorphInvalidTransaction::TokenNotRegistered(token_id))?; // Check if token is active @@ -422,7 +421,7 @@ where let total_eth_fee = l2_gas_fee.saturating_add(l1_data_fee); // Calculate token amount required for total fee - let token_amount_required = token_fee_info.calculate_token_amount(total_eth_fee); + let token_amount_required = token_fee_info.eth_to_token_amount(total_eth_fee); // Determine fee limit let mut fee_limit = tx.fee_limit.unwrap_or_default(); @@ -526,7 +525,7 @@ where DB: alloy_evm::Database, { // Sub amount - let from_storage_slot = get_mapping_account_slot(token_balance_slot, from); + let from_storage_slot = mapping_slot_for(token_balance_slot, from); let balance = journal.sload(token, from_storage_slot)?; journal.sstore( token, @@ -535,7 +534,7 @@ where )?; // Add amount - let to_storage_slot = get_mapping_account_slot(token_balance_slot, to); + let to_storage_slot = mapping_slot_for(token_balance_slot, to); let balance = journal.sload(token, to_storage_slot)?; journal.sstore(token, to_storage_slot, balance.saturating_add(token_amount))?; Ok((from_storage_slot, to_storage_slot)) diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 222dcdb..112e7e4 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -77,5 +77,8 @@ pub use l1block::{ L1BlockInfo, }; pub use precompiles::MorphPrecompiles; -pub use token_fee::{L2_TOKEN_REGISTRY_ADDRESS, TokenFeeInfo, get_erc20_balance_with_evm}; +pub use token_fee::{ + L2_TOKEN_REGISTRY_ADDRESS, TokenFeeInfo, encode_balance_of, erc20_balance_of, mapping_slot, + mapping_slot_for, +}; pub use tx::{MorphTxEnv, MorphTxExt}; diff --git a/crates/revm/src/token_fee.rs b/crates/revm/src/token_fee.rs index e6d9122..70e8840 100644 --- a/crates/revm/src/token_fee.rs +++ b/crates/revm/src/token_fee.rs @@ -9,7 +9,10 @@ use alloy_evm::Database; use alloy_primitives::{Address, Bytes, U256, address, keccak256}; use morph_chainspec::hardfork::MorphHardfork; use revm::SystemCallEvm; -use revm::{Inspector, context_interface::result::EVMError, inspector::NoOpInspector}; +use revm::{ + Database as RevmDatabase, Inspector, context_interface::result::EVMError, + inspector::NoOpInspector, +}; use crate::evm::MorphContext; use crate::{MorphEvm, MorphInvalidTransaction}; @@ -47,94 +50,86 @@ pub struct TokenFeeInfo { pub balance_slot: Option, } +#[derive(Clone, Debug)] +struct TokenRegistryEntry { + token_address: Address, + is_active: bool, + decimals: u8, + price_ratio: U256, + scale: U256, + balance_slot: Option, +} + impl TokenFeeInfo { - /// Try to fetch the token fee information from the database. + /// Fetch token fee information with EVM call fallback. /// - /// This reads the token parameters from the L2 Token Registry contract storage. + /// Reads token parameters from L2 Token Registry storage. If the token's + /// balance slot is unknown, falls back to an EVM `balanceOf` call. /// Returns `None` if the token is not registered. - pub fn try_fetch( + pub fn fetch( db: &mut DB, token_id: u16, caller: Address, - spec: MorphHardfork, + hardfork: MorphHardfork, ) -> Result, DB::Error> { - // Get the base slot for this token_id in tokenRegistry mapping - let mut token_id_bytes = [0u8; 32]; - token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); - let token_registry_base = get_mapping_slot(TOKEN_REGISTRY_SLOT, token_id_bytes.to_vec()); - - // TokenInfo struct layout in storage (following Solidity storage packing rules): - // slot + 0: tokenAddress (address, 20 bytes) + 12 bytes padding - // slot + 1: balanceSlot (bytes32, 32 bytes) - // slot + 2: isActive (bool, 1 byte) + decimals (uint8, 1 byte) + 30 bytes padding - // slot + 3: scale (uint256, 32 bytes) - - // Read tokenAddress from slot + 0 - let slot_0 = db.storage(L2_TOKEN_REGISTRY_ADDRESS, token_registry_base)?; - let token_address = Address::from_word(slot_0.into()); - if token_address == Address::default() { - return Ok(None); - } - - // Read balanceSlot from slot + 1 - let balance_slot_value = db.storage( - L2_TOKEN_REGISTRY_ADDRESS, - token_registry_base + U256::from(1), - )?; - let token_balance_slot = if !balance_slot_value.is_zero() { - Some(balance_slot_value.saturating_sub(U256::from(1u64))) - } else { - None + let entry = match load_registry_entry(db, token_id)? { + Some(e) => e, + None => return Ok(None), }; - // Read isActive and decimals from slot + 2 - // In big-endian representation, rightmost byte is the lowest position - // isActive is at the rightmost (byte 31), decimals is to its left (byte 30) - let slot_2 = db.storage( - L2_TOKEN_REGISTRY_ADDRESS, - token_registry_base + U256::from(2), - )?; - let slot_2_bytes = slot_2.to_be_bytes::<32>(); - let is_active = slot_2_bytes[31] != 0; - let decimals = slot_2_bytes[30]; - - // Read scale from slot + 3 - let scale = db.storage( - L2_TOKEN_REGISTRY_ADDRESS, - token_registry_base + U256::from(3), - )?; - - // Get price ratio from priceRatio mapping - let price_ratio = load_mapping_value( + let balance = read_erc20_balance( db, - L2_TOKEN_REGISTRY_ADDRESS, - PRICE_RATIO_SLOT, - token_id_bytes.to_vec(), + entry.token_address, + caller, + entry.balance_slot, + hardfork, )?; - // Get caller's token balance - let caller_token_balance = - get_erc20_balance(db, token_address, caller, token_balance_slot, spec)?; - - let token_fee = Self { - token_address, - is_active, - decimals, - price_ratio, - scale, + 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: caller_token_balance, - balance_slot: token_balance_slot, + balance, + balance_slot: entry.balance_slot, + })) + } + + /// Fetch token fee information using storage-only reads. + /// + /// Does not execute EVM calls. If the balance slot is unknown, + /// the returned `balance` will be zero. + pub fn fetch_storage_only( + db: &mut DB, + token_id: u16, + caller: Address, + ) -> Result, DB::Error> { + let entry = match load_registry_entry(db, token_id)? { + Some(e) => e, + None => return Ok(None), }; - Ok(Some(token_fee)) + let balance = read_balance_slot(db, entry.token_address, caller, entry.balance_slot)?; + + 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. - pub fn calculate_token_amount(&self, eth_amount: U256) -> U256 { - if self.price_ratio.is_zero() { + pub fn eth_to_token_amount(&self, eth_amount: U256) -> U256 { + if self.price_ratio.is_zero() || self.scale.is_zero() { return U256::ZERO; } @@ -152,89 +147,140 @@ impl TokenFeeInfo { /// Check if the caller has sufficient token balance for the given ETH amount. pub fn has_sufficient_balance(&self, eth_amount: U256) -> bool { - let required = self.calculate_token_amount(eth_amount); + let required = self.eth_to_token_amount(eth_amount); self.balance >= required } } -/// Calculate the storage slot for a mapping value. -/// -/// For a mapping `mapping(keyType => valueType)` at storage slot `slot_index`, -/// the value for `key` is stored at `keccak256(key || slot_index)`. -pub fn get_mapping_slot(slot_index: U256, mut key: Vec) -> U256 { - let mut pre_image = slot_index.to_be_bytes_vec(); - key.append(&mut pre_image); - let storage_key = keccak256(key); - U256::from_be_bytes(storage_key.0) +fn load_registry_entry( + db: &mut DB, + token_id: u16, +) -> Result, DB::Error> { + // Get the base slot for this token_id in tokenRegistry mapping + let mut token_id_bytes = [0u8; 32]; + token_id_bytes[30..32].copy_from_slice(&token_id.to_be_bytes()); + let base = mapping_slot(TOKEN_REGISTRY_SLOT, token_id_bytes.to_vec()); + + // TokenInfo struct layout in storage (Solidity packing): + // base + 0: tokenAddress (20 bytes) + padding + // base + 1: balanceSlot (32 bytes) + // base + 2: isActive (1 byte) + decimals (1 byte) + padding + // base + 3: scale (32 bytes) + + let slot_0 = db.storage(L2_TOKEN_REGISTRY_ADDRESS, base)?; + let token_address = Address::from_word(slot_0.into()); + if token_address == Address::default() { + return Ok(None); + } + + let balance_slot_raw = db.storage(L2_TOKEN_REGISTRY_ADDRESS, base + U256::from(1))?; + let balance_slot = if !balance_slot_raw.is_zero() { + Some(balance_slot_raw.saturating_sub(U256::from(1))) + } else { + None + }; + + // isActive at byte 31, decimals at byte 30 (big-endian) + let slot_2 = db.storage(L2_TOKEN_REGISTRY_ADDRESS, base + U256::from(2))?; + let bytes = slot_2.to_be_bytes::<32>(); + let is_active = bytes[31] != 0; + let decimals = bytes[30]; + + let scale = db.storage(L2_TOKEN_REGISTRY_ADDRESS, base + U256::from(3))?; + + // Get price ratio from priceRatio mapping + let price_ratio = load_mapping_value( + db, + L2_TOKEN_REGISTRY_ADDRESS, + PRICE_RATIO_SLOT, + token_id_bytes.to_vec(), + )?; + + Ok(Some(TokenRegistryEntry { + token_address, + is_active, + decimals, + price_ratio, + scale, + balance_slot, + })) } -/// Calculate the account's storage slot for a mapping value. +/// Calculate the storage slot for a Solidity mapping value. /// -/// For address-keyed mappings, the address is left-padded to 32 bytes. +/// For `mapping(keyType => valueType)` at slot `base_slot`, +/// the value for `key` is at `keccak256(key ++ base_slot)`. +pub fn mapping_slot(base_slot: U256, mut key: Vec) -> U256 { + let mut preimage = base_slot.to_be_bytes_vec(); + key.append(&mut preimage); + U256::from_be_bytes(keccak256(key).0) +} + +/// Calculate mapping slot for an address key (left-padded to 32 bytes). #[inline] -pub fn get_mapping_account_slot(slot_index: U256, account: Address) -> U256 { +pub fn mapping_slot_for(base_slot: U256, account: Address) -> U256 { let mut key = [0u8; 32]; key[12..32].copy_from_slice(account.as_slice()); - get_mapping_slot(slot_index, key.to_vec()) + mapping_slot(base_slot, key.to_vec()) } /// Load a value from a mapping in contract storage. -fn load_mapping_value( +fn load_mapping_value( db: &mut DB, - account: Address, - slot_index: U256, + contract: Address, + base_slot: U256, key: Vec, ) -> Result { - let storage_slot = get_mapping_slot(slot_index, key); - let storage_value = db.storage(account, storage_slot)?; - Ok(storage_value) + db.storage(contract, mapping_slot(base_slot, key)) } -/// Get ERC20 token balance for an account (storage-only version). +/// Read ERC20 balance with EVM call fallback. /// -/// First tries to read directly from storage if the balance slot is known. -/// If the balance slot is not available, returns zero. +/// If `balance_slot` is known, reads directly from storage. +/// Otherwise, constructs a temporary EVM to call `balanceOf(address)`. +fn read_erc20_balance( + db: &mut DB, + token: Address, + account: Address, + balance_slot: Option, + hardfork: MorphHardfork, +) -> Result { + if balance_slot.is_some() { + return read_balance_slot(db, token, account, balance_slot); + } + + // EVM fallback: construct temporary MorphEvm for balanceOf call + let db: &mut dyn Database = db; + let mut evm = MorphEvm::new(MorphContext::new(db, hardfork), NoOpInspector {}); + + match call_balance_of(&mut evm, token, account) { + Ok(balance) => Ok(balance), + Err(EVMError::Database(e)) => Err(e), + Err(_) => Ok(U256::ZERO), // Non-DB errors → zero (safe fallback) + } +} + +/// Read ERC20 balance directly from storage slot. /// -/// Use [`get_erc20_balance_with_evm`] if you have access to a `MorphEvm` instance -/// and need the EVM call fallback. -pub fn get_erc20_balance( +/// Returns zero if `balance_slot` is `None`. +fn read_balance_slot( db: &mut DB, token: Address, account: Address, - token_balance_slot: Option, - spec: MorphHardfork, + balance_slot: Option, ) -> Result { - // If balance slot is provided, read directly from storage - if let Some(slot) = token_balance_slot { - let mut data = [0u8; 32]; - data[12..32].copy_from_slice(account.as_slice()); - load_mapping_value(db, token, slot, data.to_vec()) - } else { - // For the EVM fallback we construct a temporary MorphEvm instance. - // - // Notes: - // - `MorphContext::new` requires a hardfork/spec parameter. - // - We pass `&mut DB` as the context database type (so we don't move `db`). - // - `NoOpInspector` satisfies the `Inspector` bound without adding side effects. - let db: &mut dyn Database = db; - - let mut evm = MorphEvm::new(MorphContext::new(db, spec), NoOpInspector {}); - - match get_erc20_balance_with_evm(&mut evm, token, account) { - Ok(balance) => Ok(balance), - Err(EVMError::Database(db_err)) => Err(db_err), - // For non-database EVM errors, fall back to zero (matches original behavior). - Err(_) => Ok(U256::ZERO), + match balance_slot { + Some(slot) => { + let mut key = [0u8; 32]; + key[12..32].copy_from_slice(account.as_slice()); + load_mapping_value(db, token, slot, key.to_vec()) } + None => Ok(U256::ZERO), } } -/// Get ERC20 token balance for an account with EVM call fallback. -/// -/// First tries to read directly from storage if the balance slot is known. -/// If the balance slot is not available, falls back to executing an EVM call -/// to `balanceOf(address)` using the provided `MorphEvm` instance. -pub fn get_erc20_balance_with_evm( +/// Execute EVM `balanceOf(address)` call. +fn call_balance_of( evm: &mut MorphEvm, token: Address, account: Address, @@ -243,37 +289,46 @@ where DB: Database, I: Inspector>, { - // Fallback: Execute EVM call to balanceOf(address) - let calldata = build_balance_of_calldata(account); + let calldata = encode_balance_of(account); match evm.system_call_one(token, calldata) { - Ok(result) => { - if result.is_success() { - // Parse the returned balance (32 bytes) - if let Some(output) = result.output() - && output.len() >= 32 - { - return Ok(U256::from_be_slice(&output[..32])); - } + Ok(result) if result.is_success() => { + if let Some(output) = result.output() + && output.len() >= 32 + { + return Ok(U256::from_be_slice(&output[..32])); } Ok(U256::ZERO) } - Err(_) => { - // On error, return zero (matches original behavior) - Ok(U256::ZERO) - } + Ok(_) => Ok(U256::ZERO), + Err(_) => Ok(U256::ZERO), } } -/// Build the calldata for ERC20 balanceOf(address) call. +/// Query ERC20 balance via EVM call. +/// +/// Use this when you have a `MorphEvm` instance and need to call `balanceOf`. +pub fn erc20_balance_of( + evm: &mut MorphEvm, + token: Address, + account: Address, +) -> Result> +where + DB: Database, + I: Inspector>, +{ + call_balance_of(evm, token, account) +} + +/// Encode ERC20 `balanceOf(address)` calldata. /// -/// Method signature: `balanceOf(address) -> 0x70a08231` -pub fn build_balance_of_calldata(account: Address) -> Bytes { - let method_id = [0x70u8, 0xa0, 0x82, 0x31]; - let mut calldata = Vec::with_capacity(36); - calldata.extend_from_slice(&method_id); - calldata.extend_from_slice(&[0u8; 12]); // Pad address to 32 bytes - calldata.extend_from_slice(account.as_slice()); - Bytes::from(calldata) +/// Function selector: `0x70a08231` +pub fn encode_balance_of(account: Address) -> Bytes { + const SELECTOR: [u8; 4] = [0x70, 0xa0, 0x82, 0x31]; + let mut data = Vec::with_capacity(36); + data.extend_from_slice(&SELECTOR); + data.extend_from_slice(&[0u8; 12]); // Left-pad address + data.extend_from_slice(account.as_slice()); + Bytes::from(data) } #[cfg(test)] @@ -293,26 +348,26 @@ mod tests { } #[test] - fn test_get_mapping_slot() { + fn test_mapping_slot() { // Test that mapping slot calculation produces deterministic results let slot = U256::from(151); let key = vec![0u8; 32]; - let result1 = get_mapping_slot(slot, key.clone()); - let result2 = get_mapping_slot(slot, key); + let result1 = mapping_slot(slot, key.clone()); + let result2 = mapping_slot(slot, key); assert_eq!(result1, result2); } #[test] - fn test_get_mapping_account_slot() { + fn test_mapping_slot_for() { let slot = U256::from(1); let account = address!("1234567890123456789012345678901234567890"); - let result = get_mapping_account_slot(slot, account); + let result = mapping_slot_for(slot, account); // Result should be non-zero assert!(!result.is_zero()); } #[test] - fn test_calculate_token_amount() { + fn test_eth_to_token_amount() { let info = TokenFeeInfo { price_ratio: U256::from(2_000_000_000_000_000_000u128), // 2 ETH per token scale: U256::from(1_000_000_000_000_000_000u128), // 1e18 @@ -321,12 +376,12 @@ mod tests { // 1 ETH should give 0.5 tokens let eth_amount = U256::from(1_000_000_000_000_000_000u128); // 1 ETH - let token_amount = info.calculate_token_amount(eth_amount); + let token_amount = info.eth_to_token_amount(eth_amount); assert_eq!(token_amount, U256::from(500_000_000_000_000_000u128)); // 0.5 tokens } #[test] - fn test_calculate_token_amount_zero_ratio() { + fn test_eth_to_token_amount_zero_ratio() { let info = TokenFeeInfo { price_ratio: U256::ZERO, scale: U256::from(1_000_000_000_000_000_000u128), @@ -334,7 +389,7 @@ mod tests { }; let eth_amount = U256::from(1_000_000_000_000_000_000u128); - let token_amount = info.calculate_token_amount(eth_amount); + let token_amount = info.eth_to_token_amount(eth_amount); assert_eq!(token_amount, U256::ZERO); } @@ -356,13 +411,13 @@ mod tests { } #[test] - fn test_build_balance_of_calldata() { + fn test_encode_balance_of() { let account = address!("1234567890123456789012345678901234567890"); - let calldata = build_balance_of_calldata(account); + let calldata = encode_balance_of(account); - // Should be 4 bytes method id + 32 bytes address = 36 bytes + // Should be 4 bytes selector + 32 bytes address = 36 bytes assert_eq!(calldata.len(), 36); - // First 4 bytes should be the method id + // First 4 bytes should be the selector assert_eq!(&calldata[0..4], &[0x70, 0xa0, 0x82, 0x31]); // Last 20 bytes should be the address assert_eq!(&calldata[16..36], account.as_slice()); diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml new file mode 100644 index 0000000..889991a --- /dev/null +++ b/crates/rpc/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "morph-rpc" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +publish.workspace = true + +[lints] +workspace = true + +[dependencies] +# Morph crates +morph-primitives = { workspace = true, features = ["serde-bincode-compat", "reth-codec"] } +morph-chainspec.workspace = true +morph-revm = { workspace = true, features = ["rpc"] } +morph-evm = { workspace = true, features = ["rpc"] } + +# Reth +reth-chainspec.workspace = true +reth-node-api.workspace = true +reth-node-builder.workspace = true +reth-primitives-traits.workspace = true +reth-rpc.workspace = true +reth-rpc-convert.workspace = true +reth-rpc-eth-api.workspace = true +reth-rpc-eth-types.workspace = true +reth-evm.workspace = true +reth-provider.workspace = true +reth-revm.workspace = true +reth-tasks.workspace = true +reth-errors.workspace = true +reth-transaction-pool.workspace = true + +# Alloy +alloy-consensus.workspace = true +alloy-primitives.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-serde.workspace = true +alloy-network.workspace = true +alloy-eips.workspace = true + +# JSON-RPC +jsonrpsee.workspace = true + +# Serialization +serde.workspace = true +derive_more.workspace = true +tokio.workspace = true +revm.workspace = true + +# Error handling +thiserror.workspace = true +eyre.workspace = true + +# Logging +tracing.workspace = true + + +[features] +default = [] diff --git a/crates/rpc/src/error.rs b/crates/rpc/src/error.rs new file mode 100644 index 0000000..a0f2b57 --- /dev/null +++ b/crates/rpc/src/error.rs @@ -0,0 +1,200 @@ +//! Error types for Morph RPC + +use alloy_primitives::B256; +use morph_evm::MorphEvmConfig; +use reth_errors::ProviderError; +use reth_evm::revm::context::result::EVMError; +use reth_evm::{HaltReasonFor, InvalidTxError}; +use reth_rpc_convert::TransactionConversionError; +use reth_rpc_eth_types::{ + EthApiError, + error::{AsEthApiError, api::FromEvmHalt, api::FromRevert}, +}; +use std::convert::Infallible; +use thiserror::Error; + +/// Extension trait for converting `Result` where `E: Into` to `Result`. +pub trait ToMorphErr { + /// Convert the error to `MorphEthApiError`. + fn to_morph_err(self) -> Result; +} + +impl> ToMorphErr for Result { + fn to_morph_err(self) -> Result { + self.map_err(|e| MorphEthApiError::Eth(e.into())) + } +} + +/// Morph Eth API errors +#[derive(Debug, Error)] +pub enum MorphEthApiError { + /// Inner eth API error + #[error(transparent)] + Eth(#[from] EthApiError), + + /// Block not found + #[error("block not found")] + BlockNotFound, + + /// Transaction not found + #[error("transaction {0} not found")] + TransactionNotFound(B256), + + /// Skipped transaction not found + #[error("skipped transaction {0} not found")] + SkippedTransactionNotFound(B256), + + /// Invalid block number or hash + #[error("invalid block number or hash")] + InvalidBlockNumberOrHash, + + /// State not available for block + #[error("state not available for block")] + StateNotAvailable, + + /// Internal error + #[error("internal error: {0}")] + Internal(String), + + /// Database error + #[error("database error: {0}")] + Database(String), + + /// Provider error + #[error("provider error: {0}")] + Provider(String), + + // ========== Gas estimation errors ========== + /// Insufficient funds for L1 data fee + #[error("insufficient funds for l1 fee")] + InsufficientFundsForL1Fee, + + /// Insufficient funds for value transfer + #[error("insufficient funds for transfer")] + InsufficientFundsForTransfer, + + /// Invalid fee token (not registered, inactive, or invalid configuration) + #[error("invalid token")] + InvalidFeeToken, +} + +/// Converts [`MorphEthApiError`] to a JSON-RPC error object. +impl From for jsonrpsee::types::ErrorObject<'static> { + fn from(err: MorphEthApiError) -> Self { + match err { + MorphEthApiError::Eth(e) => e.into(), + MorphEthApiError::BlockNotFound => { + jsonrpsee::types::ErrorObject::owned(-32001, "Block not found", None::<()>) + } + MorphEthApiError::TransactionNotFound(hash) => jsonrpsee::types::ErrorObject::owned( + -32002, + format!("Transaction {hash} not found"), + None::<()>, + ), + MorphEthApiError::SkippedTransactionNotFound(hash) => { + jsonrpsee::types::ErrorObject::owned( + -32003, + format!("Skipped transaction {hash} not found"), + None::<()>, + ) + } + MorphEthApiError::InvalidBlockNumberOrHash => jsonrpsee::types::ErrorObject::owned( + -32004, + "Invalid block number or hash", + None::<()>, + ), + MorphEthApiError::StateNotAvailable => jsonrpsee::types::ErrorObject::owned( + -32005, + "State not available for block", + None::<()>, + ), + MorphEthApiError::Internal(msg) => jsonrpsee::types::ErrorObject::owned( + -32603, + format!("Internal error: {msg}"), + None::<()>, + ), + MorphEthApiError::Database(msg) => jsonrpsee::types::ErrorObject::owned( + -32006, + format!("Database error: {msg}"), + None::<()>, + ), + MorphEthApiError::Provider(msg) => jsonrpsee::types::ErrorObject::owned( + -32007, + format!("Provider error: {msg}"), + None::<()>, + ), + MorphEthApiError::InsufficientFundsForL1Fee => jsonrpsee::types::ErrorObject::owned( + -32008, + "insufficient funds for l1 fee", + None::<()>, + ), + MorphEthApiError::InsufficientFundsForTransfer => jsonrpsee::types::ErrorObject::owned( + -32009, + "insufficient funds for transfer", + None::<()>, + ), + MorphEthApiError::InvalidFeeToken => { + jsonrpsee::types::ErrorObject::owned(-32010, "invalid token", None::<()>) + } + } + } +} + +/// Extracts the inner [`EthApiError`] if present. +impl AsEthApiError for MorphEthApiError { + fn as_err(&self) -> Option<&EthApiError> { + match self { + Self::Eth(err) => Some(err), + _ => None, + } + } +} + +// Note: `FromEthApiError` is auto-implemented via blanket impl for any `T: From`. +// We get it for free since we have `#[from] EthApiError` above. + +/// Converts EVM halt reasons to [`MorphEthApiError`]. +impl FromEvmHalt> for MorphEthApiError { + fn from_evm_halt(halt: HaltReasonFor, gas_limit: u64) -> Self { + Self::Eth(EthApiError::from_evm_halt(halt, gas_limit)) + } +} + +/// Converts EVM revert output to [`MorphEthApiError`]. +impl FromRevert for MorphEthApiError { + fn from_revert(output: alloy_primitives::Bytes) -> Self { + Self::Eth(EthApiError::from_revert(output)) + } +} + +/// Converts [`ProviderError`] to [`MorphEthApiError`]. +impl From for MorphEthApiError { + fn from(err: ProviderError) -> Self { + Self::Eth(err.into()) + } +} + +/// Converts [`EVMError`] to [`MorphEthApiError`]. +impl From> for MorphEthApiError +where + T: Into, + TxError: InvalidTxError, +{ + fn from(err: EVMError) -> Self { + Self::Eth(err.into()) + } +} + +/// Converts [`TransactionConversionError`] to [`MorphEthApiError`]. +impl From for MorphEthApiError { + fn from(err: TransactionConversionError) -> Self { + Self::Eth(err.into()) + } +} + +/// Infallible conversion (never fails). +impl From for MorphEthApiError { + fn from(err: Infallible) -> Self { + match err {} + } +} diff --git a/crates/rpc/src/eth/call.rs b/crates/rpc/src/eth/call.rs new file mode 100644 index 0000000..b368ada --- /dev/null +++ b/crates/rpc/src/eth/call.rs @@ -0,0 +1,254 @@ +//! Morph `eth_call` and `eth_estimateGas` overrides. + +use crate::MorphEthApiError; +use crate::error::ToMorphErr; +use crate::eth::{MorphEthApi, MorphNodeCore}; +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::{ + 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 + N: MorphNodeCore, + N::Provider: ChainSpecProvider, + Rpc: + reth_rpc_convert::RpcConvert, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, +{ +} + +impl EstimateCall for MorphEthApi +where + N: MorphNodeCore, + N::Provider: ChainSpecProvider, + Rpc: + reth_rpc_convert::RpcConvert, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, +{ +} + +impl Call for MorphEthApi +where + N: MorphNodeCore, + N::Provider: ChainSpecProvider, + Rpc: + reth_rpc_convert::RpcConvert, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, +{ + fn call_gas_limit(&self) -> u64 { + self.eth_api().gas_cap() + } + + fn max_simulate_blocks(&self) -> u64 { + self.eth_api().max_simulate_blocks() + } + + 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> { + let caller = tx_env.caller(); + let balance = db + .basic(caller) + .to_morph_err()? + .map(|acc| acc.balance) + .unwrap_or_default(); + + let value = tx_env.value(); + if value > balance { + return Err(MorphEthApiError::InsufficientFundsForTransfer); + } + + 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, + caller, + balance, + value, + l1_fee, + fee_token_id, + tx_env.fee_limit, + tx_env.gas_price(), + ); + } + + caller_gas_allowance_with_eth(balance, value, l1_fee, tx_env.gas_price()) + } +} + +impl MorphEthApi +where + N: MorphNodeCore, + N::Provider: ChainSpecProvider, + Rpc: + reth_rpc_convert::RpcConvert, +{ + /// Estimates the L1 data fee for the given transaction. + /// + /// Returns zero for L1 message transactions since they don't pay L1 fees. + fn estimate_l1_fee( + &self, + db: &mut DB, + evm_env: &EvmEnvFor<::Evm>, + tx_env: &TxEnvFor<::Evm>, + ) -> Result + where + DB: Database, + DB::Error: Into, + { + let block_number = u64::try_from(evm_env.block_env.number) + .map_err(|_| EthApiError::InvalidParams("invalid block number".to_string()))?; + let timestamp = u64::try_from(evm_env.block_env.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); + + if tx_env.is_l1_msg() { + return Ok(U256::ZERO); + } + + 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)) + } + + /// Calculate caller's gas allowance when paying with ERC20 tokens. + /// + /// Uses storage-only reads. For tokens without a known `balance_slot`, + /// skips the token balance limit (EVM handler will verify during execution). + #[allow(clippy::too_many_arguments)] + fn caller_gas_allowance_with_token( + &self, + db: &mut DB, + caller: alloy_primitives::Address, + balance: U256, + value: U256, + l1_fee: U256, + fee_token_id: u16, + fee_limit: Option, + gas_price: u128, + ) -> Result + where + DB: Database, + DB::Error: Into, + { + let token_fee_info = TokenFeeInfo::fetch_storage_only(db, fee_token_id, caller) + .map_err(|_| MorphEthApiError::InvalidFeeToken)? + .ok_or(MorphEthApiError::InvalidFeeToken)?; + + // Validate token is registered and active + if !token_fee_info.is_active + || token_fee_info.price_ratio.is_zero() + || token_fee_info.scale.is_zero() + { + return Err(MorphEthApiError::InvalidFeeToken); + } + + // Calculate base ETH allowance (for tx.value transfer) + let eth_allowance = caller_gas_allowance_with_eth(balance, value, U256::ZERO, gas_price)?; + + // If balance_slot is unknown, we cannot accurately read the token balance + // via storage. Skip the token balance limit and let the EVM handler + // (`validate_and_deduct_token_fee`) verify the actual balance. + if token_fee_info.balance_slot.is_none() { + tracing::debug!( + target: "morph::rpc", + token_id = fee_token_id, + "Token balance_slot unknown, skipping token balance limit in caller_gas_allowance" + ); + return Ok(eth_allowance); + } + + // Calculate token-based gas allowance + let limit = match fee_limit { + Some(limit) if !limit.is_zero() => token_fee_info.balance.min(limit), + _ => token_fee_info.balance, + }; + + let l1_fee_in_token = token_fee_info.eth_to_token_amount(l1_fee); + if l1_fee_in_token >= limit { + return Err(MorphEthApiError::InsufficientFundsForL1Fee); + } + + let available_token = limit - l1_fee_in_token; + let available_eth = token_amount_to_eth(available_token, &token_fee_info) + .ok_or(MorphEthApiError::InvalidFeeToken)?; + + let token_allowance = gas_allowance_from_balance(available_eth, gas_price); + Ok(eth_allowance.min(token_allowance)) + } +} + +/// Calculates the caller's gas allowance when paying with ETH. +/// +/// Subtracts the transaction value and L1 fee from the balance, +/// then converts the remaining balance to gas units. +fn caller_gas_allowance_with_eth( + balance: U256, + value: U256, + l1_fee: U256, + gas_price: u128, +) -> Result { + let mut available = balance.saturating_sub(value); + if l1_fee >= available { + return Err(MorphEthApiError::InsufficientFundsForL1Fee); + } + available -= l1_fee; + Ok(gas_allowance_from_balance(available, gas_price)) +} + +/// Converts a balance to gas units based on the gas price. +/// +/// Returns `u64::MAX` if gas price is zero (unlimited gas). +fn gas_allowance_from_balance(balance: U256, gas_price: u128) -> u64 { + if gas_price == 0 { + return u64::MAX; + } + let gas_price = U256::from(gas_price); + let allowance = balance / gas_price; + if allowance > U256::from(u64::MAX) { + u64::MAX + } else { + allowance.to::() + } +} + +/// Converts a token amount to ETH equivalent using the fee token info. +/// +/// Uses the formula: `eth = token_amount * price_ratio / scale`. +/// Returns `None` if price_ratio or scale is zero. +fn token_amount_to_eth(token_amount: U256, info: &TokenFeeInfo) -> Option { + if info.price_ratio.is_zero() || info.scale.is_zero() { + return None; + } + let (eth_amount, remainder) = token_amount + .saturating_mul(info.price_ratio) + .div_rem(info.scale); + if remainder.is_zero() { + Some(eth_amount) + } else { + Some(eth_amount.saturating_add(U256::from(1))) + } +} diff --git a/crates/rpc/src/eth/mod.rs b/crates/rpc/src/eth/mod.rs new file mode 100644 index 0000000..4aa1f30 --- /dev/null +++ b/crates/rpc/src/eth/mod.rs @@ -0,0 +1,390 @@ +//! Morph `eth_` RPC wiring and conversions. + +use crate::MorphEthApiError; +use crate::eth::receipt::MorphReceiptConverter; +use crate::types::{MorphRpcReceipt, MorphRpcTransaction, MorphTransactionRequest}; +use alloy_rpc_types_eth::Header as RpcHeader; +use eyre::Result; +use morph_chainspec::MorphChainSpec; +use morph_evm::MorphEvmConfig; +use morph_primitives::{MorphHeader, MorphPrimitives}; +use reth_evm::ConfigureEvm; +use reth_node_api::{FullNodeComponents, FullNodeTypes, HeaderTy, NodeTypes}; +use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx}; +use reth_provider::ChainSpecProvider; +use reth_rpc::EthApi; +use reth_rpc_convert::{RpcConvert, RpcConverter, RpcTypes}; +use reth_rpc_eth_api::{ + EthApiTypes, RpcNodeCore, RpcNodeCoreExt, + helpers::{ + EthApiSpec, EthBlocks, EthFees, EthState, EthTransactions, LoadBlock, LoadFee, + LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace, + pending_block::{BuildPendingEnv, PendingEnvBuilder}, + }, +}; +use reth_rpc_eth_types::{EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle}; +use reth_tasks::{ + TaskSpawner, + pool::{BlockingTaskGuard, BlockingTaskPool}, +}; +use std::{fmt, marker::PhantomData, sync::Arc, time::Duration}; + +pub mod call; +pub mod receipt; +pub mod transaction; + +// ===== RPC type wiring ===== +/// Morph RPC response type definitions. +#[derive(Clone, Debug, Default)] +pub struct MorphRpcTypes; + +impl RpcTypes for MorphRpcTypes { + type Header = RpcHeader; + type Receipt = MorphRpcReceipt; + type TransactionResponse = MorphRpcTransaction; + type TransactionRequest = MorphTransactionRequest; +} + +/// Morph RPC converter with custom receipt and header conversion. +pub type MorphRpcConverter = + RpcConverter::Evm, MorphReceiptConverter>; + +// ===== Builder entrypoint ===== +/// Builder for Morph `eth_` RPC that installs Morph RPC conversions. +#[derive(Debug)] +pub struct MorphEthApiBuilder { + _nt: PhantomData, +} + +impl Default for MorphEthApiBuilder { + fn default() -> Self { + Self { _nt: PhantomData } + } +} + +impl MorphEthApiBuilder { + /// Creates a new builder. + pub fn new() -> Self { + Self::default() + } +} + +impl EthApiBuilder for MorphEthApiBuilder +where + N: FullNodeComponents< + Evm = MorphEvmConfig, + Evm: ConfigureEvm>>, + Types: NodeTypes, + Provider: ChainSpecProvider, + > + crate::eth::MorphNodeCore + + reth_rpc_eth_api::RpcNodeCore< + Provider = ::Provider, + Pool = ::Pool, + >, + NetworkT: RpcTypes< + Header = RpcHeader, + Receipt = MorphRpcReceipt, + TransactionResponse = MorphRpcTransaction, + TransactionRequest = MorphTransactionRequest, + >, + MorphRpcConverter: reth_rpc_convert::RpcConvert< + Network = NetworkT, + Primitives = ::Primitives, + Error = reth_rpc_eth_types::EthApiError, + Evm = ::Evm, + >, +{ + type EthApi = MorphEthApi>; + + async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> Result { + let rpc_converter = RpcConverter::new(MorphReceiptConverter::default()); + Ok(MorphEthApi::new( + ctx.eth_api_builder() + .with_rpc_converter(rpc_converter) + .build(), + )) + } +} + +// ===== Core API wrapper ===== +/// Adapter for [`EthApi`], which holds all the data required to serve core `eth_` API. +pub type EthApiNodeBackend = EthApi; + +/// A helper trait with requirements for [`RpcNodeCore`] to be used in [`MorphEthApi`]. +pub trait MorphNodeCore: RpcNodeCore {} +impl MorphNodeCore for T where T: RpcNodeCore {} + +/// Morph `Eth` API implementation. +/// +/// This wraps a default `Eth` implementation, and provides additional functionality +/// where the Morph spec deviates from the default (ethereum) spec, e.g. L1 fee +/// and ERC20 fee token support in `eth_estimateGas`. +pub struct MorphEthApi { + /// Gateway to node's core components. + inner: Arc>, +} + +impl Clone for MorphEthApi { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl MorphEthApi { + /// Creates a new [`MorphEthApi`]. + pub fn new(eth_api: EthApiNodeBackend) -> Self { + let inner = Arc::new(MorphEthApiInner { eth_api }); + Self { inner } + } + + /// Returns a reference to the [`EthApiNodeBackend`]. + pub fn eth_api(&self) -> &EthApiNodeBackend { + self.inner.eth_api() + } +} + +// ===== Trait implementations ===== +impl EthApiTypes for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, +{ + type Error = MorphEthApiError; + type NetworkTypes = Rpc::Network; + type RpcConvert = Rpc; + + fn converter(&self) -> &Self::RpcConvert { + self.inner.eth_api.converter() + } +} + +impl RpcNodeCore for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, +{ + type Primitives = N::Primitives; + type Provider = N::Provider; + type Pool = N::Pool; + type Evm = ::Evm; + type Network = ::Network; + + #[inline] + fn pool(&self) -> &Self::Pool { + self.inner.eth_api.pool() + } + + #[inline] + fn evm_config(&self) -> &Self::Evm { + self.inner.eth_api.evm_config() + } + + #[inline] + fn network(&self) -> &Self::Network { + self.inner.eth_api.network() + } + + #[inline] + fn provider(&self) -> &Self::Provider { + self.inner.eth_api.provider() + } +} + +impl RpcNodeCoreExt for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, +{ + #[inline] + fn cache(&self) -> &EthStateCache { + self.inner.eth_api.cache() + } +} + +impl EthApiSpec for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, +{ + #[inline] + fn starting_block(&self) -> alloy_primitives::U256 { + self.inner.eth_api.starting_block() + } +} + +impl SpawnBlocking for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, +{ + #[inline] + fn io_task_spawner(&self) -> impl TaskSpawner { + self.inner.eth_api.task_spawner() + } + + #[inline] + fn tracing_task_pool(&self) -> &BlockingTaskPool { + self.inner.eth_api.blocking_task_pool() + } + + #[inline] + fn tracing_task_guard(&self) -> &BlockingTaskGuard { + self.inner.eth_api.blocking_task_guard() + } + + #[inline] + fn blocking_io_task_guard(&self) -> &std::sync::Arc { + self.inner.eth_api.blocking_io_request_semaphore() + } +} + +impl LoadFee for MorphEthApi +where + N: MorphNodeCore, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, + Rpc: RpcConvert, +{ + #[inline] + fn gas_oracle(&self) -> &GasPriceOracle { + self.inner.eth_api.gas_oracle() + } + + #[inline] + fn fee_history_cache(&self) -> &FeeHistoryCache> { + self.inner.eth_api.fee_history_cache() + } +} + +impl LoadPendingBlock for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, +{ + fn pending_block( + &self, + ) -> &tokio::sync::Mutex>> { + self.inner.eth_api.pending_block() + } + + fn pending_env_builder(&self) -> &dyn PendingEnvBuilder { + self.inner.eth_api.pending_env_builder() + } + + fn pending_block_kind(&self) -> reth_rpc_eth_types::builder::config::PendingBlockKind { + self.inner.eth_api.pending_block_kind() + } +} + +impl LoadBlock for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, +{ +} + +impl EthBlocks for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, +{ +} + +impl LoadTransaction for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, +{ +} + +impl EthTransactions for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, +{ + #[inline] + fn signers( + &self, + ) -> &reth_rpc_eth_api::helpers::spec::SignersForRpc { + self.inner.eth_api.signers() + } + + #[inline] + fn send_raw_transaction_sync_timeout(&self) -> Duration { + self.inner.eth_api.send_raw_transaction_sync_timeout() + } + + async fn send_transaction( + &self, + 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) + } +} + +impl EthFees for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, + MorphEthApiError: reth_rpc_eth_types::error::FromEvmError, +{ +} + +impl LoadState for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, + Self: LoadPendingBlock, +{ +} + +impl EthState for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, + Self: LoadPendingBlock, +{ + #[inline] + fn max_proof_window(&self) -> u64 { + self.inner.eth_api.eth_proof_window() + } +} + +impl Trace 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 { + f.debug_struct("MorphEthApi").finish_non_exhaustive() + } +} + +/// Container type `MorphEthApi` +#[allow(missing_debug_implementations)] +pub struct MorphEthApiInner { + /// Gateway to node's core components. + pub eth_api: EthApiNodeBackend, +} + +impl MorphEthApiInner { + /// Returns a reference to the [`EthApiNodeBackend`]. + const fn eth_api(&self) -> &EthApiNodeBackend { + &self.eth_api + } +} diff --git a/crates/rpc/src/eth/receipt.rs b/crates/rpc/src/eth/receipt.rs new file mode 100644 index 0000000..3f378b3 --- /dev/null +++ b/crates/rpc/src/eth/receipt.rs @@ -0,0 +1,140 @@ +//! Morph receipt conversion for `eth_` RPC responses. + +use crate::eth::{MorphEthApi, MorphNodeCore}; +use crate::types::receipt::MorphRpcReceipt; +use alloy_consensus::{Receipt, TxReceipt}; +use alloy_primitives::{U64, U256}; +use alloy_rpc_types_eth::Log; +use morph_primitives::{MorphReceipt, MorphReceiptEnvelope}; +use reth_primitives_traits::NodePrimitives; +use reth_rpc_convert::{ + RpcConvert, + transaction::{ConvertReceiptInput, ReceiptConverter}, +}; +use reth_rpc_eth_api::helpers::LoadReceipt; +use reth_rpc_eth_types::{EthApiError, receipt::build_receipt}; +use std::fmt::Debug; + +/// Converter for Morph receipts. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct MorphReceiptConverter; + +impl ReceiptConverter for MorphReceiptConverter +where + N: NodePrimitives, +{ + type RpcReceipt = MorphRpcReceipt; + type Error = reth_rpc_eth_types::EthApiError; + + fn convert_receipts( + &self, + inputs: Vec>, + ) -> Result, Self::Error> { + let mut receipts = Vec::with_capacity(inputs.len()); + for input in inputs { + receipts.push(MorphReceiptBuilder::new(input).build()); + } + Ok(receipts) + } +} + +/// Builds a [`MorphRpcReceipt`]. +#[derive(Debug)] +struct MorphReceiptBuilder { + receipt: MorphRpcReceipt, +} + +impl MorphReceiptBuilder { + /// Creates a new builder from a receipt conversion input. + fn new(input: ConvertReceiptInput<'_, N>) -> Self + where + N: NodePrimitives, + { + let (l1_fee, fee_token_id, fee_rate, token_scale, fee_limit) = + morph_fee_fields(&input.receipt); + + let core_receipt = build_receipt(input, None, |receipt, next_log_index, meta| { + let map_logs = |receipt: Receipt| { + let Receipt { + status, + cumulative_gas_used, + logs, + } = receipt; + let logs = Log::collect_for_receipt(next_log_index, meta, logs); + Receipt { + status, + cumulative_gas_used, + logs, + } + }; + + match receipt { + MorphReceipt::Legacy(receipt) => { + MorphReceiptEnvelope::Legacy(map_logs(receipt.inner).into_with_bloom()) + } + MorphReceipt::Eip2930(receipt) => { + MorphReceiptEnvelope::Eip2930(map_logs(receipt.inner).into_with_bloom()) + } + MorphReceipt::Eip1559(receipt) => { + MorphReceiptEnvelope::Eip1559(map_logs(receipt.inner).into_with_bloom()) + } + MorphReceipt::Eip7702(receipt) => { + MorphReceiptEnvelope::Eip7702(map_logs(receipt.inner).into_with_bloom()) + } + MorphReceipt::L1Msg(receipt) => { + MorphReceiptEnvelope::L1Message(map_logs(receipt).into_with_bloom()) + } + MorphReceipt::Morph(receipt) => { + MorphReceiptEnvelope::Morph(map_logs(receipt.inner).into_with_bloom()) + } + } + }); + + let receipt = MorphRpcReceipt { + inner: core_receipt, + l1_fee, + fee_rate, + token_scale, + fee_limit, + fee_token_id: fee_token_id.map(U64::from), + }; + + Self { receipt } + } + + /// Consumes the builder and returns the built receipt. + fn build(self) -> MorphRpcReceipt { + self.receipt + } +} + +impl LoadReceipt for MorphEthApi +where + N: MorphNodeCore, + Rpc: RpcConvert, +{ +} + +/// Extracts Morph-specific fee fields from a receipt. +/// +/// Returns a tuple of (l1_fee, fee_token_id, fee_rate, token_scale, fee_limit). +/// L1 message receipts return zero/None for all fee fields. +fn morph_fee_fields( + receipt: &MorphReceipt, +) -> (U256, Option, Option, Option, Option) { + match receipt { + MorphReceipt::Legacy(r) + | MorphReceipt::Eip2930(r) + | MorphReceipt::Eip1559(r) + | MorphReceipt::Eip7702(r) + | MorphReceipt::Morph(r) => ( + r.l1_fee, + r.fee_token_id, + r.fee_rate, + r.token_scale, + r.fee_limit, + ), + MorphReceipt::L1Msg(_) => (U256::ZERO, None, None, None, None), + } +} diff --git a/crates/rpc/src/eth/transaction.rs b/crates/rpc/src/eth/transaction.rs new file mode 100644 index 0000000..11b0318 --- /dev/null +++ b/crates/rpc/src/eth/transaction.rs @@ -0,0 +1,276 @@ +//! Morph transaction conversion for `eth_` RPC responses. + +use crate::MorphTransactionRequest; +use crate::types::transaction::MorphRpcTransaction; +use alloy_consensus::{ + EthereumTxEnvelope, SignableTransaction, Transaction, TxEip4844, transaction::Recovered, +}; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::TxSigner; +use alloy_primitives::{Address, Bytes, Signature, TxKind, U64, U256}; +use alloy_rpc_types_eth::{AccessList, Transaction as RpcTransaction, TransactionInfo}; +use reth_rpc_convert::{ + SignTxRequestError, SignableTxRequest, TryIntoSimTx, TryIntoTxEnv, transaction::FromConsensusTx, +}; +use reth_rpc_eth_types::EthApiError; +use revm::context::Transaction as RevmTransaction; +use std::convert::Infallible; + +use morph_primitives::{MorphTxEnvelope, TxMorph}; +use morph_revm::{MorphBlockEnv, MorphTxEnv}; +use reth_evm::EvmEnv; + +/// Converts a consensus [`MorphTxEnvelope`] to an RPC [`MorphRpcTransaction`]. +impl FromConsensusTx for MorphRpcTransaction { + type TxInfo = TransactionInfo; + type Err = Infallible; + + fn from_consensus_tx( + tx: MorphTxEnvelope, + signer: Address, + tx_info: Self::TxInfo, + ) -> Result { + let (sender, queue_index) = match &tx { + MorphTxEnvelope::L1Msg(msg) => (Some(msg.sender), Some(U64::from(msg.queue_index))), + _ => (None, None), + }; + let fee_token_id = tx.fee_token_id().map(U64::from); + let fee_limit = tx.fee_limit(); + + let effective_gas_price = tx_info.base_fee.map(|base_fee| { + tx.effective_tip_per_gas(base_fee) + .unwrap_or_default() + .saturating_add(base_fee as u128) + }); + + let inner = RpcTransaction { + inner: Recovered::new_unchecked(tx, signer), + block_hash: tx_info.block_hash, + block_number: tx_info.block_number, + transaction_index: tx_info.index, + effective_gas_price, + }; + + Ok(Self { + inner, + sender, + queue_index, + fee_token_id, + fee_limit, + }) + } +} + +/// Converts a [`MorphTransactionRequest`] into a simulated transaction envelope. +/// +/// Handles both standard Ethereum transactions and Morph-specific fee token transactions. +impl TryIntoSimTx for MorphTransactionRequest { + fn try_into_sim_tx(self) -> Result> { + if let Some(fee_token_id) = self.fee_token_id.filter(|id| id.to::() > 0) { + let morph_tx = build_morph_tx_from_request( + &self.inner, + fee_token_id, + self.fee_limit.unwrap_or_default(), + ) + .map_err(|err| alloy_consensus::error::ValueError::new(self, err))?; + let signature = Signature::new(Default::default(), Default::default(), false); + return Ok(MorphTxEnvelope::Morph(morph_tx.into_signed(signature))); + } + + let inner = self.inner.clone(); + let envelope = inner.build_typed_simulate_transaction().map_err(|err| { + err.map(|inner| Self { + inner, + fee_token_id: self.fee_token_id, + fee_limit: self.fee_limit, + }) + })?; + morph_envelope_from_ethereum(envelope) + .map_err(|err| alloy_consensus::error::ValueError::new(self, err)) + } +} + +/// Builds and signs a transaction from an RPC request. +/// +/// Supports both standard Ethereum transactions and Morph fee token transactions. +impl SignableTxRequest for MorphTransactionRequest { + async fn try_build_and_sign( + self, + signer: impl TxSigner + Send, + ) -> Result { + if let Some(fee_token_id) = self.fee_token_id.filter(|id| id.to::() > 0) { + let mut morph_tx = build_morph_tx_from_request( + &self.inner, + fee_token_id, + self.fee_limit.unwrap_or_default(), + ) + .map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + let signature = signer.sign_transaction(&mut morph_tx).await?; + return Ok(MorphTxEnvelope::Morph(morph_tx.into_signed(signature))); + } + + let mut tx = self + .inner + .build_typed_tx() + .map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + let signature = signer.sign_transaction(&mut tx).await?; + let signed_envelope: EthereumTxEnvelope = + EthereumTxEnvelope::new_unhashed(tx, signature).into(); + morph_envelope_from_ethereum(signed_envelope) + .map_err(|_| SignTxRequestError::InvalidTransactionRequest) + } +} + +/// Converts a transaction request into a transaction environment for EVM execution. +/// +/// Also encodes the transaction for L1 fee calculation. +impl TryIntoTxEnv for MorphTransactionRequest { + type Err = EthApiError; + + fn try_into_tx_env( + self, + evm_env: &EvmEnv, + ) -> Result { + let fee_token_id = self.fee_token_id; + let fee_limit = self.fee_limit; + let inner = self.inner; + let access_list = inner.access_list.clone().unwrap_or_default(); + + let inner_tx_env = inner + .clone() + .try_into_tx_env(evm_env) + .map_err(EthApiError::from)?; + + let mut tx_env = MorphTxEnv::new(inner_tx_env); + tx_env.fee_token_id = match fee_token_id { + Some(id) => Some( + u16::try_from(id.to::()) + .map_err(|_| EthApiError::InvalidParams("invalid token".to_string()))?, + ), + None => None, + }; + tx_env.fee_limit = fee_limit; + if tx_env.fee_token_id.unwrap_or_default() > 0 { + tx_env.inner.tx_type = morph_primitives::MORPH_TX_TYPE_ID; + } + + let rlp_bytes = encode_tx_for_l1_fee(&tx_env, access_list, evm_env, inner)?; + + tx_env.rlp_bytes = Some(rlp_bytes); + Ok(tx_env) + } +} + +/// Converts an Ethereum transaction envelope to a Morph envelope. +/// +/// EIP-4844 blob transactions are not supported on Morph. +fn morph_envelope_from_ethereum( + env: EthereumTxEnvelope, +) -> Result { + match env { + EthereumTxEnvelope::Legacy(tx) => Ok(MorphTxEnvelope::Legacy(tx)), + EthereumTxEnvelope::Eip2930(tx) => Ok(MorphTxEnvelope::Eip2930(tx)), + EthereumTxEnvelope::Eip1559(tx) => Ok(MorphTxEnvelope::Eip1559(tx)), + EthereumTxEnvelope::Eip7702(tx) => Ok(MorphTxEnvelope::Eip7702(tx)), + EthereumTxEnvelope::Eip4844(_) => Err("EIP-4844 transactions are not supported on Morph"), + } +} + +/// Builds a [`TxMorph`] from an RPC transaction request. +/// +/// Extracts fields from the request and constructs a Morph transaction +/// with the specified fee token ID and fee limit. +fn build_morph_tx_from_request( + req: &alloy_rpc_types_eth::TransactionRequest, + fee_token_id: U64, + fee_limit: U256, +) -> Result { + let chain_id = req + .chain_id + .ok_or("missing chain_id for morph transaction")?; + let fee_token_id = u16::try_from(fee_token_id.to::()).map_err(|_| "invalid token")?; + let gas_limit = req.gas.unwrap_or_default() as u128; + let nonce = req.nonce.unwrap_or_default(); + let max_fee_per_gas = req.max_fee_per_gas.or(req.gas_price).unwrap_or_default(); + let max_priority_fee_per_gas = req.max_priority_fee_per_gas.unwrap_or_default(); + let access_list: AccessList = req.access_list.clone().unwrap_or_default(); + let input = req.input.clone().into_input().unwrap_or_default(); + let to = req.to.unwrap_or(TxKind::Create); + + Ok(TxMorph { + chain_id, + nonce, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value: req.value.unwrap_or_default(), + access_list, + input, + fee_token_id, + fee_limit, + }) +} + +/// Builds a [`TxMorph`] from an existing transaction environment. +/// +/// Used for encoding transactions for L1 fee calculation. +fn build_morph_tx_from_env( + tx_env: &MorphTxEnv, + fee_token_id: U64, + fee_limit: U256, + access_list: AccessList, + evm_env: &EvmEnv, +) -> Result { + let fee_token_id = u16::try_from(fee_token_id.to::()) + .map_err(|_| EthApiError::InvalidParams("invalid token".to_string()))?; + let chain_id = tx_env.chain_id().unwrap_or(evm_env.cfg_env.chain_id); + let input = tx_env.input().clone(); + let to = tx_env.kind(); + let max_fee_per_gas = tx_env.max_fee_per_gas(); + let max_priority_fee_per_gas = tx_env.max_priority_fee_per_gas().unwrap_or_default(); + + Ok(TxMorph { + chain_id, + nonce: tx_env.nonce(), + gas_limit: tx_env.gas_limit() as u128, + max_fee_per_gas, + max_priority_fee_per_gas, + to, + value: tx_env.value(), + access_list, + input, + fee_token_id, + fee_limit, + }) +} + +/// Encodes a transaction for L1 fee calculation. +/// +/// Returns the RLP-encoded bytes used to calculate the L1 data fee. +fn encode_tx_for_l1_fee( + tx_env: &MorphTxEnv, + access_list: AccessList, + evm_env: &EvmEnv, + inner: alloy_rpc_types_eth::TransactionRequest, +) -> Result { + if tx_env.fee_token_id.unwrap_or_default() > 0 { + let fee_token_id = U64::from(tx_env.fee_token_id.unwrap_or_default()); + let fee_limit = tx_env.fee_limit.unwrap_or_default(); + let morph_tx = + build_morph_tx_from_env(tx_env, fee_token_id, fee_limit, access_list, evm_env)?; + Ok(encode_2718(morph_tx)) + } else { + let envelope = inner + .build_typed_simulate_transaction() + .map_err(|err| EthApiError::InvalidParams(err.to_string()))?; + Ok(encode_2718(envelope)) + } +} + +/// Encodes a transaction using EIP-2718 typed transaction encoding. +fn encode_2718(tx: T) -> Bytes { + let mut out = Vec::with_capacity(tx.encode_2718_len()); + tx.encode_2718(&mut out); + Bytes::from(out) +} diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs new file mode 100644 index 0000000..eb9cb3a --- /dev/null +++ b/crates/rpc/src/lib.rs @@ -0,0 +1,11 @@ +//! Morph RPC implementation and type conversions. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub mod error; +pub mod eth; +pub mod types; + +pub use error::MorphEthApiError; +pub use eth::{MorphEthApi, MorphEthApiBuilder, MorphRpcConverter, MorphRpcTypes}; +pub use types::*; diff --git a/crates/rpc/src/types/mod.rs b/crates/rpc/src/types/mod.rs new file mode 100644 index 0000000..e34391b --- /dev/null +++ b/crates/rpc/src/types/mod.rs @@ -0,0 +1,9 @@ +//! Morph-specific RPC types. + +pub mod receipt; +pub mod request; +pub mod transaction; + +pub use receipt::MorphRpcReceipt; +pub use request::MorphTransactionRequest; +pub use transaction::MorphRpcTransaction; diff --git a/crates/rpc/src/types/receipt.rs b/crates/rpc/src/types/receipt.rs new file mode 100644 index 0000000..86f7d32 --- /dev/null +++ b/crates/rpc/src/types/receipt.rs @@ -0,0 +1,101 @@ +//! Morph RPC receipt type. + +use alloy_network::ReceiptResponse; +use alloy_primitives::{Address, BlockHash, U64, U256}; +use alloy_rpc_types_eth::{Log, TransactionReceipt}; +use morph_primitives::MorphReceiptEnvelope; +use serde::{Deserialize, Serialize}; + +/// Morph RPC transaction receipt representation. +/// +/// Wraps the standard RPC transaction receipt and adds Morph-specific fields: +/// - L1 fee and fee token metadata +/// - Custom tx type for L1 message / Morph tx receipts +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MorphRpcReceipt { + /// Standard RPC receipt fields. + #[serde(flatten)] + pub inner: TransactionReceipt>, + + /// L1 data fee paid (in wei). + #[serde(rename = "l1Fee")] + pub l1_fee: U256, + + /// Fee rate used for token fee calculation. + #[serde(skip_serializing_if = "Option::is_none")] + pub fee_rate: Option, + + /// Token scale factor. + #[serde(skip_serializing_if = "Option::is_none")] + pub token_scale: Option, + + /// Fee limit specified in the transaction. + #[serde(skip_serializing_if = "Option::is_none")] + pub fee_limit: Option, + + /// Token ID used for fee payment. + #[serde(rename = "feeTokenID", skip_serializing_if = "Option::is_none")] + pub fee_token_id: Option, +} + +/// Implementation of [`ReceiptResponse`] for Morph receipts. +/// +/// Delegates all methods to the inner receipt. +impl ReceiptResponse for MorphRpcReceipt { + fn contract_address(&self) -> Option
{ + self.inner.contract_address + } + + fn status(&self) -> bool { + self.inner.inner.status() + } + + fn block_hash(&self) -> Option { + self.inner.block_hash + } + + fn block_number(&self) -> Option { + self.inner.block_number + } + + fn transaction_hash(&self) -> alloy_primitives::B256 { + self.inner.transaction_hash + } + + fn transaction_index(&self) -> Option { + self.inner.transaction_index() + } + + fn gas_used(&self) -> u64 { + self.inner.gas_used() + } + + fn effective_gas_price(&self) -> u128 { + self.inner.effective_gas_price() + } + + fn blob_gas_used(&self) -> Option { + self.inner.blob_gas_used() + } + + fn blob_gas_price(&self) -> Option { + self.inner.blob_gas_price() + } + + fn from(&self) -> Address { + self.inner.from() + } + + fn to(&self) -> Option
{ + self.inner.to() + } + + fn cumulative_gas_used(&self) -> u64 { + self.inner.cumulative_gas_used() + } + + fn state_root(&self) -> Option { + self.inner.state_root() + } +} diff --git a/crates/rpc/src/types/request.rs b/crates/rpc/src/types/request.rs new file mode 100644 index 0000000..354926a --- /dev/null +++ b/crates/rpc/src/types/request.rs @@ -0,0 +1,76 @@ +//! Morph RPC transaction request type. + +use alloy_primitives::{U64, U256}; +use alloy_rpc_types_eth::TransactionRequest; +use serde::{Deserialize, Serialize}; + +/// Morph RPC transaction request representation. +/// +/// Extends standard Ethereum transaction request with: +/// - `feeTokenID`: Token ID for ERC20 gas payment +/// - `feeLimit`: Maximum token amount willing to pay for fees +#[derive( + Debug, + Clone, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + derive_more::Deref, + derive_more::DerefMut, +)] +#[serde(rename_all = "camelCase")] +pub struct MorphTransactionRequest { + /// Inner [`TransactionRequest`]. + #[serde(flatten)] + #[deref] + #[deref_mut] + pub inner: TransactionRequest, + + /// Token ID for fee payment (only for MorphTx type 0x7F). + #[serde( + rename = "feeTokenID", + default, + skip_serializing_if = "Option::is_none" + )] + pub fee_token_id: Option, + + /// Maximum token amount willing to pay for fees (only for MorphTx type 0x7F). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fee_limit: Option, +} + +/// Returns a reference to the inner [`TransactionRequest`]. +impl AsRef for MorphTransactionRequest { + fn as_ref(&self) -> &TransactionRequest { + &self.inner + } +} + +/// Returns a mutable reference to the inner [`TransactionRequest`]. +impl AsMut for MorphTransactionRequest { + fn as_mut(&mut self) -> &mut TransactionRequest { + &mut self.inner + } +} + +/// Creates a [`MorphTransactionRequest`] from a standard [`TransactionRequest`]. +/// +/// Sets `fee_token_id` and `fee_limit` to `None`. +impl From for MorphTransactionRequest { + fn from(value: TransactionRequest) -> Self { + Self { + inner: value, + fee_token_id: None, + fee_limit: None, + } + } +} + +/// Extracts the inner [`TransactionRequest`] from a [`MorphTransactionRequest`]. +impl From for TransactionRequest { + fn from(value: MorphTransactionRequest) -> Self { + value.inner + } +} diff --git a/crates/rpc/src/types/transaction.rs b/crates/rpc/src/types/transaction.rs new file mode 100644 index 0000000..d12e860 --- /dev/null +++ b/crates/rpc/src/types/transaction.rs @@ -0,0 +1,152 @@ +//! Morph RPC transaction type. + +use alloy_consensus::Transaction as ConsensusTransaction; +use alloy_consensus::Transaction as TransactionTrait; +use alloy_eips::Typed2718; +use alloy_network::TransactionResponse; +use alloy_primitives::{Address, BlockHash, TxKind, U64, U256}; +use alloy_rpc_types_eth::Transaction as RpcTransaction; +use morph_primitives::MorphTxEnvelope; +use serde::{Deserialize, Serialize}; + +/// Morph RPC transaction representation. +/// +/// Wraps the standard RPC transaction and adds Morph-specific fields: +/// - L1 message sender/queue index +/// - Morph fee token fields +#[derive( + Clone, Debug, PartialEq, Eq, Serialize, Deserialize, derive_more::Deref, derive_more::DerefMut, +)] +#[serde(rename_all = "camelCase")] +pub struct MorphRpcTransaction { + /// Standard RPC transaction fields. + #[serde(flatten)] + #[deref] + #[deref_mut] + pub inner: RpcTransaction, + + /// L1 message sender (only for L1Message type 0x7E). + #[serde(skip_serializing_if = "Option::is_none")] + pub sender: Option
, + + /// L1 message queue index (only for L1Message type 0x7E). + #[serde(skip_serializing_if = "Option::is_none")] + pub queue_index: Option, + + /// Token ID for fee payment (only for MorphTx type 0x7F). + #[serde(rename = "feeTokenID", skip_serializing_if = "Option::is_none")] + pub fee_token_id: Option, + + /// Maximum token amount willing to pay for fees (only for MorphTx type 0x7F). + #[serde(skip_serializing_if = "Option::is_none")] + pub fee_limit: Option, +} + +/// Implementation of [`Typed2718`] for Morph RPC transactions. +impl Typed2718 for MorphRpcTransaction { + fn ty(&self) -> u8 { + self.inner.ty() + } +} + +/// Implementation of [`ConsensusTransaction`] for Morph RPC transactions. +/// +/// Delegates all consensus transaction methods to the inner transaction. +impl ConsensusTransaction for MorphRpcTransaction { + fn chain_id(&self) -> Option { + self.inner.chain_id() + } + + fn nonce(&self) -> u64 { + self.inner.nonce() + } + + fn gas_limit(&self) -> u64 { + self.inner.gas_limit() + } + + fn gas_price(&self) -> Option { + TransactionTrait::gas_price(&self.inner) + } + + fn max_fee_per_gas(&self) -> u128 { + TransactionTrait::max_fee_per_gas(&self.inner) + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.inner.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.inner.max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.inner.priority_fee_or_price() + } + + fn effective_gas_price(&self, base_fee: Option) -> u128 { + self.inner.effective_gas_price(base_fee) + } + + fn is_dynamic_fee(&self) -> bool { + self.inner.is_dynamic_fee() + } + + fn kind(&self) -> TxKind { + self.inner.kind() + } + + fn is_create(&self) -> bool { + self.inner.is_create() + } + + fn to(&self) -> Option
{ + self.inner.to() + } + + fn value(&self) -> U256 { + self.inner.value() + } + + fn input(&self) -> &alloy_primitives::Bytes { + self.inner.input() + } + + fn access_list(&self) -> Option<&alloy_eips::eip2930::AccessList> { + self.inner.access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[alloy_primitives::B256]> { + self.inner.blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> { + self.inner.authorization_list() + } +} + +/// Implementation of [`TransactionResponse`] for Morph RPC transactions. +/// +/// Provides RPC-specific transaction metadata like block hash and index. +impl TransactionResponse for MorphRpcTransaction { + fn tx_hash(&self) -> alloy_primitives::B256 { + self.inner.tx_hash() + } + + fn block_hash(&self) -> Option { + self.inner.block_hash() + } + + fn block_number(&self) -> Option { + self.inner.block_number() + } + + fn transaction_index(&self) -> Option { + self.inner.transaction_index() + } + + fn from(&self) -> Address { + self.inner.from() + } +} diff --git a/crates/txpool/Cargo.toml b/crates/txpool/Cargo.toml index 77e6e13..0ebfd7a 100644 --- a/crates/txpool/Cargo.toml +++ b/crates/txpool/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] # Morph morph-chainspec.workspace = true -morph-primitives = { workspace = true, features = ["reth-codec"] } +morph-primitives = { workspace = true, features = ["serde-bincode-compat"] } morph-revm.workspace = true # Reth diff --git a/crates/txpool/src/morph_tx_validation.rs b/crates/txpool/src/morph_tx_validation.rs index 10fa069..673bb07 100644 --- a/crates/txpool/src/morph_tx_validation.rs +++ b/crates/txpool/src/morph_tx_validation.rs @@ -71,7 +71,7 @@ pub fn validate_morph_tx( } // Fetch token info from L2TokenRegistry - let token_info = TokenFeeInfo::try_fetch(db, fee_token_id, input.sender, input.hardfork) + let token_info = TokenFeeInfo::fetch(db, fee_token_id, input.sender, input.hardfork) .map_err(|err| MorphTxError::TokenInfoFetchFailed { token_id: fee_token_id, message: format!("{err:?}"), @@ -103,7 +103,7 @@ pub fn validate_morph_tx( let total_eth_fee = gas_fee.saturating_add(input.l1_data_fee); // Convert ETH fee to token amount - let required_token_amount = token_info.calculate_token_amount(total_eth_fee); + let required_token_amount = token_info.eth_to_token_amount(total_eth_fee); // Check fee_limit >= required_token_amount if fee_limit < required_token_amount {