From e4d209627d805b00d323307dc9cb6c03b7f9cf32 Mon Sep 17 00:00:00 2001 From: refcell Date: Tue, 19 Nov 2024 17:25:05 -0500 Subject: [PATCH] feat: engine --- Cargo.lock | 296 +++++++++++++++++++++++++++-------- Cargo.toml | 5 +- crates/engine/Cargo.toml | 29 ++++ crates/engine/README.md | 3 + crates/engine/src/api.rs | 302 ++++++++++++++++++++++++++++++++++++ crates/engine/src/lib.rs | 20 +++ crates/engine/src/traits.rs | 104 +++++++++++++ crates/engine/src/types.rs | 30 ++++ 8 files changed, 721 insertions(+), 68 deletions(-) create mode 100644 crates/engine/Cargo.toml create mode 100644 crates/engine/README.md create mode 100644 crates/engine/src/api.rs create mode 100644 crates/engine/src/lib.rs create mode 100644 crates/engine/src/traits.rs create mode 100644 crates/engine/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index f565040..0cf99ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,17 @@ 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" @@ -243,7 +254,7 @@ dependencies = [ "derive_arbitrary", "derive_more", "foldhash", - "getrandom", + "getrandom 0.2.15", "hashbrown 0.15.1", "hex-literal", "indexmap 2.6.0", @@ -253,7 +264,7 @@ dependencies = [ "paste", "proptest", "proptest-derive", - "rand", + "rand 0.8.5", "ruint", "rustc-hash 2.0.0", "serde", @@ -323,6 +334,8 @@ dependencies = [ "derive_more", "ethereum_ssz", "ethereum_ssz_derive", + "jsonwebtoken", + "rand 0.8.5", "serde", "strum", ] @@ -644,7 +657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -654,7 +667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] [[package]] @@ -1331,7 +1344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -1343,7 +1356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -1439,7 +1452,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.10", ] [[package]] @@ -1599,8 +1612,8 @@ dependencies = [ "lazy_static", "lru", "more-asserts", - "parking_lot", - "rand", + "parking_lot 0.12.3", + "rand 0.8.5", "smallvec", "socket2", "tokio", @@ -1670,7 +1683,7 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core", + "rand_core 0.6.4", "serde", "sha2 0.10.8", "subtle", @@ -1696,7 +1709,7 @@ dependencies = [ "generic-array", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -1722,7 +1735,7 @@ dependencies = [ "bytes", "hex", "log", - "rand", + "rand 0.8.5", "sha3", "zeroize", ] @@ -1740,7 +1753,7 @@ dependencies = [ "hex", "k256", "log", - "rand", + "rand 0.8.5", "serde", "sha3", "zeroize", @@ -1880,7 +1893,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1909,7 +1922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand", + "rand 0.8.5", "rustc-hex", "static_assertions", ] @@ -2110,6 +2123,17 @@ 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" @@ -2119,7 +2143,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -2152,7 +2176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2285,7 +2309,7 @@ dependencies = [ "idna 0.4.0", "ipnet", "once_cell", - "rand", + "rand 0.8.5", "socket2", "thiserror 1.0.69", "tinyvec", @@ -2306,8 +2330,8 @@ dependencies = [ "ipconfig", "lru-cache", "once_cell", - "parking_lot", - "rand", + "parking_lot 0.12.3", + "rand 0.8.5", "resolv-conf", "smallvec", "thiserror 1.0.69", @@ -2340,7 +2364,7 @@ dependencies = [ "op-alloy-genesis", "op-alloy-protocol", "op-alloy-rpc-types-engine", - "parking_lot", + "parking_lot 0.12.3", "reqwest", "reth-primitives", "reth-provider", @@ -2349,6 +2373,21 @@ dependencies = [ "url", ] +[[package]] +name = "hilo-engine" +version = "0.11.0" +dependencies = [ + "again", + "alloy-rpc-types-engine", + "async-trait", + "futures", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.3", + "tokio", +] + [[package]] name = "hilo-net" version = "0.11.0" @@ -2811,7 +2850,7 @@ dependencies = [ "http 0.2.12", "hyper 0.14.31", "log", - "rand", + "rand 0.8.5", "tokio", "url", "xmltree", @@ -2970,6 +3009,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem", + "ring 0.17.8", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "k256" version = "0.13.4" @@ -3104,7 +3158,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom", + "getrandom 0.2.15", "libp2p-allow-block-list", "libp2p-connection-limits", "libp2p-core", @@ -3165,10 +3219,10 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot", + "parking_lot 0.12.3", "pin-project", "quick-protobuf", - "rand", + "rand 0.8.5", "rw-stream-sink", "smallvec", "thiserror 1.0.69", @@ -3189,7 +3243,7 @@ dependencies = [ "hickory-resolver", "libp2p-core", "libp2p-identity", - "parking_lot", + "parking_lot 0.12.3", "smallvec", "tracing", ] @@ -3208,7 +3262,7 @@ dependencies = [ "fnv", "futures", "futures-ticker", - "getrandom", + "getrandom 0.2.15", "hex_fmt", "libp2p-core", "libp2p-identity", @@ -3216,7 +3270,7 @@ dependencies = [ "prometheus-client", "quick-protobuf", "quick-protobuf-codec", - "rand", + "rand 0.8.5", "regex", "sha2 0.10.8", "smallvec", @@ -3238,7 +3292,7 @@ dependencies = [ "libsecp256k1", "multihash", "quick-protobuf", - "rand", + "rand 0.8.5", "sha2 0.10.8", "thiserror 1.0.69", "tracing", @@ -3258,7 +3312,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "rand", + "rand 0.8.5", "smallvec", "socket2", "tokio", @@ -3299,7 +3353,7 @@ dependencies = [ "multihash", "once_cell", "quick-protobuf", - "rand", + "rand 0.8.5", "sha2 0.10.8", "snow", "static_assertions", @@ -3321,7 +3375,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-swarm", - "rand", + "rand 0.8.5", "tracing", "void", "web-time", @@ -3340,9 +3394,9 @@ dependencies = [ "libp2p-core", "libp2p-identity", "libp2p-tls", - "parking_lot", + "parking_lot 0.12.3", "quinn", - "rand", + "rand 0.8.5", "ring 0.17.8", "rustls", "socket2", @@ -3367,7 +3421,7 @@ dependencies = [ "lru", "multistream-select", "once_cell", - "rand", + "rand 0.8.5", "smallvec", "tokio", "tracing", @@ -3462,7 +3516,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall", + "redox_syscall 0.5.7", ] [[package]] @@ -3478,7 +3532,7 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand", + "rand 0.8.5", "serde", "sha2 0.9.9", "typenum", @@ -3686,7 +3740,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -3698,7 +3752,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -4325,6 +4379,17 @@ 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" @@ -4332,7 +4397,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core", + "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", ] [[package]] @@ -4343,7 +4422,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -4547,7 +4626,7 @@ checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" dependencies = [ "dtoa", "itoa", - "parking_lot", + "parking_lot 0.12.3", "prometheus-client-derive-encode", ] @@ -4573,8 +4652,8 @@ dependencies = [ "bitflags 2.6.0", "lazy_static", "num-traits", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "rand_xorshift", "regex-syntax 0.8.5", "rusty-fork", @@ -4603,7 +4682,7 @@ dependencies = [ "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -4662,8 +4741,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", - "getrandom", - "rand", + "getrandom 0.2.15", + "rand 0.8.5", "ring 0.17.8", "rustc-hash 2.0.0", "rustls", @@ -4704,6 +4783,19 @@ 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" @@ -4711,11 +4803,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "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" @@ -4723,7 +4825,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "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", ] [[package]] @@ -4732,7 +4843,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "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", ] [[package]] @@ -4741,7 +4861,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -4785,6 +4905,15 @@ dependencies = [ "yasna", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.7" @@ -4916,7 +5045,7 @@ dependencies = [ "auto_impl", "derive_more", "metrics", - "parking_lot", + "parking_lot 0.12.3", "pin-project", "reth-chainspec", "reth-errors", @@ -5220,7 +5349,7 @@ dependencies = [ "dashmap", "derive_more", "indexmap 2.6.0", - "parking_lot", + "parking_lot 0.12.3", "reth-mdbx-sys", "smallvec", "thiserror 1.0.69", @@ -5427,7 +5556,7 @@ dependencies = [ "itertools 0.13.0", "metrics", "notify", - "parking_lot", + "parking_lot 0.12.3", "rayon", "reth-blockchain-tree-api", "reth-chain-state", @@ -5720,7 +5849,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -5800,7 +5929,7 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand", + "rand 0.8.5", "rlp", "ruint-macro", "serde", @@ -5832,7 +5961,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] @@ -6013,7 +6142,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "rand", + "rand 0.8.5", "secp256k1-sys", ] @@ -6225,7 +6354,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 1.0.69", + "time", ] [[package]] @@ -6268,7 +6409,7 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "ring 0.17.8", "rustc_version 0.4.1", "sha2 0.10.8", @@ -6356,7 +6497,7 @@ dependencies = [ "byteorder", "crunchy", "lazy_static", - "rand", + "rand 0.8.5", "rustc-hex", ] @@ -7027,6 +7168,12 @@ 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" @@ -7100,6 +7247,21 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.72" @@ -7464,7 +7626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "serde", "zeroize", ] @@ -7510,9 +7672,9 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot", + "parking_lot 0.12.3", "pin-project", - "rand", + "rand 0.8.5", "static_assertions", ] @@ -7525,9 +7687,9 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot", + "parking_lot 0.12.3", "pin-project", - "rand", + "rand 0.8.5", "static_assertions", "web-time", ] diff --git a/Cargo.toml b/Cargo.toml index dce0dff..47e24c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,12 +109,15 @@ arbitrary = "1" url = "2.5.2" lru = "0.12.5" eyre = "0.6.12" +again = "0.1.2" clap = "4.5.20" tokio = "1.41.0" -parking_lot = "0.12.3" futures = "0.3.31" reqwest = "0.12.9" +parking_lot = "0.12.3" async-trait = "0.1.83" +futures-timer = "3.0.3" unsigned-varint = "0.8.0" +thiserror = { version = "2.0", default-features = false } derive_more = { version = "1.0.0", default-features = false } lazy_static = { version = "1.5.0", default-features = false } diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml new file mode 100644 index 0000000..af925d7 --- /dev/null +++ b/crates/engine/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "hilo-engine" +description = "Engine Controller Peripheral to the Driver" + +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +# Alloy +alloy-rpc-types-engine = { workspace = true, features = ["jwt", "serde"] } + +# Misc +again.workspace = true +serde.workspace = true +futures.workspace = true +reqwest.workspace = true +thiserror.workspace = true +serde_json.workspace = true +async-trait.workspace = true + +[dev-dependencies] +tokio.workspace = true diff --git a/crates/engine/README.md b/crates/engine/README.md new file mode 100644 index 0000000..cfba125 --- /dev/null +++ b/crates/engine/README.md @@ -0,0 +1,3 @@ +# `hilo-engine` + +An Engine Controller for hilo. diff --git a/crates/engine/src/api.rs b/crates/engine/src/api.rs new file mode 100644 index 0000000..9a094e9 --- /dev/null +++ b/crates/engine/src/api.rs @@ -0,0 +1,302 @@ +//! 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 crate::{ + Engine, DEFAULT_AUTH_PORT, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_V2, + ENGINE_NEW_PAYLOAD_V2, JSONRPC_VERSION, STATIC_ID, +}; + +/// 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) + } +} + +/// An engine api 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), +} + +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(); + + // 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 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"); + + Self { base_url, port, client: Some(client), secret } + } + + /// 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( + &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, + }; + 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) + } +} + +#[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); + + // Stop the server + // server.stop().unwrap(); + // server.stopped().await; + } +} diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs new file mode 100644 index 0000000..dcc9b25 --- /dev/null +++ b/crates/engine/src/lib.rs @@ -0,0 +1,20 @@ +#![doc = include_str!("../README.md")] +#![doc(issue_tracker_base_url = "https://github.com/anton-rs/hilo/issues/")] +#![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 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; diff --git a/crates/engine/src/traits.rs b/crates/engine/src/traits.rs new file mode 100644 index 0000000..33bd643 --- /dev/null +++ b/crates/engine/src/traits.rs @@ -0,0 +1,104 @@ +//! 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 new file mode 100644 index 0000000..d0fd527 --- /dev/null +++ b/crates/engine/src/types.rs @@ -0,0 +1,30 @@ +//! 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);