From 3372182ca37ce6ce11aeb869c1075298a32fd327 Mon Sep 17 00:00:00 2001 From: refcell Date: Tue, 19 Nov 2024 21:11:04 -0500 Subject: [PATCH] fix: re-work engine --- Cargo.lock | 535 +++++++++++++++++++++----------- Cargo.toml | 9 +- bin/node/Cargo.toml | 7 + bin/node/src/cli.rs | 116 +++++++ bin/node/src/main.rs | 29 +- crates/engine/Cargo.toml | 20 +- crates/engine/src/api.rs | 338 ++++---------------- crates/engine/src/lib.rs | 15 +- crates/engine/src/traits.rs | 104 ------- crates/engine/src/types.rs | 30 -- crates/engine/src/validation.rs | 41 +++ crates/engine/src/validator.rs | 130 ++++++++ 12 files changed, 738 insertions(+), 636 deletions(-) create mode 100644 bin/node/src/cli.rs delete mode 100644 crates/engine/src/traits.rs delete mode 100644 crates/engine/src/types.rs create mode 100644 crates/engine/src/validation.rs create mode 100644 crates/engine/src/validator.rs diff --git a/Cargo.lock b/Cargo.lock index d54dabb..30345db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,17 +52,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "again" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05802a5ad4d172eaf796f7047b42d0af9db513585d16d4169660a21613d34b93" -dependencies = [ - "log", - "rand 0.7.3", - "wasm-timer", -] - [[package]] name = "ahash" version = "0.8.11" @@ -70,6 +59,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -254,7 +244,7 @@ dependencies = [ "derive_arbitrary", "derive_more", "foldhash", - "getrandom 0.2.15", + "getrandom", "hashbrown 0.15.1", "hex-literal", "indexmap 2.6.0", @@ -264,7 +254,7 @@ dependencies = [ "paste", "proptest", "proptest-derive", - "rand 0.8.5", + "rand", "ruint", "rustc-hash 2.0.0", "serde", @@ -272,6 +262,64 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "alloy-provider" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c1f9eede27bf4c13c099e8e64d54efd7ce80ef6ea47478aa75d5d74e2dba3b" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "futures", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest", + "schnellru", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-pubsub" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f1f34232f77341076541c405482e4ae12f0ee7153d8f9969fc1691201b2247" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "bimap", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", +] + [[package]] name = "alloy-rlp" version = "0.3.9" @@ -294,6 +342,31 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "alloy-rpc-client" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374dbe0dc3abdc2c964f36b3d3edf9cdb3db29d16bda34aa123f03d810bec1dd" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", + "wasmtimer", +] + [[package]] name = "alloy-rpc-types" version = "0.6.4" @@ -335,7 +408,7 @@ dependencies = [ "ethereum_ssz", "ethereum_ssz_derive", "jsonwebtoken", - "rand 0.8.5", + "rand", "serde", "strum", ] @@ -442,6 +515,65 @@ dependencies = [ "const-hex", ] +[[package]] +name = "alloy-transport" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f99acddb34000d104961897dbb0240298e8b775a7efffb9fda2a1a3efedd65b3" +dependencies = [ + "alloy-json-rpc", + "base64 0.22.1", + "futures-util", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc013132e34eeadaa0add7e74164c1503988bfba8bae885b32e0918ba85a8a6" +dependencies = [ + "alloy-json-rpc", + "alloy-rpc-types-engine", + "alloy-transport", + "http-body-util", + "hyper 1.5.1", + "hyper-util", + "jsonwebtoken", + "reqwest", + "serde_json", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-ipc" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063edc0660e81260653cc6a95777c29d54c2543a668aa5da2359fb450d25a1ba" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "alloy-trie" version = "0.7.4" @@ -657,7 +789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -667,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand", ] [[package]] @@ -760,6 +892,28 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "async-trait" version = "0.1.83" @@ -879,6 +1033,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[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" @@ -1344,7 +1504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -1356,7 +1516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "typenum", ] @@ -1452,7 +1612,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.10", + "parking_lot_core", ] [[package]] @@ -1612,8 +1772,8 @@ dependencies = [ "lazy_static", "lru", "more-asserts", - "parking_lot 0.12.3", - "rand 0.8.5", + "parking_lot", + "rand", "smallvec", "socket2", "tokio", @@ -1633,6 +1793,12 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "dtoa" version = "1.0.9" @@ -1683,7 +1849,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core 0.6.4", + "rand_core", "serde", "sha2 0.10.8", "subtle", @@ -1709,7 +1875,7 @@ dependencies = [ "generic-array", "group", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -1735,7 +1901,7 @@ dependencies = [ "bytes", "hex", "log", - "rand 0.8.5", + "rand", "sha3", "zeroize", ] @@ -1753,7 +1919,7 @@ dependencies = [ "hex", "k256", "log", - "rand 0.8.5", + "rand", "serde", "sha3", "zeroize", @@ -1893,7 +2059,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -1922,7 +2088,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand", "rustc-hex", "static_assertions", ] @@ -2123,17 +2289,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -2143,7 +2298,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2176,7 +2331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2224,6 +2379,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + [[package]] name = "hashbrown" version = "0.14.5" @@ -2309,7 +2470,7 @@ dependencies = [ "idna 0.4.0", "ipnet", "once_cell", - "rand 0.8.5", + "rand", "socket2", "thiserror 1.0.69", "tinyvec", @@ -2330,8 +2491,8 @@ dependencies = [ "ipconfig", "lru-cache", "once_cell", - "parking_lot 0.12.3", - "rand 0.8.5", + "parking_lot", + "rand", "resolv-conf", "smallvec", "thiserror 1.0.69", @@ -2364,7 +2525,7 @@ dependencies = [ "op-alloy-genesis", "op-alloy-protocol", "op-alloy-rpc-types-engine", - "parking_lot 0.12.3", + "parking_lot", "reqwest", "reth-primitives", "reth-provider", @@ -2377,15 +2538,22 @@ dependencies = [ name = "hilo-engine" version = "0.11.0" dependencies = [ - "again", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-client", "alloy-rpc-types-engine", - "async-trait", - "futures", - "reqwest", - "serde", - "serde_json", + "alloy-rpc-types-eth", + "alloy-transport-http", + "http-body-util", + "op-alloy-provider", + "op-alloy-rpc-types-engine", "thiserror 2.0.3", "tokio", + "tower", + "tracing", + "url", ] [[package]] @@ -2850,7 +3018,7 @@ dependencies = [ "http 0.2.12", "hyper 0.14.31", "log", - "rand 0.8.5", + "rand", "tokio", "url", "xmltree", @@ -2943,6 +3111,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interprocess" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -3139,7 +3322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -3158,7 +3341,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom 0.2.15", + "getrandom", "libp2p-allow-block-list", "libp2p-connection-limits", "libp2p-core", @@ -3219,10 +3402,10 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot 0.12.3", + "parking_lot", "pin-project", "quick-protobuf", - "rand 0.8.5", + "rand", "rw-stream-sink", "smallvec", "thiserror 1.0.69", @@ -3243,7 +3426,7 @@ dependencies = [ "hickory-resolver", "libp2p-core", "libp2p-identity", - "parking_lot 0.12.3", + "parking_lot", "smallvec", "tracing", ] @@ -3262,7 +3445,7 @@ dependencies = [ "fnv", "futures", "futures-ticker", - "getrandom 0.2.15", + "getrandom", "hex_fmt", "libp2p-core", "libp2p-identity", @@ -3270,7 +3453,7 @@ dependencies = [ "prometheus-client", "quick-protobuf", "quick-protobuf-codec", - "rand 0.8.5", + "rand", "regex", "sha2 0.10.8", "smallvec", @@ -3292,7 +3475,7 @@ dependencies = [ "libsecp256k1", "multihash", "quick-protobuf", - "rand 0.8.5", + "rand", "sha2 0.10.8", "thiserror 1.0.69", "tracing", @@ -3312,7 +3495,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "rand 0.8.5", + "rand", "smallvec", "socket2", "tokio", @@ -3353,7 +3536,7 @@ dependencies = [ "multihash", "once_cell", "quick-protobuf", - "rand 0.8.5", + "rand", "sha2 0.10.8", "snow", "static_assertions", @@ -3375,7 +3558,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "rand 0.8.5", + "rand", "tracing", "void", "web-time", @@ -3394,9 +3577,9 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-tls", - "parking_lot 0.12.3", + "parking_lot", "quinn", - "rand 0.8.5", + "rand", "ring 0.17.8", "rustls", "socket2", @@ -3421,7 +3604,7 @@ dependencies = [ "lru", "multistream-select", "once_cell", - "rand 0.8.5", + "rand", "smallvec", "tokio", "tracing", @@ -3516,7 +3699,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall", ] [[package]] @@ -3532,7 +3715,7 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand 0.8.5", + "rand", "serde", "sha2 0.9.9", "typenum", @@ -3740,7 +3923,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -3752,7 +3935,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -3950,10 +4133,15 @@ version = "0.1.0" dependencies = [ "clap", "eyre", + "hilo-engine", "metrics-exporter-prometheus", + "op-alloy-genesis", + "op-alloy-registry", + "serde_json", "tokio", "tracing", "tracing-subscriber", + "url", ] [[package]] @@ -4185,7 +4373,9 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", + "alloy-serde", "derive_more", + "serde", "thiserror 2.0.3", ] @@ -4213,16 +4403,32 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", + "alloy-serde", "async-trait", "brotli", "miniz_oxide", "op-alloy-consensus 0.6.7 (git+https://github.com/alloy-rs/op-alloy?branch=main)", "op-alloy-genesis", + "serde", "thiserror 2.0.3", "tracing", "unsigned-varint 0.8.0", ] +[[package]] +name = "op-alloy-provider" +version = "0.6.7" +source = "git+https://github.com/alloy-rs/op-alloy?branch=main#afc125b647f7da0a7e701706ff4893ca188f663a" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-engine", + "alloy-transport", + "async-trait", + "op-alloy-rpc-types-engine", +] + [[package]] name = "op-alloy-registry" version = "0.6.7" @@ -4261,10 +4467,12 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", + "alloy-serde", "derive_more", "ethereum_ssz", "op-alloy-consensus 0.6.7 (git+https://github.com/alloy-rs/op-alloy?branch=main)", "op-alloy-protocol", + "serde", "snap", "thiserror 2.0.3", ] @@ -4379,17 +4587,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -4397,21 +4594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.10", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -4422,7 +4605,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -4626,7 +4809,7 @@ checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" dependencies = [ "dtoa", "itoa", - "parking_lot 0.12.3", + "parking_lot", "prometheus-client-derive-encode", ] @@ -4652,8 +4835,8 @@ dependencies = [ "bitflags 2.6.0", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "rand_xorshift", "regex-syntax 0.8.5", "rusty-fork", @@ -4682,7 +4865,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "web-sys", "winapi", ] @@ -4741,8 +4924,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", + "getrandom", + "rand", "ring 0.17.8", "rustc-hash 2.0.0", "rustls", @@ -4783,19 +4966,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -4803,21 +4973,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand_chacha", + "rand_core", "serde", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -4825,16 +4985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -4843,16 +4994,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", + "getrandom", ] [[package]] @@ -4861,7 +5003,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -4906,13 +5048,10 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "recvmsg" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" @@ -4998,7 +5137,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", @@ -5045,7 +5184,7 @@ dependencies = [ "auto_impl", "derive_more", "metrics", - "parking_lot 0.12.3", + "parking_lot", "pin-project", "reth-chainspec", "reth-errors", @@ -5349,7 +5488,7 @@ dependencies = [ "dashmap", "derive_more", "indexmap 2.6.0", - "parking_lot 0.12.3", + "parking_lot", "reth-mdbx-sys", "smallvec", "thiserror 1.0.69", @@ -5556,7 +5695,7 @@ dependencies = [ "itertools 0.13.0", "metrics", "notify", - "parking_lot 0.12.3", + "parking_lot", "rayon", "reth-blockchain-tree-api", "reth-chain-state", @@ -5849,7 +5988,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -5929,7 +6068,7 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", + "rand", "rlp", "ruint-macro", "serde", @@ -5961,7 +6100,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" dependencies = [ - "rand 0.8.5", + "rand", ] [[package]] @@ -6116,6 +6255,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schnellru" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +dependencies = [ + "ahash", + "cfg-if", + "hashbrown 0.13.2", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -6142,7 +6292,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "rand 0.8.5", + "rand", "secp256k1-sys", ] @@ -6354,7 +6504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -6409,7 +6559,7 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek", - "rand_core 0.6.4", + "rand_core", "ring 0.17.8", "rustc_version 0.4.1", "sha2 0.10.8", @@ -6497,7 +6647,7 @@ dependencies = [ "byteorder", "crunchy", "lazy_static", - "rand 0.8.5", + "rand", "rustc-hex", ] @@ -6548,6 +6698,12 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -6856,6 +7012,26 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -7168,12 +7344,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -7248,18 +7418,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] -name = "wasm-timer" -version = "0.2.5" +name = "wasmtimer" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" dependencies = [ "futures", "js-sys", - "parking_lot 0.11.2", + "parking_lot", "pin-utils", + "slab", "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", ] [[package]] @@ -7310,7 +7479,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -7626,7 +7795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core 0.6.4", + "rand_core", "serde", "zeroize", ] @@ -7672,9 +7841,9 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.12.3", + "parking_lot", "pin-project", - "rand 0.8.5", + "rand", "static_assertions", ] @@ -7687,9 +7856,9 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.12.3", + "parking_lot", "pin-project", - "rand 0.8.5", + "rand", "static_assertions", "web-time", ] diff --git a/Cargo.toml b/Cargo.toml index de4da14..4068671 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ kona-driver = { git = "https://github.com/anton-rs/kona", branch = "rf/test/op-a hilo = { version = "0.11.0", path = "crates/hilo", default-features = false } hilo-net = { version = "0.11.0", path = "crates/net", default-features = false } hilo-driver = { version = "0.11.0", path = "crates/driver", default-features = false } +hilo-engine = { version = "0.11.0", path = "crates/engine", default-features = false } # Kona kona-derive = { version = "0.0.6", default-features = false } @@ -56,11 +57,13 @@ alloy-rlp = { version = "0.3.9", default-features = false } alloy-eips = { version = "0.6.4", default-features = false } alloy-serde = { version = "0.6.4", default-features = false } alloy-signer = { version = "0.6.4", default-features = false } +alloy-network = { version = "0.6.4", default-features = false } alloy-provider = { version = "0.6.4", default-features = false } -alloy-primitives = { version = "0.8.12", default-features = false } alloy-consensus = { version = "0.6.4", default-features = false } +alloy-rpc-types = { version = "0.6.4", default-features = false } alloy-transport = { version = "0.6.4", default-features = false } alloy-rpc-client = { version = "0.6.4", default-features = false } +alloy-primitives = { version = "0.8.12", default-features = false } alloy-rpc-types-eth = { version = "0.6.4", default-features = false } alloy-node-bindings = { version = "0.6.4", default-features = false } alloy-transport-http = { version = "0.6.4", default-features = false } @@ -75,12 +78,12 @@ alloy-rpc-types-engine = { version = "0.6.4", default-features = false } # op-alloy-rpc-types-engine = { version = "0.6.7", default-features = false } op-alloy-genesis = { git = "https://github.com/alloy-rs/op-alloy", branch = "main", default-features = false } +op-alloy-provider = { git = "https://github.com/alloy-rs/op-alloy", branch = "main", default-features = false } op-alloy-protocol = { git = "https://github.com/alloy-rs/op-alloy", branch = "main", default-features = false } op-alloy-registry = { git = "https://github.com/alloy-rs/op-alloy", branch = "main", default-features = false } op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", branch = "main", default-features = false } op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", branch = "main", default-features = false } - # Reth reth-provider = { git = "https://github.com/paradigmxyz/reth", rev = "7ae8ce1" } reth-primitives = { git = "https://github.com/paradigmxyz/reth", rev = "7ae8ce1" } @@ -114,6 +117,8 @@ clap = "4.5.20" tokio = "1.41.0" futures = "0.3.31" reqwest = "0.12.9" +tower = "0.5" +http-body-util = "0.1.2" parking_lot = "0.12.3" async-trait = "0.1.83" futures-timer = "3.0.3" diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 5d20e0a..851e10b 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -13,12 +13,19 @@ rust-version.workspace = true [dependencies] # Local +hilo-engine.workspace = true # hilo-net.workspace = true # hilo = { workspace = true, features = ["registry"] } +# Alloy +op-alloy-genesis.workspace = true +op-alloy-registry.workspace = true + # Workspace +url.workspace = true eyre.workspace = true tracing.workspace = true +serde_json = { workspace = true, features = ["std"] } clap = { workspace = true, features = ["derive", "env"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs new file mode 100644 index 0000000..2c678eb --- /dev/null +++ b/bin/node/src/cli.rs @@ -0,0 +1,116 @@ +//! CLI arguments for the Hilo Node. + +use std::{fs::File, path::PathBuf, sync::Arc}; + +use clap::Parser; +use eyre::{bail, Context, Result}; +use serde_json::from_reader; +use tracing::debug; +use url::Url; + +use op_alloy_genesis::RollupConfig; +use op_alloy_registry::ROLLUP_CONFIGS; + +use hilo_engine::ValidationMode; + +/// The default L2 chain ID to use. This corresponds to OP Mainnet. +pub const DEFAULT_L2_CHAIN_ID: u64 = 10; + +/// The default L1 RPC URL to use. +pub const DEFAULT_L1_RPC_URL: &str = "https://cloudflare-eth.com"; + +/// The default L2 RPC URL to use. +pub const DEFAULT_L2_RPC_URL: &str = "https://optimism.llamarpc.com/"; + +/// The default L1 Beacon Client RPC URL to use. +pub const DEFAULT_L1_BEACON_CLIENT_URL: &str = "http://localhost:5052/"; + +/// CLI Arguments. +#[derive(Parser, Clone, Debug)] +#[command(author, version, about, long_about = None)] +pub(crate) struct NodeArgs { + /// A port to serve prometheus metrics on. + #[clap( + long, + short = 'm', + default_value = "9090", + help = "The port to serve prometheus metrics on" + )] + pub metrics_port: u16, + + /// Chain ID of the L2 network + #[clap(long = "l2-chain-id", default_value_t = DEFAULT_L2_CHAIN_ID)] + pub l2_chain_id: u64, + + /// Path to a custom L2 rollup configuration file + /// (overrides the default rollup configuration from the registry) + #[clap(long = "hera.l2-config-file")] + pub l2_config_file: Option, + + /// RPC URL of an L2 execution client + #[clap(long = "hera.l2-rpc-url", default_value = DEFAULT_L2_RPC_URL)] + pub l2_rpc_url: Url, + + /// RPC URL of an L1 execution client + /// (This is only needed when running in Standalone mode) + #[clap(long = "hera.l1-rpc-url", default_value = DEFAULT_L1_RPC_URL)] + pub l1_rpc_url: Url, + + /// URL of an L1 beacon client to fetch blobs + #[clap(long = "hera.l1-beacon-client-url", default_value = DEFAULT_L1_BEACON_CLIENT_URL)] + pub l1_beacon_client_url: Url, + + /// URL of the blob archiver to fetch blobs that are expired on + /// the beacon client but still needed for processing. + /// + /// Blob archivers need to implement the `blob_sidecars` API: + /// + #[clap(long = "hera.l1-blob-archiver-url")] + pub l1_blob_archiver_url: Option, + + /// The payload validation mode to use. + /// + /// - Trusted: rely on a trusted synced L2 execution client. Validation happens by fetching the + /// same block and comparing the results. + /// - Engine API: use a local or remote engine API of an L2 execution client. Validation + /// happens by sending the `new_payload` to the API and expecting a VALID response. + #[clap(long = "hera.validation-mode", default_value = "engine-api")] + pub validation_mode: ValidationMode, + + /// URL of the engine API endpoint of an L2 execution client. + #[clap(long = "hera.l2-engine-api-url", env = "L2_ENGINE_API_URL")] + pub l2_engine_api_url: Url, + + /// JWT secret for the auth-rpc endpoint of the execution client. + /// This MUST be a valid path to a file containing the hex-encoded JWT secret. + #[clap(long = "hera.l2-engine-jwt-secret", env = "L2_ENGINE_JWT_SECRET")] + pub l2_engine_jwt_secret: PathBuf, + + /// The maximum **number of blocks** to keep cached in the chain provider. + /// + /// This is used to limit the memory usage of the chain provider. + /// When the limit is reached, the oldest blocks are discarded. + #[clap(long = "hera.l1-chain-cache-size", default_value_t = 256)] + pub l1_chain_cache_size: usize, +} + +impl NodeArgs { + /// Get the L2 rollup config, either from a file or the superchain registry. + #[allow(unused)] + pub fn get_l2_config(&self) -> Result> { + match &self.l2_config_file { + Some(path) => { + debug!("Loading l2 config from file: {:?}", path); + let file = File::open(path).wrap_err("Failed to open l2 config file")?; + Ok(Arc::new(from_reader(file).wrap_err("Failed to read l2 config file")?)) + } + None => { + debug!("Loading l2 config from superchain registry"); + let Some(cfg) = ROLLUP_CONFIGS.get(&self.l2_chain_id).cloned() else { + bail!("Failed to find l2 config for chain ID {}", self.l2_chain_id); + }; + Ok(Arc::new(cfg)) + } + } + } +} diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index b559c3d..37438ea 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -3,35 +3,14 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use clap::Parser; -use eyre::Result; - +mod cli; mod telemetry; -/// CLI Arguments. -#[derive(Parser, Clone, Debug)] -#[command(author, version, about, long_about = None)] -pub(crate) struct NodeArgs { - /// The L2 chain ID to use. - #[clap(long, short = 'c', default_value = "10", help = "The L2 chain ID to use")] - pub l2_chain_id: u64, - /// A port to serve prometheus metrics on. - #[clap( - long, - short = 'm', - default_value = "9090", - help = "The port to serve prometheus metrics on" - )] - pub metrics_port: u16, - // The hilo Rollup node configuration. - // #[clap(flatten)] - // pub hilo_config: HiloArgsExt, -} - #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> eyre::Result<()> { // Parse arguments. - let args = NodeArgs::parse(); + use clap::Parser; + let args = cli::NodeArgs::parse(); // Initialize the telemetry stack. telemetry::init_stack(args.metrics_port)?; diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index c3b137f..2ac7f7d 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -14,16 +14,24 @@ rust-version.workspace = true [dependencies] # Alloy +alloy-eips.workspace = true +alloy-network.workspace = true +alloy-rpc-client.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-provider = { workspace = true, features = ["ipc", "reqwest"] } +alloy-primitives = { workspace = true, features = ["map"] } +alloy-transport-http = { workspace = true, features = ["jwt-auth"] } alloy-rpc-types-engine = { workspace = true, features = ["jwt", "serde"] } +op-alloy-provider.workspace = true +op-alloy-rpc-types-engine.workspace = true + # Misc -again.workspace = true -serde.workspace = true -futures.workspace = true -reqwest = { workspace = true, features = ["json"] } +url.workspace = true +tracing.workspace = true +tower.workspace = true +http-body-util.workspace = true thiserror.workspace = true -serde_json.workspace = true -async-trait.workspace = true [dev-dependencies] tokio.workspace = true diff --git a/crates/engine/src/api.rs b/crates/engine/src/api.rs index 9a094e9..a2a8cd5 100644 --- a/crates/engine/src/api.rs +++ b/crates/engine/src/api.rs @@ -1,302 +1,90 @@ //! Contains the engine api client. -use std::{collections::HashMap, time::Duration}; - -use again::RetryPolicy; -use futures::future::TryFutureExt; -use reqwest::{header, Client}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::Value; - -use alloy_rpc_types_engine::{ - Claims, ExecutionPayloadEnvelopeV3, ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, - JwtSecret, PayloadAttributes, PayloadId, PayloadStatus, +use http_body_util::Full; +use tower::ServiceBuilder; +use tracing::warn; +use url::Url; + +use alloy_network::AnyNetwork; +use alloy_primitives::Bytes; +use alloy_provider::RootProvider; +use alloy_rpc_client::RpcClient; +use alloy_rpc_types_engine::{ForkchoiceState, JwtSecret}; +use alloy_transport_http::{ + hyper_util::{ + client::legacy::{connect::HttpConnector, Client}, + rt::TokioExecutor, + }, + AuthLayer, AuthService, Http, HyperClient, }; +use op_alloy_provider::ext::engine::OpEngineApi; +use op_alloy_rpc_types_engine::OpAttributesWithParent; -use crate::{ - Engine, DEFAULT_AUTH_PORT, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_V2, - ENGINE_NEW_PAYLOAD_V2, JSONRPC_VERSION, STATIC_ID, -}; +/// A Hyper HTTP client with a JWT authentication layer. +type HyperAuthClient> = HyperClient>>; /// An external op-geth engine api client #[derive(Debug, Clone)] pub struct EngineApi { - /// Base request url - pub base_url: String, - /// The url port - pub port: u16, - /// HTTP Client - pub client: Option, - /// A JWT secret used to authenticate with the engine api - secret: JwtSecret, -} - -/// Generic Engine API response -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EngineApiResponse

