diff --git a/Cargo.lock b/Cargo.lock index e86146da..d7e6c21b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,9 +55,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.13.0" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27d301f5bcfd37e3aac727c360d8b50c33ddff9169ce0370198dedda36a9927d" +checksum = "32c3f3bc4f2a6b725970cd354e78e9738ea1e8961a91898f57bf6317970b1915" dependencies = [ "alloy-eips", "alloy-primitives", @@ -66,11 +66,12 @@ dependencies = [ "alloy-trie", "auto_impl", "c-kzg", - "derive_more 2.0.1", + "derive_more", "either", "k256", "once_cell", "rand 0.8.5", + "secp256k1", "serde", "serde_with", "thiserror 2.0.12", @@ -78,9 +79,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "0.13.0" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f4f97a85a45965e0e4f9f5b94bbafaa3e4ee6868bdbcf2e4a9acb4b358038fe" +checksum = "a16df75c656be4465ab411b88c5ec6bae4931ce806834efbf375ca85b8db6f9a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -92,9 +93,9 @@ dependencies = [ [[package]] name = "alloy-eip2124" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675264c957689f0fd75f5993a73123c2cc3b5c235a38f5b9037fe6c826bfb2c0" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -105,9 +106,9 @@ dependencies = [ [[package]] name = "alloy-eip2930" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +checksum = "dbe3e16484669964c26ac48390245d84c410b1a5f968976076c17184725ef235" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -116,9 +117,9 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b15b13d38b366d01e818fe8e710d4d702ef7499eacd44926a06171dd9585d0c" +checksum = "804cefe429015b4244966c006d25bda5545fa9db5990e9c9079faf255052f50a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -128,9 +129,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.13.0" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b11c382ca8075128d1ae6822b60921cf484c911d9a5831797a01218f98125f" +checksum = "2f7b2f7010581f29bcace81776cf2f0e022008d05a7d326884763f16f3044620" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -140,7 +141,7 @@ dependencies = [ "alloy-serde", "auto_impl", "c-kzg", - "derive_more 2.0.1", + "derive_more", "either", "ethereum_ssz", "ethereum_ssz_derive", @@ -150,9 +151,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.25" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6beff64ad0aa6ad1019a3db26fef565aefeb011736150ab73ed3366c3cfd1b" +checksum = "5189fa9a8797e92396bc4b4454c5f2073a4945f7c2b366af9af60f9536558f7a" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -162,9 +163,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.13.0" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f736e1d1eb1b770dbd32919bdf46d4dcd4617f2eed07947dfb32649962baba" +checksum = "3449fdd0908f50fb68eef8c43201d73d4ff51882b36e2adaadab277a7539ce4f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -175,17 +176,17 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.25" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c77490fe91a0ce933a1f219029521f20fc28c2c0ca95d53fa4da9c00b8d9d4e" +checksum = "6a12fe11d0b8118e551c29e1a67ccb6d01cc07ef08086df30f07487146de6fa1" dependencies = [ "alloy-rlp", "bytes", "cfg-if 1.0.0", "const-hex", - "derive_more 2.0.1", + "derive_more", "foldhash", - "getrandom 0.2.15", + "getrandom 0.3.2", "hashbrown 0.15.2", "indexmap 2.8.0", "itoa", @@ -193,7 +194,7 @@ dependencies = [ "keccak-asm", "paste", "proptest", - "rand 0.8.5", + "rand 0.9.0", "ruint", "rustc-hash 2.1.1", "serde", @@ -225,16 +226,16 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "0.13.0" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "689521777149dabe210ef122605fb00050e038f2e85b8c9897534739f1a904f8" +checksum = "246c225f878dbcb8ad405949a30c403b3bb43bdf974f7f0331b276f835790a5e" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", - "derive_more 2.0.1", + "derive_more", "ethereum_ssz", "ethereum_ssz_derive", "jsonwebtoken", @@ -245,9 +246,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.13.0" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8b6d55bdaa0c4a08650d4b32f174494cbade56adf6f2fcfa2a4f3490cb5511" +checksum = "bc1323310d87f9d950fb3ff58d943fdf832f5e10e6f902f405c0eaa954ffbaf1" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -265,9 +266,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.13.0" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1824791912f468a481dedc1db50feef3e85a078f6d743a62db2ee9c2ca674882" +checksum = "d05ace2ef3da874544c3ffacfd73261cdb1405d8631765deb991436a53ec6069" dependencies = [ "alloy-primitives", "serde", @@ -276,9 +277,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.25" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2" +checksum = "60fcfa26956bcb22f66ab13407115197f26ef23abca5b48d39a1946897382d74" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -290,9 +291,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.8.25" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26" +checksum = "72a9b402f0013f1ff8c24066eeafc2207a8e52810a2b18b77776ce7fead5af41" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -308,9 +309,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.8.25" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e" +checksum = "d02d61741337bb6b3f4899c2e3173fe17ffa2810e143d3b28acd953197c8dd79" dependencies = [ "const-hex", "dunce", @@ -324,9 +325,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.8.25" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d162f8524adfdfb0e4bd0505c734c985f3e2474eb022af32eef0d52a4f3935c" +checksum = "d2b5f5f9f561c29f78ea521ebe2e5ac1633f1b1442dae582f68ecd57c6350042" dependencies = [ "serde", "winnow", @@ -334,9 +335,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.25" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43d5e60466a440230c07761aa67671d4719d46f43be8ea6e7ed334d8db4a9ab" +checksum = "c02635bce18205ff8149fb752c753b0a91ea3f3c8ee04c58846448be4811a640" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -347,14 +348,14 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.7.9" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" +checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more 1.0.0", + "derive_more", "nybbles", "serde", "smallvec", @@ -432,15 +433,6 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "ark-ff" version = "0.3.0" @@ -803,6 +795,22 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -909,11 +917,10 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7e3c397401eb76228c89561cf22f85f41c95aa799ee9d860de3ea1cbc728fc" +checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" dependencies = [ - "arbitrary", "blst", "cc", "glob", @@ -1089,6 +1096,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -1241,6 +1258,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "data-encoding" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" + [[package]] name = "der" version = "0.7.9" @@ -1272,44 +1295,13 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", -] - [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "derive_more-impl 2.0.1", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "derive_more-impl", ] [[package]] @@ -1463,9 +1455,9 @@ dependencies = [ [[package]] name = "ethereum_serde_utils" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70cbccfccf81d67bff0ab36e591fa536c8a935b078a7b0e58c1d00d418332fc9" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" dependencies = [ "alloy-primitives", "hex", @@ -1476,9 +1468,9 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86da3096d1304f5f28476ce383005385459afeaf0eea08592b65ddbc9b258d16" +checksum = "9ca8ba45b63c389c6e115b095ca16381534fdcc03cf58176a3f8554db2dbe19b" dependencies = [ "alloy-primitives", "ethereum_serde_utils", @@ -1491,9 +1483,9 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d832a5c38eba0e7ad92592f7a22d693954637fbb332b4f669590d66a5c3183e5" +checksum = "0dd55d08012b4e0dfcc92b8d6081234df65f2986ad34cc76eeed69c5e2ce7506" dependencies = [ "darling", "proc-macro2", @@ -1592,6 +1584,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1830,6 +1837,15 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2684,6 +2700,23 @@ dependencies = [ "uuid", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -2807,32 +2840,33 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "op-alloy-consensus" -version = "0.12.0" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91764ebe0eddf6e3cfff41650332ff4e01defe372386027703f2e7e334734a05" +checksum = "aec9fec6da224c0a86218a21e8e3c28f49a98dbab4bb5ccbd94f80bfbd6a66fb" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", - "derive_more 2.0.1", + "derive_more", "serde", "thiserror 2.0.12", ] [[package]] name = "op-alloy-rpc-types-engine" -version = "0.12.0" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc26f8288839926d0137d39d70628bb5ac00fca449bab24c54cebd66c71b9cf4" +checksum = "8e2bbca5c5c85b833810cadde847bad2ebcf5bdd97abbce8ec4cf1565b98c90f" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "alloy-serde", - "derive_more 2.0.1", + "derive_more", "ethereum_ssz", "op-alloy-consensus", "serde", @@ -2840,12 +2874,50 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags 2.9.0", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "opentelemetry" version = "0.28.0" @@ -3306,6 +3378,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", + "serde", "zerocopy 0.8.24", ] @@ -3345,6 +3418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.2", + "serde", ] [[package]] @@ -3466,8 +3540,8 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.3.7" -source = "git+https://github.com/paradigmxyz/reth.git?rev=v1.3.7#ed7da87da4de340a437bf46f39a7e1397ac82065" +version = "1.3.12" +source = "git+https://github.com/paradigmxyz/reth.git?rev=bc9722d9e2b880bda36eba367a05e6abc7f8cc9e#bc9722d9e2b880bda36eba367a05e6abc7f8cc9e" dependencies = [ "alloy-rpc-types-engine", "http", @@ -3520,6 +3594,7 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", + "alloy-serde", "anyhow", "assert_cmd", "bytes", @@ -3555,12 +3630,14 @@ dependencies = [ "thiserror 2.0.12", "time", "tokio", + "tokio-tungstenite", "tokio-util", "tower 0.4.13", "tower-http 0.5.2", "tracing", "tracing-opentelemetry", "tracing-subscriber", + "url", ] [[package]] @@ -3698,7 +3775,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ] [[package]] @@ -3713,7 +3790,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5467026f437b4cb2a533865eaa73eb840019a0916f4b9ec563c6e617e086c9" dependencies = [ - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "jni", "log", @@ -3722,7 +3799,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.2.0", "security-framework-sys", "webpki-root-certs", "windows-sys 0.59.0", @@ -3815,6 +3892,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.2.0" @@ -3822,7 +3933,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.0", - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -4170,9 +4281,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.25" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560533fbd6914b94a8fb5cc803ed6801c3455668db3b810702c57612bac9412" +checksum = "34c9c96de1f835488c1501092847b522be88c9ac6fb0d4c0fbea92992324c8f4" dependencies = [ "paste", "proc-macro2", @@ -4369,6 +4480,16 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.2" @@ -4391,6 +4512,20 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.14" @@ -4649,6 +4784,24 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.9.0", + "sha1", + "thiserror 2.0.12", + "utf-8", +] + [[package]] name = "typenum" version = "1.18.0" @@ -4714,6 +4867,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -4747,6 +4906,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index e1aabb10..d407f24b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] -op-alloy-rpc-types-engine = "0.12.0" -alloy-rpc-types-engine = "0.13.0" -alloy-eips = { version = "0.13.0", features = ["serde"], optional = true } -alloy-primitives = { version = "0.8.10", features = ["rand"] } +op-alloy-rpc-types-engine = "0.15.4" +alloy-rpc-types-engine = "0.15.11" +alloy-rpc-types-eth = "0.15.11" +alloy-serde = "0.15.11" +alloy-eips = { version = "0.15.11", features = ["serde"], optional = true } +alloy-primitives = { version = "1.1.0", features = ["rand"] } tokio = { version = "1", features = ["full"] } tracing = "0.1.4" tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } @@ -39,6 +41,8 @@ tracing-opentelemetry = "0.29.0" futures = "0.3.31" metrics = "0.24.0" metrics-exporter-prometheus = "0.16.0" +tokio-tungstenite = { version = "0.26.2", features = ["native-tls"] } +url = "2.5" metrics-util = "0.19.0" eyre = "0.6.12" paste = "1.0.15" @@ -49,15 +53,14 @@ time = { version = "0.3.36", features = ["macros", "formatting", "parsing"], opt lazy_static = {version = "1.5.0", optional = true } [dev-dependencies] -op-alloy-consensus = "0.12.0" -alloy-rpc-types-eth = "0.13.0" +op-alloy-consensus = "0.15.4" anyhow = "1.0" assert_cmd = "2.0.10" predicates = "3.1.2" tokio-util = { version = "0.7.13" } nix = "0.15.0" bytes = "1.2" -reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth.git", rev = "v1.3.7" } +reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth.git", rev = "bc9722d9e2b880bda36eba367a05e6abc7f8cc9e" } ctor = "0.4.1" [features] diff --git a/src/bin/main.rs b/src/bin/main.rs index b4538743..6b85435d 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -2,8 +2,8 @@ use ::tracing::info; use clap::Parser; use rollup_boost::{ - Args, Commands, DebugClient, DebugCommands, PayloadSource, ProxyLayer, RollupBoostServer, - RpcClient, init_metrics, init_tracing, + Args, Commands, DebugClient, DebugCommands, Flashblocks, PayloadSource, ProxyLayer, + RollupBoostServer, RpcClient, init_metrics, init_tracing, }; use std::net::SocketAddr; @@ -90,11 +90,21 @@ async fn main() -> eyre::Result<()> { info!("Boost sync enabled"); } + let flashblocks_client = if args.flashblocks.flashblocks { + let inbound_url = args.flashblocks.flashblocks_url; + let outbound_url = args.flashblocks.flashblocks_outbound_url; + + Some(Flashblocks::run(inbound_url, outbound_url).unwrap()) + } else { + None + }; + let rollup_boost = RollupBoostServer::new( l2_client, builder_client, boost_sync_enabled, args.execution_mode, + flashblocks_client, ); // Spawn the debug server diff --git a/src/cli.rs b/src/cli.rs index e973fb91..2255f5ce 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -69,6 +69,25 @@ pub struct Args { /// Execution mode to start rollup boost with #[arg(long, env, default_value = "enabled")] pub execution_mode: ExecutionMode, + + /// Enable Flashblocks client + #[clap(flatten)] + pub flashblocks: FlashblocksArgs, +} + +#[derive(Parser, Debug)] +pub struct FlashblocksArgs { + /// Enable Flashblocks client + #[arg(long, env, default_value = "false")] + pub flashblocks: bool, + + /// Flashblocks WebSocket URL + #[arg(long, env, default_value = "ws://localhost:1111")] + pub flashblocks_url: String, + + /// Flashblocks outbound WebSocket URL + #[arg(long, env, default_value = "127.0.0.1:1112")] + pub flashblocks_outbound_url: String, } #[derive(Clone, Debug)] diff --git a/src/client/rpc.rs b/src/client/rpc.rs index bdf863e7..fb63854d 100644 --- a/src/client/rpc.rs +++ b/src/client/rpc.rs @@ -24,6 +24,9 @@ use thiserror::Error; use tracing::{error, info, instrument}; const INTERNAL_ERROR: i32 = 13; +const INVALID_PAYLOAD: i32 = 1337; +const IO_ERROR: i32 = 1338; +const JWT_ERROR: i32 = 1339; pub(crate) type ClientResult = Result; @@ -43,7 +46,8 @@ trait Code: Sized { fn code(&self) -> i32; fn set_code(self) -> Self { - tracing::Span::current().record("code", self.code()); + let code_value: i64 = self.code().into(); + tracing::Span::current().record("code", code_value.to_string()); self } } @@ -61,9 +65,10 @@ impl Code for Result { impl Code for RpcClientError { fn code(&self) -> i32 { match self { + RpcClientError::InvalidPayload(_) => INVALID_PAYLOAD, + RpcClientError::Io(_) => IO_ERROR, + RpcClientError::Jwt(_) => JWT_ERROR, RpcClientError::Jsonrpsee(e) => e.code(), - // Status code 13 == internal error - _ => INTERNAL_ERROR, } } } @@ -153,6 +158,7 @@ impl RpcClient { } if res.is_invalid() { + error!("Invalid payload ({}): {:?}", self.payload_source, res); return Err(RpcClientError::InvalidPayload( res.payload_status.status.to_string(), )) @@ -170,6 +176,7 @@ impl RpcClient { target = self.payload_source.to_string(), url = %self.auth_rpc, %payload_id, + code, ) )] pub async fn get_payload_v3( @@ -213,6 +220,7 @@ impl RpcClient { .set_code()?; if res.is_invalid() { + error!("Invalid payload ({}): {:?}", self.payload_source, res); return Err(RpcClientError::InvalidPayload(res.status.to_string()).set_code()); } @@ -227,6 +235,7 @@ impl RpcClient { target = self.payload_source.to_string(), url = %self.auth_rpc, %payload_id, + code, ) )] pub async fn get_payload_v4( @@ -291,6 +300,7 @@ impl RpcClient { .set_code()?; if res.is_invalid() { + error!("Invalid payload ({}): {:?}", self.payload_source, res); return Err(RpcClientError::InvalidPayload(res.status.to_string()).set_code()); } @@ -377,7 +387,7 @@ mod tests { use super::*; const AUTH_PORT: u32 = 8550; - const AUTH_ADDR: &str = "0.0.0.0"; + const AUTH_ADDR: &str = "127.0.0.1"; const SECRET: &str = "f79ae8046bc11c9927afe911db7143c51a806c4a537cc08e0d37140b0192f430"; #[test] diff --git a/src/flashblocks/inbound.rs b/src/flashblocks/inbound.rs new file mode 100644 index 00000000..fbefffbe --- /dev/null +++ b/src/flashblocks/inbound.rs @@ -0,0 +1,61 @@ +use super::primitives::FlashblocksPayloadV1; +use futures::StreamExt; +use tokio::sync::mpsc; +use tokio_tungstenite::{connect_async, tungstenite::Message}; +use tracing::{error, info}; +use url::Url; + +pub struct FlashblocksReceiverService { + url: Url, + sender: mpsc::Sender, +} + +impl FlashblocksReceiverService { + pub fn new( + url: String, + sender: mpsc::Sender, + ) -> Result { + Ok(Self { + url: Url::parse(&url)?, + sender, + }) + } + + pub async fn run(self) { + loop { + match self.connect_and_handle().await { + Ok(()) => break, + Err(e) => { + error!( + message = "Flashblocks receiver connection error, retrying in 5 seconds", + error = %e + ); + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + } + } + } + } + + async fn connect_and_handle(&self) -> Result<(), Box> { + let (ws_stream, _) = connect_async(self.url.as_str()).await?; + let (_, mut read) = ws_stream.split(); + + info!("Connected to Flashblocks receiver at {}", self.url); + + while let Some(msg) = read.next().await { + let msg = msg?; + match msg { + Message::Text(text) => { + if let Ok(flashblocks_msg) = serde_json::from_str::(&text) + // TODO: Version this + { + self.sender.send(flashblocks_msg).await?; + } + } + _ => continue, + } + } + + Ok(()) + } +} diff --git a/src/flashblocks/mod.rs b/src/flashblocks/mod.rs new file mode 100644 index 00000000..1d7be3d0 --- /dev/null +++ b/src/flashblocks/mod.rs @@ -0,0 +1,45 @@ +mod inbound; +mod outbound; +pub mod primitives; +mod service; +use inbound::FlashblocksReceiverService; +use outbound::FlashblocksOutboundService; +use std::net::SocketAddr; +use tokio::sync::mpsc; + +pub use service::FlashblocksService; + +pub struct Flashblocks {} + +impl Flashblocks { + pub fn run(builder_url: String, outbound_addr: String) -> eyre::Result { + let (tx, rx) = mpsc::channel(100); + let (outbound_tx, outbound_rx) = mpsc::channel(100); + + let receiver = FlashblocksReceiverService::new(builder_url, tx)?; + + tokio::spawn(async move { + let _ = receiver.run().await; + }); + + // Create and spawn the outbound WebSocket service + let outbound_service = FlashblocksOutboundService::new(outbound_rx); + let addr: SocketAddr = outbound_addr + .parse() + .map_err(|e| eyre::eyre!("Invalid outbound address {}: {}", outbound_addr, e))?; + + tokio::spawn(async move { + if let Err(e) = outbound_service.run(addr).await { + tracing::error!("Outbound service error: {}", e); + } + }); + + let service = FlashblocksService::new(outbound_tx); + let mut service_handle = service.clone(); + tokio::spawn(async move { + service_handle.run(rx).await; + }); + + Ok(service) + } +} diff --git a/src/flashblocks/outbound.rs b/src/flashblocks/outbound.rs new file mode 100644 index 00000000..6e6e0ed9 --- /dev/null +++ b/src/flashblocks/outbound.rs @@ -0,0 +1,77 @@ +use super::primitives::FlashblocksPayloadV1; +use futures::{SinkExt, StreamExt}; +use std::{net::SocketAddr, sync::Arc}; +use tokio::{ + net::TcpListener, + sync::{Mutex, mpsc}, +}; +use tokio_tungstenite::{accept_async, tungstenite::Message}; +use tracing::{debug, error, info}; + +type WebSocketSink = + futures::stream::SplitSink, Message>; + +pub struct FlashblocksOutboundService { + clients: Arc>>, + receiver: mpsc::Receiver, +} + +impl FlashblocksOutboundService { + pub fn new(receiver: mpsc::Receiver) -> Self { + Self { + clients: Arc::new(Mutex::new(Vec::new())), + receiver, + } + } + + pub async fn run(mut self, addr: SocketAddr) -> Result<(), Box> { + let listener = TcpListener::bind(&addr).await?; + info!("Outbound WebSocket server listening on: {}", addr); + + let clients = self.clients.clone(); + + // Spawn a task to handle new WebSocket connections + tokio::spawn(async move { + while let Ok((stream, addr)) = listener.accept().await { + debug!("New WebSocket connection from: {}", addr); + let clients = clients.clone(); + + tokio::spawn(async move { + match accept_async(stream).await { + Ok(ws_stream) => { + let (sink, _) = ws_stream.split(); + clients.lock().await.push(sink); + debug!("Client added: {}", addr); + } + Err(e) => error!("Error accepting WebSocket connection: {}", e), + } + }); + } + }); + + // Handle incoming messages from the channel and broadcast them + while let Some(payload) = self.receiver.recv().await { + let message = match serde_json::to_string(&payload) { + Ok(msg) => Message::Text(msg.into()), + Err(e) => { + error!("Failed to serialize payload: {}", e); + continue; + } + }; + + let mut clients = self.clients.lock().await; + clients.retain_mut(|sink| { + let send_future = sink.send(message.clone()); + match futures::executor::block_on(send_future) { + Ok(_) => true, + Err(e) => { + error!("Failed to send message to client: {}", e); + false // Remove failed client + } + } + }); + } + + Ok(()) + } +} diff --git a/src/flashblocks/primitives.rs b/src/flashblocks/primitives.rs new file mode 100644 index 00000000..baf53ead --- /dev/null +++ b/src/flashblocks/primitives.rs @@ -0,0 +1,73 @@ +use alloy_primitives::{Address, B256, Bloom, Bytes, U256}; +use alloy_rpc_types_engine::PayloadId; +use alloy_rpc_types_eth::Withdrawal; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +/// Represents the modified portions of an execution payload within a flashblock. +/// This structure contains only the fields that can be updated during block construction, +/// such as state root, receipts, logs, and new transactions. Other immutable block fields +/// like parent hash and block number are excluded since they remain constant throughout +/// the block's construction. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct ExecutionPayloadFlashblockDeltaV1 { + /// The state root of the block. + pub state_root: B256, + /// The receipts root of the block. + pub receipts_root: B256, + /// The logs bloom of the block. + pub logs_bloom: Bloom, + /// The gas used of the block. + #[serde(with = "alloy_serde::quantity")] + pub gas_used: u64, + /// The block hash of the block. + pub block_hash: B256, + /// The transactions of the block. + pub transactions: Vec, + /// Array of [`Withdrawal`] enabled with V2 + pub withdrawals: Vec, +} + +/// Represents the base configuration of an execution payload that remains constant +/// throughout block construction. This includes fundamental block properties like +/// parent hash, block number, and other header fields that are determined at +/// block creation and cannot be modified. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct ExecutionPayloadBaseV1 { + /// Ecotone parent beacon block root + pub parent_beacon_block_root: B256, + /// The parent hash of the block. + pub parent_hash: B256, + /// The fee recipient of the block. + pub fee_recipient: Address, + /// The previous randao of the block. + pub prev_randao: B256, + /// The block number. + #[serde(with = "alloy_serde::quantity")] + pub block_number: u64, + /// The gas limit of the block. + #[serde(with = "alloy_serde::quantity")] + pub gas_limit: u64, + /// The timestamp of the block. + #[serde(with = "alloy_serde::quantity")] + pub timestamp: u64, + /// The extra data of the block. + pub extra_data: Bytes, + /// The base fee per gas of the block. + pub base_fee_per_gas: U256, +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct FlashblocksPayloadV1 { + /// The payload id of the flashblock + pub payload_id: PayloadId, + /// The index of the flashblock in the block + pub index: u64, + /// The base execution payload configuration + #[serde(skip_serializing_if = "Option::is_none")] + pub base: Option, + /// The delta/diff containing modified portions of the execution payload + pub diff: ExecutionPayloadFlashblockDeltaV1, + /// Additional metadata associated with the flashblock + pub metadata: Value, +} diff --git a/src/flashblocks/service.rs b/src/flashblocks/service.rs new file mode 100644 index 00000000..af7c416d --- /dev/null +++ b/src/flashblocks/service.rs @@ -0,0 +1,218 @@ +use super::primitives::{ + ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, +}; +use alloy_primitives::U256; +use alloy_rpc_types_engine::PayloadId; +use alloy_rpc_types_engine::{ + BlobsBundleV1, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, +}; +use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelopeV3; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use thiserror::Error; +use tokio::sync::RwLock; +use tokio::sync::mpsc; +use tracing::error; +#[derive(Debug, Error)] +pub enum FlashblocksError { + #[error("Missing base payload for initial flashblock")] + MissingBasePayload, + #[error("Unexpected base payload for non-initial flashblock")] + UnexpectedBasePayload, + #[error("Missing delta for flashblock")] + MissingDelta, + #[error("Invalid index for flashblock")] + InvalidIndex, + #[error("Missing payload")] + MissingPayload, +} + +#[derive(Debug, Deserialize, Serialize)] +struct FlashbotsMessage { + method: String, + params: serde_json::Value, + #[serde(default)] + id: Option, +} + +// Simplify actor messages to just handle shutdown +#[derive(Debug)] +enum FlashblocksEngineMessage { + FlashblocksPayloadV1(FlashblocksPayloadV1), +} + +#[derive(Debug, Default)] +struct FlashblockBuilder { + base: Option, + flashblocks: Vec, +} + +impl FlashblockBuilder { + pub fn new() -> Self { + Self { + base: None, + flashblocks: Vec::new(), + } + } + + pub fn extend(&mut self, payload: FlashblocksPayloadV1) -> Result<(), FlashblocksError> { + tracing::debug!(message = "Extending payload", payload_id = %payload.payload_id, index = payload.index, has_base=payload.base.is_some()); + + // Check base payload rules + match (payload.index, payload.base) { + // First payload must have a base + (0, None) => return Err(FlashblocksError::MissingBasePayload), + (0, Some(base)) => self.base = Some(base), + // Subsequent payloads must have no base + (_, Some(_)) => return Err(FlashblocksError::UnexpectedBasePayload), + (_, None) => {} // Non-zero index without base is fine + } + + // Validate the index is contiguous + if payload.index != self.flashblocks.len() as u64 { + return Err(FlashblocksError::InvalidIndex); + } + + // Update latest diff and accumulate transactions and withdrawals + self.flashblocks.push(payload.diff); + + Ok(()) + } + + pub fn into_envelope(self) -> Result { + let base = self.base.ok_or(FlashblocksError::MissingPayload)?; + + // There must be at least one delta + let diff = self + .flashblocks + .last() + .ok_or(FlashblocksError::MissingDelta)?; + + let transactions = self + .flashblocks + .iter() + .flat_map(|diff| diff.transactions.clone()) + .collect(); + + let withdrawals = self + .flashblocks + .iter() + .flat_map(|diff| diff.withdrawals.clone()) + .collect(); + + Ok(OpExecutionPayloadEnvelopeV3 { + parent_beacon_block_root: base.parent_beacon_block_root, + block_value: U256::ZERO, + blobs_bundle: BlobsBundleV1 { + commitments: Vec::new(), + proofs: Vec::new(), + blobs: Vec::new(), + }, + should_override_builder: false, + execution_payload: ExecutionPayloadV3 { + blob_gas_used: 0, + excess_blob_gas: 0, + payload_inner: ExecutionPayloadV2 { + withdrawals, + payload_inner: ExecutionPayloadV1 { + parent_hash: base.parent_hash, + fee_recipient: base.fee_recipient, + state_root: diff.state_root, + receipts_root: diff.receipts_root, + logs_bloom: diff.logs_bloom, + prev_randao: base.prev_randao, + block_number: base.block_number, + gas_limit: base.gas_limit, + gas_used: diff.gas_used, + timestamp: base.timestamp, + extra_data: base.extra_data, + base_fee_per_gas: base.base_fee_per_gas, + block_hash: diff.block_hash, + transactions, + }, + }, + }, + }) + } +} + +#[derive(Clone)] +pub struct FlashblocksService { + // Current payload ID we're processing (set from external notification) + current_payload_id: Arc>, + + // flashblocks payload being constructed + best_payload: Arc>, + + // outbound sender for valid messages + outbound: mpsc::Sender, +} + +impl FlashblocksService { + pub fn new(outbound: mpsc::Sender) -> Self { + Self { + current_payload_id: Arc::new(RwLock::new(PayloadId::default())), + best_payload: Arc::new(RwLock::new(FlashblockBuilder::new())), + outbound, + } + } + + pub async fn get_best_payload( + &self, + ) -> Result, FlashblocksError> { + // consume the best payload and reset the builder + let payload = { + let mut builder = self.best_payload.write().await; + std::mem::take(&mut *builder).into_envelope()? + }; + *self.best_payload.write().await = FlashblockBuilder::new(); + + Ok(Some(payload)) + } + + pub async fn set_current_payload_id(&self, payload_id: PayloadId) { + tracing::debug!(message = "Setting current payload ID", payload_id = %payload_id); + *self.current_payload_id.write().await = payload_id; + } + + async fn on_event(&mut self, event: FlashblocksEngineMessage) { + match event { + FlashblocksEngineMessage::FlashblocksPayloadV1(payload) => { + tracing::debug!( + message = "Received flashblock payload", + payload_id = %payload.payload_id, + index = payload.index + ); + + // make sure the payload id matches the current payload id + let current_payload_id = *self.current_payload_id.read().await; + if current_payload_id != payload.payload_id { + error!(message = "Payload ID mismatch", current_payload_id = %current_payload_id, payload_id = %payload.payload_id); + return; + } + + if let Err(e) = self.best_payload.write().await.extend(payload.clone()) { + error!(message = "Failed to extend payload", error = %e); + } else { + // Broadcast the valid message + if let Err(e) = self.outbound.send(payload).await { + error!(message = "Failed to broadcast payload", error = %e); + } + } + } + } + } + + pub async fn run(&mut self, mut stream: mpsc::Receiver) { + loop { + let event = stream.recv().await; + match event { + Some(event) => { + self.on_event(FlashblocksEngineMessage::FlashblocksPayloadV1(event)) + .await + } + None => break, + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 7cf19d5c..ca4cf3cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,3 +32,6 @@ pub use server::*; mod tracing; pub use tracing::*; + +mod flashblocks; +pub use flashblocks::*; diff --git a/src/proxy.rs b/src/proxy.rs index 7afd478d..927891eb 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -206,7 +206,7 @@ mod tests { // None, )); - let temp_listener = TcpListener::bind("0.0.0.0:0").await?; + let temp_listener = TcpListener::bind("127.0.0.1:0").await?; let server_addr = temp_listener.local_addr()?; drop(temp_listener); @@ -247,7 +247,7 @@ mod tests { impl MockHttpServer { async fn serve() -> eyre::Result { - let listener = TcpListener::bind("0.0.0.0:0").await?; + let listener = TcpListener::bind("127.0.0.1:0").await?; let addr = listener.local_addr()?; let requests = Arc::new(Mutex::new(vec![])); diff --git a/src/server.rs b/src/server.rs index a2139dd3..a7f15a13 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,6 @@ use crate::client::rpc::RpcClient; use crate::debug_api::DebugServer; +use crate::flashblocks::FlashblocksService; use alloy_primitives::{B256, Bytes}; use moka::sync::Cache; use opentelemetry::trace::SpanKind; @@ -20,7 +21,7 @@ use op_alloy_rpc_types_engine::{ }; use serde::{Deserialize, Serialize}; -use tracing::{debug, info, instrument}; +use tracing::{debug, error, info, instrument}; use jsonrpsee::proc_macros::rpc; @@ -121,7 +122,8 @@ pub struct RollupBoostServer { pub builder_client: Arc, pub boost_sync: bool, pub payload_trace_context: Arc, - execution_mode: Arc>, + pub flashblocks_client: Option>, + pub execution_mode: Arc>, } impl RollupBoostServer { @@ -130,12 +132,14 @@ impl RollupBoostServer { builder_client: RpcClient, boost_sync: bool, initial_execution_mode: ExecutionMode, + flashblocks_client: Option, ) -> Self { Self { l2_client: Arc::new(l2_client), builder_client: Arc::new(builder_client), boost_sync, payload_trace_context: Arc::new(PayloadTraceContext::new()), + flashblocks_client: flashblocks_client.map(Arc::new), execution_mode: Arc::new(Mutex::new(initial_execution_mode)), } } @@ -289,6 +293,12 @@ impl EngineApiServer for RollupBoostServer { if execution_mode.is_disabled() { debug!(message = "execution mode is disabled, skipping FCU call to builder", "head_block_hash" = %fork_choice_state.head_block_hash); } else if should_send_to_builder { + if let Some(flashblocks_client) = &self.flashblocks_client { + if let Some(payload_id) = l2_response.payload_id { + flashblocks_client.set_current_payload_id(payload_id).await; + } + } + let builder_client = self.builder_client.clone(); tokio::spawn(async move { let _ = builder_client @@ -308,7 +318,9 @@ impl EngineApiServer for RollupBoostServer { fields( otel.kind = ?SpanKind::Server, %payload_id, - payload_source + payload_source, + gas_delta, + tx_count_delta, ) )] async fn get_payload_v3( @@ -356,7 +368,9 @@ impl EngineApiServer for RollupBoostServer { fields( otel.kind = ?SpanKind::Server, %payload_id, - payload_source + payload_source, + gas_delta, + tx_count_delta, ) )] async fn get_payload_v4( @@ -414,6 +428,39 @@ impl OpExecutionPayloadEnvelope { OpExecutionPayloadEnvelope::V4(_) => Version::V4, } } + + pub fn transactions(&self) -> &[Bytes] { + match self { + OpExecutionPayloadEnvelope::V3(v3) => { + &v3.execution_payload + .payload_inner + .payload_inner + .transactions + } + OpExecutionPayloadEnvelope::V4(v4) => { + &v4.execution_payload + .payload_inner + .payload_inner + .payload_inner + .transactions + } + } + } + + pub fn gas_used(&self) -> u64 { + match self { + OpExecutionPayloadEnvelope::V3(v3) => { + v3.execution_payload.payload_inner.payload_inner.gas_used + } + OpExecutionPayloadEnvelope::V4(v4) => { + v4.execution_payload + .payload_inner + .payload_inner + .payload_inner + .gas_used + } + } + } } impl From for ExecutionPayload { @@ -559,8 +606,28 @@ impl RollupBoostServer { return Ok(None); } + // Use the flashblocks payload if available + let payload = if let Some(flashblocks_client) = &self.flashblocks_client { + match flashblocks_client.get_best_payload().await { + Ok(payload) => payload, + Err(e) => { + error!(message = "error getting flashblocks payload", "error" = %e); + None + } + } + } else { + None + }; + let builder = self.builder_client.clone(); - let payload = builder.get_payload(payload_id, version).await?; + + // Fallback to the get_payload_v3 from the builder if no flashblocks payload is available + let payload = if let Some(payload) = payload { + info!(message = "using flashblocks payload"); + OpExecutionPayloadEnvelope::V3(payload) + } else { + builder.get_payload(payload_id, version).await? + }; // Send the payload to the local execution engine with engine_newPayload to validate the block from the builder. // Otherwise, we do not want to risk the network to a halt since op-node will not be able to propose the block. @@ -575,6 +642,17 @@ impl RollupBoostServer { let (l2_payload, builder_payload) = tokio::join!(l2_client_future, builder_client_future); let (payload, context) = match (builder_payload, l2_payload) { + (Ok(Some(builder)), Ok(l2)) => { + tracing::Span::current().record( + "gas_delta", + (builder.gas_used() - l2.gas_used()).to_string(), + ); + tracing::Span::current().record( + "tx_count_delta", + (builder.transactions().len() - l2.transactions().len()).to_string(), + ); + Ok((builder, PayloadSource::Builder)) + } (Ok(Some(builder)), _) => Ok((builder, PayloadSource::Builder)), (_, Ok(l2)) => Ok((l2, PayloadSource::L2)), (_, Err(e)) => Err(e), @@ -621,12 +699,12 @@ mod tests { use std::sync::Arc; use tokio::time::sleep; - const HOST: &str = "0.0.0.0"; + const HOST: &str = "127.0.0.1"; const L2_PORT: u16 = 8545; const L2_ADDR: &str = "127.0.0.1:8545"; const BUILDER_PORT: u16 = 8544; const BUILDER_ADDR: &str = "127.0.0.1:8544"; - const SERVER_ADDR: &str = "0.0.0.0:8556"; + const SERVER_ADDR: &str = "127.0.0.1:8556"; #[derive(Debug, Clone)] pub struct MockEngineServer { @@ -717,12 +795,13 @@ mod tests { builder_client, boost_sync, ExecutionMode::Enabled, + None, ); let module: RpcModule<()> = rollup_boost_client.try_into().unwrap(); let proxy_server = ServerBuilder::default() - .build("0.0.0.0:8556".parse::().unwrap()) + .build(SERVER_ADDR.parse::().unwrap()) .await .unwrap() .start(module); diff --git a/src/tracing.rs b/src/tracing.rs index fdd917c7..1c20bb67 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -57,6 +57,34 @@ impl SpanProcessor for MetricsSpanProcessor { ]) .collect::>(); + // 0 = no difference in gas build via builder vs l2 + // > 0 = gas used by builder block is greater than l2 block + // < 0 = gas used by l2 block is greater than builder block + let gas_delta = span + .attributes + .iter() + .find(|attr| attr.key.as_str() == "gas_delta") + .map(|attr| attr.value.as_str().to_string()); + + if let Some(gas_delta) = gas_delta { + histogram!("block_building_gas_delta", &labels) + .record(gas_delta.parse::().unwrap_or_default() as f64); + } + + // 0 = no difference in tx count build via builder vs l2 + // > 0 = num txs in builder block is greater than l2 block + // < 0 = num txs in l2 block is greater than builder block + let tx_count_delta = span + .attributes + .iter() + .find(|attr| attr.key.as_str() == "tx_count_delta") + .map(|attr| attr.value.as_str().to_string()); + + if let Some(tx_count_delta) = tx_count_delta { + histogram!("block_building_tx_count_delta", &labels) + .record(tx_count_delta.parse::().unwrap_or_default() as f64); + } + histogram!(format!("{}_duration", span.name), &labels).record(duration); }