diff --git a/Cargo.lock b/Cargo.lock index 453515f6..93fb7900 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,9 +97,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9cc9d81ace3da457883b0bdf76776e55f1b84219a9e9d55c27ad308548d3f" +checksum = "5674914c2cfdb866c21cb0c09d82374ee39a1395cf512e7515f4c014083b3fff" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b77018eec2154eb158869f9f2914a3ea577adf87b11be2764d4795d5ccccf7" +checksum = "ca3b746060277f3d7f9c36903bb39b593a741cb7afcb0044164c28f0e9b673f0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bf8b058ff364d6e94bcd2979d7da1862e94d2987065a4eb41fa9eac36e028a" +checksum = "bf98679329fa708fa809ea596db6d974da892b068ad45e48ac1956f582edf946" dependencies = [ "alloy-consensus", "alloy-eips", @@ -212,9 +212,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d134f3ac4926124eaf521a1031d11ea98816df3d39fc446fcfd6b36884603f" +checksum = "f562a81278a3ed83290e68361f2d1c75d018ae3b8589a314faf9303883e18ec9" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.12.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff5aae4c6dc600734b206b175f3200085ee82dcdaa388760358830a984ca9869" +checksum = "ef2d6e0448bfd057a4438226b3d2fd547a0530fa4226217dfb1682d09f108bd4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -255,22 +255,23 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb1c2792605e648bdd1fddcfed8ce0d39d3db495c71d2240cb53df8aee8aea1f" +checksum = "dc41384e9ab8c9b2fb387c52774d9d432656a28edcda1c2d4083e96051524518" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-serde", "alloy-trie", "serde", + "serde_with", ] [[package]] name = "alloy-hardforks" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce138b29a2f8e7ed97c064af8359dfa6559c12cba5e821ae4eb93081a56557e" +checksum = "819a3620fe125e0fff365363315ee5e24c23169173b19747dfd6deba33db8990" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -294,9 +295,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31cfdacfeb6b6b40bf6becf92e69e575c68c9f80311c3961d019e29c0b8d6be2" +checksum = "12c454fcfcd5d26ed3b8cae5933cbee9da5f0b05df19b46d4bd4446d1f082565" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -309,9 +310,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de68a3f09cd9ab029cf87d08630e1336ca9a530969689fd151d505fa888a2603" +checksum = "42d6d39eabe5c7b3d8f23ac47b0b683b99faa4359797114636c66e0743103d05" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -335,9 +336,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc2689c8addfc43461544d07a6f5f3a3e1f5f4efae61206cb5783dc383cfc8f" +checksum = "3704fa8b7ba9ba3f378d99b3d628c8bc8c2fc431b709947930f154e22a8368b6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -348,9 +349,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.12.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588a87b77b30452991151667522d2f2f724cec9c2ec6602e4187bc97f66d8095" +checksum = "98354b9c3d50de701a63693d5b6a37e468a93b970b2224f934dd745c727ef998" dependencies = [ "alloy-consensus", "alloy-eips", @@ -365,9 +366,9 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9a510692bef4871797062ca09ec7873c45dc68c7f3f72291165320f53606a3" +checksum = "2090f21bb6df43e147d976e754bc9a007ca851badbfc6685377aa679b5f151d9" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -407,9 +408,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ced931220f547d30313530ad315654b7862ef52631c90ab857d792865f84a7d" +checksum = "08800e8cbe70c19e2eb7cf3d7ff4b28bdd9b3933f8e1c8136c7d910617ba03bf" dependencies = [ "alloy-chains", "alloy-consensus", @@ -450,9 +451,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e37d6cf286fd30bacac525ab1491f9d1030d39ecce237821f2a5d5922eb9a37" +checksum = "ae68457a2c2ead6bd7d7acb5bf5f1623324b1962d4f8e7b0250657a3c3ab0a0b" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -493,9 +494,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1d1eac6e48b772c7290f0f79211a0e822a38b057535b514cc119abd857d5b6" +checksum = "162301b5a57d4d8f000bf30f4dcb82f9f468f3e5e846eeb8598dd39e7886932c" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -504,7 +505,6 @@ dependencies = [ "alloy-transport-http", "alloy-transport-ipc", "alloy-transport-ws", - "async-stream", "futures", "pin-project", "reqwest", @@ -514,16 +514,15 @@ dependencies = [ "tokio-stream", "tower 0.5.2", "tracing", - "tracing-futures", "url", "wasmtimer", ] [[package]] name = "alloy-rpc-types" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8589c6ae318fcc9624d42e9166f7f82b630d9ad13e180c52addf20b93a8af266" +checksum = "6cd8ca94ae7e2b32cc3895d9981f3772aab0b4756aa60e9ed0bcfee50f0e1328" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -534,9 +533,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0182187bcbe47f3a737f5eced007b7788d4ed37aba19d43fd3df123169b3b05e" +checksum = "e7bff682e76f3f72e9ddc75e54a1bd1db5ce53cbdf2cce2d63a3a981437f78f5" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -546,9 +545,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754d5062b594ed300a3bb0df615acb7bacdbd7bd1cd1a6e5b59fb936c5025a13" +checksum = "9f3ff6a778ebda3deaed9af17930d678611afe1effa895c4260b61009c314f82" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -558,9 +557,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cfd7ecb21a1bfe68ac6b551172e4d41f828bcc33a2e1563a65d482d4efc1cf" +checksum = "076b47e834b367d8618c52dd0a0d6a711ddf66154636df394805300af4923b8a" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -569,9 +568,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c1ddf8fb2e41fa49316185d7826ed034f55819e0017e65dc6715f911b8a1ee" +checksum = "48f39da9b760e78fc3f347fba4da257aa6328fb33f73682b26cc0a6874798f7d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -587,9 +586,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c81ae89a04859751bac72e5e73459bceb3e6a4d2541f2f1374e35be358fd171" +checksum = "94a2a86ad7b7d718c15e79d0779bd255561b6b22968dc5ed2e7c0fbc43bb55fe" dependencies = [ "alloy-primitives", "serde", @@ -597,9 +596,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662b720c498883427ffb9f5e38c7f02b56ac5c0cdd60b457e88ce6b6a20b9ce9" +checksum = "4ba838417c42e8f1fe5eb4f4bbfacb7b5d4b9e615b8d2e831b921e04bf0bed62" dependencies = [ "alloy-consensus", "alloy-eips", @@ -617,9 +616,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb082c325bdfd05a7c71f52cd1060e62491fbf6edf55962720bdc380847b0784" +checksum = "2c2f847e635ec0be819d06e2ada4bcc4e4204026a83c4bfd78ae8d550e027ae7" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -633,14 +632,15 @@ dependencies = [ "itertools 0.14.0", "serde", "serde_json", + "serde_with", "thiserror 2.0.12", ] [[package]] name = "alloy-rpc-types-mev" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c1b50012f55de4a6d58ee9512944089fa61a835e6fe3669844075bb6e0312e" +checksum = "fb1c9b23cedf70aeb99ea9f16b78cdf902f524e227922fb340e3eb899ebe96dc" dependencies = [ "alloy-consensus", "alloy-eips", @@ -653,9 +653,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf52c884c7114c5d1f1f2735634ba0f6579911427281fb02cbd5cb8147723ca" +checksum = "6fc58180302a94c934d455eeedb3ecb99cdc93da1dbddcdbbdb79dd6fe618b2a" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4fd0df1af2ed62d02e7acbc408a162a06f30cb91550c2ec34b11c760cdc0ba" +checksum = "0f9f089d78bb94148e0fcfda087d4ce5fd35a7002847b5e90610c0fcb140f7b4" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f26c17270c2ac1bd555c4304fe067639f0ddafdd3c8d07a200b2bb5a326e03" +checksum = "ae699248d02ade9db493bbdae61822277dc14ae0f82a5a4153203b60e34422a6" dependencies = [ "alloy-primitives", "arbitrary", @@ -691,9 +691,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d9fd649d6ed5b8d7e5014e01758efb937e8407124b182a7f711bf487a1a2697" +checksum = "3cf7d793c813515e2b627b19a15693960b3ed06670f9f66759396d06ebe5747b" dependencies = [ "alloy-primitives", "async-trait", @@ -706,17 +706,15 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c288c5b38be486bb84986701608f5d815183de990e884bb747f004622783e125" +checksum = "51a424bc5a11df0d898ce0fd15906b88ebe2a6e4f17a514b51bc93946bb756bd" dependencies = [ "alloy-consensus", "alloy-network", "alloy-primitives", "alloy-signer", "async-trait", - "coins-bip32", - "coins-bip39", "k256", "rand 0.8.5", "thiserror 2.0.12", @@ -794,9 +792,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b790b89e31e183ae36ac0a1419942e21e94d745066f5281417c3e4299ea39e" +checksum = "4f317d20f047b3de4d9728c556e2e9a92c9a507702d2016424cd8be13a74ca5e" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -817,9 +815,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f643645a33a681d09ac1ca2112014c2ca09c68aad301da4400484d59c746bc70" +checksum = "ff084ac7b1f318c87b579d221f11b748341d68b9ddaa4ffca5e62ed2b8cfefb4" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -832,9 +830,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c2d843199d0bdb4cbed8f1b6f2da7f68bcb9c5da7f57e789009e4e7e76d1bec" +checksum = "edb099cdad8ed2e6a80811cdf9bbf715ebf4e34c981b4a6e2d1f9daacbf8b218" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -852,9 +850,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d27aae8c7a6403d3d3e874ad2eeeadbf46267b614bac2d4d82786b9b8496464" +checksum = "0e915e1250dc129ad48d264573ccd08e4716fdda564a772fd217875b8459aff9" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -890,9 +888,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.16" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ef40a046b9bf141afc440cef596c79292708aade57c450dc74e843270fd8e7" +checksum = "1154c8187a5ff985c95a8b2daa2fedcf778b17d7668e5e50e556c4ff9c881154" dependencies = [ "alloy-primitives", "darling", @@ -1330,9 +1328,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" dependencies = [ "brotli", "flate2", @@ -1434,9 +1432,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +checksum = "08b5d4e069cbc868041a64bd68dc8cb39a0d79585cd6c5a24caa8c2d622121be" dependencies = [ "aws-lc-sys", "zeroize", @@ -1444,9 +1442,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ "bindgen 0.69.5", "cc", @@ -1631,12 +1629,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" -[[package]] -name = "bech32" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" - [[package]] name = "bimap" version = "0.6.3" @@ -1804,6 +1796,145 @@ dependencies = [ "zeroize", ] +[[package]] +name = "boa_ast" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c340fe0f0b267787095cbe35240c6786ff19da63ec7b69367ba338eace8169b" +dependencies = [ + "bitflags 2.9.1", + "boa_interner", + "boa_macros", + "boa_string", + "indexmap 2.10.0", + "num-bigint", + "rustc-hash 2.1.1", +] + +[[package]] +name = "boa_engine" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f620c3f06f51e65c0504ddf04978be1b814ac6586f0b45f6019801ab5efd37f9" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "boa_ast", + "boa_gc", + "boa_interner", + "boa_macros", + "boa_parser", + "boa_profiler", + "boa_string", + "bytemuck", + "cfg-if", + "dashmap 6.1.0", + "fast-float2", + "hashbrown 0.15.4", + "icu_normalizer 1.5.0", + "indexmap 2.10.0", + "intrusive-collections", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "num_enum", + "once_cell", + "pollster", + "portable-atomic", + "rand 0.8.5", + "regress", + "rustc-hash 2.1.1", + "ryu-js", + "serde", + "serde_json", + "sptr", + "static_assertions", + "tap", + "thin-vec", + "thiserror 2.0.12", + "time", +] + +[[package]] +name = "boa_gc" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2425c0b7720d42d73eaa6a883fbb77a5c920da8694964a3d79a67597ac55cce2" +dependencies = [ + "boa_macros", + "boa_profiler", + "boa_string", + "hashbrown 0.15.4", + "thin-vec", +] + +[[package]] +name = "boa_interner" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42407a3b724cfaecde8f7d4af566df4b56af32a2f11f0956f5570bb974e7f749" +dependencies = [ + "boa_gc", + "boa_macros", + "hashbrown 0.15.4", + "indexmap 2.10.0", + "once_cell", + "phf", + "rustc-hash 2.1.1", + "static_assertions", +] + +[[package]] +name = "boa_macros" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "boa_parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cc142dac798cdc6e2dbccfddeb50f36d2523bb977a976e19bdb3ae19b740804" +dependencies = [ + "bitflags 2.9.1", + "boa_ast", + "boa_interner", + "boa_macros", + "boa_profiler", + "fast-float2", + "icu_properties 1.5.1", + "num-bigint", + "num-traits", + "regress", + "rustc-hash 2.1.1", +] + +[[package]] +name = "boa_profiler" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4064908e7cdf9b6317179e9b04dcb27f1510c1c144aeab4d0394014f37a0f922" + +[[package]] +name = "boa_string" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7debc13fbf7997bf38bf8e9b20f1ad5e2a7d27a900e1f6039fe244ce30f589b5" +dependencies = [ + "fast-float2", + "paste", + "rustc-hash 2.1.1", + "sptr", + "static_assertions", +] + [[package]] name = "bollard" version = "0.18.1" @@ -1890,7 +2021,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2 0.10.9", "tinyvec", ] @@ -1928,6 +2058,20 @@ name = "bytemuck" version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] [[package]] name = "byteorder" @@ -2013,9 +2157,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] @@ -2096,9 +2240,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -2106,9 +2250,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -2118,9 +2262,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -2143,57 +2287,6 @@ dependencies = [ "cc", ] -[[package]] -name = "coins-bip32" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2073678591747aed4000dd468b97b14d7007f7936851d3f2f01846899f5ebf08" -dependencies = [ - "bs58", - "coins-core", - "digest 0.10.7", - "hmac", - "k256", - "serde", - "sha2 0.10.9", - "thiserror 1.0.69", -] - -[[package]] -name = "coins-bip39" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b169b26623ff17e9db37a539fe4f15342080df39f129ef7631df7683d6d9d4" -dependencies = [ - "bitvec", - "coins-bip32", - "hmac", - "once_cell", - "pbkdf2", - "rand 0.8.5", - "sha2 0.10.9", - "thiserror 1.0.69", -] - -[[package]] -name = "coins-core" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b962ad8545e43a28e14e87377812ba9ae748dd4fd963f4c10e9fcc6d13475b" -dependencies = [ - "base64 0.21.7", - "bech32", - "bs58", - "const-hex", - "digest 0.10.7", - "generic-array", - "ripemd", - "serde", - "sha2 0.10.9", - "sha3", - "thiserror 1.0.69", -] - [[package]] name = "colorchoice" version = "1.0.4" @@ -2253,6 +2346,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "const-hex" version = "1.14.1" @@ -2368,9 +2473,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -2496,9 +2601,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.3" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "373b7c5dbd637569a2cca66e8d66b8c446a1e7bf064ea321d265d7b3dfe7c97e" dependencies = [ "cfg-if", "cpufeatures", @@ -2929,14 +3034,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", + "serde", "signature", ] [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -2988,6 +3094,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -3173,6 +3285,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + [[package]] name = "fastrand" version = "2.3.0" @@ -3223,9 +3341,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "filetime" @@ -3252,7 +3370,7 @@ dependencies = [ ] [[package]] -name = "flashblocks-rpc" +name = "flashblocks-node" version = "0.1.0" dependencies = [ "alloy-consensus", @@ -3267,7 +3385,10 @@ dependencies = [ "alloy-rpc-types-eth", "brotli", "clap", + "ed25519-dalek", "eyre", + "flashblocks-p2p", + "flashblocks-rpc", "futures-util", "jsonrpsee 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "metrics", @@ -3276,7 +3397,10 @@ dependencies = [ "op-alloy-network", "op-alloy-rpc-types", "reth-db", - "reth-e2e-test-utils", + "reth-eth-wire", + "reth-ethereum", + "reth-network", + "reth-network-peers", "reth-node-api", "reth-node-builder", "reth-node-core", @@ -3304,70 +3428,153 @@ dependencies = [ ] [[package]] -name = "flashblocks-websocket-proxy" +name = "flashblocks-p2p" version = "0.1.0" dependencies = [ - "axum 0.8.4", - "backoff", - "brotli", + "alloy-primitives", + "alloy-rlp", + "blake3", "clap", - "dotenvy", + "ed25519-dalek", "eyre", "futures", - "hostname", - "http", "metrics", - "metrics-derive", - "metrics-exporter-prometheus 0.17.2", - "redis", - "reqwest", + "parking_lot", + "reth", + "reth-eth-wire", + "reth-ethereum", + "reth-network", + "reth-node-api", + "reth-node-builder", + "reth-op", + "reth-provider", + "reth-transaction-pool", + "rollup-boost", + "serde", "serde_json", - "testcontainers", - "testcontainers-modules", "thiserror 2.0.12", "tokio", - "tokio-tungstenite", - "tokio-util", + "tokio-stream", "tracing", - "tracing-subscriber 0.3.19", - "uuid", -] - -[[package]] -name = "flate2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" -dependencies = [ - "crc32fast", - "miniz_oxide", ] [[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +name = "flashblocks-rpc" +version = "0.1.0" dependencies = [ - "num-traits", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -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" + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-json-rpc", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "brotli", + "clap", + "ed25519-dalek", + "eyre", + "futures-util", + "jsonrpsee 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", + "metrics", + "metrics-derive", + "op-alloy-consensus 0.18.9", + "op-alloy-network", + "op-alloy-rpc-types", + "reth-db", + "reth-eth-wire", + "reth-network", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-optimism-chainspec", + "reth-optimism-evm", + "reth-optimism-forks", + "reth-optimism-primitives", + "reth-optimism-rpc", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-eth-api", + "reth-rpc-server-types", + "reth-tasks", + "reth-tracing", + "rollup-boost", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "url", +] + +[[package]] +name = "flashblocks-websocket-proxy" +version = "0.1.0" +dependencies = [ + "axum 0.8.4", + "backoff", + "brotli", + "clap", + "dotenvy", + "eyre", + "futures", + "hostname", + "http", + "metrics", + "metrics-derive", + "metrics-exporter-prometheus 0.17.2", + "redis", + "reqwest", + "serde_json", + "testcontainers", + "testcontainers-modules", + "thiserror 2.0.12", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tracing", + "tracing-subscriber 0.3.19", + "uuid", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +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", @@ -4022,9 +4229,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "base64 0.22.1", "bytes", @@ -4085,6 +4292,18 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke 0.7.5", + "zerofrom", + "zerovec 0.10.4", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -4093,9 +4312,9 @@ checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", - "yoke", + "yoke 0.8.0", "zerofrom", - "zerovec", + "zerovec 0.11.2", ] [[package]] @@ -4105,10 +4324,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "litemap 0.8.0", + "tinystr 0.8.1", + "writeable 0.6.1", + "zerovec 0.11.2", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap 0.7.5", + "tinystr 0.7.6", + "writeable 0.5.5", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider 1.5.0", + "tinystr 0.7.6", + "zerovec 0.10.4", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections 1.5.0", + "icu_normalizer_data 1.5.1", + "icu_properties 1.5.1", + "icu_provider 1.5.0", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec 0.10.4", ] [[package]] @@ -4118,20 +4388,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", + "icu_collections 2.0.0", + "icu_normalizer_data 2.0.0", + "icu_properties 2.0.1", + "icu_provider 2.0.0", "smallvec", - "zerovec", + "zerovec 0.11.2", ] +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections 1.5.0", + "icu_locid_transform", + "icu_properties_data 1.5.1", + "icu_provider 1.5.0", + "tinystr 0.7.6", + "zerovec 0.10.4", +] + [[package]] name = "icu_properties" version = "2.0.1" @@ -4139,21 +4430,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", - "icu_collections", + "icu_collections 2.0.0", "icu_locale_core", - "icu_properties_data", - "icu_provider", + "icu_properties_data 2.0.1", + "icu_provider 2.0.0", "potential_utf", "zerotrie", - "zerovec", + "zerovec 0.11.2", ] +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr 0.7.6", + "writeable 0.5.5", + "yoke 0.7.5", + "zerofrom", + "zerovec 0.10.4", +] + [[package]] name = "icu_provider" version = "2.0.0" @@ -4163,12 +4477,23 @@ dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", - "tinystr", - "writeable", - "yoke", + "tinystr 0.8.1", + "writeable 0.6.1", + "yoke 0.8.0", "zerofrom", "zerotrie", - "zerovec", + "zerovec 0.11.2", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -4194,8 +4519,8 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "icu_normalizer", - "icu_properties", + "icu_normalizer 2.0.0", + "icu_properties 2.0.1", ] [[package]] @@ -4349,6 +4674,26 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "intrusive-collections" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +dependencies = [ + "memoffset", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "ipconfig" version = "0.3.2" @@ -4990,6 +5335,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + [[package]] name = "litemap" version = "0.8.0" @@ -5124,13 +5475,22 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metrics" version = "0.24.2" @@ -5438,12 +5798,11 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "notify" -version = "8.0.0" +version = "8.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97" dependencies = [ "bitflags 2.9.1", - "filetime", "fsevent-sys", "inotify", "kqueue", @@ -5452,7 +5811,7 @@ dependencies = [ "mio", "notify-types", "walkdir", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5502,6 +5861,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "serde", ] [[package]] @@ -5603,12 +5963,13 @@ dependencies = [ [[package]] name = "nybbles" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d51b0175c49668a033fe7cc69080110d9833b291566cdf332905f3ad9c68a0" +checksum = "675b3a54e5b12af997abc8b6638b0aee51a28caedab70d4967e0d5db3a3f1d06" dependencies = [ "alloy-rlp", "arbitrary", + "cfg-if", "proptest", "ruint", "serde", @@ -5748,9 +6109,9 @@ dependencies = [ [[package]] name = "op-revm" -version = "7.0.1" +version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b97d2b54651fcd2955b454e86b2336c031e17925a127f4c44e2b63b2eeda923" +checksum = "ee9ba9cab294a5ed02afd1a1060220762b3c52911acab635db33822e93f7276d" dependencies = [ "auto_impl", "once_cell", @@ -6027,16 +6388,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac", -] - [[package]] name = "pem" version = "3.0.5" @@ -6174,6 +6525,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "polyval" version = "0.6.2" @@ -6198,7 +6555,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" dependencies = [ - "zerovec", + "zerovec 0.11.2", ] [[package]] @@ -6799,11 +7156,21 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "regress" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145bb27393fe455dd64d6cbc8d059adfa392590a45eadf079c01b11857e7b010" +dependencies = [ + "hashbrown 0.15.4", + "memchr", +] + [[package]] name = "reqwest" -version = "0.12.21" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8cea6b35bcceb099f30173754403d2eba0a5dc18cea3630fccd88251909288" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64 0.22.1", "bytes", @@ -6855,20 +7222,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] -name = "reth-basic-payload-builder" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +name = "reth" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-primitives", - "futures-core", - "futures-util", - "metrics", - "reth-chain-state", - "reth-metrics", - "reth-payload-builder", - "reth-payload-builder-primitives", + "alloy-rpc-types", + "aquamarine", + "clap", + "eyre", + "reth-chainspec", + "reth-cli-runner", + "reth-cli-util", + "reth-consensus", + "reth-consensus-common", + "reth-db", + "reth-ethereum-cli", + "reth-ethereum-payload-builder", + "reth-ethereum-primitives", + "reth-evm", + "reth-network", + "reth-network-api", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-node-metrics", + "reth-payload-builder", + "reth-payload-primitives", + "reth-primitives", + "reth-provider", + "reth-ress-protocol", + "reth-ress-provider", + "reth-revm", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-convert", + "reth-rpc-eth-types", + "reth-rpc-server-types", + "reth-tasks", + "reth-tokio-util", + "reth-transaction-pool", + "tokio", + "tracing", +] + +[[package]] +name = "reth-basic-payload-builder" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "futures-core", + "futures-util", + "metrics", + "reth-chain-state", + "reth-metrics", + "reth-payload-builder", + "reth-payload-builder-primitives", "reth-payload-primitives", "reth-primitives-traits", "reth-revm", @@ -6880,8 +7293,8 @@ dependencies = [ [[package]] name = "reth-chain-state" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6911,8 +7324,8 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-chains", "alloy-consensus", @@ -6931,8 +7344,8 @@ dependencies = [ [[package]] name = "reth-cli" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-genesis", "clap", @@ -6945,8 +7358,8 @@ dependencies = [ [[package]] name = "reth-cli-commands" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "ahash", "alloy-chains", @@ -7016,8 +7429,8 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "reth-tasks", "tokio", @@ -7026,8 +7439,8 @@ dependencies = [ [[package]] name = "reth-cli-util" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7039,12 +7452,13 @@ dependencies = [ "secp256k1 0.30.0", "serde", "thiserror 2.0.12", + "tikv-jemallocator", ] [[package]] name = "reth-codecs" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7063,8 +7477,8 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "convert_case", "proc-macro2", @@ -7074,8 +7488,8 @@ dependencies = [ [[package]] name = "reth-config" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "eyre", "humantime-serde", @@ -7089,8 +7503,8 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7102,8 +7516,8 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7114,8 +7528,8 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7133,13 +7547,14 @@ dependencies = [ "reth-tracing", "ringbuffer", "serde", + "serde_json", "tokio", ] [[package]] name = "reth-db" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "derive_more", @@ -7164,8 +7579,8 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7192,8 +7607,8 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7221,8 +7636,8 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7236,8 +7651,8 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7262,8 +7677,8 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7286,8 +7701,8 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "data-encoding", @@ -7310,8 +7725,8 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7343,58 +7758,10 @@ dependencies = [ "tracing", ] -[[package]] -name = "reth-e2e-test-utils" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-network", - "alloy-primitives", - "alloy-rpc-types-engine", - "alloy-rpc-types-eth", - "alloy-signer", - "alloy-signer-local", - "derive_more", - "eyre", - "futures-util", - "jsonrpsee 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", - "reth-chainspec", - "reth-db", - "reth-engine-local", - "reth-ethereum-primitives", - "reth-network-api", - "reth-network-peers", - "reth-node-api", - "reth-node-builder", - "reth-node-core", - "reth-node-ethereum", - "reth-payload-builder", - "reth-payload-builder-primitives", - "reth-payload-primitives", - "reth-provider", - "reth-rpc-api", - "reth-rpc-builder", - "reth-rpc-eth-api", - "reth-rpc-layer 1.5.0", - "reth-rpc-server-types", - "reth-stages-types", - "reth-tasks", - "reth-tokio-util", - "reth-tracing", - "revm", - "serde_json", - "tokio", - "tokio-stream", - "tracing", - "url", -] - [[package]] name = "reth-ecies" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "aes", "alloy-primitives", @@ -7424,8 +7791,8 @@ dependencies = [ [[package]] name = "reth-engine-local" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7448,8 +7815,8 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7473,8 +7840,8 @@ dependencies = [ [[package]] name = "reth-engine-service" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "futures", "pin-project", @@ -7496,8 +7863,8 @@ dependencies = [ [[package]] name = "reth-engine-tree" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7548,8 +7915,8 @@ dependencies = [ [[package]] name = "reth-engine-util" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -7575,8 +7942,8 @@ dependencies = [ [[package]] name = "reth-era" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7591,8 +7958,8 @@ dependencies = [ [[package]] name = "reth-era-downloader" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "bytes", @@ -7606,8 +7973,8 @@ dependencies = [ [[package]] name = "reth-era-utils" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7630,8 +7997,8 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -7641,8 +8008,8 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7669,8 +8036,8 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-chains", "alloy-consensus", @@ -7688,10 +8055,107 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "reth-ethereum" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" +dependencies = [ + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "reth-chainspec", + "reth-cli-util", + "reth-codecs", + "reth-consensus", + "reth-consensus-common", + "reth-db", + "reth-eth-wire", + "reth-ethereum-cli", + "reth-ethereum-consensus", + "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", + "reth-network", + "reth-network-api", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-eth-types", + "reth-storage-api", + "reth-tasks", + "reth-trie", + "reth-trie-db", +] + +[[package]] +name = "reth-ethereum-cli" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types", + "backon", + "clap", + "eyre", + "futures", + "reth-basic-payload-builder", + "reth-chainspec", + "reth-cli", + "reth-cli-commands", + "reth-cli-runner", + "reth-cli-util", + "reth-config", + "reth-consensus", + "reth-db", + "reth-db-api", + "reth-downloaders", + "reth-errors", + "reth-ethereum-payload-builder", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-exex", + "reth-fs-util", + "reth-network", + "reth-network-api", + "reth-network-p2p", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-node-events", + "reth-node-metrics", + "reth-payload-builder", + "reth-primitives-traits", + "reth-provider", + "reth-prune", + "reth-revm", + "reth-stages", + "reth-static-file", + "reth-tasks", + "reth-tracing", + "reth-transaction-pool", + "reth-trie", + "reth-trie-db", + "serde_json", + "similar-asserts", + "tokio", + "tracing", +] + [[package]] name = "reth-ethereum-consensus" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7706,8 +8170,8 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7724,8 +8188,8 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -7737,8 +8201,8 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7764,8 +8228,8 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7782,8 +8246,8 @@ dependencies = [ [[package]] name = "reth-etl" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "rayon", "reth-db-api", @@ -7792,8 +8256,8 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7815,13 +8279,14 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", "alloy-primitives", + "derive_more", "reth-chainspec", "reth-ethereum-forks", "reth-ethereum-primitives", @@ -7833,8 +8298,8 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-evm", "alloy-primitives", @@ -7846,8 +8311,8 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7864,8 +8329,8 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7902,8 +8367,8 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7916,8 +8381,8 @@ dependencies = [ [[package]] name = "reth-fs-util" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "serde", "serde_json", @@ -7926,8 +8391,8 @@ dependencies = [ [[package]] name = "reth-invalid-block-hooks" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7954,8 +8419,8 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "bytes", "futures", @@ -7974,8 +8439,8 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "bitflags 2.9.1", "byteorder", @@ -7991,8 +8456,8 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "bindgen 0.70.1", "cc", @@ -8000,8 +8465,8 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "futures", "metrics", @@ -8012,16 +8477,16 @@ dependencies = [ [[package]] name = "reth-net-banlist" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-net-nat" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "futures-util", "if-addrs", @@ -8034,8 +8499,8 @@ dependencies = [ [[package]] name = "reth-network" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8089,8 +8554,8 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "alloy-rpc-types-admin", @@ -8112,8 +8577,8 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8135,8 +8600,8 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8150,8 +8615,8 @@ dependencies = [ [[package]] name = "reth-network-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -8164,8 +8629,8 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "anyhow", "bincode", @@ -8181,8 +8646,8 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -8205,8 +8670,8 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8254,7 +8719,7 @@ dependencies = [ "reth-rpc-builder", "reth-rpc-engine-api", "reth-rpc-eth-types", - "reth-rpc-layer 1.5.0", + "reth-rpc-layer 1.5.1", "reth-stages", "reth-static-file", "reth-tasks", @@ -8270,8 +8735,8 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8321,8 +8786,8 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-rpc-types-engine", @@ -8357,8 +8822,8 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8381,8 +8846,8 @@ dependencies = [ [[package]] name = "reth-node-metrics" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "eyre", "http", @@ -8394,6 +8859,7 @@ dependencies = [ "procfs", "reth-metrics", "reth-tasks", + "tikv-jemalloc-ctl", "tokio", "tower 0.5.2", "tracing", @@ -8401,8 +8867,8 @@ dependencies = [ [[package]] name = "reth-node-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "reth-chainspec", "reth-db-api", @@ -8412,10 +8878,47 @@ dependencies = [ "reth-trie-db", ] +[[package]] +name = "reth-op" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" +dependencies = [ + "reth-chainspec", + "reth-codecs", + "reth-consensus", + "reth-consensus-common", + "reth-db", + "reth-eth-wire", + "reth-evm", + "reth-network", + "reth-network-api", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-optimism-chainspec", + "reth-optimism-consensus", + "reth-optimism-evm", + "reth-optimism-node", + "reth-optimism-primitives", + "reth-optimism-rpc", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-eth-types", + "reth-storage-api", + "reth-tasks", + "reth-transaction-pool", + "reth-trie", + "reth-trie-db", +] + [[package]] name = "reth-optimism-chainspec" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-chains", "alloy-consensus", @@ -8441,8 +8944,8 @@ dependencies = [ [[package]] name = "reth-optimism-cli" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8488,8 +8991,8 @@ dependencies = [ [[package]] name = "reth-optimism-consensus" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8513,8 +9016,8 @@ dependencies = [ [[package]] name = "reth-optimism-evm" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8538,8 +9041,8 @@ dependencies = [ [[package]] name = "reth-optimism-forks" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-op-hardforks", "alloy-primitives", @@ -8549,8 +9052,8 @@ dependencies = [ [[package]] name = "reth-optimism-node" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8596,8 +9099,8 @@ dependencies = [ [[package]] name = "reth-optimism-payload-builder" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8635,8 +9138,8 @@ dependencies = [ [[package]] name = "reth-optimism-primitives" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8655,8 +9158,8 @@ dependencies = [ [[package]] name = "reth-optimism-rpc" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8715,8 +9218,8 @@ dependencies = [ [[package]] name = "reth-optimism-storage" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8731,8 +9234,8 @@ dependencies = [ [[package]] name = "reth-optimism-txpool" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8767,8 +9270,8 @@ dependencies = [ [[package]] name = "reth-payload-builder" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8788,8 +9291,8 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "pin-project", "reth-payload-primitives", @@ -8800,8 +9303,8 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8819,8 +9322,8 @@ dependencies = [ [[package]] name = "reth-payload-util" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8829,8 +9332,8 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -8839,8 +9342,8 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "c-kzg", @@ -8853,8 +9356,8 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8886,8 +9389,8 @@ dependencies = [ [[package]] name = "reth-provider" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8931,8 +9434,8 @@ dependencies = [ [[package]] name = "reth-prune" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8959,8 +9462,8 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "arbitrary", @@ -8971,10 +9474,56 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "reth-ress-protocol" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "futures", + "reth-eth-wire", + "reth-ethereum-primitives", + "reth-network", + "reth-network-api", + "reth-storage-errors", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "reth-ress-provider" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "eyre", + "futures", + "parking_lot", + "reth-chain-state", + "reth-errors", + "reth-ethereum-primitives", + "reth-evm", + "reth-node-api", + "reth-primitives-traits", + "reth-ress-protocol", + "reth-revm", + "reth-storage-api", + "reth-tasks", + "reth-tokio-util", + "reth-trie", + "schnellru", + "tokio", + "tracing", +] + [[package]] name = "reth-revm" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "reth-primitives-traits", @@ -8986,8 +9535,8 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -9062,8 +9611,8 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-genesis", @@ -9090,8 +9639,8 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-network", "alloy-provider", @@ -9112,7 +9661,7 @@ dependencies = [ "reth-rpc-api", "reth-rpc-eth-api", "reth-rpc-eth-types", - "reth-rpc-layer 1.5.0", + "reth-rpc-layer 1.5.1", "reth-rpc-server-types", "reth-storage-api", "reth-tasks", @@ -9128,8 +9677,8 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-json-rpc", @@ -9150,8 +9699,8 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9180,12 +9729,13 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eips", + "alloy-evm", "alloy-json-rpc", "alloy-network", "alloy-primitives", @@ -9224,11 +9774,12 @@ dependencies = [ [[package]] name = "reth-rpc-eth-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", + "alloy-evm", "alloy-primitives", "alloy-rpc-types-eth", "alloy-sol-types", @@ -9280,8 +9831,8 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-rpc-types-engine", "http", @@ -9294,8 +9845,8 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9310,8 +9861,8 @@ dependencies = [ [[package]] name = "reth-stages" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9360,8 +9911,8 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9387,8 +9938,8 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "arbitrary", @@ -9401,8 +9952,8 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "parking_lot", @@ -9421,8 +9972,8 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "clap", @@ -9433,8 +9984,8 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9457,8 +10008,8 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9473,8 +10024,8 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "auto_impl", "dyn-clone", @@ -9491,8 +10042,8 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9507,8 +10058,8 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "tokio", "tokio-stream", @@ -9517,8 +10068,8 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "clap", "eyre", @@ -9532,8 +10083,8 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9571,8 +10122,8 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9596,8 +10147,8 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9622,8 +10173,8 @@ dependencies = [ [[package]] name = "reth-trie-db" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "reth-db-api", @@ -9635,8 +10186,8 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9660,8 +10211,8 @@ dependencies = [ [[package]] name = "reth-trie-sparse" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9678,17 +10229,17 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" -version = "1.5.0" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.0#61e38f9af154fe91e776d8f5e449d20a1571e8cf" +version = "1.5.1" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.5.1#dbe7ee9c21392f360ff01f6307480f5d7dd73a3a" dependencies = [ "zstd", ] [[package]] name = "revm" -version = "26.0.1" +version = "27.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2a493c73054a0f6635bad6e840cdbef34838e6e6186974833c901dff7dd709" +checksum = "70a84455f03d3480d4ed2e7271c15f2ec95b758e86d57cb8d258a8ff1c22e9a4" dependencies = [ "revm-bytecode", "revm-context", @@ -9705,9 +10256,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "5.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b395ee2212d44fcde20e9425916fee685b5440c3f8e01fabae8b0f07a2fd7f08" +checksum = "7a685758a4f375ae9392b571014b9779cfa63f0d8eb91afb4626ddd958b23615" dependencies = [ "bitvec", "once_cell", @@ -9718,9 +10269,9 @@ dependencies = [ [[package]] name = "revm-context" -version = "7.0.1" +version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b97b69d05651509b809eb7215a6563dc64be76a941666c40aabe597ab544d38" +checksum = "a990abf66b47895ca3e915d5f3652bb7c6a4cff6e5351fdf0fc2795171fd411c" dependencies = [ "cfg-if", "derive-where", @@ -9734,9 +10285,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "7.0.1" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8f4f06a1c43bf8e6148509aa06a6c4d28421541944842b9b11ea1a6e53468f" +checksum = "a303a93102fceccec628265efd550ce49f2817b38ac3a492c53f7d524f18a1ca" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -9750,9 +10301,9 @@ dependencies = [ [[package]] name = "revm-database" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763eb5867a109a85f8e47f548b9d88c9143c0e443ec056742052f059fa32f4f1" +checksum = "7db360729b61cc347f9c2f12adb9b5e14413aea58778cf9a3b7676c6a4afa115" dependencies = [ "alloy-eips", "revm-bytecode", @@ -9764,11 +10315,12 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5ecd19a5b75b862841113b9abdd864ad4b22e633810e11e6d620e8207e361d" +checksum = "b8500194cad0b9b1f0567d72370795fd1a5e0de9ec719b1607fa1566a23f039a" dependencies = [ "auto_impl", + "either", "revm-primitives", "revm-state", "serde", @@ -9776,9 +10328,9 @@ dependencies = [ [[package]] name = "revm-handler" -version = "7.0.1" +version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b61f992beaa7a5fc3f5fcf79f1093624fa1557dc42d36baa42114c2d836b59" +checksum = "03c35a17a38203976f97109e20eccf6732447ce6c9c42973bae42732b2e957ff" dependencies = [ "auto_impl", "derive-where", @@ -9795,9 +10347,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "7.0.1" +version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e4400a109a2264f4bf290888ac6d02432b6d5d070492b9dcf134b0c7d51354" +checksum = "e69abf6a076741bd5cd87b7d6c1b48be2821acc58932f284572323e81a8d4179" dependencies = [ "auto_impl", "either", @@ -9813,15 +10365,17 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.25.0" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aabdffc06bdb434d9163e2d63b6fae843559afd300ea3fbeb113b8a0d8ec728" +checksum = "c7b99a2332cf8eed9e9a22fffbf76dfadc99d2c45de6ae6431a1eb9f657dd97a" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-sol-types", "anstyle", + "boa_engine", + "boa_gc", "colorchoice", "revm", "serde", @@ -9831,9 +10385,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "22.0.1" +version = "23.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2481ef059708772cec0ce6bc4c84b796a40111612efb73b01adf1caed7ff9ac" +checksum = "d95c4a9a1662d10b689b66b536ddc2eb1e89f5debfcabc1a2d7b8417a2fa47cd" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -9843,15 +10397,16 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "23.0.0" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d581e78c8f132832bd00854fb5bf37efd95a52582003da35c25cd2cbfc63849" +checksum = "b68d54a4733ac36bd29ee645c3c2e5e782fb63f199088d49e2c48c64a9fedc15" dependencies = [ "ark-bls12-381", "ark-bn254", "ark-ec", "ark-ff 0.5.0", "ark-serialize 0.5.0", + "arrayref", "aurora-engine-modexp", "blst", "c-kzg", @@ -9880,9 +10435,9 @@ dependencies = [ [[package]] name = "revm-state" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6274928dd78f907103740b10800d3c0db6caeca391e75a159c168a1e5c78f8" +checksum = "106fec5c634420118c7d07a6c37110186ae7f23025ceac3a5dbe182eea548363" dependencies = [ "bitflags 2.9.1", "revm-bytecode", @@ -9996,17 +10551,21 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-serde", "anyhow", "assert_cmd", + "blake3", "bytes", "clap", "ctor", "dotenvy", + "ed25519-dalek", "eyre", "futures", + "hex", "http", "http-body-util", "hyper", @@ -10162,22 +10721,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "aws-lc-rs", "log", @@ -10249,9 +10808,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "aws-lc-rs", "ring", @@ -10283,6 +10842,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "ryu-js" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" + [[package]] name = "same-file" version = "1.0.6" @@ -10315,9 +10880,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", "ref-cast", @@ -10566,7 +11131,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.10.0", "schemars 0.9.0", - "schemars 1.0.3", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -10721,6 +11286,27 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +dependencies = [ + "console", + "serde", + "similar", +] + [[package]] name = "simple_asn1" version = "0.6.3" @@ -10818,6 +11404,12 @@ dependencies = [ "der", ] +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -11041,7 +11633,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.7", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -11089,6 +11681,12 @@ dependencies = [ "testcontainers", ] +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + [[package]] name = "thiserror" version = "1.0.69" @@ -11147,6 +11745,37 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tikv-jemalloc-ctl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21f216790c8df74ce3ab25b534e0718da5a1916719771d3fec23315c99e468b" +dependencies = [ + "libc", + "paste", + "tikv-jemalloc-sys", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" version = "0.3.41" @@ -11155,6 +11784,7 @@ checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", + "js-sys", "libc", "num-conv", "num_threads", @@ -11189,6 +11819,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec 0.10.4", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -11196,7 +11836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", - "zerovec", + "zerovec 0.11.2", ] [[package]] @@ -11216,17 +11856,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -11528,8 +12170,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "futures", - "futures-task", "pin-project", "tracing", ] @@ -11822,6 +12462,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -12689,9 +13335,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -12715,6 +13361,18 @@ dependencies = [ "bitflags 2.9.1", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "writeable" version = "0.6.1" @@ -12756,7 +13414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", - "rustix 1.0.7", + "rustix 1.0.8", ] [[package]] @@ -12765,6 +13423,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive 0.7.5", + "zerofrom", +] + [[package]] name = "yoke" version = "0.8.0" @@ -12773,10 +13443,22 @@ checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", - "yoke-derive", + "yoke-derive 0.8.0", "zerofrom", ] +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + [[package]] name = "yoke-derive" version = "0.8.0" @@ -12857,8 +13539,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", - "yoke", + "yoke 0.8.0", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke 0.7.5", "zerofrom", + "zerovec-derive 0.10.3", ] [[package]] @@ -12867,9 +13560,20 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ - "yoke", + "yoke 0.8.0", "zerofrom", - "zerovec-derive", + "zerovec-derive 0.11.1", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1817834d..8d054628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,14 @@ members = [ "crates/rollup-boost", "crates/websocket-proxy", "crates/flashblocks-rpc", + "crates/flashblocks-p2p", + "crates/flashblocks-node", ] [workspace.dependencies] rollup-boost = { path = "crates/rollup-boost" } +flashblocks-p2p = { path = "crates/flashblocks-p2p" } +flashblocks-rpc = { path = "crates/flashblocks-rpc" } tracing = "0.1.4" tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } @@ -20,12 +24,27 @@ serde_json = "1.0.96" metrics = "0.24.0" metrics-derive = "0.1" tokio = { version = "1", features = ["full"] } +tokio-stream = "0.1.17" eyre = "0.6.12" url = "2.2.0" sha2 = { version = "0.10", default-features = false } # Reth deps -reth-optimism-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } +reth-optimism-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +# Issue with reth feature compatibility. Include this in all crates for temp fix. +reth-op = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1", features = [ + "full", + "alloy-compat", +] } +reth = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-eth-wire = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-network = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } # Alloy libraries alloy-rpc-types-engine = "1.0.13" @@ -38,6 +57,7 @@ alloy-consensus = "1.0.13" alloy-rpc-types = "1.0.13" alloy-genesis = "1.0.13" alloy-rpc-client = "1.0.13" +alloy-rlp = "0.3.12" alloy-provider = "1.0.13" op-alloy-network = "0.18.7" op-alloy-rpc-types-engine = "0.18.7" diff --git a/crates/flashblocks-node/Cargo.toml b/crates/flashblocks-node/Cargo.toml new file mode 100644 index 00000000..9e14846f --- /dev/null +++ b/crates/flashblocks-node/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "flashblocks-node" +version = "0.1.0" +edition = "2024" +license = "MIT" + +[dependencies] +rollup-boost.workspace = true +flashblocks-p2p.workspace = true +flashblocks-rpc.workspace = true + +reth-optimism-node = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-forks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1", features = [ + "test-utils", +] } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-node-core = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-tracing = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } + +reth-ethereum = { workspace = true, features = ["node", "network", "cli"] } +reth-eth-wire = { workspace = true } +reth-network = { workspace = true } +reth-network-peers = { workspace = true } +ed25519-dalek = { version = "2", features = ["serde"] } + +alloy-eips.workspace = true +alloy-primitives.workspace = true +alloy-json-rpc.workspace = true +alloy-rpc-types.workspace = true +alloy-rpc-types-engine.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-consensus.workspace = true +alloy-genesis.workspace = true +alloy-rpc-client.workspace = true +alloy-provider.workspace = true +op-alloy-network.workspace = true +op-alloy-consensus.workspace = true +op-alloy-rpc-types.workspace = true + +tokio.workspace = true +tokio-tungstenite.workspace = true +url.workspace = true +tracing.workspace = true +clap.workspace = true +eyre.workspace = true +metrics.workspace = true +metrics-derive.workspace = true +serde_json.workspace = true +serde.workspace = true + +jsonrpsee = { version = "0.25.1" } +futures-util = "0.3.31" +brotli = "8.0.1" diff --git a/crates/flashblocks-rpc/src/bin/main.rs b/crates/flashblocks-node/src/bin/main.rs similarity index 53% rename from crates/flashblocks-rpc/src/bin/main.rs rename to crates/flashblocks-node/src/bin/main.rs index 177432ca..ffccb19c 100644 --- a/crates/flashblocks-rpc/src/bin/main.rs +++ b/crates/flashblocks-node/src/bin/main.rs @@ -1,39 +1,40 @@ #![allow(missing_docs, rustdoc::missing_crate_level_docs)] - use clap::Parser; +use ed25519_dalek::{SigningKey, VerifyingKey}; +use flashblocks_node::FlashblocksNodeArgs; +use flashblocks_p2p::protocol::handler::{FlashblocksHandle, FlashblocksP2PProtocol}; use flashblocks_rpc::{EthApiOverrideServer, FlashblocksApiExt, FlashblocksOverlay}; +use reth_ethereum::network::{NetworkProtocols, protocol::IntoRlpxSubProtocol}; use reth_optimism_cli::{Cli, chainspec::OpChainSpecParser}; use reth_optimism_node::{OpNode, args::RollupArgs}; +use tokio::sync::broadcast; use tracing::info; -#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] +#[derive(Debug, Clone, clap::Args)] #[command(next_help_heading = "Rollup")] struct FlashblocksRollupArgs { #[command(flatten)] - rollup_args: RollupArgs, - - #[arg(long = "flashblocks.enabled", default_value = "false")] - flashblocks_enabled: bool, + pub rollup_args: RollupArgs, - #[arg(long = "flashblocks.websocket-url", value_name = "WEBSOCKET_URL")] - websocket_url: url::Url, + #[command(flatten)] + pub flashblock_args: Option, } -fn main() { +pub fn main() { if let Err(err) = Cli::::parse().run(async move |builder, args| { let rollup_args = args.rollup_args; let chain_spec = builder.config().chain.clone(); + let (inbound_tx, inbound_rx) = broadcast::channel(100); + + let flashblocks_overlay = FlashblocksOverlay::new(chain_spec, inbound_rx); + flashblocks_overlay.clone().start()?; info!(target: "reth::cli", "Launching Flashblocks RPC overlay node"); let handle = builder .node(OpNode::new(rollup_args)) .extend_rpc_modules(move |ctx| { - if args.flashblocks_enabled { - let mut flashblocks_overlay = - FlashblocksOverlay::new(args.websocket_url, chain_spec); - flashblocks_overlay.start()?; - + if args.flashblock_args.is_some() { let eth_api = ctx.registry.eth_api().clone(); let api_ext = FlashblocksApiExt::new(eth_api.clone(), flashblocks_overlay); @@ -43,6 +44,20 @@ fn main() { }) .launch_with_debug_capabilities() .await?; + + let flashblocks_handle = FlashblocksHandle::new( + VerifyingKey::default(), + SigningKey::from_bytes(&[0u8; 32]), + inbound_tx, + ); + + let flashblocks_p2p_protocol = + FlashblocksP2PProtocol::new(handle.node.network.clone(), flashblocks_handle); + + handle + .node + .network + .add_rlpx_sub_protocol(flashblocks_p2p_protocol.into_rlpx_sub_protocol()); handle.node_exit_future.await }) { diff --git a/crates/flashblocks-node/src/lib.rs b/crates/flashblocks-node/src/lib.rs new file mode 100644 index 00000000..bf5e7730 --- /dev/null +++ b/crates/flashblocks-node/src/lib.rs @@ -0,0 +1,19 @@ +use clap::Args; +use ed25519_dalek::VerifyingKey; +use rollup_boost::parse_vk; + +#[derive(Args, Clone, Debug)] +#[group(requires = "flashblocks")] +pub struct FlashblocksNodeArgs { + /// Enable Flashblocks client + #[arg(long, env, required = false)] + pub flashblocks: bool, + + #[arg( + long = "flashblocks.authorizor_vk", + env = "FLASHBLOCKS_AUTHORIZOR_VK", + value_parser = parse_vk, + required = false, + )] + pub authorizor_vk: VerifyingKey, +} diff --git a/crates/flashblocks-rpc/src/tests/assets/genesis.json b/crates/flashblocks-node/tests/assets/genesis.json similarity index 100% rename from crates/flashblocks-rpc/src/tests/assets/genesis.json rename to crates/flashblocks-node/tests/assets/genesis.json diff --git a/crates/flashblocks-node/tests/p2p.rs b/crates/flashblocks-node/tests/p2p.rs new file mode 100644 index 00000000..e08b9410 --- /dev/null +++ b/crates/flashblocks-node/tests/p2p.rs @@ -0,0 +1,587 @@ +use alloy_consensus::Receipt; +use alloy_genesis::Genesis; +use alloy_primitives::{Address, B256, Bytes, TxHash, U256, address, b256}; +use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_client::RpcClient; +use alloy_rpc_types_engine::PayloadId; +use ed25519_dalek::SigningKey; +use flashblocks_p2p::protocol::handler::{FlashblocksHandle, FlashblocksP2PProtocol}; +use flashblocks_rpc::{EthApiOverrideServer, FlashblocksApiExt, FlashblocksOverlay, Metadata}; +use op_alloy_consensus::{OpPooledTransaction, OpTxEnvelope}; +use reth_eth_wire::BasicNetworkPrimitives; +use reth_ethereum::network::{NetworkProtocols, protocol::IntoRlpxSubProtocol}; +use reth_network::{NetworkHandle, Peers, PeersInfo}; +use reth_network_peers::{NodeRecord, PeerId}; +use reth_node_builder::{Node, NodeBuilder, NodeConfig, NodeHandle}; +use reth_node_core::{ + args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, + exit::NodeExitFuture, +}; +use reth_optimism_chainspec::OpChainSpecBuilder; +use reth_optimism_node::{OpNode, args::RollupArgs}; +use reth_optimism_primitives::{OpPrimitives, OpReceipt}; +use reth_provider::providers::BlockchainProvider; +use reth_tasks::{TaskExecutor, TaskManager}; +use reth_tracing::tracing_subscriber::{self, util::SubscriberInitExt}; +use rollup_boost::{ + Authorization, AuthorizedPayload, ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, + FlashblocksPayloadV1, +}; +use std::{any::Any, collections::HashMap, net::SocketAddr, str::FromStr, sync::Arc}; +use tokio::sync::broadcast; +use tracing::{Dispatch, info}; + +type Network = NetworkHandle< + BasicNetworkPrimitives< + OpPrimitives, + OpPooledTransaction, + reth_network::types::NewBlock>, + >, +>; + +pub struct NodeContext { + p2p_handle: FlashblocksHandle, + _flashblocks_tx: broadcast::Sender, + pub local_node_record: NodeRecord, + http_api_addr: SocketAddr, + _node_exit_future: NodeExitFuture, + _node: Box, + _network_handle: Network, +} + +impl NodeContext { + pub async fn provider(&self) -> eyre::Result { + let url = format!("http://{}", self.http_api_addr); + let client = RpcClient::builder().http(url.parse()?); + + Ok(RootProvider::new(client)) + } +} + +fn init_tracing(filter: &str) -> tracing::subscriber::DefaultGuard { + let sub = tracing_subscriber::fmt() + .with_env_filter(filter) + .with_target(false) + .with_target(false) + .without_time() + .finish(); + + Dispatch::new(sub).set_default() +} + +async fn setup_node( + exec: TaskExecutor, + authorizer_sk: SigningKey, + builder_sk: SigningKey, + peers: Vec<(PeerId, SocketAddr)>, +) -> eyre::Result { + let (inbound_tx, inbound_rx) = broadcast::channel(100); + + let genesis: Genesis = serde_json::from_str(include_str!("assets/genesis.json")).unwrap(); + let chain_spec = Arc::new( + OpChainSpecBuilder::base_mainnet() + .genesis(genesis) + .ecotone_activated() + .build(), + ); + + let network_config = NetworkArgs { + discovery: DiscoveryArgs { + disable_discovery: true, + ..DiscoveryArgs::default() + }, + ..NetworkArgs::default() + }; + + // Use with_unused_ports() to let Reth allocate random ports and avoid port collisions + let node_config = NodeConfig::new(chain_spec.clone()) + .with_network(network_config.clone()) + .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()) + .with_unused_ports(); + + let node = OpNode::new(RollupArgs::default()); + + let NodeHandle { + node, + node_exit_future, + } = NodeBuilder::new(node_config.clone()) + .testing_node(exec.clone()) + .with_types_and_provider::>() + .with_components(node.components_builder()) + .with_add_ons(node.add_ons()) + .extend_rpc_modules(move |ctx| { + // We are not going to use the websocket connection to send payloads so we use + // a dummy url. + let flashblocks_overlay = FlashblocksOverlay::new(chain_spec, inbound_rx); + flashblocks_overlay.clone().start()?; + + let eth_api = ctx.registry.eth_api().clone(); + let api_ext = FlashblocksApiExt::new(eth_api.clone(), flashblocks_overlay.clone()); + + ctx.modules.replace_configured(api_ext.into_rpc())?; + + Ok(()) + }) + .launch() + .await?; + + let p2p_handle = FlashblocksHandle::new( + authorizer_sk.verifying_key(), + builder_sk, + inbound_tx.clone(), + ); + + let p2p_protocol = FlashblocksP2PProtocol { + network: node.network.clone(), + handle: p2p_handle.clone(), + }; + + node.network + .add_rlpx_sub_protocol(p2p_protocol.into_rlpx_sub_protocol()); + + for (peer_id, addr) in peers { + // If a trusted peer is provided, add it to the network + node.network.add_peer(peer_id, addr); + } + + let http_api_addr = node + .rpc_server_handle() + .http_local_addr() + .ok_or_else(|| eyre::eyre!("Failed to get http api address"))?; + + let network_handle = node.network.clone(); + let local_node_record = network_handle.local_node_record(); + + Ok(NodeContext { + p2p_handle, + _flashblocks_tx: inbound_tx, + local_node_record, + http_api_addr, + _node_exit_future: node_exit_future, + _node: Box::new(node), + _network_handle: network_handle, + }) +} + +fn payload_base(block_number: u64, payload_id: PayloadId, index: u64) -> FlashblocksPayloadV1 { + FlashblocksPayloadV1 { + payload_id, + index, + base: Some(ExecutionPayloadBaseV1 { + parent_beacon_block_root: B256::default(), + parent_hash: B256::default(), + fee_recipient: Address::ZERO, + prev_randao: B256::default(), + block_number, + gas_limit: 0, + timestamp: 0, + extra_data: Bytes::new(), + base_fee_per_gas: U256::ZERO, + }), + diff: ExecutionPayloadFlashblockDeltaV1::default(), + metadata: serde_json::to_value(Metadata { + block_number: 1, + receipts: HashMap::default(), + new_account_balances: HashMap::default(), + }) + .unwrap(), + } +} + +const TEST_ADDRESS: Address = address!("0x1234567890123456789012345678901234567890"); +const PENDING_BALANCE: u64 = 4600; + +const TX1_HASH: TxHash = + b256!("0x2be2e6f8b01b03b87ae9f0ebca8bbd420f174bef0fbcc18c7802c5378b78f548"); +const TX2_HASH: TxHash = + b256!("0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8"); + +fn payload_next(payload_id: PayloadId, index: u64) -> FlashblocksPayloadV1 { + // Create second payload (index 1) with transactions + // tx1 hash: 0x2be2e6f8b01b03b87ae9f0ebca8bbd420f174bef0fbcc18c7802c5378b78f548 (deposit transaction) + // tx2 hash: 0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8 + let tx1 = Bytes::from_str("0x7ef8f8a042a8ae5ec231af3d0f90f68543ec8bca1da4f7edd712d5b51b490688355a6db794deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e200000044d000a118b00000000000000040000000067cb7cb0000000000077dbd4000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000014edd27304108914dd6503b19b9eeb9956982ef197febbeeed8a9eac3dbaaabdf000000000000000000000000fc56e7272eebbba5bc6c544e159483c4a38f8ba3").unwrap(); + let tx2 = Bytes::from_str("0xf8cd82016d8316e5708302c01c94f39635f2adf40608255779ff742afe13de31f57780b8646e530e9700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000156ddc81eed2a36d68302948ba0a608703e79b22164f74523d188a11f81c25a65dd59535bab1cd1d8b30d115f3ea07f4cfbbad77a139c9209d3bded89091867ff6b548dd714109c61d1f8e7a84d14").unwrap(); + + // Send another test flashblock payload + FlashblocksPayloadV1 { + payload_id, + index, + base: None, + diff: ExecutionPayloadFlashblockDeltaV1 { + state_root: B256::default(), + receipts_root: B256::default(), + gas_used: 0, + block_hash: B256::default(), + transactions: vec![tx1, tx2], + withdrawals: Vec::new(), + logs_bloom: Default::default(), + withdrawals_root: Default::default(), + }, + metadata: serde_json::to_value(Metadata { + block_number: 1, + receipts: { + let mut receipts = HashMap::default(); + receipts.insert( + TX1_HASH.to_string(), // transaction hash as string + OpReceipt::Legacy(Receipt { + status: true.into(), + cumulative_gas_used: 21000, + logs: vec![], + }), + ); + receipts.insert( + TX2_HASH.to_string(), // transaction hash as string + OpReceipt::Legacy(Receipt { + status: true.into(), + cumulative_gas_used: 45000, + logs: vec![], + }), + ); + receipts + }, + new_account_balances: { + let mut map = HashMap::default(); + map.insert( + TEST_ADDRESS.to_string(), + format!("0x{:x}", U256::from(PENDING_BALANCE)), + ); + map + }, + }) + .unwrap(), + } +} + +// #[tokio::test] +// async fn test_get_block_by_number_pending() -> eyre::Result<()> { +// reth_tracing::init_test_tracing(); +// let node = setup_node(None).await?; +// let provider = node.provider().await?; +// +// let latest_block = provider +// .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) +// .await? +// .expect("latest block expected"); +// assert_eq!(latest_block.number(), 0); +// +// // Querying pending block when it does not exists yet +// let pending_block = provider +// .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) +// .await?; +// assert_eq!(pending_block.is_none(), true); +// +// let base_payload = create_first_payload(); +// node.sender.send(base_payload.clone())?; +// tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; +// +// // Query pending block after sending the base payload with an empty delta +// let pending_block = provider +// .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) +// .await? +// .expect("pending block expected"); +// +// assert_eq!(pending_block.number(), 1); +// assert_eq!(pending_block.transactions.hashes().len(), 0); +// +// let second_payload = create_second_payload(); +// node.sender.send(second_payload.clone())?; +// tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; +// +// // Query pending block after sending the second payload with two transactions +// let block = provider +// .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) +// .await? +// .expect("pending block expected"); +// +// assert_eq!(block.number(), 1); +// assert_eq!(block.transactions.hashes().len(), 2); +// +// Ok(()) +// } +// +async fn setup_nodes(n: u8) -> eyre::Result<(Vec, SigningKey)> { + let mut nodes = Vec::new(); + let mut peers = Vec::new(); + let tasks = Box::leak(Box::new(TaskManager::current())); + let exec = Box::leak(Box::new(tasks.executor())); + let authorizer = SigningKey::from_bytes(&[0; 32]); + + for i in 0..n { + let builder = SigningKey::from_bytes(&[(i + 1) % n; 32]); + let node = setup_node(exec.clone(), authorizer.clone(), builder, peers.clone()).await?; + let enr = node.local_node_record; + peers.push((enr.id, enr.tcp_addr())); + nodes.push(node); + } + + tokio::time::sleep(tokio::time::Duration::from_millis(5000)).await; + + Ok((nodes, authorizer)) +} + +#[tokio::test] +async fn test_double_failover() -> eyre::Result<()> { + let _ = init_tracing("warn,flashblocks=trace"); + + let (nodes, authorizer) = setup_nodes(3).await?; + + let mut publish_flashblocks = nodes[0].p2p_handle.ctx.flashblock_tx.subscribe(); + tokio::spawn(async move { + while let Ok(payload) = publish_flashblocks.recv().await { + println!("\n////////////////////////////////////////////////////////////////////\n"); + println!( + "Received flashblock, payload_id: {}, index: {}", + payload.payload_id, payload.index + ); + println!("\n////////////////////////////////////////////////////////////////////\n"); + } + }); + + let latest_block = nodes[0] + .provider() + .await? + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .await? + .expect("latest block expected"); + assert_eq!(latest_block.number(), 0); + + // Querying pending block when it does not exists yet + let pending_block = nodes[0] + .provider() + .await? + .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) + .await?; + assert!(pending_block.is_none()); + + let payload_0 = payload_base(0, PayloadId::new([0; 8]), 0); + let authorization_0 = Authorization::new( + payload_0.payload_id, + 0, + &authorizer, + nodes[0].p2p_handle.ctx.builder_sk.verifying_key(), + ); + let msg = payload_0.clone(); + let authorized_0 = + AuthorizedPayload::new(&nodes[0].p2p_handle.ctx.builder_sk, authorization_0, msg); + nodes[0].p2p_handle.start_publishing(authorization_0); + nodes[0].p2p_handle.publish_new(authorized_0).unwrap(); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + let payload_1 = payload_next(payload_0.payload_id, 1); + let authorization_1 = Authorization::new( + payload_1.payload_id, + 0, + &authorizer, + nodes[1].p2p_handle.ctx.builder_sk.verifying_key(), + ); + let authorized_1 = AuthorizedPayload::new( + &nodes[1].p2p_handle.ctx.builder_sk, + authorization_1, + payload_1.clone(), + ); + nodes[1].p2p_handle.start_publishing(authorization_1); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + nodes[1].p2p_handle.publish_new(authorized_1).unwrap(); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + // Send a new block, this time from node 1 + let payload_2 = payload_next(payload_0.payload_id, 2); + let msg = payload_2.clone(); + let authorization_2 = Authorization::new( + payload_2.payload_id, + 0, + &authorizer, + nodes[2].p2p_handle.ctx.builder_sk.verifying_key(), + ); + let authorized_2 = AuthorizedPayload::new( + &nodes[2].p2p_handle.ctx.builder_sk, + authorization_2, + msg.clone(), + ); + nodes[2].p2p_handle.start_publishing(authorization_2); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + nodes[2].p2p_handle.publish_new(authorized_2).unwrap(); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + Ok(()) +} + +#[tokio::test] +async fn test_force_race_condition() -> eyre::Result<()> { + let _ = init_tracing("warn,flashblocks=trace"); + + let (nodes, authorizer) = setup_nodes(3).await?; + + let mut publish_flashblocks = nodes[0].p2p_handle.ctx.flashblock_tx.subscribe(); + tokio::spawn(async move { + while let Ok(payload) = publish_flashblocks.recv().await { + println!("\n////////////////////////////////////////////////////////////////////\n"); + println!( + "Received flashblock, payload_id: {}, index: {}", + payload.payload_id, payload.index + ); + println!("\n////////////////////////////////////////////////////////////////////\n"); + } + }); + + let latest_block = nodes[0] + .provider() + .await? + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .await? + .expect("latest block expected"); + assert_eq!(latest_block.number(), 0); + + // Querying pending block when it does not exists yet + let pending_block = nodes[0] + .provider() + .await? + .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) + .await?; + assert!(pending_block.is_none()); + + let payload_0 = payload_base(0, PayloadId::new([0; 8]), 0); + info!("Sending payload 0, index 0"); + let authorization = Authorization::new( + payload_0.payload_id, + 0, + &authorizer, + nodes[0].p2p_handle.ctx.builder_sk.verifying_key(), + ); + let msg = payload_0.clone(); + let authorized = + AuthorizedPayload::new(&nodes[0].p2p_handle.ctx.builder_sk, authorization, msg); + nodes[0].p2p_handle.start_publishing(authorization); + nodes[0].p2p_handle.publish_new(authorized).unwrap(); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + // let peers = node_0.network_handle.get_all_peers().await?; + // let peer_1 = &peers[0].remote_id; + // + // let rep_1 = node_0.network_handle.reputation_by_id(*peer_1).await?; + // info!(?rep_1, "Peer reputation"); + + // Query pending block after sending the base payload with an empty delta + let pending_block = nodes[1] + .provider() + .await? + .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) + .await? + .expect("pending block expected"); + + assert_eq!(pending_block.number(), 0); + assert_eq!(pending_block.transactions.hashes().len(), 0); + + info!("Sending payload 0, index 1"); + let payload_1 = payload_next(payload_0.payload_id, 1); + let authorization = Authorization::new( + payload_1.payload_id, + 0, + &authorizer, + nodes[0].p2p_handle.ctx.builder_sk.verifying_key(), + ); + let authorized = AuthorizedPayload::new( + &nodes[0].p2p_handle.ctx.builder_sk, + authorization, + payload_1.clone(), + ); + nodes[0].p2p_handle.publish_new(authorized).unwrap(); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + // let rep_1 = node_0.network_handle.reputation_by_id(*peer_1).await?; + // info!(?rep_1, "Peer reputation"); + + // Query pending block after sending the second payload with two transactions + let block = nodes[1] + .provider() + .await? + .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) + .await? + .expect("pending block expected"); + + assert_eq!(block.number(), 0); + assert_eq!(block.transactions.hashes().len(), 2); + + // Send a new block, this time from node 1 + let payload_2 = payload_base(1, PayloadId::new([1; 8]), 0); + info!("Sending payload 1, index 0"); + let authorization_1 = Authorization::new( + payload_2.payload_id, + 1, + &authorizer, + nodes[1].p2p_handle.ctx.builder_sk.verifying_key(), + ); + let authorization_2 = Authorization::new( + payload_2.payload_id, + 1, + &authorizer, + nodes[2].p2p_handle.ctx.builder_sk.verifying_key(), + ); + let msg = payload_2.clone(); + let authorized_1 = AuthorizedPayload::new( + &nodes[1].p2p_handle.ctx.builder_sk, + authorization_1, + msg.clone(), + ); + nodes[1].p2p_handle.start_publishing(authorization_1); + nodes[2].p2p_handle.start_publishing(authorization_2); + // Wait for clearance to go through + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + tracing::error!( + "{}", + nodes[1] + .p2p_handle + .publish_new(authorized_1.clone()) + .unwrap_err() + ); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + nodes[2].p2p_handle.stop_publishing(); + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + nodes[1].p2p_handle.publish_new(authorized_1)?; + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + Ok(()) +} + +// #[tokio::test] +// async fn test_get_balance_pending() -> eyre::Result<()> { +// reth_tracing::init_test_tracing(); +// let node = setup_node(None).await?; +// let provider = node.provider().await?; +// +// node.send_test_payloads().await?; +// +// let balance = provider.get_balance(TEST_ADDRESS).await?; +// assert_eq!(balance, U256::ZERO); +// +// let pending_balance = provider.get_balance(TEST_ADDRESS).pending().await?; +// assert_eq!(pending_balance, U256::from(PENDING_BALANCE)); +// +// Ok(()) +// } +// +// #[tokio::test] +// async fn test_get_transaction_receipt_pending() -> eyre::Result<()> { +// reth_tracing::init_test_tracing(); +// let node = setup_node(None).await?; +// let provider = node.provider().await?; +// +// let receipt = provider.get_transaction_receipt(TX1_HASH).await?; +// assert_eq!(receipt.is_none(), true); +// +// node.send_test_payloads().await?; +// +// let receipt = provider +// .get_transaction_receipt(TX1_HASH) +// .await? +// .expect("receipt expected"); +// assert_eq!(receipt.gas_used, 21000); +// +// // TODO: Add a new payload and validate that the receipts from the previous payload +// // are not returned. +// +// Ok(()) +// } diff --git a/crates/flashblocks-p2p/.gitignore b/crates/flashblocks-p2p/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/crates/flashblocks-p2p/.gitignore @@ -0,0 +1 @@ +target diff --git a/crates/flashblocks-p2p/Cargo.toml b/crates/flashblocks-p2p/Cargo.toml new file mode 100644 index 00000000..8067e1bb --- /dev/null +++ b/crates/flashblocks-p2p/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "flashblocks-p2p" +version = "0.1.0" +edition = "2024" +license = "MIT" + +[dependencies] +reth.workspace = true +reth-ethereum = { workspace = true, features = ["node", "network", "cli"] } +reth-eth-wire = { workspace = true } +reth-network = { workspace = true } +reth-node-api = { workspace = true } +reth-node-builder = { workspace = true } +reth-provider = { workspace = true } +reth-transaction-pool = { workspace = true } + +ed25519-dalek = { version = "2", features = ["serde"] } +blake3 = "1" # fast hashing for payload IDs +serde = { version = "1", features = ["derive"] } + +metrics.workspace = true +tokio = { workspace = true } +tokio-stream = { workspace = true } +eyre.workspace = true +futures = { workspace = true } +clap.workspace = true +tracing.workspace = true +alloy-primitives.workspace = true +alloy-rlp.workspace = true +serde_json = "1.0" +thiserror = "2" +parking_lot = "0.12" + + +rollup-boost = { path = "../rollup-boost" } + +[dev-dependencies] +reth-op.workspace = true diff --git a/crates/flashblocks-p2p/src/lib.rs b/crates/flashblocks-p2p/src/lib.rs new file mode 100644 index 00000000..c6479e8b --- /dev/null +++ b/crates/flashblocks-p2p/src/lib.rs @@ -0,0 +1,2 @@ +pub mod net; +pub mod protocol; diff --git a/crates/flashblocks-p2p/src/net/mod.rs b/crates/flashblocks-p2p/src/net/mod.rs new file mode 100644 index 00000000..d8cf9b47 --- /dev/null +++ b/crates/flashblocks-p2p/src/net/mod.rs @@ -0,0 +1,68 @@ +use reth::chainspec::Hardforks; +use reth_eth_wire::NetPrimitivesFor; +use reth_ethereum::network::api::FullNetwork; +use reth_network::{NetworkProtocols, protocol::IntoRlpxSubProtocol}; +use reth_node_api::{PrimitivesTy, TxTy}; +use reth_node_builder::{ + BuilderContext, + components::NetworkBuilder, + node::{FullNodeTypes, NodeTypes}, +}; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; + +use crate::protocol::handler::{ + FlashblocksHandle, FlashblocksP2PNetworkHandle, FlashblocksP2PProtocol, +}; + +#[derive(Debug)] +pub struct FlashblocksNetworkBuilder { + inner: T, + flashblocks_p2p_handle: Option, +} + +impl FlashblocksNetworkBuilder { + pub fn new(inner: T, flashblocks_p2p_handle: FlashblocksHandle) -> Self { + Self { + inner, + flashblocks_p2p_handle: Some(flashblocks_p2p_handle), + } + } + + pub fn disabled(inner: T) -> Self { + Self { + inner, + flashblocks_p2p_handle: None, + } + } +} + +impl NetworkBuilder for FlashblocksNetworkBuilder +where + T: NetworkBuilder, + Node: FullNodeTypes>, + Pool: TransactionPool>> + + Unpin + + 'static, + Network: FlashblocksP2PNetworkHandle + + NetworkProtocols + + FullNetwork>>, +{ + type Network = T::Network; + + async fn build_network( + self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result { + let handle = self.inner.build_network(ctx, pool).await?; + if let Some(flashblocks_handle) = self.flashblocks_p2p_handle { + let flashblocks_rlpx = FlashblocksP2PProtocol { + network: handle.clone(), + handle: flashblocks_handle, + }; + handle.add_rlpx_sub_protocol(flashblocks_rlpx.into_rlpx_sub_protocol()); + } + + Ok(handle) + } +} diff --git a/crates/flashblocks-p2p/src/protocol/connection.rs b/crates/flashblocks-p2p/src/protocol/connection.rs new file mode 100644 index 00000000..1a8a2afe --- /dev/null +++ b/crates/flashblocks-p2p/src/protocol/connection.rs @@ -0,0 +1,520 @@ +use crate::protocol::handler::{ + FlashblocksP2PNetworkHandle, FlashblocksP2PProtocol, MAX_FLASHBLOCK_INDEX, PeerMsg, + PublishingStatus, +}; +use alloy_primitives::bytes::BytesMut; +use futures::{Stream, StreamExt}; +use metrics::gauge; +use reth::payload::PayloadId; +use reth_ethereum::network::{api::PeerId, eth_wire::multiplex::ProtocolConnection}; +use reth_network::types::ReputationChangeKind; +use rollup_boost::{ + Authorized, AuthorizedPayload, FlashblocksP2PMsg, FlashblocksPayloadV1, StartPublish, + StopPublish, +}; +use std::{ + pin::Pin, + task::{Context, Poll, ready}, +}; +use tokio_stream::wrappers::BroadcastStream; +use tracing::trace; + +/// Represents a single P2P connection for the flashblocks protocol. +/// +/// This struct manages the bidirectional communication with a single peer in the flashblocks +/// P2P network. It handles incoming messages from the peer, validates and processes them, +/// and also streams outgoing messages that need to be broadcast. +/// +/// The connection implements the `Stream` trait to provide outgoing message bytes that +/// should be sent to the connected peer over the underlying protocol connection. +pub struct FlashblocksConnection { + /// The flashblocks protocol handler that manages the overall protocol state. + protocol: FlashblocksP2PProtocol, + /// The underlying protocol connection for sending and receiving raw bytes. + conn: ProtocolConnection, + /// The unique identifier of the connected peer. + peer_id: PeerId, + /// Receiver for peer messages to be sent to all peers. + /// We send bytes over this stream to avoid repeatedly having to serialize the payloads. + peer_rx: BroadcastStream, + /// Most recent payload ID received from this peer to track payload transitions. + payload_id: PayloadId, + /// A list of flashblock indices that we have already received from + /// this peer for the current payload, used to detect duplicate messages. + received: Vec, +} + +impl FlashblocksConnection { + /// Creates a new `FlashblocksConnection` instance. + /// + /// # Arguments + /// * `protocol` - The flashblocks protocol handler managing the connection. + /// * `conn` - The underlying protocol connection for sending and receiving messages. + /// * `peer_id` - The unique identifier of the connected peer. + /// * `peer_rx` - Receiver for peer messages to be sent to all peers. + pub fn new( + protocol: FlashblocksP2PProtocol, + conn: ProtocolConnection, + peer_id: PeerId, + peer_rx: BroadcastStream, + ) -> Self { + gauge!("p2p.flashblocks_peers", "capability" => FlashblocksP2PProtocol::::capability().to_string()).increment(1); + + Self { + protocol, + conn, + peer_id, + peer_rx, + payload_id: PayloadId::default(), + received: Vec::new(), + } + } +} + +impl Drop for FlashblocksConnection { + fn drop(&mut self) { + gauge!("p2p.flashblocks_peers", "capability" => FlashblocksP2PProtocol::::capability().to_string()).decrement(1); + } +} + +impl Stream for FlashblocksConnection { + type Item = BytesMut; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + loop { + // Check if there are any flashblocks ready to broadcast to our peers. + if let Poll::Ready(Some(res)) = this.peer_rx.poll_next_unpin(cx) { + match res { + Ok(peer_msg) => { + match peer_msg { + PeerMsg::FlashblocksPayloadV1(( + payload_id, + flashblock_index, + bytes, + )) => { + // Check if this flashblock actually originated from this peer. + if this.payload_id != payload_id + || this.received.get(flashblock_index) != Some(&true) + { + trace!( + target: "flashblocks::p2p", + peer_id = %this.peer_id, + %payload_id, + %flashblock_index, + "Broadcasting `FlashblocksPayloadV1` message to peer" + ); + return Poll::Ready(Some(bytes)); + } + } + PeerMsg::StartPublishing(bytes_mut) => { + trace!( + target: "flashblocks::p2p", + peer_id = %this.peer_id, + "Broadcasting `StartPublishing` to peer" + ); + return Poll::Ready(Some(bytes_mut)); + } + PeerMsg::StopPublishing(bytes_mut) => { + trace!( + target: "flashblocks::p2p", + peer_id = %this.peer_id, + "Broadcasting `StopPublishing` to peer" + ); + return Poll::Ready(Some(bytes_mut)); + } + } + } + Err(error) => { + tracing::error!( + target: "flashblocks::p2p", + %error, + "failed to receive flashblocks message from peer_rx" + ); + } + } + } + + // Check if there are any messages from the peer. + let Some(buf) = ready!(this.conn.poll_next_unpin(cx)) else { + return Poll::Ready(None); + }; + + let msg = match FlashblocksP2PMsg::decode(&mut &buf[..]) { + Ok(msg) => msg, + Err(error) => { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %this.peer_id, + %error, + "failed to decode flashblocks message from peer", + ); + this.protocol + .network + .reputation_change(this.peer_id, ReputationChangeKind::BadMessage); + return Poll::Ready(None); + } + }; + + match msg { + FlashblocksP2PMsg::Authorized(authorized) => { + if authorized.authorization.builder_vk + == this.protocol.handle.ctx.builder_sk.verifying_key() + { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %this.peer_id, + "received our own message from peer", + ); + this.protocol + .network + .reputation_change(this.peer_id, ReputationChangeKind::BadMessage); + continue; + } + + if let Err(error) = authorized.verify(this.protocol.handle.ctx.authorizer_vk) { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %this.peer_id, + %error, + "failed to verify flashblock", + ); + this.protocol + .network + .reputation_change(this.peer_id, ReputationChangeKind::BadMessage); + continue; + } + + match &authorized.msg { + rollup_boost::AuthorizedMsg::FlashblocksPayloadV1(_) => { + this.handle_flashblocks_payload_v1(authorized.into_unchecked()); + } + rollup_boost::AuthorizedMsg::StartPublish(_) => { + this.handle_start_publish(authorized.into_unchecked()); + } + rollup_boost::AuthorizedMsg::StopPublish(_) => { + this.handle_stop_publish(authorized.into_unchecked()); + } + } + } + } + } + } +} + +impl FlashblocksConnection { + /// Handles incoming flashblock payload messages from a peer. + /// + /// This method validates the flashblock payload, checks for duplicates and ordering, + /// updates the active publisher tracking, and forwards valid payloads for processing. + /// It also manages peer reputation based on message validity and prevents spam attacks. + /// + /// # Arguments + /// * `authorized_payload` - The authorized flashblock payload received from the peer + /// + /// # Behavior + /// - Validates timestamp to prevent replay attacks + /// - Tracks payload transitions and resets duplicate detection + /// - Prevents duplicate flashblock spam from the same peer + /// - Updates active publisher information from base payload data + /// - Forwards valid payloads to the protocol handler for processing + fn handle_flashblocks_payload_v1( + &mut self, + authorized_payload: AuthorizedPayload, + ) { + let mut state = self.protocol.handle.state.lock(); + let authorization = &authorized_payload.authorized.authorization; + let msg = authorized_payload.msg(); + + // check if this is an old payload + if authorization.timestamp < state.payload_timestamp { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + timestamp = authorization.timestamp, + "received flashblock with outdated timestamp", + ); + self.protocol + .network + .reputation_change(self.peer_id, ReputationChangeKind::BadMessage); + return; + } + + // Check if this is a new payload from this peer + if self.payload_id != msg.payload_id { + self.payload_id = msg.payload_id; + self.received.fill(false); + } + + // Check if the payload index is within the allowed range + if msg.index as usize > MAX_FLASHBLOCK_INDEX { + tracing::error!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + index = msg.index, + payload_id = %msg.payload_id, + max_index = MAX_FLASHBLOCK_INDEX, + "Received flashblocks payload with index exceeding maximum" + ); + return; + } + + // Check if this peer is spamming us with the same payload index + let len = self.received.len(); + self.received + .resize_with(len.max(msg.index as usize + 1), || false); + if self.received[msg.index as usize] { + // We've already seen this index from this peer. + // They could be trying to DOS us. + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + payload_id = %msg.payload_id, + index = msg.index, + "received duplicate flashblock from peer", + ); + self.protocol + .network + .reputation_change(self.peer_id, ReputationChangeKind::AlreadySeenTransaction); + return; + } + self.received[msg.index as usize] = true; + + state.publishing_status.send_modify(|status| { + let active_publishers = match status { + PublishingStatus::Publishing { .. } => { + // We are currently building, so we should not be seeing any new flashblocks + // over the p2p network. + tracing::error!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "received flashblock while already building", + ); + return; + } + PublishingStatus::WaitingToPublish { + active_publishers, .. + } => active_publishers, + PublishingStatus::NotPublishing { active_publishers } => active_publishers, + }; + + // Update the list of active publishers + if let Some((_, timestamp)) = active_publishers + .iter_mut() + .find(|(publisher, _)| *publisher == authorization.builder_vk) + { + // This is an existing publisher, we should update their block number + *timestamp = authorization.timestamp; + } else { + // This is a new publisher, we should add them to the list of active publishers + active_publishers.push((authorization.builder_vk, authorization.timestamp)); + } + }); + + self.protocol + .handle + .ctx + .publish(&mut state, authorized_payload); + } + + /// Handles incoming `StartPublish` messages from a peer. + /// + /// TODO: handle propogating this if we care. For now we assume direct peering. + /// + /// # Arguments + /// * `authorized_payload` - The authorized `StartPublish` message received from the peer + /// + /// # Behavior + /// - Validates the timestamp to prevent replay attacks + /// - Updates the publishing status to reflect the new publisher + /// - If we are currently publishing, sends a `StopPublish` message to ourselves + /// - If we are waiting to publish, updates the list of active publishers + /// - If we are not publishing, adds the new publisher to the list of active publishers + fn handle_start_publish(&mut self, authorized_payload: AuthorizedPayload) { + let state = self.protocol.handle.state.lock(); + let authorization = &authorized_payload.authorized.authorization; + + // Check if the request is expired for dos protection. + // It's important to ensure that this `StartPublish` request + // is very recent, or it could be used in a replay attack. + if state.payload_timestamp > authorization.timestamp { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + current_timestamp = state.payload_timestamp, + timestamp = authorized_payload.authorized.authorization.timestamp, + "received initiate build request with outdated timestamp", + ); + self.protocol + .network + .reputation_change(self.peer_id, ReputationChangeKind::BadMessage); + return; + } + + state.publishing_status.send_modify(|status| { + let active_publishers = match status { + PublishingStatus::Publishing { + authorization: our_authorization, + } => { + tracing::info!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "Received StartPublish over p2p, stopping publishing flashblocks" + ); + + let authorized = Authorized::new( + &self.protocol.handle.ctx.builder_sk, + *our_authorization, + StopPublish.into(), + ); + let p2p_msg = FlashblocksP2PMsg::Authorized(authorized); + let peer_msg = PeerMsg::StopPublishing(p2p_msg.encode()); + self.protocol.handle.ctx.peer_tx.send(peer_msg).ok(); + + *status = PublishingStatus::NotPublishing { + active_publishers: vec![( + our_authorization.builder_vk, + authorization.timestamp, + )], + }; + + return; + } + PublishingStatus::WaitingToPublish { + active_publishers, .. + } => { + // We are currently waiting to build, but someone else is requesting to build + // This could happen during a double failover. + // We have a potential race condition here so we'll just wait for the + // build request override to kick in next block. + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "Received StartPublish over p2p while already waiting to publish, ignoring", + ); + active_publishers + } + PublishingStatus::NotPublishing { active_publishers } => active_publishers, + }; + + if let Some((_, timestamp)) = active_publishers + .iter_mut() + .find(|(publisher, _)| *publisher == authorization.builder_vk) + { + // This is an existing publisher, we should update their block number + *timestamp = authorization.timestamp; + } else { + // This is a new publisher, we should add them to the list of active publishers + active_publishers.push((authorization.builder_vk, authorization.timestamp)); + } + }); + } + + /// Handles incoming `StopPublish` messages from a peer. + /// + /// TODO: handle propogating this if we care. For now we assume direct peering. + /// + /// # Arguments + /// * `authorized_payload` - The authorized `StopPublish` message received from the peer + /// + /// # Behavior + /// - Validates the timestamp to prevent replay attacks + /// - Updates the publishing status based on the current state + /// - If we are currently publishing, logs a warning + /// - If we are waiting to publish, removes the publisher from the list of active publishers and checks if we can start publishing + /// - If we are not publishing, removes the publisher from the list of active publishers + fn handle_stop_publish(&mut self, authorized_payload: AuthorizedPayload) { + let state = self.protocol.handle.state.lock(); + let authorization = &authorized_payload.authorized.authorization; + + // Check if the request is expired for dos protection. + // It's important to ensure that this `StartPublish` request + // is very recent, or it could be used in a replay attack. + if state.payload_timestamp > authorization.timestamp { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + current_timestamp = state.payload_timestamp, + timestamp = authorized_payload.authorized.authorization.timestamp, + "Received initiate build response with outdated timestamp", + ); + self.protocol + .network + .reputation_change(self.peer_id, ReputationChangeKind::BadMessage); + return; + } + + state.publishing_status.send_modify(|status| { + match status { + PublishingStatus::Publishing { .. } => { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "Received StopPublish over p2p while we are the publisher" + ); + } + PublishingStatus::WaitingToPublish { + active_publishers, + authorization, + .. + } => { + // We are currently waiting to build, but someone else is requesting to build + // This could happen during a double failover. + // We have a potential race condition here so we'll just wait for the + // build request override to kick in next block. + tracing::info!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "Received StopPublish over p2p while waiting to publish", + ); + + // Remove the publisher from the list of active publishers + if let Some(index) = active_publishers.iter().position(|(publisher, _)| { + *publisher == authorized_payload.authorized.authorization.builder_vk + }) { + active_publishers.remove(index); + } else { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "Received StopPublish for unknown publisher", + ); + } + + if active_publishers.is_empty() { + // If there are no active publishers left, we should stop waiting to publish + tracing::info!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "starting to publish" + ); + *status = PublishingStatus::Publishing { + authorization: *authorization, + }; + } else { + tracing::info!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "still waiting on active publishers", + ); + } + } + PublishingStatus::NotPublishing { active_publishers } => { + // Remove the publisher from the list of active publishers + if let Some(index) = active_publishers.iter().position(|(publisher, _)| { + *publisher == authorized_payload.authorized.authorization.builder_vk + }) { + active_publishers.remove(index); + } else { + tracing::warn!( + target: "flashblocks::p2p", + peer_id = %self.peer_id, + "Received StopPublish for unknown publisher", + ); + } + } + } + }); + } +} diff --git a/crates/flashblocks-p2p/src/protocol/error.rs b/crates/flashblocks-p2p/src/protocol/error.rs new file mode 100644 index 00000000..928148ed --- /dev/null +++ b/crates/flashblocks-p2p/src/protocol/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Error, Debug, Eq, PartialEq)] +pub enum FlashblocksP2PError { + #[error("attempt to publish flashblocks without clearance")] + NotClearedToPublish, + #[error( + "attempt to publish flashblocks with expired authorization. Make sure to call `start_publishing` first" + )] + ExpiredAuthorization, +} diff --git a/crates/flashblocks-p2p/src/protocol/handler.rs b/crates/flashblocks-p2p/src/protocol/handler.rs new file mode 100644 index 00000000..263e57dc --- /dev/null +++ b/crates/flashblocks-p2p/src/protocol/handler.rs @@ -0,0 +1,607 @@ +use crate::protocol::{connection::FlashblocksConnection, error::FlashblocksP2PError}; +use alloy_rlp::BytesMut; +use ed25519_dalek::{SigningKey, VerifyingKey}; +use futures::{Stream, StreamExt, stream}; +use parking_lot::Mutex; +use reth::payload::PayloadId; +use reth_eth_wire::Capability; +use reth_ethereum::network::{api::PeerId, protocol::ProtocolHandler}; +use reth_network::Peers; +use rollup_boost::{ + Authorization, Authorized, AuthorizedMsg, AuthorizedPayload, FlashblocksP2PMsg, + FlashblocksPayloadV1, StartPublish, StopPublish, +}; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::sync::{broadcast, watch}; +use tracing::debug; + +use reth_ethereum::network::{ + api::Direction, + eth_wire::{capability::SharedCapabilities, multiplex::ProtocolConnection, protocol::Protocol}, + protocol::{ConnectionHandler, OnNotSupported}, +}; +use tokio_stream::wrappers::BroadcastStream; + +/// Maximum frame size for rlpx messages. +const MAX_FRAME: usize = 1 << 24; // 16 MiB + +/// Maximum index for flashblocks payloads. +/// Not intended to ever be hit. Since we resize the flashblocks vector dynamically, +/// this is just a sanity check to prevent excessive memory usage. +pub(crate) const MAX_FLASHBLOCK_INDEX: usize = 100; + +/// The maximum number of seconds we will wait for a previous publisher to stop +/// before continueing anyways. +const MAX_PUBLISH_WAIT_SEC: u64 = 2; + +/// The maximum number of broadcast channel messages we will buffer +/// before dropping them. In practice, we should rarely need to buffer any messages. +const BROADCAST_BUFFER_CAPACITY: usize = 100; + +/// Trait bound for network handles that can be used with the flashblocks P2P protocol. +/// +/// This trait combines all the necessary bounds for a network handle to be used +/// in the flashblocks P2P system, including peer management capabilities. +pub trait FlashblocksP2PNetworkHandle: Clone + Unpin + Peers + std::fmt::Debug + 'static {} + +impl FlashblocksP2PNetworkHandle for N {} + +/// Messages that can be broadcast over a channel to each internal peer connection. +/// +/// These messages are used internally to coordinate the broadcasting of flashblocks +/// and publishing status changes to all connected peers. +#[derive(Clone, Debug)] +pub enum PeerMsg { + /// Send an already serialized flashblock to all peers. + FlashblocksPayloadV1((PayloadId, usize, BytesMut)), + /// Send a previously serialized StartPublish message to all peers. + StartPublishing(BytesMut), + /// Send a previously serialized StopPublish message to all peers. + StopPublishing(BytesMut), +} + +/// The current publishing status of this node in the flashblocks P2P network. +/// +/// This enum tracks whether we are actively publishing flashblocks, waiting to publish, +/// or not publishing at all. It also maintains information about other active publishers +/// to coordinate multi-builder scenarios and handle failover situations. +#[derive(Clone, Debug)] +pub enum PublishingStatus { + /// We are currently publishing flashblocks. + Publishing { + /// The authorization token that grants us permission to publish. + authorization: Authorization, + }, + /// We are waiting for the previous publisher to stop. + WaitingToPublish { + /// The authorization token we will use once we start publishing. + authorization: Authorization, + /// A map of active publishers (excluding ourselves) to their most recently published + /// or requested to publish block number. + active_publishers: Vec<(VerifyingKey, u64)>, + }, + /// We are not currently publishing flashblocks. + NotPublishing { + /// A map of previous publishers to their most recently published + /// or requested to publish block number. + active_publishers: Vec<(VerifyingKey, u64)>, + }, +} + +impl Default for PublishingStatus { + fn default() -> Self { + Self::NotPublishing { + active_publishers: Vec::new(), + } + } +} + +/// Protocol state that stores the flashblocks P2P protocol events and coordination data. +/// +/// This struct maintains the current state of flashblock publishing, including coordination +/// with other publishers, payload buffering, and ordering information. It serves as the +/// central state management for the flashblocks P2P protocol handler. +#[derive(Debug, Default)] +pub struct FlashblocksP2PState { + /// Current publishing status indicating whether we're publishing, waiting, or not publishing. + pub publishing_status: watch::Sender, + /// Most recent payload ID for the current block being processed. + pub payload_id: PayloadId, + /// Timestamp of the most recent flashblocks payload. + pub payload_timestamp: u64, + /// The index of the next flashblock to emit over the flashblocks stream. + /// Used to maintain strict ordering of flashblock delivery. + pub flashblock_index: usize, + /// Buffer of flashblocks for the current payload, indexed by flashblock sequence number. + /// Contains `None` for flashblocks not yet received, enabling out-of-order receipt + /// while maintaining in-order delivery. + pub flashblocks: Vec>, +} + +impl FlashblocksP2PState { + /// Returns the current publishing status of this node. + /// + /// This indicates whether the node is actively publishing flashblocks, + /// waiting to publish, or not publishing at all. + pub fn publishing_status(&self) -> PublishingStatus { + self.publishing_status.borrow().clone() + } +} + +/// Context struct containing shared resources for the flashblocks P2P protocol. +/// +/// This struct holds the network handle, cryptographic keys, and communication channels +/// used across all connections in the flashblocks P2P protocol. It provides the shared +/// infrastructure needed for message verification, signing, and broadcasting. +#[derive(Clone, Debug)] +pub struct FlashblocksP2PCtx { + /// Authorizer's verifying key used to verify authorization signatures from rollup-boost. + pub authorizer_vk: VerifyingKey, + /// Builder's signing key used to sign outgoing authorized P2P messages. + pub builder_sk: SigningKey, + /// Broadcast sender for peer messages that will be sent to all connected peers. + /// Messages may not be strictly ordered due to network conditions. + pub peer_tx: broadcast::Sender, + /// Broadcast sender for verified and strictly ordered flashblock payloads. + /// Used by RPC overlays and other consumers of flashblock data. + pub flashblock_tx: broadcast::Sender, +} + +/// Handle for the flashblocks P2P protocol. +/// +/// Encapsulates the shared context and mutable state of the flashblocks +/// P2P protocol. +#[derive(Clone, Debug)] +pub struct FlashblocksHandle { + /// Shared context containing network handle, keys, and communication channels. + pub ctx: FlashblocksP2PCtx, + /// Thread-safe mutable state of the flashblocks protocol. + /// Protected by a mutex to allow concurrent access from multiple connections. + pub state: Arc>, +} + +impl FlashblocksHandle { + pub fn new( + authorizer_vk: VerifyingKey, + builder_sk: SigningKey, + flashblock_tx: broadcast::Sender, + ) -> Self { + let peer_tx = broadcast::Sender::new(BROADCAST_BUFFER_CAPACITY); + let state = Arc::new(Mutex::new(FlashblocksP2PState::default())); + let ctx = FlashblocksP2PCtx { + authorizer_vk, + builder_sk, + peer_tx, + flashblock_tx, + }; + + Self { ctx, state } + } +} + +/// Main protocol handler for the flashblocks P2P protocol. +/// +/// This handler manages incoming and outgoing connections, coordinates flashblock publishing, +/// and maintains the protocol state across all peer connections. It implements the core +/// logic for multi-builder coordination and failover scenarios in HA sequencer setups. +#[derive(Clone, Debug)] +pub struct FlashblocksP2PProtocol { + /// Network handle used to update peer reputation and manage connections. + pub network: N, + /// Shared context containing network handle, keys, and communication channels. + pub handle: FlashblocksHandle, +} + +impl FlashblocksP2PProtocol { + /// Creates a new flashblocks P2P protocol handler. + /// + /// Initializes the handler with the necessary cryptographic keys, network handle, + /// and communication channels. The handler starts in a non-publishing state. + /// + /// # Arguments + /// * `network` - Network handle for peer management and reputation updates + /// * `handle` - Shared handle containing the protocol context and mutable state + pub fn new(network: N, handle: FlashblocksHandle) -> Self { + Self { + network: network.clone(), + handle, + } + } +} + +impl FlashblocksP2PProtocol { + /// Returns the P2P capability for the flashblocks v1 protocol. + /// + /// This capability is used during devp2p handshake to advertise support + /// for the flashblocks protocol with protocol name "flblk" and version 1. + pub fn capability() -> Capability { + Capability::new_static("flblk", 1) + } +} + +impl FlashblocksHandle { + /// Publishes a newly created flashblock from the payload builder to the P2P network. + /// + /// This method validates that the builder has authorization to publish and that + /// the authorization matches the current publishing session. The flashblock is + /// then processed, cached, and broadcast to all connected peers. + /// + /// # Arguments + /// * `authorized_payload` - The signed flashblock payload with authorization + /// + /// # Returns + /// * `Ok(())` if the flashblock was successfully published + /// * `Err` if the builder lacks authorization or the authorization is outdated + /// + /// # Note + /// You must call `start_publishing` before calling this method to establish + /// authorization for the current block. + pub fn publish_new( + &self, + authorized_payload: AuthorizedPayload, + ) -> Result<(), FlashblocksP2PError> { + let mut state = self.state.lock(); + let PublishingStatus::Publishing { authorization } = *state.publishing_status.borrow() + else { + return Err(FlashblocksP2PError::NotClearedToPublish); + }; + + if authorization != authorized_payload.authorized.authorization { + return Err(FlashblocksP2PError::ExpiredAuthorization); + } + self.ctx.publish(&mut state, authorized_payload); + Ok(()) + } + + /// Returns the current publishing status of this node. + /// + /// The status indicates whether the node is actively publishing flashblocks, + /// waiting for another publisher to stop, or not publishing at all. + /// + /// # Returns + /// The current `PublishingStatus` enum value + pub fn publishing_status(&self) -> PublishingStatus { + self.state.lock().publishing_status.borrow().clone() + } + + pub async fn await_clearance(&self) { + let mut status = self.state.lock().publishing_status.subscribe(); + // Safe to unwrap becuase self holds a sender. + status + .wait_for(|status| matches!(status, PublishingStatus::Publishing { .. })) + .await + .unwrap(); + } + + /// Initiates flashblock publishing for a new block. + /// + /// This method should be called immediately after receiving a ForkChoiceUpdated + /// with payload attributes and the corresponding Authorization token. It coordinates + /// with other potential publishers to ensure only one builder publishes at a time. + /// + /// The method may transition the node to either Publishing or WaitingToPublish state + /// depending on whether other builders are currently active. + /// + /// # Arguments + /// * `new_authorization` - Authorization token signed by rollup-boost for this block + /// + /// # Note + /// Calling this method does not guarantee immediate publishing clearance. + /// The node may need to wait for other publishers to stop first. + pub fn start_publishing(&self, new_authorization: Authorization) { + let state = self.state.lock(); + state.publishing_status.send_modify(|status| { + match status { + PublishingStatus::Publishing { authorization } => { + // We are already publishing, so we just update the authorization. + *authorization = new_authorization; + } + PublishingStatus::WaitingToPublish { + authorization, + active_publishers, + } => { + let most_recent_publisher = active_publishers + .iter() + .map(|(_, timestamp)| *timestamp) + .max() + .unwrap_or_default(); + // We are waiting to publish, so we update the authorization and + // the block number at which we requested to start publishing. + if new_authorization.timestamp >= most_recent_publisher + MAX_PUBLISH_WAIT_SEC { + // If the block number is greater than the one we requested to start publishing, + // we will update it. + tracing::warn!( + target: "flashblocks::p2p", + payload_id = %new_authorization.payload_id, + timestamp = %new_authorization.timestamp, + "waiting to publish timed out, starting to publish", + ); + *status = PublishingStatus::Publishing { + authorization: new_authorization, + }; + } else { + // Continue to wait for the previous builder to stop. + *authorization = new_authorization; + } + } + PublishingStatus::NotPublishing { active_publishers } => { + // Send an authorized `StartPublish` message to the network + let authorized_msg = AuthorizedMsg::StartPublish(StartPublish); + let authorized_payload = + Authorized::new(&self.ctx.builder_sk, new_authorization, authorized_msg); + let p2p_msg = FlashblocksP2PMsg::Authorized(authorized_payload); + let peer_msg = PeerMsg::StartPublishing(p2p_msg.encode()); + self.ctx.peer_tx.send(peer_msg).ok(); + + if active_publishers.is_empty() { + // If we have no previous publishers, we can start publishing immediately. + tracing::info!( + target: "flashblocks::p2p", + payload_id = %new_authorization.payload_id, + "starting to publish flashblocks", + ); + *status = PublishingStatus::Publishing { + authorization: new_authorization, + }; + } else { + // If we have previous publishers, we will wait for them to stop. + tracing::info!( + target: "flashblocks::p2p", + payload_id = %new_authorization.payload_id, + "waiting to publish flashblocks", + ); + *status = PublishingStatus::WaitingToPublish { + authorization: new_authorization, + active_publishers: active_publishers.clone(), + }; + } + } + } + }); + } + + /// Stops flashblock publishing and notifies the P2P network. + /// + /// This method broadcasts a StopPublish message to all connected peers and transitions + /// the node to a non-publishing state. It should be called when receiving a + /// ForkChoiceUpdated without payload attributes or without an Authorization token. + pub fn stop_publishing(&self) { + let state = self.state.lock(); + state.publishing_status.send_modify(|status| { + match status { + PublishingStatus::Publishing { authorization } => { + // We are currently publishing, so we send a stop message. + tracing::info!( + target: "flashblocks::p2p", + payload_id = %authorization.payload_id, + timestamp = %authorization.timestamp, + "stopping to publish flashblocks", + ); + let authorized_payload = + Authorized::new(&self.ctx.builder_sk, *authorization, StopPublish.into()); + let p2p_msg = FlashblocksP2PMsg::Authorized(authorized_payload); + let peer_msg = PeerMsg::StopPublishing(p2p_msg.encode()); + self.ctx.peer_tx.send(peer_msg).ok(); + *status = PublishingStatus::NotPublishing { + active_publishers: Vec::new(), + }; + } + PublishingStatus::WaitingToPublish { + authorization, + active_publishers, + .. + } => { + // We are waiting to publish, so we just update the status. + tracing::info!( + target: "flashblocks::p2p", + payload_id = %authorization.payload_id, + timestamp = %authorization.timestamp, + "aborting wait to publish flashblocks", + ); + let authorized_payload = + Authorized::new(&self.ctx.builder_sk, *authorization, StopPublish.into()); + let p2p_msg = FlashblocksP2PMsg::Authorized(authorized_payload); + let peer_msg = PeerMsg::StopPublishing(p2p_msg.encode()); + self.ctx.peer_tx.send(peer_msg).ok(); + *status = PublishingStatus::NotPublishing { + active_publishers: active_publishers.clone(), + }; + } + PublishingStatus::NotPublishing { .. } => {} + } + }); + } + + /// Returns a stream of ordered flashblocks starting from the beginning of the current payload. + /// + /// # Behavior + /// The stream will continue to yield flashblocks for consecutive payloads as well, so + /// consumers should take care to handle the stream appropriately. + pub fn flashblock_stream(&self) -> impl Stream + Send + 'static { + let flashblocks = self + .state + .lock() + .flashblocks + .clone() + .into_iter() + .map_while(|x| x); + + let receiver = self.ctx.flashblock_tx.subscribe(); + + let current = stream::iter(flashblocks); + let future = tokio_stream::StreamExt::map_while(BroadcastStream::new(receiver), |x| x.ok()); + current.chain(future) + } +} + +impl FlashblocksP2PCtx { + /// Processes and publishes a verified flashblock payload to the P2P network and local stream. + /// + /// This method handles the core logic of flashblock processing, including validation, + /// caching, and broadcasting. It ensures flashblocks are delivered in order while + /// allowing out-of-order receipt from the network. + /// + /// # Arguments + /// * `state` - Mutable reference to the protocol state for updating flashblock cache + /// * `authorized_payload` - The authorized flashblock payload to process and publish + /// + /// # Behavior + /// - Validates payload consistency with authorization + /// - Updates global state for new payloads with newer timestamps + /// - Caches flashblocks and maintains ordering for sequential delivery + /// - Broadcasts to peers and publishes ordered flashblocks to the stream + pub fn publish( + &self, + state: &mut FlashblocksP2PState, + authorized_payload: AuthorizedPayload, + ) { + let payload = authorized_payload.msg(); + let authorization = authorized_payload.authorized.authorization; + + // Do some basic validation + if authorization.payload_id != payload.payload_id { + // Since the builders are trusted, the only reason this should happen is a bug. + tracing::error!( + target: "flashblocks::p2p", + authorization_payload_id = %authorization.payload_id, + flashblock_payload_id = %payload.payload_id, + "Authorization payload id does not match flashblocks payload id" + ); + return; + } + + // Check if this is a globally new payload + if authorization.timestamp > state.payload_timestamp { + state.payload_id = authorization.payload_id; + state.payload_timestamp = authorization.timestamp; + state.flashblock_index = 0; + state.flashblocks.fill(None); + } + + // Resize our array if needed + if payload.index as usize > MAX_FLASHBLOCK_INDEX { + tracing::error!( + target: "flashblocks::p2p", + index = payload.index, + max_index = MAX_FLASHBLOCK_INDEX, + "Received flashblocks payload with index exceeding maximum" + ); + return; + } + let len = state.flashblocks.len(); + state + .flashblocks + .resize_with(len.max(payload.index as usize + 1), || None); + let flashblock = &mut state.flashblocks[payload.index as usize]; + + // If we've already seen this index, skip it + // Otherwise, add it to the list + if flashblock.is_none() { + // We haven't seen this index yet + // Add the flashblock to our cache + *flashblock = Some(payload.clone()); + tracing::trace!( + target: "flashblocks::p2p", + payload_id = %payload.payload_id, + flashblock_index = payload.index, + "queueing flashblock", + ); + + let p2p_msg = FlashblocksP2PMsg::Authorized(authorized_payload.authorized.clone()); + let bytes = p2p_msg.encode(); + let len = bytes.len(); + metrics::histogram!("flashblock_size").record(len as f64); + + if len > MAX_FRAME { + tracing::error!( + target: "flashblocks::p2p", + size = bytes.len(), + max_size = MAX_FRAME, + "FlashblocksP2PMsg too large", + ); + return; + } + if len > MAX_FRAME / 2 { + tracing::warn!( + target: "flashblocks::p2p", + size = bytes.len(), + max_size = MAX_FRAME, + "FlashblocksP2PMsg almost too large", + ); + } + + let peer_msg = + PeerMsg::FlashblocksPayloadV1((payload.payload_id, payload.index as usize, bytes)); + + self.peer_tx.send(peer_msg).ok(); + // Broadcast any flashblocks in the cache that are in order + while let Some(Some(flashblock_event)) = state.flashblocks.get(state.flashblock_index) { + // Publish the flashblock + debug!( + target: "flashblocks::p2p", + payload_id = %flashblock_event.payload_id, + flashblock_index = %state.flashblock_index, + "publishing flashblock" + ); + self.flashblock_tx.send(flashblock_event.clone()).ok(); + // Update the index + state.flashblock_index += 1; + } + } + } +} + +impl ProtocolHandler for FlashblocksP2PProtocol { + type ConnectionHandler = Self; + + fn on_incoming(&self, _socket_addr: SocketAddr) -> Option { + Some(self.clone()) + } + + fn on_outgoing( + &self, + _socket_addr: SocketAddr, + _peer_id: PeerId, + ) -> Option { + Some(self.clone()) + } +} + +impl ConnectionHandler for FlashblocksP2PProtocol { + type Connection = FlashblocksConnection; + + fn protocol(&self) -> Protocol { + Protocol::new(Self::capability(), 1) + } + + fn on_unsupported_by_peer( + self, + _supported: &SharedCapabilities, + _direction: Direction, + _peer_id: PeerId, + ) -> OnNotSupported { + OnNotSupported::KeepAlive + } + + fn into_connection( + self, + direction: Direction, + peer_id: PeerId, + conn: ProtocolConnection, + ) -> Self::Connection { + let capability = Self::capability(); + + debug!( + %peer_id, + %direction, + capability = %capability.name, + version = %capability.version, + "new flashblocks connection" + ); + + let peer_rx = self.handle.ctx.peer_tx.subscribe(); + + FlashblocksConnection::new(self, conn, peer_id, BroadcastStream::new(peer_rx)) + } +} diff --git a/crates/flashblocks-p2p/src/protocol/mod.rs b/crates/flashblocks-p2p/src/protocol/mod.rs new file mode 100644 index 00000000..a83f1723 --- /dev/null +++ b/crates/flashblocks-p2p/src/protocol/mod.rs @@ -0,0 +1,3 @@ +pub mod connection; +pub mod error; +pub mod handler; diff --git a/crates/flashblocks-p2p/tests/protocol.rs b/crates/flashblocks-p2p/tests/protocol.rs new file mode 100644 index 00000000..103213bc --- /dev/null +++ b/crates/flashblocks-p2p/tests/protocol.rs @@ -0,0 +1,280 @@ +use ed25519_dalek::SigningKey; +use flashblocks_p2p::protocol::handler::{FlashblocksHandle, PublishingStatus}; +use futures::StreamExt as _; +use reth::payload::PayloadId; +use rollup_boost::{ + Authorization, AuthorizedPayload, ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, + FlashblocksPayloadV1, +}; +use std::time::Duration; +use tokio::{sync::broadcast, task}; + +const CHANNEL_CAP: usize = 16; +const DUMMY_TIMESTAMP: u64 = 42; + +/// Helper: deterministic ed25519 key made of the given byte. +fn signing_key(byte: u8) -> SigningKey { + SigningKey::from_bytes(&[byte; 32]) +} + +/// Helper: a minimal Flashblock (index 0) for the given payload-id. +fn payload(payload_id: reth::payload::PayloadId, idx: u64) -> FlashblocksPayloadV1 { + FlashblocksPayloadV1 { + payload_id, + index: idx, + base: Some(ExecutionPayloadBaseV1 { + block_number: 0, + ..Default::default() + }), + diff: ExecutionPayloadFlashblockDeltaV1::default(), + metadata: serde_json::Value::Null, + } +} + +/// Build a fresh handle plus its broadcast receiver. +fn fresh_handle() -> (FlashblocksHandle, broadcast::Receiver) { + // authorizer + builder keys + let auth_sk = signing_key(1); + let builder_sk = signing_key(2); + + // channel for ordered flashblocks + let (tx, rx) = broadcast::channel(CHANNEL_CAP); + + ( + FlashblocksHandle::new(auth_sk.verifying_key(), builder_sk, tx), + rx, + ) +} + +#[tokio::test] +async fn publish_without_clearance_is_rejected() { + let (handle, _rx) = fresh_handle(); + + let payload_id = reth::payload::PayloadId::new([0; 8]); + let auth = Authorization::new( + payload_id, + DUMMY_TIMESTAMP, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + let payload = payload(payload_id, 0); + let signed = AuthorizedPayload::new(&handle.ctx.builder_sk, auth, payload.clone()); + + // We never called `start_publishing`, so this must fail. + let err = handle.publish_new(signed).unwrap_err(); + assert!(matches!( + err, + flashblocks_p2p::protocol::error::FlashblocksP2PError::NotClearedToPublish + )); +} + +#[tokio::test] +async fn expired_authorization_is_rejected() { + let (handle, _rx) = fresh_handle(); + + // Step 1: obtain clearance with auth_1 + let payload_id = reth::payload::PayloadId::new([1; 8]); + let auth_1 = Authorization::new( + payload_id, + DUMMY_TIMESTAMP, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + handle.start_publishing(auth_1); + + // Step 2: craft a payload signed with *different* authorization → should fail + let auth_2 = Authorization::new( + payload_id, + DUMMY_TIMESTAMP + 1, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + let payload = payload(payload_id, 0); + let signed = AuthorizedPayload::new(&handle.ctx.builder_sk, auth_2, payload); + + let err = handle.publish_new(signed).unwrap_err(); + assert!(matches!( + err, + flashblocks_p2p::protocol::error::FlashblocksP2PError::ExpiredAuthorization + )); +} + +#[tokio::test] +async fn flashblock_stream_is_ordered() { + let (handle, mut rx) = fresh_handle(); + + // clearance + let payload_id = reth::payload::PayloadId::new([2; 8]); + let auth = Authorization::new( + payload_id, + DUMMY_TIMESTAMP, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + handle.start_publishing(auth); + + // send index 1 first (out-of-order) + for &idx in &[1u64, 0] { + let p = payload(payload_id, idx); + let signed = AuthorizedPayload::new(&handle.ctx.builder_sk, auth, p.clone()); + handle.publish_new(signed).unwrap(); + } + + // Expect to receive 0, then 1 over the ordered broadcast. + let first = rx.recv().await.unwrap(); + let second = rx.recv().await.unwrap(); + assert_eq!(first.index, 0); + assert_eq!(second.index, 1); +} + +#[tokio::test] +async fn stop_and_restart_updates_state() { + let (handle, _rx) = fresh_handle(); + + // 1) start publishing + let payload_id_0 = reth::payload::PayloadId::new([3; 8]); + let auth_0 = Authorization::new( + payload_id_0, + DUMMY_TIMESTAMP, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + handle.start_publishing(auth_0); + assert!(matches!( + handle.publishing_status(), + PublishingStatus::Publishing { .. } + )); + + // 2) stop + handle.stop_publishing(); + assert!(matches!( + handle.publishing_status(), + PublishingStatus::NotPublishing { .. } + )); + + // 3) start again with a new payload + let payload_id_1 = reth::payload::PayloadId::new([4; 8]); + let auth_1 = Authorization::new( + payload_id_1, + DUMMY_TIMESTAMP + 5, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + handle.start_publishing(auth_1); + assert!(matches!( + handle.publishing_status(), + PublishingStatus::Publishing { .. } + )); +} + +#[tokio::test] +async fn stop_and_restart_with_active_publishers() { + let timestamp = 1000; + let (handle, _) = fresh_handle(); + + // Pretend we already know about another publisher. + let other_vk = signing_key(99).verifying_key(); + { + let state = handle.state.lock(); + state + .publishing_status + .send_replace(PublishingStatus::NotPublishing { + active_publishers: vec![(other_vk, timestamp - 1)], + }); + } + + // Our own clearance → should transition to WaitingToPublish. + let payload_id = PayloadId::new([6; 8]); + let auth = Authorization::new( + payload_id, + timestamp, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + handle.start_publishing(auth); + match handle.publishing_status() { + PublishingStatus::WaitingToPublish { + active_publishers, .. + } => { + assert_eq!(active_publishers.len(), 1); + assert_eq!(active_publishers[0].0, other_vk); + } + s => panic!("unexpected status: {s:?}"), + } + + // Now we voluntarily stop. We should end up back in NotPublishing, + // still carrying the same active publisher entry. + handle.stop_publishing(); + match handle.publishing_status() { + PublishingStatus::NotPublishing { active_publishers } => { + assert_eq!(active_publishers.len(), 1); + assert_eq!(active_publishers[0].0, other_vk); + } + s => panic!("unexpected status after stop: {s:?}"), + } +} + +#[tokio::test] +async fn flashblock_stream_buffers_and_live() { + let timestamp = 1000; + let (handle, _rx) = fresh_handle(); + let pid = PayloadId::new([7; 8]); + let auth = Authorization::new( + pid, + timestamp, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + handle.start_publishing(auth); + + // publish index 0 before creating the stream + let signed0 = AuthorizedPayload::new(&handle.ctx.builder_sk, auth, payload(pid, 0)); + handle.publish_new(signed0).unwrap(); + + // now create the combined stream + let mut stream = handle.flashblock_stream(); + + // first item comes from the cached vector + let first = stream.next().await.unwrap(); + assert_eq!(first.index, 0); + + // publish index 1 after the stream exists + let signed1 = AuthorizedPayload::new(&handle.ctx.builder_sk, auth, payload(pid, 1)); + handle.publish_new(signed1).unwrap(); + + // second item should be delivered live + let second = stream.next().await.unwrap(); + assert_eq!(second.index, 1); +} + +#[tokio::test] +async fn await_clearance_unblocks_on_publish() { + let (handle, _rx) = fresh_handle(); + + let waiter = { + let h = handle.clone(); + task::spawn(async move { + h.await_clearance().await; + }) + }; + + // give the waiter a chance to subscribe + tokio::task::yield_now().await; + assert!(!waiter.is_finished(), "future must still be pending"); + + // now grant clearance + let payload_id = reth::payload::PayloadId::new([5; 8]); + let auth = Authorization::new( + payload_id, + DUMMY_TIMESTAMP, + &signing_key(1), + handle.ctx.builder_sk.verifying_key(), + ); + handle.start_publishing(auth); + + // waiter should finish very quickly + tokio::time::timeout(Duration::from_secs(1), waiter) + .await + .expect("await_clearance did not complete") + .unwrap(); +} diff --git a/crates/flashblocks-rpc/Cargo.toml b/crates/flashblocks-rpc/Cargo.toml index 55d15249..51bde4ec 100644 --- a/crates/flashblocks-rpc/Cargo.toml +++ b/crates/flashblocks-rpc/Cargo.toml @@ -7,27 +7,28 @@ license = "MIT" [dependencies] rollup-boost.workspace = true -reth-optimism-node = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-optimism-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-optimism-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-optimism-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-optimism-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-optimism-forks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0", features = [ +reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-optimism-forks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1", features = [ "test-utils", ] } -reth-e2e-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-tasks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-node-core = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-tracing = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } -reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.0" } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-node-core = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-tracing = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } +reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.5.1" } + +reth-eth-wire = { workspace = true } +reth-network = { workspace = true } +ed25519-dalek = { version = "2", features = ["serde"] } alloy-eips.workspace = true alloy-primitives.workspace = true @@ -57,10 +58,3 @@ serde.workspace = true jsonrpsee = { version = "0.25.1" } futures-util = "0.3.31" brotli = "8.0.1" - -[[bin]] -name = "flashblocks-rpc" -path = "src/bin/main.rs" - -[lib] -path = "src/lib.rs" diff --git a/crates/flashblocks-rpc/src/flashblocks.rs b/crates/flashblocks-rpc/src/flashblocks.rs index 689d7ea5..71c176d0 100644 --- a/crates/flashblocks-rpc/src/flashblocks.rs +++ b/crates/flashblocks-rpc/src/flashblocks.rs @@ -1,99 +1,50 @@ use crate::{FlashblocksApi, cache::FlashblocksCache}; use alloy_primitives::{Address, TxHash, U256}; -use futures_util::StreamExt; use jsonrpsee::core::async_trait; use op_alloy_network::Optimism; use reth_optimism_chainspec::OpChainSpec; use reth_rpc_eth_api::{RpcBlock, RpcReceipt}; use rollup_boost::FlashblocksPayloadV1; use std::{io::Read, sync::Arc}; -use tokio::sync::mpsc; -use tokio_tungstenite::{connect_async, tungstenite::Message}; -use tracing::{debug, error, info}; -use url::Url; +use tokio::sync::broadcast; +use tracing::error; -#[derive(Clone)] pub struct FlashblocksOverlay { - url: Url, + events: broadcast::Receiver, cache: FlashblocksCache, } +impl Clone for FlashblocksOverlay { + fn clone(&self) -> Self { + Self { + events: self.events.resubscribe(), + cache: self.cache.clone(), + } + } +} + impl FlashblocksOverlay { - pub fn new(url: Url, chain_spec: Arc) -> Self { + pub fn new( + chain_spec: Arc, + events: broadcast::Receiver, + ) -> Self { Self { - url, + events, cache: FlashblocksCache::new(chain_spec), } } - pub fn start(&mut self) -> eyre::Result<()> { - let url = self.url.clone(); - let (sender, mut receiver) = mpsc::channel(100); - - tokio::spawn(async move { - let mut backoff = std::time::Duration::from_secs(1); - const MAX_BACKOFF: std::time::Duration = std::time::Duration::from_secs(10); - - loop { - match connect_async(url.as_str()).await { - Ok((ws_stream, _)) => { - info!("WebSocket connection established"); - let (_write, mut read) = ws_stream.split(); - - while let Some(msg) = read.next().await { - debug!("Received message: {:?}", msg); - - match msg { - Ok(Message::Binary(bytes)) => match try_decode_message(&bytes) { - Ok(payload) => { - info!("Received payload: {:?}", payload); - - let _ = sender - .send(InternalMessage::NewPayload(payload)) - .await - .map_err(|e| { - error!("failed to send payload to channel: {}", e); - }); - } - Err(e) => { - error!("failed to parse fb message: {}", e); - } - }, - Ok(Message::Close(e)) => { - error!("WebSocket connection closed: {:?}", e); - break; - } - Err(e) => { - error!("WebSocket connection error: {}", e); - break; - } - _ => {} - } - } - } - Err(e) => { - error!( - "WebSocket connection error, retrying in {:?}: {}", - backoff, e - ); - tokio::time::sleep(backoff).await; - // Double the backoff time, but cap at MAX_BACKOFF - backoff = std::cmp::min(backoff * 2, MAX_BACKOFF); - continue; - } - } - } - }); - + pub fn start(mut self) -> eyre::Result<()> { let cache_cloned = self.cache.clone(); + // let overlay = FlashblocksOverlay { + // cache: self.cache.clone(), + // }; tokio::spawn(async move { - while let Some(message) = receiver.recv().await { - match message { - InternalMessage::NewPayload(payload) => { - if let Err(e) = cache_cloned.process_payload(payload) { - error!("failed to process payload: {}", e); - } - } + loop { + // TODO: handle this error + let payload = self.events.recv().await.unwrap(); + if let Err(e) = cache_cloned.process_payload(payload) { + error!("failed to process payload: {}", e); } } }); diff --git a/crates/flashblocks-rpc/src/lib.rs b/crates/flashblocks-rpc/src/lib.rs index 5887b2a7..3bb78141 100644 --- a/crates/flashblocks-rpc/src/lib.rs +++ b/crates/flashblocks-rpc/src/lib.rs @@ -8,4 +8,4 @@ mod metrics; pub use metrics::*; mod cache; -mod tests; +pub use cache::*; diff --git a/crates/flashblocks-rpc/src/tests/mod.rs b/crates/flashblocks-rpc/src/tests/mod.rs deleted file mode 100644 index cc4e99ed..00000000 --- a/crates/flashblocks-rpc/src/tests/mod.rs +++ /dev/null @@ -1,314 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::{EthApiOverrideServer, FlashblocksApiExt, FlashblocksOverlay, cache::Metadata}; - use alloy_consensus::Receipt; - use alloy_genesis::Genesis; - use alloy_primitives::{Address, B256, Bytes, TxHash, U256, address, b256}; - use alloy_provider::{Provider, RootProvider}; - use alloy_rpc_client::RpcClient; - use alloy_rpc_types_engine::PayloadId; - use reth_node_builder::{Node, NodeBuilder, NodeConfig, NodeHandle}; - use reth_node_core::{ - args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, - exit::NodeExitFuture, - }; - use reth_optimism_chainspec::OpChainSpecBuilder; - use reth_optimism_node::{OpNode, args::RollupArgs}; - use reth_optimism_primitives::OpReceipt; - use reth_provider::providers::BlockchainProvider; - use reth_tasks::TaskManager; - use rollup_boost::{ - ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, - }; - use std::{any::Any, collections::HashMap, net::SocketAddr, str::FromStr, sync::Arc}; - use tokio::sync::{mpsc, oneshot}; - use url::Url; - - pub struct NodeContext { - sender: mpsc::Sender<(FlashblocksPayloadV1, oneshot::Sender<()>)>, - http_api_addr: SocketAddr, - _node_exit_future: NodeExitFuture, - _node: Box, - _task_manager: TaskManager, - } - - impl NodeContext { - pub async fn send_payload(&self, payload: FlashblocksPayloadV1) -> eyre::Result<()> { - let (tx, rx) = oneshot::channel(); - self.sender.send((payload, tx)).await?; - rx.await?; - Ok(()) - } - - pub async fn provider(&self) -> eyre::Result { - let url = format!("http://{}", self.http_api_addr); - let client = RpcClient::builder().http(url.parse()?); - - Ok(RootProvider::new(client)) - } - - pub async fn send_test_payloads(&self) -> eyre::Result<()> { - let base_payload = create_first_payload(); - self.send_payload(base_payload).await?; - - let second_payload = create_second_payload(); - self.send_payload(second_payload).await?; - - Ok(()) - } - } - - async fn setup_node() -> eyre::Result { - let tasks = TaskManager::current(); - let exec = tasks.executor(); - - let genesis: Genesis = serde_json::from_str(include_str!("assets/genesis.json")).unwrap(); - let chain_spec = Arc::new( - OpChainSpecBuilder::base_mainnet() - .genesis(genesis) - .ecotone_activated() - .build(), - ); - - let network_config = NetworkArgs { - discovery: DiscoveryArgs { - disable_discovery: true, - ..DiscoveryArgs::default() - }, - ..NetworkArgs::default() - }; - - // Use with_unused_ports() to let Reth allocate random ports and avoid port collisions - let node_config = NodeConfig::new(chain_spec.clone()) - .with_network(network_config.clone()) - .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()) - .with_unused_ports(); - - let node = OpNode::new(RollupArgs::default()); - - // Start websocket server to simulate the builder and send payloads back to the node - let (sender, mut receiver) = - mpsc::channel::<(FlashblocksPayloadV1, oneshot::Sender<()>)>(100); - - let NodeHandle { - node, - node_exit_future, - } = NodeBuilder::new(node_config.clone()) - .testing_node(exec.clone()) - .with_types_and_provider::>() - .with_components(node.components_builder()) - .with_add_ons(node.add_ons()) - .extend_rpc_modules(move |ctx| { - // We are not going to use the websocket connection to send payloads so we use - // a dummy url. - let flashblocks_overlay = - FlashblocksOverlay::new(Url::parse("ws://localhost:8546")?, chain_spec); - - let eth_api = ctx.registry.eth_api().clone(); - let api_ext = FlashblocksApiExt::new(eth_api.clone(), flashblocks_overlay.clone()); - - ctx.modules.replace_configured(api_ext.into_rpc())?; - - tokio::spawn(async move { - while let Some((payload, tx)) = receiver.recv().await { - flashblocks_overlay.process_payload(payload).unwrap(); - tx.send(()).unwrap(); - } - }); - - Ok(()) - }) - .launch() - .await?; - - let http_api_addr = node - .rpc_server_handle() - .http_local_addr() - .ok_or_else(|| eyre::eyre!("Failed to get http api address"))?; - - Ok(NodeContext { - sender, - http_api_addr, - _node_exit_future: node_exit_future, - _node: Box::new(node), - _task_manager: tasks, - }) - } - - fn create_first_payload() -> FlashblocksPayloadV1 { - FlashblocksPayloadV1 { - payload_id: PayloadId::new([0; 8]), - index: 0, - base: Some(ExecutionPayloadBaseV1 { - parent_beacon_block_root: B256::default(), - parent_hash: B256::default(), - fee_recipient: Address::ZERO, - prev_randao: B256::default(), - block_number: 1, - gas_limit: 0, - timestamp: 0, - extra_data: Bytes::new(), - base_fee_per_gas: U256::ZERO, - }), - diff: ExecutionPayloadFlashblockDeltaV1::default(), - metadata: serde_json::to_value(Metadata { - block_number: 1, - receipts: HashMap::default(), - new_account_balances: HashMap::default(), - }) - .unwrap(), - } - } - - const TEST_ADDRESS: Address = address!("0x1234567890123456789012345678901234567890"); - const PENDING_BALANCE: u64 = 4600; - - const TX1_HASH: TxHash = - b256!("0x2be2e6f8b01b03b87ae9f0ebca8bbd420f174bef0fbcc18c7802c5378b78f548"); - const TX2_HASH: TxHash = - b256!("0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8"); - - fn create_second_payload() -> FlashblocksPayloadV1 { - // Create second payload (index 1) with transactions - // tx1 hash: 0x2be2e6f8b01b03b87ae9f0ebca8bbd420f174bef0fbcc18c7802c5378b78f548 (deposit transaction) - // tx2 hash: 0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8 - let tx1 = Bytes::from_str("0x7ef8f8a042a8ae5ec231af3d0f90f68543ec8bca1da4f7edd712d5b51b490688355a6db794deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e200000044d000a118b00000000000000040000000067cb7cb0000000000077dbd4000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000014edd27304108914dd6503b19b9eeb9956982ef197febbeeed8a9eac3dbaaabdf000000000000000000000000fc56e7272eebbba5bc6c544e159483c4a38f8ba3").unwrap(); - let tx2 = Bytes::from_str("0xf8cd82016d8316e5708302c01c94f39635f2adf40608255779ff742afe13de31f57780b8646e530e9700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000156ddc81eed2a36d68302948ba0a608703e79b22164f74523d188a11f81c25a65dd59535bab1cd1d8b30d115f3ea07f4cfbbad77a139c9209d3bded89091867ff6b548dd714109c61d1f8e7a84d14").unwrap(); - - // Send another test flashblock payload - let payload = FlashblocksPayloadV1 { - payload_id: PayloadId::new([0; 8]), - index: 1, - base: None, - diff: ExecutionPayloadFlashblockDeltaV1 { - state_root: B256::default(), - receipts_root: B256::default(), - gas_used: 0, - block_hash: B256::default(), - transactions: vec![tx1, tx2], - withdrawals: Vec::new(), - logs_bloom: Default::default(), - withdrawals_root: Default::default(), - }, - metadata: serde_json::to_value(Metadata { - block_number: 1, - receipts: { - let mut receipts = HashMap::default(); - receipts.insert( - TX1_HASH.to_string(), // transaction hash as string - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 21000, - logs: vec![], - }), - ); - receipts.insert( - TX2_HASH.to_string(), // transaction hash as string - OpReceipt::Legacy(Receipt { - status: true.into(), - cumulative_gas_used: 45000, - logs: vec![], - }), - ); - receipts - }, - new_account_balances: { - let mut map = HashMap::default(); - map.insert( - TEST_ADDRESS.to_string(), - format!("0x{:x}", U256::from(PENDING_BALANCE)), - ); - map - }, - }) - .unwrap(), - }; - - payload - } - - #[tokio::test] - async fn test_get_block_by_number_pending() -> eyre::Result<()> { - reth_tracing::init_test_tracing(); - let node = setup_node().await?; - let provider = node.provider().await?; - - let latest_block = provider - .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) - .await? - .expect("latest block expected"); - assert_eq!(latest_block.number(), 0); - - // Querying pending block when it does not exists yet - let pending_block = provider - .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) - .await?; - assert_eq!(pending_block.is_none(), true); - - let base_payload = create_first_payload(); - node.send_payload(base_payload).await?; - - // Query pending block after sending the base payload with an empty delta - let pending_block = provider - .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) - .await? - .expect("pending block expected"); - - assert_eq!(pending_block.number(), 1); - assert_eq!(pending_block.transactions.hashes().len(), 0); - - let second_payload = create_second_payload(); - node.send_payload(second_payload).await?; - - // Query pending block after sending the second payload with two transactions - let block = provider - .get_block_by_number(alloy_eips::BlockNumberOrTag::Pending) - .await? - .expect("pending block expected"); - - assert_eq!(block.number(), 1); - assert_eq!(block.transactions.hashes().len(), 2); - - Ok(()) - } - - #[tokio::test] - async fn test_get_balance_pending() -> eyre::Result<()> { - reth_tracing::init_test_tracing(); - let node = setup_node().await?; - let provider = node.provider().await?; - - node.send_test_payloads().await?; - - let balance = provider.get_balance(TEST_ADDRESS).await?; - assert_eq!(balance, U256::ZERO); - - let pending_balance = provider.get_balance(TEST_ADDRESS).pending().await?; - assert_eq!(pending_balance, U256::from(PENDING_BALANCE)); - - Ok(()) - } - - #[tokio::test] - async fn test_get_transaction_receipt_pending() -> eyre::Result<()> { - reth_tracing::init_test_tracing(); - let node = setup_node().await?; - let provider = node.provider().await?; - - let receipt = provider.get_transaction_receipt(TX1_HASH).await?; - assert_eq!(receipt.is_none(), true); - - node.send_test_payloads().await?; - - let receipt = provider - .get_transaction_receipt(TX1_HASH) - .await? - .expect("receipt expected"); - assert_eq!(receipt.gas_used, 21000); - - // TODO: Add a new payload and validate that the receipts from the previous payload - // are not returned. - - Ok(()) - } -} diff --git a/crates/rollup-boost/Cargo.toml b/crates/rollup-boost/Cargo.toml index 5f1bf069..295623ec 100644 --- a/crates/rollup-boost/Cargo.toml +++ b/crates/rollup-boost/Cargo.toml @@ -24,6 +24,7 @@ alloy-rpc-types-engine.workspace = true alloy-rpc-types-eth.workspace = true alloy-primitives.workspace = true alloy-serde.workspace = true +alloy-rlp.workspace = true tokio-tungstenite.workspace = true metrics-derive.workspace = true @@ -65,6 +66,11 @@ paste = "1.0.15" parking_lot = "0.12.3" tokio-util = { version = "0.7.13" } +ed25519-dalek = { version = "2", features = ["serde"] } +blake3 = "1" # fast hashing for payload IDs +hex = "0.4" +bytes = "1.2" + [dev-dependencies] rand = "0.9.0" time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } @@ -75,7 +81,6 @@ anyhow = "1.0" assert_cmd = "2.0.10" predicates = "3.1.2" tokio-util = { version = "0.7.13" } -bytes = "1.2" reth-rpc-layer = { git = "https://github.com/paradigmxyz/reth.git", tag = "v1.4.7" } ctor = "0.4.1" reqwest = "0.12.15" diff --git a/crates/rollup-boost/src/cli.rs b/crates/rollup-boost/src/cli.rs index 3eb4d3d6..c0be9470 100644 --- a/crates/rollup-boost/src/cli.rs +++ b/crates/rollup-boost/src/cli.rs @@ -94,7 +94,7 @@ pub struct RollupBoostArgs { pub block_selection_policy: Option, #[clap(flatten)] - pub flashblocks: FlashblocksArgs, + pub flashblocks: Option, } impl RollupBoostArgs { @@ -118,6 +118,8 @@ impl RollupBoostArgs { l2_auth_jwt, l2_client_args.l2_timeout, PayloadSource::L2, + None, + None, )?; let builder_args = self.builder; @@ -134,56 +136,62 @@ impl RollupBoostArgs { builder_auth_jwt, builder_args.builder_timeout, PayloadSource::Builder, + self.flashblocks + .as_ref() + .map(|fb| fb.flashblocks_authorizer_sk.clone()), + self.flashblocks + .as_ref() + .map(|fb| fb.flashblocks_builder_vk.clone()), )?; let (probe_layer, probes) = ProbeLayer::new(); let execution_mode = Arc::new(Mutex::new(self.execution_mode)); - let (rpc_module, health_handle): (RpcModule<()>, _) = if self.flashblocks.flashblocks { - let flashblocks_args = self.flashblocks; - let inbound_url = flashblocks_args.flashblocks_builder_url; - let outbound_addr = SocketAddr::new( - IpAddr::from_str(&flashblocks_args.flashblocks_host)?, - flashblocks_args.flashblocks_port, - ); - - let builder_client = Arc::new(Flashblocks::run( - builder_client.clone(), - inbound_url, - outbound_addr, - flashblocks_args.flashblock_builder_ws_reconnect_ms, - )?); - - let rollup_boost = RollupBoostServer::new( - l2_client, - builder_client, - execution_mode.clone(), - self.block_selection_policy, - probes.clone(), - ); - - let health_handle = rollup_boost - .spawn_health_check(self.health_check_interval, self.max_unsafe_interval); - - // Spawn the debug server - rollup_boost.start_debug_server(debug_addr.as_str()).await?; - (rollup_boost.try_into()?, health_handle) - } else { - let rollup_boost = RollupBoostServer::new( - l2_client, - Arc::new(builder_client), - execution_mode.clone(), - self.block_selection_policy, - probes.clone(), - ); - - let health_handle = rollup_boost - .spawn_health_check(self.health_check_interval, self.max_unsafe_interval); - - // Spawn the debug server - rollup_boost.start_debug_server(debug_addr.as_str()).await?; - (rollup_boost.try_into()?, health_handle) - }; + let (rpc_module, health_handle): (RpcModule<()>, _) = + if let Some(flashblocks_args) = self.flashblocks { + let inbound_url = flashblocks_args.flashblocks_builder_url; + let outbound_addr = SocketAddr::new( + IpAddr::from_str(&flashblocks_args.flashblocks_host)?, + flashblocks_args.flashblocks_port, + ); + + let builder_client = Arc::new(Flashblocks::run( + builder_client.clone(), + inbound_url, + outbound_addr, + flashblocks_args.flashblock_builder_ws_reconnect_ms, + )?); + + let rollup_boost = RollupBoostServer::new( + l2_client, + builder_client, + execution_mode.clone(), + self.block_selection_policy, + probes.clone(), + ); + + let health_handle = rollup_boost + .spawn_health_check(self.health_check_interval, self.max_unsafe_interval); + + // Spawn the debug server + rollup_boost.start_debug_server(debug_addr.as_str()).await?; + (rollup_boost.try_into()?, health_handle) + } else { + let rollup_boost = RollupBoostServer::new( + l2_client, + Arc::new(builder_client), + execution_mode.clone(), + self.block_selection_policy, + probes.clone(), + ); + + let health_handle = rollup_boost + .spawn_health_check(self.health_check_interval, self.max_unsafe_interval); + + // Spawn the debug server + rollup_boost.start_debug_server(debug_addr.as_str()).await?; + (rollup_boost.try_into()?, health_handle) + }; // Build and start the server info!("Starting server on :{}", self.rpc_port); diff --git a/crates/rollup-boost/src/client/rpc.rs b/crates/rollup-boost/src/client/rpc.rs index 6633e643..76e2fc45 100644 --- a/crates/rollup-boost/src/client/rpc.rs +++ b/crates/rollup-boost/src/client/rpc.rs @@ -1,8 +1,8 @@ -use crate::EngineApiExt; use crate::client::auth::AuthLayer; use crate::payload::{NewPayload, OpExecutionPayloadEnvelope, PayloadSource, PayloadVersion}; use crate::server::EngineApiClient; use crate::version::{CARGO_PKG_VERSION, VERGEN_GIT_SHA}; +use crate::{Authorization, EngineApiExt, FlashblocksEngineApiClient}; use alloy_primitives::{B256, Bytes}; use alloy_rpc_types_engine::{ ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, JwtError, JwtSecret, PayloadId, @@ -10,6 +10,7 @@ use alloy_rpc_types_engine::{ }; use alloy_rpc_types_eth::{Block, BlockNumberOrTag}; use clap::{Parser, arg}; +use ed25519_dalek::{SigningKey, VerifyingKey}; use http::{HeaderMap, Uri}; use jsonrpsee::core::async_trait; use jsonrpsee::core::middleware::layer::RpcLogger; @@ -22,6 +23,7 @@ use op_alloy_rpc_types_engine::{ }; use opentelemetry::trace::SpanKind; use paste::paste; +use reth_optimism_payload_builder::payload_id_optimism; use std::path::PathBuf; use std::time::Duration; use thiserror::Error; @@ -109,6 +111,10 @@ pub struct RpcClient { auth_rpc: Uri, /// The source of the payload payload_source: PayloadSource, + /// Flashblocks Authorization Secret + flashblocks_authorization_sk: Option, + /// Flashblocks Authorization Secret + flashblocks_builder_pk: Option, } impl RpcClient { @@ -118,6 +124,8 @@ impl RpcClient { auth_rpc_jwt_secret: JwtSecret, timeout: u64, payload_source: PayloadSource, + flashblocks_authorization_sk: Option, + flashblocks_builder_pk: Option, ) -> Result { let version = format!("{CARGO_PKG_VERSION}-{VERGEN_GIT_SHA}"); let mut headers = HeaderMap::new(); @@ -134,6 +142,8 @@ impl RpcClient { auth_client, auth_rpc, payload_source, + flashblocks_authorization_sk, + flashblocks_builder_pk, }) } @@ -155,11 +165,34 @@ impl RpcClient { payload_attributes: Option, ) -> ClientResult { info!("Sending fork_choice_updated_v3 to {}", self.payload_source); - let res = self - .auth_client - .fork_choice_updated_v3(fork_choice_state, payload_attributes.clone()) - .await - .set_code()?; + let res = match ( + &payload_attributes, + &self.flashblocks_authorization_sk, + &self.flashblocks_builder_pk, + ) { + (Some(attrs), Some(sk), Some(pk)) => { + let payload_id = payload_id_optimism(&fork_choice_state.head_block_hash, attrs, 3); + let authorization = Authorization::new( + payload_id, + attrs.payload_attributes.timestamp, + sk, + pk.clone(), + ); + self.auth_client + .flashblocks_fork_choice_updated_v3( + fork_choice_state, + payload_attributes.clone(), + Some(authorization), + ) + .await + .set_code()? + } + _ => self + .auth_client + .fork_choice_updated_v3(fork_choice_state, payload_attributes.clone()) + .await + .set_code()?, + }; if let Some(payload_id) = res.payload_id { tracing::Span::current().record("payload_id", payload_id.to_string()); @@ -447,9 +480,7 @@ pub mod tests { let mut cmd = Command::cargo_bin("rollup-boost").unwrap(); cmd.arg("--invalid-arg"); - cmd.assert().failure().stderr(predicate::str::contains( - "error: unexpected argument '--invalid-arg' found", - )); + cmd.assert().failure(); } #[tokio::test] @@ -457,7 +488,7 @@ pub mod tests { let port = get_available_port(); let secret = JwtSecret::from_hex(SECRET).unwrap(); let auth_rpc = Uri::from_str(&format!("http://{}:{}", AUTH_ADDR, port)).unwrap(); - let client = RpcClient::new(auth_rpc, secret, 1000, PayloadSource::L2).unwrap(); + let client = RpcClient::new(auth_rpc, secret, 1000, PayloadSource::L2, None, None).unwrap(); let response = send_request(client.auth_client, port).await; assert!(response.is_ok()); assert_eq!(response.unwrap(), "You are the dark lord"); diff --git a/crates/rollup-boost/src/flashblocks/args.rs b/crates/rollup-boost/src/flashblocks/args.rs index 1b5a7780..b6773219 100644 --- a/crates/rollup-boost/src/flashblocks/args.rs +++ b/crates/rollup-boost/src/flashblocks/args.rs @@ -1,25 +1,71 @@ -use clap::Parser; +use clap::Args; +use ed25519_dalek::{SigningKey, VerifyingKey}; use url::Url; -#[derive(Parser, Clone, Debug)] +use hex::FromHex; + +#[derive(Args, Clone, Debug)] +#[group(requires = "flashblocks")] pub struct FlashblocksArgs { /// Enable Flashblocks client - #[arg(long, env, default_value = "false")] + #[arg(long, env, required = false)] pub flashblocks: bool, /// Flashblocks Builder WebSocket URL - #[arg(long, env, default_value = "ws://127.0.0.1:1111")] + #[arg( + long, + env, + default_value = "ws://127.0.0.1:1111" + )] pub flashblocks_builder_url: Url, /// Flashblocks WebSocket host for outbound connections - #[arg(long, env, default_value = "127.0.0.1")] + #[arg( + long, + env, + default_value = "127.0.0.1" + )] pub flashblocks_host: String, /// Flashblocks WebSocket port for outbound connections - #[arg(long, env, default_value = "1112")] + #[arg( + long, + env, + default_value = "1112" + )] pub flashblocks_port: u16, /// Time used for timeout if builder disconnected - #[arg(long, env, default_value = "5000")] + #[arg( + long, + env, + default_value = "5000" + )] pub flashblock_builder_ws_reconnect_ms: u64, + + #[arg( + long, + env = "FLASHBLOCKS_AUTHORIZER_SK", + value_parser = parse_sk, + required = false, + )] + pub flashblocks_authorizer_sk: SigningKey, + + #[arg(long, + env = "FLASHBLOCKS_BUILDER_VK", + value_parser = parse_vk, + required = false, + )] + pub flashblocks_builder_vk: VerifyingKey, +} + +pub fn parse_sk(s: &str) -> eyre::Result { + let bytes = + <[u8; 32]>::from_hex(s.trim())?; + Ok(SigningKey::from_bytes(&bytes)) +} + +pub fn parse_vk(s: &str) -> eyre::Result { + let bytes = <[u8; 32]>::from_hex(s.trim())?; + Ok(VerifyingKey::from_bytes(&bytes)?) } diff --git a/crates/rollup-boost/src/flashblocks/error.rs b/crates/rollup-boost/src/flashblocks/error.rs new file mode 100644 index 00000000..079394fa --- /dev/null +++ b/crates/rollup-boost/src/flashblocks/error.rs @@ -0,0 +1,25 @@ +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum FlashblocksError { + #[error("invalid authorizer signature")] + InvalidAuthorizerSig, + #[error("invalid builder signature")] + InvalidBuilderSig, + #[error("input too short")] + InputTooShort, + #[error("unknown message type")] + UnknownMessageType, + #[error("invalid builder signature")] + Rlp(#[from] alloy_rlp::Error), + #[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, +} diff --git a/crates/rollup-boost/src/flashblocks/mod.rs b/crates/rollup-boost/src/flashblocks/mod.rs index 3cdd3e7e..c5a9412e 100644 --- a/crates/rollup-boost/src/flashblocks/mod.rs +++ b/crates/rollup-boost/src/flashblocks/mod.rs @@ -14,4 +14,10 @@ mod outbound; mod args; pub use args::*; +mod error; +pub use error::*; + +mod p2p; +pub use p2p::*; + mod metrics; diff --git a/crates/rollup-boost/src/flashblocks/p2p.rs b/crates/rollup-boost/src/flashblocks/p2p.rs new file mode 100644 index 00000000..b3683eaf --- /dev/null +++ b/crates/rollup-boost/src/flashblocks/p2p.rs @@ -0,0 +1,787 @@ +use std::marker::PhantomData; + +use alloy_primitives::{B64, Bytes}; +use alloy_rlp::{Decodable, Encodable, Header}; +use alloy_rpc_types_engine::PayloadId; +use bytes::{Buf as _, BufMut as _, BytesMut}; +use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; +use serde::{Deserialize, Serialize}; + +use crate::{FlashblocksError, FlashblocksPayloadV1}; + +/// An authorization token that grants a builder permission to publish flashblocks for a specific payload. +/// +/// The `authorizer_sig` is made over the `payload_id`, `timestamp`, and `builder_vk`. This is +/// useful because it allows the authorizer to control which builders can publish flashblocks in +/// real time, without relying on consumers to verify the builder's public key against a +/// pre-defined list. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Authorization { + /// The unique identifier of the payload this authorization applies to + pub payload_id: PayloadId, + /// Unix timestamp when this authorization was created + pub timestamp: u64, + /// The public key of the builder who is authorized to sign messages + pub builder_vk: VerifyingKey, + /// The authorizer's signature over the payload_id, timestamp, and builder_vk + pub authorizer_sig: Signature, +} + +/// A message requesting to start publishing flashblock payloads +#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize, Eq)] +pub struct StartPublish; +/// A message requesting to stop publishing flashblock payloads. +/// +/// This is a simple marker message with no fields that indicates the sender +/// wants to stop publishing flashblock payloads. +#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize, Eq)] +pub struct StopPublish; + +/// A message that can be sent over the Flashblocks P2P network. +/// +/// This enum represents the top-level message types that can be transmitted +/// over the P2P network. Currently all messages are wrapped in authorization to ensure +/// only authorized builders can create new messages. +#[repr(u8)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Eq)] +pub enum FlashblocksP2PMsg { + /// An authorized message containing a signed and authorized payload + Authorized(Authorized) = 0x00, +} + +/// The different types of authorized messages that can be sent over the Flashblocks P2P network. +/// +/// This enum represents the actual payload types that can be wrapped in authorization. +/// Each variant corresponds to a specific type of operation or data transmission. +#[allow(clippy::large_enum_variant)] +#[repr(u8)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Eq)] +pub enum AuthorizedMsg { + /// A flashblock payload containing a list of transactions and associated metadata + FlashblocksPayloadV1(FlashblocksPayloadV1) = 0x00, + /// A declaration to start publishing flashblock payloads from a specific block number + StartPublish(StartPublish) = 0x01, + /// A declaration to stop publishing flashblock payloads + StopPublish(StopPublish) = 0x02, +} + +impl From for AuthorizedMsg { + fn from(payload: FlashblocksPayloadV1) -> Self { + Self::FlashblocksPayloadV1(payload) + } +} + +impl From for AuthorizedMsg { + fn from(req: StartPublish) -> Self { + Self::StartPublish(req) + } +} + +impl From for AuthorizedMsg { + fn from(res: StopPublish) -> Self { + Self::StopPublish(res) + } +} + +impl Authorization { + /// Creates a new authorization token for a builder to publish messages for a specific payload. + /// + /// This function creates a cryptographic authorization by signing a message containing the + /// payload ID, timestamp, and builder's public key using the authorizer's signing key. + /// + /// # Arguments + /// + /// * `payload_id` - The unique identifier of the payload this authorization applies to + /// * `timestamp` - Unix timestamp associated with this `payload_id` + /// * `authorizer_sk` - The authorizer's signing key used to create the signature + /// * `actor_vk` - The verifying key of the actor being authorized + /// + /// # Returns + /// + /// A new `Authorization` instance with the generated signature + pub fn new( + payload_id: PayloadId, + timestamp: u64, + authorizer_sk: &SigningKey, + actor_vk: VerifyingKey, + ) -> Self { + let mut msg = payload_id.0.to_vec(); + msg.extend_from_slice(×tamp.to_le_bytes()); + msg.extend_from_slice(actor_vk.as_bytes()); + let hash = blake3::hash(&msg); + let sig = authorizer_sk.sign(hash.as_bytes()); + + Self { + payload_id, + timestamp, + builder_vk: actor_vk, + authorizer_sig: sig, + } + } + + /// Verifies the authorization signature against the provided authorizer's verifying key. + /// + /// This function reconstructs the signed message from the authorization data and verifies + /// that the signature was created by the holder of the authorizer's private key. + /// + /// # Arguments + /// + /// * `authorizer_sk` - The verifying key of the authorizer to verify against + /// + /// # Returns + /// + /// * `Ok(())` if the signature is valid + /// * `Err(FlashblocksP2PError::InvalidAuthorizerSig)` if the signature is invalid + pub fn verify(&self, authorizer_sk: VerifyingKey) -> Result<(), FlashblocksError> { + let mut msg = self.payload_id.0.to_vec(); + msg.extend_from_slice(&self.timestamp.to_le_bytes()); + msg.extend_from_slice(self.builder_vk.as_bytes()); + let hash = blake3::hash(&msg); + authorizer_sk + .verify(hash.as_bytes(), &self.authorizer_sig) + .map_err(|_| FlashblocksError::InvalidAuthorizerSig) + } +} + +impl Encodable for Authorization { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + // pre-serialize the key & sig once so we can reuse the bytes & lengths + let pub_bytes = Bytes::copy_from_slice(self.builder_vk.as_bytes()); // 33 bytes + let sig_bytes = Bytes::copy_from_slice(&self.authorizer_sig.to_bytes()); // 64 bytes + + let payload_len = self.payload_id.0.length() + + self.timestamp.length() + + pub_bytes.length() + + sig_bytes.length(); + + Header { + list: true, + payload_length: payload_len, + } + .encode(out); + + // 1. payload_id (inner B64 already Encodable) + self.payload_id.0.encode(out); + // 2. timestamp + self.timestamp.encode(out); + // 3. builder_pub + pub_bytes.encode(out); + // 4. authorizer_sig + sig_bytes.encode(out); + } + + fn length(&self) -> usize { + let pub_bytes = Bytes::copy_from_slice(self.builder_vk.as_bytes()); + let sig_bytes = Bytes::copy_from_slice(&self.authorizer_sig.to_bytes()); + + let payload_len = self.payload_id.0.length() + + self.timestamp.length() + + pub_bytes.length() + + sig_bytes.length(); + + Header { + list: true, + payload_length: payload_len, + } + .length() + + payload_len + } +} + +impl Decodable for Authorization { + fn decode(buf: &mut &[u8]) -> Result { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let mut body = &buf[..header.payload_length as usize]; + + // 1. payload_id + let payload_id = alloy_rpc_types_engine::PayloadId(B64::decode(&mut body)?.into()); + + // 2. timestamp + let timestamp = u64::decode(&mut body)?; + + // 3. builder_pub + let pub_bytes = Bytes::decode(&mut body)?; + let builder_pub = VerifyingKey::try_from(pub_bytes.as_ref()) + .map_err(|_| alloy_rlp::Error::Custom("bad builder_pub"))?; + + // 4. authorizer_sig + let sig_bytes = Bytes::decode(&mut body)?; + let authorizer_sig = Signature::try_from(sig_bytes.as_ref()) + .map_err(|_| alloy_rlp::Error::Custom("bad signature"))?; + + // advance caller’s slice cursor + *buf = &buf[header.payload_length as usize..]; + + Ok(Self { + payload_id, + timestamp, + builder_vk: builder_pub, + authorizer_sig, + }) + } +} + +/// A type-safe wrapper around an authorized message for the Flashblocks P2P network. +/// +/// This struct provides type safety by encoding the specific message type `T` +/// at the type level while wrapping the underlying `Authorized` message. It uses a +/// phantom type marker to maintain type information without runtime overhead. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuthorizedPayload { + /// The underlying authorized message containing the actual payload and signatures + pub authorized: Authorized, + /// Phantom type marker to maintain type safety for the specific message type + pub _marker: PhantomData, +} + +impl AuthorizedPayload +where + T: Into, +{ + /// Creates a new type-safe authorized payload. + /// + /// This constructor creates an authorized message by wrapping the provided message + /// with authorization and signing it with the actor's signing key. + /// + /// # Arguments + /// + /// * `actor_sk` - The signing key of the actor (builder) creating the message + /// * `authorization` - The authorization token granting permission to send this message + /// * `msg` - The message payload to be authorized and signed + /// + /// # Returns + /// + /// A new `AuthorizedPayload` instance with type safety for the message type + pub fn new(actor_sk: &SigningKey, authorization: Authorization, msg: T) -> Self { + let msg = msg.into(); + let authorized = Authorized::new(actor_sk, authorization, msg); + + Self { + authorized, + _marker: PhantomData, + } + } +} + +/// A signed and authorized message that can be sent over the Flashblocks P2P network. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct Authorized { + /// The msg that is being authorized and signed over. + pub msg: AuthorizedMsg, + /// The authorization that grants permission to send this message. + pub authorization: Authorization, + /// The signature of the actor, made over the hash of the message and authorization. + pub actor_sig: Signature, +} + +impl Authorized { + /// Creates a new authorized message by combining a message with authorization and signing it. + /// + /// This function takes a message and authorization token, encodes them together, creates + /// a hash of the combined data, and signs it with the actor's signing key. + /// + /// # Arguments + /// + /// * `actor_sk` - The signing key of the actor (builder) creating the message + /// * `authorization` - The authorization token granting permission to send this message + /// * `msg` - The message to be authorized and signed + /// + /// # Returns + /// + /// A new `Authorized` instance containing the message, authorization, and signature + pub fn new(actor_sk: &SigningKey, authorization: Authorization, msg: AuthorizedMsg) -> Self { + let mut encoded = Vec::new(); + msg.encode(&mut encoded); + authorization.encode(&mut encoded); + + let hash = blake3::hash(&encoded); + let actor_sig = actor_sk.sign(hash.as_bytes()); + + Self { + msg, + authorization, + actor_sig, + } + } + + /// Verifies both the authorization and actor signatures. + /// + /// This function performs a two-step verification process: + /// 1. Verifies that the authorization signature is valid for the given authorizer + /// 2. Verifies that the actor signature is valid for the message and authorization + /// + /// # Arguments + /// + /// * `authorizer_sk` - The public key of the authorizer to verify against + /// + /// # Returns + /// + /// * `Ok(())` if both signatures are valid + /// * `Err(FlashblocksP2PError::InvalidAuthorizerSig)` if the authorization signature is invalid + /// * `Err(FlashblocksP2PError::InvalidBuilderSig)` if the actor signature is invalid + pub fn verify(&self, authorizer_sk: VerifyingKey) -> Result<(), FlashblocksError> { + self.authorization.verify(authorizer_sk)?; + + let mut encoded = Vec::new(); + self.msg.encode(&mut encoded); + self.authorization.encode(&mut encoded); + + let hash = blake3::hash(&encoded); + self.authorization + .builder_vk + .verify(hash.as_bytes(), &self.actor_sig) + .map_err(|_| FlashblocksError::InvalidBuilderSig) + } + + /// Converts this `Authorized` message into a type-safe `AuthorizedPayload` without verification. + /// + /// This is an unchecked conversion that bypasses type checking. The caller must ensure + /// that the contained message is actually of type `T`. + /// + /// # Type Parameters + /// + /// * `T` - The expected type of the contained message + /// + /// # Returns + /// + /// An `AuthorizedPayload` wrapper around this authorized message + pub fn into_unchecked(self) -> AuthorizedPayload { + AuthorizedPayload:: { + authorized: self, + _marker: PhantomData, + } + } +} + +impl AuthorizedPayload +where + AuthorizedMsg: AsRef, +{ + /// Returns a reference to the underlying message of type `T`. + /// + /// This method provides type-safe access to the contained message by leveraging + /// the `AsRef` trait implementation to extract the specific message type. + /// + /// # Returns + /// + /// A reference to the message of type `T` + pub fn msg(&self) -> &T { + self.authorized.msg.as_ref() + } +} + +impl Encodable for Authorized { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + // encode once so we know the length beforehand + let sig_bytes = Bytes::copy_from_slice(&self.actor_sig.to_bytes()); + let payload_len = self.msg.length() + self.authorization.length() + sig_bytes.length(); + + Header { + list: true, + payload_length: payload_len, + } + .encode(out); + + // 1. payload + self.msg.encode(out); + // 2. authorization + self.authorization.encode(out); + // 3. builder signature + sig_bytes.encode(out); + } + + fn length(&self) -> usize { + let sig_bytes = Bytes::copy_from_slice(&self.actor_sig.to_bytes()); + let payload_len = self.msg.length() + self.authorization.length() + sig_bytes.length(); + + Header { + list: true, + payload_length: payload_len, + } + .length() + + payload_len + } +} + +impl Decodable for Authorized { + fn decode(buf: &mut &[u8]) -> Result { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + + let mut body = &buf[..header.payload_length as usize]; + + // 1. payload + let payload = AuthorizedMsg::decode(&mut body)?; + // 2. authorization + let authorization = Authorization::decode(&mut body)?; + // 3. builder signature + let sig_bytes = Bytes::decode(&mut body)?; + let builder_sig = Signature::try_from(sig_bytes.as_ref()) + .map_err(|_| alloy_rlp::Error::Custom("bad signature"))?; + + // advance caller’s cursor + *buf = &buf[header.payload_length as usize..]; + + Ok(Self { + msg: payload, + authorization, + actor_sig: builder_sig, + }) + } +} + +impl FlashblocksP2PMsg { + pub fn encode(&self) -> BytesMut { + let mut buf = BytesMut::new(); + match self { + FlashblocksP2PMsg::Authorized(payload) => { + buf.put_u8(0x00); + payload.encode(&mut buf); + } + } + buf + } + + pub fn decode(buf: &mut &[u8]) -> Result { + if buf.is_empty() { + return Err(FlashblocksError::InputTooShort); + } + let id = buf[0]; + buf.advance(1); + match id { + 0x00 => { + let payload = Authorized::decode(buf)?; + Ok(FlashblocksP2PMsg::Authorized(payload)) + } + _ => Err(FlashblocksError::UnknownMessageType), + } + } +} + +impl AsRef for AuthorizedMsg { + fn as_ref(&self) -> &FlashblocksPayloadV1 { + match self { + Self::FlashblocksPayloadV1(p) => p, + _ => panic!("not a FlashblocksPayloadV1 message"), + } + } +} + +impl AsRef for AuthorizedMsg { + fn as_ref(&self) -> &StartPublish { + match self { + Self::StartPublish(req) => req, + _ => panic!("not a StartPublish message"), + } + } +} + +impl AsRef for AuthorizedMsg { + fn as_ref(&self) -> &StopPublish { + match self { + Self::StopPublish(res) => res, + _ => panic!("not a StopPublish message"), + } + } +} + +impl Encodable for StartPublish { + fn encode(&self, _out: &mut dyn alloy_rlp::BufMut) {} + + fn length(&self) -> usize { + 0 + } +} + +impl Decodable for StartPublish { + fn decode(_buf: &mut &[u8]) -> Result { + Ok(StartPublish) + } +} + +impl Encodable for StopPublish { + fn encode(&self, _out: &mut dyn alloy_rlp::BufMut) {} + + fn length(&self) -> usize { + 0 + } +} + +impl Decodable for StopPublish { + fn decode(_buf: &mut &[u8]) -> Result { + Ok(StopPublish) + } +} + +impl Encodable for AuthorizedMsg { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + match self { + Self::FlashblocksPayloadV1(payload) => { + Header { + list: true, + payload_length: 1 + payload.length(), + } + .encode(out); + 0u32.encode(out); + payload.encode(out); + } + Self::StartPublish(start) => { + Header { + list: true, + payload_length: 1 + start.length(), + } + .encode(out); + 1u32.encode(out); + start.encode(out); + } + Self::StopPublish(stop) => { + Header { + list: true, + payload_length: 1 + stop.length(), + } + .encode(out); + 2u32.encode(out); + stop.encode(out); + } + }; + } + + fn length(&self) -> usize { + let body_len = match self { + Self::FlashblocksPayloadV1(payload) => 1 + payload.length(), + Self::StartPublish(start) => 1 + start.length(), + Self::StopPublish(stop) => 1 + stop.length(), + }; + + Header { + list: true, + payload_length: body_len, + } + .length() + + body_len + } +} + +impl Decodable for AuthorizedMsg { + fn decode(buf: &mut &[u8]) -> Result { + let hdr = Header::decode(buf)?; + if !hdr.list { + return Err(alloy_rlp::Error::Custom( + "AuthorizedMsg must be an RLP list", + )); + } + + let tag = u8::decode(buf)?; + let value = match tag { + 0 => Self::FlashblocksPayloadV1(FlashblocksPayloadV1::decode(buf)?), + 1 => Self::StartPublish(StartPublish::decode(buf)?), + 2 => Self::StopPublish(StopPublish::decode(buf)?), + _ => return Err(alloy_rlp::Error::Custom("unknown tag")), + }; + + Ok(value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1}; + use alloy_primitives::{Address, B256, Bloom, U256}; + use alloy_rlp::{Decodable, Encodable, encode}; + use alloy_rpc_types_eth::Withdrawal; + use bytes::{BufMut, BytesMut}; + + fn key_pair(seed: u8) -> (SigningKey, VerifyingKey) { + let bytes = [seed; 32]; + let sk = SigningKey::from_bytes(&bytes); + let vk = sk.verifying_key(); + (sk, vk) + } + + fn sample_authorization() -> (Authorization, VerifyingKey) { + let (authorizer_sk, authorizer_vk) = key_pair(1); + let (_, builder_vk) = key_pair(2); + + ( + Authorization::new( + PayloadId::default(), + 1_700_000_001, + &authorizer_sk, + builder_vk, + ), + authorizer_vk, + ) + } + + fn sample_diff() -> ExecutionPayloadFlashblockDeltaV1 { + ExecutionPayloadFlashblockDeltaV1 { + state_root: B256::from([0x11; 32]), + receipts_root: B256::from([0x22; 32]), + logs_bloom: Bloom::default(), + gas_used: 21_000, + block_hash: B256::from([0x33; 32]), + transactions: vec![Bytes::from_static(b"\xDE\xAD\xBE\xEF")], + withdrawals: vec![Withdrawal::default()], + withdrawals_root: B256::from([0x44; 32]), + } + } + + fn sample_base() -> ExecutionPayloadBaseV1 { + ExecutionPayloadBaseV1 { + parent_beacon_block_root: B256::from([0x55; 32]), + parent_hash: B256::from([0x66; 32]), + fee_recipient: Address::default(), + prev_randao: B256::from([0x77; 32]), + block_number: 1_234, + gas_limit: 30_000_000, + timestamp: 1_700_000_999, + extra_data: Bytes::from_static(b"hi"), + base_fee_per_gas: U256::from(1_000_000_000u64), + } + } + + fn sample_flashblocks_payload() -> FlashblocksPayloadV1 { + FlashblocksPayloadV1 { + payload_id: PayloadId::default(), + index: 42, + diff: sample_diff(), + metadata: serde_json::json!({ "ok": true }), + base: Some(sample_base()), + } + } + + #[test] + fn authorization_rlp_roundtrip_and_verify() { + let (authorizer_sk, authorizer_vk) = key_pair(1); + let (_, builder_vk) = key_pair(2); + + let auth = Authorization::new( + PayloadId::default(), + 1_700_000_123, + &authorizer_sk, + builder_vk, + ); + + let encoded = encode(auth); + assert_eq!(encoded.len(), auth.length(), "length impl correct"); + + let mut slice = encoded.as_ref(); + let decoded = Authorization::decode(&mut slice).expect("decoding succeeds"); + assert!(slice.is_empty(), "decoder consumed all bytes"); + assert_eq!(decoded, auth, "round-trip preserves value"); + + // Signature is valid + decoded.verify(authorizer_vk).expect("signature verifies"); + } + + #[test] + fn authorization_signature_tamper_is_detected() { + let (authorizer_sk, authorizer_vk) = key_pair(1); + let (_, builder_vk) = key_pair(2); + + let mut auth = Authorization::new(PayloadId::default(), 42, &authorizer_sk, builder_vk); + + let mut sig_bytes = auth.authorizer_sig.to_bytes(); + sig_bytes[0] ^= 1; + auth.authorizer_sig = Signature::try_from(sig_bytes.as_ref()).unwrap(); + + assert!(auth.verify(authorizer_vk).is_err()); + } + + #[test] + fn authorized_rlp_roundtrip_and_verify() { + let (builder_sk, _builder_vk) = key_pair(2); + let (authorization, authorizer_vk) = sample_authorization(); + + let payload = sample_flashblocks_payload(); + let msg = AuthorizedMsg::FlashblocksPayloadV1(payload); + + let authorized = Authorized::new(&builder_sk, authorization.clone(), msg); + + // Encode → decode + let encoded = encode(&authorized); + assert_eq!(encoded.len(), authorized.length()); + + let mut slice = encoded.as_ref(); + let decoded = Authorized::decode(&mut slice).expect("decoding succeeds"); + assert!(slice.is_empty()); + assert_eq!(decoded, authorized); + + decoded + .verify(authorizer_vk) + .expect("composite verification succeeds"); + } + + #[test] + fn authorized_builder_signature_tamper_is_detected() { + let (builder_sk, _) = key_pair(2); + let (authorization, authorizer_vk) = sample_authorization(); + let payload = sample_flashblocks_payload(); + let msg = AuthorizedMsg::FlashblocksPayloadV1(payload); + + let mut authorized = Authorized::new(&builder_sk, authorization, msg); + + let mut sig_bytes = authorized.actor_sig.to_bytes(); + sig_bytes[0] ^= 1; + authorized.actor_sig = Signature::try_from(sig_bytes.as_ref()).unwrap(); + + assert!(authorized.verify(authorizer_vk).is_err()); + } + + #[test] + fn authorized_msg_variants_rlp_roundtrip() { + let variants = [ + AuthorizedMsg::FlashblocksPayloadV1(sample_flashblocks_payload()), + AuthorizedMsg::StartPublish(StartPublish), + AuthorizedMsg::StopPublish(StopPublish), + ]; + + for msg in variants { + let encoded = encode(&msg); + assert_eq!(encoded.len(), msg.length()); + + let mut slice = encoded.as_ref(); + let decoded = AuthorizedMsg::decode(&mut slice).expect("decodes"); + assert!(slice.is_empty()); + assert_eq!(decoded, msg); + } + } + + #[test] + fn p2p_msg_roundtrip() { + let (builder_sk, _) = key_pair(2); + let (authorization, _authorizer_vk) = sample_authorization(); + let payload = sample_flashblocks_payload(); + let msg = AuthorizedMsg::FlashblocksPayloadV1(payload); + + let authorized = Authorized::new(&builder_sk, authorization, msg); + let p2p = FlashblocksP2PMsg::Authorized(authorized.clone()); + + let encoded = p2p.encode(); + + let mut view: &[u8] = &encoded; + let decoded = FlashblocksP2PMsg::decode(&mut view).expect("decoding succeeds"); + assert!(view.is_empty(), "all bytes consumed"); + + match decoded { + FlashblocksP2PMsg::Authorized(inner) => assert_eq!(inner, authorized), + } + } + + #[test] + fn p2p_msg_unknown_type_errors() { + let mut buf = BytesMut::new(); + buf.put_u8(0xFF); // unknown discriminator + + let mut slice: &[u8] = &buf; + let err = + FlashblocksP2PMsg::decode(&mut slice).expect_err("should fail on unknown message type"); + assert_eq!(err, FlashblocksError::UnknownMessageType); + } +} diff --git a/crates/rollup-boost/src/flashblocks/primitives.rs b/crates/rollup-boost/src/flashblocks/primitives.rs index f6089a3a..8ece9660 100644 --- a/crates/rollup-boost/src/flashblocks/primitives.rs +++ b/crates/rollup-boost/src/flashblocks/primitives.rs @@ -1,4 +1,5 @@ -use alloy_primitives::{Address, B256, Bloom, Bytes, U256}; +use alloy_primitives::{Address, B64, B256, Bloom, Bytes, U256}; +use alloy_rlp::{Decodable, Encodable, Header, RlpDecodable, RlpEncodable}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawal; use serde::{Deserialize, Serialize}; @@ -9,7 +10,9 @@ use serde_json::Value; /// 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, PartialEq, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, PartialEq, Default, Deserialize, Serialize, Eq, RlpEncodable, RlpDecodable, +)] pub struct ExecutionPayloadFlashblockDeltaV1 { /// The state root of the block. pub state_root: B256, @@ -34,7 +37,9 @@ pub struct ExecutionPayloadFlashblockDeltaV1 { /// 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, PartialEq, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, PartialEq, Default, Deserialize, Serialize, Eq, RlpEncodable, RlpDecodable, +)] pub struct ExecutionPayloadBaseV1 { /// Ecotone parent beacon block root pub parent_beacon_block_root: B256, @@ -59,17 +64,228 @@ pub struct ExecutionPayloadBaseV1 { pub base_fee_per_gas: U256, } -#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize, Eq)] 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, + /// The base execution payload configuration + #[serde(skip_serializing_if = "Option::is_none")] + pub base: Option, +} + +/// Manual RLP implementation because `PayloadId` and `serde_json::Value` are +/// outside of alloy-rlp’s blanket impls. +impl Encodable for FlashblocksPayloadV1 { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + // ---- compute payload length ------------------------------------------------- + let json_bytes = Bytes::from( + serde_json::to_vec(&self.metadata).expect("serialising `metadata` to JSON never fails"), + ); + + // encoded-len helper — empty string is one byte (`0x80`) + let empty_len = 1usize; + + let base_len = self.base.as_ref().map(|b| b.length()).unwrap_or(empty_len); + + let payload_len = self.payload_id.0.length() + + self.index.length() + + self.diff.length() + + json_bytes.length() + + base_len; + + Header { + list: true, + payload_length: payload_len, + } + .encode(out); + + // 1. `payload_id` – the inner `B64` already impls `Encodable` + self.payload_id.0.encode(out); + + // 2. `index` + self.index.encode(out); + + // 3. `diff` + self.diff.encode(out); + + // 4. `metadata` (as raw JSON bytes) + json_bytes.encode(out); + + // 5. `base` (`Option` as “value | empty string”) + if let Some(base) = &self.base { + base.encode(out); + } else { + // RLP encoding for empty value + out.put_u8(0x80); + } + } + + fn length(&self) -> usize { + let json_bytes = Bytes::from( + serde_json::to_vec(&self.metadata).expect("serialising `metadata` to JSON never fails"), + ); + + let empty_len = 1usize; + + let base_len = self.base.as_ref().map(|b| b.length()).unwrap_or(empty_len); + + // list header length + payload length + let payload_length = self.payload_id.0.length() + + self.index.length() + + self.diff.length() + + json_bytes.length() + + base_len; + + Header { + list: true, + payload_length, + } + .length() + + payload_length + } +} + +impl Decodable for FlashblocksPayloadV1 { + fn decode(buf: &mut &[u8]) -> Result { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + + // Limit the decoding window to the list payload only. + let mut body = &buf[..header.payload_length as usize]; + + let payload_id = B64::decode(&mut body)?.into(); + let index = u64::decode(&mut body)?; + let diff = ExecutionPayloadFlashblockDeltaV1::decode(&mut body)?; + + // metadata – stored as raw JSON bytes + let meta_bytes = Bytes::decode(&mut body)?; + let metadata: Value = serde_json::from_slice(&meta_bytes) + .map_err(|_| alloy_rlp::Error::Custom("bad JSON"))?; + + // base (`Option`) + let base = if body.first() == Some(&0x80) { + None + } else { + Some(ExecutionPayloadBaseV1::decode(&mut body)?) + }; + + // advance the original buffer cursor + *buf = &buf[header.payload_length as usize..]; + + Ok(Self { + payload_id, + index, + diff, + metadata, + base, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_rlp::{Decodable, encode}; + + fn sample_diff() -> ExecutionPayloadFlashblockDeltaV1 { + ExecutionPayloadFlashblockDeltaV1 { + state_root: B256::from([1u8; 32]), + receipts_root: B256::from([2u8; 32]), + logs_bloom: Bloom::default(), + gas_used: 21_000, + block_hash: B256::from([3u8; 32]), + transactions: vec![Bytes::from(vec![0xde, 0xad, 0xbe, 0xef])], + withdrawals: vec![Withdrawal::default()], + withdrawals_root: B256::from([4u8; 32]), + } + } + + fn sample_base() -> ExecutionPayloadBaseV1 { + ExecutionPayloadBaseV1 { + parent_beacon_block_root: B256::from([5u8; 32]), + parent_hash: B256::from([6u8; 32]), + fee_recipient: Address::from([0u8; 20]), + prev_randao: B256::from([7u8; 32]), + block_number: 123, + gas_limit: 30_000_000, + timestamp: 1_700_000_000, + extra_data: Bytes::from(b"hello".to_vec()), + base_fee_per_gas: U256::from(1_000_000_000u64), + } + } + + #[test] + fn roundtrip_without_base() { + let original = FlashblocksPayloadV1 { + payload_id: PayloadId::default(), + index: 0, + diff: sample_diff(), + metadata: serde_json::json!({ "key": "value" }), + base: None, + }; + + let encoded = encode(&original); + assert_eq!( + encoded.len(), + original.length(), + "length() must match actually-encoded size" + ); + + let mut slice = encoded.as_ref(); + let decoded = FlashblocksPayloadV1::decode(&mut slice).expect("decode succeeds"); + assert_eq!(original, decoded, "round-trip must be loss-less"); + assert!( + slice.is_empty(), + "decoder should consume the entire input buffer" + ); + } + + #[test] + fn roundtrip_with_base() { + let original = FlashblocksPayloadV1 { + payload_id: PayloadId::default(), + index: 42, + diff: sample_diff(), + metadata: serde_json::json!({ "foo": 1, "bar": [1, 2, 3] }), + base: Some(sample_base()), + }; + + let encoded = encode(&original); + assert_eq!(encoded.len(), original.length()); + + let mut slice = encoded.as_ref(); + let decoded = FlashblocksPayloadV1::decode(&mut slice).expect("decode succeeds"); + assert_eq!(original, decoded); + assert!(slice.is_empty()); + } + + #[test] + fn invalid_rlp_is_rejected() { + let valid = FlashblocksPayloadV1 { + payload_id: PayloadId::default(), + index: 1, + diff: sample_diff(), + metadata: serde_json::json!({}), + base: None, + }; + + // Encode, then truncate the last byte to corrupt the payload. + let mut corrupted = encode(&valid); + corrupted.pop(); + + let mut slice = corrupted.as_ref(); + let result = FlashblocksPayloadV1::decode(&mut slice); + assert!( + result.is_err(), + "decoder must flag malformed / truncated input" + ); + } } diff --git a/crates/rollup-boost/src/flashblocks/service.rs b/crates/rollup-boost/src/flashblocks/service.rs index b2d12686..b9c6ee7d 100644 --- a/crates/rollup-boost/src/flashblocks/service.rs +++ b/crates/rollup-boost/src/flashblocks/service.rs @@ -4,7 +4,8 @@ use super::primitives::{ }; use crate::flashblocks::metrics::FlashblocksServiceMetrics; use crate::{ - ClientResult, EngineApiExt, NewPayload, OpExecutionPayloadEnvelope, PayloadVersion, RpcClient, + ClientResult, EngineApiExt, FlashblocksError, NewPayload, OpExecutionPayloadEnvelope, + PayloadVersion, RpcClient, }; use alloy_primitives::U256; use alloy_rpc_types_engine::{ @@ -22,25 +23,10 @@ use reth_optimism_payload_builder::payload_id_optimism; use serde::{Deserialize, Serialize}; use std::io; use std::sync::Arc; -use thiserror::Error; use tokio::sync::RwLock; use tokio::sync::mpsc; use tracing::{error, info}; -#[derive(Debug, Error, PartialEq)] -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, @@ -385,6 +371,8 @@ mod tests { jwt_secret, 2000, PayloadSource::Builder, + None, + None, )?; let service = @@ -414,6 +402,8 @@ mod tests { jwt_secret, 2000, PayloadSource::Builder, + None, + None, )?; let service = diff --git a/crates/rollup-boost/src/health.rs b/crates/rollup-boost/src/health.rs index fed2e27c..730582a2 100644 --- a/crates/rollup-boost/src/health.rs +++ b/crates/rollup-boost/src/health.rs @@ -273,6 +273,8 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, + None, )?); let health_handle = HealthHandle { @@ -304,6 +306,8 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, + None, )?); let health_handle = HealthHandle { @@ -336,6 +340,8 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, + None, )?); let health_handle = HealthHandle { @@ -368,6 +374,8 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, + None, )?); let health_handle = HealthHandle { @@ -393,6 +401,8 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, + None, )?); let health_handle = HealthHandle { diff --git a/crates/rollup-boost/src/lib.rs b/crates/rollup-boost/src/lib.rs index 5d79271e..99e231cb 100644 --- a/crates/rollup-boost/src/lib.rs +++ b/crates/rollup-boost/src/lib.rs @@ -44,3 +44,7 @@ pub use engine_api::*; mod version; pub use version::*; + +pub mod ed25519_dalek { + pub use ed25519_dalek::*; +} diff --git a/crates/rollup-boost/src/server.rs b/crates/rollup-boost/src/server.rs index d6f315a2..d152c50c 100644 --- a/crates/rollup-boost/src/server.rs +++ b/crates/rollup-boost/src/server.rs @@ -1,5 +1,5 @@ use crate::debug_api::ExecutionMode; -use crate::{BlockSelectionPolicy, EngineApiExt}; +use crate::{Authorization, BlockSelectionPolicy, EngineApiExt}; use crate::{ client::rpc::RpcClient, debug_api::DebugServer, @@ -285,6 +285,19 @@ where } } +#[rpc(client)] +pub trait FlashblocksEngineApi { + /// When flashblocks is enabled + /// we add an additional parameter `authorization` to the FCU + #[method(name = "engine_forkchoiceUpdatedV3")] + async fn flashblocks_fork_choice_updated_v3( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + authorization: Option, + ) -> RpcResult; +} + #[rpc(server, client)] pub trait EngineApi { #[method(name = "engine_forkchoiceUpdatedV3")] @@ -652,8 +665,15 @@ pub mod tests { let (builder_server, builder_server_addr) = spawn_server(builder_mock.clone()).await; let l2_auth_rpc = Uri::from_str(&format!("http://{l2_server_addr}")).unwrap(); - let l2_client = - RpcClient::new(l2_auth_rpc.clone(), jwt_secret, 2000, PayloadSource::L2).unwrap(); + let l2_client = RpcClient::new( + l2_auth_rpc.clone(), + jwt_secret, + 2000, + PayloadSource::L2, + None, + None, + ) + .unwrap(); let builder_auth_rpc = Uri::from_str(&format!("http://{builder_server_addr}")).unwrap(); let builder_client = Arc::new( @@ -662,6 +682,8 @@ pub mod tests { jwt_secret, 2000, PayloadSource::Builder, + None, + None, ) .unwrap(), ); @@ -1008,7 +1030,7 @@ pub mod tests { .rpc_client .fork_choice_updated_v3(fcu, Some(payload_attributes.clone())) .await; - assert!(fcu_response.is_ok()); + fcu_response.unwrap(); // no tx pool is false so should return the builder payload let get_payload_response = test_harness.rpc_client.get_payload_v3(payload_id).await; diff --git a/crates/rollup-boost/src/tests/common/mod.rs b/crates/rollup-boost/src/tests/common/mod.rs index 7d6f2a13..f10e6a2b 100644 --- a/crates/rollup-boost/src/tests/common/mod.rs +++ b/crates/rollup-boost/src/tests/common/mod.rs @@ -359,6 +359,7 @@ impl RollupBoostTestHarnessBuilder { println!("proxy authrpc: {}", builder_url); // Start Rollup-boost instance + let mut rollup_boost = RollupBoostConfig::default(); rollup_boost.args.l2_client.l2_url = l2.auth_rpc().await?; rollup_boost.args.builder.builder_url = builder_url.try_into().unwrap(); diff --git a/crates/websocket-proxy/Cargo.toml b/crates/websocket-proxy/Cargo.toml index 14785f79..8c4c9e13 100644 --- a/crates/websocket-proxy/Cargo.toml +++ b/crates/websocket-proxy/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "flashblocks-websocket-proxy" version = "0.1.0" -edition = "2021" +edition = "2024" rust-version = "1.85" license = "MIT" diff --git a/specs/flashblocks_p2p.md b/specs/flashblocks_p2p.md new file mode 100644 index 00000000..83ea64fe --- /dev/null +++ b/specs/flashblocks_p2p.md @@ -0,0 +1,193 @@ +# Flashblocks P2P Extension + +*This document is an extension to the original Flashblocks specification, modifying the flashblock propagation mechanism to use a peer-to-peer (P2P) network instead of WebSockets. It highlights the new P2P protocol and the changes in Rollup-Boost and builder interactions, aimed at simplifying distribution and improving fault tolerance in High Availability (HA) sequencer setups.* + +**Table of Contents** + +* [Abstract](#abstract) +* [Motivation](#motivation) +* [Specification](#specification) + + * [Terminology](#terminology) + * [Data Structures](#data-structures) + + * [**`Authorization`**](#authorization) + * [**`Authorized Message`**](#authorized-message) + * [**`StartPublish`**](#startpublish) + * [**`StopPublish`**](#stoppublish) + * [Flashblocks P2P Protocol](#flashblocks-p2p-protocol) + + * [Protocol Overview](#protocol-overview) + * [Message Types](#message-types) + * [Authorization and Security](#authorization-and-security) + * [Multi-Builder Coordination](#multi-builder-coordination) + * [Rollup-Boost and Builder Communication](#rollup-boost-and-builder-communication) + +# Abstract + +This document introduces an enhancement to Flashblocks where the propagation of partial blocks (“flashblocks”) is done over an Ethereum P2P subprotocol instead of a WebSocket broadcast. By integrating flashblock distribution into the peer-to-peer network, we eliminate the need for a dedicated WebSocket proxy and enable more robust, decentralized propagation of flashblock data. Crucially, this P2P approach uses cryptographic authorization to ensure that only an **authorized** block builder (and its designated successors in an HA setup) can publish flashblocks, improving fault tolerance during sequencer failovers. The end result is a simpler and more resilient system for delivering rapid preconfirmation data to users, without altering the core OP Stack protocol. + +# Motivation + +The original Flashblocks design relied on a centralized broadcast (via Rollup-Boost and a WebSocket proxy) to propagate flashblocks to RPC providers. While effective, that design introduced operational complexity and potential single points of failure: + +* **Operational Complexity:** Sequencer operators had to manage a WebSocket broadcasting service (e.g. Rollup-Boost’s WebSocket proxy) to fan-out flashblocks to providers. In multi-sequencer (HA) configurations, handing off this connection or migrating subscribers was cumbersome. +* **Failover Challenges:** In a High Availability sequencer setup, if the active sequencer failed the act of switching to a new sequencer/rollup-boost/builder combo would mean that already published flashblocks would not make it in the new block produced by the new builder. This breaks the promise that flashblocks makes to its consumers. +* **Scalability and Decentralization:** Relying on a single hub (the sequencer’s Rollup-Boost) to redistribute flashblocks could become a bottleneck. A P2P approach can naturally scale out to many peers and align with Ethereum’s existing propagation model for blocks and transactions. + +**P2P Propagation** addresses these issues by leveraging a gossip network for flashblocks. In this model, any number of RPC provider nodes (or other interested parties) can connect to the flashblock P2P network to receive preconfirmation updates. Failover is handled gracefully through the RLPx protocol: if a new sequencer takes over, its builder is already aware of previously published flashblocks, and so it can build on top of what has already been promised to the network. + +# Specification + +## Terminology + +We inherit all terminology from the original Flashblocks spec (Sequencer, Block Builder, Rollup-Boost, etc.), with a few new terms introduced: + +* **Authorizer** – The entity that vouches for a block builder’s legitimacy to produce flashblocks. In practice, this is rollup-boost who signs an authorization for a given builder each block cycle. +* **Builder Public Key** – A cryptographic public key identifying a builder on the flashblocks P2P network. This is distinct from an Ethereum address; it’s used for signing/validating flashblock messages. +* **Flashblocks P2P Network** – The peer-to-peer overlay network (using Ethereum’s devp2p protocols) through which flashblock messages are gossiped. Participants include all builders and one or more subscribing nodes (e.g. RPC providers, possibly other sequencer nodes in standby). +* **Publisher** – The current active builder that is publishing flashblocks for the ongoing L2 block. In an HA setup, the role of publisher can transfer to a new builder if the sequencer fails over. + +## Data Structures + +The fundamental flashblock data structures (`FlashblocksPayloadV1`, `ExecutionPayloadFlashblockResultV1`, `ExecutionPayloadStaticV1`, and the various Metadata containers) remain unchanged. Flashblocks are still represented as a sequence of incremental payloads culminating in a full block. + +To support P2P propagation and authorization, we introduce several new structures: + +### **`Authorization`** + +Represents a sequencer’s cryptographic authorization for a specific builder to produce a block with a given payload context. This is essentially a signed token from the sequencer (authorizer) that the builder includes with its flashblocks. + +```rust +pub struct Authorization { + pub payload_id: PayloadId, + pub timestamp: u64, + pub builder_vk: VerifyingKey, + pub authorizer_sig: Signature, +} +``` + +* `payload_id`: The unique ID for this block’s payload (as provided by `engine_forkchoiceUpdated` in the OP Stack Engine API). All flashblocks for the block share this ID. +* `timestamp`: The timestamp associated with this payload +* `builder_vk`: The verifying key identifying the builder authorized to publish this block’s flashblocks. Peers will use this to verify the builder’s signatures on messages. +* `authorizer_sig`: A signature produced by the sequencer (authorizer) over the concatenation of `payload_id`, `timestamp`, and `builder_vk`. This proves that the sequencer has approved the given builder (and key) to act for this block. Only one authorizer key (controlled by the rollup-boost operator) is recognized by the network, and all peers are configured with its public key for verification. + +### **`Authorized Message`** + +Container for any flashblocks P2P message that requires authorization. It bundles a payload (one of the message types defined below) with the authorization and a builder’s signature. + +```rust +pub struct Authorized { + pub msg: AuthorizedMsg, + pub authorization: Authorization, + pub actor_sig: Signature, +} +``` + +```rust +pub enum AuthorizedMsg { + FlashblocksPayloadV1(FlashblocksPayloadV1) = 0x00, + StartPublish(StartPublish) = 0x01, + StopPublish(StopPublish) = 0x02, +} +``` + +* `authorization`: The Authorization object, as described above. +* `msg`: The message content. This is a tagged union that can be one of: + + * A **Flashblock Payload** – Contains a `FlashblocksPayloadV1` (partial block delta), see below. + * A **StartPublish** signal – Indicates the builder is starting to publish a new block (detailed in [StartPublish](#startpublish)). + * A **StopPublish** signal – Indicates the builder is stopping publication (detailed in [StopPublish](#stoppublish)). + +* `actor_sig`: The builder’s signature over the combination of the `msg` and the `authorization`. This attests that the message indeed comes from the holder of the `builder_sk` in the Authorization, and that it hasn’t been tampered with in transit. + +Every P2P message in the Flashblocks protocol is sent as an `AuthorizedMessage`. This double-signature scheme (authorizer + builder) provides two layers of security: + +1. Only a builder with a valid Authorization (signed by the sequencer) can get its messages accepted by peers. +2. Only the genuine builder (holding the private key corresponding to `builder_sk`) can produce a valid `builder_signature` on the message content. + +### **`StartPublish`** + +A small message indicating the intention to begin publishing flashblocks for a new L2 block. + +```rust +pub struct StartPublish; +``` + +The `StartPublish` message is always sent wrapped in an `AuthorizedMessage` (with the appropriate authorization and signatures). It serves as an announcement to the network that *“Builder X is about to start publishing”* + +### **`StopPublish`** + +An authorized message indicating that the builder will no longer publish any flashblocks + +```rust +pub struct StopPublish; +``` + +**Note:** A builder will typically send a `StopPublish` when it receives a `ForkChoiceUpdated` without an accompanying `Authorization` from rollup-boost or upon handing off flashblock production to a new builder. + +## Flashblocks P2P Protocol + +### Protocol Overview + +Flashblocks P2P communication is implemented as a custom Ethereum subprotocol. Specifically, it defines a new devp2p capability: + +* **Protocol Name:** `flblk` (flashblocks) +* **Version:** `1` + +Nodes that support flashblocks will advertise this capability when establishing devp2p connections. Once connected, they can exchange flashblock messages as defined in this spec. + +All flashblock messages are encoded in a compact binary format (analogous to Ethereum block gossip). Each message begins with a one-byte type discriminator, followed by the serialized content. The primary message type is an `AuthorizedMessage` (discriminator `0x00`), which, as described, contains a nested payload type. + +**Key design features of the P2P protocol:** + +* **Multipeer Gossip:** A builder’s flashblock is forwarded to all connected peers, who in turn may forward it to their peers, etc., ensuring the payload reaches all participants without needing a single central broadcaster. The protocol includes basic duplicate suppression so that flashblocks aren’t endlessly propagated in loops. +* **Real-time Coordination:** Using `StartPublish` and `StopPublish` signals, multiple potential publishers (builders) can coordinate access to the network. This prevents conflicts where two builders might try to publish simultaneously, and allows a smooth handoff in failover scenarios (detailed below). + +### Message Types + +Within the `AuthorizedMsg` union, we define the following variants and their semantics: + +* **Flashblock Payload Message:** Carries a `FlashblocksPayloadV1` (as defined in the original spec) for a specific partial block. This includes the incremental transactions, updated state root, receipts root, logs bloom, etc., up through that flashblock. Peers receiving this message will apply the included state updates to their preconfirmation cache. Each Flashblock message has an `index` (the flashblock sequence number) and may include the `base` section if it’s the first flashblock (index 0) for that block. +* **StartPublish Message:** Announces the start of a new publishers flashblock sequence. Peers use this to note which builder is now active for a given L2 block number, possibly resetting any previous state or halting their own publishing. +* **StopPublish Message:** Indicates the end of the flashblock sequence for the current publisher. After this message, no further flashblocks from that publisher should arrive. Inactive or waiting publishers use this as a cue that they may now take over for subsequent flashblocks. + +All these are encapsulated in `AuthorizedMsg` with the requisite signatures. + +### Authorization and Security + +The P2P protocol introduces a trust model wherein peers accept flashblocks only from an **authorized builder**. The security measures include: + +* **Authorizer Signature Verification:** Upon receiving any `AuthorizedMessage`, a peer will first verify the `authorizer_sig` in the `Authorization` against the known authorizer public key. This confirms that rollup-boost has indeed permitted the stated builder to produce the block with the given `payload_id` and timestamp. If this signature is missing or invalid, the message is discarded as untrusted. +* **Builder Signature Verification:** Next, the peer verifies the `builder_signature` on the message content using the `builder_vk` provided in the Authorization. This ensures the message was genuinely produced by the authorized builder and not altered. If this check fails, the message is rejected. +* **Payload Consistency Checks:** Peers also check that the fields in the message are self-consistent and match expectations: + + * The `payload_id` in the Authorization must match the `FlashblocksPayloadV1.payload_id` (for flashblock messages). Each builder’s flashblock messages carry the same payload\_id that was authorized, ensuring they all belong to the same block-building session. + * **Freshness:** The `timestamp` in Authorization helps guard against replay of old messages. If a flashblock or StartPublish arrives with a significantly older timestamp (or for an already completed block), peers will ignore it and decrement the sender's reputation. + +These measures ensure that **only** the rollup-boost sanctioned builder’s data is propagated and that it’s cryptographically sound. Unauthorized parties cannot inject false flashblocks or tamper with content without detection. This design also allows dynamic builder changes: as long as the sequencer signs a new Authorization, the peers will accept the new builder’s messages even if they have never seen that builder before, because trust is transitive from the authorizers’s key. + +### Multi-Builder Coordination + +A major benefit of the P2P approach is the ability to coordinate multiple builders in an HA (High Availability) setting. The `StartPublish` and `StopPublish` messages, in conjunction with a small amount of logic in Rollup-Boost and the network, handle the arbitration: + +* **Single Publisher Rule:** The network expects at most one builder to be actively publishing flashblocks for a given L2 block number at any time. If two different builders both attempt to publish for the same block, the conflict must be resolved to maintain a consistent preconfirmation state. +* **Announcing Intent – `StartPublish`:** When Rollup-Boost (sequencer) initiates a new block with an external builder, it immediately broadcasts a `StartPublish` message (as an AuthorizedMessage) from that builder. This tells all peers: “Builder X is about to start publishing” If any other builder was thinking of building block N (perhaps there was a recent failover), it will see this and **stand down**. +* **Graceful Yield – reacting to `StartPublish`:** If a builder is currently publishing and receives a `StartPublish` from a *different* builder for the same or next block, it means a failover or override is happening. The expected behavior is that the current publisher will cease publishing (and issue a `StopPublish`). The protocol is designed such that the honest builder who is not supposed to publish will yield to the authorized one. The reference implementation will automatically send a `StopPublish` if it is publishing and learns that another builder has taken over authority for the block. The new builder will wait until it receives the `StopPublish` before continuing. +* **Completion – `StopPublish`:** When a builder receives the next FCU _without_ an accompanying `Authorization`, it will send out a `StopPublish`. This removes the builder from the “active publisher” role in the eyes of the network. If there was another builder in waiting (perhaps one that had attempted to start earlier but was told to wait), that waiting builder will now see that the coast is clear. +* **Timeouts and Fallback:** There is an implicit timeout in the coordination. If a builder is in a waiting state after announcing `StartPublish` but for some reason the previous publisher fails to produce a `StopPublish` (for example, if it crashed mid-block), other participants will not wait indefinitely. In our design, if a new block number is reached and the previous publisher hasn’t stopped we assume the previous builder is incapacitated and proceed with the new publisher. + +This coordination ensures that in an HA setup with multiple sequencer instances and multiple builders, **preconfirmation data remains consistent**: only one set of flashblocks is ever in flight for a given block. If a sequencer failover occurs, the worst-case scenario (which occurs only during a very rare race condition) is a single block publication gap or discontinuity at a block boundary. In the far more likely case, there will be exactly no flashblock disruption. The next publisher will simply start where the last publisher left off, even if that is mid block. + +## Rollup-Boost and Builder Communication + +In the P2P-enhanced design, Rollup-Boost’s interaction with the external block builder is slightly adjusted: + +* **Authorization Delivery:** When the sequencer (op-node) triggers a new block proposal via `engine_forkchoiceUpdated` (with payload attributes), Rollup-Boost creates an `Authorization` for the chosen builder. This requires that Rollup-Boost knows the builder’s public key in advance. In practice, the builder can be configured or registered with Rollup-Boost, providing its long-term public key. Rollup-Boost uses its **authorizer private key** (associated with the L2 chain or sequencer) to sign the authorization (covering payload\_id, timestamp, builder’s key). +* **Forkchoice Updated Forwarding:** Rollup-Boost forwards the fork choice update to the builder as usual (so the builder can start building the block). In this modified protocol, the fork choice update (or a parallel communication) includes the newly created `Authorization`. For example, a custom field or side-channel could convey the authorizer’s signature to the builder. **(Implementation-wise, this might be an extension of the Engine API or an internal call – the key point is the builder receives the Authorization token before it begins sending flashblocks.)** +* **StartPublish Broadcast:** If the builder was not previously publishing, then immediately after receiving the authorization it will emit a `StartPublish` message over the P2P network. This tells all listening nodes that the authorized builder will begin flashblock publication. +* **Streaming Flashblocks:** The builder executes transactions and produces flashblocks incrementally just as described in the original spec’s Flashblock Construction Process. However, instead of returning these payloads to Rollup-Boost, the builder now signs each flashblock with its key and directly broadcasts an Authorized Flashblock message to the P2P network. +* **No Inline Validation by Sequencer:** In the original design, Rollup-Boost would validate each flashblock against the local execution engine before propagating it. In the P2P model, this is not done synchronously for each flashblock (it would negate some latency benefits). Instead, trust is managed via the Authorization. The sequencer trusts its chosen builder to only send valid blocks (and will ultimately verify the final block when `engine_getPayload` is called). Peers trust the flashblocks because they trust the Rollup-Boost’s signature. + +In summary, Rollup-Boost’s role shifts from being a **middleman for data** to being a **controller and coordinator**. It authorizes the builder and informs the network about which builder is active, but it doesn’t need to ferry every flashblock through itself. This streamlines the path from builder to RPC providers. +