diff --git a/Cargo.lock b/Cargo.lock index 54e1373c..00595b1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ea09cffa9ad82f6404e6ab415ea0c41a7674c0f2e2e689cb8683f772b5940d" +checksum = "8e30ab0d3e3c32976f67fc1a96179989e45a69594af42003a6663332f9b0bb9d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aafa1f0ddb5cbb6cba6b10e8fa6e31f8c5d5c22e262b30a5d2fa9d336c3b637" +checksum = "c20736b1f9d927d875d8777ef0c2250d4c57ea828529a9dbfa2c628db57b911e" dependencies = [ "alloy-consensus", "alloy-eips", @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398c81368b864fdea950071a00b298c22b21506fed1ed8abc7f2902727f987f1" +checksum = "008aba161fce2a0d94956ae09d7d7a09f8fbdf18acbef921809ef126d6cdaf97" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -241,9 +241,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "691fed81bbafefae0f5a6cedd837ebb3fade46e7d91c5b67a463af12ecf5b11a" +checksum = "15b85157b7be31fc4adf6acfefcb0d4308cba5dbd7a8d8e62bcc02ff37d6131a" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -290,9 +290,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf91e325928dfffe90c769c2c758cc6e9ba35331c6e984310fe8276548df4a9e" +checksum = "a838301c4e2546c96db1848f18ffe9f722f2fccd9715b83d4bf269a2cf00b5a1" dependencies = [ "alloy-eips", "alloy-primitives", @@ -344,9 +344,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8618cd8431d82d21ed98c300b6072f73fe925dff73b548aa2d4573b5a8d3ca91" +checksum = "60f045b69b5e80b8944b25afe74ae6b974f3044d84b4a7a113da04745b2524cc" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390641d0e7e51d5d39b905be654ef391a89d62b9e6d3a74fd931b4df26daae20" +checksum = "2b314ed5bdc7f449c53853125af2db5ac4d3954a9f4b205e7d694f02fc1932d1" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -385,9 +385,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9badd9de9f310f0c17602c642c043eee40033c0651f45809189e411f6b166e0f" +checksum = "5e9762ac5cca67b0f6ab614f7f8314942eead1c8eeef61511ea43a6ff048dbe0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -471,9 +471,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7dcf6452993e31ea728b9fc316ebe4e4e3a820c094f2aad55646041ee812a0" +checksum = "ea8f7ca47514e7f552aa9f3f141ab17351332c6637e3bf00462d8e7c5f10f51f" dependencies = [ "alloy-chains", "alloy-consensus", @@ -515,9 +515,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "040dabce173e246b9522cf189db8e383c811b89cf6bd07a6ab952ec3b822a1e6" +checksum = "4082778c908aa801a1f9fdc85d758812842ab4b2aaba58e9dbe7626d708ab7e1" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce4a28b1302733f565a2900a0d7cb3db94ffd1dd58ad7ebf5b0ec302e868ed1e" +checksum = "26dd083153d2cb73cce1516f5a3f9c3af74764a2761d901581a355777468bd8f" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -585,9 +585,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1408505e2a41c71f7b3f83ee52e5ecd0f2a6f2db98046d0a4defb9f85a007a9e" +checksum = "8c998214325cfee1fbe61e5abaed3a435f4ca746ac7399b46feb57c364552452" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -598,9 +598,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee46cb2875073395f936482392d63f8128f1676a788762468857bd81390f8a4" +checksum = "730a38742dc0753f25b8ce7330c2fa88d79f165c5fc2f19f3d35291739c42e83" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -610,9 +610,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456a35438dc5631320a747466a0366bf21b03494fc2e33ac903c128504a68edf" +checksum = "a2b03d65fcf579fbf17d3aac32271f99e2b562be04097436cd6e766b3e06613b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -622,9 +622,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6792425a4a8e74be38e8785f90f497f8f325188f40f13c168a220310fd421d12" +checksum = "4b4a6f49d161ef83354d5ba3c8bc83c8ee464cb90182b215551d5c4b846579be" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -633,9 +633,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e181ada2cd52aaad734a03a541e2ccc5a6198eb5b011843c41b0d6c0d245f5" +checksum = "3b6654644613f33fd2e6f333f4ce8ad0a26f036c0513699d7bc168bba18d412d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -653,9 +653,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f72b891c28aa7376f7e4468c40d2bdcc1013ab47ceae57a2696e78b0cd1e8341" +checksum = "467025b916f32645f322a085d0017f2996d0200ac89dd82a4fc2bf0f17b9afa3" dependencies = [ "alloy-primitives", "derive_more", @@ -665,9 +665,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7bcd9ead89076095806364327a1b18c2215998b6fff5a45f82c658bfbabf2df" +checksum = "933aaaace9faa6d7efda89472add89a8bfd15270318c47a2be8bb76192c951e2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -685,9 +685,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b505d6223c88023fb1217ac24eab950e4368f6634405bea3977d34cae6935b" +checksum = "11920b16ab7c86052f990dcb4d25312fb2889faf506c4ee13dc946b450536989" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -707,9 +707,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6c1a9891c2fe0582fe19dda5064e7ad8f21762ed51731717cce676193b3baa" +checksum = "1826454c2890af6d642bf052909e0162ad7f261d172e56ef2e936d479960699c" dependencies = [ "alloy-consensus", "alloy-eips", @@ -722,9 +722,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca8db59fa69da9da5bb6b75823c2b07c27b0f626a0f3af72bac32a7c361a418" +checksum = "498375e6a13b6edd04422a13d2b1a6187183e5a3aa14c5907b4c566551248bab" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -736,9 +736,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14194567368b8c8b7aeef470831bbe90cc8b12ef5f48b18acdda9cf20070ff1" +checksum = "6d9123d321ecd70925646eb2c60b1d9b7a965f860fbd717643e2c20fcf85d48d" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -748,9 +748,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a755a3cc0297683c2879bbfe2ff22778f35068f07444f0b52b5b87570142b6" +checksum = "d1a0d2d5c64881f3723232eaaf6c2d9f4f88b061c63e87194b2db785ff3aa31f" dependencies = [ "alloy-primitives", "arbitrary", @@ -760,9 +760,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d73afcd1fb2d851bf4ba67504a951b73231596f819cc814f50d11126db7ac1b" +checksum = "5ea4ac9765e5a7582877ca53688e041fe184880fe75f16edf0945b24a319c710" dependencies = [ "alloy-primitives", "async-trait", @@ -775,9 +775,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807b043936012acc788c96cba06b8580609d124bb105dc470a1617051cc4aa63" +checksum = "3c9d85b9f7105ab5ce7dae7b0da33cd9d977601a48f759e1c82958978dd1a905" dependencies = [ "alloy-consensus", "alloy-network", @@ -867,9 +867,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b84a605484a03959436e5bea194e6d62f77c3caef750196b4b4f1c8d23254df" +checksum = "4e72f5c4ba505ebead6a71144d72f21a70beadfb2d84e0a560a985491ecb71de" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -890,9 +890,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a400ad5b73590a099111481d4a66a2ca1266ebc85972a844958caf42bfdd37d" +checksum = "400dc298aaabdbd48be05448c4a19eaa38416c446043f3e54561249149269c32" dependencies = [ "alloy-json-rpc", "alloy-rpc-types-engine", @@ -914,9 +914,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74adc2ef0cb8c2cad4de2044afec2d4028061bc016148a251704dc204f259477" +checksum = "ba22ff961cf99495ee4fdbaf4623f8d5483d408ca2c6e1b1a54ef438ca87f8dd" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -934,9 +934,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2c1672b97fef0057f3ca268507fb4f1bc59497531603f39ccaf47cc1e5b9cb4" +checksum = "c38b4472f2bbd96a27f393de9e2f12adca0dc1075fb4d0f7c8f3557c5c600392" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -971,9 +971,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17272de4df6b8b59889b264f0306eba47a69f23f57f1c08f1366a4617b48c30" +checksum = "e2183706e24173309b0ab0e34d3e53cf3163b71a419803b2b3b0c1fb7ff7a941" dependencies = [ "darling 0.21.3", "proc-macro2", diff --git a/Justfile b/Justfile index f569b5e0..ef8c43f2 100644 --- a/Justfile +++ b/Justfile @@ -1,4 +1,5 @@ -set positional-arguments +set positional-arguments := true + alias t := test alias f := fix alias b := build @@ -18,31 +19,31 @@ ci: fix check lychee zepter # Performs lychee checks, installing the lychee command if necessary lychee: - @command -v lychee >/dev/null 2>&1 || cargo install lychee - lychee --config ./lychee.toml . + @command -v lychee >/dev/null 2>&1 || cargo install lychee + lychee --config ./lychee.toml . # Checks formatting, udeps, clippy, and tests check: check-format check-udeps check-clippy test check-deny # Runs cargo deny to check dependencies check-deny: - @command -v cargo-deny >/dev/null 2>&1 || cargo install cargo-deny - cargo deny check bans --hide-inclusion-graph + @command -v cargo-deny >/dev/null 2>&1 || cargo install cargo-deny + cargo deny check bans --hide-inclusion-graph # Fixes formatting and clippy issues fix: format-fix clippy-fix zepter-fix # Runs zepter feature checks, installing zepter if necessary zepter: - @command -v zepter >/dev/null 2>&1 || cargo install zepter - zepter --version - zepter format features - zepter + @command -v zepter >/dev/null 2>&1 || cargo install zepter + zepter --version + zepter format features + zepter # Fixes zepter feature formatting. zepter-fix: - @command -v zepter >/dev/null 2>&1 || cargo install zepter - zepter format features --fix + @command -v zepter >/dev/null 2>&1 || cargo install zepter + zepter format features --fix # Runs tests across workspace with all features enabled (excludes builder crates) test: build-contracts @@ -51,7 +52,7 @@ test: build-contracts # Runs cargo hack against the workspace hack: - cargo hack check --feature-powerset --no-dev-deps + cargo hack check --feature-powerset --no-dev-deps # Checks formatting (builder crates excluded via rustfmt.toml) check-format: @@ -96,8 +97,8 @@ clean: # Checks if there are any unused dependencies (excludes builder crates) check-udeps: build-contracts - @command -v cargo-udeps >/dev/null 2>&1 || cargo install cargo-udeps - cargo +nightly udeps --workspace --all-features --all-targets + @command -v cargo-udeps >/dev/null 2>&1 || cargo install cargo-udeps + cargo +nightly udeps --workspace --all-features --all-targets # Checks that shared crates don't depend on client crates check-crate-deps: @@ -144,6 +145,9 @@ test-builder: check-clippy-builder: cargo +nightly clippy -p op-rbuilder --all-features -- -D warnings +fix-clippy-builder: + cargo +nightly clippy -p op-rbuilder --all-features --fix --allow-dirty --allow-staged + # Fixes formatting for builder crates format-builder: cargo +nightly fmt -p op-rbuilder diff --git a/bin/builder/src/main.rs b/bin/builder/src/main.rs index ee94136c..2546dca1 100644 --- a/bin/builder/src/main.rs +++ b/bin/builder/src/main.rs @@ -4,55 +4,11 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] use eyre::Result; -use op_rbuilder::{ - args::CliExt, - builders::{BuilderMode, FlashblocksBuilder, StandardBuilder}, - launcher::BuilderLauncher, -}; +use op_rbuilder::launcher::launch; #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); fn main() -> Result<()> { - // Parse CLI arguments with builder-specific configuration - let cli = op_rbuilder::args::Cli::parsed(); - let mode = cli.builder_mode(); - - // Extract telemetry args before configuring CLI (if telemetry feature enabled) - #[cfg(feature = "telemetry")] - let telemetry_args = match &cli.command { - reth_optimism_cli::commands::Commands::Node(node_command) => { - node_command.ext.telemetry.clone() - } - _ => Default::default(), - }; - - // Configure CLI application - #[cfg(not(feature = "telemetry"))] - let cli_app = cli.configure(); - - #[cfg(feature = "telemetry")] - let mut cli_app = cli.configure(); - #[cfg(feature = "telemetry")] - { - use op_rbuilder::primitives::telemetry::setup_telemetry_layer; - let telemetry_layer = setup_telemetry_layer(&telemetry_args)?; - cli_app.access_tracing_layers()?.add_layer(telemetry_layer); - } - - // Launch the builder with the appropriate mode - match mode { - BuilderMode::Standard => { - tracing::info!("Starting OP builder in standard mode"); - let launcher = BuilderLauncher::::new(); - cli_app.run(launcher)?; - } - BuilderMode::Flashblocks => { - tracing::info!("Starting OP builder in flashblocks mode"); - let launcher = BuilderLauncher::::new(); - cli_app.run(launcher)?; - } - } - - Ok(()) + launch() } diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index b9296eee..32180fb6 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -3,26 +3,20 @@ pub use op::{FlashblocksArgs, OpRbuilderArgs, TelemetryArgs}; use playground::PlaygroundOptions; use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; -use crate::{ - builders::BuilderMode, - metrics::{LONG_VERSION, SHORT_VERSION}, -}; +use crate::metrics::{LONG_VERSION, SHORT_VERSION}; mod op; mod playground; /// This trait is used to extend Reth's CLI with additional functionality that /// are specific to the OP builder, such as populating default values for CLI arguments -/// when running in the playground mode or checking the builder mode. +/// when running in the playground mode. /// pub trait CliExt { /// Populates the default values for the CLI arguments when the user specifies /// the `--builder.playground` flag. fn populate_defaults(self) -> Self; - /// Returns the builder mode that the node is started with. - fn builder_mode(&self) -> BuilderMode; - /// Returns the Cli instance with the parsed command line arguments /// and defaults populated if applicable. fn parsed() -> Self; @@ -66,17 +60,6 @@ impl CliExt for Cli { Self::set_version().populate_defaults() } - /// Returns the type of builder implementation that the node is started with. - /// Currently supports `Standard` and `Flashblocks` modes. - fn builder_mode(&self) -> BuilderMode { - if let Commands::Node(ref node_command) = self.command - && node_command.ext.flashblocks.enabled - { - return BuilderMode::Flashblocks; - } - BuilderMode::Standard - } - /// Parses commands and overrides versions fn set_version() -> Self { let logs_dir = dirs_next::cache_dir() diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 082b3644..7f8cafa1 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -80,18 +80,11 @@ fn expand_path(s: &str) -> Result { /// Parameters for Flashblocks configuration /// The names in the struct are prefixed with `flashblocks` to avoid conflicts -/// with the standard block building configuration since these args are flattened -/// into the main `OpRbuilderArgs` struct with the other rollup/node args. +/// with the legacy standard builder configuration (now removed) since these args are +/// flattened into the main `OpRbuilderArgs` struct with the other rollup/node args. #[derive(Debug, Clone, PartialEq, Eq, clap::Args)] pub struct FlashblocksArgs { - /// When set to true, the builder will build flashblocks - /// and will build standard blocks at the chain block time. - /// - /// The default value will change in the future once the flashblocks - /// feature is stable. - #[arg(long = "flashblocks.enabled", default_value = "false", env = "ENABLE_FLASHBLOCKS")] - pub enabled: bool, - + /// Flashblocks is always enabled; these options tune its behavior. /// The port that we bind to for the websocket server that provides flashblocks #[arg(long = "flashblocks.port", env = "FLASHBLOCKS_WS_PORT", default_value = "1111")] pub flashblocks_port: u16, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs deleted file mode 100644 index cb1835e6..00000000 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ /dev/null @@ -1,197 +0,0 @@ -use core::fmt::Debug; - -use alloy_eips::Encodable2718; -use alloy_evm::{Database, Evm}; -use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, B256}; -use alloy_rpc_types_eth::TransactionInput; -use alloy_sol_types::{SolCall, SolEvent, sol}; -use op_alloy_rpc_types::OpTransactionRequest; -use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap}; -use reth_revm::State; -use revm::{DatabaseRef, inspector::NoOpInspector}; -use tracing::warn; - -use crate::{ - builders::{ - BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - SimulationSuccessResult, - builder_tx::BuilderTxBase, - context::OpPayloadBuilderCtx, - flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, - get_nonce, - }, - tx_signer::Signer, -}; - -sol!( - // From https://github.com/Uniswap/flashblocks_number_contract/blob/main/src/FlashblockNumber.sol - #[sol(rpc, abi)] - #[derive(Debug)] - interface IFlashblockNumber { - uint256 public flashblockNumber; - - function incrementFlashblockNumber() external; - - function permitIncrementFlashblockNumber(uint256 currentFlashblockNumber, bytes memory signature) external; - - function computeStructHash(uint256 currentFlashblockNumber) external pure returns (bytes32); - - function hashTypedDataV4(bytes32 structHash) external view returns (bytes32); - - - // @notice Emitted when flashblock index is incremented - // @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block) - event FlashblockIncremented(uint256 newFlashblockIndex); - - /// ----------------------------------------------------------------------- - /// Errors - /// ----------------------------------------------------------------------- - error NonBuilderAddress(address addr); - error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber); - } -); - -// This will be the end of block transaction of a regular block -#[derive(Debug, Clone)] -pub(super) struct FlashblocksBuilderTx { - pub base_builder_tx: BuilderTxBase, -} - -impl FlashblocksBuilderTx { - pub(super) const fn new(signer: Option) -> Self { - let base_builder_tx = BuilderTxBase::new(signer); - Self { base_builder_tx } - } -} - -impl BuilderTransactions for FlashblocksBuilderTx { - fn simulate_builder_txs( - &self, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result, BuilderTransactionError> { - let mut builder_txs = Vec::::new(); - - if ctx.is_first_flashblock() { - let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; - builder_txs.extend(flashblocks_builder_tx); - } - - if ctx.is_last_flashblock() { - let base_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; - builder_txs.extend(base_tx); - } - Ok(builder_txs) - } -} - -// This will be the end of block transaction of a regular block -#[derive(Debug, Clone)] -pub(super) struct FlashblocksNumberBuilderTx { - pub signer: Signer, - pub flashblock_number_address: Address, - pub base_builder_tx: BuilderTxBase, -} - -impl FlashblocksNumberBuilderTx { - pub(super) const fn new(signer: Signer, flashblock_number_address: Address) -> Self { - let base_builder_tx = BuilderTxBase::new(Some(signer)); - Self { signer, flashblock_number_address, base_builder_tx } - } - - fn signed_increment_flashblocks_tx( - &self, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm, - ) -> Result { - let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}; - self.increment_flashblocks_tx(calldata, ctx, evm) - } - - fn increment_flashblocks_tx( - &self, - calldata: T, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm, - ) -> Result { - let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call( - calldata.clone(), - vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH], - ctx, - evm, - )?; - let signed_tx = self.sign_tx( - self.flashblock_number_address, - self.signer, - gas_used, - calldata.abi_encode().into(), - ctx, - evm.db_mut(), - )?; - let da_size = - op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); - Ok(BuilderTransactionCtx { signed_tx, gas_used, da_size, is_top_of_block: true }) - } - - fn simulate_flashblocks_call( - &self, - calldata: T, - expected_logs: Vec, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm, - ) -> Result, BuilderTransactionError> { - let tx_req = OpTransactionRequest::default() - .gas_limit(ctx.block_gas_limit()) - .max_fee_per_gas(ctx.base_fee().into()) - .to(self.flashblock_number_address) - .from(self.signer.address) - .nonce(get_nonce(evm.db(), self.signer.address)?) - .input(TransactionInput::new(calldata.abi_encode().into())); - self.simulate_call::( - tx_req, - expected_logs, - evm, - ) - } -} - -impl BuilderTransactions - for FlashblocksNumberBuilderTx -{ - fn simulate_builder_txs( - &self, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result, BuilderTransactionError> { - let mut builder_txs = Vec::::new(); - - if ctx.is_first_flashblock() { - // fallback block builder tx - builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?); - } else { - // we increment the flashblock number for the next flashblock so we don't increment in the last flashblock - let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); - evm.modify_cfg(|cfg| { - cfg.disable_balance_check = true; - cfg.disable_block_gas_limit = true; - }); - - let flashblocks_num_tx = self.signed_increment_flashblocks_tx(ctx, &mut evm); - - let tx = match flashblocks_num_tx { - Ok(tx) => Some(tx), - Err(e) => { - warn!(target: "builder_tx", error = ?e, "flashblocks number contract tx simulation failed, defaulting to fallback builder tx"); - self.base_builder_tx - .simulate_builder_tx(ctx, &mut *db)? - .map(|tx| tx.set_top_of_block()) - } - }; - - builder_txs.extend(tx); - } - - Ok(builder_txs) - } -} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs deleted file mode 100644 index 03e89ab8..00000000 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -use config::FlashblocksConfig; -use service::FlashblocksServiceBuilder; - -use super::BuilderConfig; -use crate::traits::{NodeBounds, PoolBounds}; - -mod best_txs; -mod builder_tx; -mod config; -mod ctx; -mod payload; -mod payload_handler; -mod service; -mod wspub; - -/// Block building strategy that progressively builds chunks of a block and makes them available -/// through a websocket update, then merges them into a full block every chain block time. -#[derive(Debug)] -pub struct FlashblocksBuilder; - -impl super::PayloadBuilder for FlashblocksBuilder { - type Config = FlashblocksConfig; - - type ServiceBuilder - = FlashblocksServiceBuilder - where - Node: NodeBounds, - Pool: PoolBounds; - - fn new_service( - config: BuilderConfig, - ) -> eyre::Result> - where - Node: NodeBounds, - Pool: PoolBounds, - { - Ok(FlashblocksServiceBuilder(config)) - } -} diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs deleted file mode 100644 index 8fdb6026..00000000 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ /dev/null @@ -1,199 +0,0 @@ -use core::{ - convert::{Infallible, TryFrom}, - fmt::Debug, - time::Duration, -}; - -use reth_node_builder::components::PayloadServiceBuilder; -use reth_optimism_evm::OpEvmConfig; -use reth_optimism_payload_builder::config::{OpDAConfig, OpGasLimitConfig}; - -use crate::{ - args::OpRbuilderArgs, - gas_limiter::args::GasLimiterArgs, - traits::{NodeBounds, PoolBounds}, - tx_signer::Signer, -}; - -mod builder_tx; -mod context; -mod flashblocks; -mod generator; -mod standard; - -pub use builder_tx::{ - BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, InvalidContractDataError, - SimulationSuccessResult, get_balance, get_nonce, -}; -pub use context::OpPayloadBuilderCtx; -pub use flashblocks::FlashblocksBuilder; -pub use standard::StandardBuilder; - -use crate::tx_data_store::TxDataStore; - -/// Defines the payload building mode for the OP builder. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum BuilderMode { - /// Uses the plain OP payload builder that produces blocks every chain blocktime. - #[default] - Standard, - /// Uses the flashblocks payload builder that progressively builds chunks of a - /// block every short interval and makes it available through a websocket update - /// then merges them into a full block every chain block time. - Flashblocks, -} - -/// Defines the interface for any block builder implementation API entry point. -/// -/// Instances of this trait are used during Reth node construction as an argument -/// to the `NodeBuilder::with_components` method to construct the payload builder -/// service that gets called whenver the current node is asked to build a block. -pub trait PayloadBuilder: Send + Sync + 'static { - /// The type that has an implementation specific variant of the Config struct. - /// This is used to configure the payload builder service during startup. - type Config: TryFrom + Clone + Debug + Send + Sync + 'static; - - /// The type that is used to instantiate the payload builder service - /// that will be used by reth to build blocks whenever the node is - /// asked to do so. - type ServiceBuilder: PayloadServiceBuilder - where - Node: NodeBounds, - Pool: PoolBounds; - - /// Called during node startup by reth. Returns a [`PayloadBuilderService`] instance - /// that is preloaded with a [`PayloadJobGenerator`] instance specific to the builder - /// type. - fn new_service( - config: BuilderConfig, - ) -> eyre::Result> - where - Node: NodeBounds, - Pool: PoolBounds; -} - -/// Configuration values that are applicable to any type of block builder. -#[derive(Clone)] -pub struct BuilderConfig { - /// Secret key of the builder that is used to sign the end of block transaction. - pub builder_signer: Option, - - /// The interval at which blocks are added to the chain. - /// This is also the frequency at which the builder will be receiving FCU requests from the - /// sequencer. - pub block_time: Duration, - - /// Data Availability configuration for the OP builder - /// Defines constraints for the maximum size of data availability transactions. - pub da_config: OpDAConfig, - - /// Gas limit configuration for the payload builder - pub gas_limit_config: OpGasLimitConfig, - - // The deadline is critical for payload availability. If we reach the deadline, - // the payload job stops and cannot be queried again. With tight deadlines close - // to the block number, we risk reaching the deadline before the node queries the payload. - // - // Adding 0.5 seconds as wiggle room since block times are shorter here. - // TODO: A better long-term solution would be to implement cancellation logic - // that cancels existing jobs when receiving new block building requests. - // - // When batcher's max channel duration is big enough (e.g. 10m), the - // sequencer would send an avalanche of FCUs/getBlockByNumber on - // each batcher update (with 10m channel it's ~800 FCUs at once). - // At such moment it can happen that the time b/w FCU and ensuing - // getPayload would be on the scale of ~2.5s. Therefore we should - // "remember" the payloads long enough to accommodate this corner-case - // (without it we are losing blocks). Postponing the deadline for 5s - // (not just 0.5s) because of that. - pub block_time_leeway: Duration, - - /// Inverted sampling frequency in blocks. 1 - each block, 100 - every 100th block. - pub sampling_ratio: u64, - - /// Configuration values that are specific to the block builder implementation used. - pub specific: Specific, - - /// Maximum gas a transaction can use before being excluded. - pub max_gas_per_txn: Option, - - /// Address gas limiter stuff - pub gas_limiter_config: GasLimiterArgs, - - /// Unified transaction data store (backrun bundles + resource metering) - pub tx_data_store: TxDataStore, -} - -impl core::fmt::Debug for BuilderConfig { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Config") - .field( - "builder_signer", - &self - .builder_signer - .as_ref() - .map_or_else(|| "None".into(), |signer| signer.address.to_string()), - ) - .field("block_time", &self.block_time) - .field("block_time_leeway", &self.block_time_leeway) - .field("da_config", &self.da_config) - .field("gas_limit_config", &self.gas_limit_config) - .field("sampling_ratio", &self.sampling_ratio) - .field("specific", &self.specific) - .field("max_gas_per_txn", &self.max_gas_per_txn) - .field("gas_limiter_config", &self.gas_limiter_config) - .field("tx_data_store", &self.tx_data_store) - .finish() - } -} - -impl Default for BuilderConfig { - fn default() -> Self { - Self { - builder_signer: None, - block_time: Duration::from_secs(2), - block_time_leeway: Duration::from_millis(500), - da_config: OpDAConfig::default(), - gas_limit_config: OpGasLimitConfig::default(), - specific: S::default(), - sampling_ratio: 100, - max_gas_per_txn: None, - gas_limiter_config: GasLimiterArgs::default(), - tx_data_store: TxDataStore::default(), - } - } -} - -impl TryFrom for BuilderConfig -where - S: TryFrom + Clone, -{ - type Error = S::Error; - - fn try_from(args: OpRbuilderArgs) -> Result { - Ok(Self { - builder_signer: args.builder_signer, - block_time: Duration::from_millis(args.chain_block_time), - block_time_leeway: Duration::from_secs(args.extra_block_deadline_secs), - da_config: Default::default(), - gas_limit_config: Default::default(), - sampling_ratio: args.telemetry.sampling_ratio, - max_gas_per_txn: args.max_gas_per_txn, - gas_limiter_config: args.gas_limiter.clone(), - tx_data_store: TxDataStore::new( - args.enable_resource_metering, - args.tx_data_store_buffer_size, - ), - specific: S::try_from(args)?, - }) - } -} - -#[expect(clippy::infallible_try_from)] -impl TryFrom for () { - type Error = Infallible; - - fn try_from(_: OpRbuilderArgs) -> Result { - Ok(()) - } -} diff --git a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs deleted file mode 100644 index 501defd5..00000000 --- a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs +++ /dev/null @@ -1,39 +0,0 @@ -use core::fmt::Debug; - -use alloy_evm::Database; -use reth_revm::State; -use revm::DatabaseRef; - -use crate::{ - builders::{ - BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - builder_tx::BuilderTxBase, context::OpPayloadBuilderCtx, - }, - tx_signer::Signer, -}; - -// This will be the end of block transaction of a regular block -#[derive(Debug, Clone)] -pub(super) struct StandardBuilderTx { - pub base_builder_tx: BuilderTxBase, -} - -impl StandardBuilderTx { - pub(super) const fn new(signer: Option) -> Self { - let base_builder_tx = BuilderTxBase::new(signer); - Self { base_builder_tx } - } -} - -impl BuilderTransactions for StandardBuilderTx { - fn simulate_builder_txs( - &self, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result, BuilderTransactionError> { - let mut builder_txs = Vec::::new(); - let standard_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; - builder_txs.extend(standard_builder_tx); - Ok(builder_txs) - } -} diff --git a/crates/builder/op-rbuilder/src/builders/standard/mod.rs b/crates/builder/op-rbuilder/src/builders/standard/mod.rs deleted file mode 100644 index afe11622..00000000 --- a/crates/builder/op-rbuilder/src/builders/standard/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -use super::BuilderConfig; -use crate::{ - builders::standard::service::StandardServiceBuilder, - traits::{NodeBounds, PoolBounds}, -}; - -mod builder_tx; -mod payload; -mod service; - -/// Block building strategy that builds blocks using the standard approach by -/// producing blocks every chain block time. -#[derive(Debug)] -pub struct StandardBuilder; - -impl super::PayloadBuilder for StandardBuilder { - type Config = (); - - type ServiceBuilder - = StandardServiceBuilder - where - Node: NodeBounds, - Pool: PoolBounds; - - fn new_service( - config: BuilderConfig, - ) -> eyre::Result> - where - Node: NodeBounds, - Pool: PoolBounds, - { - Ok(StandardServiceBuilder(config)) - } -} diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs deleted file mode 100644 index d05c5ef3..00000000 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ /dev/null @@ -1,562 +0,0 @@ -use std::{sync::Arc, time::Instant}; - -use alloy_consensus::{ - BlockBody, EMPTY_OMMER_ROOT_HASH, Header, constants::EMPTY_WITHDRAWALS, proofs, -}; -use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; -use alloy_evm::Database; -use alloy_primitives::U256; -use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; -use reth_chain_state::ExecutedBlock; -use reth_evm::{ConfigureEvm, execute::BlockBuilder}; -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::OpTransactionSigned; -use reth_payload_primitives::PayloadBuilderAttributes; -use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; -use reth_primitives::RecoveredBlock; -use reth_primitives_traits::InMemorySize; -use reth_provider::{ExecutionOutcome, StateProvider}; -use reth_revm::{ - State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, -}; -use reth_transaction_pool::{ - BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool, -}; -use tokio_util::sync::CancellationToken; -use tracing::{error, info, warn}; - -use super::super::context::OpPayloadBuilderCtx; -use crate::{ - builders::{BuilderConfig, BuilderTransactions, generator::BuildArguments}, - gas_limiter::AddressGasLimiter, - metrics::OpRBuilderMetrics, - primitives::reth::ExecutionInfo, - traits::{ClientBounds, PayloadTxsBounds, PoolBounds}, -}; - -/// Optimism's payload builder -#[derive(Debug, Clone)] -pub(super) struct StandardOpPayloadBuilder { - /// The type responsible for creating the evm. - pub evm_config: OpEvmConfig, - /// The transaction pool - pub pool: Pool, - /// Node client - pub client: Client, - /// Settings for the builder, e.g. DA settings. - pub config: BuilderConfig<()>, - /// The type responsible for yielding the best transactions for the payload if mempool - /// transactions are allowed. - pub best_transactions: Txs, - /// The metrics for the builder - pub metrics: Arc, - /// Rate limiting based on gas. This is an optional feature. - pub address_gas_limiter: AddressGasLimiter, - /// The type responsible for creating the builder transactions - pub builder_tx: BuilderTx, -} - -impl StandardOpPayloadBuilder { - /// `OpPayloadBuilder` constructor. - pub(super) fn new( - evm_config: OpEvmConfig, - pool: Pool, - client: Client, - config: BuilderConfig<()>, - builder_tx: BuilderTx, - ) -> Self { - let address_gas_limiter = AddressGasLimiter::new(config.gas_limiter_config.clone()); - Self { - pool, - client, - config, - evm_config, - best_transactions: (), - metrics: Default::default(), - address_gas_limiter, - builder_tx, - } - } -} - -/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. -pub(super) trait OpPayloadTransactions: - Clone + Send + Sync + Unpin + 'static -{ - /// Returns an iterator that yields the transaction in the order they should get included in the - /// new payload. - fn best_transactions>( - &self, - pool: Pool, - attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions; -} - -impl OpPayloadTransactions for () { - fn best_transactions>( - &self, - pool: Pool, - attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions { - // TODO: once this issue is fixed we could remove without_updates and rely on regular impl - // https://github.com/paradigmxyz/reth/issues/17325 - BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr).without_updates()) - } -} - -impl reth_basic_payload_builder::PayloadBuilder - for StandardOpPayloadBuilder -where - Pool: PoolBounds, - Client: ClientBounds, - BuilderTx: BuilderTransactions + Clone + Send + Sync, - Txs: OpPayloadTransactions, -{ - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; - - fn try_build( - &self, - args: reth_basic_payload_builder::BuildArguments, - ) -> Result, PayloadBuilderError> { - let pool = self.pool.clone(); - - let reth_basic_payload_builder::BuildArguments { - cached_reads, - config, - cancel: _, // TODO - best_payload: _, - } = args; - - let args = BuildArguments { cached_reads, config, cancel: CancellationToken::new() }; - - self.build_payload(args, |attrs| { - #[allow(clippy::unit_arg)] - self.best_transactions.best_transactions(pool.clone(), attrs) - }) - } - - fn on_missing_payload( - &self, - _args: reth_basic_payload_builder::BuildArguments, - ) -> MissingPayloadBehaviour { - MissingPayloadBehaviour::AwaitInProgress - } - - fn build_empty_payload( - &self, - config: reth_basic_payload_builder::PayloadConfig< - Self::Attributes, - reth_basic_payload_builder::HeaderForPayload, - >, - ) -> Result { - let args = - BuildArguments { config, cached_reads: Default::default(), cancel: Default::default() }; - self.build_payload(args, |_| NoopPayloadTransactions::::default())? - .into_payload() - .ok_or_else(|| PayloadBuilderError::MissingPayload) - } -} - -impl StandardOpPayloadBuilder -where - Pool: PoolBounds, - Client: ClientBounds, - BuilderTx: BuilderTransactions + Clone, -{ - /// Constructs an Optimism payload from the transactions sent via the - /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in - /// the payload attributes, the transaction pool will be ignored and the only transactions - /// included in the payload will be those sent through the attributes. - /// - /// Given build arguments including an Optimism client, transaction pool, - /// and configuration, this function creates a transaction payload. Returns - /// a result indicating success with the payload or an error in case of failure. - fn build_payload<'a, Txs: PayloadTxsBounds>( - &self, - args: BuildArguments, OpBuiltPayload>, - best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, - ) -> Result, PayloadBuilderError> { - let block_build_start_time = Instant::now(); - - let BuildArguments { mut cached_reads, config, cancel } = args; - - let chain_spec = self.client.chain_spec(); - let timestamp = config.attributes.timestamp(); - - let extra_data = if chain_spec.is_jovian_active_at_timestamp(timestamp) { - config - .attributes - .get_jovian_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) - .map_err(PayloadBuilderError::other)? - } else if chain_spec.is_holocene_active_at_timestamp(timestamp) { - config - .attributes - .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) - .map_err(PayloadBuilderError::other)? - } else { - Default::default() - }; - - let block_env_attributes = OpNextBlockEnvAttributes { - timestamp, - suggested_fee_recipient: config.attributes.suggested_fee_recipient(), - prev_randao: config.attributes.prev_randao(), - gas_limit: config.attributes.gas_limit.unwrap_or(config.parent_header.gas_limit), - parent_beacon_block_root: config.attributes.payload_attributes.parent_beacon_block_root, - extra_data, - }; - - let evm_env = self - .evm_config - .next_evm_env(&config.parent_header, &block_env_attributes) - .map_err(PayloadBuilderError::other)?; - - let ctx = OpPayloadBuilderCtx { - evm_config: self.evm_config.clone(), - da_config: self.config.da_config.clone(), - gas_limit_config: self.config.gas_limit_config.clone(), - chain_spec, - config, - evm_env, - block_env_attributes, - cancel, - builder_signer: self.config.builder_signer, - metrics: self.metrics.clone(), - extra_ctx: Default::default(), - max_gas_per_txn: self.config.max_gas_per_txn, - address_gas_limiter: self.address_gas_limiter.clone(), - tx_data_store: self.config.tx_data_store.clone(), - }; - - let builder = OpBuilder::new(best); - - self.address_gas_limiter.refresh(ctx.block_number()); - - let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let db = StateProviderDatabase::new(&state_provider); - let metrics = ctx.metrics.clone(); - if ctx.attributes().no_tx_pool { - let state = State::builder().with_database(db).with_bundle_update().build(); - builder.build(state, &state_provider, ctx, self.builder_tx.clone()) - } else { - // sequencer mode we can reuse cachedreads from previous runs - let state = State::builder() - .with_database(cached_reads.as_db_mut(db)) - .with_bundle_update() - .build(); - builder.build(state, &state_provider, ctx, self.builder_tx.clone()) - } - .map(|out| { - let total_block_building_time = block_build_start_time.elapsed(); - metrics.total_block_built_duration.record(total_block_building_time); - metrics.total_block_built_gauge.set(total_block_building_time); - - out.with_cached_reads(cached_reads) - }) - } -} - -/// The type that builds the payload. -/// -/// Payload building for optimism is composed of several steps. -/// The first steps are mandatory and defined by the protocol. -/// -/// 1. first all System calls are applied. -/// 2. After canyon the forced deployed `create2deployer` must be loaded -/// 3. all sequencer transactions are executed (part of the payload attributes) -/// -/// Depending on whether the node acts as a sequencer and is allowed to include additional -/// transactions (`no_tx_pool == false`): -/// 4. include additional transactions -/// -/// And finally -/// 5. build the block: compute all roots (txs, state) -#[derive(derive_more::Debug)] -pub(super) struct OpBuilder<'a, Txs> { - /// Yields the best transaction to include if transactions from the mempool are allowed. - best: Box Txs + 'a>, -} - -impl<'a, Txs> OpBuilder<'a, Txs> { - fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self { - Self { best: Box::new(best) } - } -} - -/// Holds the state after execution -#[derive(Debug)] -pub(super) struct ExecutedPayload { - /// Tracked execution info - pub info: ExecutionInfo, -} - -impl OpBuilder<'_, Txs> { - /// Executes the payload and returns the outcome. - pub(crate) fn execute( - self, - state_provider: impl StateProvider, - db: &mut State, - ctx: &OpPayloadBuilderCtx, - builder_tx: BuilderTx, - ) -> Result, PayloadBuilderError> - where - BuilderTx: BuilderTransactions, - { - let Self { best } = self; - info!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); - - // 1. apply pre-execution changes - ctx.evm_config - .builder_for_next_block(db, ctx.parent(), ctx.block_env_attributes.clone()) - .map_err(PayloadBuilderError::other)? - .apply_pre_execution_changes()?; - - let sequencer_tx_start_time = Instant::now(); - - // 3. execute sequencer transactions - let mut info = ctx.execute_sequencer_transactions(db)?; - - let sequencer_tx_time = sequencer_tx_start_time.elapsed(); - ctx.metrics.sequencer_tx_duration.record(sequencer_tx_time); - ctx.metrics.sequencer_tx_gauge.set(sequencer_tx_time); - - // 4. if mem pool transactions are requested we execute them - - // gas reserved for builder tx - let builder_txs = - match builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, true) { - Ok(builder_txs) => builder_txs, - Err(e) => { - error!(target: "payload_builder", "Error adding builder txs to block: {}", e); - vec![] - } - }; - - let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); - - let block_gas_limit = ctx.block_gas_limit().saturating_sub(builder_tx_gas); - if block_gas_limit == 0 { - error!( - "Builder tx gas subtraction resulted in block gas limit to be 0. No transactions would be included" - ); - } - // Save some space in the block_da_limit for builder tx - let builder_tx_da_size = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); - let block_da_limit = ctx - .da_config - .max_da_block_size() - .map(|da_limit| { - let da_limit = da_limit.saturating_sub(builder_tx_da_size); - if da_limit == 0 { - error!("Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included."); - } - da_limit - }); - let block_da_footprint = info.da_footprint_scalar - .map(|da_footprint_scalar| { - let da_footprint_limit = ctx.block_gas_limit().saturating_sub(builder_tx_da_size.saturating_mul(da_footprint_scalar as u64)); - if da_footprint_limit == 0 { - error!("Builder tx da size subtraction caused max_da_footprint to be 0. No transaction would be included."); - } - da_footprint_limit - }); - - if !ctx.attributes().no_tx_pool { - let best_txs_start_time = Instant::now(); - let mut best_txs = best(ctx.best_transaction_attributes()); - let transaction_pool_fetch_time = best_txs_start_time.elapsed(); - ctx.metrics.transaction_pool_fetch_duration.record(transaction_pool_fetch_time); - ctx.metrics.transaction_pool_fetch_gauge.set(transaction_pool_fetch_time); - - if ctx - .execute_best_transactions( - &mut info, - db, - &mut best_txs, - block_gas_limit, - block_da_limit, - block_da_footprint, - )? - .is_some() - { - return Ok(BuildOutcomeKind::Cancelled); - } - } - - // Add builder tx to the block - if let Err(e) = builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, false) { - error!(target: "payload_builder", "Error adding builder txs to fallback block: {}", e); - }; - - let state_merge_start_time = Instant::now(); - - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - db.merge_transitions(BundleRetention::Reverts); - - let state_transition_merge_time = state_merge_start_time.elapsed(); - ctx.metrics.state_transition_merge_duration.record(state_transition_merge_time); - ctx.metrics.state_transition_merge_gauge.set(state_transition_merge_time); - - ctx.metrics.payload_num_tx.record(info.executed_transactions.len() as f64); - ctx.metrics.payload_num_tx_gauge.set(info.executed_transactions.len() as f64); - - let payload = ExecutedPayload { info }; - - ctx.metrics.block_built_success.increment(1); - Ok(BuildOutcomeKind::Better { payload }) - } - - /// Builds the payload on top of the state. - pub(super) fn build( - self, - state: impl Database, - state_provider: impl StateProvider, - ctx: OpPayloadBuilderCtx, - builder_tx: BuilderTx, - ) -> Result, PayloadBuilderError> - where - BuilderTx: BuilderTransactions, - { - let mut db = State::builder().with_database(state).with_bundle_update().build(); - let ExecutedPayload { info } = - match self.execute(&state_provider, &mut db, &ctx, builder_tx)? { - BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, - BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), - BuildOutcomeKind::Aborted { fees } => { - return Ok(BuildOutcomeKind::Aborted { fees }); - } - }; - - let block_number = ctx.block_number(); - // OP doesn't support blobs/EIP-4844. - // https://specs.optimism.io/protocol/exec-engine.html#ecotone-disable-blob-transactions - // Need [Some] or [None] based on hardfork to match block hash. - let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(&info); - - let execution_outcome = - ExecutionOutcome::new(db.take_bundle(), vec![info.receipts], block_number, Vec::new()); - let receipts_root = execution_outcome - .generic_receipts_root_slow(block_number, |receipts| { - calculate_receipt_root_no_memo_optimism( - receipts, - &ctx.chain_spec, - ctx.attributes().timestamp(), - ) - }) - .expect("Number is in range"); - let logs_bloom = - execution_outcome.block_logs_bloom(block_number).expect("Number is in range"); - - // calculate the state root - let state_root_start_time = Instant::now(); - - let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); - let (state_root, trie_output) = { - state_provider.state_root_with_updates(hashed_state.clone()).inspect_err(|err| { - warn!(target: "payload_builder", - parent_header=%ctx.parent().hash(), - %err, - "failed to calculate state root for payload" - ); - })? - }; - - let state_root_calculation_time = state_root_start_time.elapsed(); - ctx.metrics.state_root_calculation_duration.record(state_root_calculation_time); - ctx.metrics.state_root_calculation_gauge.set(state_root_calculation_time); - - let (withdrawals_root, requests_hash) = if ctx.is_isthmus_active() { - // withdrawals root field in block header is used for storage root of L2 predeploy - // `l2tol1-message-passer` - ( - Some( - isthmus::withdrawals_root(execution_outcome.state(), state_provider) - .map_err(PayloadBuilderError::other)?, - ), - Some(EMPTY_REQUESTS_HASH), - ) - } else if ctx.is_canyon_active() { - (Some(EMPTY_WITHDRAWALS), None) - } else { - (None, None) - }; - - // create the block header - let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); - - let extra_data = ctx.extra_data()?; - - let header = Header { - parent_hash: ctx.parent().hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: ctx.evm_env.block_env.beneficiary, - state_root, - transactions_root, - receipts_root, - withdrawals_root, - logs_bloom, - timestamp: ctx.attributes().payload_attributes.timestamp, - mix_hash: ctx.attributes().payload_attributes.prev_randao, - nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(ctx.base_fee()), - number: ctx.parent().number + 1, - gas_limit: ctx.block_gas_limit(), - difficulty: U256::ZERO, - gas_used: info.cumulative_gas_used, - extra_data, - parent_beacon_block_root: ctx.attributes().payload_attributes.parent_beacon_block_root, - blob_gas_used, - excess_blob_gas, - requests_hash, - }; - - // seal the block - let block = alloy_consensus::Block::::new( - header, - BlockBody { - transactions: info.executed_transactions, - ommers: vec![], - withdrawals: ctx.withdrawals().cloned(), - }, - ); - - let sealed_block = Arc::new(block.seal_slow()); - info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); - - // create the executed block data - let executed = ExecutedBlock { - recovered_block: Arc::new( - RecoveredBlock::>::new_sealed( - sealed_block.as_ref().clone(), - info.executed_senders, - ), - ), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - trie_updates: Arc::new(trie_output), - }; - - let no_tx_pool = ctx.attributes().no_tx_pool; - - let payload = - OpBuiltPayload::new(ctx.payload_id(), sealed_block, info.total_fees, Some(executed)); - - ctx.metrics.payload_byte_size.record(InMemorySize::size(payload.block()) as f64); - ctx.metrics.payload_byte_size_gauge.set(InMemorySize::size(payload.block()) as f64); - - if no_tx_pool { - // if `no_tx_pool` is set only transactions from the payload attributes will be included - // in the payload. In other words, the payload is deterministic and we can - // freeze it once we've successfully built it. - Ok(BuildOutcomeKind::Freeze(payload)) - } else { - Ok(BuildOutcomeKind::Better { payload }) - } - } -} diff --git a/crates/builder/op-rbuilder/src/builders/standard/service.rs b/crates/builder/op-rbuilder/src/builders/standard/service.rs deleted file mode 100644 index d6ea9817..00000000 --- a/crates/builder/op-rbuilder/src/builders/standard/service.rs +++ /dev/null @@ -1,78 +0,0 @@ -use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; -use reth_node_api::NodeTypes; -use reth_node_builder::{BuilderContext, components::PayloadServiceBuilder}; -use reth_optimism_evm::OpEvmConfig; -use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; -use reth_provider::CanonStateSubscriptions; - -use crate::{ - builders::{ - BuilderConfig, BuilderTransactions, - standard::{builder_tx::StandardBuilderTx, payload::StandardOpPayloadBuilder}, - }, - traits::{NodeBounds, PoolBounds}, -}; - -#[allow(unnameable_types)] -#[derive(Debug)] -pub struct StandardServiceBuilder(pub BuilderConfig<()>); - -impl StandardServiceBuilder { - pub fn spawn_payload_builder_service( - self, - evm_config: OpEvmConfig, - ctx: &BuilderContext, - pool: Pool, - builder_tx: BuilderTx, - ) -> eyre::Result::Payload>> - where - Node: NodeBounds, - Pool: PoolBounds, - BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static, - { - let payload_builder = StandardOpPayloadBuilder::new( - evm_config, - pool, - ctx.provider().clone(), - self.0, - builder_tx, - ); - - let conf = ctx.config().builder.clone(); - - let payload_job_config = BasicPayloadJobGeneratorConfig::default() - .interval(conf.interval) - .deadline(conf.deadline) - .max_payload_tasks(conf.max_payload_tasks); - - let payload_generator = BasicPayloadJobGenerator::with_builder( - ctx.provider().clone(), - ctx.task_executor().clone(), - payload_job_config, - payload_builder, - ); - let (payload_service, payload_service_handle) = - PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - - ctx.task_executor().spawn_critical("payload builder service", Box::pin(payload_service)); - - Ok(payload_service_handle) - } -} - -impl PayloadServiceBuilder for StandardServiceBuilder -where - Node: NodeBounds, - Pool: PoolBounds, -{ - async fn spawn_payload_builder_service( - self, - ctx: &BuilderContext, - pool: Pool, - evm_config: OpEvmConfig, - ) -> eyre::Result::Payload>> { - let signer = self.0.builder_signer; - - self.spawn_payload_builder_service(evm_config, ctx, pool, StandardBuilderTx::new(signer)) - } -} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/flashblocks/best_txs.rs similarity index 98% rename from crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs rename to crates/builder/op-rbuilder/src/flashblocks/best_txs.rs index b67160e7..6f6daa3a 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/best_txs.rs @@ -76,7 +76,7 @@ mod tests { test_utils::{MockTransaction, MockTransactionFactory}, }; - use crate::builders::flashblocks::best_txs::BestFlashblocksTxs; + use crate::flashblocks::best_txs::BestFlashblocksTxs; #[test] fn test_simple_case() { diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/flashblocks/builder_tx.rs similarity index 63% rename from crates/builder/op-rbuilder/src/builders/builder_tx.rs rename to crates/builder/op-rbuilder/src/flashblocks/builder_tx.rs index 23dfb6d4..8ec9017b 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/builder_tx.rs @@ -8,7 +8,8 @@ use alloy_primitives::{ Address, B256, Bytes, TxKind, U256, map::{HashMap, HashSet}, }; -use alloy_sol_types::{ContractError, Revert, SolCall, SolError, SolInterface}; +use alloy_rpc_types_eth::TransactionInput; +use alloy_sol_types::{ContractError, Revert, SolCall, SolError, SolEvent, SolInterface, sol}; use op_alloy_consensus::OpTypedTransaction; use op_alloy_rpc_types::OpTransactionRequest; use op_revm::{OpHaltReason, OpTransactionError}; @@ -33,9 +34,36 @@ use revm::{ }; use tracing::{trace, warn}; -use crate::{ - builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer, -}; +use super::payload::FlashblocksExecutionInfo; +use crate::{flashblocks::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer}; + +sol!( + // From https://github.com/Uniswap/flashblocks_number_contract/blob/main/src/FlashblockNumber.sol + #[sol(rpc, abi)] + #[derive(Debug)] + interface IFlashblockNumber { + uint256 public flashblockNumber; + + function incrementFlashblockNumber() external; + + function permitIncrementFlashblockNumber(uint256 currentFlashblockNumber, bytes memory signature) external; + + function computeStructHash(uint256 currentFlashblockNumber) external pure returns (bytes32); + + function hashTypedDataV4(bytes32 structHash) external view returns (bytes32); + + + // @notice Emitted when flashblock index is incremented + // @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block) + event FlashblockIncremented(uint256 newFlashblockIndex); + + /// ----------------------------------------------------------------------- + /// Errors + /// ----------------------------------------------------------------------- + error NonBuilderAddress(address addr); + error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber); + } +); #[derive(Debug, Default)] pub struct SimulationSuccessResult { @@ -140,30 +168,236 @@ impl BuilderTransactionError { } } -pub trait BuilderTransactions { - // Simulates and returns the signed builder transactions. The simulation modifies and commit - // changes to the db so call new_simulation_state to simulate on a new copy of the state - fn simulate_builder_txs( +/// Unified flashblocks builder transaction handler. +/// +/// Handles both regular flashblock building and flashblock number contract interactions +/// based on whether `flashblock_number_address` is set. +#[derive(Debug, Clone)] +pub struct FlashblocksBuilderTx { + pub signer: Option, + pub flashblock_number_address: Option
, +} + +impl FlashblocksBuilderTx { + /// Creates a new builder tx handler. + /// + /// When `flashblock_number_address` is `Some`, the builder will interact with the + /// flashblock number contract to increment the flashblock counter. + /// When `None`, it will use simple builder transactions. + pub const fn new(signer: Option, flashblock_number_address: Option
) -> Self { + Self { signer, flashblock_number_address } + } + + /// Simulates and returns the signed builder transactions. + /// The simulation modifies and commits changes to the db. + pub fn simulate_builder_txs( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + match (self.signer, self.flashblock_number_address) { + // Flashblock number contract mode + (Some(signer), Some(flashblock_number_address)) => { + self.simulate_flashblock_number_txs(signer, flashblock_number_address, ctx, db) + } + // Simple builder tx mode (with or without signer) + _ => self.simulate_simple_builder_txs(ctx, db), + } + } + + /// Simulates builder txs for flashblock number contract mode. + fn simulate_flashblock_number_txs( + &self, + signer: Signer, + flashblock_number_address: Address, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + + if ctx.is_first_flashblock() { + // fallback block builder tx + builder_txs.extend(self.simulate_base_builder_tx(ctx, &mut *db)?); + } else { + // we increment the flashblock number for the next flashblock so we don't increment in the last flashblock + let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + cfg.disable_block_gas_limit = true; + }); + + let flashblocks_num_tx = self.signed_increment_flashblocks_tx( + signer, + flashblock_number_address, + ctx, + &mut evm, + ); + + let tx = match flashblocks_num_tx { + Ok(tx) => Some(tx), + Err(e) => { + warn!(target: "builder_tx", error = ?e, "flashblocks number contract tx simulation failed, defaulting to fallback builder tx"); + self.simulate_base_builder_tx(ctx, &mut *db)?.map(|tx| tx.set_top_of_block()) + } + }; + + builder_txs.extend(tx); + } + + Ok(builder_txs) + } + + /// Simulates builder txs for simple mode (no flashblock number contract). + fn simulate_simple_builder_txs( &self, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, db: &mut State, - ) -> Result, BuilderTransactionError>; + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + + if ctx.is_first_flashblock() { + let flashblocks_builder_tx = self.simulate_base_builder_tx(ctx, &mut *db)?; + builder_txs.extend(flashblocks_builder_tx); + } + + if ctx.is_last_flashblock() { + let base_tx = self.simulate_base_builder_tx(ctx, &mut *db)?; + builder_txs.extend(base_tx); + } + + Ok(builder_txs) + } + + fn signed_increment_flashblocks_tx( + &self, + signer: Signer, + flashblock_number_address: Address, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}; + self.increment_flashblocks_tx(signer, flashblock_number_address, calldata, ctx, evm) + } + + fn increment_flashblocks_tx( + &self, + signer: Signer, + flashblock_number_address: Address, + calldata: T, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let SimulationSuccessResult { gas_used, .. } = self.simulate_flashblocks_call( + signer, + flashblock_number_address, + calldata.clone(), + vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH], + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + flashblock_number_address, + signer, + gas_used, + calldata.abi_encode().into(), + ctx, + evm.db_mut(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { signed_tx, gas_used, da_size, is_top_of_block: true }) + } + + fn simulate_flashblocks_call( + &self, + signer: Signer, + flashblock_number_address: Address, + calldata: T, + expected_logs: Vec, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + let tx_req = OpTransactionRequest::default() + .gas_limit(ctx.block_gas_limit()) + .max_fee_per_gas(ctx.base_fee().into()) + .to(flashblock_number_address) + .from(signer.address) // use tee key as signer for simulations + .nonce(get_nonce(evm.db(), signer.address)?) + .input(TransactionInput::new(calldata.abi_encode().into())); + self.simulate_call::( + tx_req, + expected_logs, + evm, + ) + } + + fn simulate_base_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: impl DatabaseRef, + ) -> Result, BuilderTransactionError> { + match self.signer { + Some(signer) => { + let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); + let gas_used = estimate_builder_tx_gas(&message); + let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + signed_tx.encoded_2718().as_slice(), + ); + Ok(Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + })) + } + None => Ok(None), + } + } + + fn signed_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: impl DatabaseRef, + signer: Signer, + gas_used: u64, + message: Vec, + ) -> Result, BuilderTransactionError> { + let nonce = get_nonce(db, signer.address)?; + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + gas_limit: gas_used, + max_fee_per_gas: ctx.base_fee().into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(Address::ZERO), + // Include the message as part of the transaction data + input: message.into(), + ..Default::default() + }); + // Sign the transaction + let builder_tx = signer.sign_tx(tx).map_err(BuilderTransactionError::SigningError)?; - fn simulate_builder_txs_with_state_copy( + Ok(builder_tx) + } + + pub fn simulate_builder_txs_with_state_copy( &self, state_provider: impl StateProvider + Clone, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, db: &State, ) -> Result, BuilderTransactionError> { let mut simulation_state = self.new_simulation_state(state_provider, db); self.simulate_builder_txs(ctx, &mut simulation_state) } - fn add_builder_txs( + pub fn add_builder_txs( &self, state_provider: impl StateProvider + Clone, - info: &mut ExecutionInfo, - builder_ctx: &OpPayloadBuilderCtx, + info: &mut ExecutionInfo, + builder_ctx: &OpPayloadBuilderCtx, db: &mut State, top_of_block: bool, ) -> Result, BuilderTransactionError> { @@ -264,7 +498,7 @@ pub trait BuilderTransactions, + ctx: &OpPayloadBuilderCtx, db: impl DatabaseRef, ) -> Result, BuilderTransactionError> { let nonce = get_nonce(db, from.address)?; @@ -282,10 +516,10 @@ pub trait BuilderTransactions>, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, db: &mut State, ) -> Result<(), BuilderTransactionError> { let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); @@ -359,85 +593,21 @@ pub trait BuilderTransactions { - pub signer: Option, - _marker: std::marker::PhantomData, -} +fn estimate_builder_tx_gas(input: &[u8]) -> u64 { + // Count zero and non-zero bytes + let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { + if byte == 0 { (zeros + 1, nonzeros) } else { (zeros, nonzeros + 1) } + }); -impl BuilderTxBase { - pub(super) const fn new(signer: Option) -> Self { - Self { signer, _marker: std::marker::PhantomData } - } + // Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte) + let zero_cost = zero_bytes * 4; + let nonzero_cost = nonzero_bytes * 16; - pub(super) fn simulate_builder_tx( - &self, - ctx: &OpPayloadBuilderCtx, - db: impl DatabaseRef, - ) -> Result, BuilderTransactionError> { - match self.signer { - Some(signer) => { - let message: Vec = format!("Block Number: {}", ctx.block_number()).into_bytes(); - let gas_used = self.estimate_builder_tx_gas(&message); - let signed_tx = self.signed_builder_tx(ctx, db, signer, gas_used, message)?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - signed_tx.encoded_2718().as_slice(), - ); - Ok(Some(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx, - is_top_of_block: false, - })) - } - None => Ok(None), - } - } - - fn estimate_builder_tx_gas(&self, input: &[u8]) -> u64 { - // Count zero and non-zero bytes - let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { - if byte == 0 { (zeros + 1, nonzeros) } else { (zeros, nonzeros + 1) } - }); - - // Calculate gas cost (4 gas per zero byte, 16 gas per non-zero byte) - let zero_cost = zero_bytes * 4; - let nonzero_cost = nonzero_bytes * 16; + // Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623 + let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; + let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; - // Tx gas should be not less than floor gas https://eips.ethereum.org/EIPS/eip-7623 - let tokens_in_calldata = zero_bytes + nonzero_bytes * 4; - let floor_gas = 21_000 + tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN; - - std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) - } - - fn signed_builder_tx( - &self, - ctx: &OpPayloadBuilderCtx, - db: impl DatabaseRef, - signer: Signer, - gas_used: u64, - message: Vec, - ) -> Result, BuilderTransactionError> { - let nonce = get_nonce(db, signer.address)?; - - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: ctx.chain_id(), - nonce, - gas_limit: gas_used, - max_fee_per_gas: ctx.base_fee().into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(Address::ZERO), - // Include the message as part of the transaction data - input: message.into(), - ..Default::default() - }); - // Sign the transaction - let builder_tx = signer.sign_tx(tx).map_err(BuilderTransactionError::SigningError)?; - - Ok(builder_tx) - } + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) } pub fn get_nonce(db: impl DatabaseRef, address: Address) -> Result { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/flashblocks/config.rs similarity index 93% rename from crates/builder/op-rbuilder/src/builders/flashblocks/config.rs rename to crates/builder/op-rbuilder/src/flashblocks/config.rs index 00904d60..c9bdbf66 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/config.rs @@ -5,7 +5,7 @@ use core::{ use alloy_primitives::Address; -use crate::{args::OpRbuilderArgs, builders::BuilderConfig}; +use crate::{args::OpRbuilderArgs, flashblocks::BuilderConfig}; /// Configuration values that are specific to the flashblocks builder. #[allow(unnameable_types)] @@ -90,11 +90,11 @@ pub(super) trait FlashBlocksConfigExt { fn flashblocks_per_block(&self) -> u64; } -impl FlashBlocksConfigExt for BuilderConfig { +impl FlashBlocksConfigExt for BuilderConfig { fn flashblocks_per_block(&self) -> u64 { if self.block_time.as_millis() == 0 { return 0; } - (self.block_time.as_millis() / self.specific.interval.as_millis()) as u64 + (self.block_time.as_millis() / self.flashblocks.interval.as_millis()) as u64 } } diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/flashblocks/context.rs similarity index 93% rename from crates/builder/op-rbuilder/src/builders/context.rs rename to crates/builder/op-rbuilder/src/flashblocks/context.rs index d52c5b75..00116817 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/context.rs @@ -41,6 +41,7 @@ use tokio_util::sync::CancellationToken; use tracing::{debug, info, trace}; use crate::{ + flashblocks::payload::FlashblocksExecutionInfo, gas_limiter::AddressGasLimiter, metrics::OpRBuilderMetrics, primitives::reth::{ExecutionInfo, TxnExecutionResult}, @@ -49,9 +50,48 @@ use crate::{ tx_signer::Signer, }; +#[derive(Debug, Default, Clone)] +pub struct FlashblocksExtraCtx { + /// Current flashblock index + pub flashblock_index: u64, + /// Target flashblock count per block + pub target_flashblock_count: u64, + /// Total gas left for the current flashblock + pub target_gas_for_batch: u64, + /// Total DA bytes left for the current flashblock + pub target_da_for_batch: Option, + /// Total DA footprint left for the current flashblock + pub target_da_footprint_for_batch: Option, + /// Gas limit per flashblock + pub gas_per_batch: u64, + /// DA bytes limit per flashblock + pub da_per_batch: Option, + /// DA footprint limit per flashblock + pub da_footprint_per_batch: Option, + /// Whether to disable state root calculation for each flashblock + pub disable_state_root: bool, +} + +impl FlashblocksExtraCtx { + pub const fn next( + self, + target_gas_for_batch: u64, + target_da_for_batch: Option, + target_da_footprint_for_batch: Option, + ) -> Self { + Self { + flashblock_index: self.flashblock_index + 1, + target_gas_for_batch, + target_da_for_batch, + target_da_footprint_for_batch, + ..self + } + } +} + /// Container type that holds all necessities to build a new payload. #[derive(Debug)] -pub struct OpPayloadBuilderCtx { +pub struct OpPayloadBuilderCtx { /// The type that knows how to perform system calls and configure the evm. pub evm_config: OpEvmConfig, /// The DA config for the payload builder @@ -73,7 +113,7 @@ pub struct OpPayloadBuilderCtx { /// The metrics for the builder pub metrics: Arc, /// Extra context for the payload builder - pub extra_ctx: ExtraCtx, + pub extra: FlashblocksExtraCtx, /// Max gas that can be used by a transaction. pub max_gas_per_txn: Option, /// Rate limiting based on gas. This is an optional feature. @@ -82,15 +122,29 @@ pub struct OpPayloadBuilderCtx { pub tx_data_store: TxDataStore, } -impl OpPayloadBuilderCtx { - #[allow(clippy::missing_const_for_fn)] +impl OpPayloadBuilderCtx { pub(super) fn with_cancel(self, cancel: CancellationToken) -> Self { Self { cancel, ..self } } - #[allow(clippy::missing_const_for_fn)] - pub(super) fn with_extra_ctx(self, extra_ctx: ExtraCtx) -> Self { - Self { extra_ctx, ..self } + pub(super) fn with_extra_ctx(self, extra: FlashblocksExtraCtx) -> Self { + Self { extra, ..self } + } + + pub(crate) const fn flashblock_index(&self) -> u64 { + self.extra.flashblock_index + } + + pub(crate) const fn target_flashblock_count(&self) -> u64 { + self.extra.target_flashblock_count + } + + pub(crate) const fn is_first_flashblock(&self) -> bool { + self.flashblock_index() == 0 + } + + pub(crate) const fn is_last_flashblock(&self) -> bool { + self.flashblock_index() == self.target_flashblock_count() } /// Returns the parent block the payload will be build on. @@ -234,7 +288,7 @@ impl OpPayloadBuilderCtx { } } -impl OpPayloadBuilderCtx { +impl OpPayloadBuilderCtx { /// Constructs a receipt for the given transaction. pub fn build_receipt( &self, @@ -268,10 +322,10 @@ impl OpPayloadBuilderCtx { } /// Executes all sequencer transactions that are included in the payload attributes. - pub(super) fn execute_sequencer_transactions( + pub(super) fn execute_sequencer_transactions( &self, db: &mut State, - ) -> Result, PayloadBuilderError> { + ) -> Result, PayloadBuilderError> { let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); @@ -366,9 +420,9 @@ impl OpPayloadBuilderCtx { /// Executes the given best transactions and updates the execution info. /// /// Returns `Ok(Some(())` if the job was cancelled. - pub(super) fn execute_best_transactions( + pub(super) fn execute_best_transactions( &self, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, db: &mut State, best_txs: &mut impl PayloadTxsBounds, block_gas_limit: u64, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/flashblocks/ctx.rs similarity index 94% rename from crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs rename to crates/builder/op-rbuilder/src/flashblocks/ctx.rs index 357d9106..900d399d 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/ctx.rs @@ -13,7 +13,7 @@ use reth_optimism_primitives::OpTransactionSigned; use tokio_util::sync::CancellationToken; use crate::{ - builders::{BuilderConfig, OpPayloadBuilderCtx, flashblocks::FlashblocksConfig}, + flashblocks::{BuilderConfig, FlashblocksExtraCtx, OpPayloadBuilderCtx}, gas_limiter::{AddressGasLimiter, args::GasLimiterArgs}, metrics::OpRBuilderMetrics, traits::ClientBounds, @@ -41,7 +41,7 @@ pub(super) struct OpPayloadSyncerCtx { impl OpPayloadSyncerCtx { pub(super) fn new( client: &Client, - builder_config: BuilderConfig, + builder_config: BuilderConfig, evm_config: OpEvmConfig, metrics: Arc, ) -> eyre::Result @@ -85,7 +85,7 @@ impl OpPayloadSyncerCtx { cancel, builder_signer: None, metrics: self.metrics, - extra_ctx: (), + extra: FlashblocksExtraCtx::default(), max_gas_per_txn: self.max_gas_per_txn, address_gas_limiter: AddressGasLimiter::new(GasLimiterArgs::default()), tx_data_store: self.tx_data_store, diff --git a/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/flashblocks/generator.rs similarity index 100% rename from crates/builder/op-rbuilder/src/builders/generator.rs rename to crates/builder/op-rbuilder/src/flashblocks/generator.rs diff --git a/crates/builder/op-rbuilder/src/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/flashblocks/mod.rs new file mode 100644 index 00000000..324914af --- /dev/null +++ b/crates/builder/op-rbuilder/src/flashblocks/mod.rs @@ -0,0 +1,128 @@ +use core::{convert::TryFrom, time::Duration}; + +use reth_optimism_payload_builder::config::{OpDAConfig, OpGasLimitConfig}; + +use crate::{ + args::OpRbuilderArgs, gas_limiter::args::GasLimiterArgs, tx_data_store::TxDataStore, + tx_signer::Signer, +}; + +pub(crate) mod best_txs; +pub(crate) mod builder_tx; +pub(crate) mod config; +pub(crate) mod context; +pub(crate) mod ctx; +pub(crate) mod generator; +pub(crate) mod payload; +pub(crate) mod payload_handler; +pub(crate) mod service; +pub(crate) mod wspub; + +pub use builder_tx::{ + BuilderTransactionCtx, BuilderTransactionError, FlashblocksBuilderTx, InvalidContractDataError, + SimulationSuccessResult, get_balance, get_nonce, +}; +pub use config::FlashblocksConfig; +pub use context::{FlashblocksExtraCtx, OpPayloadBuilderCtx}; +pub use payload::FlashblocksExecutionInfo; +pub use service::FlashblocksServiceBuilder; + +/// Configuration values for the flashblocks builder. +#[derive(Clone)] +pub struct BuilderConfig { + /// Secret key of the builder that is used to sign the end of block transaction. + pub builder_signer: Option, + + /// The interval at which blocks are added to the chain. + /// This is also the frequency at which the builder will be receiving FCU requests from the + /// sequencer. + pub block_time: Duration, + + /// Data Availability configuration for the OP builder + /// Defines constraints for the maximum size of data availability transactions. + pub da_config: OpDAConfig, + + /// Gas limit configuration for the payload builder + pub gas_limit_config: OpGasLimitConfig, + + /// Extra time allowed for payload building before garbage collection. + pub block_time_leeway: Duration, + + /// Inverted sampling frequency in blocks. 1 - each block, 100 - every 100th block. + pub sampling_ratio: u64, + + /// Configuration values that are specific to the flashblocks block builder. + pub flashblocks: FlashblocksConfig, + + /// Maximum gas a transaction can use before being excluded. + pub max_gas_per_txn: Option, + + /// Address gas limiter config. + pub gas_limiter_config: GasLimiterArgs, + + /// Unified transaction data store (backrun bundles + resource metering) + pub tx_data_store: TxDataStore, +} + +impl core::fmt::Debug for BuilderConfig { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Config") + .field( + "builder_signer", + &self + .builder_signer + .as_ref() + .map_or_else(|| "None".into(), |signer| signer.address.to_string()), + ) + .field("block_time", &self.block_time) + .field("block_time_leeway", &self.block_time_leeway) + .field("da_config", &self.da_config) + .field("gas_limit_config", &self.gas_limit_config) + .field("sampling_ratio", &self.sampling_ratio) + .field("flashblocks", &self.flashblocks) + .field("max_gas_per_txn", &self.max_gas_per_txn) + .field("gas_limiter_config", &self.gas_limiter_config) + .field("tx_data_store", &self.tx_data_store) + .finish() + } +} + +impl Default for BuilderConfig { + fn default() -> Self { + Self { + builder_signer: None, + block_time: Duration::from_secs(2), + block_time_leeway: Duration::from_millis(500), + da_config: OpDAConfig::default(), + gas_limit_config: OpGasLimitConfig::default(), + flashblocks: FlashblocksConfig::default(), + sampling_ratio: 100, + max_gas_per_txn: None, + gas_limiter_config: GasLimiterArgs::default(), + tx_data_store: TxDataStore::default(), + } + } +} + +impl TryFrom for BuilderConfig { + type Error = eyre::Report; + + fn try_from(args: OpRbuilderArgs) -> Result { + let flashblocks = FlashblocksConfig::try_from(args.clone())?; + Ok(Self { + builder_signer: args.builder_signer, + block_time: Duration::from_millis(args.chain_block_time), + block_time_leeway: Duration::from_secs(args.extra_block_deadline_secs), + da_config: Default::default(), + gas_limit_config: Default::default(), + sampling_ratio: args.telemetry.sampling_ratio, + max_gas_per_txn: args.max_gas_per_txn, + gas_limiter_config: args.gas_limiter.clone(), + tx_data_store: TxDataStore::new( + args.enable_resource_metering, + args.tx_data_store_buffer_size, + ), + flashblocks, + }) + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/flashblocks/payload.rs similarity index 89% rename from crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs rename to crates/builder/op-rbuilder/src/flashblocks/payload.rs index e5a7b2e9..30cbd878 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/payload.rs @@ -42,13 +42,13 @@ use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, metadata::Level, span, warn}; -use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; +use super::wspub::WebSocketPublisher; use crate::{ - builders::{ - BuilderConfig, - builder_tx::BuilderTransactions, + flashblocks::{ + BuilderConfig, FlashblocksBuilderTx, FlashblocksExtraCtx, + best_txs::BestFlashblocksTxs, + config::FlashBlocksConfigExt, context::OpPayloadBuilderCtx, - flashblocks::{best_txs::BestFlashblocksTxs, config::FlashBlocksConfigExt}, generator::{BlockCell, BuildArguments, PayloadBuilder}, }, gas_limiter::AddressGasLimiter, @@ -71,76 +71,14 @@ type NextBestFlashblocksTxs = BestFlashblocksTxs< >; #[derive(Debug, Default, Clone)] -pub(super) struct FlashblocksExecutionInfo { +pub struct FlashblocksExecutionInfo { /// Index of the last consumed flashblock last_flashblock_index: usize, } -#[allow(unnameable_types)] -#[derive(Debug, Default, Clone)] -pub struct FlashblocksExtraCtx { - /// Current flashblock index - flashblock_index: u64, - /// Target flashblock count per block - target_flashblock_count: u64, - /// Total gas left for the current flashblock - target_gas_for_batch: u64, - /// Total DA bytes left for the current flashblock - target_da_for_batch: Option, - /// Total DA footprint left for the current flashblock - target_da_footprint_for_batch: Option, - /// Gas limit per flashblock - gas_per_batch: u64, - /// DA bytes limit per flashblock - da_per_batch: Option, - /// DA footprint limit per flashblock - da_footprint_per_batch: Option, - /// Whether to disable state root calculation for each flashblock - disable_state_root: bool, -} - -impl FlashblocksExtraCtx { - const fn next( - self, - target_gas_for_batch: u64, - target_da_for_batch: Option, - target_da_footprint_for_batch: Option, - ) -> Self { - Self { - flashblock_index: self.flashblock_index + 1, - target_gas_for_batch, - target_da_for_batch, - target_da_footprint_for_batch, - ..self - } - } -} - -impl OpPayloadBuilderCtx { - /// Returns the current flashblock index - pub(crate) const fn flashblock_index(&self) -> u64 { - self.extra_ctx.flashblock_index - } - - /// Returns the target flashblock count - pub(crate) const fn target_flashblock_count(&self) -> u64 { - self.extra_ctx.target_flashblock_count - } - - /// Returns if the flashblock is the first fallback block - pub(crate) const fn is_first_flashblock(&self) -> bool { - self.flashblock_index() == 0 - } - - /// Returns if the flashblock is the last one - pub(crate) const fn is_last_flashblock(&self) -> bool { - self.flashblock_index() == self.target_flashblock_count() - } -} - /// Optimism's payload builder #[derive(Debug, Clone)] -pub(super) struct OpPayloadBuilder { +pub(super) struct OpPayloadBuilder { /// The type responsible for creating the evm. pub evm_config: OpEvmConfig, /// The transaction pool @@ -154,24 +92,24 @@ pub(super) struct OpPayloadBuilder { /// to all connected subscribers. pub ws_pub: Arc, /// System configuration for the builder - pub config: BuilderConfig, + pub config: BuilderConfig, /// The metrics for the builder pub metrics: Arc, /// The end of builder transaction type - pub builder_tx: BuilderTx, + pub builder_tx: FlashblocksBuilderTx, /// Rate limiting based on gas. This is an optional feature. pub address_gas_limiter: AddressGasLimiter, } -impl OpPayloadBuilder { +impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. #[allow(clippy::too_many_arguments)] pub(super) fn new( evm_config: OpEvmConfig, pool: Pool, client: Client, - config: BuilderConfig, - builder_tx: BuilderTx, + config: BuilderConfig, + builder_tx: FlashblocksBuilderTx, payload_tx: mpsc::Sender, ws_pub: Arc, metrics: Arc, @@ -191,12 +129,10 @@ impl OpPayloadBuilder { } } -impl reth_basic_payload_builder::PayloadBuilder - for OpPayloadBuilder +impl reth_basic_payload_builder::PayloadBuilder for OpPayloadBuilder where Pool: Clone + Send + Sync, Client: Clone + Send + Sync, - BuilderTx: Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; @@ -219,11 +155,10 @@ where } } -impl OpPayloadBuilder +impl OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BuilderTx: BuilderTransactions + Send + Sync, { fn get_op_payload_builder_ctx( &self, @@ -231,8 +166,8 @@ where OpPayloadBuilderAttributes, >, cancel: CancellationToken, - extra_ctx: FlashblocksExtraCtx, - ) -> eyre::Result> { + extra: FlashblocksExtraCtx, + ) -> eyre::Result { let chain_spec = self.client.chain_spec(); let timestamp = config.attributes.timestamp(); @@ -265,7 +200,7 @@ where .next_evm_env(&config.parent_header, &block_env_attributes) .wrap_err("failed to create next evm env")?; - Ok(OpPayloadBuilderCtx:: { + Ok(OpPayloadBuilderCtx { evm_config: self.evm_config.clone(), chain_spec, config, @@ -276,7 +211,7 @@ where gas_limit_config: self.config.gas_limit_config.clone(), builder_signer: self.config.builder_signer, metrics: Default::default(), - extra_ctx, + extra, max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), tx_data_store: self.config.tx_data_store.clone(), @@ -311,7 +246,7 @@ where span.record("payload_id", config.attributes.payload_attributes.id.to_string()); let timestamp = config.attributes.timestamp(); - let disable_state_root = self.config.specific.disable_state_root; + let disable_state_root = self.config.flashblocks.disable_state_root; let ctx = self .get_op_payload_builder_ctx( config.clone(), @@ -396,7 +331,7 @@ where message = "Performed flashblocks timing derivation", flashblocks_per_block, first_flashblock_offset = first_flashblock_offset.as_millis(), - flashblocks_interval = self.config.specific.interval.as_millis(), + flashblocks_interval = self.config.flashblocks.interval.as_millis(), ); ctx.metrics.reduced_flashblocks_number.record( self.config.flashblocks_per_block().saturating_sub(ctx.target_flashblock_count()) @@ -418,7 +353,7 @@ where let da_footprint_per_batch = info.da_footprint_scalar.map(|_| ctx.block_gas_limit() / flashblocks_per_block); - let extra_ctx = FlashblocksExtraCtx { + let extra = FlashblocksExtraCtx { flashblock_index: 1, target_flashblock_count: flashblocks_per_block, target_gas_for_batch: gas_per_batch, @@ -432,14 +367,14 @@ where let mut fb_cancel = block_cancel.child_token(); let mut ctx = self - .get_op_payload_builder_ctx(config, fb_cancel.clone(), extra_ctx) + .get_op_payload_builder_ctx(config, fb_cancel.clone(), extra) .map_err(|e| PayloadBuilderError::Other(e.into()))?; // Create best_transaction iterator let mut best_txs = BestFlashblocksTxs::new(BestPayloadTransactions::new( self.pool.best_transactions_with_attributes(ctx.best_transaction_attributes()), )); - let interval = self.config.specific.interval; + let interval = self.config.flashblocks.interval; let (tx, mut rx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); tokio::spawn({ @@ -562,7 +497,7 @@ where P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, >( &self, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, info: &mut ExecutionInfo, state: &mut State, state_provider: impl StateProvider + Clone, @@ -572,9 +507,9 @@ where span: &tracing::Span, ) -> eyre::Result> { let flashblock_index = ctx.flashblock_index(); - let mut target_gas_for_batch = ctx.extra_ctx.target_gas_for_batch; - let mut target_da_for_batch = ctx.extra_ctx.target_da_for_batch; - let mut target_da_footprint_for_batch = ctx.extra_ctx.target_da_footprint_for_batch; + let mut target_gas_for_batch = ctx.extra.target_gas_for_batch; + let mut target_da_for_batch = ctx.extra.target_da_for_batch; + let mut target_da_footprint_for_batch = ctx.extra.target_da_footprint_for_batch; info!( target: "payload_builder", @@ -674,7 +609,7 @@ where state, ctx, info, - !ctx.extra_ctx.disable_state_root || ctx.attributes().no_tx_pool, + !ctx.extra.disable_state_root || ctx.attributes().no_tx_pool, ); let total_block_built_duration = total_block_built_duration.elapsed(); ctx.metrics.total_block_built_duration.record(total_block_built_duration); @@ -719,7 +654,7 @@ where .record(info.executed_transactions.len() as f64); // Update bundle_state for next iteration - if let Some(da_limit) = ctx.extra_ctx.da_per_batch { + if let Some(da_limit) = ctx.extra.da_per_batch { if let Some(da) = target_da_for_batch.as_mut() { *da += da_limit; } else { @@ -729,16 +664,15 @@ where } } - let target_gas_for_batch = - ctx.extra_ctx.target_gas_for_batch + ctx.extra_ctx.gas_per_batch; + let target_gas_for_batch = ctx.extra.target_gas_for_batch + ctx.extra.gas_per_batch; if let (Some(footprint), Some(da_footprint_limit)) = - (target_da_footprint_for_batch.as_mut(), ctx.extra_ctx.da_footprint_per_batch) + (target_da_footprint_for_batch.as_mut(), ctx.extra.da_footprint_per_batch) { *footprint += da_footprint_limit; } - let next_extra_ctx = ctx.extra_ctx.clone().next( + let next_extra = ctx.extra.clone().next( target_gas_for_batch, target_da_for_batch, target_da_footprint_for_batch, @@ -753,7 +687,7 @@ where target_flashblocks = ctx.target_flashblock_count(), ); - Ok(Some(next_extra_ctx)) + Ok(Some(next_extra)) } } } @@ -761,7 +695,7 @@ where /// Do some logging and metric recording when we stop build flashblocks fn record_flashblocks_metrics( &self, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, info: &ExecutionInfo, flashblocks_per_block: u64, span: &tracing::Span, @@ -788,11 +722,11 @@ where /// Calculate number of flashblocks. /// If dynamic is enabled this function will take time drift into the account. pub(super) fn calculate_flashblocks(&self, timestamp: u64) -> (u64, Duration) { - if self.config.specific.fixed { + if self.config.flashblocks.fixed { return ( self.config.flashblocks_per_block(), // We adjust first FB to ensure that we have at least some time to make all FB in time - self.config.specific.interval - self.config.specific.leeway_time, + self.config.flashblocks.interval - self.config.flashblocks.leeway_time, ); } @@ -803,7 +737,7 @@ where // FCU(a) could arrive with `delay < fb_time` - in this case we will shrink first flashblock // FCU(a) could arrive with `fb_time < delay < block_time - fb_time` - in this case we will issue less flashblocks let target_time = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp) - - self.config.specific.leeway_time; + - self.config.flashblocks.leeway_time; let now = std::time::SystemTime::now(); let Ok(time_drift) = target_time.duration_since(now) else { error!( @@ -812,7 +746,7 @@ where ?target_time, ?now, ); - return (self.config.flashblocks_per_block(), self.config.specific.interval); + return (self.config.flashblocks_per_block(), self.config.flashblocks.interval); }; self.metrics.flashblocks_time_drift.record( self.config.block_time.as_millis().saturating_sub(time_drift.as_millis()) as f64, @@ -826,7 +760,7 @@ where ); // This is extra check to ensure that we would account at least for block time in case we have any timer discrepancies. let time_drift = time_drift.min(self.config.block_time); - let interval = self.config.specific.interval.as_millis() as u64; + let interval = self.config.flashblocks.interval.as_millis() as u64; let time_drift = time_drift.as_millis() as u64; let first_flashblock_offset = time_drift.rem(interval); if first_flashblock_offset == 0 { @@ -840,12 +774,10 @@ where } #[async_trait::async_trait] -impl PayloadBuilder for OpPayloadBuilder +impl PayloadBuilder for OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BuilderTx: - BuilderTransactions + Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; @@ -866,13 +798,12 @@ struct FlashblocksMetadata { block_number: u64, } -fn execute_pre_steps( +fn execute_pre_steps( state: &mut State, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, ) -> Result, PayloadBuilderError> where DB: Database + std::fmt::Debug, - ExtraCtx: std::fmt::Debug + Default, { // 1. apply pre-execution changes ctx.evm_config @@ -886,16 +817,15 @@ where Ok(info) } -pub(super) fn build_block( +pub(super) fn build_block( state: &mut State, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, info: &mut ExecutionInfo, calculate_state_root: bool, ) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError> where DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, - ExtraCtx: std::fmt::Debug + Default, { // We use it to preserve state, so we run merge_transitions on transition state at most once let untouched_transition_state = state.transition_state.clone(); diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/crates/builder/op-rbuilder/src/flashblocks/payload_handler.rs similarity index 80% rename from crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs rename to crates/builder/op-rbuilder/src/flashblocks/payload_handler.rs index a8803cb9..7c9c7cfa 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/payload_handler.rs @@ -4,12 +4,13 @@ use reth_optimism_payload_builder::OpBuiltPayload; use tokio::sync::mpsc; use tracing::warn; -use crate::{builders::flashblocks::ctx::OpPayloadSyncerCtx, traits::ClientBounds}; +use crate::{flashblocks::ctx::OpPayloadSyncerCtx, traits::ClientBounds}; /// Handles newly built flashblock payloads. /// -/// When a payload is built by this node, an event is sent to the payload builder. -pub(crate) struct PayloadHandler { +/// In the case of a payload built by this node, it is broadcast to peers and an event is sent to the payload builder. +/// In the case of a payload received from a peer, it is executed and if successful, an event is sent to the payload builder. +pub(super) struct PayloadHandler { // receives new payloads built by this builder. built_rx: mpsc::Receiver, // sends a `Events::BuiltPayload` to the reth payload builder when a new payload is received. @@ -25,7 +26,8 @@ impl PayloadHandler where Client: ClientBounds + 'static, { - pub(crate) const fn new( + #[allow(clippy::too_many_arguments)] + pub(super) const fn new( built_rx: mpsc::Receiver, payload_events_handle: tokio::sync::broadcast::Sender>, ctx: OpPayloadSyncerCtx, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/flashblocks/service.rs similarity index 66% rename from crates/builder/op-rbuilder/src/builders/flashblocks/service.rs rename to crates/builder/op-rbuilder/src/flashblocks/service.rs index 3c96566e..5fc472fe 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/flashblocks/service.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use derive_more::Debug; use eyre::WrapErr as _; use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; use reth_node_api::NodeTypes; @@ -8,43 +9,28 @@ use reth_optimism_evm::OpEvmConfig; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_provider::CanonStateSubscriptions; -use super::{FlashblocksConfig, payload::OpPayloadBuilder}; +use super::{ + BuilderConfig, FlashblocksBuilderTx, generator::BlockPayloadJobGenerator, + payload::OpPayloadBuilder, payload_handler::PayloadHandler, wspub::WebSocketPublisher, +}; use crate::{ - builders::{ - BuilderConfig, - builder_tx::BuilderTransactions, - flashblocks::{ - builder_tx::{FlashblocksBuilderTx, FlashblocksNumberBuilderTx}, - payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, - payload_handler::PayloadHandler, - wspub::WebSocketPublisher, - }, - generator::BlockPayloadJobGenerator, - }, metrics::OpRBuilderMetrics, traits::{NodeBounds, PoolBounds}, }; -#[allow(unnameable_types)] #[derive(Debug)] -pub struct FlashblocksServiceBuilder(pub BuilderConfig); +pub struct FlashblocksServiceBuilder(pub BuilderConfig); impl FlashblocksServiceBuilder { - fn spawn_payload_builder_service( + fn spawn_payload_builder_service( self, ctx: &BuilderContext, pool: Pool, - builder_tx: BuilderTx, + builder_tx: FlashblocksBuilderTx, ) -> eyre::Result::Payload>> where Node: NodeBounds, Pool: PoolBounds, - BuilderTx: BuilderTransactions - + Unpin - + Clone - + Send - + Sync - + 'static, { // TODO: is there a different global token? // this is effectively unused right now due to the usage of reth's `task_executor`. @@ -54,7 +40,7 @@ impl FlashblocksServiceBuilder { let (built_payload_tx, built_payload_rx) = tokio::sync::mpsc::channel(16); let ws_pub: Arc = - WebSocketPublisher::new(self.0.specific.ws_addr, metrics.clone()) + WebSocketPublisher::new(self.0.flashblocks.ws_addr, metrics.clone()) .wrap_err("failed to create ws publisher")? .into(); let payload_builder = OpPayloadBuilder::new( @@ -81,7 +67,7 @@ impl FlashblocksServiceBuilder { let (payload_service, payload_builder_handle) = PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - let syncer_ctx = crate::builders::flashblocks::ctx::OpPayloadSyncerCtx::new( + let syncer_ctx = super::ctx::OpPayloadSyncerCtx::new( &ctx.provider().clone(), self.0, OpEvmConfig::optimism(ctx.chain_spec()), @@ -118,22 +104,10 @@ where pool: Pool, _: OpEvmConfig, ) -> eyre::Result::Payload>> { - let signer = self.0.builder_signer; - - if let Some(builder_signer) = signer - && let Some(flashblocks_number_contract_address) = - self.0.specific.flashblocks_number_contract_address - { - self.spawn_payload_builder_service( - ctx, - pool, - FlashblocksNumberBuilderTx::new( - builder_signer, - flashblocks_number_contract_address, - ), - ) - } else { - self.spawn_payload_builder_service(ctx, pool, FlashblocksBuilderTx::new(signer)) - } + let builder_tx = FlashblocksBuilderTx::new( + self.0.builder_signer, + self.0.flashblocks.flashblocks_number_contract_address, + ); + self.spawn_payload_builder_service(ctx, pool, builder_tx) } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/flashblocks/wspub.rs similarity index 100% rename from crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs rename to crates/builder/op-rbuilder/src/flashblocks/wspub.rs diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index e2c748ec..1d441e83 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -1,5 +1,4 @@ -use core::fmt::Debug; -use std::{marker::PhantomData, sync::Arc}; +use std::sync::Arc; use eyre::Result; use reth_cli_commands::launcher::Launcher; @@ -15,53 +14,66 @@ use reth_optimism_rpc::OpEthApiBuilder; use reth_optimism_txpool::OpPooledTransaction; use crate::{ - args::OpRbuilderArgs, - builders::{BuilderConfig, PayloadBuilder}, - metrics::{VERSION, record_flag_gauge_metrics}, + args::{Cli, CliExt, OpRbuilderArgs}, + flashblocks::{BuilderConfig, FlashblocksServiceBuilder}, + metrics::VERSION, primitives::reth::engine_api_builder::OpEngineApiBuilder, tx_data_store::{BaseApiExtServer, TxDataStoreExt}, }; -/// Launcher for the OP builder node. -#[derive(Debug)] -pub struct BuilderLauncher { - _builder: PhantomData, +pub fn launch() -> Result<()> { + let cli = Cli::parsed(); + + #[cfg(feature = "telemetry")] + let telemetry_args = match &cli.command { + reth_optimism_cli::commands::Commands::Node(node_command) => { + node_command.ext.telemetry.clone() + } + _ => Default::default(), + }; + + #[cfg(not(feature = "telemetry"))] + let cli_app = cli.configure(); + + #[cfg(feature = "telemetry")] + let mut cli_app = cli.configure(); + #[cfg(feature = "telemetry")] + { + use crate::primitives::telemetry::setup_telemetry_layer; + let telemetry_layer = setup_telemetry_layer(&telemetry_args)?; + cli_app.access_tracing_layers()?.add_layer(telemetry_layer); + } + + tracing::info!("Starting OP builder in flashblocks mode"); + let launcher = BuilderLauncher::new(); + cli_app.run(launcher)?; + Ok(()) } -impl BuilderLauncher -where - B: PayloadBuilder, -{ +#[derive(Debug)] +pub struct BuilderLauncher; + +impl BuilderLauncher { pub const fn new() -> Self { - Self { _builder: PhantomData } + Self } } -impl Default for BuilderLauncher -where - B: PayloadBuilder, -{ +impl Default for BuilderLauncher { fn default() -> Self { Self::new() } } -impl Launcher for BuilderLauncher -where - B: PayloadBuilder, - BuilderConfig: TryFrom, - as TryFrom>::Error: Debug, -{ +impl Launcher for BuilderLauncher { async fn entrypoint( self, builder: WithLaunchContext, OpChainSpec>>, builder_args: OpRbuilderArgs, ) -> Result<()> { - let builder_config = BuilderConfig::::try_from(builder_args.clone()) + let builder_config = BuilderConfig::try_from(builder_args.clone()) .expect("Failed to convert rollup args to builder config"); - record_flag_gauge_metrics(&builder_args); - let da_config = builder_config.da_config.clone(); let gas_limit_config = builder_config.gas_limit_config.clone(); let rollup_args = builder_args.rollup_args; @@ -97,7 +109,7 @@ where rollup_args.supervisor_safety_level, ), ) - .payload(B::new_service(builder_config)?), + .payload(FlashblocksServiceBuilder(builder_config)), ) .with_add_ons(addons) .extend_rpc_modules(move |ctx| { diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index 31b985d7..c9398fd7 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,7 +1,7 @@ #![allow(missing_docs)] pub mod args; -pub mod builders; +pub mod flashblocks; pub mod gas_limiter; pub mod launcher; pub mod metrics; diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 389dca8a..6c575aea 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -4,8 +4,6 @@ use reth_metrics::{ metrics::{Counter, Gauge, Histogram, gauge}, }; -use crate::args::OpRbuilderArgs; - /// The latest version from Cargo.toml. pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -209,12 +207,6 @@ impl OpRBuilderMetrics { } } -/// Set gauge metrics for some flags so we can inspect which ones are set -/// and which ones aren't. -pub fn record_flag_gauge_metrics(builder_args: &OpRbuilderArgs) { - gauge!("op_rbuilder_flags_flashblocks_enabled").set(builder_args.flashblocks.enabled as i32); -} - /// Contains version information for the application. #[derive(Debug, Clone)] pub struct VersionInfo { diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index dfff871c..081c8e24 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -11,7 +11,6 @@ use crate::{ #[rb_test(flashblocks, args = OpRbuilderArgs { chain_block_time: 2000, flashblocks: FlashblocksArgs { - enabled: true, flashblocks_port: 1239, flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, @@ -45,7 +44,6 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { #[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, @@ -79,7 +77,6 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { #[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, @@ -113,7 +110,6 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { #[rb_test(flashblocks, args = OpRbuilderArgs { chain_block_time: 2000, flashblocks: FlashblocksArgs { - enabled: true, flashblocks_port: 1239, flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, @@ -147,7 +143,6 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { #[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, @@ -183,7 +178,6 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> #[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, @@ -215,7 +209,6 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<() #[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, diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index fdd44045..8cad61d2 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -37,7 +37,7 @@ use tokio_util::sync::CancellationToken; use crate::{ args::OpRbuilderArgs, - builders::{BuilderConfig, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, + flashblocks::{BuilderConfig, FlashblocksServiceBuilder}, primitives::reth::engine_api_builder::OpEngineApiBuilder, tests::{ EngineApi, Ipc, TransactionPoolObserver, builder_signer, create_test_db, @@ -86,8 +86,8 @@ impl LocalInstance { /// /// This method does not prefund any accounts, so before sending any transactions /// make sure that sender accounts are funded. - pub async fn new(args: OpRbuilderArgs) -> eyre::Result { - Box::pin(Self::new_with_config::

