diff --git a/Cargo.lock b/Cargo.lock
index 804f7400e..ba23d8f01 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -576,7 +576,7 @@ dependencies = [
"serde_json",
"tokio",
"tokio-stream",
- "tower 0.5.2",
+ "tower",
"tracing",
"wasmtimer",
]
@@ -623,7 +623,7 @@ dependencies = [
"serde_json",
"tokio",
"tokio-stream",
- "tower 0.5.2",
+ "tower",
"tracing",
"url",
"wasmtimer",
@@ -999,7 +999,7 @@ dependencies = [
"serde_json",
"thiserror 2.0.17",
"tokio",
- "tower 0.5.2",
+ "tower",
"tracing",
"url",
"wasmtimer",
@@ -1015,7 +1015,7 @@ dependencies = [
"alloy-transport",
"reqwest",
"serde_json",
- "tower 0.5.2",
+ "tower",
"tracing",
"url",
]
@@ -1734,40 +1734,13 @@ dependencies = [
"fs_extra",
]
-[[package]]
-name = "axum"
-version = "0.7.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
-dependencies = [
- "async-trait",
- "axum-core 0.4.5",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "itoa",
- "matchit 0.7.3",
- "memchr",
- "mime",
- "percent-encoding",
- "pin-project-lite",
- "rustversion",
- "serde",
- "sync_wrapper",
- "tower 0.5.2",
- "tower-layer",
- "tower-service",
-]
-
[[package]]
name = "axum"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
dependencies = [
- "axum-core 0.5.5",
+ "axum-core",
"bytes",
"form_urlencoded",
"futures-util",
@@ -1777,7 +1750,7 @@ dependencies = [
"hyper",
"hyper-util",
"itoa",
- "matchit 0.8.4",
+ "matchit",
"memchr",
"mime",
"percent-encoding",
@@ -1788,32 +1761,12 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper",
"tokio",
- "tower 0.5.2",
+ "tower",
"tower-layer",
"tower-service",
"tracing",
]
-[[package]]
-name = "axum-core"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
-dependencies = [
- "async-trait",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "mime",
- "pin-project-lite",
- "rustversion",
- "sync_wrapper",
- "tower-layer",
- "tower-service",
-]
-
[[package]]
name = "axum-core"
version = "0.5.5"
@@ -1839,17 +1792,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
-[[package]]
-name = "backoff"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
-dependencies = [
- "getrandom 0.2.16",
- "instant",
- "rand 0.8.5",
-]
-
[[package]]
name = "backon"
version = "1.6.0"
@@ -3705,12 +3647,6 @@ dependencies = [
"cfg-if",
]
-[[package]]
-name = "endian-type"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
-
[[package]]
name = "enr"
version = "0.13.0"
@@ -5134,15 +5070,6 @@ dependencies = [
"syn 2.0.111",
]
-[[package]]
-name = "instant"
-version = "0.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
-dependencies = [
- "cfg-if",
-]
-
[[package]]
name = "interprocess"
version = "2.2.3"
@@ -5291,20 +5218,6 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "jsonrpsee"
-version = "0.25.1"
-source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff"
-dependencies = [
- "jsonrpsee-core 0.25.1",
- "jsonrpsee-http-client 0.25.1",
- "jsonrpsee-proc-macros 0.25.1",
- "jsonrpsee-server 0.25.1",
- "jsonrpsee-types 0.25.1",
- "tokio",
- "tracing",
-]
-
[[package]]
name = "jsonrpsee"
version = "0.26.0"
@@ -5312,11 +5225,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a"
dependencies = [
"jsonrpsee-client-transport",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-http-client 0.26.0",
- "jsonrpsee-proc-macros 0.26.0",
- "jsonrpsee-server 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee-core",
+ "jsonrpsee-http-client",
+ "jsonrpsee-proc-macros",
+ "jsonrpsee-server",
+ "jsonrpsee-types",
"jsonrpsee-wasm-client",
"jsonrpsee-ws-client",
"tokio",
@@ -5334,7 +5247,7 @@ dependencies = [
"futures-util",
"gloo-net",
"http",
- "jsonrpsee-core 0.26.0",
+ "jsonrpsee-core",
"pin-project",
"rustls",
"rustls-pki-types",
@@ -5348,30 +5261,6 @@ dependencies = [
"url",
]
-[[package]]
-name = "jsonrpsee-core"
-version = "0.25.1"
-source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff"
-dependencies = [
- "async-trait",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "jsonrpsee-types 0.25.1",
- "parking_lot",
- "pin-project",
- "rand 0.9.2",
- "rustc-hash 2.1.1",
- "serde",
- "serde_json",
- "thiserror 2.0.17",
- "tokio",
- "tower 0.5.2",
- "tracing",
-]
-
[[package]]
name = "jsonrpsee-core"
version = "0.26.0"
@@ -5385,7 +5274,7 @@ dependencies = [
"http",
"http-body",
"http-body-util",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee-types",
"parking_lot",
"pin-project",
"rand 0.9.2",
@@ -5395,33 +5284,11 @@ dependencies = [
"thiserror 2.0.17",
"tokio",
"tokio-stream",
- "tower 0.5.2",
+ "tower",
"tracing",
"wasm-bindgen-futures",
]
-[[package]]
-name = "jsonrpsee-http-client"
-version = "0.25.1"
-source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff"
-dependencies = [
- "base64 0.22.1",
- "http-body",
- "hyper",
- "hyper-rustls",
- "hyper-util",
- "jsonrpsee-core 0.25.1",
- "jsonrpsee-types 0.25.1",
- "rustls",
- "rustls-platform-verifier",
- "serde",
- "serde_json",
- "thiserror 2.0.17",
- "tokio",
- "tower 0.5.2",
- "url",
-]
-
[[package]]
name = "jsonrpsee-http-client"
version = "0.26.0"
@@ -5433,30 +5300,18 @@ dependencies = [
"hyper",
"hyper-rustls",
"hyper-util",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
"rustls",
"rustls-platform-verifier",
"serde",
"serde_json",
"thiserror 2.0.17",
"tokio",
- "tower 0.5.2",
+ "tower",
"url",
]
-[[package]]
-name = "jsonrpsee-proc-macros"
-version = "0.25.1"
-source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff"
-dependencies = [
- "heck",
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn 2.0.111",
-]
-
[[package]]
name = "jsonrpsee-proc-macros"
version = "0.26.0"
@@ -5470,32 +5325,6 @@ dependencies = [
"syn 2.0.111",
]
-[[package]]
-name = "jsonrpsee-server"
-version = "0.25.1"
-source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff"
-dependencies = [
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-util",
- "jsonrpsee-core 0.25.1",
- "jsonrpsee-types 0.25.1",
- "pin-project",
- "route-recognizer",
- "serde",
- "serde_json",
- "soketto",
- "thiserror 2.0.17",
- "tokio",
- "tokio-stream",
- "tokio-util",
- "tower 0.5.2",
- "tracing",
-]
-
[[package]]
name = "jsonrpsee-server"
version = "0.26.0"
@@ -5508,8 +5337,8 @@ dependencies = [
"http-body-util",
"hyper",
"hyper-util",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
"pin-project",
"route-recognizer",
"serde",
@@ -5519,21 +5348,10 @@ dependencies = [
"tokio",
"tokio-stream",
"tokio-util",
- "tower 0.5.2",
+ "tower",
"tracing",
]
-[[package]]
-name = "jsonrpsee-types"
-version = "0.25.1"
-source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff"
-dependencies = [
- "http",
- "serde",
- "serde_json",
- "thiserror 2.0.17",
-]
-
[[package]]
name = "jsonrpsee-types"
version = "0.26.0"
@@ -5553,9 +5371,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7902885de4779f711a95d82c8da2d7e5f9f3a7c7cfa44d51c067fd1c29d72a3c"
dependencies = [
"jsonrpsee-client-transport",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
- "tower 0.5.2",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
+ "tower",
]
[[package]]
@@ -5566,9 +5384,9 @@ checksum = "9b6fceceeb05301cc4c065ab3bd2fa990d41ff4eb44e4ca1b30fa99c057c3e79"
dependencies = [
"http",
"jsonrpsee-client-transport",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
- "tower 0.5.2",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
+ "tower",
"url",
]
@@ -6199,15 +6017,6 @@ dependencies = [
"hashbrown 0.15.5",
]
-[[package]]
-name = "lru"
-version = "0.16.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f"
-dependencies = [
- "hashbrown 0.16.1",
-]
-
[[package]]
name = "lru-slab"
version = "0.1.2"
@@ -6289,12 +6098,6 @@ dependencies = [
"regex-automata",
]
-[[package]]
-name = "matchit"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
-
[[package]]
name = "matchit"
version = "0.8.4"
@@ -6364,18 +6167,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034"
dependencies = [
"base64 0.22.1",
- "http-body-util",
- "hyper",
- "hyper-rustls",
- "hyper-util",
"indexmap 2.12.1",
- "ipnet",
"metrics",
"metrics-util 0.19.1",
"quanta",
"thiserror 1.0.69",
- "tokio",
- "tracing",
]
[[package]]
@@ -6421,15 +6217,11 @@ version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376"
dependencies = [
- "aho-corasick",
"crossbeam-epoch",
"crossbeam-utils",
"hashbrown 0.15.5",
- "indexmap 2.12.1",
"metrics",
- "ordered-float",
"quanta",
- "radix_trie",
"rand 0.9.2",
"rand_xoshiro",
"sketches-ddsketch",
@@ -6703,15 +6495,6 @@ dependencies = [
"tokio",
]
-[[package]]
-name = "nibble_vec"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
-dependencies = [
- "smallvec",
-]
-
[[package]]
name = "nix"
version = "0.26.4"
@@ -7040,7 +6823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ef9114426b16172254555aad34a8ea96c01895e40da92f5d12ea680a1baeaa7"
dependencies = [
"alloy-primitives 1.4.1",
- "jsonrpsee 0.26.0",
+ "jsonrpsee",
]
[[package]]
@@ -7085,7 +6868,7 @@ dependencies = [
[[package]]
name = "op-rbuilder"
-version = "0.2.13"
+version = "0.2.14"
dependencies = [
"alloy-consensus",
"alloy-contract",
@@ -7124,9 +6907,9 @@ dependencies = [
"http-body-util",
"hyper",
"hyper-util",
- "jsonrpsee 0.26.0",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
"k256",
"macros",
"metrics",
@@ -7138,7 +6921,7 @@ dependencies = [
"op-alloy-rpc-types",
"op-alloy-rpc-types-engine",
"op-revm",
- "opentelemetry 0.31.0",
+ "opentelemetry",
"p2p",
"parking_lot",
"rand 0.9.2",
@@ -7191,7 +6974,6 @@ dependencies = [
"reth-trie",
"revm",
"rlimit",
- "rollup-boost",
"secp256k1 0.30.0",
"serde",
"serde_json",
@@ -7209,7 +6991,7 @@ dependencies = [
"tokio",
"tokio-tungstenite",
"tokio-util",
- "tower 0.5.2",
+ "tower",
"tracing",
"tracing-subscriber 0.3.20",
"url",
@@ -7279,20 +7061,6 @@ dependencies = [
"vcpkg",
]
-[[package]]
-name = "opentelemetry"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "236e667b670a5cdf90c258f5a55794ec5ac5027e960c224bff8367a59e1e6426"
-dependencies = [
- "futures-core",
- "futures-sink",
- "js-sys",
- "pin-project-lite",
- "thiserror 2.0.17",
- "tracing",
-]
-
[[package]]
name = "opentelemetry"
version = "0.31.0"
@@ -7307,20 +7075,6 @@ dependencies = [
"tracing",
]
-[[package]]
-name = "opentelemetry-http"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253"
-dependencies = [
- "async-trait",
- "bytes",
- "http",
- "opentelemetry 0.28.0",
- "reqwest",
- "tracing",
-]
-
[[package]]
name = "opentelemetry-http"
version = "0.31.0"
@@ -7330,30 +7084,8 @@ dependencies = [
"async-trait",
"bytes",
"http",
- "opentelemetry 0.31.0",
- "reqwest",
-]
-
-[[package]]
-name = "opentelemetry-otlp"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91"
-dependencies = [
- "async-trait",
- "futures-core",
- "http",
- "opentelemetry 0.28.0",
- "opentelemetry-http 0.28.0",
- "opentelemetry-proto 0.28.0",
- "opentelemetry_sdk 0.28.0",
- "prost 0.13.5",
+ "opentelemetry",
"reqwest",
- "serde_json",
- "thiserror 2.0.17",
- "tokio",
- "tonic 0.12.3",
- "tracing",
]
[[package]]
@@ -7363,43 +7095,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf"
dependencies = [
"http",
- "opentelemetry 0.31.0",
- "opentelemetry-http 0.31.0",
- "opentelemetry-proto 0.31.0",
- "opentelemetry_sdk 0.31.0",
- "prost 0.14.1",
+ "opentelemetry",
+ "opentelemetry-http",
+ "opentelemetry-proto",
+ "opentelemetry_sdk",
+ "prost",
"reqwest",
"thiserror 2.0.17",
"tokio",
- "tonic 0.14.2",
+ "tonic",
"tracing",
]
-[[package]]
-name = "opentelemetry-proto"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d"
-dependencies = [
- "base64 0.22.1",
- "hex",
- "opentelemetry 0.28.0",
- "opentelemetry_sdk 0.28.0",
- "prost 0.13.5",
- "serde",
- "tonic 0.12.3",
-]
-
[[package]]
name = "opentelemetry-proto"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f"
dependencies = [
- "opentelemetry 0.31.0",
- "opentelemetry_sdk 0.31.0",
- "prost 0.14.1",
- "tonic 0.14.2",
+ "opentelemetry",
+ "opentelemetry_sdk",
+ "prost",
+ "tonic",
"tonic-prost",
]
@@ -7409,27 +7126,6 @@ version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846"
-[[package]]
-name = "opentelemetry_sdk"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570"
-dependencies = [
- "async-trait",
- "futures-channel",
- "futures-executor",
- "futures-util",
- "glob",
- "opentelemetry 0.28.0",
- "percent-encoding",
- "rand 0.8.5",
- "serde_json",
- "thiserror 2.0.17",
- "tokio",
- "tokio-stream",
- "tracing",
-]
-
[[package]]
name = "opentelemetry_sdk"
version = "0.31.0"
@@ -7439,7 +7135,7 @@ dependencies = [
"futures-channel",
"futures-executor",
"futures-util",
- "opentelemetry 0.31.0",
+ "opentelemetry",
"percent-encoding",
"rand 0.9.2",
"thiserror 2.0.17",
@@ -7451,15 +7147,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
-[[package]]
-name = "ordered-float"
-version = "4.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
-dependencies = [
- "num-traits",
-]
-
[[package]]
name = "p256"
version = "0.13.2"
@@ -7474,7 +7161,7 @@ dependencies = [
[[package]]
name = "p2p"
-version = "0.2.13"
+version = "0.2.14"
dependencies = [
"derive_more",
"eyre",
@@ -8048,16 +7735,6 @@ dependencies = [
"syn 2.0.111",
]
-[[package]]
-name = "prost"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
-dependencies = [
- "bytes",
- "prost-derive 0.13.5",
-]
-
[[package]]
name = "prost"
version = "0.14.1"
@@ -8065,20 +7742,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d"
dependencies = [
"bytes",
- "prost-derive 0.14.1",
-]
-
-[[package]]
-name = "prost-derive"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
-dependencies = [
- "anyhow",
- "itertools 0.14.0",
- "proc-macro2",
- "quote",
- "syn 2.0.111",
+ "prost-derive",
]
[[package]]
@@ -8225,16 +7889,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
-[[package]]
-name = "radix_trie"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
-dependencies = [
- "endian-type",
- "nibble_vec",
-]
-
[[package]]
name = "rand"
version = "0.8.5"
@@ -8521,7 +8175,7 @@ dependencies = [
"tokio-native-tls",
"tokio-rustls",
"tokio-util",
- "tower 0.5.2",
+ "tower",
"tower-http",
"tower-service",
"url",
@@ -9654,7 +9308,7 @@ dependencies = [
"alloy-rpc-types-debug",
"eyre",
"futures",
- "jsonrpsee 0.26.0",
+ "jsonrpsee",
"pretty_assertions",
"reth-engine-primitives",
"reth-evm",
@@ -9680,14 +9334,14 @@ dependencies = [
"futures",
"futures-util",
"interprocess",
- "jsonrpsee 0.26.0",
+ "jsonrpsee",
"pin-project",
"serde_json",
"thiserror 2.0.17",
"tokio",
"tokio-stream",
"tokio-util",
- "tower 0.5.2",
+ "tower",
"tracing",
]
@@ -9938,7 +9592,7 @@ dependencies = [
"eyre",
"fdlimit",
"futures",
- "jsonrpsee 0.26.0",
+ "jsonrpsee",
"rayon",
"reth-basic-payload-builder",
"reth-chain-state",
@@ -10137,7 +9791,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8
dependencies = [
"eyre",
"http",
- "jsonrpsee-server 0.26.0",
+ "jsonrpsee-server",
"metrics",
"metrics-exporter-prometheus 0.16.2",
"metrics-process",
@@ -10148,7 +9802,7 @@ dependencies = [
"reth-tasks",
"tikv-jemalloc-ctl",
"tokio",
- "tower 0.5.2",
+ "tower",
"tracing",
]
@@ -10470,9 +10124,9 @@ dependencies = [
"derive_more",
"eyre",
"futures",
- "jsonrpsee 0.26.0",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
"metrics",
"op-alloy-consensus",
"op-alloy-network",
@@ -10508,7 +10162,7 @@ dependencies = [
"thiserror 2.0.17",
"tokio",
"tokio-stream",
- "tower 0.5.2",
+ "tower",
"tracing",
]
@@ -10857,8 +10511,8 @@ dependencies = [
"http-body",
"hyper",
"itertools 0.14.0",
- "jsonrpsee 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee",
+ "jsonrpsee-types",
"jsonwebtoken",
"parking_lot",
"pin-project",
@@ -10897,7 +10551,7 @@ dependencies = [
"thiserror 2.0.17",
"tokio",
"tokio-stream",
- "tower 0.5.2",
+ "tower",
"tracing",
"tracing-futures",
]
@@ -10922,7 +10576,7 @@ dependencies = [
"alloy-rpc-types-trace",
"alloy-rpc-types-txpool",
"alloy-serde",
- "jsonrpsee 0.26.0",
+ "jsonrpsee",
"reth-chain-state",
"reth-engine-primitives",
"reth-network-peers",
@@ -10939,7 +10593,7 @@ dependencies = [
"alloy-provider",
"dyn-clone",
"http",
- "jsonrpsee 0.26.0",
+ "jsonrpsee",
"metrics",
"pin-project",
"reth-chain-state",
@@ -10964,7 +10618,7 @@ dependencies = [
"thiserror 2.0.17",
"tokio",
"tokio-util",
- "tower 0.5.2",
+ "tower",
"tower-http",
"tracing",
]
@@ -10982,7 +10636,7 @@ dependencies = [
"alloy-signer",
"auto_impl",
"dyn-clone",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee-types",
"op-alloy-consensus",
"op-alloy-network",
"op-alloy-rpc-types",
@@ -11005,8 +10659,8 @@ dependencies = [
"alloy-primitives 1.4.1",
"alloy-rpc-types-engine",
"async-trait",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
"metrics",
"parking_lot",
"reth-chainspec",
@@ -11046,8 +10700,8 @@ dependencies = [
"auto_impl",
"dyn-clone",
"futures",
- "jsonrpsee 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee",
+ "jsonrpsee-types",
"parking_lot",
"reth-chain-state",
"reth-chainspec",
@@ -11087,8 +10741,8 @@ dependencies = [
"derive_more",
"futures",
"itertools 0.14.0",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
"metrics",
"rand 0.9.2",
"reqwest",
@@ -11124,9 +10778,9 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8
dependencies = [
"alloy-rpc-types-engine",
"http",
- "jsonrpsee-http-client 0.26.0",
+ "jsonrpsee-http-client",
"pin-project",
- "tower 0.5.2",
+ "tower",
"tower-http",
"tracing",
]
@@ -11139,8 +10793,8 @@ dependencies = [
"alloy-eips",
"alloy-primitives 1.4.1",
"alloy-rpc-types-engine",
- "jsonrpsee-core 0.26.0",
- "jsonrpsee-types 0.26.0",
+ "jsonrpsee-core",
+ "jsonrpsee-types",
"reth-errors",
"reth-network-api",
"serde",
@@ -11375,12 +11029,12 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8
dependencies = [
"clap",
"eyre",
- "opentelemetry 0.31.0",
- "opentelemetry-otlp 0.31.0",
+ "opentelemetry",
+ "opentelemetry-otlp",
"opentelemetry-semantic-conventions",
- "opentelemetry_sdk 0.31.0",
+ "opentelemetry_sdk",
"tracing",
- "tracing-opentelemetry 0.32.0",
+ "tracing-opentelemetry",
"tracing-subscriber 0.3.20",
"url",
]
@@ -11870,60 +11524,6 @@ dependencies = [
"chrono",
]
-[[package]]
-name = "rollup-boost"
-version = "0.1.0"
-source = "git+https://github.com/flashbots/rollup-boost?tag=v0.7.11#196237bab2a02298de994b439e0455abb1ac512f"
-dependencies = [
- "alloy-primitives 1.4.1",
- "alloy-rpc-types-engine",
- "alloy-rpc-types-eth",
- "alloy-serde",
- "backoff",
- "bytes",
- "clap",
- "dashmap 6.1.0",
- "dotenvy",
- "eyre",
- "futures",
- "http",
- "http-body-util",
- "hyper",
- "hyper-rustls",
- "hyper-util",
- "jsonrpsee 0.25.1",
- "lru 0.16.2",
- "metrics",
- "metrics-derive",
- "metrics-exporter-prometheus 0.16.2",
- "metrics-util 0.19.1",
- "moka",
- "op-alloy-rpc-types-engine",
- "opentelemetry 0.28.0",
- "opentelemetry-otlp 0.28.0",
- "opentelemetry_sdk 0.28.0",
- "parking_lot",
- "paste",
- "reth-optimism-payload-builder",
- "rustls",
- "serde",
- "serde_json",
- "sha2",
- "thiserror 2.0.17",
- "tokio",
- "tokio-tungstenite",
- "tokio-util",
- "tower 0.5.2",
- "tower-http",
- "tracing",
- "tracing-opentelemetry 0.29.0",
- "tracing-subscriber 0.3.20",
- "url",
- "uuid",
- "vergen",
- "vergen-git2",
-]
-
[[package]]
name = "route-recognizer"
version = "0.3.1"
@@ -13130,7 +12730,7 @@ dependencies = [
name = "tdx-quote-provider"
version = "0.1.0"
dependencies = [
- "axum 0.8.7",
+ "axum",
"clap",
"dotenvy",
"eyre",
@@ -13454,12 +13054,10 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [
"futures-util",
"log",
- "native-tls",
"rustls",
"rustls-native-certs",
"rustls-pki-types",
"tokio",
- "tokio-native-tls",
"tokio-rustls",
"tungstenite",
"webpki-roots 0.26.11",
@@ -13572,36 +13170,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
-[[package]]
-name = "tonic"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
-dependencies = [
- "async-stream",
- "async-trait",
- "axum 0.7.9",
- "base64 0.22.1",
- "bytes",
- "h2",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-timeout",
- "hyper-util",
- "percent-encoding",
- "pin-project",
- "prost 0.13.5",
- "socket2 0.5.10",
- "tokio",
- "tokio-stream",
- "tower 0.4.13",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
[[package]]
name = "tonic"
version = "0.14.2"
@@ -13622,7 +13190,7 @@ dependencies = [
"sync_wrapper",
"tokio",
"tokio-stream",
- "tower 0.5.2",
+ "tower",
"tower-layer",
"tower-service",
"tracing",
@@ -13635,28 +13203,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67"
dependencies = [
"bytes",
- "prost 0.14.1",
- "tonic 0.14.2",
-]
-
-[[package]]
-name = "tower"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
-dependencies = [
- "futures-core",
- "futures-util",
- "indexmap 1.9.3",
- "pin-project",
- "pin-project-lite",
- "rand 0.8.5",
- "slab",
- "tokio",
- "tokio-util",
- "tower-layer",
- "tower-service",
- "tracing",
+ "prost",
+ "tonic",
]
[[package]]
@@ -13703,7 +13251,7 @@ dependencies = [
"pin-project-lite",
"tokio",
"tokio-util",
- "tower 0.5.2",
+ "tower",
"tower-layer",
"tower-service",
"tracing",
@@ -13811,24 +13359,6 @@ dependencies = [
"tracing-subscriber 0.3.20",
]
-[[package]]
-name = "tracing-opentelemetry"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "721f2d2569dce9f3dfbbddee5906941e953bfcdf736a62da3377f5751650cc36"
-dependencies = [
- "js-sys",
- "once_cell",
- "opentelemetry 0.28.0",
- "opentelemetry_sdk 0.28.0",
- "smallvec",
- "tracing",
- "tracing-core",
- "tracing-log",
- "tracing-subscriber 0.3.20",
- "web-time",
-]
-
[[package]]
name = "tracing-opentelemetry"
version = "0.32.0"
@@ -13836,8 +13366,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6e5658463dd88089aba75c7791e1d3120633b1bfde22478b28f625a9bb1b8e"
dependencies = [
"js-sys",
- "opentelemetry 0.31.0",
- "opentelemetry_sdk 0.31.0",
+ "opentelemetry",
+ "opentelemetry_sdk",
"rustversion",
"smallvec",
"thiserror 2.0.17",
@@ -13979,7 +13509,6 @@ dependencies = [
"http",
"httparse",
"log",
- "native-tls",
"rand 0.9.2",
"rustls",
"rustls-pki-types",
diff --git a/Cargo.toml b/Cargo.toml
index a338c9b00..561883ab5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace.package]
-version = "0.2.13"
+version = "0.2.14"
edition = "2024"
rust-version = "1.88"
license = "MIT OR Apache-2.0"
@@ -161,17 +161,14 @@ alloy-trie = { version = "0.9.1" }
alloy-hardforks = "0.4.4"
-# rollup-boost
-rollup-boost = { git = "https://github.com/flashbots/rollup-boost", tag = "v0.7.11" }
-
# optimism
alloy-op-evm = { version = "0.23.0", default-features = false }
alloy-op-hardforks = "0.4.4"
-op-alloy-rpc-types = { version = "0.22.0", default-features = false }
-op-alloy-rpc-types-engine = { version = "0.22.0", default-features = false }
-op-alloy-rpc-jsonrpsee = { version = "0.22.0", default-features = false }
-op-alloy-network = { version = "0.22.0", default-features = false }
-op-alloy-consensus = { version = "0.22.0", default-features = false }
+op-alloy-rpc-types = { version = "0.22.4", default-features = false }
+op-alloy-rpc-types-engine = { version = "0.22.4", default-features = false }
+op-alloy-rpc-jsonrpsee = { version = "0.22.4", default-features = false }
+op-alloy-network = { version = "0.22.4", default-features = false }
+op-alloy-consensus = { version = "0.22.4", default-features = false }
op-alloy-flz = { version = "0.13.1", default-features = false }
async-trait = { version = "0.1.83" }
diff --git a/crates/op-rbuilder/Cargo.toml b/crates/op-rbuilder/Cargo.toml
index a0f5c8d2f..4375adbf7 100644
--- a/crates/op-rbuilder/Cargo.toml
+++ b/crates/op-rbuilder/Cargo.toml
@@ -128,14 +128,12 @@ rand = "0.9.0"
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
shellexpand = "3.1"
serde_yaml = { version = "0.9" }
-moka = "0.12"
+moka = { version = "0.12", features = ["future"] }
http = "1.0"
sha3 = "0.10"
reqwest = "0.12.23"
k256 = "0.13.4"
-rollup-boost.workspace = true
-
nanoid = { version = "0.4", optional = true }
reth-ipc = { workspace = true, optional = true }
tar = { version = "0.4", optional = true }
diff --git a/crates/op-rbuilder/src/args/op.rs b/crates/op-rbuilder/src/args/op.rs
index 4282d54d2..b7e288df3 100644
--- a/crates/op-rbuilder/src/args/op.rs
+++ b/crates/op-rbuilder/src/args/op.rs
@@ -151,7 +151,7 @@ pub struct FlashblocksArgs {
/// building time before calculating number of fbs.
#[arg(
long = "flashblocks.leeway-time",
- default_value = "75",
+ default_value = "0",
env = "FLASHBLOCK_LEEWAY_TIME"
)]
pub flashblocks_leeway_time: u64,
@@ -183,6 +183,35 @@ pub struct FlashblocksArgs {
)]
pub flashblocks_number_contract_use_permit: bool,
+ /// Build flashblock at the end of the flashblock interval
+ #[arg(
+ long = "flashblocks.build-at-interval-end",
+ env = "FLASHBLOCK_BUILD_AT_INTERVAL_END",
+ default_value = "false"
+ )]
+ pub flashblocks_build_at_interval_end: bool,
+
+ /// Offset in milliseconds for when to send flashblocks.
+ /// Positive values send late, negative values send early.
+ /// Example: -20 sends 20ms early, 20 sends 20ms late.
+ #[arg(
+ long = "flashblocks.send-offset-ms",
+ env = "FLASHBLOCK_SEND_OFFSET_MS",
+ default_value = "0",
+ allow_hyphen_values = true
+ )]
+ pub flashblocks_send_offset_ms: i64,
+
+ /// Time in milliseconds to build the last flashblock early before the end of the slot
+ /// This serves as a buffer time to account for the last flashblock being delayed
+ /// at the end of the slot due to processing the final block
+ #[arg(
+ long = "flashblocks.end-buffer-ms",
+ env = "FLASHBLOCK_END_BUFFER_MS",
+ default_value = "0"
+ )]
+ pub flashblocks_end_buffer_ms: u64,
+
/// Flashblocks p2p configuration
#[command(flatten)]
pub p2p: FlashblocksP2pArgs,
diff --git a/crates/op-rbuilder/src/builders/flashblocks/config.rs b/crates/op-rbuilder/src/builders/flashblocks/config.rs
index cdc6ae915..364c68115 100644
--- a/crates/op-rbuilder/src/builders/flashblocks/config.rs
+++ b/crates/op-rbuilder/src/builders/flashblocks/config.rs
@@ -37,10 +37,21 @@ pub struct FlashblocksConfig {
/// The address of the flashblocks number contract.
///
/// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx.
- pub flashblocks_number_contract_address: Option
,
+ pub number_contract_address: Option,
/// whether to use permit signatures for the contract calls
- pub flashblocks_number_contract_use_permit: bool,
+ pub number_contract_use_permit: bool,
+
+ /// Build flashblock at the end of the flashblock interval
+ pub build_at_interval_end: bool,
+
+ /// Offset in milliseconds for when to send flashblocks.
+ /// Positive values send late, negative values send early.
+ pub send_offset_ms: i64,
+
+ /// Time in milliseconds to build the last flashblock early before the end of the slot.
+ /// This serves as a buffer time to account for the last flashblock being delayed.
+ pub end_buffer_ms: u64,
/// Whether to enable the p2p node for flashblocks
pub p2p_enabled: bool,
@@ -63,11 +74,14 @@ impl Default for FlashblocksConfig {
Self {
ws_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 1111),
interval: Duration::from_millis(250),
- leeway_time: Duration::from_millis(50),
+ leeway_time: Duration::from_millis(0),
fixed: false,
disable_state_root: false,
- flashblocks_number_contract_address: None,
- flashblocks_number_contract_use_permit: false,
+ number_contract_address: None,
+ number_contract_use_permit: false,
+ build_at_interval_end: false,
+ send_offset_ms: 0,
+ end_buffer_ms: 0,
p2p_enabled: false,
p2p_port: 9009,
p2p_private_key_file: None,
@@ -94,11 +108,9 @@ impl TryFrom for FlashblocksConfig {
let disable_state_root = args.flashblocks.flashblocks_disable_state_root;
- let flashblocks_number_contract_address =
- args.flashblocks.flashblocks_number_contract_address;
+ let number_contract_address = args.flashblocks.flashblocks_number_contract_address;
- let flashblocks_number_contract_use_permit =
- args.flashblocks.flashblocks_number_contract_use_permit;
+ let number_contract_use_permit = args.flashblocks.flashblocks_number_contract_use_permit;
Ok(Self {
ws_addr,
@@ -106,8 +118,11 @@ impl TryFrom for FlashblocksConfig {
leeway_time,
fixed,
disable_state_root,
- flashblocks_number_contract_address,
- flashblocks_number_contract_use_permit,
+ number_contract_address,
+ number_contract_use_permit,
+ build_at_interval_end: args.flashblocks.flashblocks_build_at_interval_end,
+ send_offset_ms: args.flashblocks.flashblocks_send_offset_ms,
+ end_buffer_ms: args.flashblocks.flashblocks_end_buffer_ms,
p2p_enabled: args.flashblocks.p2p.p2p_enabled,
p2p_port: args.flashblocks.p2p.p2p_port,
p2p_private_key_file: args.flashblocks.p2p.p2p_private_key_file,
diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/op-rbuilder/src/builders/flashblocks/payload.rs
index 9562f53df..b1e8772f2 100644
--- a/crates/op-rbuilder/src/builders/flashblocks/payload.rs
+++ b/crates/op-rbuilder/src/builders/flashblocks/payload.rs
@@ -16,20 +16,24 @@ use alloy_consensus::{
BlockBody, EMPTY_OMMER_ROOT_HASH, Header, constants::EMPTY_WITHDRAWALS, proofs,
};
use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE};
-use alloy_primitives::{Address, B256, U256, map::foldhash::HashMap};
+use alloy_primitives::{Address, B256, U256};
use core::time::Duration;
use eyre::WrapErr as _;
+use op_alloy_rpc_types_engine::{
+ OpFlashblockPayload, OpFlashblockPayloadBase, OpFlashblockPayloadDelta,
+ OpFlashblockPayloadMetadata,
+};
use reth::payload::PayloadBuilderAttributes;
use reth_basic_payload_builder::BuildOutcome;
use reth_chain_state::ExecutedBlock;
use reth_chainspec::EthChainSpec;
use reth_evm::{ConfigureEvm, execute::BlockBuilder};
-use reth_node_api::{Block, NodePrimitives, PayloadBuilderError};
+use reth_node_api::{Block, PayloadBuilderError};
use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus};
use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes};
use reth_optimism_forks::OpHardforks;
use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes};
-use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned};
+use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};
use reth_payload_util::BestPayloadTransactions;
use reth_primitives_traits::RecoveredBlock;
use reth_provider::{
@@ -42,11 +46,8 @@ use reth_revm::{
use reth_transaction_pool::TransactionPool;
use reth_trie::{HashedPostState, updates::TrieUpdates};
use revm::Database;
-use rollup_boost::{
- ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1,
-};
-use serde::{Deserialize, Serialize};
use std::{
+ collections::BTreeMap,
ops::{Div, Rem},
sync::Arc,
time::Instant,
@@ -55,6 +56,24 @@ use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use tracing::{debug, error, info, metadata::Level, span, warn};
+/// Converts a reth OpReceipt to an op-alloy OpReceipt
+/// TODO: remove this once reth updates to use the op-alloy defined type as well.
+fn convert_receipt(receipt: &OpReceipt) -> op_alloy_consensus::OpReceipt {
+ match receipt {
+ OpReceipt::Legacy(r) => op_alloy_consensus::OpReceipt::Legacy(r.clone()),
+ OpReceipt::Eip2930(r) => op_alloy_consensus::OpReceipt::Eip2930(r.clone()),
+ OpReceipt::Eip1559(r) => op_alloy_consensus::OpReceipt::Eip1559(r.clone()),
+ OpReceipt::Eip7702(r) => op_alloy_consensus::OpReceipt::Eip7702(r.clone()),
+ OpReceipt::Deposit(r) => {
+ op_alloy_consensus::OpReceipt::Deposit(op_alloy_consensus::OpDepositReceipt {
+ inner: r.inner.clone(),
+ deposit_nonce: r.deposit_nonce,
+ deposit_receipt_version: r.deposit_receipt_version,
+ })
+ }
+ }
+}
+
type NextBestFlashblocksTxs = BestFlashblocksTxs<
::Transaction,
Box<
@@ -68,6 +87,17 @@ type NextBestFlashblocksTxs = BestFlashblocksTxs<
>,
>;
+/// Timing information for flashblock building
+#[derive(Debug, Clone, Copy)]
+pub(super) struct FlashblocksTiming {
+ /// Number of flashblocks to build in this block
+ pub flashblocks_per_block: u64,
+ /// Time until the first flashblock should be built
+ pub first_flashblock_offset: Duration,
+ /// Total time available for flashblock building (deadline)
+ pub flashblocks_deadline: Duration,
+}
+
#[derive(Debug, Default, Clone)]
pub(super) struct FlashblocksExecutionInfo {
/// Index of the last consumed flashblock
@@ -419,9 +449,25 @@ where
// return early since we don't need to build a block with transactions from the pool
return Ok(());
}
- // We adjust our flashblocks timings based on time_drift if dynamic adjustment enable
- let (flashblocks_per_block, first_flashblock_offset) =
- self.calculate_flashblocks(timestamp);
+ // We adjust our flashblocks timings based on time the fcu block building signal arrived
+ let (flashblocks_per_block, first_flashblock_offset, flashblocks_deadline) =
+ if self.config.specific.build_at_interval_end {
+ let timing = self.calculate_flashblocks_timing(timestamp);
+ (
+ timing.flashblocks_per_block,
+ timing.first_flashblock_offset,
+ timing.flashblocks_deadline,
+ )
+ } else {
+ let (flashblocks_per_block, first_flashblock_offset) =
+ self.calculate_flashblocks(timestamp);
+ (
+ flashblocks_per_block,
+ first_flashblock_offset,
+ self.config.block_time,
+ )
+ };
+
info!(
target: "payload_builder",
message = "Performed flashblocks timing derivation",
@@ -479,11 +525,22 @@ where
));
let interval = self.config.specific.interval;
let (tx, mut rx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize);
+ let build_at_interval_end = self.config.specific.build_at_interval_end;
tokio::spawn({
let block_cancel = block_cancel.clone();
async move {
+ // If NOT building at interval end, send immediate signal to build first
+ // flashblock right away (preserves current default behavior).
+ // Otherwise, wait for first_flashblock_offset before first build.
+ if !build_at_interval_end && tx.send(fb_cancel.clone()).await.is_err() {
+ error!(
+ target: "payload_builder",
+ "Did not trigger first flashblock build due to payload building error or block building being cancelled");
+ return;
+ }
+
let mut timer = tokio::time::interval_at(
tokio::time::Instant::now()
.checked_add(first_flashblock_offset)
@@ -491,6 +548,12 @@ where
interval,
);
+ // Set deadline to ensure the last flashblock will be built before the leeway time
+ let deadline_sleep = async {
+ tokio::time::sleep(flashblocks_deadline).await;
+ };
+ tokio::pin!(deadline_sleep);
+
loop {
tokio::select! {
_ = timer.tick() => {
@@ -498,15 +561,30 @@ where
fb_cancel.cancel();
fb_cancel = block_cancel.child_token();
// this will tick at first_flashblock_offset,
- // starting the second flashblock
+ // starting the next flashblock
if tx.send(fb_cancel.clone()).await.is_err() {
// receiver channel was dropped, return.
// this will only happen if the `build_payload` function returns,
// due to payload building error or the main cancellation token being
// cancelled.
+ error!(
+ target: "payload_builder",
+ "Did not trigger next flashblock build due to payload building error or block building being cancelled",
+ );
return;
}
}
+ _ = &mut deadline_sleep => {
+ // Deadline reached (with leeway applied to end). Cancel current payload building job
+ fb_cancel.cancel();
+ if tx.send(block_cancel.child_token()).await.is_err() {
+ error!(
+ target: "payload_builder",
+ "Did not trigger next flashblock build due to payload building error or block building being cancelled",
+ );
+ }
+ return;
+ }
_ = block_cancel.cancelled() => {
return;
}
@@ -517,6 +595,25 @@ where
// Process flashblocks in a blocking loop
loop {
+ // Wait for signal before building flashblock.
+ // If build_at_interval_end is false, an immediate signal is sent so we don't wait.
+ // If build_at_interval_end is true, we wait for the timer tick (first_flashblock_offset).
+ tokio::select! {
+ Some(new_fb_cancel) = rx.recv() => {
+ ctx = ctx.with_cancel(new_fb_cancel);
+ },
+ _ = block_cancel.cancelled() => {
+ self.record_flashblocks_metrics(
+ &ctx,
+ &info,
+ flashblocks_per_block,
+ &span,
+ "Payload building complete, cancelled before first flashblock",
+ );
+ return Ok(());
+ }
+ }
+
let fb_span = if span.is_none() {
tracing::Span::none()
} else {
@@ -539,7 +636,7 @@ where
return Ok(());
}
- // build first flashblock immediately
+ // Build flashblock after receiving signal
let next_flashblocks_ctx = match self
.build_next_flashblock(
&ctx,
@@ -576,21 +673,7 @@ where
}
};
- tokio::select! {
- Some(fb_cancel) = rx.recv() => {
- ctx = ctx.with_cancel(fb_cancel).with_extra_ctx(next_flashblocks_ctx);
- },
- _ = block_cancel.cancelled() => {
- self.record_flashblocks_metrics(
- &ctx,
- &info,
- flashblocks_per_block,
- &span,
- "Payload building complete, channel closed or job cancelled",
- );
- return Ok(());
- }
- }
+ ctx = ctx.with_extra_ctx(next_flashblocks_ctx);
}
}
@@ -859,6 +942,7 @@ where
/// Calculate number of flashblocks.
/// If dynamic is enabled this function will take time drift into the account.
+ /// TODO: deprecate this flashblocks timing calculation
pub(super) fn calculate_flashblocks(&self, timestamp: u64) -> (u64, Duration) {
if self.config.specific.fixed {
return (
@@ -918,6 +1002,155 @@ where
)
}
}
+
+ /// Calculate number of flashblocks and time until first flashblock and deadline for building flashblocks
+ /// If dynamic is enabled this function will take time drift of FCU arrival into the account.
+ pub(super) fn calculate_flashblocks_timing(&self, timestamp: u64) -> FlashblocksTiming {
+ let offset_delta = self.config.specific.send_offset_ms.unsigned_abs();
+ if self.config.specific.fixed {
+ let offset = if self.config.specific.send_offset_ms > 0 {
+ self.config
+ .specific
+ .interval
+ .saturating_add(Duration::from_millis(offset_delta))
+ } else {
+ self.config
+ .specific
+ .interval
+ .saturating_sub(Duration::from_millis(offset_delta))
+ };
+ return FlashblocksTiming {
+ flashblocks_per_block: self.config.flashblocks_per_block(),
+ first_flashblock_offset: offset,
+ flashblocks_deadline: self
+ .config
+ .block_time
+ .saturating_sub(Duration::from_millis(self.config.specific.end_buffer_ms)),
+ };
+ }
+
+ // FLASHBLOCK TIMING SCENARIOS
+ // ===========================
+
+ // Block time = 1000ms, Flashblock interval (fb_time) = 250ms
+ // Target: 4 flashblocks per block
+
+ // Timeline: Block starts at timestamp T, ends at T+1000ms
+ // |<------------------- block_time (1000ms) ------------------->|
+
+ // SCENARIO 1: IDEAL - FCU arrives on time (delay = 0)
+ // ─────────────────────────────────────────────────────
+ // T T+1000ms
+ // │ │
+ // FCU(a) ▼ │
+ // arrives ├────────────┬────────────┬────────────┬────────────┤
+ // │ FB 1 │ FB 2 │ FB 3 │ FB 4 │
+ // │ 250ms │ 250ms │ 250ms │ 250ms │
+ // └────────────┴────────────┴────────────┴────────────┘
+
+ // Result: 4 flashblocks, each 250ms
+
+ // SCENARIO 2: LATE FCU - delay < fb_time (e.g., delay = 100ms)
+ // ─────────────────────────────────────────────────────────────
+ // T T+1000ms
+ // │ │
+ // │ delay │ │
+ // │◄──100ms──►│ │
+ // │ ▼ FCU(a) arrives │
+ // ├───────────┼────────┬────────────┬────────────┬──────────┤
+ // │ (missed) │ FB 1 │ FB 2 │ FB 3 │ FB 4 │
+ // │ │ 150ms │ 250ms │ 250ms │ 250ms │
+ // │ │(shrunk)│ │ │ │
+ // └───────────┴────────┴────────────┴────────────┴──────────┘
+ // │◄─────── remaining time: 900ms ─────────────►│
+
+ // Result: 4 flashblocks, but FB 1 is shrunk (only 150ms)
+ // first_flashblock_offset = delay % fb_time = 100 % 250 = 100ms remaining
+
+ // SCENARIO 3: VERY LATE FCU - block_time - fb_time < delay (e.g., delay = 800ms)
+ // ──────────────────────────────────────────────────────────────────────────────
+ // T T+1000ms
+ // │ │
+ // │◄─────────────── delay (800ms) ────────────────►│ │
+ // │ ▼ FCU(a) │
+ // ├─────────────────────────────────────────────────┼────────┤
+ // │ (missed - too late) │ FB 1 │
+ // │ │ 200ms │
+ // │ │ │
+ // └─────────────────────────────────────────────────┴────────┘
+ // │◄─200ms─►│
+
+ // Result: Only 1 flashblock possible (200ms remaining < 250ms interval)
+ let target_time = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp);
+ let now = std::time::SystemTime::now();
+ let Ok(remaining_time) = target_time.duration_since(now) else {
+ error!(
+ target: "payload_builder",
+ message = "FCU arrived too late or system clock are unsynced",
+ ?target_time,
+ ?now,
+ );
+ return FlashblocksTiming {
+ flashblocks_per_block: self.config.flashblocks_per_block(),
+ first_flashblock_offset: self.config.specific.interval,
+ flashblocks_deadline: self
+ .config
+ .block_time
+ .saturating_sub(Duration::from_millis(self.config.specific.end_buffer_ms)),
+ };
+ };
+ self.metrics.flashblocks_time_drift.record(
+ self.config
+ .block_time
+ .as_millis()
+ .saturating_sub(remaining_time.as_millis()) as f64,
+ );
+ debug!(
+ target: "payload_builder",
+ message = "Time delay for building round",
+ ?target_time,
+ delay = self.config.block_time.as_millis().saturating_sub(remaining_time.as_millis()),
+ ?timestamp
+ );
+ // This is extra check to ensure that we would account at least for block time in case we have any timer discrepancies.
+ let remaining_time = remaining_time.min(self.config.block_time).as_millis() as u64;
+ let interval = self.config.specific.interval.as_millis() as u64;
+ let first_flashblock_offset = remaining_time.rem(interval);
+ let (flashblocks_per_block, offset) = if first_flashblock_offset == 0 {
+ // We have perfect division, so we use interval as first fb offset
+ (
+ remaining_time.div(interval),
+ Duration::from_millis(interval),
+ )
+ } else {
+ // Non-perfect division, set the first flashblock offset to the remainder of the division
+ (
+ remaining_time.div(interval) + 1,
+ Duration::from_millis(first_flashblock_offset),
+ )
+ };
+ // Apply send_offset_ms to the timer start time.
+ // Positive values = send later, negative values = send earlier.
+ let deadline = Duration::from_millis(
+ remaining_time.saturating_sub(self.config.specific.end_buffer_ms),
+ );
+ let (adjusted_offset, adjusted_deadline) = if self.config.specific.send_offset_ms >= 0 {
+ (
+ offset.saturating_add(Duration::from_millis(offset_delta)),
+ deadline.saturating_add(Duration::from_millis(offset_delta)),
+ )
+ } else {
+ (
+ offset.saturating_sub(Duration::from_millis(offset_delta)),
+ deadline.saturating_sub(Duration::from_millis(offset_delta)),
+ )
+ };
+ FlashblocksTiming {
+ flashblocks_per_block,
+ first_flashblock_offset: adjusted_offset,
+ flashblocks_deadline: adjusted_deadline,
+ }
+ }
}
#[async_trait::async_trait]
@@ -940,13 +1173,6 @@ where
}
}
-#[derive(Debug, Serialize, Deserialize)]
-struct FlashblocksMetadata {
- receipts: HashMap::Receipt>,
- new_account_balances: HashMap,
- block_number: u64,
-}
-
fn execute_pre_steps(
state: &mut State,
ctx: &OpPayloadBuilderCtx,
@@ -972,7 +1198,7 @@ pub(super) fn build_block(
ctx: &OpPayloadBuilderCtx,
info: &mut ExecutionInfo,
calculate_state_root: bool,
-) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError>
+) -> Result<(OpBuiltPayload, OpFlashblockPayload), PayloadBuilderError>
where
DB: Database + AsRef,
P: StateRootProvider + HashedPostStateProvider + StorageRootProvider,
@@ -1139,16 +1365,16 @@ where
let receipts_with_hash = new_transactions
.iter()
.zip(new_receipts.iter())
- .map(|(tx, receipt)| (tx.tx_hash(), receipt.clone()))
- .collect::>();
+ .map(|(tx, receipt)| (tx.tx_hash(), convert_receipt(receipt)))
+ .collect::>();
let new_account_balances = state
.bundle_state
.state
.iter()
.filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance)))
- .collect::>();
+ .collect::>();
- let metadata: FlashblocksMetadata = FlashblocksMetadata {
+ let metadata = OpFlashblockPayloadMetadata {
receipts: receipts_with_hash,
new_account_balances,
block_number: ctx.parent().number + 1,
@@ -1157,10 +1383,10 @@ where
let (_, blob_gas_used) = ctx.blob_fields(info);
// Prepare the flashblocks message
- let fb_payload = FlashblocksPayloadV1 {
+ let fb_payload = OpFlashblockPayload {
payload_id: ctx.payload_id(),
index: 0,
- base: Some(ExecutionPayloadBaseV1 {
+ base: Some(OpFlashblockPayloadBase {
parent_beacon_block_root: ctx
.attributes()
.payload_attributes
@@ -1173,9 +1399,9 @@ where
gas_limit: ctx.block_gas_limit(),
timestamp: ctx.attributes().payload_attributes.timestamp,
extra_data: ctx.extra_data()?,
- base_fee_per_gas: ctx.base_fee().try_into().unwrap(),
+ base_fee_per_gas: U256::from(ctx.base_fee()),
}),
- diff: ExecutionPayloadFlashblockDeltaV1 {
+ diff: OpFlashblockPayloadDelta {
state_root,
receipts_root,
logs_bloom,
@@ -1186,7 +1412,7 @@ where
withdrawals_root: withdrawals_root.unwrap_or_default(),
blob_gas_used,
},
- metadata: serde_json::to_value(&metadata).unwrap_or_default(),
+ metadata,
};
// We clean bundle and place initial state transaction back
diff --git a/crates/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/crates/op-rbuilder/src/builders/flashblocks/payload_handler.rs
index 96b6f683a..8613abc86 100644
--- a/crates/op-rbuilder/src/builders/flashblocks/payload_handler.rs
+++ b/crates/op-rbuilder/src/builders/flashblocks/payload_handler.rs
@@ -9,6 +9,7 @@ use alloy_evm::eth::receipt_builder::ReceiptBuilderCtx;
use alloy_primitives::B64;
use eyre::{WrapErr as _, bail};
use op_alloy_consensus::OpTxEnvelope;
+use op_alloy_rpc_types_engine::OpFlashblockPayload;
use reth::revm::{State, database::StateProviderDatabase};
use reth_basic_payload_builder::PayloadConfig;
use reth_evm::FromRecoveredTx;
@@ -19,7 +20,6 @@ use reth_optimism_node::{OpEngineTypes, OpPayloadBuilderAttributes};
use reth_optimism_payload_builder::OpBuiltPayload;
use reth_optimism_primitives::{OpReceipt, OpTransactionSigned};
use reth_payload_builder::EthPayloadBuilderAttributes;
-use rollup_boost::FlashblocksPayloadV1;
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::warn;
@@ -135,7 +135,7 @@ fn execute_flashblock(
ctx: OpPayloadSyncerCtx,
client: Client,
cancel: tokio_util::sync::CancellationToken,
-) -> eyre::Result<(OpBuiltPayload, FlashblocksPayloadV1)>
+) -> eyre::Result<(OpBuiltPayload, OpFlashblockPayload)>
where
Client: ClientBounds,
{
diff --git a/crates/op-rbuilder/src/builders/flashblocks/service.rs b/crates/op-rbuilder/src/builders/flashblocks/service.rs
index 2c1e684bc..b0563421a 100644
--- a/crates/op-rbuilder/src/builders/flashblocks/service.rs
+++ b/crates/op-rbuilder/src/builders/flashblocks/service.rs
@@ -196,9 +196,9 @@ where
if let Some(builder_signer) = signer
&& let Some(flashblocks_number_contract_address) =
- self.0.specific.flashblocks_number_contract_address
+ self.0.specific.number_contract_address
{
- let use_permit = self.0.specific.flashblocks_number_contract_use_permit;
+ let use_permit = self.0.specific.number_contract_use_permit;
self.spawn_payload_builder_service(
ctx,
pool,
diff --git a/crates/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/op-rbuilder/src/builders/flashblocks/wspub.rs
index e2f003dc1..acdbf0c28 100644
--- a/crates/op-rbuilder/src/builders/flashblocks/wspub.rs
+++ b/crates/op-rbuilder/src/builders/flashblocks/wspub.rs
@@ -5,7 +5,7 @@ use core::{
};
use futures::SinkExt;
use futures_util::StreamExt;
-use rollup_boost::FlashblocksPayloadV1;
+use op_alloy_rpc_types_engine::OpFlashblockPayload;
use std::{io, net::TcpListener, sync::Arc};
use tokio::{
net::TcpStream,
@@ -25,7 +25,7 @@ use crate::metrics::OpRBuilderMetrics;
/// A WebSockets publisher that accepts connections from client websockets and broadcasts to them
/// updates about new flashblocks. It maintains a count of sent messages and active subscriptions.
///
-/// This is modelled as a `futures::Sink` that can be used to send `FlashblocksPayloadV1` messages.
+/// This is modelled as a `futures::Sink` that can be used to send `OpFlashblockPayload` messages.
pub(super) struct WebSocketPublisher {
sent: Arc,
subs: Arc,
@@ -59,7 +59,7 @@ impl WebSocketPublisher {
})
}
- pub(super) fn publish(&self, payload: &FlashblocksPayloadV1) -> io::Result {
+ pub(super) fn publish(&self, payload: &OpFlashblockPayload) -> io::Result {
// Serialize the payload to a UTF-8 string
// serialize only once, then just copy around only a pointer
// to the serialized data for each subscription.
diff --git a/crates/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/op-rbuilder/src/flashtestations/tx_manager.rs
index d6412bf6d..a306cb1b6 100644
--- a/crates/op-rbuilder/src/flashtestations/tx_manager.rs
+++ b/crates/op-rbuilder/src/flashtestations/tx_manager.rs
@@ -66,7 +66,7 @@ impl TxManager {
attestation: Vec,
extra_registration_data: Bytes,
) -> Result<(), TxManagerError> {
- info!(target: "flashtestations", "funding TEE address at {}", self.tee_service_signer.address);
+ info!(target: "flashtestations", "registering TEE address at {}", self.tee_service_signer.address);
let quote_bytes = Bytes::from(attestation);
let wallet =
PrivateKeySigner::from_bytes(&self.builder_signer.secret.secret_bytes().into())
diff --git a/crates/op-rbuilder/src/metrics.rs b/crates/op-rbuilder/src/metrics.rs
index 5d72d9eea..75169fdf5 100644
--- a/crates/op-rbuilder/src/metrics.rs
+++ b/crates/op-rbuilder/src/metrics.rs
@@ -293,7 +293,8 @@ pub struct VersionInfo {
impl VersionInfo {
/// This exposes op-rbuilder's version information over prometheus.
pub fn register_version_metrics(&self) {
- let labels: [(&str, &str); 8] = [
+ let labels: [(&str, &str); 9] = [
+ ("builder_flavor", "op-rbuilder"),
("version", self.version),
("build_timestamp", self.build_timestamp),
("cargo_features", self.cargo_features),
diff --git a/crates/op-rbuilder/src/tests/flashblocks.rs b/crates/op-rbuilder/src/tests/flashblocks.rs
index c60e9d5ad..2947c788f 100644
--- a/crates/op-rbuilder/src/tests/flashblocks.rs
+++ b/crates/op-rbuilder/src/tests/flashblocks.rs
@@ -9,8 +9,9 @@ use std::time::Duration;
use crate::{
args::{FlashblocksArgs, OpRbuilderArgs},
tests::{
- BlockTransactionsExt, BundleOpts, ChainDriver, FLASHBLOCKS_NUMBER_ADDRESS, LocalInstance,
- TransactionBuilderExt, flashblocks_number_contract::FlashblocksNumber,
+ BlockTransactionsExt, BuilderTxValidation, BundleOpts, ChainDriver,
+ FLASHBLOCKS_NUMBER_ADDRESS, LocalInstance, TransactionBuilderExt,
+ flashblocks_number_contract::FlashblocksNumber,
},
};
@@ -43,6 +44,10 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> {
}
let block = driver.build_new_block_with_current_timestamp(None).await?;
assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn
+
+ // Validate builder transactions using BuilderTxValidation
+ block.assert_builder_tx_count(2);
+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
@@ -81,6 +86,10 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> {
}
let block = driver.build_new_block_with_current_timestamp(None).await?;
assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn
+
+ // Validate builder transactions using BuilderTxValidation
+ block.assert_builder_tx_count(2);
+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
@@ -119,6 +128,10 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> {
}
let block = driver.build_new_block().await?;
assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn
+
+ // Validate builder transactions using BuilderTxValidation
+ block.assert_builder_tx_count(2);
+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
@@ -157,6 +170,10 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> {
}
let block = driver.build_new_block().await?;
assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn
+
+ // Validate builder transactions using BuilderTxValidation
+ block.assert_builder_tx_count(2);
+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
@@ -660,3 +677,236 @@ fn verify_user_tx_hashes(
);
}
}
+
+/// Test that build_at_interval_end causes flashblocks to be built after the interval
+/// instead of immediately. With this flag enabled, the first flashblock should be
+/// delayed by first_flashblock_offset.
+#[rb_test(flashblocks, args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ flashblocks: FlashblocksArgs {
+ enabled: true,
+ flashblocks_port: 1239,
+ flashblocks_addr: "127.0.0.1".into(),
+ flashblocks_block_time: 250,
+ flashblocks_end_buffer_ms: 50,
+ flashblocks_fixed: false,
+ flashblocks_build_at_interval_end: true,
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn build_at_interval_end_basic(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let flashblocks_listener = rbuilder.spawn_flashblocks_listener();
+
+ for _ in 0..5 {
+ for _ in 0..3 {
+ let _ = driver
+ .create_transaction()
+ .random_valid_transfer()
+ .send()
+ .await?;
+ }
+ let block = driver.build_new_block_with_current_timestamp(None).await?;
+ assert_eq!(block.transactions.len(), 6); // 3 normal txn + deposit + 2 builder txn
+ tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+ }
+
+ let flashblocks = flashblocks_listener.get_flashblocks();
+ // With build_at_interval_end, the deadline_sleep controls when to stop
+ // Expected: ~4 flashblocks per block with end_buffer_ms applied at the end
+ assert_eq!(
+ 25,
+ flashblocks.len(),
+ "Expected 15 flashblocks, got {:#?}",
+ flashblocks.len()
+ );
+
+ flashblocks_listener.stop().await
+}
+
+/// Test build_at_interval_end with send_offset_ms combined
+#[rb_test(flashblocks, args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ flashblocks: FlashblocksArgs {
+ enabled: true,
+ flashblocks_port: 1239,
+ flashblocks_addr: "127.0.0.1".into(),
+ flashblocks_block_time: 250,
+ flashblocks_end_buffer_ms: 50,
+ flashblocks_fixed: false,
+ flashblocks_build_at_interval_end: true,
+ flashblocks_send_offset_ms: -25, // Send 25ms earlier
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn build_at_interval_end_with_offset(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver: ChainDriver = rbuilder.driver().await?;
+ let flashblocks_listener = rbuilder.spawn_flashblocks_listener();
+
+ for _ in 0..5 {
+ for _ in 0..3 {
+ let _ = driver
+ .create_transaction()
+ .random_valid_transfer()
+ .send()
+ .await?;
+ }
+ let block = driver.build_new_block_with_current_timestamp(None).await?;
+ assert_eq!(block.transactions.len(), 6);
+ tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+ }
+
+ let flashblocks = flashblocks_listener.get_flashblocks();
+ // Combined flags should still produce reasonable flashblock count
+ assert_eq!(
+ 25,
+ flashblocks.len(),
+ "Expected 15 flashblocks, got {:#?}",
+ flashblocks.len()
+ );
+
+ flashblocks_listener.stop().await
+}
+
+/// Test fixed mode with build_at_interval_end and end_buffer_ms
+/// This verifies that the deadline is correctly calculated with the buffer
+#[rb_test(flashblocks, args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ flashblocks: FlashblocksArgs {
+ enabled: true,
+ flashblocks_port: 1239,
+ flashblocks_addr: "127.0.0.1".into(),
+ flashblocks_block_time: 250,
+ flashblocks_leeway_time: 0,
+ flashblocks_fixed: true,
+ flashblocks_build_at_interval_end: true,
+ flashblocks_end_buffer_ms: 100, // Stop building 100ms early
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn fixed_mode_with_end_buffer(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let flashblocks_listener = rbuilder.spawn_flashblocks_listener();
+
+ for _ in 0..3 {
+ for _ in 0..2 {
+ let _ = driver
+ .create_transaction()
+ .random_valid_transfer()
+ .send()
+ .await?;
+ }
+ let block = driver.build_new_block_with_current_timestamp(None).await?;
+ assert!(block.transactions.len() >= 4); // deposit + builder tx + user txs
+ tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+ }
+
+ let flashblocks = flashblocks_listener.get_flashblocks();
+ // With end_buffer_ms=100 and interval=250, we should get 4 + 1 (fallback) flashblocks per block
+ assert_eq!(
+ 15,
+ flashblocks.len(),
+ "Expected 15 flashblocks with end_buffer, got {:#?}",
+ flashblocks.len()
+ );
+
+ flashblocks_listener.stop().await
+}
+
+/// Test dynamic mode with significant FCU delay (700ms into 1000ms block)
+/// Should produce fewer flashblocks due to less remaining time
+#[rb_test(flashblocks, args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ flashblocks: FlashblocksArgs {
+ enabled: true,
+ flashblocks_port: 1239,
+ flashblocks_addr: "127.0.0.1".into(),
+ flashblocks_block_time: 200,
+ flashblocks_end_buffer_ms: 50,
+ flashblocks_build_at_interval_end: true,
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn dynamic_mode_late_fcu_reduces_flashblocks(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let flashblocks_listener = rbuilder.spawn_flashblocks_listener();
+
+ // Send a transaction
+ let _ = driver
+ .create_transaction()
+ .random_valid_transfer()
+ .send()
+ .await?;
+
+ // Build block with 700ms delay - only 300ms remaining for a 200ms interval
+ // Should produce only 2 flashblocks
+ let block = driver
+ .build_new_block_with_current_timestamp(Some(Duration::from_millis(700)))
+ .await?;
+
+ assert!(block.transactions.len() >= 2); // deposit + at least builder tx
+
+ let flashblocks = flashblocks_listener.get_flashblocks();
+
+ // With only ~300ms remaining and 200ms interval, we should get 2 + 1 (fallback) flashblocks
+ assert_eq!(
+ 3,
+ flashblocks.len(),
+ "Expected 3 flashblocks with 700ms FCU delay, got {:#?}",
+ flashblocks.len()
+ );
+
+ flashblocks_listener.stop().await
+}
+
+/// Test with 0 end_buffer, build_at_interval_end, and negative offset
+/// Ensures all flashblocks are built when negative offset gives margin before deadline
+#[rb_test(flashblocks, args = OpRbuilderArgs {
+ chain_block_time: 1000,
+ flashblocks: FlashblocksArgs {
+ enabled: true,
+ flashblocks_port: 1239,
+ flashblocks_addr: "127.0.0.1".into(),
+ flashblocks_block_time: 250,
+ flashblocks_fixed: true,
+ flashblocks_build_at_interval_end: true,
+ flashblocks_send_offset_ms: -50, // Send 50ms earlier to ensure all FBs complete before deadline
+ ..Default::default()
+ },
+ ..Default::default()
+})]
+async fn build_at_interval_end_negative_offset(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+ let flashblocks_listener = rbuilder.spawn_flashblocks_listener();
+
+ for _ in 0..3 {
+ for _ in 0..2 {
+ let _ = driver
+ .create_transaction()
+ .random_valid_transfer()
+ .send()
+ .await?;
+ }
+ let block = driver.build_new_block_with_current_timestamp(None).await?;
+ assert!(block.transactions.len() >= 4); // deposit + builder tx + user txs
+ tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+ }
+
+ let flashblocks = flashblocks_listener.get_flashblocks();
+
+ // With 1000ms block time, 250ms interval, -50ms offset:
+ // First FB at 200ms, then 450, 700, 950ms. Deadline at 950ms.
+ // All 4 flashblocks + 1 fallback = 5 per block, 3 blocks = 15 total
+ assert_eq!(
+ 15,
+ flashblocks.len(),
+ "Expected 15 flashblocks (5 per block * 3 blocks), got {}",
+ flashblocks.len()
+ );
+
+ flashblocks_listener.stop().await
+}
diff --git a/crates/op-rbuilder/src/tests/framework/driver.rs b/crates/op-rbuilder/src/tests/framework/driver.rs
index 5246bd5e8..a9c0a8ca0 100644
--- a/crates/op-rbuilder/src/tests/framework/driver.rs
+++ b/crates/op-rbuilder/src/tests/framework/driver.rs
@@ -9,7 +9,6 @@ use op_alloy_consensus::{OpTypedTransaction, TxDeposit};
use op_alloy_network::Optimism;
use op_alloy_rpc_types::Transaction;
use reth_optimism_node::OpPayloadAttributes;
-use rollup_boost::OpExecutionPayloadEnvelope;
use super::{EngineApi, Ipc, LocalInstance, TransactionBuilder};
use crate::{
@@ -219,13 +218,8 @@ impl ChainDriver {
.await;
}
- let payload =
- OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?);
-
- let OpExecutionPayloadEnvelope::V4(payload) = payload else {
- return Err(eyre::eyre!("Expected V4 payload, got something else"));
- };
-
+ let payload: op_alloy_rpc_types_engine::OpExecutionPayloadEnvelopeV4 =
+ self.engine_api.get_payload(payload_id).await?;
let payload = payload.execution_payload;
if self
diff --git a/crates/op-rbuilder/src/tests/framework/instance.rs b/crates/op-rbuilder/src/tests/framework/instance.rs
index 52016e6b8..32bed19c3 100644
--- a/crates/op-rbuilder/src/tests/framework/instance.rs
+++ b/crates/op-rbuilder/src/tests/framework/instance.rs
@@ -30,6 +30,7 @@ use hyper_util::rt::TokioIo;
use moka::future::Cache;
use nanoid::nanoid;
use op_alloy_network::Optimism;
+use op_alloy_rpc_types_engine::OpFlashblockPayload;
use parking_lot::Mutex;
use reth::{
args::{DatadirArgs, NetworkArgs, RpcServerArgs},
@@ -45,10 +46,10 @@ use reth_optimism_node::{
};
use reth_optimism_rpc::OpEthApiBuilder;
use reth_transaction_pool::{AllTransactionsEvents, TransactionPool};
-use rollup_boost::FlashblocksPayloadV1;
use std::{
net::SocketAddr,
sync::{Arc, LazyLock},
+ time::Instant,
};
use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle};
use tokio_tungstenite::{connect_async, tungstenite::Message};
@@ -387,23 +388,32 @@ async fn spawn_attestation_provider() -> eyre::Result {
Ok(service)
}
+/// A flashblock payload with its receive timestamp
+#[derive(Debug, Clone)]
+pub struct TimestampedFlashblock {
+ pub payload: OpFlashblockPayload,
+ pub received_at: Instant,
+}
+
/// A utility for listening to flashblocks WebSocket messages during tests.
///
/// This provides a reusable way to capture and inspect flashblocks that are produced
/// during test execution, eliminating the need for duplicate WebSocket listening code.
pub struct FlashblocksListener {
- pub flashblocks: Arc>>,
+ pub flashblocks: Arc>>,
pub cancellation_token: CancellationToken,
pub handle: JoinHandle>,
+ pub start_time: Instant,
}
impl FlashblocksListener {
/// Create a new flashblocks listener that connects to the given WebSocket URL.
///
- /// The listener will automatically parse incoming messages as FlashblocksPayloadV1.
+ /// The listener will automatically parse incoming messages as OpFlashblockPayload.
fn new(flashblocks_ws_url: String) -> Self {
let flashblocks = Arc::new(Mutex::new(Vec::new()));
let cancellation_token = CancellationToken::new();
+ let start_time = Instant::now();
let flashblocks_clone = flashblocks.clone();
let cancellation_token_clone = cancellation_token.clone();
@@ -418,8 +428,12 @@ impl FlashblocksListener {
break Ok(());
}
Some(Ok(Message::Text(text))) = read.next() => {
- let fb = serde_json::from_str(&text).unwrap();
- flashblocks_clone.lock().push(fb);
+ let payload = serde_json::from_str(&text).unwrap();
+ let timestamped = TimestampedFlashblock {
+ payload,
+ received_at: Instant::now(),
+ };
+ flashblocks_clone.lock().push(timestamped);
}
}
}
@@ -429,47 +443,58 @@ impl FlashblocksListener {
flashblocks,
cancellation_token,
handle,
+ start_time,
}
}
- /// Get a snapshot of all received flashblocks
- pub fn get_flashblocks(&self) -> Vec {
+ /// Get a snapshot of all received flashblocks (payloads only)
+ pub fn get_flashblocks(&self) -> Vec {
+ self.flashblocks
+ .lock()
+ .iter()
+ .map(|tf| tf.payload.clone())
+ .collect()
+ }
+
+ /// Get a snapshot of all received flashblocks with timestamps
+ pub fn get_timestamped_flashblocks(&self) -> Vec {
self.flashblocks.lock().clone()
}
+ /// Get the time elapsed since the listener was started for each flashblock
+ pub fn get_flashblock_timings(&self) -> Vec {
+ self.flashblocks
+ .lock()
+ .iter()
+ .map(|tf| tf.received_at.duration_since(self.start_time))
+ .collect()
+ }
+
/// Find a flashblock by index
- pub fn find_flashblock(&self, index: u64) -> Option {
+ pub fn find_flashblock(&self, index: u64) -> Option {
self.flashblocks
.lock()
.iter()
- .find(|fb| fb.index == index)
- .cloned()
+ .find(|tf| tf.payload.index == index)
+ .map(|tf| tf.payload.clone())
}
/// Check if any flashblock contains the given transaction hash
pub fn contains_transaction(&self, tx_hash: &B256) -> bool {
- let tx_hash_str = format!("{tx_hash:#x}");
- self.flashblocks.lock().iter().any(|fb| {
- if let Some(receipts) = fb.metadata.get("receipts")
- && let Some(receipts_obj) = receipts.as_object()
- {
- return receipts_obj.contains_key(&tx_hash_str);
- }
- false
- })
+ self.flashblocks
+ .lock()
+ .iter()
+ .any(|fb| fb.payload.metadata.receipts.contains_key(tx_hash))
}
/// Find which flashblock index contains the given transaction hash
pub fn find_transaction_flashblock(&self, tx_hash: &B256) -> Option {
- let tx_hash_str = format!("{tx_hash:#x}");
self.flashblocks.lock().iter().find_map(|fb| {
- if let Some(receipts) = fb.metadata.get("receipts")
- && let Some(receipts_obj) = receipts.as_object()
- && receipts_obj.contains_key(&tx_hash_str)
- {
- return Some(fb.index);
- }
- None
+ fb.payload
+ .metadata
+ .receipts
+ .contains_key(tx_hash)
+ .then_some(fb.payload.index)
})
}
diff --git a/crates/op-rbuilder/src/tests/framework/utils.rs b/crates/op-rbuilder/src/tests/framework/utils.rs
index 99772de19..1a1658c8d 100644
--- a/crates/op-rbuilder/src/tests/framework/utils.rs
+++ b/crates/op-rbuilder/src/tests/framework/utils.rs
@@ -9,6 +9,7 @@ use crate::{
tx_signer::Signer,
};
use alloy_eips::Encodable2718;
+use alloy_network::TransactionResponse;
use alloy_primitives::{Address, B256, BlockHash, TxHash, TxKind, U256, hex};
use alloy_rpc_types_eth::{Block, BlockTransactionHashes};
use alloy_sol_types::SolCall;
@@ -272,10 +273,66 @@ impl ChainDriverExt for ChainDriver {
}
}
+/// Result of builder transaction validation in a block.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BuilderTxInfo {
+ /// Number of builder transactions found in the block.
+ pub count: usize,
+ /// Indices of builder transactions within the block.
+ pub indices: Vec,
+}
+
+impl BuilderTxInfo {
+ /// Returns true if the block contains at least one builder transaction.
+ pub fn has_builder_tx(&self) -> bool {
+ self.count > 0
+ }
+}
+
pub trait BlockTransactionsExt {
fn includes(&self, txs: &impl AsTxs) -> bool;
}
+/// Extension trait for validating builder transactions in blocks.
+pub trait BuilderTxValidation {
+ /// Checks if the block contains builder transactions from the configured builder address.
+ /// Returns information about builder transactions found in the block.
+ fn find_builder_txs(&self) -> BuilderTxInfo;
+
+ /// Returns true if the block contains at least one builder transaction.
+ fn has_builder_tx(&self) -> bool {
+ self.find_builder_txs().has_builder_tx()
+ }
+
+ /// Asserts that the block contains exactly the expected number of builder transactions.
+ fn assert_builder_tx_count(&self, expected: usize) {
+ let info = self.find_builder_txs();
+ assert_eq!(
+ info.count, expected,
+ "Expected {} builder transaction(s), found {} at indices {:?}",
+ expected, info.count, info.indices
+ );
+ }
+}
+
+impl BuilderTxValidation for Block {
+ fn find_builder_txs(&self) -> BuilderTxInfo {
+ let builder_address = builder_signer().address;
+ let mut indices = Vec::new();
+
+ for (idx, tx) in self.transactions.txns().enumerate() {
+ if tx.from() == builder_address {
+ indices.push(idx);
+ }
+ }
+
+ BuilderTxInfo {
+ count: indices.len(),
+ indices,
+ }
+ }
+}
+
impl BlockTransactionsExt for Block {
fn includes(&self, txs: &impl AsTxs) -> bool {
txs.as_txs()
diff --git a/crates/op-rbuilder/src/tests/revert.rs b/crates/op-rbuilder/src/tests/revert.rs
index 76e1c5b9b..8ed792c9d 100644
--- a/crates/op-rbuilder/src/tests/revert.rs
+++ b/crates/op-rbuilder/src/tests/revert.rs
@@ -6,8 +6,8 @@ use crate::{
args::OpRbuilderArgs,
primitives::bundle::MAX_BLOCK_RANGE_BLOCKS,
tests::{
- BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, LocalInstance, ONE_ETH,
- OpRbuilderArgsTestExt, TransactionBuilderExt,
+ BlockTransactionsExt, BuilderTxValidation, BundleOpts, ChainDriver, ChainDriverExt,
+ LocalInstance, ONE_ETH, OpRbuilderArgsTestExt, TransactionBuilderExt,
},
};
@@ -53,6 +53,14 @@ async fn monitor_transaction_gc(rbuilder: LocalInstance) -> eyre::Result<()> {
assert_eq!(generated_block.transactions.len(), 3);
}
+ // Validate builder transactions using BuilderTxValidation
+ if_standard! {
+ generated_block.assert_builder_tx_count(1);
+ }
+ if_flashblocks! {
+ generated_block.assert_builder_tx_count(2);
+ }
+
// since we created the 10 transactions with increasing block ranges, as we generate blocks
// one transaction will be gc on each block.
// transactions from [0, i] should be dropped, transactions from [i+1, 10] should be queued
@@ -88,6 +96,9 @@ async fn disabled(rbuilder: LocalInstance) -> eyre::Result<()> {
assert!(block.includes(valid_tx.tx_hash()));
assert!(block.includes(reverting_tx.tx_hash()));
+
+ // Validate builder transactions are present
+ assert!(block.has_builder_tx());
}
Ok(())
@@ -396,6 +407,9 @@ async fn allow_reverted_transactions_without_bundle(rbuilder: LocalInstance) ->
assert!(block.includes(valid_tx.tx_hash()));
assert!(block.includes(reverting_tx.tx_hash()));
+
+ // Validate builder transactions are present
+ assert!(block.has_builder_tx());
}
Ok(())
diff --git a/crates/op-rbuilder/src/tests/smoke.rs b/crates/op-rbuilder/src/tests/smoke.rs
index 63cfa77b7..c379b51e8 100644
--- a/crates/op-rbuilder/src/tests/smoke.rs
+++ b/crates/op-rbuilder/src/tests/smoke.rs
@@ -1,6 +1,6 @@
use crate::{
args::OpRbuilderArgs,
- tests::{LocalInstance, TransactionBuilderExt},
+ tests::{BuilderTxValidation, LocalInstance, TransactionBuilderExt},
};
use alloy_primitives::TxHash;
@@ -34,6 +34,15 @@ async fn chain_produces_blocks(rbuilder: LocalInstance) -> eyre::Result<()> {
// the deposit transaction and the block generator's transaction
for _ in 0..SAMPLE_SIZE {
let block = driver.build_new_block_with_current_timestamp(None).await?;
+
+ // Validate builder transactions are present (must be done before moving transactions)
+ if_standard! {
+ block.assert_builder_tx_count(1);
+ }
+ if_flashblocks! {
+ block.assert_builder_tx_count(2);
+ }
+
let transactions = block.transactions;
if_standard! {
@@ -73,6 +82,14 @@ async fn chain_produces_blocks(rbuilder: LocalInstance) -> eyre::Result<()> {
let block = driver.build_new_block_with_current_timestamp(None).await?;
+ // Validate builder transactions are present (must be done before moving transactions)
+ if_standard! {
+ block.assert_builder_tx_count(1);
+ }
+ if_flashblocks! {
+ block.assert_builder_tx_count(2);
+ }
+
let txs = block.transactions;
if_standard! {
@@ -223,6 +240,15 @@ async fn chain_produces_big_tx_with_gas_limit(rbuilder: LocalInstance) -> eyre::
.expect("Failed to send transaction");
let block = driver.build_new_block_with_current_timestamp(None).await?;
+
+ // Validate builder transactions are present (must be done before moving transactions)
+ if_standard! {
+ block.assert_builder_tx_count(1);
+ }
+ if_flashblocks! {
+ block.assert_builder_tx_count(2);
+ }
+
let txs = block.transactions;
if_standard! {
@@ -272,6 +298,15 @@ async fn chain_produces_big_tx_without_gas_limit(rbuilder: LocalInstance) -> eyr
.expect("Failed to send transaction");
let block = driver.build_new_block_with_current_timestamp(None).await?;
+
+ // Validate builder transactions are present (must be done before moving transactions)
+ if_standard! {
+ block.assert_builder_tx_count(1);
+ }
+ if_flashblocks! {
+ block.assert_builder_tx_count(2);
+ }
+
let txs = block.transactions;
// assert we included the tx
@@ -296,3 +331,34 @@ async fn chain_produces_big_tx_without_gas_limit(rbuilder: LocalInstance) -> eyr
Ok(())
}
+
+/// Validates that each block contains builder transactions using the
+/// BuilderTxValidation utility.
+#[rb_test]
+async fn block_includes_builder_transaction(rbuilder: LocalInstance) -> eyre::Result<()> {
+ let driver = rbuilder.driver().await?;
+
+ const SAMPLE_SIZE: usize = 5;
+
+ for _ in 0..SAMPLE_SIZE {
+ let block = driver.build_new_block_with_current_timestamp(None).await?;
+
+ // Validate that the block contains builder transactions
+ assert!(
+ block.has_builder_tx(),
+ "Block should contain at least one builder transaction"
+ );
+
+ // Standard builder: 1 builder tx
+ // Flashblocks builder: 2 builder txs (fallback + flashblock number)
+ if_standard! {
+ block.assert_builder_tx_count(1);
+ }
+
+ if_flashblocks! {
+ block.assert_builder_tx_count(2);
+ }
+ }
+
+ Ok(())
+}