{ - /// JSON RPC version - jsonrpc: String, - /// Request ID - id: u64, - /// JSON RPC payload - result: Option

, - /// JSON RPC error payload - error: Option, -} - -/// Engine API error payload -#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq, Serialize, Deserialize)] -pub struct EngineApiErrorPayload { - /// The error code - pub code: i64, - /// The error message - pub message: String, - /// Optional additional error data - pub data: Option, -} - -impl std::fmt::Display for EngineApiErrorPayload { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Engine API Error: code: {}, message: {}", self.code, self.message) - } + /// The inner provider + provider: RootProvider, AnyNetwork>, } - -/// An engine api error +/// A validation error #[derive(Debug, thiserror::Error)] -pub enum EngineApiError { - /// An error converting the raw value to json. - #[error("Error converting value to json")] - SerdeError(#[from] serde_json::Error), - /// Missing http client - #[error("Missing http client")] - MissingHttpClient, - /// Failed to encode the JWT Claims - #[error("Failed to encode JWT Claims")] - JwtEncode, - /// A reqwest error - #[error("Reqwest error: {0}")] - ReqwestError(#[from] reqwest::Error), - /// An [EngineApiErrorPayload] returned by the engine api - #[error("Engine API error")] - EngineApiPayload(Option), +pub enum ValidationError { + /// An RPC error + #[error("RPC error")] + RpcError, } impl EngineApi { - /// Creates a new [`EngineApi`] with a base url and secret. - pub fn new(base_url: &str, secret_str: &str) -> Self { - let secret = JwtSecret::from_hex(secret_str).unwrap(); + /// Creates a new [`EngineApi`] from the provided [Url] and [JwtSecret]. + pub fn new_http(url: Url, jwt: JwtSecret) -> Self { + let hyper_client = Client::builder(TokioExecutor::new()).build_http::>(); - // Gracefully parse the port from the base url - let parts: Vec<&str> = base_url.split(':').collect(); - let port = parts[parts.len() - 1].parse::().unwrap_or(DEFAULT_AUTH_PORT); - let base_url = if parts.len() <= 2 { parts[0].to_string() } else { parts.join(":") }; + let auth_layer = AuthLayer::new(jwt); + let service = ServiceBuilder::new().layer(auth_layer).service(hyper_client); - let client = reqwest::Client::builder() - .default_headers({ - header::HeaderMap::from_iter([( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - )]) - }) - .timeout(Duration::from_secs(5)) - .build() - .expect("reqwest::Client could not be built, TLS backend could not be initialized"); + let layer_transport = HyperClient::with_service(service); + let http_hyper = Http::with_client(layer_transport, url); + let rpc_client = RpcClient::new(http_hyper, true); + let provider = RootProvider::<_, AnyNetwork>::new(rpc_client); - Self { base_url, port, client: Some(client), secret } + Self { provider } } - /// Constructs the base engine api url for the given address - pub fn auth_url_from_addr(addr: &str, port: Option) -> String { - let stripped = addr.strip_prefix("http://").unwrap_or(addr); - let stripped = addr.strip_prefix("https://").unwrap_or(stripped); - let port = port.unwrap_or(DEFAULT_AUTH_PORT); - format!("http://{stripped}:{port}") - } - - /// Returns if the provided secret matches the secret used to authenticate with the engine api. - pub fn check_secret(&self, secret: &str) -> bool { - self.secret.validate(secret).is_ok() - } - - /// Creates an engine api from environment variables - pub fn from_env() -> Self { - let base_url = std::env::var("ENGINE_API_URL").unwrap_or_else(|_| { - panic!( - "ENGINE_API_URL environment variable not set. \ - Please set this to the base url of the engine api" - ) - }); - let secret_key = std::env::var("JWT_SECRET").unwrap_or_else(|_| { - panic!( - "JWT_SECRET environment variable not set. \ - Please set this to the 256 bit hex-encoded secret key used to authenticate with the engine api. \ - This should be the same as set in the `--auth.secret` flag when executing go-ethereum." - ) - }); - let base_url = EngineApi::auth_url_from_addr(&base_url, None); - Self::new(&base_url, &secret_key) - } - - /// Construct base body - pub fn base_body(&self) -> HashMap { - let mut map = HashMap::new(); - map.insert("jsonrpc".to_string(), Value::String(JSONRPC_VERSION.to_string())); - map.insert("id".to_string(), Value::Number(STATIC_ID.into())); - map - } - - /// Helper to construct a post request through the client - async fn post

(&self, method: &str, params: Vec) -> Result - where - P: DeserializeOwned, - { - // Construct the request params - let mut body = self.base_body(); - body.insert("method".to_string(), Value::String(method.to_string())); - body.insert("params".to_string(), Value::Array(params)); - - // Send the client request - let client = self.client.as_ref().ok_or(EngineApiError::MissingHttpClient)?; - - // Clone the secret so we can use it in the retry policy. - let secret_clone = self.secret; - - let policy = RetryPolicy::fixed(Duration::ZERO).with_max_retries(5); - - // Send the request - let res = policy - .retry(|| async { - // Construct the JWT Authorization Token - let claims = Claims::with_current_timestamp(); - let jwt = secret_clone.encode(&claims).map_err(|_| EngineApiError::JwtEncode)?; - - // Send the request - client - .post(&self.base_url) - .header(header::AUTHORIZATION, format!("Bearer {}", jwt)) - .json(&body) - .send() - .map_err(EngineApiError::ReqwestError) - // .timeout(Duration::from_secs(2)) - .await? - .json::>() - .map_err(EngineApiError::ReqwestError) - // .timeout(Duration::from_secs(2)) - // .map_err(|e| EngineApiError::ReqwestError(e)) - .await - }) - .await?; - - if let Some(res) = res.result { - return Ok(res); - } - - Err(EngineApiError::EngineApiPayload(res.error)) - } - - /// Calls the engine to verify it's available to receive requests - pub async fn is_available(&self) -> bool { - self.post::("eth_chainId", vec![]).await.is_ok() - } -} - -#[async_trait::async_trait] -impl Engine for EngineApi { - type Error = EngineApiError; - - /// Sends an `engine_forkchoiceUpdatedV2` (V3 post Ecotone) message to the engine. - async fn forkchoice_updated( + /// Validates the payload using the Fork Choice Update API. + pub async fn validate_payload_fcu( &self, - forkchoice_state: ForkchoiceState, - payload_attributes: Option, - ) -> Result { - let payload_attributes_param = match payload_attributes { - Some(payload_attributes) => serde_json::to_value(payload_attributes)?, - None => Value::Null, + attributes: &OpAttributesWithParent, + ) -> Result { + // TODO: use the correct values + let fork_choice_state = ForkchoiceState { + head_block_hash: attributes.parent.block_info.hash, + finalized_block_hash: attributes.parent.block_info.hash, + safe_block_hash: attributes.parent.block_info.hash, }; - let forkchoice_state_param = serde_json::to_value(forkchoice_state)?; - let params = vec![forkchoice_state_param, payload_attributes_param]; - let res = self.post(ENGINE_FORKCHOICE_UPDATED_V2, params).await?; - Ok(res) - } - /// Sends an `engine_newPayloadV2` (V3 post Ecotone) message to the engine. - async fn new_payload( - &self, - execution_payload: ExecutionPayloadV3, - ) -> Result { - let params = vec![serde_json::to_value(execution_payload)?]; - let res = self.post(ENGINE_NEW_PAYLOAD_V2, params).await?; - Ok(res) - } - - /// Sends an `engine_getPayloadV2` (V3 post Ecotone) message to the engine. - async fn get_payload(&self, payload_id: PayloadId) -> Result { - let encoded = format!("{:x}", payload_id.0); - let padded = format!("0x{:0>16}", encoded); - let params = vec![Value::String(padded)]; - let res = self.post::(ENGINE_GET_PAYLOAD_V2, params).await?; - Ok(res.execution_payload) + let attributes = Some(attributes.attributes.clone()); + let fcu = self + .provider + .fork_choice_updated_v2(fork_choice_state, attributes) + .await + .map_err(|_| ValidationError::RpcError)?; + + if fcu.is_valid() { + Ok(true) + } else { + warn!(status = %fcu.payload_status, "Engine API returned invalid fork choice update"); + Ok(false) + } } } -#[cfg(test)] -mod tests { - use alloy_rpc_types_engine::Claims; - use std::time::SystemTime; - - // use std::str::FromStr; - // use ethers_core::types::H256; - - use super::*; - - const AUTH_ADDR: &str = "0.0.0.0"; - const SECRET: &str = "f79ae8046bc11c9927afe911db7143c51a806c4a537cc08e0d37140b0192f430"; - - #[tokio::test] - async fn test_engine_get_payload() { - // Construct the engine api client - let base_url = EngineApi::auth_url_from_addr(AUTH_ADDR, Some(8551)); - assert_eq!(base_url, "http://0.0.0.0:8551"); - let engine_api = EngineApi::new(&base_url, SECRET); - assert_eq!(engine_api.base_url, "http://0.0.0.0:8551"); - assert_eq!(engine_api.port, 8551); - - // Construct mock server params - let secret = JwtSecret::from_hex(SECRET).unwrap(); - let iat = SystemTime::UNIX_EPOCH; - let iat_secs = iat.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); - let claims = Claims { iat: iat_secs, exp: Some(iat_secs + 60) }; - let jwt = secret.encode(&claims).unwrap(); - assert_eq!(jwt, String::from("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjAsImV4cCI6NjB9.rJv_krfkQefjWnZxrpnDimR1NN1UEUffK3hQzD1KInA")); - // let bearer = format!("Bearer {jwt}"); - // let expected_body = r#"{"jsonrpc": "2.0", "method": "engine_getPayloadV1", "params": - // [""], "id": 1}"#; let mock_response = ExecutionPayloadResponse { - // jsonrpc: "2.0".to_string(), - // id: 1, - // result: ExecutionPayload { - // parent_hash: H256::from( - // } - // }; - - // Create the mock server - // let server = ServerBuilder::default() - // .set_id_provider(RandomStringIdProvider::new(16)) - // .set_middleware(middleware) - // .build(addr.parse::().unwrap()) - // .await - // .unwrap(); - - // Query the engine api client - // let execution_payload = engine_api.get_payload(PayloadId::default()).await.unwrap(); - // let expected_block_hash = - // H256::from_str("0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae") - // .unwrap(); - // assert_eq!(expected_block_hash, execution_payload.block_hash); +impl std::ops::Deref for EngineApi { + type Target = RootProvider, AnyNetwork>; - // Stop the server - // server.stop().unwrap(); - // server.stopped().await; + fn deref(&self) -> &Self::Target { + &self.provider } } diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index dcc9b25..4d162d6 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -3,18 +3,11 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// Re-export the [JwtSecret] type from alloy_rpc_types_engine. -pub use alloy_rpc_types_engine::JwtSecret; +mod validation; +pub use validation::ValidationMode; mod api; pub use api::EngineApi; -mod types; -pub use types::{ - DEFAULT_AUTH_PORT, ENGINE_FORKCHOICE_UPDATED_TIMEOUT, ENGINE_FORKCHOICE_UPDATED_V2, - ENGINE_GET_PAYLOAD_TIMEOUT, ENGINE_GET_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_TIMEOUT, - ENGINE_NEW_PAYLOAD_V2, JSONRPC_VERSION, STATIC_ID, -}; - -mod traits; -pub use traits::Engine; +mod validator; +pub use validator::{TrustedPayloadValidator, TrustedValidationError}; diff --git a/crates/engine/src/traits.rs b/crates/engine/src/traits.rs deleted file mode 100644 index 33bd643..0000000 --- a/crates/engine/src/traits.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Contains a trait around the Engine API. - -use alloy_rpc_types_engine::{ - payload::{ExecutionPayloadV3, PayloadId}, - ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadStatus, -}; -use async_trait::async_trait; -use std::fmt::Display; - -/// ## Engine -/// -/// A set of methods that allow a consensus client to interact with an execution engine. -/// This is a modified version of the [Ethereum Execution API Specs](https://github.com/ethereum/execution-apis), -/// as defined in the [Optimism Exec Engine Specs](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/exec-engine.md). -#[async_trait] -pub trait Engine: Send + Sync + 'static { - type Error: Display + ToString; - - /// ## forkchoice_updated - /// - /// Updates were made to [`engine_forkchoiceUpdatedV2`](https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_forkchoiceupdatedv2) - /// for L2: an extended [PayloadAttributes] - /// This updates which L2 blocks the engine considers to be canonical ([ForkchoiceState] - /// argument), and optionally initiates block production ([PayloadAttributes] argument). - /// - /// ### Specification - /// - /// method: engine_forkchoiceUpdatedV2 - /// params: - /// - [ForkchoiceState] - /// - [PayloadAttributes] - /// - /// timeout: 8s - /// - /// returns: - /// - [ForkchoiceUpdated] - /// - /// potential errors: - /// - code and message set in case an exception happens while the validating payload, updating - /// the forkchoice or initiating the payload build process. - /// - /// ### Reference - /// - /// See more details in the [Optimism Specs](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/exec-engine.md#engine_forkchoiceupdatedv1). - async fn forkchoice_updated( - &self, - forkchoice_state: ForkchoiceState, - payload_attributes: Option, - ) -> Result; - - /// ## new_payload - /// - /// No modifications to [`engine_newPayloadV2`](https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_newpayloadv2) - /// were made for L2. Applies a L2 block to the engine state. - /// - /// ### Specification - /// - /// method: engine_newPayloadV2 - /// - /// params: - /// - [ExecutionPayloadV3] - /// - /// timeout: 8s - /// - /// returns: - /// - [PayloadStatus] - /// - /// potential errors: - /// - code and message set in case an exception happens while processing the payload. - /// - /// ### Reference - /// - /// See more details in the [Optimism Specs](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/exec-engine.md#engine_newPayloadv1). - async fn new_payload( - &self, - execution_payload: ExecutionPayloadV3, - ) -> Result; - - /// ## get_payload - /// - /// No modifications to [`engine_getPayloadV2`](https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_getpayloadv2) - /// were made for L2. Retrieves a payload by ID, prepared by - /// [engine_forkchoiceUpdatedV2](super::EngineApi) when called with [PayloadAttributes]. - /// - /// ### Specification - /// - /// method: engine_getPayloadV2 - /// - /// params: - /// - [PayloadId]: DATA, 8 Bytes - Identifier of the payload build process - /// - /// timeout: 1s - /// - /// returns: - /// - [ExecutionPayloadV3] - /// - /// potential errors: - /// - code and message set in case an exception happens while getting the payload. - /// - /// ### Reference - /// - /// See more details in the [Optimism Specs](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/exec-engine.md#engine_getPayloadv1). - async fn get_payload(&self, payload_id: PayloadId) -> Result; -} diff --git a/crates/engine/src/types.rs b/crates/engine/src/types.rs deleted file mode 100644 index d0fd527..0000000 --- a/crates/engine/src/types.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Types for the Engine Controller. - -use std::time::Duration; - -/// The default engine api authentication port. -pub const DEFAULT_AUTH_PORT: u16 = 8551; - -/// The ID of the static payload -pub const STATIC_ID: u32 = 1; - -/// The json rpc version string -pub const JSONRPC_VERSION: &str = "2.0"; - -/// The new payload method string -pub const ENGINE_NEW_PAYLOAD_V2: &str = "engine_newPayloadV2"; - -/// The new payload timeout -pub const ENGINE_NEW_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(8); - -/// The get payload method string -pub const ENGINE_GET_PAYLOAD_V2: &str = "engine_getPayloadV2"; - -/// The get payload timeout -pub const ENGINE_GET_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(2); - -/// The forkchoice updated method string -pub const ENGINE_FORKCHOICE_UPDATED_V2: &str = "engine_forkchoiceUpdatedV2"; - -/// The forkchoice updated timeout -pub const ENGINE_FORKCHOICE_UPDATED_TIMEOUT: Duration = Duration::from_secs(8); diff --git a/crates/engine/src/validation.rs b/crates/engine/src/validation.rs new file mode 100644 index 0000000..7298320 --- /dev/null +++ b/crates/engine/src/validation.rs @@ -0,0 +1,41 @@ +//! Validation mode for payload validation. + +/// The payload validation mode. +/// +/// Every newly derived payload needs to be validated against a local +/// execution of all transactions included inside it. This can be done +/// in two ways: +/// +/// - Trusted: rely on a trusted synced L2 execution client. Validation happens by fetching the same +/// block and comparing the results. +/// - Engine API: use the authenticated engine API of an L2 execution client. Validation happens by +/// sending the `new_payload` to the API and expecting a VALID response. This method can also be +/// used to verify unsafe payloads from the sequencer. +#[derive(Debug, Clone)] +pub enum ValidationMode { + /// Use a trusted synced L2 execution client. + Trusted, + /// Use the authenticated engine API of an L2 execution client. + EngineApi, +} + +impl std::str::FromStr for ValidationMode { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "trusted" => Ok(ValidationMode::Trusted), + "engine-api" => Ok(ValidationMode::EngineApi), + _ => Err(format!("Invalid validation mode: {}", s)), + } + } +} + +impl std::fmt::Display for ValidationMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ValidationMode::Trusted => write!(f, "trusted"), + ValidationMode::EngineApi => write!(f, "engine-api"), + } + } +} diff --git a/crates/engine/src/validator.rs b/crates/engine/src/validator.rs new file mode 100644 index 0000000..afa3be4 --- /dev/null +++ b/crates/engine/src/validator.rs @@ -0,0 +1,130 @@ +//! Attributes validator for the rollup node + +use std::fmt::Debug; + +use alloy_eips::BlockNumberOrTag; +use alloy_primitives::Bytes; +use alloy_provider::{network::primitives::BlockTransactionsKind, Provider, ReqwestProvider}; +use alloy_rpc_types_engine::PayloadAttributes; +use alloy_rpc_types_eth::Header; + +use op_alloy_rpc_types_engine::{OpAttributesWithParent, OpPayloadAttributes}; +use tracing::error; +use url::Url; + +/// Trusted node client that validates the [`OpAttributesWithParent`] by fetching the associated L2 +/// block from a trusted L2 RPC and constructing the L2 Attributes from the block. +#[derive(Debug, Clone)] +pub struct TrustedPayloadValidator { + /// The L2 provider. + provider: ReqwestProvider, + /// The canyon activation timestamp. + canyon_activation: u64, +} + +/// An error returned by the trusted payload validator. +#[derive(Debug, thiserror::Error)] +pub enum TrustedValidationError { + /// The block was not found. + #[error("Block not found: {0}")] + BlockNotFound(BlockNumberOrTag), + /// Failed to fetch the transaction. + #[error("Failed to fetch transaction")] + FailedTransactionFetch, + /// A mismatch in the transaction count. + #[error("Transaction count mismatch")] + TransactionCountMismatch, + /// Failed to fetch the payload. + #[error("Failed to fetch payload")] + PayloadFetchFailed, +} + +impl TrustedPayloadValidator { + /// Creates a new [`TrustedPayloadValidator`]. + pub fn new(provider: ReqwestProvider, canyon_activation: u64) -> Self { + Self { provider, canyon_activation } + } + + /// Creates a new [`TrustedPayloadValidator`] from the provided [Url]. + pub fn new_http(url: Url, canyon_activation: u64) -> Self { + let inner = ReqwestProvider::new_http(url); + Self::new(inner, canyon_activation) + } + + /// Fetches a block [Header] and a list of raw RLP encoded transactions from the L2 provider. + /// + /// This method needs to fetch the non-hydrated block and then + /// fetch the raw transactions using the `debug_*` namespace. + pub async fn get_block( + &self, + tag: BlockNumberOrTag, + ) -> Result<(Header, Vec), TrustedValidationError> { + // Don't hydrate the block so we only get a list of transaction hashes. + let block = self + .provider + .get_block(tag.into(), BlockTransactionsKind::Hashes) + .await + .map_err(|_| TrustedValidationError::BlockNotFound(tag))? + .ok_or(TrustedValidationError::BlockNotFound(tag))?; + + // For each transaction hash, fetch the raw transaction RLP. + let mut txs = vec![]; + for tx in block.transactions.hashes() { + match self.provider.raw_request("debug_getRawTransaction".into(), [tx]).await { + Ok(tx) => txs.push(tx), + Err(err) => { + error!(?err, "Failed to fetch RLP transaction"); + return Err(TrustedValidationError::FailedTransactionFetch); + } + } + } + + // sanity check that we fetched all transactions + if txs.len() != block.transactions.len() { + return Err(TrustedValidationError::TransactionCountMismatch); + } + + Ok((block.header, txs)) + } + + /// Gets the payload for the specified [BlockNumberOrTag]. + pub async fn get_payload( + &self, + tag: BlockNumberOrTag, + ) -> Result { + let (header, transactions) = self.get_block(tag).await?; + + Ok(OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: header.timestamp, + suggested_fee_recipient: header.inner.beneficiary, + prev_randao: header.mix_hash, + // Withdrawals on optimism are always empty, *after* canyon (Shanghai) activation + withdrawals: (header.timestamp >= self.canyon_activation).then_some(Vec::default()), + parent_beacon_block_root: header.parent_beacon_block_root, + }, + transactions: Some(transactions), + no_tx_pool: Some(true), + gas_limit: Some(header.gas_limit), + eip_1559_params: None, // TODO: fix this + }) + } + + /// Validates the [`OpAttributesWithParent`] by fetching the associated L2 block from + /// a trusted L2 RPC and constructing the L2 Attributes from the block. + pub async fn validate_payload( + &self, + attributes: &OpAttributesWithParent, + ) -> Result { + let expected = attributes.parent.block_info.number + 1; + let tag = BlockNumberOrTag::from(expected); + + match self.get_payload(tag).await { + Ok(payload) => Ok(attributes.attributes == payload), + Err(err) => { + error!(?err, "Failed to fetch payload for block {}", expected); + Err(TrustedValidationError::PayloadFetchFailed) + } + } + } +}