(args, default_node_config())).await + pub async fn new(args: OpRbuilderArgs) -> eyre::Result { + Box::pin(Self::new_with_config(args, default_node_config())).await } /// Creates a new local instance of the OP builder node with the given arguments, @@ -95,7 +95,7 @@ impl LocalInstance { /// /// This method does not prefund any accounts, so before sending any transactions /// make sure that sender accounts are funded. - pub async fn new_with_config( + pub async fn new_with_config( args: OpRbuilderArgs, config: NodeConfig, ) -> eyre::Result { @@ -111,7 +111,7 @@ impl LocalInstance { let signer = args.builder_signer.unwrap_or(builder_signer()); args.builder_signer = Some(signer); - let builder_config = BuilderConfig::::try_from(args.clone()) + let builder_config = BuilderConfig::try_from(args.clone()) .expect("Failed to convert rollup args to builder config"); let da_config = builder_config.da_config.clone(); let gas_limit_config = builder_config.gas_limit_config.clone(); @@ -137,7 +137,7 @@ impl LocalInstance { op_node .components() .pool(pool_component(&args)) - .payload(P::new_service(builder_config)?), + .payload(FlashblocksServiceBuilder(builder_config)), ) .with_add_ons(addons) .on_rpc_started(move |_, _| { @@ -173,24 +173,14 @@ impl LocalInstance { }) } - /// Creates new local instance of the OP builder node with the standard builder configuration. - /// This method prefunds the default accounts with 1 ETH each. - pub async fn standard() -> eyre::Result { - clear_otel_env_vars(); - let args = crate::args::Cli::parse_from(["dummy", "node"]); - let Commands::Node(ref node_command) = args.command else { unreachable!() }; - Self::new::(node_command.ext.clone()).await - } - /// Creates new local instance of the OP builder node with the flashblocks builder configuration. /// This method prefunds the default accounts with 1 ETH each. pub async fn flashblocks() -> eyre::Result { clear_otel_env_vars(); let mut args = crate::args::Cli::parse_from(["dummy", "node"]); let Commands::Node(ref mut node_command) = args.command else { unreachable!() }; - node_command.ext.flashblocks.enabled = true; node_command.ext.flashblocks.flashblocks_port = 0; // use random os assigned port - Self::new::(node_command.ext.clone()).await + Self::new(node_command.ext.clone()).await } pub const fn config(&self) -> &NodeConfig { diff --git a/crates/builder/op-rbuilder/src/tests/framework/macros/src/lib.rs b/crates/builder/op-rbuilder/src/tests/framework/macros/src/lib.rs index 537ebbff..c6c8d3e9 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/macros/src/lib.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/macros/src/lib.rs @@ -5,46 +5,33 @@ use syn::{Expr, ItemFn, Meta, Token, parse_macro_input, punctuated::Punctuated}; // Define all variant information in one place struct VariantInfo { name: &'static str, - builder_type: &'static str, default_instance_call: &'static str, args_modifier: fn(&proc_macro2::TokenStream) -> proc_macro2::TokenStream, default_args_factory: fn() -> proc_macro2::TokenStream, } -const BUILDER_VARIANTS: &[VariantInfo] = &[ - VariantInfo { - name: "standard", - builder_type: "crate::builders::StandardBuilder", - default_instance_call: "crate::tests::LocalInstance::standard().await?", - args_modifier: |args| quote! { #args }, - default_args_factory: || quote! { Default::default() }, - }, - VariantInfo { - name: "flashblocks", - builder_type: "crate::builders::FlashblocksBuilder", - default_instance_call: "crate::tests::LocalInstance::flashblocks().await?", - args_modifier: |args| { - quote! { - { - let mut args = #args; - args.flashblocks.enabled = true; - args.flashblocks.flashblocks_port = crate::tests::get_available_port(); - args - } +const BUILDER_VARIANTS: &[VariantInfo] = &[VariantInfo { + name: "flashblocks", + default_instance_call: "crate::tests::LocalInstance::flashblocks().await?", + args_modifier: |args| { + quote! { + { + let mut args = #args; + args.flashblocks.flashblocks_port = crate::tests::get_available_port(); + args } - }, - default_args_factory: || { - quote! { - { - let mut args = crate::args::OpRbuilderArgs::default(); - args.flashblocks.enabled = true; - args.flashblocks.flashblocks_port = crate::tests::get_available_port(); - args - } + } + }, + default_args_factory: || { + quote! { + { + let mut args = crate::args::OpRbuilderArgs::default(); + args.flashblocks.flashblocks_port = crate::tests::get_available_port(); + args } - }, + } }, -]; +}]; fn get_variant_info(variant: &str) -> Option<&'static VariantInfo> { BUILDER_VARIANTS.iter().find(|v| v.name == variant) @@ -173,7 +160,6 @@ fn generate_instance_init( let variant_info = get_variant_info(variant).unwrap_or_else(|| panic!("Unknown variant: {variant}")); - let builder_type: proc_macro2::TokenStream = variant_info.builder_type.parse().unwrap(); let default_call: proc_macro2::TokenStream = variant_info.default_instance_call.parse().unwrap(); @@ -181,18 +167,18 @@ fn generate_instance_init( (None, None) => default_call, (Some(args_expr), None) => { let modified_args = (variant_info.args_modifier)("e! { #args_expr }); - quote! { crate::tests::LocalInstance::new::<#builder_type>(#modified_args).await? } + quote! { crate::tests::LocalInstance::new(#modified_args).await? } } (None, Some(config_expr)) => { let default_args = (variant_info.default_args_factory)(); quote! { - crate::tests::LocalInstance::new_with_config::<#builder_type>(#default_args, #config_expr).await? + crate::tests::LocalInstance::new_with_config(#default_args, #config_expr).await? } } (Some(args_expr), Some(config_expr)) => { let modified_args = (variant_info.args_modifier)("e! { #args_expr }); quote! { - crate::tests::LocalInstance::new_with_config::<#builder_type>(#modified_args, #config_expr).await? + crate::tests::LocalInstance::new_with_config(#modified_args, #config_expr).await? } } } @@ -289,4 +275,9 @@ macro_rules! generate_if_variant_macros { } // Generate macros for all variants -generate_if_variant_macros!(standard, flashblocks); +generate_if_variant_macros!(flashblocks); + +#[proc_macro] +pub fn if_standard(_input: TokenStream) -> TokenStream { + TokenStream::new() +} diff --git a/crates/builder/op-rbuilder/src/tests/ordering.rs b/crates/builder/op-rbuilder/src/tests/ordering.rs index 15e86c70..9d5a09ee 100644 --- a/crates/builder/op-rbuilder/src/tests/ordering.rs +++ b/crates/builder/op-rbuilder/src/tests/ordering.rs @@ -1,14 +1,13 @@ use alloy_consensus::Transaction; +use alloy_network::TransactionResponse; use futures::{StreamExt, future::join_all, stream}; use macros::rb_test; use crate::tests::{ChainDriverExt, LocalInstance, framework::ONE_ETH}; -/// This test ensures that the transactions are ordered by fee priority in the block. -/// This version of the test is only applicable to the standard builder because in flashblocks -/// the transaction order is commited by the block after each flashblock is produced, -/// so the order is only going to hold within one flashblock, but not the entire block. -#[rb_test(standard)] +/// This test ensures that the transactions are ordered by fee priority within each flashblock. +/// We expect breaks in global ordering that align with flashblock boundaries. +#[rb_test(flashblocks)] async fn fee_priority_ordering(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let accounts = driver.fund_accounts(10, ONE_ETH).await?; @@ -52,19 +51,30 @@ async fn fee_priority_ordering(rbuilder: LocalInstance) -> eyre::Result<()> { "not all transactions included in the block" ); - // verify all transactions are ordered by fee priority - let txs_tips = driver + let flashblocks_per_block = + rbuilder.args().chain_block_time / rbuilder.args().flashblocks.flashblocks_block_time; + + // verify user transactions are fee-ordered within each flashblock boundary + let tips_in_block_order = driver .latest_full() .await? .into_transactions_vec() .into_iter() - .skip(1) // skip the deposit transaction - .take(txs.len()) // skip the last builder transaction - .map(|tx| tx.effective_tip_per_gas(base_fee as u64)) - .rev() // we want to check descending order + .filter_map(|tx| { + if txs.contains(&tx.tx_hash()) { + Some(tx.effective_tip_per_gas(base_fee as u64)) + } else { + None + } + }) .collect::>(); - assert!(txs_tips.is_sorted(), "Transactions not ordered by fee priority"); + let breaks = tips_in_block_order.windows(2).filter(|pair| pair[0] < pair[1]).count(); + + assert!( + (breaks as u64) <= flashblocks_per_block, + "Observed more ordering resets than flashblocks_per_block (breaks={breaks}, flashblocks_per_block={flashblocks_per_block})" + ); Ok(()) } diff --git a/crates/builder/op-rbuilder/src/tests/txpool.rs b/crates/builder/op-rbuilder/src/tests/txpool.rs index fed14211..2fdcdecf 100644 --- a/crates/builder/op-rbuilder/src/tests/txpool.rs +++ b/crates/builder/op-rbuilder/src/tests/txpool.rs @@ -15,8 +15,7 @@ use crate::tests::{ ..Default::default() }, ..default_node_config() - }, - standard + } )] async fn pending_pool_limit(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; diff --git a/crates/shared/jwt/Cargo.toml b/crates/shared/jwt/Cargo.toml index 0fe3a0b1..72a1a2b4 100644 --- a/crates/shared/jwt/Cargo.toml +++ b/crates/shared/jwt/Cargo.toml @@ -12,7 +12,7 @@ repository.workspace = true workspace = true [features] -test-utils = [] +test-utils = [ "kona-engine?/test-utils" ] engine-validation = [ "dep:alloy-provider", "dep:alloy-transport-http",