From f11381fa4dd6c1c4349fa1dedf2699624d4c0518 Mon Sep 17 00:00:00 2001 From: liamaharon Date: Thu, 14 Nov 2024 20:16:49 +0400 Subject: [PATCH 001/262] op-rbuilder (#244) --- crates/builder/op-rbuilder/Cargo.toml | 58 ++ crates/builder/op-rbuilder/node/Cargo.toml | 40 + crates/builder/op-rbuilder/node/src/args.rs | 155 ++++ crates/builder/op-rbuilder/node/src/lib.rs | 16 + crates/builder/op-rbuilder/node/src/node.rs | 286 ++++++++ .../op-rbuilder/payload_builder/Cargo.toml | 44 ++ .../payload_builder/src/builder.rs | 690 ++++++++++++++++++ .../op-rbuilder/payload_builder/src/lib.rs | 8 + .../builder/op-rbuilder/src/eth_bundle_api.rs | 80 ++ crates/builder/op-rbuilder/src/main.rs | 84 +++ .../transaction-pool-bundle-ext/Cargo.toml | 14 + .../bundle_pool_ops/rbuilder/Cargo.toml | 27 + .../bundle_pool_ops/rbuilder/src/lib.rs | 280 +++++++ .../src/bundle_supported_pool.rs | 410 +++++++++++ .../transaction-pool-bundle-ext/src/lib.rs | 28 + .../transaction-pool-bundle-ext/src/traits.rs | 51 ++ 16 files changed, 2271 insertions(+) create mode 100644 crates/builder/op-rbuilder/Cargo.toml create mode 100644 crates/builder/op-rbuilder/node/Cargo.toml create mode 100644 crates/builder/op-rbuilder/node/src/args.rs create mode 100644 crates/builder/op-rbuilder/node/src/lib.rs create mode 100644 crates/builder/op-rbuilder/node/src/node.rs create mode 100644 crates/builder/op-rbuilder/payload_builder/Cargo.toml create mode 100644 crates/builder/op-rbuilder/payload_builder/src/builder.rs create mode 100644 crates/builder/op-rbuilder/payload_builder/src/lib.rs create mode 100644 crates/builder/op-rbuilder/src/eth_bundle_api.rs create mode 100644 crates/builder/op-rbuilder/src/main.rs create mode 100644 crates/builder/transaction-pool-bundle-ext/Cargo.toml create mode 100644 crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml create mode 100644 crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs create mode 100644 crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs create mode 100644 crates/builder/transaction-pool-bundle-ext/src/lib.rs create mode 100644 crates/builder/transaction-pool-bundle-ext/src/traits.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml new file mode 100644 index 00000000..c75a6f0f --- /dev/null +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "op-rbuilder" +version = "0.1.0" +edition = "2021" + +[dependencies] +rbuilder = { path = "../rbuilder" } +op-rbuilder-node-optimism = { path = "./node" } +transaction-pool-bundle-ext = { path = "../transaction-pool-bundle-ext" } + +reth.workspace = true +reth-node-optimism.workspace = true +reth-cli-util.workspace = true +reth-optimism-rpc.workspace = true + +tokio.workspace = true +tracing.workspace = true +jsonrpsee = { workspace = true } +async-trait = { workspace = true } +clap_builder = { workspace = true } + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { version = "0.5.0", optional = true } + +[dev-dependencies] +reth-discv4.workspace = true + +[features] +default = ["jemalloc"] + +jemalloc = ["dep:tikv-jemallocator"] +jemalloc-prof = [ + "jemalloc", + "tikv-jemallocator?/profiling", + "reth/jemalloc-prof" +] + +min-error-logs = ["tracing/release_max_level_error"] +min-warn-logs = ["tracing/release_max_level_warn"] +min-info-logs = ["tracing/release_max_level_info"] +min-debug-logs = ["tracing/release_max_level_debug"] +min-trace-logs = ["tracing/release_max_level_trace"] + +optimism = [ + "reth-node-optimism/optimism", + "reth/optimism", + "reth-optimism-rpc/optimism", + "op-rbuilder-node-optimism/optimism", + "rbuilder/optimism" +] + +redact-sensitive = [ + "rbuilder/redact-sensitive" +] + +[[bin]] +name = "op-rbuilder" +path = "src/main.rs" diff --git a/crates/builder/op-rbuilder/node/Cargo.toml b/crates/builder/op-rbuilder/node/Cargo.toml new file mode 100644 index 00000000..350dc79d --- /dev/null +++ b/crates/builder/op-rbuilder/node/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "op-rbuilder-node-optimism" +edition = "2021" + +[dependencies] +# workspace +op-rbuilder-payload-builder = { path = "../payload_builder" } +transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } +rbuilder-bundle-pool-operations = { path = "../../transaction-pool-bundle-ext/bundle_pool_ops/rbuilder" } + +# reth +reth-chainspec.workspace = true +reth-payload-builder.workspace = true +reth-primitives.workspace = true +reth-basic-payload-builder.workspace = true +reth-node-builder.workspace = true +reth-tracing.workspace = true +reth-provider.workspace = true +reth-transaction-pool.workspace = true +reth-evm.workspace = true +reth-evm-optimism.workspace = true +reth-node-optimism = { workspace = true } + +# async +tracing.workspace = true + +# misc +clap.workspace = true +eyre.workspace = true + +[features] +optimism = [ + "reth-node-optimism/optimism", + "reth-chainspec/optimism", + "reth-provider/optimism", + "reth-evm-optimism/optimism", + "op-rbuilder-payload-builder/optimism", + "rbuilder-bundle-pool-operations/optimism", + "reth-primitives/optimism" +] diff --git a/crates/builder/op-rbuilder/node/src/args.rs b/crates/builder/op-rbuilder/node/src/args.rs new file mode 100644 index 00000000..c0ea455a --- /dev/null +++ b/crates/builder/op-rbuilder/node/src/args.rs @@ -0,0 +1,155 @@ +//! Additional Node command arguments. +//! +//! Copied from OptimismNode to allow easy extension. + +//! clap [Args](clap::Args) for optimism rollup configuration + +use std::path::PathBuf; + +/// Parameters for rollup configuration +#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +#[command(next_help_heading = "Rollup")] +pub struct OpRbuilderArgs { + /// HTTP endpoint for the sequencer mempool + #[arg(long = "rollup.sequencer-http", value_name = "HTTP_URL")] + pub sequencer_http: Option, + + /// Disable transaction pool gossip + #[arg(long = "rollup.disable-tx-pool-gossip")] + pub disable_txpool_gossip: bool, + + /// Enable walkback to genesis on startup. This is useful for re-validating the existing DB + /// prior to beginning normal syncing. + #[arg(long = "rollup.enable-genesis-walkback")] + pub enable_genesis_walkback: bool, + + /// By default the pending block equals the latest block + /// to save resources and not leak txs from the tx-pool, + /// this flag enables computing of the pending block + /// from the tx-pool instead. + /// + /// If `compute_pending_block` is not enabled, the payload builder + /// will use the payload attributes from the latest block. Note + /// that this flag is not yet functional. + #[arg(long = "rollup.compute-pending-block")] + pub compute_pending_block: bool, + + /// enables discovery v4 if provided + #[arg(long = "rollup.discovery.v4", default_value = "false")] + pub discovery_v4: bool, + + /// Enable the engine2 experimental features on op-reth binary + #[arg(long = "engine.experimental", default_value = "false")] + pub experimental: bool, + + /// Enable the engine2 experimental features on op-reth binary + #[arg(long = "rbuilder.config")] + pub rbuilder_config_path: PathBuf, +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::{Args, Parser}; + + /// A helper type to parse Args more easily + #[derive(Parser)] + struct CommandParser { + #[command(flatten)] + args: T, + } + + #[test] + fn test_parse_optimism_default_args() { + let default_args = OpRbuilderArgs::default(); + let args = CommandParser::::parse_from(["reth"]).args; + assert_eq!(args, default_args); + } + + #[test] + fn test_parse_optimism_walkback_args() { + let expected_args = OpRbuilderArgs { + enable_genesis_walkback: true, + ..Default::default() + }; + let args = CommandParser::::parse_from([ + "reth", + "--rollup.enable-genesis-walkback", + ]) + .args; + assert_eq!(args, expected_args); + } + + #[test] + fn test_parse_optimism_compute_pending_block_args() { + let expected_args = OpRbuilderArgs { + compute_pending_block: true, + ..Default::default() + }; + let args = + CommandParser::::parse_from(["reth", "--rollup.compute-pending-block"]) + .args; + assert_eq!(args, expected_args); + } + + #[test] + fn test_parse_optimism_discovery_v4_args() { + let expected_args = OpRbuilderArgs { + discovery_v4: true, + ..Default::default() + }; + let args = + CommandParser::::parse_from(["reth", "--rollup.discovery.v4"]).args; + assert_eq!(args, expected_args); + } + + #[test] + fn test_parse_optimism_sequencer_http_args() { + let expected_args = OpRbuilderArgs { + sequencer_http: Some("http://host:port".into()), + ..Default::default() + }; + let args = CommandParser::::parse_from([ + "reth", + "--rollup.sequencer-http", + "http://host:port", + ]) + .args; + assert_eq!(args, expected_args); + } + + #[test] + fn test_parse_optimism_disable_txpool_args() { + let expected_args = OpRbuilderArgs { + disable_txpool_gossip: true, + ..Default::default() + }; + let args = CommandParser::::parse_from([ + "reth", + "--rollup.disable-tx-pool-gossip", + ]) + .args; + assert_eq!(args, expected_args); + } + + #[test] + fn test_parse_optimism_many_args() { + let expected_args = OpRbuilderArgs { + disable_txpool_gossip: true, + compute_pending_block: true, + enable_genesis_walkback: true, + sequencer_http: Some("http://host:port".into()), + ..Default::default() + }; + let args = CommandParser::::parse_from([ + "reth", + "--rollup.disable-tx-pool-gossip", + "--rollup.compute-pending-block", + "--rollup.enable-genesis-walkback", + "--rollup.sequencer-http", + "http://host:port", + ]) + .args; + assert_eq!(args, expected_args); + } +} diff --git a/crates/builder/op-rbuilder/node/src/lib.rs b/crates/builder/op-rbuilder/node/src/lib.rs new file mode 100644 index 00000000..02f51726 --- /dev/null +++ b/crates/builder/op-rbuilder/node/src/lib.rs @@ -0,0 +1,16 @@ +//! Standalone crate for op-rbuilder-specific Reth configuration and builder types. +//! +//! Inherits Network, Executor, and Consensus Builders from the Optimism defaults, +//! overrides the Pool and Payload Builders. + +#![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(feature = "optimism")] + +use tracing as _; + +/// CLI argument parsing. +pub mod args; + +pub mod node; +pub use node::OpRbuilderNode; diff --git a/crates/builder/op-rbuilder/node/src/node.rs b/crates/builder/op-rbuilder/node/src/node.rs new file mode 100644 index 00000000..47ce9c8f --- /dev/null +++ b/crates/builder/op-rbuilder/node/src/node.rs @@ -0,0 +1,286 @@ +//! op-rbuilder Node types config. +//! +//! Inherits Network, Executor, and Consensus Builders from the optimism node, +//! and overrides the Pool and Payload Builders. + +use rbuilder_bundle_pool_operations::BundlePoolOps; +use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; +use reth_chainspec::ChainSpec; +use reth_evm::ConfigureEvm; +use reth_evm_optimism::OptimismEvmConfig; +use reth_node_builder::{ + components::{ComponentsBuilder, PayloadServiceBuilder, PoolBuilder}, + node::{FullNodeTypes, NodeTypes}, + BuilderContext, Node, PayloadBuilderConfig, +}; +use reth_node_optimism::{ + node::{ + OptimismAddOns, OptimismConsensusBuilder, OptimismExecutorBuilder, OptimismNetworkBuilder, + }, + txpool::OpTransactionValidator, + OptimismEngineTypes, +}; +use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; +use reth_primitives::TransactionSigned; +use reth_provider::CanonStateSubscriptions; +use reth_tracing::tracing::{debug, info}; +use reth_transaction_pool::{ + blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPooledTransaction, + TransactionValidationTaskExecutor, +}; +use std::path::PathBuf; +use transaction_pool_bundle_ext::{ + BundlePoolOperations, BundleSupportedPool, TransactionPoolBundleExt, +}; + +use crate::args::OpRbuilderArgs; + +/// Type configuration for an OP rbuilder node. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct OpRbuilderNode { + /// Additional args + pub args: OpRbuilderArgs, +} + +impl OpRbuilderNode { + /// Creates a new instance of the OP rbuilder node type. + pub const fn new(args: OpRbuilderArgs) -> Self { + Self { args } + } + + /// Returns the components for the given [`OpRbuilderArgs`]. + pub fn components( + args: OpRbuilderArgs, + ) -> ComponentsBuilder< + Node, + OpRbuilderPoolBuilder, + OpRbuilderPayloadServiceBuilder, + OptimismNetworkBuilder, + OptimismExecutorBuilder, + OptimismConsensusBuilder, + > + where + Node: FullNodeTypes, + { + let OpRbuilderArgs { + disable_txpool_gossip, + compute_pending_block, + discovery_v4, + rbuilder_config_path, + .. + } = args; + ComponentsBuilder::default() + .node_types::() + .pool(OpRbuilderPoolBuilder::new(rbuilder_config_path)) + .payload(OpRbuilderPayloadServiceBuilder::new( + compute_pending_block, + OptimismEvmConfig::default(), + )) + .network(OptimismNetworkBuilder { + disable_txpool_gossip, + disable_discovery_v4: !discovery_v4, + }) + .executor(OptimismExecutorBuilder::default()) + .consensus(OptimismConsensusBuilder::default()) + } +} + +impl Node for OpRbuilderNode +where + N: FullNodeTypes, +{ + type ComponentsBuilder = ComponentsBuilder< + N, + OpRbuilderPoolBuilder, + OpRbuilderPayloadServiceBuilder, + OptimismNetworkBuilder, + OptimismExecutorBuilder, + OptimismConsensusBuilder, + >; + + type AddOns = OptimismAddOns; + + fn components_builder(&self) -> Self::ComponentsBuilder { + let Self { args } = self; + Self::components(args.clone()) + } +} + +impl NodeTypes for OpRbuilderNode { + type Primitives = (); + type Engine = OptimismEngineTypes; + type ChainSpec = ChainSpec; +} + +/// An extended optimism transaction pool with bundle support. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct OpRbuilderPoolBuilder { + rbuilder_config_path: PathBuf, +} + +impl OpRbuilderPoolBuilder { + /// Creates a new instance of the OP rbuilder pool builder. + pub fn new(rbuilder_config_path: PathBuf) -> Self { + Self { + rbuilder_config_path, + } + } +} + +pub type OpRbuilderTransactionPool = BundleSupportedPool< + TransactionValidationTaskExecutor>, + CoinbaseTipOrdering, + S, + BundlePoolOps, +>; + +impl PoolBuilder for OpRbuilderPoolBuilder +where + Node: FullNodeTypes, +{ + type Pool = OpRbuilderTransactionPool; + + async fn build_pool(self, ctx: &BuilderContext) -> eyre::Result { + let data_dir = ctx.config().datadir(); + let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?; + + let validator = TransactionValidationTaskExecutor::eth_builder(ctx.chain_spec()) + .with_head_timestamp(ctx.head().timestamp) + .kzg_settings(ctx.kzg_settings()?) + .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) + .build_with_tasks( + ctx.provider().clone(), + ctx.task_executor().clone(), + blob_store.clone(), + ) + .map(|validator| { + OpTransactionValidator::new(validator) + // In --dev mode we can't require gas fees because we're unable to decode the L1 + // block info + .require_l1_data_gas_fee(!ctx.config().dev.dev) + }); + + let bundle_ops = BundlePoolOps::new(ctx.provider().clone(), self.rbuilder_config_path) + .await + .expect("Failed to instantiate RbuilderBundlePoolOps"); + let transaction_pool = OpRbuilderTransactionPool::new( + validator, + CoinbaseTipOrdering::default(), + blob_store, + bundle_ops, + ctx.pool_config(), + ); + + info!(target: "reth::cli", "Transaction pool initialized"); + let transactions_path = data_dir.txpool_transactions(); + + // spawn txpool maintenance task + { + let pool = transaction_pool.clone(); + let chain_events = ctx.provider().canonical_state_stream(); + let client = ctx.provider().clone(); + let transactions_backup_config = + reth_transaction_pool::maintain::LocalTransactionBackupConfig::with_local_txs_backup(transactions_path); + + ctx.task_executor() + .spawn_critical_with_graceful_shutdown_signal( + "local transactions backup task", + |shutdown| { + reth_transaction_pool::maintain::backup_local_transactions_task( + shutdown, + pool.clone(), + transactions_backup_config, + ) + }, + ); + + // spawn the maintenance task + ctx.task_executor().spawn_critical( + "txpool maintenance task", + reth_transaction_pool::maintain::maintain_transaction_pool_future( + client, + pool, + chain_events, + ctx.task_executor().clone(), + Default::default(), + ), + ); + debug!(target: "reth::cli", "Spawned txpool maintenance task"); + } + + Ok(transaction_pool) + } +} + +/// A op-rbuilder payload service builder +#[derive(Debug, Default, Clone)] +pub struct OpRbuilderPayloadServiceBuilder { + /// By default the pending block equals the latest block + /// to save resources and not leak txs from the tx-pool, + /// this flag enables computing of the pending block + /// from the tx-pool instead. + /// + /// If `compute_pending_block` is not enabled, the payload builder + /// will use the payload attributes from the latest block. Note + /// that this flag is not yet functional. + pub compute_pending_block: bool, + /// The EVM configuration to use for the payload builder. + pub evm_config: EVM, +} + +impl OpRbuilderPayloadServiceBuilder { + /// Create a new instance with the given `compute_pending_block` flag and evm config. + pub const fn new(compute_pending_block: bool, evm_config: EVM) -> Self { + Self { + compute_pending_block, + evm_config, + } + } +} + +impl PayloadServiceBuilder for OpRbuilderPayloadServiceBuilder +where + Node: FullNodeTypes, + Pool: TransactionPoolBundleExt + + BundlePoolOperations + + Unpin + + 'static, + EVM: ConfigureEvm, +{ + async fn spawn_payload_service( + self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result> { + let payload_builder = op_rbuilder_payload_builder::OpRbuilderPayloadBuilder::new( + OptimismEvmConfig::default(), + ) + .set_compute_pending_block(self.compute_pending_block); + let conf = ctx.payload_builder_config(); + + let payload_job_config = BasicPayloadJobGeneratorConfig::default() + .interval(conf.interval()) + .deadline(conf.deadline()) + .max_payload_tasks(conf.max_payload_tasks()) + // no extradata for OP + .extradata(Default::default()); + + let payload_generator = BasicPayloadJobGenerator::with_builder( + ctx.provider().clone(), + pool, + ctx.task_executor().clone(), + payload_job_config, + ctx.chain_spec(), + payload_builder, + ); + let (payload_service, payload_builder) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical("payload builder service", Box::pin(payload_service)); + + Ok(payload_builder) + } +} diff --git a/crates/builder/op-rbuilder/payload_builder/Cargo.toml b/crates/builder/op-rbuilder/payload_builder/Cargo.toml new file mode 100644 index 00000000..ca4e1ca6 --- /dev/null +++ b/crates/builder/op-rbuilder/payload_builder/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "op-rbuilder-payload-builder" +version = "0.1.0" +edition = "2021" +description = "A payload builder for op-rbuilder with bundle support." + +[dependencies] +# workspace +transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } + +# reth +reth-chainspec.workspace = true +reth-primitives.workspace = true +reth-revm.workspace = true +reth-rpc-types.workspace = true +reth-provider.workspace = true +reth-evm.workspace = true +reth-evm-optimism.workspace = true +reth-node-optimism.workspace = true +reth-execution-types.workspace = true +reth-payload-builder.workspace = true +reth-basic-payload-builder.workspace = true +reth-trie.workspace = true +reth-chain-state.workspace = true +reth-optimism-payload-builder.workspace = true + +# ethereum +revm.workspace = true + +# misc +tracing.workspace = true + +[features] +optimism = [ + "reth-chainspec/optimism", + "reth-primitives/optimism", + "reth-provider/optimism", + "reth-node-optimism/optimism", + "reth-evm-optimism/optimism", + "reth-revm/optimism", + "reth-execution-types/optimism", + "reth-optimism-payload-builder/optimism", + "revm/optimism" +] diff --git a/crates/builder/op-rbuilder/payload_builder/src/builder.rs b/crates/builder/op-rbuilder/payload_builder/src/builder.rs new file mode 100644 index 00000000..a00db5e6 --- /dev/null +++ b/crates/builder/op-rbuilder/payload_builder/src/builder.rs @@ -0,0 +1,690 @@ +//! Optimism payload builder implementation with Flashbots bundle support. + +use reth_basic_payload_builder::*; +use reth_chain_state::ExecutedBlock; +use reth_chainspec::{EthereumHardforks, OptimismHardfork}; +use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvm}; +use reth_execution_types::ExecutionOutcome; +use reth_node_optimism::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}; +use reth_optimism_payload_builder::error::OptimismPayloadBuilderError; +use reth_payload_builder::error::PayloadBuilderError; +use reth_primitives::{ + constants::{BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS}, + eip4844::calculate_excess_blob_gas, + proofs, Block, Header, Receipt, TransactionSigned, TxType, EMPTY_OMMER_ROOT_HASH, U256, +}; +use reth_provider::StateProviderFactory; +use reth_revm::database::StateProviderDatabase; +use reth_rpc_types::{ + beacon::events::{PayloadAttributesData, PayloadAttributesEvent}, + engine::PayloadAttributes, +}; +use reth_trie::HashedPostState; +use revm::{ + db::states::bundle_state::BundleRetention, + primitives::{EVMError, EnvWithHandlerCfg, InvalidTransaction, ResultAndState}, + DatabaseCommit, State, +}; +use std::sync::Arc; +use tracing::{debug, error, trace, warn}; +use transaction_pool_bundle_ext::{BundlePoolOperations, TransactionPoolBundleExt}; + +/// Payload builder for OP Stack, which includes bundle support. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OpRbuilderPayloadBuilder { + /// The rollup's compute pending block configuration option. + compute_pending_block: bool, + /// The type responsible for creating the evm. + evm_config: EvmConfig, +} + +impl OpRbuilderPayloadBuilder { + pub const fn new(evm_config: EvmConfig) -> Self { + Self { + compute_pending_block: true, + evm_config, + } + } + + /// Sets the rollup's compute pending block configuration option. + pub const fn set_compute_pending_block(mut self, compute_pending_block: bool) -> Self { + self.compute_pending_block = compute_pending_block; + self + } + + /// Enables the rollup's compute pending block configuration option. + pub const fn compute_pending_block(self) -> Self { + self.set_compute_pending_block(true) + } + + /// Returns the rollup's compute pending block configuration option. + pub const fn is_compute_pending_block(&self) -> bool { + self.compute_pending_block + } +} + +/// Implementation of the [`PayloadBuilder`] trait for [`OpRbuilderPayloadBuilder`]. +impl PayloadBuilder for OpRbuilderPayloadBuilder +where + Client: StateProviderFactory, + EvmConfig: ConfigureEvm, + Pool: TransactionPoolBundleExt + BundlePoolOperations, +{ + type Attributes = OptimismPayloadBuilderAttributes; + type BuiltPayload = OptimismBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + ) -> Result, PayloadBuilderError> { + let parent_block = args.config.parent_block.clone(); + // Notify our BundleOperations of the new bundle. + let eth_payload_attributes = args.config.attributes.payload_attributes.clone(); + let payload_attributes = PayloadAttributes { + timestamp: eth_payload_attributes.timestamp, + prev_randao: eth_payload_attributes.prev_randao, + withdrawals: Some(eth_payload_attributes.withdrawals.into_inner()), + parent_beacon_block_root: eth_payload_attributes.parent_beacon_block_root, + suggested_fee_recipient: eth_payload_attributes.suggested_fee_recipient, + }; + let payload_attributes_data = PayloadAttributesData { + parent_block_number: parent_block.number, + parent_block_root: parent_block.header.hash(), + parent_block_hash: parent_block.hash(), + proposal_slot: parent_block.number + 1, + proposer_index: 0, // Shouldn't be required for core building logic + payload_attributes, + }; + let payload_attributes_event = PayloadAttributesEvent { + version: "placeholder".to_string(), + data: payload_attributes_data, + }; + + if let Err(e) = args.pool.notify_payload_attributes_event( + payload_attributes_event, + args.config.attributes.gas_limit, + ) { + error!(?e, "Failed to notify payload attributes event!"); + }; + try_build_inner(self.evm_config.clone(), args, self.compute_pending_block) + } + + fn on_missing_payload( + &self, + _args: BuildArguments, + ) -> MissingPayloadBehaviour { + // we want to await the job that's already in progress because that should be returned as + // is, there's no benefit in racing another job + MissingPayloadBehaviour::AwaitInProgress + } + + fn build_empty_payload( + &self, + client: &Client, + config: PayloadConfig, + ) -> Result { + let extra_data = config.extra_data(); + let PayloadConfig { + initialized_block_env, + parent_block, + attributes, + chain_spec, + .. + } = config; + + debug!(target: "payload_builder", parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building empty payload"); + + let state = client.state_by_block_hash(parent_block.hash()).map_err(|err| { + warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to get state for empty payload"); + err + })?; + let mut db = State::builder() + .with_database(StateProviderDatabase::new(state)) + .with_bundle_update() + .build(); + + let base_fee = initialized_block_env.basefee.to::(); + let block_gas_limit: u64 = initialized_block_env + .gas_limit + .try_into() + .unwrap_or(chain_spec.max_gas_limit); + + let WithdrawalsOutcome { + withdrawals_root, + withdrawals, + } = commit_withdrawals( + &mut db, + &chain_spec, + attributes.payload_attributes.timestamp, + attributes.payload_attributes.withdrawals.clone(), + ) + .map_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to commit withdrawals for empty payload" + ); + err + })?; + + // merge all transitions into bundle state, this would apply the withdrawal balance + // changes and 4788 contract call + db.merge_transitions(BundleRetention::PlainState); + + // calculate the state root + let bundle_state = db.take_bundle(); + let hashed_state = HashedPostState::from_bundle_state(&bundle_state.state); + let state_root = db.database.state_root(hashed_state).map_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to calculate state root for empty payload" + ); + err + })?; + + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + if chain_spec.is_cancun_active_at_timestamp(attributes.payload_attributes.timestamp) { + excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { + let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); + let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); + Some(calculate_excess_blob_gas( + parent_excess_blob_gas, + parent_blob_gas_used, + )) + } else { + // for the first post-fork block, both parent.blob_gas_used and + // parent.excess_blob_gas are evaluated as 0 + Some(calculate_excess_blob_gas(0, 0)) + }; + + blob_gas_used = Some(0); + } + let header = Header { + parent_hash: parent_block.hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: initialized_block_env.coinbase, + state_root, + transactions_root: EMPTY_TRANSACTIONS, + withdrawals_root, + receipts_root: EMPTY_RECEIPTS, + logs_bloom: Default::default(), + timestamp: attributes.payload_attributes.timestamp, + mix_hash: attributes.payload_attributes.prev_randao, + nonce: BEACON_NONCE, + base_fee_per_gas: Some(base_fee), + number: parent_block.number + 1, + gas_limit: block_gas_limit, + difficulty: U256::ZERO, + gas_used: 0, + extra_data, + blob_gas_used, + excess_blob_gas, + parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, + requests_root: None, + }; + + let block = Block { + header, + body: vec![], + ommers: vec![], + withdrawals, + requests: None, + }; + let sealed_block = block.seal_slow(); + + Ok(OptimismBuiltPayload::new( + attributes.payload_attributes.payload_id(), + sealed_block, + U256::ZERO, + chain_spec, + attributes, + None, + )) + } +} + +/// Constructs an Optimism payload from the transactions sent through the +/// Payload attributes by the sequencer, and bundles returned by the BundlePool. +/// +/// Given build arguments including an Ethereum client, bundle-enabled 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. +#[inline] +pub(crate) fn try_build_inner( + evm_config: EvmConfig, + args: BuildArguments, + _compute_pending_block: bool, +) -> Result, PayloadBuilderError> +where + EvmConfig: ConfigureEvm, + Client: StateProviderFactory, + Pool: TransactionPoolBundleExt + BundlePoolOperations, +{ + let BuildArguments { + client, + pool, + mut cached_reads, + config, + cancel, + best_payload, + } = args; + + let state_provider = client.state_by_block_hash(config.parent_block.hash())?; + let state = StateProviderDatabase::new(state_provider); + let mut db = State::builder() + .with_database_ref(cached_reads.as_db(state)) + .with_bundle_update() + .build(); + let extra_data = config.extra_data(); + let PayloadConfig { + initialized_block_env, + initialized_cfg, + parent_block, + attributes, + chain_spec, + .. + } = config; + + debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); + + let mut cumulative_gas_used = 0; + let block_gas_limit: u64 = attributes.gas_limit.unwrap_or_else(|| { + initialized_block_env + .gas_limit + .try_into() + .unwrap_or(chain_spec.max_gas_limit) + }); + let base_fee = initialized_block_env.basefee.to::(); + + let mut executed_txs = Vec::with_capacity(attributes.transactions.len()); + let mut executed_senders = Vec::with_capacity(attributes.transactions.len()); + + let mut total_fees = U256::ZERO; + + let block_number = initialized_block_env.number.to::(); + + let is_regolith = chain_spec.is_fork_active_at_timestamp( + OptimismHardfork::Regolith, + attributes.payload_attributes.timestamp, + ); + + // apply eip-4788 pre block contract call + pre_block_beacon_root_contract_call( + &mut db, + &evm_config, + &chain_spec, + &initialized_cfg, + &initialized_block_env, + attributes.payload_attributes.parent_beacon_block_root, + ) + .map_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to apply beacon root contract call for empty payload" + ); + PayloadBuilderError::Internal(err.into()) + })?; + + // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism + // blocks will always have at least a single transaction in them (the L1 info transaction), + // so we can safely assume that this will always be triggered upon the transition and that + // the above check for empty blocks will never be hit on OP chains. + reth_evm_optimism::ensure_create2_deployer( + chain_spec.clone(), + attributes.payload_attributes.timestamp, + &mut db, + ) + .map_err(|err| { + warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); + PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) + })?; + + let mut receipts = Vec::with_capacity(attributes.transactions.len()); + for sequencer_tx in &attributes.transactions { + // Check if the job was cancelled, if so we can exit early. + if cancel.is_cancelled() { + return Ok(BuildOutcome::Cancelled); + } + + // A sequencer's block should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + OptimismPayloadBuilderError::BlobTransactionRejected, + )); + } + + // Convert the transaction to a [TransactionSignedEcRecovered]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx + .value() + .clone() + .try_into_ecrecovered() + .map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + // Cache the depositor account prior to the state transition for the deposit nonce. + // + // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces + // were not introduced in Bedrock. In addition, regular transactions don't have deposit + // nonces, so we don't need to touch the DB for those. + let depositor = (is_regolith && sequencer_tx.is_deposit()) + .then(|| { + db.load_cache_account(sequencer_tx.signer()) + .map(|acc| acc.account_info().unwrap_or_default()) + }) + .transpose() + .map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( + sequencer_tx.signer(), + )) + })?; + + let env = EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + evm_config.tx_env(&sequencer_tx), + ); + + let mut evm = evm_config.evm_with_env(&mut db, env); + + let ResultAndState { result, state } = match evm.transact() { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; + } + err => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(err)); + } + } + } + }; + + // to release the db reference drop evm. + drop(evm); + // commit changes + db.commit(state); + + let gas_used = result.gas_used(); + + // add gas used by the transaction to cumulative gas used, before creating the receipt + cumulative_gas_used += gas_used; + + // Push transaction changeset and calculate header bloom filter for receipt. + receipts.push(Some(Receipt { + tx_type: sequencer_tx.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: depositor.map(|account| account.nonce), + // The deposit receipt version was introduced in Canyon to indicate an update to how + // receipt hashes should be computed when set. The state transition process + // ensures this is only set for post-Canyon deposit transactions. + deposit_receipt_version: chain_spec + .is_fork_active_at_timestamp( + OptimismHardfork::Canyon, + attributes.payload_attributes.timestamp, + ) + .then_some(1), + })); + + // append sender and transaction to the respective lists + executed_senders.push(sequencer_tx.signer()); + executed_txs.push(sequencer_tx.into_signed()); + } + + // Apply rbuilder block + let mut count = 0; + let iter = pool + .get_transactions(U256::from(parent_block.number + 1)) + .unwrap() + .into_iter(); + for pool_tx in iter { + // Ensure we still have capacity for this transaction + if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit { + let inner = + EVMError::Custom("rbuilder suggested transaction over gas limit!".to_string()); + return Err(PayloadBuilderError::EvmExecutionError(inner)); + } + + // A sequencer's block should never contain blob or deposit transactions from rbuilder. + if pool_tx.is_eip4844() || pool_tx.tx_type() == TxType::Deposit as u8 { + error!("rbuilder suggested blob or deposit transaction!"); + let inner = + EVMError::Custom("rbuilder suggested blob or deposit transaction!".to_string()); + return Err(PayloadBuilderError::EvmExecutionError(inner)); + } + + // check if the job was cancelled, if so we can exit early + if cancel.is_cancelled() { + return Ok(BuildOutcome::Cancelled); + } + + // convert tx to a signed transaction + let tx = pool_tx.try_into_ecrecovered().map_err(|_| { + PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + let env = EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + evm_config.tx_env(&tx), + ); + + // Configure the environment for the block. + let mut evm = evm_config.evm_with_env(&mut db, env); + + let ResultAndState { result, state } = match evm.transact() { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + if matches!(err, InvalidTransaction::NonceTooLow { .. }) { + // if the nonce is too low, we can skip this transaction + error!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + error!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); + } + + let inner = EVMError::Custom("rbuilder transaction errored!".to_string()); + return Err(PayloadBuilderError::EvmExecutionError(inner)); + } + err => { + // this is an error that we should treat as fatal for this attempt + error!("rbuilder provided where an error occured!"); + return Err(PayloadBuilderError::EvmExecutionError(err)); + } + } + } + }; + // drop evm so db is released. + drop(evm); + // commit changes + db.commit(state); + + let gas_used = result.gas_used(); + + // add gas used by the transaction to cumulative gas used, before creating the + // receipt + cumulative_gas_used += gas_used; + + // Push transaction changeset and calculate header bloom filter for receipt. + receipts.push(Some(Receipt { + tx_type: tx.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: None, + deposit_receipt_version: None, + })); + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(Some(base_fee)) + .expect("fee is always valid; execution succeeded"); + total_fees += U256::from(miner_fee) * U256::from(gas_used); + + // append sender and transaction to the respective lists + executed_senders.push(tx.signer()); + executed_txs.push(tx.clone().into_signed()); + count += 1; + } + + trace!("Executed {} txns from rbuilder", count); + + // check if we have a better block + if !is_better_payload(best_payload.as_ref(), total_fees) { + // can skip building the block + return Ok(BuildOutcome::Aborted { + fees: total_fees, + cached_reads, + }); + } + + let WithdrawalsOutcome { + withdrawals_root, + withdrawals, + } = commit_withdrawals( + &mut db, + &chain_spec, + attributes.payload_attributes.timestamp, + attributes.clone().payload_attributes.withdrawals, + )?; + + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + db.merge_transitions(BundleRetention::PlainState); + + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![receipts].into(), + block_number, + Vec::new(), + ); + let receipts_root = execution_outcome + .optimism_receipts_root_slow( + block_number, + chain_spec.as_ref(), + attributes.payload_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 hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); + let (state_root, trie_output) = { + let state_provider = db.database.0.inner.borrow_mut(); + state_provider + .db + .state_root_with_updates(hashed_state.clone()) + .inspect_err(|err| { + warn!(target: "payload_builder", + parent_hash=%parent_block.hash(), + %err, + "failed to calculate state root for empty payload" + ); + })? + }; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&executed_txs); + + // initialize empty blob sidecars. There are no blob transactions on L2. + let blob_sidecars = Vec::new(); + let mut excess_blob_gas = None; + let mut blob_gas_used = None; + + // only determine cancun fields when active + if chain_spec.is_cancun_active_at_timestamp(attributes.payload_attributes.timestamp) { + excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { + let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); + let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); + Some(calculate_excess_blob_gas( + parent_excess_blob_gas, + parent_blob_gas_used, + )) + } else { + // for the first post-fork block, both parent.blob_gas_used and + // parent.excess_blob_gas are evaluated as 0 + Some(calculate_excess_blob_gas(0, 0)) + }; + + blob_gas_used = Some(0); + } + + let header = Header { + parent_hash: parent_block.hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: initialized_block_env.coinbase, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp: attributes.payload_attributes.timestamp, + mix_hash: attributes.payload_attributes.prev_randao, + nonce: BEACON_NONCE, + base_fee_per_gas: Some(base_fee), + number: parent_block.number + 1, + gas_limit: block_gas_limit, + difficulty: U256::ZERO, + gas_used: cumulative_gas_used, + extra_data, + parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_root: None, + }; + + // seal the block + let block = Block { + header, + body: executed_txs, + ommers: vec![], + withdrawals, + requests: None, + }; + + let sealed_block = block.seal_slow(); + debug!(target: "payload_builder", ?sealed_block, "sealed built block"); + + // create the executed block data + let executed = ExecutedBlock { + block: Arc::new(sealed_block.clone()), + senders: Arc::new(executed_senders), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + trie: Arc::new(trie_output), + }; + + let mut payload = OptimismBuiltPayload::new( + attributes.payload_attributes.id, + sealed_block, + total_fees, + chain_spec, + attributes, + Some(executed), + ); + + // extend the payload with the blob sidecars from the executed txs + payload.extend_sidecars(blob_sidecars); + + Ok(BuildOutcome::Better { + payload, + cached_reads, + }) +} diff --git a/crates/builder/op-rbuilder/payload_builder/src/lib.rs b/crates/builder/op-rbuilder/payload_builder/src/lib.rs new file mode 100644 index 00000000..8edcda43 --- /dev/null +++ b/crates/builder/op-rbuilder/payload_builder/src/lib.rs @@ -0,0 +1,8 @@ +//! Payload builder for the op-rbuilder with bundle support. + +#![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(feature = "optimism")] + +pub mod builder; +pub use builder::OpRbuilderPayloadBuilder; diff --git a/crates/builder/op-rbuilder/src/eth_bundle_api.rs b/crates/builder/op-rbuilder/src/eth_bundle_api.rs new file mode 100644 index 00000000..2beb28e4 --- /dev/null +++ b/crates/builder/op-rbuilder/src/eth_bundle_api.rs @@ -0,0 +1,80 @@ +//! An implemention of the *internal* eth_sendBundle api used by rbuilder. +//! +//! Should be refactored into standalone crate if required by other code. + +use jsonrpsee::{ + proc_macros::rpc, + types::{ErrorCode, ErrorObjectOwned}, +}; +use rbuilder::{ + live_builder::order_input::rpc_server::RawCancelBundle, + primitives::{ + serialize::{RawBundle, TxEncoding}, + Bundle, + }, +}; +use tracing::warn; +use transaction_pool_bundle_ext::BundlePoolOperations; + +/// [`EthBundleApiServer`] implementation. +pub struct EthBundleMinimalApi { + pool: BundlePool, +} + +impl EthBundleMinimalApi { + pub fn new(pool: BundlePool) -> Self { + Self { pool } + } +} + +#[async_trait::async_trait] +impl EthCallBundleMinimalApiServer for EthBundleMinimalApi +where + BundlePool: + BundlePoolOperations + Clone + 'static, +{ + fn send_bundle(&self, raw_bundle: RawBundle) -> jsonrpsee::core::RpcResult<()> { + let bundle = match raw_bundle.try_into(TxEncoding::WithBlobData) { + Ok(bundle) => bundle, + Err(err) => { + return Err(ErrorObjectOwned::owned( + ErrorCode::InvalidParams.code(), + format!("Failed to parse bundle: {:?}", err), + None::<()>, + )); + } + }; + tokio::task::spawn_local({ + let pool = self.pool.clone(); + async move { + if let Err(e) = pool.add_bundle(bundle).await { + warn!(?e, "Failed to send bundle"); + } + } + }); + Ok(()) + } + + fn cancel_bundle(&self, request: RawCancelBundle) -> jsonrpsee::core::RpcResult<()> { + // Following rbuilder behavior to ignore errors + tokio::task::spawn_local({ + let pool = self.pool.clone(); + async move { + if let Err(e) = pool.cancel_bundle(request).await { + warn!(?e, "Failed to cancel bundle"); + } + } + }); + Ok(()) + } +} + +/// A subset of the *internal* eth_sendBundle api used by rbuilder. +#[rpc(server, namespace = "eth")] +pub trait EthCallBundleMinimalApi { + #[method(name = "sendBundle")] + fn send_bundle(&self, bundle: RawBundle) -> jsonrpsee::core::RpcResult<()>; + + #[method(name = "cancelBundle")] + fn cancel_bundle(&self, request: RawCancelBundle) -> jsonrpsee::core::RpcResult<()>; +} diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs new file mode 100644 index 00000000..3350d78e --- /dev/null +++ b/crates/builder/op-rbuilder/src/main.rs @@ -0,0 +1,84 @@ +//! The main entry point for `op-rbuilder`. +//! +//! `op-rbuilder` is an OP Stack EL client with block building capabilities, powered by in-process +//! rbuilder. +//! +//! The primary difference between `op-rbuilder` and `op-reth` is the `PayloadBuilder` derives +//! transactions exclusively from rbuilder, rather than directly from its transaction pool. +//! +//! ## Usage +//! +//! It has a new mandatory cli arg `--rbuilder.config` which must point to an rbuilder config file. +//! +//! ## Demo +//! +//! Instructions to demo `op-rbuilder` building blocks for an OP L2, and send txns to it with `mev-flood`: +//! +//! 1. Clone [flashbots/optimism](https://github.com/flashbots/optimism) and checkout the +//! `op-rbuilder` branch. +//! 2. `rm` any existing `reth` chain db +//! 3. Run a clean OP stack: `make devnet-clean && make devnet-down && make devnet-up` +//! 4. Run `op-rbuilder` on port 8547: `cargo run --bin op-rbuilder --features "optimism,jemalloc" -- node +//! --chain ../optimism/.devnet/genesis-l2.json --http --http.port 8547 --authrpc.jwtsecret +//! ../optimism/ops-bedrock/test-jwt-secret.txt --rbuilder.config config-optimism-local.toml` +//! 5. Init `mev-flood`: `docker run mevflood init -r http://host.docker.internal:8547 -s local.json` +//! 6. Run `mev-flood`: `docker run --init -v ${PWD}:/app/cli/deployments mevflood spam -p 3 -t 5 -r http://host.docker.internal:8547 -l local.json` +//! +//! Example starting clean OP Stack in one-line: `rm -rf /Users/liamaharon/Library/Application\ Support/reth && cd ../optimism && make devnet-clean && make devnet-down && make devnet-up && cd ../rbuilder && cargo run --bin op-rbuilder --features "optimism,jemalloc" -- node --chain ../optimism/.devnet/genesis-l2.json --http --http.port 8547 --authrpc.jwtsecret ../optimism/ops-bedrock/test-jwt-secret.txt --rbuilder.config config-optimism-local.toml` + +#![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(feature = "optimism")] + +mod eth_bundle_api; + +use crate::eth_bundle_api::EthCallBundleMinimalApiServer; +use clap_builder::Parser; +use eth_bundle_api::EthBundleMinimalApi; +use op_rbuilder_node_optimism::{args::OpRbuilderArgs, OpRbuilderNode}; +use reth::cli::Cli; +use reth_node_optimism::node::OptimismAddOns; +use reth_optimism_rpc::eth::rpc::SequencerClient; +use tracing as _; + +// jemalloc provides better performance +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +fn main() { + reth_cli_util::sigsegv_handler::install(); + + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + if let Err(err) = Cli::::parse().run(|builder, op_rbuilder_args| async move { + let sequencer_http_arg = op_rbuilder_args.sequencer_http.clone(); + let handle = builder + .with_types::() + .with_components(OpRbuilderNode::components(op_rbuilder_args)) + .with_add_ons::() + .extend_rpc_modules(move |ctx| { + // register sequencer tx forwarder + if let Some(sequencer_http) = sequencer_http_arg { + ctx.registry + .eth_api() + .set_sequencer_client(SequencerClient::new(sequencer_http)); + } + + // register eth bundle api + let ext = EthBundleMinimalApi::new(ctx.registry.pool().clone()); + ctx.modules.merge_configured(ext.into_rpc())?; + + Ok(()) + }) + .launch() + .await?; + + handle.node_exit_future.await + }) { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } +} diff --git a/crates/builder/transaction-pool-bundle-ext/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/Cargo.toml new file mode 100644 index 00000000..9106b7aa --- /dev/null +++ b/crates/builder/transaction-pool-bundle-ext/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "transaction-pool-bundle-ext" +version.workspace = true +edition.workspace = true + +[dependencies] +reth = { workspace = true } +reth-eth-wire-types = { workspace = true } +reth-primitives = { workspace = true } +reth-rpc-types = { workspace = true } +reth-transaction-pool = { workspace = true } + +tokio = { workspace = true } + diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml new file mode 100644 index 00000000..ae059f56 --- /dev/null +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "rbuilder-bundle-pool-operations" +version = "0.1.0" +edition = "2021" + +[dependencies] +transaction-pool-bundle-ext = { path = "../.." } +rbuilder = { path = "../../../rbuilder" } + +reth-primitives = { workspace = true } +reth-provider = { workspace = true } +reth-db-api = { workspace = true } +reth-rpc-types = { workspace = true } + +derive_more = { workspace = true } +eyre = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true } +tracing = { workspace = true } + +[features] +optimism = [ + "reth-primitives/optimism", + "rbuilder/optimism", + "reth-provider/optimism", + "reth-db-api/optimism" +] diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs new file mode 100644 index 00000000..9a6ce200 --- /dev/null +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs @@ -0,0 +1,280 @@ +//! Implementation of [`BundlePoolOperations`] for the classic rbuilder that +//! supports [`EthSendBundle`]s. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use core::fmt; +use std::{fmt::Formatter, path::Path, sync::Arc, time::Duration}; + +use derive_more::From; +use rbuilder::{ + building::{ + builders::{ + block_building_helper::BlockBuildingHelper, ordering_builder::OrderingBuilderConfig, + UnfinishedBlockBuildingSink, UnfinishedBlockBuildingSinkFactory, + }, + Sorting, + }, + live_builder::{ + base_config::load_config_toml_and_env, + config::{create_builders, BuilderConfig, Config, SpecificBuilderConfig}, + order_input::{rpc_server::RawCancelBundle, ReplaceableOrderPoolCommand}, + payload_events::MevBoostSlotData, + SlotSource, + }, + primitives::{Bundle, BundleReplacementKey, Order}, +}; +use reth_db_api::Database; +use reth_primitives::{TransactionSigned, U256}; +use reth_provider::{DatabaseProviderFactory, HeaderProvider, StateProviderFactory}; +use reth_rpc_types::beacon::events::PayloadAttributesEvent; +use tokio::{ + sync::{ + mpsc::{self, error::SendError}, + watch, + }, + task, + time::sleep, +}; +use tokio_util::sync::CancellationToken; +use tracing::error; +use transaction_pool_bundle_ext::BundlePoolOperations; + +/// [`BundlePoolOperations`] implementation which uses components of the +/// [`rbuilder`] under the hood to handle classic [`EthSendBundle`]s. +pub struct BundlePoolOps { + // Channel to stream new [`OrderPool`] events to the rbuilder + orderpool_tx: mpsc::Sender, + // Channel to stream new payload attribute events to rbuilder + payload_attributes_tx: mpsc::UnboundedSender<(PayloadAttributesEvent, Option)>, + /// Channel containing the latest [`BlockBuildingHelper`] recieved from the rbuilder + block_building_helper_rx: watch::Receiver>>, +} + +#[derive(Debug)] +struct OurSlotSource { + /// Channel [`OurSlotSource`] uses to receive payload attributes from reth + payload_attributes_rx: mpsc::UnboundedReceiver<(PayloadAttributesEvent, Option)>, +} + +impl SlotSource for OurSlotSource { + fn recv_slot_channel(self) -> mpsc::UnboundedReceiver { + let (slot_sender, slot_receiver) = mpsc::unbounded_channel(); + + // Spawn a task that receives payload attributes, converts them + // into [`MevBoostSlotData`] for rbuilder, then forwards them. + tokio::spawn(async move { + let mut recv = self.payload_attributes_rx; + while let Some((payload_event, gas_limit)) = recv.recv().await { + let mev_boost_data = MevBoostSlotData { + payload_attributes_event: payload_event, + suggested_gas_limit: gas_limit.unwrap_or(0), + relays: vec![], + slot_data: Default::default(), + }; + + if slot_sender.send(mev_boost_data).is_err() { + error!("Error sending MevBoostSlotData through channel"); + break; + } + } + }); + + // Return the receiver end for SlotSource trait + slot_receiver + } +} + +impl BundlePoolOps { + pub async fn new( + provider: P, + rbuilder_config_path: impl AsRef, + ) -> Result + where + DB: Database + Clone + 'static, + P: DatabaseProviderFactory + StateProviderFactory + HeaderProvider + Clone + 'static, + { + // Create the payload source to trigger new block building + let cancellation_token = CancellationToken::new(); + let (payload_attributes_tx, payload_attributes_rx) = mpsc::unbounded_channel(); + let slot_source = OurSlotSource { + payload_attributes_rx, + }; + + let (block_building_helper_tx, block_building_helper_rx) = watch::channel(None); + let sink_factory = SinkFactory { + block_building_helper_tx, + }; + + // Spawn the builder! + let config: Config = load_config_toml_and_env(rbuilder_config_path)?; + + let builder_strategy = BuilderConfig { + name: "mp-ordering".to_string(), + builder: SpecificBuilderConfig::OrderingBuilder(OrderingBuilderConfig { + discard_txs: true, + sorting: Sorting::MaxProfit, + failed_order_retries: 1, + drop_failed_orders: true, + coinbase_payment: false, + build_duration_deadline_ms: None, + }), + }; + + let builders = create_builders( + vec![builder_strategy], + config.base_config.live_root_hash_config().unwrap(), + config.base_config.root_hash_task_pool().unwrap(), + config.base_config.sbundle_mergeabe_signers(), + ); + + // Build and run the process + let builder = config + .base_config + .create_builder_with_provider_factory::( + cancellation_token, + Box::new(sink_factory), + slot_source, + provider, + ) + .await + .unwrap() + .with_builders(builders); + let orderpool_tx = builder.orderpool_sender.clone(); + + // Spawn in separate thread + let _handle = task::spawn(async move { + // Wait for 5 seconds for reth to init + sleep(Duration::from_secs(5)).await; + + builder.run().await.unwrap(); + + Ok::<(), ()> + }); + + Ok(BundlePoolOps { + block_building_helper_rx, + payload_attributes_tx, + orderpool_tx, + }) + } +} + +impl BundlePoolOperations for BundlePoolOps { + /// Signed eth transaction + type Transaction = TransactionSigned; + type Bundle = Bundle; + type CancelBundleReq = RawCancelBundle; + type Error = Error; + + async fn add_bundle(&self, bundle: Self::Bundle) -> Result<(), Self::Error> { + self.orderpool_tx + .send(ReplaceableOrderPoolCommand::Order(Order::Bundle(bundle))) + .await?; + Ok(()) + } + + async fn cancel_bundle( + &self, + cancel_bundle_request: Self::CancelBundleReq, + ) -> Result<(), Self::Error> { + let key = BundleReplacementKey::new( + cancel_bundle_request.replacement_uuid, + cancel_bundle_request.signing_address, + ); + self.orderpool_tx + .send(ReplaceableOrderPoolCommand::CancelBundle(key)) + .await?; + Ok(()) + } + + fn get_transactions( + &self, + requested_slot: U256, + ) -> Result, Self::Error> { + match *self.block_building_helper_rx.borrow() { + Some(ref block_builder) => { + let rbuilder_slot = block_builder.building_context().block_env.number; + if rbuilder_slot != requested_slot { + return Ok(vec![]); + } + let orders = block_builder.built_block_trace().included_orders.clone(); + let orders = orders + .iter() + .flat_map(|order| order.txs.iter()) + .map(|er| er.clone().into_internal_tx_unsecure().into_signed()) + .collect::>(); + Ok(orders) + } + None => Ok(vec![]), + } + } + + fn notify_payload_attributes_event( + &self, + payload_attributes: PayloadAttributesEvent, + gas_limit: Option, + ) -> Result<(), Self::Error> { + self.payload_attributes_tx + .send((payload_attributes, gas_limit))?; + Ok(()) + } +} + +struct Sink { + /// Channel for rbuilder to notify us of new [`BlockBuildingHelper`]s as it builds blocks + block_building_helper_tx: watch::Sender>>, +} + +impl derive_more::Debug for Sink { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Sink").finish() + } +} + +struct SinkFactory { + /// Channel for rbuilder to notify us of new [`BlockBuildingHelper`]s as it builds blocks + block_building_helper_tx: watch::Sender>>, +} + +impl derive_more::Debug for SinkFactory { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("SinkFactory").finish() + } +} + +impl UnfinishedBlockBuildingSinkFactory for SinkFactory { + fn create_sink( + &mut self, + _slot_data: MevBoostSlotData, + _cancel: CancellationToken, + ) -> Arc { + Arc::new(Sink { + block_building_helper_tx: self.block_building_helper_tx.clone(), + }) + } +} + +impl UnfinishedBlockBuildingSink for Sink { + fn new_block(&self, block: Box) { + self.block_building_helper_tx.send(Some(block)).unwrap() + } + + fn can_use_suggested_fee_recipient_as_coinbase(&self) -> bool { + true + } +} + +#[allow(clippy::large_enum_variant)] +/// [`BundlePoolOperations`] error type. +#[derive(Debug, From)] +pub enum Error { + #[from] + Eyre(eyre::Error), + + #[from] + SendPayloadAttributes(SendError<(PayloadAttributesEvent, Option)>), + + #[from] + SendReplaceableOrderPoolCommand(SendError), +} diff --git a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs new file mode 100644 index 00000000..59efcd4f --- /dev/null +++ b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs @@ -0,0 +1,410 @@ +//! Houses [`BundleSupportedPool`]. + +use reth::providers::ChangedAccount; +use reth_eth_wire_types::HandleMempoolData; +use reth_primitives::{Address, PooledTransactionsElement, TxHash, U256}; +use reth_rpc_types::{beacon::events::PayloadAttributesEvent, BlobTransactionSidecar}; +use reth_transaction_pool::{ + AllPoolTransactions, AllTransactionsEvents, BestTransactions, BestTransactionsAttributes, + BlobStore, BlobStoreError, BlockInfo, CanonicalStateUpdate, GetPooledTransactionLimit, + NewBlobSidecar, NewTransactionEvent, Pool, PoolConfig, PoolResult, PoolSize, + PropagatedTransactions, TransactionEvents, TransactionListenerKind, TransactionOrdering, + TransactionOrigin, TransactionPool, TransactionPoolExt as TransactionPoolBlockInfoExt, + TransactionValidator, ValidPoolTransaction, +}; +use std::{collections::HashSet, future::Future, sync::Arc}; +use tokio::sync::mpsc::Receiver; + +use crate::{traits::BundlePoolOperations, TransactionPoolBundleExt}; + +/// Allows easily creating a `Pool` type for reth's `PoolBuilder` that supports +/// a new [`BundlePool`] running alongside [`Pool`]. +/// +/// To be used in place of the reth [`Pool`] when defining the `Pool` type +/// for reth's `PoolBuilder`. +/// +/// Just as logic for the [`Pool`] is generic based on `V`, `T`, `S` generics, bundle pool +/// logic is generic based on a new `B` generic that implements [`BundlePoolOperations`]. +/// +/// Achieves this by implementing [`TransactionPool`] and [`BundlePoolOperations`], +/// and therefore also [`TransactionPoolBundleExt`]. +/// +/// ## Example +/// +/// ```ignore +/// /// An extended optimism transaction pool with bundle support. +/// #[derive(Debug, Default, Clone, Copy)] +/// pub struct CustomPoolBuilder; +/// +/// pub type MyCustomTransactionPoolWithBundleSupport = BundleSupportedPool< +/// TransactionValidationTaskExecutor>, +/// CoinbaseTipOrdering, +/// S, +/// MyBundlePoolOperationsImplementation, +/// >; +/// +/// impl PoolBuilder for CustomPoolBuilder +/// where +/// Node: FullNodeTypes, +/// { +/// type Pool = MyCustomTransactionPoolWithBundleSupport; +/// // the rest of the PoolBuilder implementation... +/// ``` +#[derive(Debug)] +pub struct BundleSupportedPool { + /// Arc'ed instance of [`Pool`] internals + tx_pool: Arc>, + /// Arc'ed instance of the [`BundlePool`] internals + bundle_pool: Arc>, +} + +impl BundleSupportedPool +where + V: TransactionValidator, + T: TransactionOrdering::Transaction>, + S: BlobStore, + B: BundlePoolOperations, +{ + pub fn new( + validator: V, + ordering: T, + blob_store: S, + bundle_ops: B, + tx_pool_config: PoolConfig, + ) -> Self { + Self { + tx_pool: Arc::new(Pool::::new( + validator, + ordering, + blob_store, + tx_pool_config, + )), + bundle_pool: Arc::new(BundlePool::::new(bundle_ops)), + } + } +} + +/// Houses generic bundle logic. +#[derive(Debug, Default)] +struct BundlePool { + pub ops: B, +} + +impl BundlePool { + fn new(ops: B) -> Self { + Self { ops } + } +} + +/// [`TransactionPool`] requires implementors to be [`Clone`]. +impl Clone for BundleSupportedPool { + fn clone(&self) -> Self { + Self { + tx_pool: Arc::clone(&self.tx_pool), + bundle_pool: Arc::clone(&self.bundle_pool), + } + } +} + +/// Implements the [`TransactionPool`] interface by delegating to the inner `tx_pool`. +/// TODO: Use a crate like `delegate!` or `ambassador` to automate this. +impl TransactionPool for BundleSupportedPool +where + V: TransactionValidator, + T: TransactionOrdering::Transaction>, + S: BlobStore, + B: BundlePoolOperations, +{ + type Transaction = T::Transaction; + + fn pool_size(&self) -> PoolSize { + self.tx_pool.pool_size() + } + + fn block_info(&self) -> BlockInfo { + self.tx_pool.block_info() + } + + async fn add_transaction_and_subscribe( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> PoolResult { + self.tx_pool + .add_transaction_and_subscribe(origin, transaction) + .await + } + + async fn add_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> PoolResult { + self.tx_pool.add_transaction(origin, transaction).await + } + + async fn add_transactions( + &self, + origin: TransactionOrigin, + transactions: Vec, + ) -> Vec> { + self.tx_pool.add_transactions(origin, transactions).await + } + + fn transaction_event_listener(&self, tx_hash: TxHash) -> Option { + self.tx_pool.transaction_event_listener(tx_hash) + } + + fn all_transactions_event_listener(&self) -> AllTransactionsEvents { + self.tx_pool.all_transactions_event_listener() + } + + fn pending_transactions_listener_for(&self, kind: TransactionListenerKind) -> Receiver { + self.tx_pool.pending_transactions_listener_for(kind) + } + + fn blob_transaction_sidecars_listener(&self) -> Receiver { + self.tx_pool.blob_transaction_sidecars_listener() + } + + fn get_pending_transactions_by_origin( + &self, + origin: TransactionOrigin, + ) -> Vec>> { + self.tx_pool.get_pending_transactions_by_origin(origin) + } + + fn new_transactions_listener_for( + &self, + kind: TransactionListenerKind, + ) -> Receiver> { + self.tx_pool.new_transactions_listener_for(kind) + } + + fn pooled_transaction_hashes(&self) -> Vec { + self.tx_pool.pooled_transaction_hashes() + } + + fn pooled_transaction_hashes_max(&self, max: usize) -> Vec { + self.tx_pool.pooled_transaction_hashes_max(max) + } + + fn pooled_transactions(&self) -> Vec>> { + self.tx_pool.pooled_transactions() + } + + fn pooled_transactions_max( + &self, + max: usize, + ) -> Vec>> { + self.tx_pool.pooled_transactions_max(max) + } + + fn get_pooled_transaction_elements( + &self, + tx_hashes: Vec, + limit: GetPooledTransactionLimit, + ) -> Vec { + self.tx_pool + .get_pooled_transaction_elements(tx_hashes, limit) + } + + fn get_pooled_transaction_element(&self, tx_hash: TxHash) -> Option { + self.tx_pool.get_pooled_transaction_element(tx_hash) + } + + fn best_transactions( + &self, + ) -> Box>>> { + self.tx_pool.best_transactions() + } + + #[allow(deprecated)] + fn best_transactions_with_base_fee( + &self, + base_fee: u64, + ) -> Box>>> { + self.tx_pool.best_transactions_with_base_fee(base_fee) + } + + fn best_transactions_with_attributes( + &self, + best_transactions_attributes: BestTransactionsAttributes, + ) -> Box>>> { + self.tx_pool + .best_transactions_with_attributes(best_transactions_attributes) + } + + fn pending_transactions(&self) -> Vec>> { + self.tx_pool.pending_transactions() + } + + fn queued_transactions(&self) -> Vec>> { + self.tx_pool.queued_transactions() + } + + fn all_transactions(&self) -> AllPoolTransactions { + self.tx_pool.all_transactions() + } + + fn remove_transactions( + &self, + hashes: Vec, + ) -> Vec>> { + self.tx_pool.remove_transactions(hashes) + } + + fn retain_unknown(&self, announcement: &mut A) + where + A: HandleMempoolData, + { + self.tx_pool.retain_unknown(announcement) + } + + fn get(&self, tx_hash: &TxHash) -> Option>> { + self.tx_pool.get(tx_hash) + } + + fn get_all(&self, txs: Vec) -> Vec>> { + self.tx_pool.get_all(txs) + } + + fn on_propagated(&self, txs: PropagatedTransactions) { + self.tx_pool.on_propagated(txs) + } + + fn get_transactions_by_sender( + &self, + sender: Address, + ) -> Vec>> { + self.tx_pool.get_transactions_by_sender(sender) + } + + fn get_transaction_by_sender_and_nonce( + &self, + sender: Address, + nonce: u64, + ) -> Option>> { + self.tx_pool + .get_transaction_by_sender_and_nonce(sender, nonce) + } + + fn get_transactions_by_origin( + &self, + origin: TransactionOrigin, + ) -> Vec>> { + self.tx_pool.get_transactions_by_origin(origin) + } + + fn unique_senders(&self) -> HashSet
{ + self.tx_pool.unique_senders() + } + + fn get_blob(&self, tx_hash: TxHash) -> Result, BlobStoreError> { + self.tx_pool.get_blob(tx_hash) + } + + fn get_all_blobs( + &self, + tx_hashes: Vec, + ) -> Result, BlobStoreError> { + self.tx_pool.get_all_blobs(tx_hashes) + } + + fn get_all_blobs_exact( + &self, + tx_hashes: Vec, + ) -> Result, BlobStoreError> { + self.tx_pool.get_all_blobs_exact(tx_hashes) + } +} + +/// Implements the [`BundlePoolOperations`] interface by delegating to the inner `bundle_pool`. +/// TODO: Use a crate like `delegate!` or `ambassador` to automate this. +impl BundlePoolOperations for BundleSupportedPool +where + V: TransactionValidator, + T: TransactionOrdering::Transaction>, + S: BlobStore, + B: BundlePoolOperations, +{ + type Bundle = ::Bundle; + type CancelBundleReq = ::CancelBundleReq; + type Transaction = ::Transaction; + type Error = ::Error; + + fn add_bundle( + &self, + bundle: Self::Bundle, + ) -> impl Future> + Send { + self.bundle_pool.ops.add_bundle(bundle) + } + + fn cancel_bundle( + &self, + cancel_bundle_req: Self::CancelBundleReq, + ) -> impl Future> + Send { + self.bundle_pool.ops.cancel_bundle(cancel_bundle_req) + } + + fn get_transactions( + &self, + slot: U256, + ) -> Result, Self::Error> { + self.bundle_pool.ops.get_transactions(slot) + } + + fn notify_payload_attributes_event( + &self, + payload_attributes: PayloadAttributesEvent, + gas_limit: Option, + ) -> Result<(), Self::Error> { + self.bundle_pool + .ops + .notify_payload_attributes_event(payload_attributes, gas_limit) + } +} + +// Finally, now that [`BundleSupportedPool`] has both [`TransactionPool`] and +// [`BundlePoolOperations`] implemented, it can implement [`TransactionPoolBundleExt`]. +impl TransactionPoolBundleExt for BundleSupportedPool +where + V: TransactionValidator, + T: TransactionOrdering::Transaction>, + S: BlobStore, + B: BundlePoolOperations, +{ +} + +/// [`TransactionPool`] often requires implementing the block info extension. +impl TransactionPoolBlockInfoExt for BundleSupportedPool +where + V: TransactionValidator, + T: TransactionOrdering::Transaction>, + S: BlobStore, + B: BundlePoolOperations, +{ + fn set_block_info(&self, info: BlockInfo) { + self.tx_pool.set_block_info(info) + } + + fn on_canonical_state_change(&self, update: CanonicalStateUpdate<'_>) { + self.tx_pool.on_canonical_state_change(update); + } + + fn update_accounts(&self, accounts: Vec) { + self.tx_pool.update_accounts(accounts); + } + + fn delete_blob(&self, tx: TxHash) { + self.tx_pool.delete_blob(tx) + } + + fn delete_blobs(&self, txs: Vec) { + self.tx_pool.delete_blobs(txs) + } + + fn cleanup_blobs(&self) { + self.tx_pool.cleanup_blobs() + } +} diff --git a/crates/builder/transaction-pool-bundle-ext/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/src/lib.rs new file mode 100644 index 00000000..57ff22c1 --- /dev/null +++ b/crates/builder/transaction-pool-bundle-ext/src/lib.rs @@ -0,0 +1,28 @@ +//! Crate facilitating simple extension of the reth `TransationPool` with +//! arbitrary bundle support. +//! +//! Contains +//! - A reth `TransactionPool` trait extension ([`TransactionPoolBundleExt`]) +//! allowing bundle support to be added into reth nodes. +//! +//! - A [`TransactionPoolBundleExt`] implementation [`BundleSupportedPool`], +//! which encapsulates the reth `TransactionPool` and a generic implementation +//! of the [`BundlePoolOperations`] trait. +//! +//! ## Usage +//! +//! 1. When implementing `PoolBuilder` on your node, pass a custom `type Pool ...` +//! that is a [`BundleSupportedPool`] type. Your [`BundleSupportedPool`] type +//! must specify your chosen [`BundlePoolOperations`] implementation, which you +//! can initialise inside `fn build_pool` when you build the pool. +//! 2. Whereever you require access to bundles, e.g. when implementing RPCs or +//! `PayloadServiceBuilder` on your node, modify the `Pool` trait bound +//! replacing `TransactionPool` with [`TransactionPoolBundleExt`]. This allows access to +//! [`BundlePoolOperations`] methods almost everywhere in the node. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +mod bundle_supported_pool; +mod traits; +pub use bundle_supported_pool::BundleSupportedPool; +pub use traits::{BundlePoolOperations, TransactionPoolBundleExt}; diff --git a/crates/builder/transaction-pool-bundle-ext/src/traits.rs b/crates/builder/transaction-pool-bundle-ext/src/traits.rs new file mode 100644 index 00000000..1d21d0b4 --- /dev/null +++ b/crates/builder/transaction-pool-bundle-ext/src/traits.rs @@ -0,0 +1,51 @@ +//! [`TransactionPoolBundleExt`] implementation generic over any bundle and network type. + +use reth_primitives::U256; +use reth_rpc_types::beacon::events::PayloadAttributesEvent; +use reth_transaction_pool::TransactionPool; +use std::{fmt::Debug, future::Future}; + +/// Bundle-related operations. +/// +/// This API is under active development. +pub trait BundlePoolOperations: Sync + Send { + /// Bundle type + type Bundle: Send; + + /// Cancel bundle request type + type CancelBundleReq; + + /// Error type + type Error: Debug; + + /// Transactions type + type Transaction: Debug; + + /// Add a bundle to the pool, returning an Error if invalid. + fn add_bundle( + &self, + bundle: Self::Bundle, + ) -> impl Future> + Send; + + /// Make a best-effort attempt to cancel a bundle + fn cancel_bundle( + &self, + hash: Self::CancelBundleReq, + ) -> impl Future> + Send; + + /// Get transactions to be included in the head of the next block + fn get_transactions( + &self, + slot: U256, + ) -> Result, Self::Error>; + + /// Notify new payload attributes to use + fn notify_payload_attributes_event( + &self, + payload_attributes: PayloadAttributesEvent, + gas_limit: Option, + ) -> Result<(), Self::Error>; +} + +/// Extension for [TransactionPool] trait adding support for [BundlePoolOperations]. +pub trait TransactionPoolBundleExt: TransactionPool + BundlePoolOperations {} From e3466ff9cc1e9386c375171feb34edd1881da7d7 Mon Sep 17 00:00:00 2001 From: liamaharon Date: Mon, 25 Nov 2024 11:00:07 +0400 Subject: [PATCH 002/262] op-rbuilder telemetry (#252) Spin up telemetry servers for rbuilder when run in `op-rbuilder`. --- .../bundle_pool_ops/rbuilder/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs index 9a6ce200..cbca555f 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs @@ -7,6 +7,7 @@ use core::fmt; use std::{fmt::Formatter, path::Path, sync::Arc, time::Duration}; use derive_more::From; +use rbuilder::live_builder::cli::LiveBuilderConfig; use rbuilder::{ building::{ builders::{ @@ -23,6 +24,7 @@ use rbuilder::{ SlotSource, }, primitives::{Bundle, BundleReplacementKey, Order}, + telemetry, }; use reth_db_api::Database; use reth_primitives::{TransactionSigned, U256}; @@ -147,6 +149,22 @@ impl BundlePoolOps { // Wait for 5 seconds for reth to init sleep(Duration::from_secs(5)).await; + // Spawn redacted server that is safe for tdx builders to expose + telemetry::servers::redacted::spawn( + config.base_config().redacted_telemetry_server_address(), + ) + .await + .expect("Failed to start redacted telemetry server"); + + // Spawn debug server that exposes detailed operational information + telemetry::servers::full::spawn( + config.base_config().full_telemetry_server_address(), + config.version_for_telemetry(), + config.base_config().log_enable_dynamic, + ) + .await + .expect("Failed to start full telemetry server"); + builder.run().await.unwrap(); Ok::<(), ()> From 6cbbf3e9367fd38509ec9b1ee311293724e8b698 Mon Sep 17 00:00:00 2001 From: ZanCorDX <126988525+ZanCorDX@users.noreply.github.com> Date: Thu, 28 Nov 2024 18:05:28 -0300 Subject: [PATCH 003/262] Reth v1.1.1 (#255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary - Dependency hell. - In lots of generics (eg:ProviderFactory) the need of DB+Spec was replaced for NodeTypesWithDB. - DatabaseProviderFactory DB now is asociated type and have 2 more. In lots of places now we need Provider: BlockReader. - Lots of types moved to alloy_primitives. - encode/decode_enveloped now are XXX_2718 with minimal changes. - New alloy TrieNode::EmptyRoot . - CachedReads moved to reth::revm::cached. - AsyncStateRoot replaced by ParallelStateRoot (https://github.com/paradigmxyz/reth/pull/11213). - Some HashMap/Set changed to custom versions (eg: alloy_primitives::map::HashMap). - TransactionPool needs stronger restrictions on Transaction. - Updated block finalization (pectra). - Pectra relay submition. ## 💡 Motivation and Context - We needed this for Pectra and to be up to date with reth. - My life was going so well that I needed something to balance it so I did this boring PR. ## ✅ I have completed the following steps: * [X] Run `make lint` * [X] Run `make test` * [ ] Added tests (if applicable) --------- Co-authored-by: Liam Aharon --- crates/builder/op-rbuilder/Cargo.toml | 22 +- crates/builder/op-rbuilder/node/Cargo.toml | 19 +- crates/builder/op-rbuilder/node/src/args.rs | 21 +- crates/builder/op-rbuilder/node/src/node.rs | 185 +++++---- .../op-rbuilder/payload_builder/Cargo.toml | 27 +- .../payload_builder/src/builder.rs | 383 +++++++----------- crates/builder/op-rbuilder/src/main.rs | 85 ++-- .../transaction-pool-bundle-ext/Cargo.toml | 7 +- .../bundle_pool_ops/rbuilder/Cargo.toml | 9 +- .../bundle_pool_ops/rbuilder/src/lib.rs | 14 +- .../src/bundle_supported_pool.rs | 99 ++++- .../transaction-pool-bundle-ext/src/traits.rs | 4 +- 12 files changed, 475 insertions(+), 400 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index c75a6f0f..48ce3d66 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -9,9 +9,9 @@ op-rbuilder-node-optimism = { path = "./node" } transaction-pool-bundle-ext = { path = "../transaction-pool-bundle-ext" } reth.workspace = true -reth-node-optimism.workspace = true +reth-optimism-node.workspace = true +reth-optimism-cli.workspace = true reth-cli-util.workspace = true -reth-optimism-rpc.workspace = true tokio.workspace = true tracing.workspace = true @@ -20,7 +20,7 @@ async-trait = { workspace = true } clap_builder = { workspace = true } [target.'cfg(unix)'.dependencies] -tikv-jemallocator = { version = "0.5.0", optional = true } +tikv-jemallocator = { version = "0.6", optional = true } [dev-dependencies] reth-discv4.workspace = true @@ -28,11 +28,16 @@ reth-discv4.workspace = true [features] default = ["jemalloc"] -jemalloc = ["dep:tikv-jemallocator"] +jemalloc = [ + "dep:tikv-jemallocator", + "reth-cli-util/jemalloc", + "reth-optimism-cli/jemalloc" +] jemalloc-prof = [ "jemalloc", "tikv-jemallocator?/profiling", - "reth/jemalloc-prof" + "reth/jemalloc-prof", + "reth-cli-util/jemalloc-prof" ] min-error-logs = ["tracing/release_max_level_error"] @@ -42,11 +47,10 @@ min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] optimism = [ - "reth-node-optimism/optimism", - "reth/optimism", - "reth-optimism-rpc/optimism", + "rbuilder/optimism", + "reth-optimism-node/optimism", "op-rbuilder-node-optimism/optimism", - "rbuilder/optimism" + "reth-optimism-cli/optimism" ] redact-sensitive = [ diff --git a/crates/builder/op-rbuilder/node/Cargo.toml b/crates/builder/op-rbuilder/node/Cargo.toml index 350dc79d..8ec3d539 100644 --- a/crates/builder/op-rbuilder/node/Cargo.toml +++ b/crates/builder/op-rbuilder/node/Cargo.toml @@ -9,17 +9,19 @@ transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } rbuilder-bundle-pool-operations = { path = "../../transaction-pool-bundle-ext/bundle_pool_ops/rbuilder" } # reth -reth-chainspec.workspace = true reth-payload-builder.workspace = true reth-primitives.workspace = true reth-basic-payload-builder.workspace = true reth-node-builder.workspace = true +reth-node-api.workspace = true +reth-trie-db.workspace = true reth-tracing.workspace = true reth-provider.workspace = true reth-transaction-pool.workspace = true reth-evm.workspace = true -reth-evm-optimism.workspace = true -reth-node-optimism = { workspace = true } +reth-optimism-evm.workspace = true +reth-optimism-chainspec.workspace = true +reth-optimism-node = { workspace = true } # async tracing.workspace = true @@ -30,11 +32,10 @@ eyre.workspace = true [features] optimism = [ - "reth-node-optimism/optimism", - "reth-chainspec/optimism", - "reth-provider/optimism", - "reth-evm-optimism/optimism", - "op-rbuilder-payload-builder/optimism", "rbuilder-bundle-pool-operations/optimism", - "reth-primitives/optimism" + "reth-optimism-evm/optimism", + "reth-optimism-node/optimism", + "reth-primitives/optimism", + "reth-provider/optimism", + "op-rbuilder-payload-builder/optimism" ] diff --git a/crates/builder/op-rbuilder/node/src/args.rs b/crates/builder/op-rbuilder/node/src/args.rs index c0ea455a..16f3c67b 100644 --- a/crates/builder/op-rbuilder/node/src/args.rs +++ b/crates/builder/op-rbuilder/node/src/args.rs @@ -6,6 +6,10 @@ use std::path::PathBuf; +use reth_node_builder::engine_tree_config::{ + DEFAULT_MEMORY_BLOCK_BUFFER_TARGET, DEFAULT_PERSISTENCE_THRESHOLD, +}; + /// Parameters for rollup configuration #[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] #[command(next_help_heading = "Rollup")] @@ -38,10 +42,25 @@ pub struct OpRbuilderArgs { #[arg(long = "rollup.discovery.v4", default_value = "false")] pub discovery_v4: bool, - /// Enable the engine2 experimental features on op-reth binary + /// Enable the experimental engine features on reth binary + /// + /// DEPRECATED: experimental engine is default now, use --engine.legacy to enable the legacy + /// functionality #[arg(long = "engine.experimental", default_value = "false")] pub experimental: bool, + /// Enable the legacy engine on reth binary + #[arg(long = "engine.legacy", default_value = "false")] + pub legacy: bool, + + /// Configure persistence threshold for engine experimental. + #[arg(long = "engine.persistence-threshold", conflicts_with = "legacy", default_value_t = DEFAULT_PERSISTENCE_THRESHOLD)] + pub persistence_threshold: u64, + + /// Configure the target number of blocks to keep in memory. + #[arg(long = "engine.memory-block-buffer-target", conflicts_with = "legacy", default_value_t = DEFAULT_MEMORY_BLOCK_BUFFER_TARGET)] + pub memory_block_buffer_target: u64, + /// Enable the engine2 experimental features on op-reth binary #[arg(long = "rbuilder.config")] pub rbuilder_config_path: PathBuf, diff --git a/crates/builder/op-rbuilder/node/src/node.rs b/crates/builder/op-rbuilder/node/src/node.rs index 47ce9c8f..4e24cb8e 100644 --- a/crates/builder/op-rbuilder/node/src/node.rs +++ b/crates/builder/op-rbuilder/node/src/node.rs @@ -5,37 +5,46 @@ use rbuilder_bundle_pool_operations::BundlePoolOps; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; -use reth_chainspec::ChainSpec; use reth_evm::ConfigureEvm; -use reth_evm_optimism::OptimismEvmConfig; +use reth_node_api::NodePrimitives; use reth_node_builder::{ components::{ComponentsBuilder, PayloadServiceBuilder, PoolBuilder}, node::{FullNodeTypes, NodeTypes}, - BuilderContext, Node, PayloadBuilderConfig, + BuilderContext, Node, NodeAdapter, NodeComponentsBuilder, NodeTypesWithEngine, + PayloadBuilderConfig, }; -use reth_node_optimism::{ - node::{ - OptimismAddOns, OptimismConsensusBuilder, OptimismExecutorBuilder, OptimismNetworkBuilder, - }, +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_evm::OpEvmConfig; +use reth_optimism_node::{ + node::{OpConsensusBuilder, OpExecutorBuilder, OpNetworkBuilder, OpPrimitives, OptimismAddOns}, txpool::OpTransactionValidator, - OptimismEngineTypes, + OpEngineTypes, }; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; -use reth_primitives::TransactionSigned; -use reth_provider::CanonStateSubscriptions; +use reth_primitives::{Header, TransactionSigned}; +use reth_provider::{BlockReader, CanonStateSubscriptions, DatabaseProviderFactory}; use reth_tracing::tracing::{debug, info}; use reth_transaction_pool::{ blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPooledTransaction, TransactionValidationTaskExecutor, }; -use std::path::PathBuf; +use reth_trie_db::MerklePatriciaTrie; +use std::{path::PathBuf, sync::Arc}; use transaction_pool_bundle_ext::{ BundlePoolOperations, BundleSupportedPool, TransactionPoolBundleExt, }; use crate::args::OpRbuilderArgs; -/// Type configuration for an OP rbuilder node. +/// Optimism primitive types. +#[derive(Debug)] +pub struct OpRbuilderPrimitives; + +impl NodePrimitives for OpRbuilderPrimitives { + type Block = reth_primitives::Block; +} + +/// Type configuration for an Optimism rbuilder. #[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct OpRbuilderNode { @@ -56,12 +65,15 @@ impl OpRbuilderNode { Node, OpRbuilderPoolBuilder, OpRbuilderPayloadServiceBuilder, - OptimismNetworkBuilder, - OptimismExecutorBuilder, - OptimismConsensusBuilder, + OpNetworkBuilder, + OpExecutorBuilder, + OpConsensusBuilder, > where - Node: FullNodeTypes, + Node: FullNodeTypes< + Types: NodeTypesWithEngine, + >, + <::Provider as DatabaseProviderFactory>::Provider: BlockReader, { let OpRbuilderArgs { disable_txpool_gossip, @@ -73,44 +85,52 @@ impl OpRbuilderNode { ComponentsBuilder::default() .node_types::() .pool(OpRbuilderPoolBuilder::new(rbuilder_config_path)) - .payload(OpRbuilderPayloadServiceBuilder::new( - compute_pending_block, - OptimismEvmConfig::default(), - )) - .network(OptimismNetworkBuilder { + .payload(OpRbuilderPayloadServiceBuilder::new(compute_pending_block)) + .network(OpNetworkBuilder { disable_txpool_gossip, disable_discovery_v4: !discovery_v4, }) - .executor(OptimismExecutorBuilder::default()) - .consensus(OptimismConsensusBuilder::default()) + .executor(OpExecutorBuilder::default()) + .consensus(OpConsensusBuilder::default()) } } impl Node for OpRbuilderNode where - N: FullNodeTypes, + N: FullNodeTypes>, + <::Provider as DatabaseProviderFactory>::Provider: BlockReader, { type ComponentsBuilder = ComponentsBuilder< N, OpRbuilderPoolBuilder, OpRbuilderPayloadServiceBuilder, - OptimismNetworkBuilder, - OptimismExecutorBuilder, - OptimismConsensusBuilder, + OpNetworkBuilder, + OpExecutorBuilder, + OpConsensusBuilder, >; - type AddOns = OptimismAddOns; + type AddOns = OptimismAddOns< + NodeAdapter>::Components>, + >; fn components_builder(&self) -> Self::ComponentsBuilder { let Self { args } = self; Self::components(args.clone()) } + + fn add_ons(&self) -> Self::AddOns { + OptimismAddOns::new(self.args.sequencer_http.clone()) + } } impl NodeTypes for OpRbuilderNode { - type Primitives = (); - type Engine = OptimismEngineTypes; - type ChainSpec = ChainSpec; + type Primitives = OpPrimitives; + type ChainSpec = OpChainSpec; + type StateCommitment = MerklePatriciaTrie; +} + +impl NodeTypesWithEngine for OpRbuilderNode { + type Engine = OpEngineTypes; } /// An extended optimism transaction pool with bundle support. @@ -138,7 +158,8 @@ pub type OpRbuilderTransactionPool = BundleSupportedPool< impl PoolBuilder for OpRbuilderPoolBuilder where - Node: FullNodeTypes, + Node: FullNodeTypes>, + <::Provider as DatabaseProviderFactory>::Provider: BlockReader, { type Pool = OpRbuilderTransactionPool; @@ -146,21 +167,23 @@ where let data_dir = ctx.config().datadir(); let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?; - let validator = TransactionValidationTaskExecutor::eth_builder(ctx.chain_spec()) - .with_head_timestamp(ctx.head().timestamp) - .kzg_settings(ctx.kzg_settings()?) - .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) - .build_with_tasks( - ctx.provider().clone(), - ctx.task_executor().clone(), - blob_store.clone(), - ) - .map(|validator| { - OpTransactionValidator::new(validator) - // In --dev mode we can't require gas fees because we're unable to decode the L1 - // block info - .require_l1_data_gas_fee(!ctx.config().dev.dev) - }); + let validator = TransactionValidationTaskExecutor::eth_builder(Arc::new( + ctx.chain_spec().inner.clone(), + )) + .with_head_timestamp(ctx.head().timestamp) + .kzg_settings(ctx.kzg_settings()?) + .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) + .build_with_tasks( + ctx.provider().clone(), + ctx.task_executor().clone(), + blob_store.clone(), + ) + .map(|validator| { + OpTransactionValidator::new(validator) + // In --dev mode we can't require gas fees because we're unable to decode the L1 + // block info + .require_l1_data_gas_fee(!ctx.config().dev.dev) + }); let bundle_ops = BundlePoolOps::new(ctx.provider().clone(), self.rbuilder_config_path) .await @@ -214,9 +237,9 @@ where } } -/// A op-rbuilder payload service builder +/// An OP rbuilder payload service builder. #[derive(Debug, Default, Clone)] -pub struct OpRbuilderPayloadServiceBuilder { +pub struct OpRbuilderPayloadServiceBuilder { /// By default the pending block equals the latest block /// to save resources and not leak txs from the tx-pool, /// this flag enables computing of the pending block @@ -226,38 +249,38 @@ pub struct OpRbuilderPayloadServiceBuilder { /// will use the payload attributes from the latest block. Note /// that this flag is not yet functional. pub compute_pending_block: bool, - /// The EVM configuration to use for the payload builder. - pub evm_config: EVM, } -impl OpRbuilderPayloadServiceBuilder { - /// Create a new instance with the given `compute_pending_block` flag and evm config. - pub const fn new(compute_pending_block: bool, evm_config: EVM) -> Self { +impl OpRbuilderPayloadServiceBuilder { + /// Create a new instance with the given `compute_pending_block` flag. + pub const fn new(compute_pending_block: bool) -> Self { Self { compute_pending_block, - evm_config, } } -} -impl PayloadServiceBuilder for OpRbuilderPayloadServiceBuilder -where - Node: FullNodeTypes, - Pool: TransactionPoolBundleExt - + BundlePoolOperations - + Unpin - + 'static, - EVM: ConfigureEvm, -{ - async fn spawn_payload_service( + /// A helper method to initialize [`PayloadBuilderService`] with the given EVM config. + pub fn spawn( self, + evm_config: Evm, ctx: &BuilderContext, pool: Pool, - ) -> eyre::Result> { - let payload_builder = op_rbuilder_payload_builder::OpRbuilderPayloadBuilder::new( - OptimismEvmConfig::default(), - ) - .set_compute_pending_block(self.compute_pending_block); + ) -> eyre::Result> + where + Node: FullNodeTypes< + Types: NodeTypesWithEngine, + >, + <::Provider as DatabaseProviderFactory>::Provider: BlockReader, + Pool: TransactionPoolBundleExt + + BundlePoolOperations + + Unpin + + 'static, + + Evm: ConfigureEvm
, + { + let payload_builder = + op_rbuilder_payload_builder::OpRbuilderPayloadBuilder::new(evm_config) + .set_compute_pending_block(self.compute_pending_block); let conf = ctx.payload_builder_config(); let payload_job_config = BasicPayloadJobGeneratorConfig::default() @@ -272,7 +295,6 @@ where pool, ctx.task_executor().clone(), payload_job_config, - ctx.chain_spec(), payload_builder, ); let (payload_service, payload_builder) = @@ -284,3 +306,22 @@ where Ok(payload_builder) } } + +impl PayloadServiceBuilder for OpRbuilderPayloadServiceBuilder +where + Node: + FullNodeTypes>, + <::Provider as DatabaseProviderFactory>::Provider: BlockReader, + Pool: TransactionPoolBundleExt + + BundlePoolOperations + + Unpin + + 'static, +{ + async fn spawn_payload_service( + self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result> { + self.spawn(OpEvmConfig::new(ctx.chain_spec()), ctx, pool) + } +} diff --git a/crates/builder/op-rbuilder/payload_builder/Cargo.toml b/crates/builder/op-rbuilder/payload_builder/Cargo.toml index ca4e1ca6..38885cf5 100644 --- a/crates/builder/op-rbuilder/payload_builder/Cargo.toml +++ b/crates/builder/op-rbuilder/payload_builder/Cargo.toml @@ -12,13 +12,16 @@ transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } reth-chainspec.workspace = true reth-primitives.workspace = true reth-revm.workspace = true -reth-rpc-types.workspace = true reth-provider.workspace = true reth-evm.workspace = true -reth-evm-optimism.workspace = true -reth-node-optimism.workspace = true +reth-optimism-evm.workspace = true +reth-optimism-consensus.workspace = true +reth-optimism-chainspec.workspace = true +reth-optimism-forks.workspace = true +reth-optimism-node.workspace = true reth-execution-types.workspace = true reth-payload-builder.workspace = true +reth-payload-primitives.workspace = true reth-basic-payload-builder.workspace = true reth-trie.workspace = true reth-chain-state.workspace = true @@ -26,19 +29,23 @@ reth-optimism-payload-builder.workspace = true # ethereum revm.workspace = true +alloy-consensus.workspace = true +op-alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-rpc-types-engine.workspace = true +alloy-rpc-types-beacon.workspace = true # misc tracing.workspace = true [features] optimism = [ - "reth-chainspec/optimism", - "reth-primitives/optimism", - "reth-provider/optimism", - "reth-node-optimism/optimism", - "reth-evm-optimism/optimism", - "reth-revm/optimism", "reth-execution-types/optimism", + "reth-optimism-evm/optimism", + "reth-optimism-node/optimism", "reth-optimism-payload-builder/optimism", - "revm/optimism" + "reth-primitives/optimism", + "reth-provider/optimism", + "revm/optimism", + "reth-optimism-consensus/optimism" ] diff --git a/crates/builder/op-rbuilder/payload_builder/src/builder.rs b/crates/builder/op-rbuilder/payload_builder/src/builder.rs index a00db5e6..84e75c26 100644 --- a/crates/builder/op-rbuilder/payload_builder/src/builder.rs +++ b/crates/builder/op-rbuilder/payload_builder/src/builder.rs @@ -1,28 +1,32 @@ //! Optimism payload builder implementation with Flashbots bundle support. +use alloy_consensus::{BlockHeader, Transaction, EMPTY_OMMER_ROOT_HASH}; +use alloy_eips::merge::BEACON_NONCE; +use alloy_rpc_types_beacon::events::{PayloadAttributesData, PayloadAttributesEvent}; +use alloy_rpc_types_engine::payload::PayloadAttributes; +use op_alloy_consensus::DepositTransaction; use reth_basic_payload_builder::*; use reth_chain_state::ExecutedBlock; -use reth_chainspec::{EthereumHardforks, OptimismHardfork}; -use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvm}; +use reth_chainspec::ChainSpecProvider; +use reth_evm::{system_calls::SystemCaller, ConfigureEvm, ConfigureEvmEnv, NextBlockEnvAttributes}; use reth_execution_types::ExecutionOutcome; -use reth_node_optimism::{OptimismBuiltPayload, OptimismPayloadBuilderAttributes}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; +use reth_optimism_forks::OptimismHardforks; +use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_payload_builder::error::OptimismPayloadBuilderError; -use reth_payload_builder::error::PayloadBuilderError; -use reth_primitives::{ - constants::{BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS}, - eip4844::calculate_excess_blob_gas, - proofs, Block, Header, Receipt, TransactionSigned, TxType, EMPTY_OMMER_ROOT_HASH, U256, -}; +use reth_payload_builder::PayloadBuilderError; +use reth_payload_primitives::PayloadBuilderAttributes; +use reth_primitives::{proofs, Block, BlockBody, Header, Receipt, TransactionSigned, TxType}; use reth_provider::StateProviderFactory; use reth_revm::database::StateProviderDatabase; -use reth_rpc_types::{ - beacon::events::{PayloadAttributesData, PayloadAttributesEvent}, - engine::PayloadAttributes, -}; use reth_trie::HashedPostState; use revm::{ db::states::bundle_state::BundleRetention, - primitives::{EVMError, EnvWithHandlerCfg, InvalidTransaction, ResultAndState}, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, + ResultAndState, U256, + }, DatabaseCommit, State, }; use std::sync::Arc; @@ -63,22 +67,44 @@ impl OpRbuilderPayloadBuilder { } } +impl OpRbuilderPayloadBuilder +where + EvmConfig: ConfigureEvmEnv
, +{ + /// Returns the configured [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the targeted payload + /// (that has the `parent` as its parent). + pub fn cfg_and_block_env( + &self, + config: &PayloadConfig, + parent: &Header, + ) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), EvmConfig::Error> { + let next_attributes = NextBlockEnvAttributes { + timestamp: config.attributes.timestamp(), + suggested_fee_recipient: config.attributes.suggested_fee_recipient(), + prev_randao: config.attributes.prev_randao(), + }; + self.evm_config + .next_cfg_and_block_env(parent, next_attributes) + } +} + /// Implementation of the [`PayloadBuilder`] trait for [`OpRbuilderPayloadBuilder`]. impl PayloadBuilder for OpRbuilderPayloadBuilder where - Client: StateProviderFactory, - EvmConfig: ConfigureEvm, + Client: StateProviderFactory + ChainSpecProvider, + EvmConfig: ConfigureEvm
, Pool: TransactionPoolBundleExt + BundlePoolOperations, { - type Attributes = OptimismPayloadBuilderAttributes; - type BuiltPayload = OptimismBuiltPayload; + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; fn try_build( &self, - args: BuildArguments, - ) -> Result, PayloadBuilderError> { - let parent_block = args.config.parent_block.clone(); - // Notify our BundleOperations of the new bundle. + args: BuildArguments, + ) -> Result, PayloadBuilderError> { + let parent_header = args.config.parent_header.clone(); + + // Notify our BundleOperations of the new payload attributes event. let eth_payload_attributes = args.config.attributes.payload_attributes.clone(); let payload_attributes = PayloadAttributes { timestamp: eth_payload_attributes.timestamp, @@ -88,10 +114,10 @@ where suggested_fee_recipient: eth_payload_attributes.suggested_fee_recipient, }; let payload_attributes_data = PayloadAttributesData { - parent_block_number: parent_block.number, - parent_block_root: parent_block.header.hash(), - parent_block_hash: parent_block.hash(), - proposal_slot: parent_block.number + 1, + parent_block_number: parent_header.number, + parent_block_root: parent_header.state_root(), + parent_block_hash: parent_header.hash(), + proposal_slot: parent_header.number + 1, proposer_index: 0, // Shouldn't be required for core building logic payload_attributes, }; @@ -106,143 +132,36 @@ where ) { error!(?e, "Failed to notify payload attributes event!"); }; - try_build_inner(self.evm_config.clone(), args, self.compute_pending_block) + + let (cfg_env, block_env) = self + .cfg_and_block_env(&args.config, &args.config.parent_header) + .map_err(PayloadBuilderError::other)?; + try_build_inner( + &self.evm_config, + args, + cfg_env, + block_env, + self.compute_pending_block, + ) } fn on_missing_payload( &self, - _args: BuildArguments, + _args: BuildArguments, ) -> MissingPayloadBehaviour { // we want to await the job that's already in progress because that should be returned as // is, there's no benefit in racing another job MissingPayloadBehaviour::AwaitInProgress } + // NOTE: this should only be used for testing purposes because this doesn't have access to L1 + // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. fn build_empty_payload( &self, - client: &Client, - config: PayloadConfig, - ) -> Result { - let extra_data = config.extra_data(); - let PayloadConfig { - initialized_block_env, - parent_block, - attributes, - chain_spec, - .. - } = config; - - debug!(target: "payload_builder", parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building empty payload"); - - let state = client.state_by_block_hash(parent_block.hash()).map_err(|err| { - warn!(target: "payload_builder", parent_hash=%parent_block.hash(), %err, "failed to get state for empty payload"); - err - })?; - let mut db = State::builder() - .with_database(StateProviderDatabase::new(state)) - .with_bundle_update() - .build(); - - let base_fee = initialized_block_env.basefee.to::(); - let block_gas_limit: u64 = initialized_block_env - .gas_limit - .try_into() - .unwrap_or(chain_spec.max_gas_limit); - - let WithdrawalsOutcome { - withdrawals_root, - withdrawals, - } = commit_withdrawals( - &mut db, - &chain_spec, - attributes.payload_attributes.timestamp, - attributes.payload_attributes.withdrawals.clone(), - ) - .map_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_block.hash(), - %err, - "failed to commit withdrawals for empty payload" - ); - err - })?; - - // merge all transitions into bundle state, this would apply the withdrawal balance - // changes and 4788 contract call - db.merge_transitions(BundleRetention::PlainState); - - // calculate the state root - let bundle_state = db.take_bundle(); - let hashed_state = HashedPostState::from_bundle_state(&bundle_state.state); - let state_root = db.database.state_root(hashed_state).map_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_block.hash(), - %err, - "failed to calculate state root for empty payload" - ); - err - })?; - - let mut excess_blob_gas = None; - let mut blob_gas_used = None; - - if chain_spec.is_cancun_active_at_timestamp(attributes.payload_attributes.timestamp) { - excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { - let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); - let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); - Some(calculate_excess_blob_gas( - parent_excess_blob_gas, - parent_blob_gas_used, - )) - } else { - // for the first post-fork block, both parent.blob_gas_used and - // parent.excess_blob_gas are evaluated as 0 - Some(calculate_excess_blob_gas(0, 0)) - }; - - blob_gas_used = Some(0); - } - let header = Header { - parent_hash: parent_block.hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: initialized_block_env.coinbase, - state_root, - transactions_root: EMPTY_TRANSACTIONS, - withdrawals_root, - receipts_root: EMPTY_RECEIPTS, - logs_bloom: Default::default(), - timestamp: attributes.payload_attributes.timestamp, - mix_hash: attributes.payload_attributes.prev_randao, - nonce: BEACON_NONCE, - base_fee_per_gas: Some(base_fee), - number: parent_block.number + 1, - gas_limit: block_gas_limit, - difficulty: U256::ZERO, - gas_used: 0, - extra_data, - blob_gas_used, - excess_blob_gas, - parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, - requests_root: None, - }; - - let block = Block { - header, - body: vec![], - ommers: vec![], - withdrawals, - requests: None, - }; - let sealed_block = block.seal_slow(); - - Ok(OptimismBuiltPayload::new( - attributes.payload_attributes.payload_id(), - sealed_block, - U256::ZERO, - chain_spec, - attributes, - None, - )) + _client: &Client, + _config: PayloadConfig, + ) -> Result { + Err(PayloadBuilderError::MissingPayload) } } @@ -254,13 +173,15 @@ where /// a result indicating success with the payload or an error in case of failure. #[inline] pub(crate) fn try_build_inner( - evm_config: EvmConfig, - args: BuildArguments, + evm_config: &EvmConfig, + args: BuildArguments, + initialized_cfg: CfgEnvWithHandlerCfg, + initialized_block_env: BlockEnv, _compute_pending_block: bool, -) -> Result, PayloadBuilderError> +) -> Result, PayloadBuilderError> where - EvmConfig: ConfigureEvm, - Client: StateProviderFactory, + Client: StateProviderFactory + ChainSpecProvider, + EvmConfig: ConfigureEvm
, Pool: TransactionPoolBundleExt + BundlePoolOperations, { let BuildArguments { @@ -272,23 +193,20 @@ where best_payload, } = args; - let state_provider = client.state_by_block_hash(config.parent_block.hash())?; + let chain_spec = client.chain_spec(); + let state_provider = client.state_by_block_hash(config.parent_header.hash())?; let state = StateProviderDatabase::new(state_provider); let mut db = State::builder() - .with_database_ref(cached_reads.as_db(state)) + .with_database(cached_reads.as_db_mut(state)) .with_bundle_update() .build(); - let extra_data = config.extra_data(); let PayloadConfig { - initialized_block_env, - initialized_cfg, - parent_block, + parent_header, attributes, - chain_spec, - .. + mut extra_data, } = config; - debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_hash = ?parent_block.hash(), parent_number = parent_block.number, "building new payload"); + debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload"); let mut cumulative_gas_used = 0; let block_gas_limit: u64 = attributes.gas_limit.unwrap_or_else(|| { @@ -306,34 +224,33 @@ where let block_number = initialized_block_env.number.to::(); - let is_regolith = chain_spec.is_fork_active_at_timestamp( - OptimismHardfork::Regolith, - attributes.payload_attributes.timestamp, - ); + let is_regolith = + chain_spec.is_regolith_active_at_timestamp(attributes.payload_attributes.timestamp); // apply eip-4788 pre block contract call - pre_block_beacon_root_contract_call( - &mut db, - &evm_config, - &chain_spec, - &initialized_cfg, - &initialized_block_env, - attributes.payload_attributes.parent_beacon_block_root, - ) - .map_err(|err| { - warn!(target: "payload_builder", - parent_hash=%parent_block.hash(), - %err, - "failed to apply beacon root contract call for empty payload" - ); - PayloadBuilderError::Internal(err.into()) - })?; + let mut system_caller = SystemCaller::new(evm_config.clone(), chain_spec.clone()); + + system_caller + .pre_block_beacon_root_contract_call( + &mut db, + &initialized_cfg, + &initialized_block_env, + attributes.payload_attributes.parent_beacon_block_root, + ) + .map_err(|err| { + warn!(target: "payload_builder", + parent_header=%parent_header.hash(), + %err, + "failed to apply beacon root contract call for payload" + ); + PayloadBuilderError::Internal(err.into()) + })?; // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism // blocks will always have at least a single transaction in them (the L1 info transaction), // so we can safely assume that this will always be triggered upon the transition and that // the above check for empty blocks will never be hit on OP chains. - reth_evm_optimism::ensure_create2_deployer( + reth_optimism_evm::ensure_create2_deployer( chain_spec.clone(), attributes.payload_attributes.timestamp, &mut db, @@ -389,7 +306,7 @@ where let env = EnvWithHandlerCfg::new_with_cfg_env( initialized_cfg.clone(), initialized_block_env.clone(), - evm_config.tx_env(&sequencer_tx), + evm_config.tx_env(sequencer_tx.as_signed(), sequencer_tx.signer()), ); let mut evm = evm_config.evm_with_env(&mut db, env); @@ -431,10 +348,7 @@ where // receipt hashes should be computed when set. The state transition process // ensures this is only set for post-Canyon deposit transactions. deposit_receipt_version: chain_spec - .is_fork_active_at_timestamp( - OptimismHardfork::Canyon, - attributes.payload_attributes.timestamp, - ) + .is_canyon_active_at_timestamp(attributes.payload_attributes.timestamp) .then_some(1), })); @@ -446,7 +360,7 @@ where // Apply rbuilder block let mut count = 0; let iter = pool - .get_transactions(U256::from(parent_block.number + 1)) + .get_transactions(U256::from(parent_header.number + 1)) .unwrap() .into_iter(); for pool_tx in iter { @@ -478,7 +392,7 @@ where let env = EnvWithHandlerCfg::new_with_cfg_env( initialized_cfg.clone(), initialized_block_env.clone(), - evm_config.tx_env(&tx), + evm_config.tx_env(tx.as_signed(), tx.signer()), ); // Configure the environment for the block. @@ -532,7 +446,7 @@ where // update add to total fees let miner_fee = tx - .effective_tip_per_gas(Some(base_fee)) + .effective_tip_per_gas(base_fee) .expect("fee is always valid; execution succeeded"); total_fees += U256::from(miner_fee) * U256::from(gas_used); @@ -544,7 +458,7 @@ where trace!("Executed {} txns from rbuilder", count); - // check if we have a better block + // check if we have a better block, but only if we included transactions from the pool if !is_better_payload(best_payload.as_ref(), total_fees) { // can skip building the block return Ok(BuildOutcome::Aborted { @@ -560,25 +474,23 @@ where &mut db, &chain_spec, attributes.payload_attributes.timestamp, - attributes.clone().payload_attributes.withdrawals, + attributes.payload_attributes.withdrawals.clone(), )?; // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call - db.merge_transitions(BundleRetention::PlainState); + db.merge_transitions(BundleRetention::Reverts); let execution_outcome = ExecutionOutcome::new( db.take_bundle(), - vec![receipts].into(), + vec![receipts.clone()].into(), block_number, Vec::new(), ); let receipts_root = execution_outcome - .optimism_receipts_root_slow( - block_number, - chain_spec.as_ref(), - attributes.payload_attributes.timestamp, - ) + .generic_receipts_root_slow(block_number, |receipts| { + calculate_receipt_root_no_memo_optimism(receipts, &chain_spec, attributes.timestamp()) + }) .expect("Number is in range"); let logs_bloom = execution_outcome .block_logs_bloom(block_number) @@ -587,15 +499,14 @@ where // calculate the state root let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); let (state_root, trie_output) = { - let state_provider = db.database.0.inner.borrow_mut(); - state_provider - .db + db.database + .inner() .state_root_with_updates(hashed_state.clone()) .inspect_err(|err| { warn!(target: "payload_builder", - parent_hash=%parent_block.hash(), + parent_header=%parent_header.hash(), %err, - "failed to calculate state root for empty payload" + "failed to calculate state root for payload" ); })? }; @@ -603,31 +514,29 @@ where // create the block header let transactions_root = proofs::calculate_transaction_root(&executed_txs); - // initialize empty blob sidecars. There are no blob transactions on L2. - let blob_sidecars = Vec::new(); - let mut excess_blob_gas = None; - let mut blob_gas_used = None; - - // only determine cancun fields when active - if chain_spec.is_cancun_active_at_timestamp(attributes.payload_attributes.timestamp) { - excess_blob_gas = if chain_spec.is_cancun_active_at_timestamp(parent_block.timestamp) { - let parent_excess_blob_gas = parent_block.excess_blob_gas.unwrap_or_default(); - let parent_blob_gas_used = parent_block.blob_gas_used.unwrap_or_default(); - Some(calculate_excess_blob_gas( - parent_excess_blob_gas, - parent_blob_gas_used, - )) + // 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) = + if chain_spec.is_ecotone_active_at_timestamp(attributes.payload_attributes.timestamp) { + (Some(0), Some(0)) } else { - // for the first post-fork block, both parent.blob_gas_used and - // parent.excess_blob_gas are evaluated as 0 - Some(calculate_excess_blob_gas(0, 0)) + (None, None) }; - blob_gas_used = Some(0); + let is_holocene = + chain_spec.is_holocene_active_at_timestamp(attributes.payload_attributes.timestamp); + + if is_holocene { + extra_data = attributes + .get_holocene_extra_data( + chain_spec.base_fee_params_at_timestamp(attributes.payload_attributes.timestamp), + ) + .map_err(PayloadBuilderError::other)?; } let header = Header { - parent_hash: parent_block.hash(), + parent_hash: parent_header.hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, beneficiary: initialized_block_env.coinbase, state_root, @@ -637,9 +546,9 @@ where logs_bloom, timestamp: attributes.payload_attributes.timestamp, mix_hash: attributes.payload_attributes.prev_randao, - nonce: BEACON_NONCE, + nonce: BEACON_NONCE.into(), base_fee_per_gas: Some(base_fee), - number: parent_block.number + 1, + number: parent_header.number + 1, gas_limit: block_gas_limit, difficulty: U256::ZERO, gas_used: cumulative_gas_used, @@ -647,16 +556,17 @@ where parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, blob_gas_used, excess_blob_gas, - requests_root: None, + requests_hash: None, }; // seal the block let block = Block { header, - body: executed_txs, - ommers: vec![], - withdrawals, - requests: None, + body: BlockBody { + transactions: executed_txs, + ommers: vec![], + withdrawals, + }, }; let sealed_block = block.seal_slow(); @@ -671,7 +581,7 @@ where trie: Arc::new(trie_output), }; - let mut payload = OptimismBuiltPayload::new( + let payload = OpBuiltPayload::new( attributes.payload_attributes.id, sealed_block, total_fees, @@ -680,9 +590,6 @@ where Some(executed), ); - // extend the payload with the blob sidecars from the executed txs - payload.extend_sidecars(blob_sidecars); - Ok(BuildOutcome::Better { payload, cached_reads, diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 3350d78e..b899725d 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -36,9 +36,12 @@ use crate::eth_bundle_api::EthCallBundleMinimalApiServer; use clap_builder::Parser; use eth_bundle_api::EthBundleMinimalApi; use op_rbuilder_node_optimism::{args::OpRbuilderArgs, OpRbuilderNode}; -use reth::cli::Cli; -use reth_node_optimism::node::OptimismAddOns; -use reth_optimism_rpc::eth::rpc::SequencerClient; +use reth::{ + builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}, + providers::providers::BlockchainProvider2, +}; +use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; +use reth_optimism_node::node::OptimismAddOns; use tracing as _; // jemalloc provides better performance @@ -53,32 +56,58 @@ fn main() { std::env::set_var("RUST_BACKTRACE", "1"); } - if let Err(err) = Cli::::parse().run(|builder, op_rbuilder_args| async move { - let sequencer_http_arg = op_rbuilder_args.sequencer_http.clone(); - let handle = builder - .with_types::() - .with_components(OpRbuilderNode::components(op_rbuilder_args)) - .with_add_ons::() - .extend_rpc_modules(move |ctx| { - // register sequencer tx forwarder - if let Some(sequencer_http) = sequencer_http_arg { - ctx.registry - .eth_api() - .set_sequencer_client(SequencerClient::new(sequencer_http)); - } + if let Err(err) = + Cli::::parse().run(|builder, op_rbuilder_args| async move { + if op_rbuilder_args.experimental { + tracing::warn!(target: "reth::cli", "Experimental engine is default now, and the --engine.experimental flag is deprecated. To enable the legacy functionality, use --engine.legacy."); + } + let use_legacy_engine = op_rbuilder_args.legacy; + let sequencer_http_arg = op_rbuilder_args.sequencer_http.clone(); + match use_legacy_engine { + false => { + let engine_tree_config = TreeConfig::default() + .with_persistence_threshold(op_rbuilder_args.persistence_threshold) + .with_memory_block_buffer_target(op_rbuilder_args.memory_block_buffer_target); + let handle = builder + .with_types_and_provider::>() + .with_components(OpRbuilderNode::components(op_rbuilder_args)) + .with_add_ons(OptimismAddOns::new(sequencer_http_arg)) + .extend_rpc_modules(move |ctx| { + // register eth bundle api + let ext = EthBundleMinimalApi::new(ctx.registry.pool().clone()); + ctx.modules.merge_configured(ext.into_rpc())?; - // register eth bundle api - let ext = EthBundleMinimalApi::new(ctx.registry.pool().clone()); - ctx.modules.merge_configured(ext.into_rpc())?; + Ok(()) + }) + .launch_with_fn(|builder| { + let launcher = EngineNodeLauncher::new( + builder.task_executor().clone(), + builder.config().datadir(), + engine_tree_config, + ); + builder.launch_with(launcher) + }) + .await?; - Ok(()) - }) - .launch() - .await?; + handle.node_exit_future.await + } + true => { + let handle = + builder + .node(OpRbuilderNode::new(op_rbuilder_args.clone())).extend_rpc_modules(move |ctx| { + // register eth bundle api + let ext = EthBundleMinimalApi::new(ctx.registry.pool().clone()); + ctx.modules.merge_configured(ext.into_rpc())?; - handle.node_exit_future.await - }) { - eprintln!("Error: {err:?}"); - std::process::exit(1); - } + Ok(()) + }) + .launch().await?; + handle.node_exit_future.await + } + } + }) + { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } } diff --git a/crates/builder/transaction-pool-bundle-ext/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/Cargo.toml index 9106b7aa..ef22b78c 100644 --- a/crates/builder/transaction-pool-bundle-ext/Cargo.toml +++ b/crates/builder/transaction-pool-bundle-ext/Cargo.toml @@ -7,8 +7,13 @@ edition.workspace = true reth = { workspace = true } reth-eth-wire-types = { workspace = true } reth-primitives = { workspace = true } -reth-rpc-types = { workspace = true } reth-transaction-pool = { workspace = true } +alloy-primitives = { version = "0.8.9", default-features = false } +alloy-rpc-types-beacon = { version = "0.5.4", features = [ + "ssz", +] } +alloy-eips = { version = "0.5.4" } + tokio = { workspace = true } diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml index ae059f56..7618b468 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml @@ -7,10 +7,11 @@ edition = "2021" transaction-pool-bundle-ext = { path = "../.." } rbuilder = { path = "../../../rbuilder" } +alloy-primitives.workspace = true +alloy-rpc-types-beacon.workspace = true reth-primitives = { workspace = true } reth-provider = { workspace = true } reth-db-api = { workspace = true } -reth-rpc-types = { workspace = true } derive_more = { workspace = true } eyre = { workspace = true } @@ -20,8 +21,8 @@ tracing = { workspace = true } [features] optimism = [ - "reth-primitives/optimism", "rbuilder/optimism", - "reth-provider/optimism", - "reth-db-api/optimism" + "reth-db-api/optimism", + "reth-primitives/optimism", + "reth-provider/optimism" ] diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs index cbca555f..0cdff24e 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs @@ -6,6 +6,8 @@ use core::fmt; use std::{fmt::Formatter, path::Path, sync::Arc, time::Duration}; +use alloy_primitives::U256; +use alloy_rpc_types_beacon::events::PayloadAttributesEvent; use derive_more::From; use rbuilder::live_builder::cli::LiveBuilderConfig; use rbuilder::{ @@ -27,9 +29,8 @@ use rbuilder::{ telemetry, }; use reth_db_api::Database; -use reth_primitives::{TransactionSigned, U256}; -use reth_provider::{DatabaseProviderFactory, HeaderProvider, StateProviderFactory}; -use reth_rpc_types::beacon::events::PayloadAttributesEvent; +use reth_primitives::TransactionSigned; +use reth_provider::{BlockReader, DatabaseProviderFactory, HeaderProvider, StateProviderFactory}; use tokio::{ sync::{ mpsc::{self, error::SendError}, @@ -94,7 +95,11 @@ impl BundlePoolOps { ) -> Result where DB: Database + Clone + 'static, - P: DatabaseProviderFactory + StateProviderFactory + HeaderProvider + Clone + 'static, + P: DatabaseProviderFactory + + StateProviderFactory + + HeaderProvider + + Clone + + 'static, { // Create the payload source to trigger new block building let cancellation_token = CancellationToken::new(); @@ -126,7 +131,6 @@ impl BundlePoolOps { let builders = create_builders( vec![builder_strategy], config.base_config.live_root_hash_config().unwrap(), - config.base_config.root_hash_task_pool().unwrap(), config.base_config.sbundle_mergeabe_signers(), ); diff --git a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs index 59efcd4f..d956fc16 100644 --- a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs +++ b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs @@ -1,16 +1,18 @@ //! Houses [`BundleSupportedPool`]. +use alloy_eips::eip4844::{BlobAndProofV1, BlobTransactionSidecar}; +use alloy_primitives::{Address, TxHash, B256, U256}; +use alloy_rpc_types_beacon::events::PayloadAttributesEvent; use reth::providers::ChangedAccount; use reth_eth_wire_types::HandleMempoolData; -use reth_primitives::{Address, PooledTransactionsElement, TxHash, U256}; -use reth_rpc_types::{beacon::events::PayloadAttributesEvent, BlobTransactionSidecar}; +use reth_primitives::PooledTransactionsElement; use reth_transaction_pool::{ AllPoolTransactions, AllTransactionsEvents, BestTransactions, BestTransactionsAttributes, - BlobStore, BlobStoreError, BlockInfo, CanonicalStateUpdate, GetPooledTransactionLimit, - NewBlobSidecar, NewTransactionEvent, Pool, PoolConfig, PoolResult, PoolSize, - PropagatedTransactions, TransactionEvents, TransactionListenerKind, TransactionOrdering, - TransactionOrigin, TransactionPool, TransactionPoolExt as TransactionPoolBlockInfoExt, - TransactionValidator, ValidPoolTransaction, + BlobStore, BlobStoreError, BlockInfo, CanonicalStateUpdate, EthPoolTransaction, + GetPooledTransactionLimit, NewBlobSidecar, NewTransactionEvent, Pool, PoolConfig, PoolResult, + PoolSize, PropagatedTransactions, TransactionEvents, TransactionListenerKind, + TransactionOrdering, TransactionOrigin, TransactionPool, + TransactionPoolExt as TransactionPoolBlockInfoExt, TransactionValidator, ValidPoolTransaction, }; use std::{collections::HashSet, future::Future, sync::Arc}; use tokio::sync::mpsc::Receiver; @@ -110,7 +112,7 @@ impl Clone for BundleSupportedPool /// TODO: Use a crate like `delegate!` or `ambassador` to automate this. impl TransactionPool for BundleSupportedPool where - V: TransactionValidator, + V: TransactionValidator, T: TransactionOrdering::Transaction>, S: BlobStore, B: BundlePoolOperations, @@ -219,14 +221,6 @@ where self.tx_pool.best_transactions() } - #[allow(deprecated)] - fn best_transactions_with_base_fee( - &self, - base_fee: u64, - ) -> Box>>> { - self.tx_pool.best_transactions_with_base_fee(base_fee) - } - fn best_transactions_with_attributes( &self, best_transactions_attributes: BestTransactionsAttributes, @@ -300,23 +294,86 @@ where self.tx_pool.unique_senders() } - fn get_blob(&self, tx_hash: TxHash) -> Result, BlobStoreError> { + fn get_blob( + &self, + tx_hash: TxHash, + ) -> Result>, BlobStoreError> { self.tx_pool.get_blob(tx_hash) } fn get_all_blobs( &self, tx_hashes: Vec, - ) -> Result, BlobStoreError> { + ) -> Result)>, BlobStoreError> { self.tx_pool.get_all_blobs(tx_hashes) } fn get_all_blobs_exact( &self, tx_hashes: Vec, - ) -> Result, BlobStoreError> { + ) -> Result>, BlobStoreError> { self.tx_pool.get_all_blobs_exact(tx_hashes) } + + fn remove_transactions_and_descendants( + &self, + hashes: Vec, + ) -> Vec>> { + self.tx_pool.remove_transactions_and_descendants(hashes) + } + + fn remove_transactions_by_sender( + &self, + sender: Address, + ) -> Vec>> { + self.tx_pool.remove_transactions_by_sender(sender) + } + + fn get_pending_transactions_with_predicate( + &self, + predicate: impl FnMut(&ValidPoolTransaction) -> bool, + ) -> Vec>> { + self.tx_pool + .get_pending_transactions_with_predicate(predicate) + } + + fn get_pending_transactions_by_sender( + &self, + sender: Address, + ) -> Vec>> { + self.tx_pool.get_pending_transactions_by_sender(sender) + } + + fn get_queued_transactions_by_sender( + &self, + sender: Address, + ) -> Vec>> { + self.tx_pool.get_queued_transactions_by_sender(sender) + } + + fn get_highest_transaction_by_sender( + &self, + sender: Address, + ) -> Option>> { + self.tx_pool.get_highest_transaction_by_sender(sender) + } + + fn get_highest_consecutive_transaction_by_sender( + &self, + sender: Address, + on_chain_nonce: u64, + ) -> Option>> { + self.tx_pool + .get_highest_consecutive_transaction_by_sender(sender, on_chain_nonce) + } + + fn get_blobs_for_versioned_hashes( + &self, + versioned_hashes: &[B256], + ) -> Result>, BlobStoreError> { + self.tx_pool + .get_blobs_for_versioned_hashes(versioned_hashes) + } } /// Implements the [`BundlePoolOperations`] interface by delegating to the inner `bundle_pool`. @@ -369,7 +426,7 @@ where // [`BundlePoolOperations`] implemented, it can implement [`TransactionPoolBundleExt`]. impl TransactionPoolBundleExt for BundleSupportedPool where - V: TransactionValidator, + V: TransactionValidator, T: TransactionOrdering::Transaction>, S: BlobStore, B: BundlePoolOperations, @@ -379,7 +436,7 @@ where /// [`TransactionPool`] often requires implementing the block info extension. impl TransactionPoolBlockInfoExt for BundleSupportedPool where - V: TransactionValidator, + V: TransactionValidator, T: TransactionOrdering::Transaction>, S: BlobStore, B: BundlePoolOperations, diff --git a/crates/builder/transaction-pool-bundle-ext/src/traits.rs b/crates/builder/transaction-pool-bundle-ext/src/traits.rs index 1d21d0b4..1a68bc30 100644 --- a/crates/builder/transaction-pool-bundle-ext/src/traits.rs +++ b/crates/builder/transaction-pool-bundle-ext/src/traits.rs @@ -1,7 +1,7 @@ //! [`TransactionPoolBundleExt`] implementation generic over any bundle and network type. -use reth_primitives::U256; -use reth_rpc_types::beacon::events::PayloadAttributesEvent; +use alloy_primitives::U256; +use alloy_rpc_types_beacon::events::PayloadAttributesEvent; use reth_transaction_pool::TransactionPool; use std::{fmt::Debug, future::Future}; From 693db5b0a213a1cd78248ee5b39e7a7b7377f6cc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 6 Dec 2024 15:54:47 +0100 Subject: [PATCH 004/262] chore: rm redundant arc (#268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary remove arc wrapper around pool ## 💡 Motivation and Context Pool itself is already an Arc wrapper --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- .../src/bundle_supported_pool.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs index d956fc16..35d2cab0 100644 --- a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs +++ b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs @@ -55,7 +55,7 @@ use crate::{traits::BundlePoolOperations, TransactionPoolBundleExt}; #[derive(Debug)] pub struct BundleSupportedPool { /// Arc'ed instance of [`Pool`] internals - tx_pool: Arc>, + tx_pool: Pool, /// Arc'ed instance of the [`BundlePool`] internals bundle_pool: Arc>, } @@ -75,12 +75,7 @@ where tx_pool_config: PoolConfig, ) -> Self { Self { - tx_pool: Arc::new(Pool::::new( - validator, - ordering, - blob_store, - tx_pool_config, - )), + tx_pool: Pool::::new(validator, ordering, blob_store, tx_pool_config), bundle_pool: Arc::new(BundlePool::::new(bundle_ops)), } } @@ -102,7 +97,7 @@ impl BundlePool { impl Clone for BundleSupportedPool { fn clone(&self) -> Self { Self { - tx_pool: Arc::clone(&self.tx_pool), + tx_pool: self.tx_pool.clone(), bundle_pool: Arc::clone(&self.bundle_pool), } } From c43ab36f1d58ed4bf1f2ab87db421adc91ab43ff Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 6 Dec 2024 15:54:57 +0100 Subject: [PATCH 005/262] chore: replace std mutex with parking lot (#269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary replaces std sync primitives with parking_lot's ## 💡 Motivation and Context parking_lot's primitives are more performant and don't come with lock poisoning, hence no unwrap https://docs.rs/parking_lot/latest/parking_lot/type.Mutex.html#differences-from-the-standard-library-mutex --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- .../bundle_pool_ops/rbuilder/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs index 0cdff24e..c66bc3a0 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs @@ -9,7 +9,6 @@ use std::{fmt::Formatter, path::Path, sync::Arc, time::Duration}; use alloy_primitives::U256; use alloy_rpc_types_beacon::events::PayloadAttributesEvent; use derive_more::From; -use rbuilder::live_builder::cli::LiveBuilderConfig; use rbuilder::{ building::{ builders::{ @@ -20,6 +19,7 @@ use rbuilder::{ }, live_builder::{ base_config::load_config_toml_and_env, + cli::LiveBuilderConfig, config::{create_builders, BuilderConfig, Config, SpecificBuilderConfig}, order_input::{rpc_server::RawCancelBundle, ReplaceableOrderPoolCommand}, payload_events::MevBoostSlotData, From c1617428ea3f88650d6702038c584bf98e7e1bba Mon Sep 17 00:00:00 2001 From: morito Date: Wed, 18 Dec 2024 17:45:46 +0900 Subject: [PATCH 006/262] Bump Reth to 1.1.2 (#288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Updated Reth to version 1.1.2. The following changes are included in this update: - Renamed Optimism-related types: `OpHardforks`, `OpPayloadBuilderError`, `OpAddOns` - Changed the 2nd argument of `get_block_by_number` from `bool` to `BlockTransactionsKind` - Used getter methods to access certain fields (e.g., hash, nonce) in the `Transaction` object - Renamed the `miner` field in `Header` to `beneficiary` - Updated some import paths - Used `PrimitiveSignature` instead of `Signature` ## 💡 Motivation and Context To catch up with the newer version of Reth --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/node/Cargo.toml | 5 ++++- crates/builder/op-rbuilder/node/src/node.rs | 17 +++++++++------- .../op-rbuilder/payload_builder/Cargo.toml | 3 +-- .../payload_builder/src/builder.rs | 20 +++++++++---------- crates/builder/op-rbuilder/src/main.rs | 4 ++-- .../transaction-pool-bundle-ext/Cargo.toml | 9 +++------ 6 files changed, 30 insertions(+), 28 deletions(-) diff --git a/crates/builder/op-rbuilder/node/Cargo.toml b/crates/builder/op-rbuilder/node/Cargo.toml index 8ec3d539..29d1b732 100644 --- a/crates/builder/op-rbuilder/node/Cargo.toml +++ b/crates/builder/op-rbuilder/node/Cargo.toml @@ -8,6 +8,9 @@ op-rbuilder-payload-builder = { path = "../payload_builder" } transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } rbuilder-bundle-pool-operations = { path = "../../transaction-pool-bundle-ext/bundle_pool_ops/rbuilder" } +# alloy +alloy-consensus.workspace = true + # reth reth-payload-builder.workspace = true reth-primitives.workspace = true @@ -37,5 +40,5 @@ optimism = [ "reth-optimism-node/optimism", "reth-primitives/optimism", "reth-provider/optimism", - "op-rbuilder-payload-builder/optimism" + "op-rbuilder-payload-builder/optimism", ] diff --git a/crates/builder/op-rbuilder/node/src/node.rs b/crates/builder/op-rbuilder/node/src/node.rs index 4e24cb8e..f0008304 100644 --- a/crates/builder/op-rbuilder/node/src/node.rs +++ b/crates/builder/op-rbuilder/node/src/node.rs @@ -3,6 +3,7 @@ //! Inherits Network, Executor, and Consensus Builders from the optimism node, //! and overrides the Pool and Payload Builders. +use alloy_consensus::Header; use rbuilder_bundle_pool_operations::BundlePoolOps; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; use reth_evm::ConfigureEvm; @@ -16,12 +17,12 @@ use reth_node_builder::{ use reth_optimism_chainspec::OpChainSpec; use reth_optimism_evm::OpEvmConfig; use reth_optimism_node::{ - node::{OpConsensusBuilder, OpExecutorBuilder, OpNetworkBuilder, OpPrimitives, OptimismAddOns}, + node::{OpAddOns, OpConsensusBuilder, OpExecutorBuilder, OpNetworkBuilder, OpPrimitives}, txpool::OpTransactionValidator, OpEngineTypes, }; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; -use reth_primitives::{Header, TransactionSigned}; +use reth_primitives::TransactionSigned; use reth_provider::{BlockReader, CanonStateSubscriptions, DatabaseProviderFactory}; use reth_tracing::tracing::{debug, info}; use reth_transaction_pool::{ @@ -37,11 +38,14 @@ use transaction_pool_bundle_ext::{ use crate::args::OpRbuilderArgs; /// Optimism primitive types. -#[derive(Debug)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct OpRbuilderPrimitives; impl NodePrimitives for OpRbuilderPrimitives { type Block = reth_primitives::Block; + type SignedTx = reth_primitives::TransactionSigned; + type TxType = reth_primitives::TxType; + type Receipt = reth_primitives::Receipt; } /// Type configuration for an Optimism rbuilder. @@ -109,9 +113,8 @@ where OpConsensusBuilder, >; - type AddOns = OptimismAddOns< - NodeAdapter>::Components>, - >; + type AddOns = + OpAddOns>::Components>>; fn components_builder(&self) -> Self::ComponentsBuilder { let Self { args } = self; @@ -119,7 +122,7 @@ where } fn add_ons(&self) -> Self::AddOns { - OptimismAddOns::new(self.args.sequencer_http.clone()) + OpAddOns::new(self.args.sequencer_http.clone()) } } diff --git a/crates/builder/op-rbuilder/payload_builder/Cargo.toml b/crates/builder/op-rbuilder/payload_builder/Cargo.toml index 38885cf5..51f2afd9 100644 --- a/crates/builder/op-rbuilder/payload_builder/Cargo.toml +++ b/crates/builder/op-rbuilder/payload_builder/Cargo.toml @@ -43,9 +43,8 @@ optimism = [ "reth-execution-types/optimism", "reth-optimism-evm/optimism", "reth-optimism-node/optimism", - "reth-optimism-payload-builder/optimism", "reth-primitives/optimism", "reth-provider/optimism", "revm/optimism", - "reth-optimism-consensus/optimism" + "reth-optimism-consensus/optimism", ] diff --git a/crates/builder/op-rbuilder/payload_builder/src/builder.rs b/crates/builder/op-rbuilder/payload_builder/src/builder.rs index 84e75c26..1030e6dd 100644 --- a/crates/builder/op-rbuilder/payload_builder/src/builder.rs +++ b/crates/builder/op-rbuilder/payload_builder/src/builder.rs @@ -1,6 +1,6 @@ //! Optimism payload builder implementation with Flashbots bundle support. -use alloy_consensus::{BlockHeader, Transaction, EMPTY_OMMER_ROOT_HASH}; +use alloy_consensus::{BlockHeader, Header, Transaction, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::merge::BEACON_NONCE; use alloy_rpc_types_beacon::events::{PayloadAttributesData, PayloadAttributesEvent}; use alloy_rpc_types_engine::payload::PayloadAttributes; @@ -12,12 +12,12 @@ use reth_evm::{system_calls::SystemCaller, ConfigureEvm, ConfigureEvmEnv, NextBl use reth_execution_types::ExecutionOutcome; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; -use reth_optimism_forks::OptimismHardforks; +use reth_optimism_forks::OpHardforks; use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; -use reth_optimism_payload_builder::error::OptimismPayloadBuilderError; +use reth_optimism_payload_builder::error::OpPayloadBuilderError; use reth_payload_builder::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; -use reth_primitives::{proofs, Block, BlockBody, Header, Receipt, TransactionSigned, TxType}; +use reth_primitives::{proofs, Block, BlockBody, Receipt, TransactionSigned, TxType}; use reth_provider::StateProviderFactory; use reth_revm::database::StateProviderDatabase; use reth_trie::HashedPostState; @@ -257,7 +257,7 @@ where ) .map_err(|err| { warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); - PayloadBuilderError::other(OptimismPayloadBuilderError::ForceCreate2DeployerFail) + PayloadBuilderError::other(OpPayloadBuilderError::ForceCreate2DeployerFail) })?; let mut receipts = Vec::with_capacity(attributes.transactions.len()); @@ -270,7 +270,7 @@ where // A sequencer's block should never contain blob transactions. if sequencer_tx.value().is_eip4844() { return Err(PayloadBuilderError::other( - OptimismPayloadBuilderError::BlobTransactionRejected, + OpPayloadBuilderError::BlobTransactionRejected, )); } @@ -283,7 +283,7 @@ where .clone() .try_into_ecrecovered() .map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) + PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) })?; // Cache the depositor account prior to the state transition for the deposit nonce. @@ -298,7 +298,7 @@ where }) .transpose() .map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::AccountLoadFailed( + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( sequencer_tx.signer(), )) })?; @@ -386,7 +386,7 @@ where // convert tx to a signed transaction let tx = pool_tx.try_into_ecrecovered().map_err(|_| { - PayloadBuilderError::other(OptimismPayloadBuilderError::TransactionEcRecoverFailed) + PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) })?; let env = EnvWithHandlerCfg::new_with_cfg_env( @@ -583,7 +583,7 @@ where let payload = OpBuiltPayload::new( attributes.payload_attributes.id, - sealed_block, + Arc::new(sealed_block), total_fees, chain_spec, attributes, diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index b899725d..d38fa2a1 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -41,7 +41,7 @@ use reth::{ providers::providers::BlockchainProvider2, }; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; -use reth_optimism_node::node::OptimismAddOns; +use reth_optimism_node::node::OpAddOns; use tracing as _; // jemalloc provides better performance @@ -71,7 +71,7 @@ fn main() { let handle = builder .with_types_and_provider::>() .with_components(OpRbuilderNode::components(op_rbuilder_args)) - .with_add_ons(OptimismAddOns::new(sequencer_http_arg)) + .with_add_ons(OpAddOns::new(sequencer_http_arg)) .extend_rpc_modules(move |ctx| { // register eth bundle api let ext = EthBundleMinimalApi::new(ctx.registry.pool().clone()); diff --git a/crates/builder/transaction-pool-bundle-ext/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/Cargo.toml index ef22b78c..83b64ddd 100644 --- a/crates/builder/transaction-pool-bundle-ext/Cargo.toml +++ b/crates/builder/transaction-pool-bundle-ext/Cargo.toml @@ -8,12 +8,9 @@ reth = { workspace = true } reth-eth-wire-types = { workspace = true } reth-primitives = { workspace = true } reth-transaction-pool = { workspace = true } -alloy-primitives = { version = "0.8.9", default-features = false } -alloy-rpc-types-beacon = { version = "0.5.4", features = [ - "ssz", -] } -alloy-eips = { version = "0.5.4" } +alloy-primitives = { workspace = true, default-features = false } +alloy-rpc-types-beacon = { workspace = true, features = ["ssz"] } +alloy-eips = { workspace = true } tokio = { workspace = true } - From bdab05bbb28ce17ad189bcde5e3b152999b89820 Mon Sep 17 00:00:00 2001 From: ZanCorDX <126988525+ZanCorDX@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:44:42 -0300 Subject: [PATCH 007/262] Move bundle merger (#298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary For historical reasons, bundle merging logic (MultiShareBundleMerger) had to be implemented on each algorithm but that's not needed anymore so I removed MultiShareBundleMerger from both algorithms and added it in a single point between simulation and block building. ## 💡 Motivation and Context - The way it was before made implementing new algorithms more difficult. - Removed redundant calculations. --- ## ✅ I have completed the following steps: * [X] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- .../bundle_pool_ops/rbuilder/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs index c66bc3a0..716a26a3 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs @@ -131,7 +131,6 @@ impl BundlePoolOps { let builders = create_builders( vec![builder_strategy], config.base_config.live_root_hash_config().unwrap(), - config.base_config.sbundle_mergeabe_signers(), ); // Build and run the process From d06e580b11efc4c691f12f614c1d8e10b8c3ef00 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 9 Jan 2025 17:16:05 +0000 Subject: [PATCH 008/262] Add StateProviderFactory custom trait (#331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --------- Co-authored-by: Daniel Xifra --- .../bundle_pool_ops/rbuilder/Cargo.toml | 6 +---- .../bundle_pool_ops/rbuilder/src/lib.rs | 25 ++++++++----------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml index 7618b468..c6a03034 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml @@ -10,9 +10,7 @@ rbuilder = { path = "../../../rbuilder" } alloy-primitives.workspace = true alloy-rpc-types-beacon.workspace = true reth-primitives = { workspace = true } -reth-provider = { workspace = true } -reth-db-api = { workspace = true } - +reth-provider = {workspace = true} derive_more = { workspace = true } eyre = { workspace = true } tokio = { workspace = true } @@ -22,7 +20,5 @@ tracing = { workspace = true } [features] optimism = [ "rbuilder/optimism", - "reth-db-api/optimism", "reth-primitives/optimism", - "reth-provider/optimism" ] diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs index 716a26a3..202978ff 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs @@ -26,11 +26,11 @@ use rbuilder::{ SlotSource, }, primitives::{Bundle, BundleReplacementKey, Order}, + provider::reth_prov::StateProviderFactoryFromRethProvider, telemetry, }; -use reth_db_api::Database; use reth_primitives::TransactionSigned; -use reth_provider::{BlockReader, DatabaseProviderFactory, HeaderProvider, StateProviderFactory}; +use reth_provider::{BlockReader, DatabaseProviderFactory, HeaderProvider}; use tokio::{ sync::{ mpsc::{self, error::SendError}, @@ -89,14 +89,10 @@ impl SlotSource for OurSlotSource { } impl BundlePoolOps { - pub async fn new( - provider: P, - rbuilder_config_path: impl AsRef, - ) -> Result + pub async fn new

(provider: P, rbuilder_config_path: impl AsRef) -> Result where - DB: Database + Clone + 'static, - P: DatabaseProviderFactory - + StateProviderFactory + P: DatabaseProviderFactory + + reth_provider::StateProviderFactory + HeaderProvider + Clone + 'static, @@ -127,16 +123,17 @@ impl BundlePoolOps { build_duration_deadline_ms: None, }), }; - - let builders = create_builders( - vec![builder_strategy], - config.base_config.live_root_hash_config().unwrap(), + let provider = StateProviderFactoryFromRethProvider::new( + provider, + config.base_config().live_root_hash_config()?, ); + let builders = create_builders(vec![builder_strategy]); + // Build and run the process let builder = config .base_config - .create_builder_with_provider_factory::( + .create_builder_with_provider_factory( cancellation_token, Box::new(sink_factory), slot_source, From 6758dd96b8cab4cc952418e3d9a2f576a9024aea Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 10 Jan 2025 11:02:21 +1100 Subject: [PATCH 009/262] Add builder tx at end of block in op-rbuilder (#346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/node/Cargo.toml | 1 + crates/builder/op-rbuilder/node/src/args.rs | 4 + crates/builder/op-rbuilder/node/src/node.rs | 52 +++++-- .../op-rbuilder/payload_builder/Cargo.toml | 1 + .../payload_builder/src/builder.rs | 146 ++++++++++++++++-- crates/builder/op-rbuilder/src/main.rs | 6 +- .../bundle_pool_ops/rbuilder/src/lib.rs | 7 +- 7 files changed, 185 insertions(+), 32 deletions(-) diff --git a/crates/builder/op-rbuilder/node/Cargo.toml b/crates/builder/op-rbuilder/node/Cargo.toml index 29d1b732..e7c734df 100644 --- a/crates/builder/op-rbuilder/node/Cargo.toml +++ b/crates/builder/op-rbuilder/node/Cargo.toml @@ -4,6 +4,7 @@ edition = "2021" [dependencies] # workspace +rbuilder = { path = "../../rbuilder" } op-rbuilder-payload-builder = { path = "../payload_builder" } transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } rbuilder-bundle-pool-operations = { path = "../../transaction-pool-bundle-ext/bundle_pool_ops/rbuilder" } diff --git a/crates/builder/op-rbuilder/node/src/args.rs b/crates/builder/op-rbuilder/node/src/args.rs index 16f3c67b..3ab26f8a 100644 --- a/crates/builder/op-rbuilder/node/src/args.rs +++ b/crates/builder/op-rbuilder/node/src/args.rs @@ -42,6 +42,10 @@ pub struct OpRbuilderArgs { #[arg(long = "rollup.discovery.v4", default_value = "false")] pub discovery_v4: bool, + /// adds builder tx to the end of the block to denote a builder block + #[arg(long = "rollup.add-builder-tx")] + pub add_builder_tx: bool, + /// Enable the experimental engine features on reth binary /// /// DEPRECATED: experimental engine is default now, use --engine.legacy to enable the legacy diff --git a/crates/builder/op-rbuilder/node/src/node.rs b/crates/builder/op-rbuilder/node/src/node.rs index f0008304..50c3adb0 100644 --- a/crates/builder/op-rbuilder/node/src/node.rs +++ b/crates/builder/op-rbuilder/node/src/node.rs @@ -4,6 +4,7 @@ //! and overrides the Pool and Payload Builders. use alloy_consensus::Header; +use rbuilder::live_builder::config::Config; use rbuilder_bundle_pool_operations::BundlePoolOps; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; use reth_evm::ConfigureEvm; @@ -30,7 +31,7 @@ use reth_transaction_pool::{ TransactionValidationTaskExecutor, }; use reth_trie_db::MerklePatriciaTrie; -use std::{path::PathBuf, sync::Arc}; +use std::sync::Arc; use transaction_pool_bundle_ext::{ BundlePoolOperations, BundleSupportedPool, TransactionPoolBundleExt, }; @@ -54,17 +55,20 @@ impl NodePrimitives for OpRbuilderPrimitives { pub struct OpRbuilderNode { /// Additional args pub args: OpRbuilderArgs, + /// rbuilder config + pub config: Config, } impl OpRbuilderNode { /// Creates a new instance of the OP rbuilder node type. - pub const fn new(args: OpRbuilderArgs) -> Self { - Self { args } + pub const fn new(args: OpRbuilderArgs, config: Config) -> Self { + Self { args, config } } /// Returns the components for the given [`OpRbuilderArgs`]. pub fn components( args: OpRbuilderArgs, + config: Config, ) -> ComponentsBuilder< Node, OpRbuilderPoolBuilder, @@ -83,13 +87,17 @@ impl OpRbuilderNode { disable_txpool_gossip, compute_pending_block, discovery_v4, - rbuilder_config_path, + add_builder_tx, .. } = args; ComponentsBuilder::default() .node_types::() - .pool(OpRbuilderPoolBuilder::new(rbuilder_config_path)) - .payload(OpRbuilderPayloadServiceBuilder::new(compute_pending_block)) + .pool(OpRbuilderPoolBuilder::new(config.clone())) + .payload(OpRbuilderPayloadServiceBuilder::new( + compute_pending_block, + add_builder_tx, + config.clone(), + )) .network(OpNetworkBuilder { disable_txpool_gossip, disable_discovery_v4: !discovery_v4, @@ -117,8 +125,8 @@ where OpAddOns>::Components>>; fn components_builder(&self) -> Self::ComponentsBuilder { - let Self { args } = self; - Self::components(args.clone()) + let Self { args, config } = self; + Self::components(args.clone(), config.clone()) } fn add_ons(&self) -> Self::AddOns { @@ -140,15 +148,13 @@ impl NodeTypesWithEngine for OpRbuilderNode { #[derive(Debug, Default, Clone)] #[non_exhaustive] pub struct OpRbuilderPoolBuilder { - rbuilder_config_path: PathBuf, + config: Config, } impl OpRbuilderPoolBuilder { /// Creates a new instance of the OP rbuilder pool builder. - pub fn new(rbuilder_config_path: PathBuf) -> Self { - Self { - rbuilder_config_path, - } + pub fn new(config: Config) -> Self { + Self { config } } } @@ -188,7 +194,7 @@ where .require_l1_data_gas_fee(!ctx.config().dev.dev) }); - let bundle_ops = BundlePoolOps::new(ctx.provider().clone(), self.rbuilder_config_path) + let bundle_ops = BundlePoolOps::new(ctx.provider().clone(), self.config) .await .expect("Failed to instantiate RbuilderBundlePoolOps"); let transaction_pool = OpRbuilderTransactionPool::new( @@ -252,13 +258,21 @@ pub struct OpRbuilderPayloadServiceBuilder { /// will use the payload attributes from the latest block. Note /// that this flag is not yet functional. pub compute_pending_block: bool, + /// Whether to add a builder tx to the end of block. + /// This is used to verify blocks landed onchain was + /// built by the builder + pub add_builder_tx: bool, + /// rbuilder config to get coinbase signer + pub config: Config, } impl OpRbuilderPayloadServiceBuilder { /// Create a new instance with the given `compute_pending_block` flag. - pub const fn new(compute_pending_block: bool) -> Self { + pub const fn new(compute_pending_block: bool, add_builder_tx: bool, config: Config) -> Self { Self { compute_pending_block, + add_builder_tx, + config, } } @@ -281,9 +295,15 @@ impl OpRbuilderPayloadServiceBuilder { Evm: ConfigureEvm

, { + let builder_signer = if self.add_builder_tx { + Some(self.config.base_config.coinbase_signer()?) + } else { + None + }; let payload_builder = op_rbuilder_payload_builder::OpRbuilderPayloadBuilder::new(evm_config) - .set_compute_pending_block(self.compute_pending_block); + .set_compute_pending_block(self.compute_pending_block) + .set_builder_signer(builder_signer); let conf = ctx.payload_builder_config(); let payload_job_config = BasicPayloadJobGeneratorConfig::default() diff --git a/crates/builder/op-rbuilder/payload_builder/Cargo.toml b/crates/builder/op-rbuilder/payload_builder/Cargo.toml index 51f2afd9..dd898dd5 100644 --- a/crates/builder/op-rbuilder/payload_builder/Cargo.toml +++ b/crates/builder/op-rbuilder/payload_builder/Cargo.toml @@ -6,6 +6,7 @@ description = "A payload builder for op-rbuilder with bundle support." [dependencies] # workspace +rbuilder = { path = "../../rbuilder" } transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } # reth diff --git a/crates/builder/op-rbuilder/payload_builder/src/builder.rs b/crates/builder/op-rbuilder/payload_builder/src/builder.rs index 1030e6dd..154d0dd8 100644 --- a/crates/builder/op-rbuilder/payload_builder/src/builder.rs +++ b/crates/builder/op-rbuilder/payload_builder/src/builder.rs @@ -1,10 +1,11 @@ //! Optimism payload builder implementation with Flashbots bundle support. -use alloy_consensus::{BlockHeader, Header, Transaction, EMPTY_OMMER_ROOT_HASH}; +use alloy_consensus::{BlockHeader, Header, Transaction, TxEip1559, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::merge::BEACON_NONCE; use alloy_rpc_types_beacon::events::{PayloadAttributesData, PayloadAttributesEvent}; use alloy_rpc_types_engine::payload::PayloadAttributes; use op_alloy_consensus::DepositTransaction; +use rbuilder::utils::Signer; use reth_basic_payload_builder::*; use reth_chain_state::ExecutedBlock; use reth_chainspec::ChainSpecProvider; @@ -17,15 +18,17 @@ use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_payload_builder::error::OpPayloadBuilderError; use reth_payload_builder::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; -use reth_primitives::{proofs, Block, BlockBody, Receipt, TransactionSigned, TxType}; +use reth_primitives::{ + proofs, Block, BlockBody, Receipt, Transaction as RethTransaction, TransactionSigned, TxType, +}; use reth_provider::StateProviderFactory; use reth_revm::database::StateProviderDatabase; use reth_trie::HashedPostState; use revm::{ db::states::bundle_state::BundleRetention, primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, - ResultAndState, U256, + Address, BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, + ResultAndState, TxKind, U256, }, DatabaseCommit, State, }; @@ -38,6 +41,8 @@ use transaction_pool_bundle_ext::{BundlePoolOperations, TransactionPoolBundleExt pub struct OpRbuilderPayloadBuilder { /// The rollup's compute pending block configuration option. compute_pending_block: bool, + /// The builder's signer key to use for an end of block tx + builder_signer: Option, /// The type responsible for creating the evm. evm_config: EvmConfig, } @@ -46,6 +51,7 @@ impl OpRbuilderPayloadBuilder { pub const fn new(evm_config: EvmConfig) -> Self { Self { compute_pending_block: true, + builder_signer: None, evm_config, } } @@ -56,6 +62,12 @@ impl OpRbuilderPayloadBuilder { self } + /// Sets the builder's signer key to use for an end of block tx + pub const fn set_builder_signer(mut self, builder_signer: Option) -> Self { + self.builder_signer = builder_signer; + self + } + /// Enables the rollup's compute pending block configuration option. pub const fn compute_pending_block(self) -> Self { self.set_compute_pending_block(true) @@ -65,6 +77,11 @@ impl OpRbuilderPayloadBuilder { pub const fn is_compute_pending_block(&self) -> bool { self.compute_pending_block } + + /// Returns the builder's signer key to use for an end of block tx + pub fn builder_signer(&self) -> Option { + self.builder_signer.clone() + } } impl OpRbuilderPayloadBuilder @@ -126,21 +143,32 @@ where data: payload_attributes_data, }; + let (cfg_env, block_env) = self + .cfg_and_block_env(&args.config, &args.config.parent_header) + .map_err(PayloadBuilderError::other)?; + + // gas reserved for builder tx + let builder_tx_gas = self.builder_signer().map_or(0, |_| { + let message = format!("Block Number: {}", block_env.number); + estimate_gas_for_builder_tx(message.as_bytes().to_vec()) + }); + if let Err(e) = args.pool.notify_payload_attributes_event( payload_attributes_event, - args.config.attributes.gas_limit, + args.config + .attributes + .gas_limit + .map(|gas_limit| gas_limit - builder_tx_gas), ) { error!(?e, "Failed to notify payload attributes event!"); }; - let (cfg_env, block_env) = self - .cfg_and_block_env(&args.config, &args.config.parent_header) - .map_err(PayloadBuilderError::other)?; try_build_inner( &self.evm_config, args, cfg_env, block_env, + self.builder_signer(), self.compute_pending_block, ) } @@ -177,6 +205,7 @@ pub(crate) fn try_build_inner( args: BuildArguments, initialized_cfg: CfgEnvWithHandlerCfg, initialized_block_env: BlockEnv, + builder_signer: Option, _compute_pending_block: bool, ) -> Result, PayloadBuilderError> where @@ -357,6 +386,16 @@ where executed_txs.push(sequencer_tx.into_signed()); } + // reserved gas for builder tx + let message = format!("Block Number: {}", block_number) + .as_bytes() + .to_vec(); + let builder_tx_gas = if builder_signer.is_some() { + estimate_gas_for_builder_tx(message.clone()) + } else { + 0 + }; + // Apply rbuilder block let mut count = 0; let iter = pool @@ -365,7 +404,7 @@ where .into_iter(); for pool_tx in iter { // Ensure we still have capacity for this transaction - if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit { + if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit - builder_tx_gas { let inner = EVMError::Custom("rbuilder suggested transaction over gas limit!".to_string()); return Err(PayloadBuilderError::EvmExecutionError(inner)); @@ -467,6 +506,78 @@ where }); } + // Add builder tx to the block + builder_signer + .map(|signer| { + // Create message with block number for the builder to sign + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( + signer.address, + )) + })?; + + // Create the EIP-1559 transaction + let tx = RethTransaction::Eip1559(TxEip1559 { + chain_id: chain_spec.chain.id(), + nonce, + gas_limit: builder_tx_gas, + max_fee_per_gas: 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(PayloadBuilderError::other)?; + + let env = EnvWithHandlerCfg::new_with_cfg_env( + initialized_cfg.clone(), + initialized_block_env.clone(), + evm_config.tx_env(builder_tx.as_signed(), builder_tx.signer()), + ); + + let mut evm = evm_config.evm_with_env(&mut db, env); + + let ResultAndState { result, state } = evm + .transact() + .map_err(PayloadBuilderError::EvmExecutionError)?; + + // Release the db reference by dropping evm + drop(evm); + // Commit changes + db.commit(state); + + let gas_used = result.gas_used(); + + // Add gas used by the transaction to cumulative gas used, before creating the receipt + cumulative_gas_used += gas_used; + + // Push transaction changeset and calculate header bloom filter for receipt + receipts.push(Some(Receipt { + tx_type: builder_tx.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: None, + deposit_receipt_version: None, + })); + + // Append sender and transaction to the respective lists + executed_senders.push(builder_tx.signer()); + executed_txs.push(builder_tx.into_signed()); + Ok(()) + }) + .transpose() + .unwrap_or_else(|err: PayloadBuilderError| { + warn!(target: "payload_builder", %err, "Failed to add builder transaction"); + None + }); + let WithdrawalsOutcome { withdrawals_root, withdrawals, @@ -595,3 +706,20 @@ where cached_reads, }) } + +fn estimate_gas_for_builder_tx(input: Vec) -> 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; + + zero_cost + nonzero_cost + 21_000 +} diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index d38fa2a1..8945d572 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -36,6 +36,7 @@ use crate::eth_bundle_api::EthCallBundleMinimalApiServer; use clap_builder::Parser; use eth_bundle_api::EthBundleMinimalApi; use op_rbuilder_node_optimism::{args::OpRbuilderArgs, OpRbuilderNode}; +use rbuilder::live_builder::base_config::load_config_toml_and_env; use reth::{ builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}, providers::providers::BlockchainProvider2, @@ -63,6 +64,7 @@ fn main() { } let use_legacy_engine = op_rbuilder_args.legacy; let sequencer_http_arg = op_rbuilder_args.sequencer_http.clone(); + let config = load_config_toml_and_env(op_rbuilder_args.rbuilder_config_path.clone())?; match use_legacy_engine { false => { let engine_tree_config = TreeConfig::default() @@ -70,7 +72,7 @@ fn main() { .with_memory_block_buffer_target(op_rbuilder_args.memory_block_buffer_target); let handle = builder .with_types_and_provider::>() - .with_components(OpRbuilderNode::components(op_rbuilder_args)) + .with_components(OpRbuilderNode::components(op_rbuilder_args, config)) .with_add_ons(OpAddOns::new(sequencer_http_arg)) .extend_rpc_modules(move |ctx| { // register eth bundle api @@ -94,7 +96,7 @@ fn main() { true => { let handle = builder - .node(OpRbuilderNode::new(op_rbuilder_args.clone())).extend_rpc_modules(move |ctx| { + .node(OpRbuilderNode::new(op_rbuilder_args.clone(), config)).extend_rpc_modules(move |ctx| { // register eth bundle api let ext = EthBundleMinimalApi::new(ctx.registry.pool().clone()); ctx.modules.merge_configured(ext.into_rpc())?; diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs index 202978ff..4201e83c 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] use core::fmt; -use std::{fmt::Formatter, path::Path, sync::Arc, time::Duration}; +use std::{fmt::Formatter, sync::Arc, time::Duration}; use alloy_primitives::U256; use alloy_rpc_types_beacon::events::PayloadAttributesEvent; @@ -18,7 +18,6 @@ use rbuilder::{ Sorting, }, live_builder::{ - base_config::load_config_toml_and_env, cli::LiveBuilderConfig, config::{create_builders, BuilderConfig, Config, SpecificBuilderConfig}, order_input::{rpc_server::RawCancelBundle, ReplaceableOrderPoolCommand}, @@ -89,7 +88,7 @@ impl SlotSource for OurSlotSource { } impl BundlePoolOps { - pub async fn new

(provider: P, rbuilder_config_path: impl AsRef) -> Result + pub async fn new

(provider: P, config: Config) -> Result where P: DatabaseProviderFactory + reth_provider::StateProviderFactory @@ -110,8 +109,6 @@ impl BundlePoolOps { }; // Spawn the builder! - let config: Config = load_config_toml_and_env(rbuilder_config_path)?; - let builder_strategy = BuilderConfig { name: "mp-ordering".to_string(), builder: SpecificBuilderConfig::OrderingBuilder(OrderingBuilderConfig { From 560c9e07e59fff403be61327d61015d506ca29ad Mon Sep 17 00:00:00 2001 From: liamaharon Date: Tue, 14 Jan 2025 13:57:17 +0400 Subject: [PATCH 010/262] In-process rbuilder orderpool <> reth transaction-pool connection (#339) Adds a new method to `LiveBuilder` `connect_to_transaction_pool` allowing in-process connection between the reth transaction pool and rbuilder orderpool. This fixes an issue where the orderpool does not receive any transactions that were cached on disk by reth on start up, and finally removes the requirement for an ipc connection when running in-process. --- crates/builder/op-rbuilder/node/src/node.rs | 12 ++++++------ .../op-rbuilder/payload_builder/Cargo.toml | 1 + .../bundle_pool_ops/rbuilder/Cargo.toml | 4 +++- .../bundle_pool_ops/rbuilder/src/lib.rs | 17 +++++++++++++++-- .../src/bundle_supported_pool.rs | 18 ++++++------------ 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/crates/builder/op-rbuilder/node/src/node.rs b/crates/builder/op-rbuilder/node/src/node.rs index 50c3adb0..44e45de4 100644 --- a/crates/builder/op-rbuilder/node/src/node.rs +++ b/crates/builder/op-rbuilder/node/src/node.rs @@ -27,7 +27,7 @@ use reth_primitives::TransactionSigned; use reth_provider::{BlockReader, CanonStateSubscriptions, DatabaseProviderFactory}; use reth_tracing::tracing::{debug, info}; use reth_transaction_pool::{ - blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPooledTransaction, + blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPooledTransaction, Pool, TransactionValidationTaskExecutor, }; use reth_trie_db::MerklePatriciaTrie; @@ -194,16 +194,16 @@ where .require_l1_data_gas_fee(!ctx.config().dev.dev) }); - let bundle_ops = BundlePoolOps::new(ctx.provider().clone(), self.config) - .await - .expect("Failed to instantiate RbuilderBundlePoolOps"); - let transaction_pool = OpRbuilderTransactionPool::new( + let tx_pool = Pool::new( validator, CoinbaseTipOrdering::default(), blob_store, - bundle_ops, ctx.pool_config(), ); + let bundle_ops = BundlePoolOps::new(ctx.provider().clone(), tx_pool.clone(), self.config) + .await + .expect("Failed to instantiate RbuilderBundlePoolOps"); + let transaction_pool = OpRbuilderTransactionPool::new(tx_pool, bundle_ops); info!(target: "reth::cli", "Transaction pool initialized"); let transactions_path = data_dir.txpool_transactions(); diff --git a/crates/builder/op-rbuilder/payload_builder/Cargo.toml b/crates/builder/op-rbuilder/payload_builder/Cargo.toml index dd898dd5..c3a6acd3 100644 --- a/crates/builder/op-rbuilder/payload_builder/Cargo.toml +++ b/crates/builder/op-rbuilder/payload_builder/Cargo.toml @@ -48,4 +48,5 @@ optimism = [ "reth-provider/optimism", "revm/optimism", "reth-optimism-consensus/optimism", + "reth-optimism-payload-builder/optimism" ] diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml index c6a03034..7a4528c2 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml @@ -10,7 +10,9 @@ rbuilder = { path = "../../../rbuilder" } alloy-primitives.workspace = true alloy-rpc-types-beacon.workspace = true reth-primitives = { workspace = true } -reth-provider = {workspace = true} +reth-transaction-pool = { workspace = true } +reth-provider = { workspace = true } + derive_more = { workspace = true } eyre = { workspace = true } tokio = { workspace = true } diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs index 4201e83c..c4de66e5 100644 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs +++ b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs @@ -30,6 +30,9 @@ use rbuilder::{ }; use reth_primitives::TransactionSigned; use reth_provider::{BlockReader, DatabaseProviderFactory, HeaderProvider}; +use reth_transaction_pool::{ + BlobStore, EthPooledTransaction, Pool, TransactionOrdering, TransactionValidator, +}; use tokio::{ sync::{ mpsc::{self, error::SendError}, @@ -88,13 +91,20 @@ impl SlotSource for OurSlotSource { } impl BundlePoolOps { - pub async fn new

(provider: P, config: Config) -> Result + pub async fn new( + provider: P, + pool: Pool, + config: Config, + ) -> Result where P: DatabaseProviderFactory + reth_provider::StateProviderFactory + HeaderProvider + Clone + 'static, + V: TransactionValidator + 'static, + T: TransactionOrdering::Transaction>, + S: BlobStore, { // Create the payload source to trigger new block building let cancellation_token = CancellationToken::new(); @@ -108,7 +118,6 @@ impl BundlePoolOps { block_building_helper_tx, }; - // Spawn the builder! let builder_strategy = BuilderConfig { name: "mp-ordering".to_string(), builder: SpecificBuilderConfig::OrderingBuilder(OrderingBuilderConfig { @@ -162,6 +171,10 @@ impl BundlePoolOps { .await .expect("Failed to start full telemetry server"); + builder + .connect_to_transaction_pool(pool) + .await + .expect("Failed to connect to reth pool"); builder.run().await.unwrap(); Ok::<(), ()> diff --git a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs index 35d2cab0..c82df5e0 100644 --- a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs +++ b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs @@ -9,10 +9,10 @@ use reth_primitives::PooledTransactionsElement; use reth_transaction_pool::{ AllPoolTransactions, AllTransactionsEvents, BestTransactions, BestTransactionsAttributes, BlobStore, BlobStoreError, BlockInfo, CanonicalStateUpdate, EthPoolTransaction, - GetPooledTransactionLimit, NewBlobSidecar, NewTransactionEvent, Pool, PoolConfig, PoolResult, - PoolSize, PropagatedTransactions, TransactionEvents, TransactionListenerKind, - TransactionOrdering, TransactionOrigin, TransactionPool, - TransactionPoolExt as TransactionPoolBlockInfoExt, TransactionValidator, ValidPoolTransaction, + GetPooledTransactionLimit, NewBlobSidecar, NewTransactionEvent, Pool, PoolResult, PoolSize, + PropagatedTransactions, TransactionEvents, TransactionListenerKind, TransactionOrdering, + TransactionOrigin, TransactionPool, TransactionPoolExt as TransactionPoolBlockInfoExt, + TransactionValidator, ValidPoolTransaction, }; use std::{collections::HashSet, future::Future, sync::Arc}; use tokio::sync::mpsc::Receiver; @@ -67,15 +67,9 @@ where S: BlobStore, B: BundlePoolOperations, { - pub fn new( - validator: V, - ordering: T, - blob_store: S, - bundle_ops: B, - tx_pool_config: PoolConfig, - ) -> Self { + pub fn new(pool: Pool, bundle_ops: B) -> Self { Self { - tx_pool: Pool::::new(validator, ordering, blob_store, tx_pool_config), + tx_pool: pool, bundle_pool: Arc::new(BundlePool::::new(bundle_ops)), } } From 1999e4b05d3eb6d03abf1a3637265044ce26b4e8 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 16 Jan 2025 15:01:08 +0000 Subject: [PATCH 011/262] Add flashblocks payload builder (#352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR introduces a payload generator for Reth that does not create multiple block building blocks but only spawns a single process. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 60 +- crates/builder/op-rbuilder/node/Cargo.toml | 45 - crates/builder/op-rbuilder/node/src/args.rs | 178 ---- crates/builder/op-rbuilder/node/src/lib.rs | 16 - crates/builder/op-rbuilder/node/src/node.rs | 350 ------- .../op-rbuilder/payload_builder/Cargo.toml | 52 -- .../payload_builder/src/builder.rs | 725 --------------- .../op-rbuilder/payload_builder/src/lib.rs | 8 - .../builder/op-rbuilder/src/eth_bundle_api.rs | 80 -- crates/builder/op-rbuilder/src/generator.rs | 447 +++++++++ crates/builder/op-rbuilder/src/main.rs | 153 ++-- .../op-rbuilder/src/payload_builder.rs | 854 ++++++++++++++++++ .../src/payload_builder_vanilla.rs | 60 ++ .../builder/op-rbuilder/src/tester/tester.rs | 320 +++++++ 14 files changed, 1803 insertions(+), 1545 deletions(-) delete mode 100644 crates/builder/op-rbuilder/node/Cargo.toml delete mode 100644 crates/builder/op-rbuilder/node/src/args.rs delete mode 100644 crates/builder/op-rbuilder/node/src/lib.rs delete mode 100644 crates/builder/op-rbuilder/node/src/node.rs delete mode 100644 crates/builder/op-rbuilder/payload_builder/Cargo.toml delete mode 100644 crates/builder/op-rbuilder/payload_builder/src/builder.rs delete mode 100644 crates/builder/op-rbuilder/payload_builder/src/lib.rs delete mode 100644 crates/builder/op-rbuilder/src/eth_bundle_api.rs create mode 100644 crates/builder/op-rbuilder/src/generator.rs create mode 100644 crates/builder/op-rbuilder/src/payload_builder.rs create mode 100644 crates/builder/op-rbuilder/src/payload_builder_vanilla.rs create mode 100644 crates/builder/op-rbuilder/src/tester/tester.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 48ce3d66..a996ab35 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -4,20 +4,60 @@ version = "0.1.0" edition = "2021" [dependencies] -rbuilder = { path = "../rbuilder" } -op-rbuilder-node-optimism = { path = "./node" } -transaction-pool-bundle-ext = { path = "../transaction-pool-bundle-ext" } - reth.workspace = true reth-optimism-node.workspace = true reth-optimism-cli.workspace = true +reth-optimism-chainspec.workspace = true +reth-optimism-payload-builder.workspace = true +reth-optimism-evm.workspace = true +reth-optimism-consensus.workspace = true reth-cli-util.workspace = true +reth-payload-primitives.workspace = true +reth-evm.workspace = true +reth-chainspec.workspace = true +reth-primitives.workspace = true +reth-node-api.workspace = true +reth-basic-payload-builder.workspace = true +reth-payload-builder.workspace = true +reth-node-ethereum.workspace = true +reth-chain-state.workspace = true +reth-execution-types.workspace = true +reth-ethereum-payload-builder.workspace = true +reth-provider.workspace = true +reth-revm.workspace = true +reth-trie.workspace = true +reth-rpc-layer.workspace = true +reth-payload-builder-primitives.workspace = true +reth-payload-util.workspace = true +reth-transaction-pool.workspace = true +reth-optimism-forks.workspace = true +alloy-primitives.workspace = true +alloy-consensus.workspace = true +alloy-eips.workspace = true +alloy-rpc-types-beacon.workspace = true +alloy-rpc-types-engine.workspace = true +op-alloy-consensus.workspace = true +op-alloy-provider.workspace = true +op-alloy-rpc-types-engine.workspace = true +alloy-transport-http.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-rpc-client.workspace = true +alloy-transport.workspace = true +alloy-network.workspace = true + +revm.workspace = true -tokio.workspace = true tracing.workspace = true +futures-util = "0.3.31" +eyre.workspace = true +alloy-provider.workspace = true +tower = "0.4" + +tokio.workspace = true jsonrpsee = { workspace = true } async-trait = { workspace = true } clap_builder = { workspace = true } +clap.workspace = true [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -47,16 +87,14 @@ min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] optimism = [ - "rbuilder/optimism", "reth-optimism-node/optimism", - "op-rbuilder-node-optimism/optimism", "reth-optimism-cli/optimism" ] -redact-sensitive = [ - "rbuilder/redact-sensitive" -] - [[bin]] name = "op-rbuilder" path = "src/main.rs" + +[[bin]] +name = "tester" +path = "src/tester/tester.rs" diff --git a/crates/builder/op-rbuilder/node/Cargo.toml b/crates/builder/op-rbuilder/node/Cargo.toml deleted file mode 100644 index e7c734df..00000000 --- a/crates/builder/op-rbuilder/node/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -name = "op-rbuilder-node-optimism" -edition = "2021" - -[dependencies] -# workspace -rbuilder = { path = "../../rbuilder" } -op-rbuilder-payload-builder = { path = "../payload_builder" } -transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } -rbuilder-bundle-pool-operations = { path = "../../transaction-pool-bundle-ext/bundle_pool_ops/rbuilder" } - -# alloy -alloy-consensus.workspace = true - -# reth -reth-payload-builder.workspace = true -reth-primitives.workspace = true -reth-basic-payload-builder.workspace = true -reth-node-builder.workspace = true -reth-node-api.workspace = true -reth-trie-db.workspace = true -reth-tracing.workspace = true -reth-provider.workspace = true -reth-transaction-pool.workspace = true -reth-evm.workspace = true -reth-optimism-evm.workspace = true -reth-optimism-chainspec.workspace = true -reth-optimism-node = { workspace = true } - -# async -tracing.workspace = true - -# misc -clap.workspace = true -eyre.workspace = true - -[features] -optimism = [ - "rbuilder-bundle-pool-operations/optimism", - "reth-optimism-evm/optimism", - "reth-optimism-node/optimism", - "reth-primitives/optimism", - "reth-provider/optimism", - "op-rbuilder-payload-builder/optimism", -] diff --git a/crates/builder/op-rbuilder/node/src/args.rs b/crates/builder/op-rbuilder/node/src/args.rs deleted file mode 100644 index 3ab26f8a..00000000 --- a/crates/builder/op-rbuilder/node/src/args.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! Additional Node command arguments. -//! -//! Copied from OptimismNode to allow easy extension. - -//! clap [Args](clap::Args) for optimism rollup configuration - -use std::path::PathBuf; - -use reth_node_builder::engine_tree_config::{ - DEFAULT_MEMORY_BLOCK_BUFFER_TARGET, DEFAULT_PERSISTENCE_THRESHOLD, -}; - -/// Parameters for rollup configuration -#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] -#[command(next_help_heading = "Rollup")] -pub struct OpRbuilderArgs { - /// HTTP endpoint for the sequencer mempool - #[arg(long = "rollup.sequencer-http", value_name = "HTTP_URL")] - pub sequencer_http: Option, - - /// Disable transaction pool gossip - #[arg(long = "rollup.disable-tx-pool-gossip")] - pub disable_txpool_gossip: bool, - - /// Enable walkback to genesis on startup. This is useful for re-validating the existing DB - /// prior to beginning normal syncing. - #[arg(long = "rollup.enable-genesis-walkback")] - pub enable_genesis_walkback: bool, - - /// By default the pending block equals the latest block - /// to save resources and not leak txs from the tx-pool, - /// this flag enables computing of the pending block - /// from the tx-pool instead. - /// - /// If `compute_pending_block` is not enabled, the payload builder - /// will use the payload attributes from the latest block. Note - /// that this flag is not yet functional. - #[arg(long = "rollup.compute-pending-block")] - pub compute_pending_block: bool, - - /// enables discovery v4 if provided - #[arg(long = "rollup.discovery.v4", default_value = "false")] - pub discovery_v4: bool, - - /// adds builder tx to the end of the block to denote a builder block - #[arg(long = "rollup.add-builder-tx")] - pub add_builder_tx: bool, - - /// Enable the experimental engine features on reth binary - /// - /// DEPRECATED: experimental engine is default now, use --engine.legacy to enable the legacy - /// functionality - #[arg(long = "engine.experimental", default_value = "false")] - pub experimental: bool, - - /// Enable the legacy engine on reth binary - #[arg(long = "engine.legacy", default_value = "false")] - pub legacy: bool, - - /// Configure persistence threshold for engine experimental. - #[arg(long = "engine.persistence-threshold", conflicts_with = "legacy", default_value_t = DEFAULT_PERSISTENCE_THRESHOLD)] - pub persistence_threshold: u64, - - /// Configure the target number of blocks to keep in memory. - #[arg(long = "engine.memory-block-buffer-target", conflicts_with = "legacy", default_value_t = DEFAULT_MEMORY_BLOCK_BUFFER_TARGET)] - pub memory_block_buffer_target: u64, - - /// Enable the engine2 experimental features on op-reth binary - #[arg(long = "rbuilder.config")] - pub rbuilder_config_path: PathBuf, -} - -#[cfg(test)] -mod tests { - use super::*; - use clap::{Args, Parser}; - - /// A helper type to parse Args more easily - #[derive(Parser)] - struct CommandParser { - #[command(flatten)] - args: T, - } - - #[test] - fn test_parse_optimism_default_args() { - let default_args = OpRbuilderArgs::default(); - let args = CommandParser::::parse_from(["reth"]).args; - assert_eq!(args, default_args); - } - - #[test] - fn test_parse_optimism_walkback_args() { - let expected_args = OpRbuilderArgs { - enable_genesis_walkback: true, - ..Default::default() - }; - let args = CommandParser::::parse_from([ - "reth", - "--rollup.enable-genesis-walkback", - ]) - .args; - assert_eq!(args, expected_args); - } - - #[test] - fn test_parse_optimism_compute_pending_block_args() { - let expected_args = OpRbuilderArgs { - compute_pending_block: true, - ..Default::default() - }; - let args = - CommandParser::::parse_from(["reth", "--rollup.compute-pending-block"]) - .args; - assert_eq!(args, expected_args); - } - - #[test] - fn test_parse_optimism_discovery_v4_args() { - let expected_args = OpRbuilderArgs { - discovery_v4: true, - ..Default::default() - }; - let args = - CommandParser::::parse_from(["reth", "--rollup.discovery.v4"]).args; - assert_eq!(args, expected_args); - } - - #[test] - fn test_parse_optimism_sequencer_http_args() { - let expected_args = OpRbuilderArgs { - sequencer_http: Some("http://host:port".into()), - ..Default::default() - }; - let args = CommandParser::::parse_from([ - "reth", - "--rollup.sequencer-http", - "http://host:port", - ]) - .args; - assert_eq!(args, expected_args); - } - - #[test] - fn test_parse_optimism_disable_txpool_args() { - let expected_args = OpRbuilderArgs { - disable_txpool_gossip: true, - ..Default::default() - }; - let args = CommandParser::::parse_from([ - "reth", - "--rollup.disable-tx-pool-gossip", - ]) - .args; - assert_eq!(args, expected_args); - } - - #[test] - fn test_parse_optimism_many_args() { - let expected_args = OpRbuilderArgs { - disable_txpool_gossip: true, - compute_pending_block: true, - enable_genesis_walkback: true, - sequencer_http: Some("http://host:port".into()), - ..Default::default() - }; - let args = CommandParser::::parse_from([ - "reth", - "--rollup.disable-tx-pool-gossip", - "--rollup.compute-pending-block", - "--rollup.enable-genesis-walkback", - "--rollup.sequencer-http", - "http://host:port", - ]) - .args; - assert_eq!(args, expected_args); - } -} diff --git a/crates/builder/op-rbuilder/node/src/lib.rs b/crates/builder/op-rbuilder/node/src/lib.rs deleted file mode 100644 index 02f51726..00000000 --- a/crates/builder/op-rbuilder/node/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Standalone crate for op-rbuilder-specific Reth configuration and builder types. -//! -//! Inherits Network, Executor, and Consensus Builders from the Optimism defaults, -//! overrides the Pool and Payload Builders. - -#![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] - -use tracing as _; - -/// CLI argument parsing. -pub mod args; - -pub mod node; -pub use node::OpRbuilderNode; diff --git a/crates/builder/op-rbuilder/node/src/node.rs b/crates/builder/op-rbuilder/node/src/node.rs deleted file mode 100644 index 44e45de4..00000000 --- a/crates/builder/op-rbuilder/node/src/node.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! op-rbuilder Node types config. -//! -//! Inherits Network, Executor, and Consensus Builders from the optimism node, -//! and overrides the Pool and Payload Builders. - -use alloy_consensus::Header; -use rbuilder::live_builder::config::Config; -use rbuilder_bundle_pool_operations::BundlePoolOps; -use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; -use reth_evm::ConfigureEvm; -use reth_node_api::NodePrimitives; -use reth_node_builder::{ - components::{ComponentsBuilder, PayloadServiceBuilder, PoolBuilder}, - node::{FullNodeTypes, NodeTypes}, - BuilderContext, Node, NodeAdapter, NodeComponentsBuilder, NodeTypesWithEngine, - PayloadBuilderConfig, -}; -use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_evm::OpEvmConfig; -use reth_optimism_node::{ - node::{OpAddOns, OpConsensusBuilder, OpExecutorBuilder, OpNetworkBuilder, OpPrimitives}, - txpool::OpTransactionValidator, - OpEngineTypes, -}; -use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; -use reth_primitives::TransactionSigned; -use reth_provider::{BlockReader, CanonStateSubscriptions, DatabaseProviderFactory}; -use reth_tracing::tracing::{debug, info}; -use reth_transaction_pool::{ - blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPooledTransaction, Pool, - TransactionValidationTaskExecutor, -}; -use reth_trie_db::MerklePatriciaTrie; -use std::sync::Arc; -use transaction_pool_bundle_ext::{ - BundlePoolOperations, BundleSupportedPool, TransactionPoolBundleExt, -}; - -use crate::args::OpRbuilderArgs; - -/// Optimism primitive types. -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct OpRbuilderPrimitives; - -impl NodePrimitives for OpRbuilderPrimitives { - type Block = reth_primitives::Block; - type SignedTx = reth_primitives::TransactionSigned; - type TxType = reth_primitives::TxType; - type Receipt = reth_primitives::Receipt; -} - -/// Type configuration for an Optimism rbuilder. -#[derive(Debug, Default, Clone)] -#[non_exhaustive] -pub struct OpRbuilderNode { - /// Additional args - pub args: OpRbuilderArgs, - /// rbuilder config - pub config: Config, -} - -impl OpRbuilderNode { - /// Creates a new instance of the OP rbuilder node type. - pub const fn new(args: OpRbuilderArgs, config: Config) -> Self { - Self { args, config } - } - - /// Returns the components for the given [`OpRbuilderArgs`]. - pub fn components( - args: OpRbuilderArgs, - config: Config, - ) -> ComponentsBuilder< - Node, - OpRbuilderPoolBuilder, - OpRbuilderPayloadServiceBuilder, - OpNetworkBuilder, - OpExecutorBuilder, - OpConsensusBuilder, - > - where - Node: FullNodeTypes< - Types: NodeTypesWithEngine, - >, - <::Provider as DatabaseProviderFactory>::Provider: BlockReader, - { - let OpRbuilderArgs { - disable_txpool_gossip, - compute_pending_block, - discovery_v4, - add_builder_tx, - .. - } = args; - ComponentsBuilder::default() - .node_types::() - .pool(OpRbuilderPoolBuilder::new(config.clone())) - .payload(OpRbuilderPayloadServiceBuilder::new( - compute_pending_block, - add_builder_tx, - config.clone(), - )) - .network(OpNetworkBuilder { - disable_txpool_gossip, - disable_discovery_v4: !discovery_v4, - }) - .executor(OpExecutorBuilder::default()) - .consensus(OpConsensusBuilder::default()) - } -} - -impl Node for OpRbuilderNode -where - N: FullNodeTypes>, - <::Provider as DatabaseProviderFactory>::Provider: BlockReader, -{ - type ComponentsBuilder = ComponentsBuilder< - N, - OpRbuilderPoolBuilder, - OpRbuilderPayloadServiceBuilder, - OpNetworkBuilder, - OpExecutorBuilder, - OpConsensusBuilder, - >; - - type AddOns = - OpAddOns>::Components>>; - - fn components_builder(&self) -> Self::ComponentsBuilder { - let Self { args, config } = self; - Self::components(args.clone(), config.clone()) - } - - fn add_ons(&self) -> Self::AddOns { - OpAddOns::new(self.args.sequencer_http.clone()) - } -} - -impl NodeTypes for OpRbuilderNode { - type Primitives = OpPrimitives; - type ChainSpec = OpChainSpec; - type StateCommitment = MerklePatriciaTrie; -} - -impl NodeTypesWithEngine for OpRbuilderNode { - type Engine = OpEngineTypes; -} - -/// An extended optimism transaction pool with bundle support. -#[derive(Debug, Default, Clone)] -#[non_exhaustive] -pub struct OpRbuilderPoolBuilder { - config: Config, -} - -impl OpRbuilderPoolBuilder { - /// Creates a new instance of the OP rbuilder pool builder. - pub fn new(config: Config) -> Self { - Self { config } - } -} - -pub type OpRbuilderTransactionPool = BundleSupportedPool< - TransactionValidationTaskExecutor>, - CoinbaseTipOrdering, - S, - BundlePoolOps, ->; - -impl PoolBuilder for OpRbuilderPoolBuilder -where - Node: FullNodeTypes>, - <::Provider as DatabaseProviderFactory>::Provider: BlockReader, -{ - type Pool = OpRbuilderTransactionPool; - - async fn build_pool(self, ctx: &BuilderContext) -> eyre::Result { - let data_dir = ctx.config().datadir(); - let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?; - - let validator = TransactionValidationTaskExecutor::eth_builder(Arc::new( - ctx.chain_spec().inner.clone(), - )) - .with_head_timestamp(ctx.head().timestamp) - .kzg_settings(ctx.kzg_settings()?) - .with_additional_tasks(ctx.config().txpool.additional_validation_tasks) - .build_with_tasks( - ctx.provider().clone(), - ctx.task_executor().clone(), - blob_store.clone(), - ) - .map(|validator| { - OpTransactionValidator::new(validator) - // In --dev mode we can't require gas fees because we're unable to decode the L1 - // block info - .require_l1_data_gas_fee(!ctx.config().dev.dev) - }); - - let tx_pool = Pool::new( - validator, - CoinbaseTipOrdering::default(), - blob_store, - ctx.pool_config(), - ); - let bundle_ops = BundlePoolOps::new(ctx.provider().clone(), tx_pool.clone(), self.config) - .await - .expect("Failed to instantiate RbuilderBundlePoolOps"); - let transaction_pool = OpRbuilderTransactionPool::new(tx_pool, bundle_ops); - - info!(target: "reth::cli", "Transaction pool initialized"); - let transactions_path = data_dir.txpool_transactions(); - - // spawn txpool maintenance task - { - let pool = transaction_pool.clone(); - let chain_events = ctx.provider().canonical_state_stream(); - let client = ctx.provider().clone(); - let transactions_backup_config = - reth_transaction_pool::maintain::LocalTransactionBackupConfig::with_local_txs_backup(transactions_path); - - ctx.task_executor() - .spawn_critical_with_graceful_shutdown_signal( - "local transactions backup task", - |shutdown| { - reth_transaction_pool::maintain::backup_local_transactions_task( - shutdown, - pool.clone(), - transactions_backup_config, - ) - }, - ); - - // spawn the maintenance task - ctx.task_executor().spawn_critical( - "txpool maintenance task", - reth_transaction_pool::maintain::maintain_transaction_pool_future( - client, - pool, - chain_events, - ctx.task_executor().clone(), - Default::default(), - ), - ); - debug!(target: "reth::cli", "Spawned txpool maintenance task"); - } - - Ok(transaction_pool) - } -} - -/// An OP rbuilder payload service builder. -#[derive(Debug, Default, Clone)] -pub struct OpRbuilderPayloadServiceBuilder { - /// By default the pending block equals the latest block - /// to save resources and not leak txs from the tx-pool, - /// this flag enables computing of the pending block - /// from the tx-pool instead. - /// - /// If `compute_pending_block` is not enabled, the payload builder - /// will use the payload attributes from the latest block. Note - /// that this flag is not yet functional. - pub compute_pending_block: bool, - /// Whether to add a builder tx to the end of block. - /// This is used to verify blocks landed onchain was - /// built by the builder - pub add_builder_tx: bool, - /// rbuilder config to get coinbase signer - pub config: Config, -} - -impl OpRbuilderPayloadServiceBuilder { - /// Create a new instance with the given `compute_pending_block` flag. - pub const fn new(compute_pending_block: bool, add_builder_tx: bool, config: Config) -> Self { - Self { - compute_pending_block, - add_builder_tx, - config, - } - } - - /// A helper method to initialize [`PayloadBuilderService`] with the given EVM config. - pub fn spawn( - self, - evm_config: Evm, - ctx: &BuilderContext, - pool: Pool, - ) -> eyre::Result> - where - Node: FullNodeTypes< - Types: NodeTypesWithEngine, - >, - <::Provider as DatabaseProviderFactory>::Provider: BlockReader, - Pool: TransactionPoolBundleExt - + BundlePoolOperations - + Unpin - + 'static, - - Evm: ConfigureEvm

, - { - let builder_signer = if self.add_builder_tx { - Some(self.config.base_config.coinbase_signer()?) - } else { - None - }; - let payload_builder = - op_rbuilder_payload_builder::OpRbuilderPayloadBuilder::new(evm_config) - .set_compute_pending_block(self.compute_pending_block) - .set_builder_signer(builder_signer); - let conf = ctx.payload_builder_config(); - - let payload_job_config = BasicPayloadJobGeneratorConfig::default() - .interval(conf.interval()) - .deadline(conf.deadline()) - .max_payload_tasks(conf.max_payload_tasks()) - // no extradata for OP - .extradata(Default::default()); - - let payload_generator = BasicPayloadJobGenerator::with_builder( - ctx.provider().clone(), - pool, - ctx.task_executor().clone(), - payload_job_config, - payload_builder, - ); - let (payload_service, payload_builder) = - PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - - ctx.task_executor() - .spawn_critical("payload builder service", Box::pin(payload_service)); - - Ok(payload_builder) - } -} - -impl PayloadServiceBuilder for OpRbuilderPayloadServiceBuilder -where - Node: - FullNodeTypes>, - <::Provider as DatabaseProviderFactory>::Provider: BlockReader, - Pool: TransactionPoolBundleExt - + BundlePoolOperations - + Unpin - + 'static, -{ - async fn spawn_payload_service( - self, - ctx: &BuilderContext, - pool: Pool, - ) -> eyre::Result> { - self.spawn(OpEvmConfig::new(ctx.chain_spec()), ctx, pool) - } -} diff --git a/crates/builder/op-rbuilder/payload_builder/Cargo.toml b/crates/builder/op-rbuilder/payload_builder/Cargo.toml deleted file mode 100644 index c3a6acd3..00000000 --- a/crates/builder/op-rbuilder/payload_builder/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "op-rbuilder-payload-builder" -version = "0.1.0" -edition = "2021" -description = "A payload builder for op-rbuilder with bundle support." - -[dependencies] -# workspace -rbuilder = { path = "../../rbuilder" } -transaction-pool-bundle-ext = { path = "../../transaction-pool-bundle-ext" } - -# reth -reth-chainspec.workspace = true -reth-primitives.workspace = true -reth-revm.workspace = true -reth-provider.workspace = true -reth-evm.workspace = true -reth-optimism-evm.workspace = true -reth-optimism-consensus.workspace = true -reth-optimism-chainspec.workspace = true -reth-optimism-forks.workspace = true -reth-optimism-node.workspace = true -reth-execution-types.workspace = true -reth-payload-builder.workspace = true -reth-payload-primitives.workspace = true -reth-basic-payload-builder.workspace = true -reth-trie.workspace = true -reth-chain-state.workspace = true -reth-optimism-payload-builder.workspace = true - -# ethereum -revm.workspace = true -alloy-consensus.workspace = true -op-alloy-consensus.workspace = true -alloy-eips.workspace = true -alloy-rpc-types-engine.workspace = true -alloy-rpc-types-beacon.workspace = true - -# misc -tracing.workspace = true - -[features] -optimism = [ - "reth-execution-types/optimism", - "reth-optimism-evm/optimism", - "reth-optimism-node/optimism", - "reth-primitives/optimism", - "reth-provider/optimism", - "revm/optimism", - "reth-optimism-consensus/optimism", - "reth-optimism-payload-builder/optimism" -] diff --git a/crates/builder/op-rbuilder/payload_builder/src/builder.rs b/crates/builder/op-rbuilder/payload_builder/src/builder.rs deleted file mode 100644 index 154d0dd8..00000000 --- a/crates/builder/op-rbuilder/payload_builder/src/builder.rs +++ /dev/null @@ -1,725 +0,0 @@ -//! Optimism payload builder implementation with Flashbots bundle support. - -use alloy_consensus::{BlockHeader, Header, Transaction, TxEip1559, EMPTY_OMMER_ROOT_HASH}; -use alloy_eips::merge::BEACON_NONCE; -use alloy_rpc_types_beacon::events::{PayloadAttributesData, PayloadAttributesEvent}; -use alloy_rpc_types_engine::payload::PayloadAttributes; -use op_alloy_consensus::DepositTransaction; -use rbuilder::utils::Signer; -use reth_basic_payload_builder::*; -use reth_chain_state::ExecutedBlock; -use reth_chainspec::ChainSpecProvider; -use reth_evm::{system_calls::SystemCaller, ConfigureEvm, ConfigureEvmEnv, NextBlockEnvAttributes}; -use reth_execution_types::ExecutionOutcome; -use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; -use reth_optimism_forks::OpHardforks; -use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; -use reth_optimism_payload_builder::error::OpPayloadBuilderError; -use reth_payload_builder::PayloadBuilderError; -use reth_payload_primitives::PayloadBuilderAttributes; -use reth_primitives::{ - proofs, Block, BlockBody, Receipt, Transaction as RethTransaction, TransactionSigned, TxType, -}; -use reth_provider::StateProviderFactory; -use reth_revm::database::StateProviderDatabase; -use reth_trie::HashedPostState; -use revm::{ - db::states::bundle_state::BundleRetention, - primitives::{ - Address, BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, - ResultAndState, TxKind, U256, - }, - DatabaseCommit, State, -}; -use std::sync::Arc; -use tracing::{debug, error, trace, warn}; -use transaction_pool_bundle_ext::{BundlePoolOperations, TransactionPoolBundleExt}; - -/// Payload builder for OP Stack, which includes bundle support. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OpRbuilderPayloadBuilder { - /// The rollup's compute pending block configuration option. - compute_pending_block: bool, - /// The builder's signer key to use for an end of block tx - builder_signer: Option, - /// The type responsible for creating the evm. - evm_config: EvmConfig, -} - -impl OpRbuilderPayloadBuilder { - pub const fn new(evm_config: EvmConfig) -> Self { - Self { - compute_pending_block: true, - builder_signer: None, - evm_config, - } - } - - /// Sets the rollup's compute pending block configuration option. - pub const fn set_compute_pending_block(mut self, compute_pending_block: bool) -> Self { - self.compute_pending_block = compute_pending_block; - self - } - - /// Sets the builder's signer key to use for an end of block tx - pub const fn set_builder_signer(mut self, builder_signer: Option) -> Self { - self.builder_signer = builder_signer; - self - } - - /// Enables the rollup's compute pending block configuration option. - pub const fn compute_pending_block(self) -> Self { - self.set_compute_pending_block(true) - } - - /// Returns the rollup's compute pending block configuration option. - pub const fn is_compute_pending_block(&self) -> bool { - self.compute_pending_block - } - - /// Returns the builder's signer key to use for an end of block tx - pub fn builder_signer(&self) -> Option { - self.builder_signer.clone() - } -} - -impl OpRbuilderPayloadBuilder -where - EvmConfig: ConfigureEvmEnv
, -{ - /// Returns the configured [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the targeted payload - /// (that has the `parent` as its parent). - pub fn cfg_and_block_env( - &self, - config: &PayloadConfig, - parent: &Header, - ) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), EvmConfig::Error> { - let next_attributes = NextBlockEnvAttributes { - timestamp: config.attributes.timestamp(), - suggested_fee_recipient: config.attributes.suggested_fee_recipient(), - prev_randao: config.attributes.prev_randao(), - }; - self.evm_config - .next_cfg_and_block_env(parent, next_attributes) - } -} - -/// Implementation of the [`PayloadBuilder`] trait for [`OpRbuilderPayloadBuilder`]. -impl PayloadBuilder for OpRbuilderPayloadBuilder -where - Client: StateProviderFactory + ChainSpecProvider, - EvmConfig: ConfigureEvm
, - Pool: TransactionPoolBundleExt + BundlePoolOperations, -{ - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; - - fn try_build( - &self, - args: BuildArguments, - ) -> Result, PayloadBuilderError> { - let parent_header = args.config.parent_header.clone(); - - // Notify our BundleOperations of the new payload attributes event. - let eth_payload_attributes = args.config.attributes.payload_attributes.clone(); - let payload_attributes = PayloadAttributes { - timestamp: eth_payload_attributes.timestamp, - prev_randao: eth_payload_attributes.prev_randao, - withdrawals: Some(eth_payload_attributes.withdrawals.into_inner()), - parent_beacon_block_root: eth_payload_attributes.parent_beacon_block_root, - suggested_fee_recipient: eth_payload_attributes.suggested_fee_recipient, - }; - let payload_attributes_data = PayloadAttributesData { - parent_block_number: parent_header.number, - parent_block_root: parent_header.state_root(), - parent_block_hash: parent_header.hash(), - proposal_slot: parent_header.number + 1, - proposer_index: 0, // Shouldn't be required for core building logic - payload_attributes, - }; - let payload_attributes_event = PayloadAttributesEvent { - version: "placeholder".to_string(), - data: payload_attributes_data, - }; - - let (cfg_env, block_env) = self - .cfg_and_block_env(&args.config, &args.config.parent_header) - .map_err(PayloadBuilderError::other)?; - - // gas reserved for builder tx - let builder_tx_gas = self.builder_signer().map_or(0, |_| { - let message = format!("Block Number: {}", block_env.number); - estimate_gas_for_builder_tx(message.as_bytes().to_vec()) - }); - - if let Err(e) = args.pool.notify_payload_attributes_event( - payload_attributes_event, - args.config - .attributes - .gas_limit - .map(|gas_limit| gas_limit - builder_tx_gas), - ) { - error!(?e, "Failed to notify payload attributes event!"); - }; - - try_build_inner( - &self.evm_config, - args, - cfg_env, - block_env, - self.builder_signer(), - self.compute_pending_block, - ) - } - - fn on_missing_payload( - &self, - _args: BuildArguments, - ) -> MissingPayloadBehaviour { - // we want to await the job that's already in progress because that should be returned as - // is, there's no benefit in racing another job - MissingPayloadBehaviour::AwaitInProgress - } - - // NOTE: this should only be used for testing purposes because this doesn't have access to L1 - // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. - fn build_empty_payload( - &self, - _client: &Client, - _config: PayloadConfig, - ) -> Result { - Err(PayloadBuilderError::MissingPayload) - } -} - -/// Constructs an Optimism payload from the transactions sent through the -/// Payload attributes by the sequencer, and bundles returned by the BundlePool. -/// -/// Given build arguments including an Ethereum client, bundle-enabled 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. -#[inline] -pub(crate) fn try_build_inner( - evm_config: &EvmConfig, - args: BuildArguments, - initialized_cfg: CfgEnvWithHandlerCfg, - initialized_block_env: BlockEnv, - builder_signer: Option, - _compute_pending_block: bool, -) -> Result, PayloadBuilderError> -where - Client: StateProviderFactory + ChainSpecProvider, - EvmConfig: ConfigureEvm
, - Pool: TransactionPoolBundleExt + BundlePoolOperations, -{ - let BuildArguments { - client, - pool, - mut cached_reads, - config, - cancel, - best_payload, - } = args; - - let chain_spec = client.chain_spec(); - let state_provider = client.state_by_block_hash(config.parent_header.hash())?; - let state = StateProviderDatabase::new(state_provider); - let mut db = State::builder() - .with_database(cached_reads.as_db_mut(state)) - .with_bundle_update() - .build(); - let PayloadConfig { - parent_header, - attributes, - mut extra_data, - } = config; - - debug!(target: "payload_builder", id=%attributes.payload_attributes.payload_id(), parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload"); - - let mut cumulative_gas_used = 0; - let block_gas_limit: u64 = attributes.gas_limit.unwrap_or_else(|| { - initialized_block_env - .gas_limit - .try_into() - .unwrap_or(chain_spec.max_gas_limit) - }); - let base_fee = initialized_block_env.basefee.to::(); - - let mut executed_txs = Vec::with_capacity(attributes.transactions.len()); - let mut executed_senders = Vec::with_capacity(attributes.transactions.len()); - - let mut total_fees = U256::ZERO; - - let block_number = initialized_block_env.number.to::(); - - let is_regolith = - chain_spec.is_regolith_active_at_timestamp(attributes.payload_attributes.timestamp); - - // apply eip-4788 pre block contract call - let mut system_caller = SystemCaller::new(evm_config.clone(), chain_spec.clone()); - - system_caller - .pre_block_beacon_root_contract_call( - &mut db, - &initialized_cfg, - &initialized_block_env, - attributes.payload_attributes.parent_beacon_block_root, - ) - .map_err(|err| { - warn!(target: "payload_builder", - parent_header=%parent_header.hash(), - %err, - "failed to apply beacon root contract call for payload" - ); - PayloadBuilderError::Internal(err.into()) - })?; - - // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism - // blocks will always have at least a single transaction in them (the L1 info transaction), - // so we can safely assume that this will always be triggered upon the transition and that - // the above check for empty blocks will never be hit on OP chains. - reth_optimism_evm::ensure_create2_deployer( - chain_spec.clone(), - attributes.payload_attributes.timestamp, - &mut db, - ) - .map_err(|err| { - warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); - PayloadBuilderError::other(OpPayloadBuilderError::ForceCreate2DeployerFail) - })?; - - let mut receipts = Vec::with_capacity(attributes.transactions.len()); - for sequencer_tx in &attributes.transactions { - // Check if the job was cancelled, if so we can exit early. - if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled); - } - - // A sequencer's block should never contain blob transactions. - if sequencer_tx.value().is_eip4844() { - return Err(PayloadBuilderError::other( - OpPayloadBuilderError::BlobTransactionRejected, - )); - } - - // Convert the transaction to a [TransactionSignedEcRecovered]. This is - // purely for the purposes of utilizing the `evm_config.tx_env`` function. - // Deposit transactions do not have signatures, so if the tx is a deposit, this - // will just pull in its `from` address. - let sequencer_tx = sequencer_tx - .value() - .clone() - .try_into_ecrecovered() - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) - })?; - - // Cache the depositor account prior to the state transition for the deposit nonce. - // - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor = (is_regolith && sequencer_tx.is_deposit()) - .then(|| { - db.load_cache_account(sequencer_tx.signer()) - .map(|acc| acc.account_info().unwrap_or_default()) - }) - .transpose() - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( - sequencer_tx.signer(), - )) - })?; - - let env = EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - evm_config.tx_env(sequencer_tx.as_signed(), sequencer_tx.signer()), - ); - - let mut evm = evm_config.evm_with_env(&mut db, env); - - let ResultAndState { result, state } = match evm.transact() { - Ok(res) => res, - Err(err) => { - match err { - EVMError::Transaction(err) => { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue; - } - err => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)); - } - } - } - }; - - // to release the db reference drop evm. - drop(evm); - // commit changes - db.commit(state); - - let gas_used = result.gas_used(); - - // add gas used by the transaction to cumulative gas used, before creating the receipt - cumulative_gas_used += gas_used; - - // Push transaction changeset and calculate header bloom filter for receipt. - receipts.push(Some(Receipt { - tx_type: sequencer_tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: depositor.map(|account| account.nonce), - // The deposit receipt version was introduced in Canyon to indicate an update to how - // receipt hashes should be computed when set. The state transition process - // ensures this is only set for post-Canyon deposit transactions. - deposit_receipt_version: chain_spec - .is_canyon_active_at_timestamp(attributes.payload_attributes.timestamp) - .then_some(1), - })); - - // append sender and transaction to the respective lists - executed_senders.push(sequencer_tx.signer()); - executed_txs.push(sequencer_tx.into_signed()); - } - - // reserved gas for builder tx - let message = format!("Block Number: {}", block_number) - .as_bytes() - .to_vec(); - let builder_tx_gas = if builder_signer.is_some() { - estimate_gas_for_builder_tx(message.clone()) - } else { - 0 - }; - - // Apply rbuilder block - let mut count = 0; - let iter = pool - .get_transactions(U256::from(parent_header.number + 1)) - .unwrap() - .into_iter(); - for pool_tx in iter { - // Ensure we still have capacity for this transaction - if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit - builder_tx_gas { - let inner = - EVMError::Custom("rbuilder suggested transaction over gas limit!".to_string()); - return Err(PayloadBuilderError::EvmExecutionError(inner)); - } - - // A sequencer's block should never contain blob or deposit transactions from rbuilder. - if pool_tx.is_eip4844() || pool_tx.tx_type() == TxType::Deposit as u8 { - error!("rbuilder suggested blob or deposit transaction!"); - let inner = - EVMError::Custom("rbuilder suggested blob or deposit transaction!".to_string()); - return Err(PayloadBuilderError::EvmExecutionError(inner)); - } - - // check if the job was cancelled, if so we can exit early - if cancel.is_cancelled() { - return Ok(BuildOutcome::Cancelled); - } - - // convert tx to a signed transaction - let tx = pool_tx.try_into_ecrecovered().map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) - })?; - - let env = EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - evm_config.tx_env(tx.as_signed(), tx.signer()), - ); - - // Configure the environment for the block. - let mut evm = evm_config.evm_with_env(&mut db, env); - - let ResultAndState { result, state } = match evm.transact() { - Ok(res) => res, - Err(err) => { - match err { - EVMError::Transaction(err) => { - if matches!(err, InvalidTransaction::NonceTooLow { .. }) { - // if the nonce is too low, we can skip this transaction - error!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - error!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); - } - - let inner = EVMError::Custom("rbuilder transaction errored!".to_string()); - return Err(PayloadBuilderError::EvmExecutionError(inner)); - } - err => { - // this is an error that we should treat as fatal for this attempt - error!("rbuilder provided where an error occured!"); - return Err(PayloadBuilderError::EvmExecutionError(err)); - } - } - } - }; - // drop evm so db is released. - drop(evm); - // commit changes - db.commit(state); - - let gas_used = result.gas_used(); - - // add gas used by the transaction to cumulative gas used, before creating the - // receipt - cumulative_gas_used += gas_used; - - // Push transaction changeset and calculate header bloom filter for receipt. - receipts.push(Some(Receipt { - tx_type: tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: None, - deposit_receipt_version: None, - })); - - // update add to total fees - let miner_fee = tx - .effective_tip_per_gas(base_fee) - .expect("fee is always valid; execution succeeded"); - total_fees += U256::from(miner_fee) * U256::from(gas_used); - - // append sender and transaction to the respective lists - executed_senders.push(tx.signer()); - executed_txs.push(tx.clone().into_signed()); - count += 1; - } - - trace!("Executed {} txns from rbuilder", count); - - // check if we have a better block, but only if we included transactions from the pool - if !is_better_payload(best_payload.as_ref(), total_fees) { - // can skip building the block - return Ok(BuildOutcome::Aborted { - fees: total_fees, - cached_reads, - }); - } - - // Add builder tx to the block - builder_signer - .map(|signer| { - // Create message with block number for the builder to sign - let nonce = db - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( - signer.address, - )) - })?; - - // Create the EIP-1559 transaction - let tx = RethTransaction::Eip1559(TxEip1559 { - chain_id: chain_spec.chain.id(), - nonce, - gas_limit: builder_tx_gas, - max_fee_per_gas: 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(PayloadBuilderError::other)?; - - let env = EnvWithHandlerCfg::new_with_cfg_env( - initialized_cfg.clone(), - initialized_block_env.clone(), - evm_config.tx_env(builder_tx.as_signed(), builder_tx.signer()), - ); - - let mut evm = evm_config.evm_with_env(&mut db, env); - - let ResultAndState { result, state } = evm - .transact() - .map_err(PayloadBuilderError::EvmExecutionError)?; - - // Release the db reference by dropping evm - drop(evm); - // Commit changes - db.commit(state); - - let gas_used = result.gas_used(); - - // Add gas used by the transaction to cumulative gas used, before creating the receipt - cumulative_gas_used += gas_used; - - // Push transaction changeset and calculate header bloom filter for receipt - receipts.push(Some(Receipt { - tx_type: builder_tx.tx_type(), - success: result.is_success(), - cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: None, - deposit_receipt_version: None, - })); - - // Append sender and transaction to the respective lists - executed_senders.push(builder_tx.signer()); - executed_txs.push(builder_tx.into_signed()); - Ok(()) - }) - .transpose() - .unwrap_or_else(|err: PayloadBuilderError| { - warn!(target: "payload_builder", %err, "Failed to add builder transaction"); - None - }); - - let WithdrawalsOutcome { - withdrawals_root, - withdrawals, - } = commit_withdrawals( - &mut db, - &chain_spec, - attributes.payload_attributes.timestamp, - attributes.payload_attributes.withdrawals.clone(), - )?; - - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - db.merge_transitions(BundleRetention::Reverts); - - let execution_outcome = ExecutionOutcome::new( - db.take_bundle(), - vec![receipts.clone()].into(), - block_number, - Vec::new(), - ); - let receipts_root = execution_outcome - .generic_receipts_root_slow(block_number, |receipts| { - calculate_receipt_root_no_memo_optimism(receipts, &chain_spec, 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 hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); - let (state_root, trie_output) = { - db.database - .inner() - .state_root_with_updates(hashed_state.clone()) - .inspect_err(|err| { - warn!(target: "payload_builder", - parent_header=%parent_header.hash(), - %err, - "failed to calculate state root for payload" - ); - })? - }; - - // create the block header - let transactions_root = proofs::calculate_transaction_root(&executed_txs); - - // 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) = - if chain_spec.is_ecotone_active_at_timestamp(attributes.payload_attributes.timestamp) { - (Some(0), Some(0)) - } else { - (None, None) - }; - - let is_holocene = - chain_spec.is_holocene_active_at_timestamp(attributes.payload_attributes.timestamp); - - if is_holocene { - extra_data = attributes - .get_holocene_extra_data( - chain_spec.base_fee_params_at_timestamp(attributes.payload_attributes.timestamp), - ) - .map_err(PayloadBuilderError::other)?; - } - - let header = Header { - parent_hash: parent_header.hash(), - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: initialized_block_env.coinbase, - state_root, - transactions_root, - receipts_root, - withdrawals_root, - logs_bloom, - timestamp: attributes.payload_attributes.timestamp, - mix_hash: attributes.payload_attributes.prev_randao, - nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(base_fee), - number: parent_header.number + 1, - gas_limit: block_gas_limit, - difficulty: U256::ZERO, - gas_used: cumulative_gas_used, - extra_data, - parent_beacon_block_root: attributes.payload_attributes.parent_beacon_block_root, - blob_gas_used, - excess_blob_gas, - requests_hash: None, - }; - - // seal the block - let block = Block { - header, - body: BlockBody { - transactions: executed_txs, - ommers: vec![], - withdrawals, - }, - }; - - let sealed_block = block.seal_slow(); - debug!(target: "payload_builder", ?sealed_block, "sealed built block"); - - // create the executed block data - let executed = ExecutedBlock { - block: Arc::new(sealed_block.clone()), - senders: Arc::new(executed_senders), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - trie: Arc::new(trie_output), - }; - - let payload = OpBuiltPayload::new( - attributes.payload_attributes.id, - Arc::new(sealed_block), - total_fees, - chain_spec, - attributes, - Some(executed), - ); - - Ok(BuildOutcome::Better { - payload, - cached_reads, - }) -} - -fn estimate_gas_for_builder_tx(input: Vec) -> 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; - - zero_cost + nonzero_cost + 21_000 -} diff --git a/crates/builder/op-rbuilder/payload_builder/src/lib.rs b/crates/builder/op-rbuilder/payload_builder/src/lib.rs deleted file mode 100644 index 8edcda43..00000000 --- a/crates/builder/op-rbuilder/payload_builder/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Payload builder for the op-rbuilder with bundle support. - -#![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] - -pub mod builder; -pub use builder::OpRbuilderPayloadBuilder; diff --git a/crates/builder/op-rbuilder/src/eth_bundle_api.rs b/crates/builder/op-rbuilder/src/eth_bundle_api.rs deleted file mode 100644 index 2beb28e4..00000000 --- a/crates/builder/op-rbuilder/src/eth_bundle_api.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! An implemention of the *internal* eth_sendBundle api used by rbuilder. -//! -//! Should be refactored into standalone crate if required by other code. - -use jsonrpsee::{ - proc_macros::rpc, - types::{ErrorCode, ErrorObjectOwned}, -}; -use rbuilder::{ - live_builder::order_input::rpc_server::RawCancelBundle, - primitives::{ - serialize::{RawBundle, TxEncoding}, - Bundle, - }, -}; -use tracing::warn; -use transaction_pool_bundle_ext::BundlePoolOperations; - -/// [`EthBundleApiServer`] implementation. -pub struct EthBundleMinimalApi { - pool: BundlePool, -} - -impl EthBundleMinimalApi { - pub fn new(pool: BundlePool) -> Self { - Self { pool } - } -} - -#[async_trait::async_trait] -impl EthCallBundleMinimalApiServer for EthBundleMinimalApi -where - BundlePool: - BundlePoolOperations + Clone + 'static, -{ - fn send_bundle(&self, raw_bundle: RawBundle) -> jsonrpsee::core::RpcResult<()> { - let bundle = match raw_bundle.try_into(TxEncoding::WithBlobData) { - Ok(bundle) => bundle, - Err(err) => { - return Err(ErrorObjectOwned::owned( - ErrorCode::InvalidParams.code(), - format!("Failed to parse bundle: {:?}", err), - None::<()>, - )); - } - }; - tokio::task::spawn_local({ - let pool = self.pool.clone(); - async move { - if let Err(e) = pool.add_bundle(bundle).await { - warn!(?e, "Failed to send bundle"); - } - } - }); - Ok(()) - } - - fn cancel_bundle(&self, request: RawCancelBundle) -> jsonrpsee::core::RpcResult<()> { - // Following rbuilder behavior to ignore errors - tokio::task::spawn_local({ - let pool = self.pool.clone(); - async move { - if let Err(e) = pool.cancel_bundle(request).await { - warn!(?e, "Failed to cancel bundle"); - } - } - }); - Ok(()) - } -} - -/// A subset of the *internal* eth_sendBundle api used by rbuilder. -#[rpc(server, namespace = "eth")] -pub trait EthCallBundleMinimalApi { - #[method(name = "sendBundle")] - fn send_bundle(&self, bundle: RawBundle) -> jsonrpsee::core::RpcResult<()>; - - #[method(name = "cancelBundle")] - fn cancel_bundle(&self, request: RawCancelBundle) -> jsonrpsee::core::RpcResult<()>; -} diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs new file mode 100644 index 00000000..377cd086 --- /dev/null +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -0,0 +1,447 @@ +use alloy_eips::BlockNumberOrTag; +use alloy_primitives::Bytes; +use futures_util::Future; +use futures_util::FutureExt; +use reth::providers::{BlockReaderIdExt, BlockSource}; +use reth::{ + providers::StateProviderFactory, tasks::TaskSpawner, transaction_pool::TransactionPool, +}; +use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadConfig}; +use reth_basic_payload_builder::{BuildArguments, Cancelled}; +use reth_node_api::PayloadBuilderAttributes; +use reth_node_api::PayloadKind; +use reth_payload_builder::PayloadJobGenerator; +use reth_payload_builder::{KeepPayloadJobAlive, PayloadBuilderError, PayloadJob}; +use reth_payload_primitives::BuiltPayload; +use reth_primitives::SealedHeader; +use std::sync::{Arc, Mutex}; +use tokio::sync::oneshot; +use tokio::sync::Notify; +use tokio::time::Duration; +use tokio::time::Sleep; +use tracing::info; + +/// A trait for building payloads that encapsulate Ethereum transactions. +/// +/// This trait provides the `try_build` method to construct a transaction payload +/// using `BuildArguments`. It returns a `Result` indicating success or a +/// `PayloadBuilderError` if building fails. +/// +/// Generic parameters `Pool` and `Client` represent the transaction pool and +/// Ethereum client types. +pub trait PayloadBuilder: Send + Sync + Clone { + /// The payload attributes type to accept for building. + type Attributes: PayloadBuilderAttributes; + /// The type of the built payload. + type BuiltPayload: BuiltPayload; + + /// Tries to build a transaction payload using provided arguments. + /// + /// Constructs a transaction payload based on the given arguments, + /// returning a `Result` indicating success or an error if building fails. + /// + /// # Arguments + /// + /// - `args`: Build arguments containing necessary components. + /// + /// # Returns + /// + /// A `Result` indicating the build outcome or an error. + fn try_build( + &self, + args: BuildArguments, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError>; +} + +/// The generator type that creates new jobs that builds empty blocks. +#[derive(Debug)] +pub struct EmptyBlockPayloadJobGenerator { + /// The client that can interact with the chain. + client: Client, + /// txpool + pool: Pool, + /// How to spawn building tasks + executor: Tasks, + /// The configuration for the job generator. + _config: BasicPayloadJobGeneratorConfig, + /// The type responsible for building payloads. + /// + /// See [PayloadBuilder] + builder: Builder, +} + +// === impl EmptyBlockPayloadJobGenerator === + +impl EmptyBlockPayloadJobGenerator { + /// Creates a new [EmptyBlockPayloadJobGenerator] with the given config and custom + /// [PayloadBuilder] + pub fn with_builder( + client: Client, + pool: Pool, + executor: Tasks, + config: BasicPayloadJobGeneratorConfig, + builder: Builder, + ) -> Self { + Self { + client, + pool, + executor, + _config: config, + builder, + } + } +} + +impl PayloadJobGenerator + for EmptyBlockPayloadJobGenerator +where + Client: StateProviderFactory + BlockReaderIdExt + Clone + Unpin + 'static, + Pool: TransactionPool + Unpin + 'static, + Tasks: TaskSpawner + Clone + Unpin + 'static, + Builder: PayloadBuilder + Unpin + 'static, + >::Attributes: Unpin + Clone, + >::BuiltPayload: Unpin + Clone, +{ + type Job = EmptyBlockPayloadJob; + + /// This is invoked when the node receives payload attributes from the beacon node via + /// `engine_forkchoiceUpdatedV1` + fn new_payload_job( + &self, + attributes: >::Attributes, + ) -> Result { + let parent_block = if attributes.parent().is_zero() { + // use latest block if parent is zero: genesis block + self.client + .block_by_number_or_tag(BlockNumberOrTag::Latest)? + .ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))? + .seal_slow() + } else { + let block = self + .client + .find_block_by_hash(attributes.parent(), BlockSource::Any)? + .ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))?; + + // we already know the hash, so we can seal it + block.seal(attributes.parent()) + }; + let hash = parent_block.hash(); + let header = SealedHeader::new(parent_block.header().clone(), hash); + + info!("Spawn block building job"); + + let deadline = Box::pin(tokio::time::sleep(Duration::from_secs(2))); // Or another appropriate timeout + let config = PayloadConfig::new(Arc::new(header), Bytes::default(), attributes); + + let mut job = EmptyBlockPayloadJob { + client: self.client.clone(), + pool: self.pool.clone(), + executor: self.executor.clone(), + builder: self.builder.clone(), + config, + cell: BlockCell::new(), + cancel: None, + deadline, + build_complete: None, + }; + + job.spawn_build_job(); + + Ok(job) + } +} + +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +/// A [PayloadJob] that builds empty blocks. +pub struct EmptyBlockPayloadJob +where + Builder: PayloadBuilder, +{ + /// The configuration for how the payload will be created. + pub(crate) config: PayloadConfig, + /// The client that can interact with the chain. + pub(crate) client: Client, + /// The transaction pool. + pub(crate) pool: Pool, + /// How to spawn building tasks + pub(crate) executor: Tasks, + /// The type responsible for building payloads. + /// + /// See [PayloadBuilder] + pub(crate) builder: Builder, + /// The cell that holds the built payload. + pub(crate) cell: BlockCell, + /// Cancellation token for the running job + pub(crate) cancel: Option, + pub(crate) deadline: Pin>, // Add deadline + pub(crate) build_complete: Option>>, +} + +impl PayloadJob for EmptyBlockPayloadJob +where + Client: StateProviderFactory + Clone + Unpin + 'static, + Pool: TransactionPool + Unpin + 'static, + Tasks: TaskSpawner + Clone + 'static, + Builder: PayloadBuilder + Unpin + 'static, + >::Attributes: Unpin + Clone, + >::BuiltPayload: Unpin + Clone, +{ + type PayloadAttributes = Builder::Attributes; + type ResolvePayloadFuture = ResolvePayload; + type BuiltPayload = Builder::BuiltPayload; + + fn best_payload(&self) -> Result { + unimplemented!() + } + + fn payload_attributes(&self) -> Result { + Ok(self.config.attributes.clone()) + } + + fn resolve_kind( + &mut self, + kind: PayloadKind, + ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) { + tracing::debug!("Resolve kind {:?} {:?}", kind, self.cell.is_some()); + + // check if self.cell has a payload + self.cancel.take(); + + let resolve_future = ResolvePayload::new(self.cell.wait_for_value()); + (resolve_future, KeepPayloadJobAlive::No) + } +} + +/// A [PayloadJob] is a a future that's being polled by the `PayloadBuilderService` +impl EmptyBlockPayloadJob +where + Client: StateProviderFactory + Clone + Unpin + 'static, + Pool: TransactionPool + Unpin + 'static, + Tasks: TaskSpawner + Clone + 'static, + Builder: PayloadBuilder + Unpin + 'static, + >::Attributes: Unpin + Clone, + >::BuiltPayload: Unpin + Clone, +{ + pub fn spawn_build_job(&mut self) { + let builder = self.builder.clone(); + let client = self.client.clone(); + let pool = self.pool.clone(); + let cancel = Cancelled::default(); + let _cancel = cancel.clone(); // Clone for the task + let payload_config = self.config.clone(); + let cell = self.cell.clone(); + + let (tx, rx) = oneshot::channel(); + self.build_complete = Some(rx); + + self.cancel = Some(cancel); + self.executor.spawn_blocking(Box::pin(async move { + let args = BuildArguments { + client, + pool, + cached_reads: Default::default(), + config: payload_config, + cancel: _cancel, + best_payload: None, + }; + + let result = builder.try_build(args, cell); + let _ = tx.send(result); + })); + } +} + +/// A [PayloadJob] is a a future that's being polled by the `PayloadBuilderService` +impl Future for EmptyBlockPayloadJob +where + Client: StateProviderFactory + Clone + Unpin + 'static, + Pool: TransactionPool + Unpin + 'static, + Tasks: TaskSpawner + Clone + 'static, + Builder: PayloadBuilder + Unpin + 'static, + >::Attributes: Unpin + Clone, + >::BuiltPayload: Unpin + Clone, +{ + type Output = Result<(), PayloadBuilderError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + tracing::trace!("Polling job"); + let this = self.get_mut(); + + // Check if deadline is reached + if this.deadline.as_mut().poll(cx).is_ready() { + tracing::debug!("Deadline reached"); + return Poll::Ready(Ok(())); + } + + // If cancelled via resolve_kind() + if this.cancel.is_none() { + tracing::debug!("Job cancelled"); + return Poll::Ready(Ok(())); + } + + Poll::Pending + } +} + +// A future that resolves when a payload becomes available in the BlockCell +pub struct ResolvePayload { + future: WaitForValue, +} + +impl ResolvePayload { + pub fn new(future: WaitForValue) -> Self { + Self { future } + } +} + +impl Future for ResolvePayload { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut().future.poll_unpin(cx) { + Poll::Ready(value) => Poll::Ready(Ok(value)), + Poll::Pending => Poll::Pending, + } + } +} + +#[derive(Clone)] +pub struct BlockCell { + inner: Arc>>, + notify: Arc, +} + +impl BlockCell { + pub fn new() -> Self { + Self { + inner: Arc::new(Mutex::new(None)), + notify: Arc::new(Notify::new()), + } + } + + pub fn is_some(&self) -> bool { + let inner = self.inner.lock().unwrap(); + inner.is_some() + } + + pub fn set(&self, value: T) { + let mut inner = self.inner.lock().unwrap(); + *inner = Some(value); + self.notify.notify_one(); + } + + pub fn get(&self) -> Option { + let inner = self.inner.lock().unwrap(); + inner.clone() + } + + // Return a future that resolves when value is set + pub fn wait_for_value(&self) -> WaitForValue { + WaitForValue { cell: self.clone() } + } +} + +#[derive(Clone)] +// Future that resolves when a value is set in BlockCell +pub struct WaitForValue { + cell: BlockCell, +} + +impl Future for WaitForValue { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if let Some(value) = self.cell.get() { + Poll::Ready(value) + } else { + // Instead of register, we use notified() to get a future + cx.waker().wake_by_ref(); + Poll::Pending + } + } +} + +impl Default for BlockCell { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tokio::task; + use tokio::time::{sleep, Duration}; + + #[tokio::test] + async fn test_block_cell_wait_for_value() { + let cell = BlockCell::new(); + + // Spawn a task that will set the value after a delay + let cell_clone = cell.clone(); + task::spawn(async move { + sleep(Duration::from_millis(100)).await; + cell_clone.set(42); + }); + + // Wait for the value and verify + let wait_future = cell.wait_for_value(); + let result = wait_future.await; + assert_eq!(result, 42); + } + + #[tokio::test] + async fn test_block_cell_immediate_value() { + let cell = BlockCell::new(); + cell.set(42); + + // Value should be immediately available + let wait_future = cell.wait_for_value(); + let result = wait_future.await; + assert_eq!(result, 42); + } + + #[tokio::test] + async fn test_block_cell_multiple_waiters() { + let cell = BlockCell::new(); + + // Spawn multiple waiters + let wait1 = task::spawn({ + let cell = cell.clone(); + async move { cell.wait_for_value().await } + }); + + let wait2 = task::spawn({ + let cell = cell.clone(); + async move { cell.wait_for_value().await } + }); + + // Set value after a delay + sleep(Duration::from_millis(100)).await; + cell.set(42); + + // All waiters should receive the value + assert_eq!(wait1.await.unwrap(), 42); + assert_eq!(wait2.await.unwrap(), 42); + } + + #[tokio::test] + async fn test_block_cell_update_value() { + let cell = BlockCell::new(); + + // Set initial value + cell.set(42); + + // Set new value + cell.set(43); + + // Waiter should get the latest value + let result = cell.wait_for_value().await; + assert_eq!(result, 43); + } +} diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 8945d572..c54061e8 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,86 +1,89 @@ -//! The main entry point for `op-rbuilder`. -//! -//! `op-rbuilder` is an OP Stack EL client with block building capabilities, powered by in-process -//! rbuilder. -//! -//! The primary difference between `op-rbuilder` and `op-reth` is the `PayloadBuilder` derives -//! transactions exclusively from rbuilder, rather than directly from its transaction pool. -//! -//! ## Usage -//! -//! It has a new mandatory cli arg `--rbuilder.config` which must point to an rbuilder config file. -//! -//! ## Demo -//! -//! Instructions to demo `op-rbuilder` building blocks for an OP L2, and send txns to it with `mev-flood`: -//! -//! 1. Clone [flashbots/optimism](https://github.com/flashbots/optimism) and checkout the -//! `op-rbuilder` branch. -//! 2. `rm` any existing `reth` chain db -//! 3. Run a clean OP stack: `make devnet-clean && make devnet-down && make devnet-up` -//! 4. Run `op-rbuilder` on port 8547: `cargo run --bin op-rbuilder --features "optimism,jemalloc" -- node -//! --chain ../optimism/.devnet/genesis-l2.json --http --http.port 8547 --authrpc.jwtsecret -//! ../optimism/ops-bedrock/test-jwt-secret.txt --rbuilder.config config-optimism-local.toml` -//! 5. Init `mev-flood`: `docker run mevflood init -r http://host.docker.internal:8547 -s local.json` -//! 6. Run `mev-flood`: `docker run --init -v ${PWD}:/app/cli/deployments mevflood spam -p 3 -t 5 -r http://host.docker.internal:8547 -l local.json` -//! -//! Example starting clean OP Stack in one-line: `rm -rf /Users/liamaharon/Library/Application\ Support/reth && cd ../optimism && make devnet-clean && make devnet-down && make devnet-up && cd ../rbuilder && cargo run --bin op-rbuilder --features "optimism,jemalloc" -- node --chain ../optimism/.devnet/genesis-l2.json --http --http.port 8547 --authrpc.jwtsecret ../optimism/ops-bedrock/test-jwt-secret.txt --rbuilder.config config-optimism-local.toml` - -#![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] - -mod eth_bundle_api; - -use crate::eth_bundle_api::EthCallBundleMinimalApiServer; -use clap_builder::Parser; -use eth_bundle_api::EthBundleMinimalApi; -use op_rbuilder_node_optimism::{args::OpRbuilderArgs, OpRbuilderNode}; -use rbuilder::live_builder::base_config::load_config_toml_and_env; +use clap::Parser; +use generator::EmptyBlockPayloadJobGenerator; +use payload_builder::OpPayloadBuilder as FBPayloadBuilder; +use payload_builder_vanilla::VanillaOpPayloadBuilder; +use reth::{ + builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}, + payload::PayloadBuilderHandle, + providers::CanonStateSubscriptions, + transaction_pool::TransactionPool, +}; use reth::{ builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}, providers::providers::BlockchainProvider2, }; +use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; +use reth_node_api::NodeTypesWithEngine; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; -use reth_optimism_node::node::OpAddOns; -use tracing as _; +use reth_optimism_evm::OpEvmConfig; +use reth_optimism_node::OpEngineTypes; +use reth_optimism_node::{args::RollupArgs, node::OpAddOns, OpNode}; +use reth_payload_builder::PayloadBuilderService; -// jemalloc provides better performance -#[cfg(all(feature = "jemalloc", unix))] -#[global_allocator] -static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +pub mod generator; +pub mod payload_builder; +mod payload_builder_vanilla; -fn main() { - reth_cli_util::sigsegv_handler::install(); +#[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] +pub struct CustomPayloadBuilder; + +impl PayloadServiceBuilder for CustomPayloadBuilder +where + Node: + FullNodeTypes>, + Pool: TransactionPool + Unpin + 'static, +{ + async fn spawn_payload_service( + self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result::Engine>> { + tracing::info!("Spawning a custom payload builder"); + let _fb_builder = FBPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())); + let vanilla_builder = VanillaOpPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())); + let payload_job_config = BasicPayloadJobGeneratorConfig::default(); + + let payload_generator = EmptyBlockPayloadJobGenerator::with_builder( + ctx.provider().clone(), + pool, + ctx.task_executor().clone(), + payload_job_config, + // FBPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())), + vanilla_builder, + ); - if std::env::var_os("RUST_BACKTRACE").is_none() { - std::env::set_var("RUST_BACKTRACE", "1"); + let (payload_service, payload_builder) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical("custom payload builder service", Box::pin(payload_service)); + + Ok(payload_builder) } +} - if let Err(err) = - Cli::::parse().run(|builder, op_rbuilder_args| async move { - if op_rbuilder_args.experimental { +fn main() { + Cli::::parse() + .run(|builder, rollup_args| async move { + if rollup_args.experimental { tracing::warn!(target: "reth::cli", "Experimental engine is default now, and the --engine.experimental flag is deprecated. To enable the legacy functionality, use --engine.legacy."); } - let use_legacy_engine = op_rbuilder_args.legacy; - let sequencer_http_arg = op_rbuilder_args.sequencer_http.clone(); - let config = load_config_toml_and_env(op_rbuilder_args.rbuilder_config_path.clone())?; + let use_legacy_engine = rollup_args.legacy; + let sequencer_http_arg = rollup_args.sequencer_http.clone(); + match use_legacy_engine { false => { let engine_tree_config = TreeConfig::default() - .with_persistence_threshold(op_rbuilder_args.persistence_threshold) - .with_memory_block_buffer_target(op_rbuilder_args.memory_block_buffer_target); + .with_persistence_threshold(rollup_args.persistence_threshold) + .with_memory_block_buffer_target(rollup_args.memory_block_buffer_target); let handle = builder - .with_types_and_provider::>() - .with_components(OpRbuilderNode::components(op_rbuilder_args, config)) + .with_types_and_provider::>() + .with_components( + OpNode::components(rollup_args).payload(CustomPayloadBuilder::default()), + ) .with_add_ons(OpAddOns::new(sequencer_http_arg)) - .extend_rpc_modules(move |ctx| { - // register eth bundle api - let ext = EthBundleMinimalApi::new(ctx.registry.pool().clone()); - ctx.modules.merge_configured(ext.into_rpc())?; - - Ok(()) - }) .launch_with_fn(|builder| { let launcher = EngineNodeLauncher::new( builder.task_executor().clone(), @@ -92,24 +95,14 @@ fn main() { .await?; handle.node_exit_future.await - } + }, true => { let handle = - builder - .node(OpRbuilderNode::new(op_rbuilder_args.clone(), config)).extend_rpc_modules(move |ctx| { - // register eth bundle api - let ext = EthBundleMinimalApi::new(ctx.registry.pool().clone()); - ctx.modules.merge_configured(ext.into_rpc())?; + builder.node(OpNode::new(rollup_args.clone())).launch().await?; - Ok(()) - }) - .launch().await?; handle.node_exit_future.await - } + }, } }) - { - eprintln!("Error: {err:?}"); - std::process::exit(1); - } + .unwrap(); } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs new file mode 100644 index 00000000..fc080968 --- /dev/null +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -0,0 +1,854 @@ +use std::{fmt::Display, sync::Arc}; + +use crate::generator::{BlockCell, PayloadBuilder}; +use alloy_consensus::{Header, Transaction, EMPTY_OMMER_ROOT_HASH}; +use alloy_eips::merge::BEACON_NONCE; +use alloy_primitives::{Address, Bytes, U256}; +use alloy_rpc_types_engine::PayloadId; +use reth_basic_payload_builder::*; +use reth_chain_state::ExecutedBlock; +use reth_chainspec::ChainSpecProvider; +use reth_evm::{system_calls::SystemCaller, ConfigureEvm, NextBlockEnvAttributes}; +use reth_execution_types::ExecutionOutcome; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; +use reth_optimism_forks::OpHardforks; +use reth_payload_builder_primitives::PayloadBuilderError; +use reth_payload_primitives::PayloadBuilderAttributes; +use reth_payload_util::PayloadTransactions; +use reth_primitives::{proofs, Block, BlockBody, Receipt, SealedHeader, TransactionSigned, TxType}; +use reth_provider::{ProviderError, StateProviderFactory, StateRootProvider}; +use reth_revm::database::StateProviderDatabase; +use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; +use reth_trie::HashedPostState; +use revm::{ + db::{states::bundle_state::BundleRetention, BundleState, State}, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, + ResultAndState, TxEnv, + }, + Database, DatabaseCommit, +}; +use tracing::{debug, trace, warn}; + +use op_alloy_consensus::DepositTransaction; +use reth_optimism_payload_builder::error::OpPayloadBuilderError; +use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_transaction_pool::pool::BestPayloadTransactions; + +/// Optimism's payload builder +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OpPayloadBuilder { + /// The rollup's compute pending block configuration option. + // TODO(clabby): Implement this feature. + pub compute_pending_block: bool, + /// The type responsible for creating the evm. + pub evm_config: EvmConfig, + /// The type responsible for yielding the best transactions for the payload if mempool + /// transactions are allowed. + pub best_transactions: Txs, +} + +impl OpPayloadBuilder { + /// `OpPayloadBuilder` constructor. + pub const fn new(evm_config: EvmConfig) -> Self { + Self { + compute_pending_block: true, + evm_config, + best_transactions: (), + } + } +} + +impl OpPayloadBuilder +where + EvmConfig: ConfigureEvm
, + Txs: OpPayloadTransactions, +{ + /// 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( + &self, + args: BuildArguments, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> + where + Client: StateProviderFactory + ChainSpecProvider, + Pool: TransactionPool, + { + let (initialized_cfg, initialized_block_env) = self + .cfg_and_block_env(&args.config.attributes, &args.config.parent_header) + .map_err(PayloadBuilderError::other)?; + + let BuildArguments { + client, + pool, + config, + cancel, + .. + } = args; + + let ctx = OpPayloadBuilderCtx { + evm_config: self.evm_config.clone(), + chain_spec: client.chain_spec(), + config, + initialized_cfg, + initialized_block_env, + cancel, + best_payload: None, // remove + }; + + let best = self.best_transactions.clone(); + + let state_provider = client.state_by_block_hash(ctx.parent().hash())?; + let state = StateProviderDatabase::new(&state_provider); + + let mut db = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + + // 1. execute the pre steps and seal an early block with that + let mut info = execute_pre_steps(&mut db, &ctx)?; + let (payload, mut bundle_state) = build_block(db, &ctx, &info)?; + best_payload.set(payload); + + tracing::info!(target: "payload_builder", "Fallback block built"); + + if ctx.attributes().no_tx_pool { + tracing::info!( + target: "payload_builder", + "No transaction pool, skipping transaction pool processing", + ); + + // return early since we don't need to build a block with transactions from the pool + return Ok(()); + } + + // Right now it assumes a 1 second block time (TODO) + let gas_per_batch = ctx.block_gas_limit() / 4; + let mut total_gas_per_batch = gas_per_batch; + + let mut flashblock_count = 0; + + // 2. loop every n time and try to build an increasing block + loop { + if ctx.cancel.is_cancelled() { + tracing::info!( + target: "payload_builder", + "Job cancelled, stopping payload building", + ); + // if the job was cancelled, stop + return Ok(()); + } + + tracing::info!( + target: "payload_builder", + "Building flashblock {}", + flashblock_count, + ); + + let state = StateProviderDatabase::new(&state_provider); + + let mut db = State::builder() + .with_database(state) + .with_bundle_update() + .with_bundle_prestate(bundle_state) + .build(); + + let best_txs = best.best_transactions(&pool, ctx.best_transaction_attributes()); + ctx.execute_best_transactions::<_, Pool>( + &mut info, + &mut db, + best_txs, + total_gas_per_batch, + )?; + + if ctx.cancel.is_cancelled() { + tracing::info!( + target: "payload_builder", + "Job cancelled, stopping payload building", + ); + // if the job was cancelled, stop + return Ok(()); + } + + let (payload, new_bundle_state) = build_block(db, &ctx, &info)?; + + best_payload.set(payload); + + bundle_state = new_bundle_state; + total_gas_per_batch += gas_per_batch; + flashblock_count += 1; + + std::thread::sleep(std::time::Duration::from_millis(250)); + } + } +} + +impl OpPayloadBuilder +where + EvmConfig: ConfigureEvm
, +{ + /// Returns the configured [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the targeted payload + /// (that has the `parent` as its parent). + pub fn cfg_and_block_env( + &self, + attributes: &OpPayloadBuilderAttributes, + parent: &Header, + ) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), EvmConfig::Error> { + let next_attributes = NextBlockEnvAttributes { + timestamp: attributes.timestamp(), + suggested_fee_recipient: attributes.suggested_fee_recipient(), + prev_randao: attributes.prev_randao(), + }; + self.evm_config + .next_cfg_and_block_env(parent, next_attributes) + } +} + +impl PayloadBuilder for OpPayloadBuilder +where + Client: StateProviderFactory + ChainSpecProvider, + Pool: TransactionPool, + EvmConfig: ConfigureEvm
, +{ + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + self.build_payload(args, best_payload) + } +} + +pub fn build_block( + mut state: State, + ctx: &OpPayloadBuilderCtx, + info: &ExecutionInfo, +) -> Result<(OpBuiltPayload, BundleState), PayloadBuilderError> +where + EvmConfig: ConfigureEvm
, + DB: Database + AsRef

, + P: StateRootProvider, +{ + let withdrawals_outcome = ctx.commit_withdrawals(&mut state)?; + + let WithdrawalsOutcome { + withdrawals, + withdrawals_root, + } = withdrawals_outcome; + + // TODO: We must run this only once per block, but we are running it on every flashblock + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + state.merge_transitions(BundleRetention::Reverts); + + let new_bundle = state.take_bundle(); + + let block_number = ctx.block_number(); + assert_eq!(block_number, ctx.parent().number + 1); + + let execution_outcome = ExecutionOutcome::new( + new_bundle.clone(), + vec![info.receipts.clone()].into(), + 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 hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); + let (state_root, trie_output) = { + state + .database + .as_ref() + .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" + ); + })? + }; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); + + // 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(); + let extra_data = ctx.extra_data()?; + + let header = Header { + parent_hash: ctx.parent().hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: ctx.initialized_block_env.coinbase, + 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: None, + }; + + // seal the block + let block = Block { + header, + body: BlockBody { + transactions: info.executed_transactions.clone(), + ommers: vec![], + withdrawals, + }, + }; + + let sealed_block = Arc::new(block.seal_slow()); + debug!(target: "payload_builder", ?sealed_block, "sealed built block"); + + // create the executed block data + let _executed = ExecutedBlock { + block: sealed_block.clone(), + senders: Arc::new(info.executed_senders.clone()), + execution_output: Arc::new(execution_outcome.clone()), + hashed_state: Arc::new(hashed_state.clone()), + trie: Arc::new(trie_output.clone()), + }; + + Ok(( + OpBuiltPayload::new( + ctx.payload_id(), + sealed_block, + info.total_fees, + ctx.chain_spec.clone(), + ctx.config.attributes.clone(), + // This must be set to NONE for now because we are doing merge transitions on every flashblock + // when it should only happen once per block, thus, it returns a confusing state back to op-reth. + // We can live without this for now because Op syncs up the executed block using new_payload + // calls, but eventually we would want to return the executed block here. + None, + ), + new_bundle, + )) +} + +fn execute_pre_steps( + state: &mut State, + ctx: &OpPayloadBuilderCtx, +) -> Result +where + EvmConfig: ConfigureEvm

, + DB: Database, +{ + // 1. apply eip-4788 pre block contract call + ctx.apply_pre_beacon_root_contract_call(state)?; + + // 2. ensure create2deployer is force deployed + ctx.ensure_create2_deployer(state)?; + + // 3. execute sequencer transactions + let info = ctx.execute_sequencer_transactions(state)?; + + Ok(info) +} + +/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. +pub 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 { + BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) + } +} + +/// Holds the state after execution +#[derive(Debug)] +pub struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, + /// Outcome after committing withdrawals. + pub withdrawals_outcome: WithdrawalsOutcome, +} + +/// This acts as the container for executed transactions and its byproducts (receipts, gas used) +#[derive(Default, Debug)] +pub struct ExecutionInfo { + /// All executed transactions (unrecovered). + pub executed_transactions: Vec, + /// The recovered senders for the executed transactions. + pub executed_senders: Vec
, + /// The transaction receipts + pub receipts: Vec>, + /// All gas used so far + pub cumulative_gas_used: u64, + /// Tracks fees from executed mempool transactions + pub total_fees: U256, +} + +impl ExecutionInfo { + /// Create a new instance with allocated slots. + pub fn with_capacity(capacity: usize) -> Self { + Self { + executed_transactions: Vec::with_capacity(capacity), + executed_senders: Vec::with_capacity(capacity), + receipts: Vec::with_capacity(capacity), + cumulative_gas_used: 0, + total_fees: U256::ZERO, + } + } +} + +/// Container type that holds all necessities to build a new payload. +#[derive(Debug)] +pub struct OpPayloadBuilderCtx { + /// The type that knows how to perform system calls and configure the evm. + pub evm_config: EvmConfig, + /// The chainspec + pub chain_spec: Arc, + /// How to build the payload. + pub config: PayloadConfig, + /// Evm Settings + pub initialized_cfg: CfgEnvWithHandlerCfg, + /// Block config + pub initialized_block_env: BlockEnv, + /// Marker to check whether the job has been cancelled. + pub cancel: Cancelled, + /// The currently best payload. + pub best_payload: Option, +} + +impl OpPayloadBuilderCtx { + /// Returns the parent block the payload will be build on. + pub fn parent(&self) -> &SealedHeader { + &self.config.parent_header + } + + /// Returns the builder attributes. + pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { + &self.config.attributes + } + + /// Returns the block gas limit to target. + pub fn block_gas_limit(&self) -> u64 { + self.attributes() + .gas_limit + .unwrap_or_else(|| self.initialized_block_env.gas_limit.saturating_to()) + } + + /// Returns the block number for the block. + pub fn block_number(&self) -> u64 { + self.initialized_block_env.number.to() + } + + /// Returns the current base fee + pub fn base_fee(&self) -> u64 { + self.initialized_block_env.basefee.to() + } + + /// Returns the current blob gas price. + pub fn get_blob_gasprice(&self) -> Option { + self.initialized_block_env + .get_blob_gasprice() + .map(|gasprice| gasprice as u64) + } + + /// Returns the blob fields for the header. + /// + /// This will always return `Some(0)` after ecotone. + pub fn blob_fields(&self) -> (Option, Option) { + // 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. + if self.is_ecotone_active() { + (Some(0), Some(0)) + } else { + (None, None) + } + } + + /// Returns the extra data for the block. + /// + /// After holocene this extracts the extradata from the paylpad + pub fn extra_data(&self) -> Result { + if self.is_holocene_active() { + self.attributes() + .get_holocene_extra_data( + self.chain_spec.base_fee_params_at_timestamp( + self.attributes().payload_attributes.timestamp, + ), + ) + .map_err(PayloadBuilderError::other) + } else { + Ok(self.config.extra_data.clone()) + } + } + + /// Returns the current fee settings for transactions from the mempool + pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { + BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) + } + + /// Returns the unique id for this payload job. + pub fn payload_id(&self) -> PayloadId { + self.attributes().payload_id() + } + + /// Returns true if regolith is active for the payload. + pub fn is_regolith_active(&self) -> bool { + self.chain_spec + .is_regolith_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if ecotone is active for the payload. + pub fn is_ecotone_active(&self) -> bool { + self.chain_spec + .is_ecotone_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if canyon is active for the payload. + pub fn is_canyon_active(&self) -> bool { + self.chain_spec + .is_canyon_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if holocene is active for the payload. + pub fn is_holocene_active(&self) -> bool { + self.chain_spec + .is_holocene_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if the fees are higher than the previous payload. + pub fn is_better_payload(&self, total_fees: U256) -> bool { + is_better_payload(self.best_payload.as_ref(), total_fees) + } + + /// Commits the withdrawals from the payload attributes to the state. + pub fn commit_withdrawals( + &self, + db: &mut State, + ) -> Result + where + DB: Database, + { + commit_withdrawals( + db, + &self.chain_spec, + self.attributes().payload_attributes.timestamp, + self.attributes().payload_attributes.withdrawals.clone(), + ) + } + + /// Ensure that the create2deployer is force-deployed at the canyon transition. Optimism + /// blocks will always have at least a single transaction in them (the L1 info transaction), + /// so we can safely assume that this will always be triggered upon the transition and that + /// the above check for empty blocks will never be hit on OP chains. + pub fn ensure_create2_deployer(&self, db: &mut State) -> Result<(), PayloadBuilderError> + where + DB: Database, + DB::Error: Display, + { + reth_optimism_evm::ensure_create2_deployer( + self.chain_spec.clone(), + self.attributes().payload_attributes.timestamp, + db, + ) + .map_err(|err| { + warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); + PayloadBuilderError::other(OpPayloadBuilderError::ForceCreate2DeployerFail) + }) + } +} + +impl OpPayloadBuilderCtx +where + EvmConfig: ConfigureEvm
, +{ + /// apply eip-4788 pre block contract call + pub fn apply_pre_beacon_root_contract_call( + &self, + db: &mut DB, + ) -> Result<(), PayloadBuilderError> + where + DB: Database + DatabaseCommit, + DB::Error: Display, + { + SystemCaller::new(self.evm_config.clone(), self.chain_spec.clone()) + .pre_block_beacon_root_contract_call( + db, + &self.initialized_cfg, + &self.initialized_block_env, + self.attributes() + .payload_attributes + .parent_beacon_block_root, + ) + .map_err(|err| { + warn!(target: "payload_builder", + parent_header=%self.parent().hash(), + %err, + "failed to apply beacon root contract call for payload" + ); + PayloadBuilderError::Internal(err.into()) + })?; + + Ok(()) + } + + /// Executes all sequencer transactions that are included in the payload attributes. + pub fn execute_sequencer_transactions( + &self, + db: &mut State, + ) -> Result + where + DB: Database, + { + let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), + TxEnv::default(), + ); + let mut evm = self.evm_config.evm_with_env(&mut *db, env); + + for sequencer_tx in &self.attributes().transactions { + // A sequencer's block should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + OpPayloadBuilderError::BlobTransactionRejected, + )); + } + + // Convert the transaction to a [TransactionSignedEcRecovered]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx + .value() + .clone() + .try_into_ecrecovered() + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + // Cache the depositor account prior to the state transition for the deposit nonce. + // + // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces + // were not introduced in Bedrock. In addition, regular transactions don't have deposit + // nonces, so we don't need to touch the DB for those. + let depositor = (self.is_regolith_active() && sequencer_tx.is_deposit()) + .then(|| { + evm.db_mut() + .load_cache_account(sequencer_tx.signer()) + .map(|acc| acc.account_info().unwrap_or_default()) + }) + .transpose() + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( + sequencer_tx.signer(), + )) + })?; + + *evm.tx_mut() = self + .evm_config + .tx_env(sequencer_tx.as_signed(), sequencer_tx.signer()); + + let ResultAndState { result, state } = match evm.transact() { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; + } + err => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(err)); + } + } + } + }; + + // commit changes + evm.db_mut().commit(state); + + let gas_used = result.gas_used(); + + // add gas used by the transaction to cumulative gas used, before creating the receipt + info.cumulative_gas_used += gas_used; + + // Push transaction changeset and calculate header bloom filter for receipt. + info.receipts.push(Some(Receipt { + tx_type: sequencer_tx.tx_type(), + success: result.is_success(), + cumulative_gas_used: info.cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: depositor.map(|account| account.nonce), + // The deposit receipt version was introduced in Canyon to indicate an update to how + // receipt hashes should be computed when set. The state transition process + // ensures this is only set for post-Canyon deposit transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + })); + + // append sender and transaction to the respective lists + info.executed_senders.push(sequencer_tx.signer()); + info.executed_transactions.push(sequencer_tx.into_signed()); + } + + Ok(info) + } + + /// Executes the given best transactions and updates the execution info. + /// + /// Returns `Ok(Some(())` if the job was cancelled. + pub fn execute_best_transactions( + &self, + info: &mut ExecutionInfo, + db: &mut State, + mut best_txs: impl PayloadTransactions, + batch_gas_limit: u64, + ) -> Result, PayloadBuilderError> + where + DB: Database, + Pool: TransactionPool, + { + let base_fee = self.base_fee(); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), + TxEnv::default(), + ); + let mut evm = self.evm_config.evm_with_env(&mut *db, env); + + while let Some(tx) = best_txs.next(()) { + // check in info if the txn has been executed already + if info.executed_transactions.contains(&tx) { + continue; + } + + // ensure we still have capacity for this transaction + if info.cumulative_gas_used + tx.gas_limit() > batch_gas_limit { + // we can't fit this transaction into the block, so we need to mark it as + // invalid which also removes all dependent transaction from + // the iterator before we can continue + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // A sequencer's block should never contain blob or deposit transactions from the pool. + if tx.is_eip4844() || tx.tx_type() == TxType::Deposit as u8 { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // check if the job was cancelled, if so we can exit early + if self.cancel.is_cancelled() { + return Ok(Some(())); + } + + // Configure the environment for the tx. + *evm.tx_mut() = self.evm_config.tx_env(tx.as_signed(), tx.signer()); + + let ResultAndState { result, state } = match evm.transact() { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + if matches!(err, InvalidTransaction::NonceTooLow { .. }) { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + } + + continue; + } + err => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(err)); + } + } + } + }; + + // commit changes + evm.db_mut().commit(state); + + let gas_used = result.gas_used(); + + // add gas used by the transaction to cumulative gas used, before creating the + // receipt + info.cumulative_gas_used += gas_used; + + // Push transaction changeset and calculate header bloom filter for receipt. + info.receipts.push(Some(Receipt { + tx_type: tx.tx_type(), + success: result.is_success(), + cumulative_gas_used: info.cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: None, + deposit_receipt_version: None, + })); + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + + // append sender and transaction to the respective lists + info.executed_senders.push(tx.signer()); + info.executed_transactions.push(tx.into_signed()); + } + + Ok(None) + } +} diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs new file mode 100644 index 00000000..dda42c54 --- /dev/null +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -0,0 +1,60 @@ +use crate::generator::{BlockCell, PayloadBuilder}; +use alloy_consensus::Header; +use reth_basic_payload_builder::PayloadBuilder as RethPayloadBuilder; // Used to import the trait +use reth_basic_payload_builder::*; +use reth_chainspec::ChainSpecProvider; +use reth_evm::ConfigureEvm; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_payload_builder_primitives::PayloadBuilderError; +use reth_provider::StateProviderFactory; +use reth_transaction_pool::TransactionPool; + +#[derive(Clone)] +pub struct VanillaOpPayloadBuilder { + inner: reth_optimism_payload_builder::OpPayloadBuilder, +} + +impl VanillaOpPayloadBuilder { + /// `OpPayloadBuilder` constructor. + pub const fn new(evm_config: EvmConfig) -> Self { + Self { + inner: reth_optimism_payload_builder::OpPayloadBuilder::new(evm_config), + } + } +} + +impl PayloadBuilder for VanillaOpPayloadBuilder +where + Client: StateProviderFactory + ChainSpecProvider, + Pool: TransactionPool, + EvmConfig: ConfigureEvm
, +{ + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + match self.inner.try_build(args)? { + BuildOutcome::Better { payload, .. } => { + best_payload.set(payload); + Ok(()) + } + BuildOutcome::Freeze(payload) => { + best_payload.set(payload); + Ok(()) + } + BuildOutcome::Cancelled => { + tracing::warn!("Payload build cancelled"); + Err(PayloadBuilderError::MissingPayload) + } + _ => { + tracing::warn!("No better payload found"); + Err(PayloadBuilderError::MissingPayload) + } + } + } +} diff --git a/crates/builder/op-rbuilder/src/tester/tester.rs b/crates/builder/op-rbuilder/src/tester/tester.rs new file mode 100644 index 00000000..b68f6000 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tester/tester.rs @@ -0,0 +1,320 @@ +use alloy_eips::BlockNumberOrTag; +use alloy_network::Ethereum; +use alloy_primitives::B256; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_provider::RootProvider; +use alloy_rpc_types_engine::ExecutionPayloadV1; +use alloy_rpc_types_engine::ExecutionPayloadV2; +use alloy_rpc_types_engine::PayloadAttributes; +use alloy_rpc_types_engine::PayloadStatusEnum; +use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatus}; +use alloy_transport::BoxTransport; +use clap::Parser; +use jsonrpsee::http_client::{transport::HttpBackend, HttpClient}; +use op_alloy_rpc_types_engine::OpPayloadAttributes; +use reth::{ + api::EngineTypes, + rpc::{ + api::EngineApiClient, + types::{engine::ForkchoiceState, BlockTransactionsKind}, + }, +}; +use reth_optimism_node::OpEngineTypes; +use reth_payload_builder::PayloadId; +use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; +use std::{marker::PhantomData, str::FromStr}; + +/// Helper for engine api operations +pub struct EngineApi { + url: String, + pub engine_api_client: HttpClient>, + pub _marker: PhantomData, +} + +pub type BoxedProvider = RootProvider; + +impl EngineApi { + pub fn new(url: &str, non_auth_url: &str) -> Result> { + let secret_layer = AuthClientLayer::new(JwtSecret::from_str( + "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a", + )?); + let middleware = tower::ServiceBuilder::default().layer(secret_layer); + let client = jsonrpsee::http_client::HttpClientBuilder::default() + .set_http_middleware(middleware) + .build(url) + .expect("Failed to create http client"); + + Ok(Self { + url: non_auth_url.to_string(), + engine_api_client: client, + _marker: PhantomData, + }) + } +} + +impl EngineApi { + /// Retrieves a v3 payload from the engine api + pub async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> eyre::Result { + Ok(EngineApiClient::::get_payload_v3(&self.engine_api_client, payload_id).await?) + } + + pub async fn new_payload( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> eyre::Result { + Ok(EngineApiClient::::new_payload_v3( + &self.engine_api_client, + payload, + versioned_hashes, + parent_beacon_block_root, + ) + .await?) + } + + /// Sends forkchoice update to the engine api + pub async fn update_forkchoice( + &self, + current_head: B256, + new_head: B256, + payload_attributes: Option, + ) -> eyre::Result { + Ok(EngineApiClient::::fork_choice_updated_v3( + &self.engine_api_client, + ForkchoiceState { + head_block_hash: new_head, + safe_block_hash: current_head, + finalized_block_hash: current_head, + }, + payload_attributes, + ) + .await?) + } + + pub async fn latest(&self) -> alloy_rpc_types_eth::Block { + self.get_block_by_number(BlockNumberOrTag::Latest, BlockTransactionsKind::Hashes) + .await + } + + pub async fn get_block_by_number( + &self, + number: BlockNumberOrTag, + kind: BlockTransactionsKind, + ) -> alloy_rpc_types_eth::Block { + // TODO: Do not know how to use the other auth provider for this rpc call + let provider: BoxedProvider = RootProvider::new_http(self.url.parse().unwrap()).boxed(); + provider + .get_block_by_number(number, kind) + .await + .unwrap() + .unwrap() + } +} + +/// This is a simple program +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(long, short, action)] + validation: bool, + + #[clap(long, short, action, default_value = "false")] + no_tx_pool: bool, +} + +#[tokio::main] +async fn main() { + let args = Args::parse(); + println!("Validation: {}", args.validation); + + let engine_api = + EngineApi::::new("http://localhost:4444", "http://localhost:1111").unwrap(); + + let validation_node_api = if args.validation { + Some( + EngineApi::::new("http://localhost:5555", "http://localhost:2222") + .unwrap(), + ) + } else { + None + }; + + let latest_block = engine_api.latest().await; + + // latest hash and timestamp + let mut latest = latest_block.header.hash; + let mut timestamp = latest_block.header.timestamp; + + if let Some(validation_node_api) = &validation_node_api { + // if the validation node is behind the builder, try to sync it using the engine api + let latest_validation_block = validation_node_api.latest().await; + + if latest_validation_block.header.number > latest_block.header.number { + panic!("validation node is ahead of the builder") + } + if latest_validation_block.header.number < latest_block.header.number { + println!( + "validation node {} is behind the builder {}, syncing up", + latest_validation_block.header.number, latest_block.header.number + ); + + let mut latest_hash = latest_validation_block.header.hash; + + // sync them up using fcu requests + for i in (latest_validation_block.header.number + 1)..=latest_block.header.number { + println!("syncing block {}", i); + + let block = engine_api + .get_block_by_number(BlockNumberOrTag::Number(i), BlockTransactionsKind::Full) + .await; + + if block.header.parent_hash != latest_hash { + panic!("not expected") + } + + let payload_request = ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: block.header.parent_hash, + fee_recipient: block.header.beneficiary, + state_root: block.header.state_root, + receipts_root: block.header.receipts_root, + logs_bloom: block.header.logs_bloom, + prev_randao: B256::ZERO, + block_number: block.header.number, + gas_limit: block.header.gas_limit, + gas_used: block.header.gas_used, + timestamp: block.header.timestamp, + extra_data: block.header.extra_data.clone(), + base_fee_per_gas: U256::from(block.header.base_fee_per_gas.unwrap()), + block_hash: block.header.hash, + transactions: vec![], // there are no txns yet + }, + withdrawals: block.withdrawals.unwrap().to_vec(), + }, + blob_gas_used: block.header.inner.blob_gas_used.unwrap(), + excess_blob_gas: block.header.inner.excess_blob_gas.unwrap(), + }; + + let validation_payload = validation_node_api + .new_payload(payload_request, vec![], B256::ZERO) + .await + .unwrap(); + + if validation_payload.status != PayloadStatusEnum::Valid { + panic!("not expected") + } + + let new_chain_hash = validation_payload.latest_valid_hash.unwrap(); + if new_chain_hash != block.header.hash { + println!("synced block {:?}", new_chain_hash); + println!("expected block {:?}", block.header.hash); + + panic!("stop") + } + + // send an fcu request to add to the canonical chain + let _result = validation_node_api + .update_forkchoice(latest_hash, new_chain_hash, None) + .await + .unwrap(); + + latest_hash = new_chain_hash; + } + } + } + + loop { + println!("latest block: {}", latest); + + let result = engine_api + .update_forkchoice( + latest, + latest, + Some(OpPayloadAttributes { + payload_attributes: PayloadAttributes { + withdrawals: Some(vec![]), + parent_beacon_block_root: Some(B256::ZERO), + timestamp: timestamp + 1000, + prev_randao: B256::ZERO, + suggested_fee_recipient: Default::default(), + }, + transactions: None, + no_tx_pool: Some(args.no_tx_pool), + gas_limit: Some(10000000000), + eip_1559_params: None, + }), + ) + .await + .unwrap(); + + if result.payload_status.status != PayloadStatusEnum::Valid { + panic!("not expected") + } + + let payload_id = result.payload_id.unwrap(); + + // Only wait for the block time to request the payload if the block builder + // is expected to use the txpool (this is, no_tx_pool is false) + if !args.no_tx_pool { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + + // query the result, do nothing, just checks that we can get it back + let payload = engine_api.get_payload_v3(payload_id).await.unwrap(); + + // Send a new_payload request to the builder node again. THIS MUST BE DONE + let validation_status = engine_api + .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) + .await + .unwrap(); + if validation_status.status != PayloadStatusEnum::Valid { + panic!("not expected") + } + + if let Some(validation_node_api) = &validation_node_api { + // Validate the payload with the validation node + let validation_status = validation_node_api + .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) + .await + .unwrap(); + + if validation_status.status != PayloadStatusEnum::Valid { + panic!("not expected") + } + } + + let new_block_hash = payload + .execution_payload + .payload_inner + .payload_inner + .block_hash; + let new_timestamp = payload + .execution_payload + .payload_inner + .payload_inner + .timestamp; + + // send an FCU wihtout payload attributes to lock in the block for the builder + let _result = engine_api + .update_forkchoice(latest, new_block_hash, None) + .await + .unwrap(); + + if let Some(validation_node_api) = &validation_node_api { + // do the same for the validator + let _result = validation_node_api + .update_forkchoice(latest, new_block_hash, None) + .await + .unwrap(); + } + + latest = new_block_hash; + timestamp = new_timestamp; + } +} From c428405f15dac5997bdf6bdeaaba28a4ef990f11 Mon Sep 17 00:00:00 2001 From: Helen Grachtz Date: Wed, 22 Jan 2025 12:22:45 +0100 Subject: [PATCH 012/262] Fix error message for KeyNotFound variant (#367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/generator.rs | 2 +- crates/builder/op-rbuilder/src/tester/tester.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 377cd086..cc6e2d51 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -217,7 +217,7 @@ where } } -/// A [PayloadJob] is a a future that's being polled by the `PayloadBuilderService` +/// A [PayloadJob] is a future that's being polled by the `PayloadBuilderService` impl EmptyBlockPayloadJob where Client: StateProviderFactory + Clone + Unpin + 'static, diff --git a/crates/builder/op-rbuilder/src/tester/tester.rs b/crates/builder/op-rbuilder/src/tester/tester.rs index b68f6000..9ebd013f 100644 --- a/crates/builder/op-rbuilder/src/tester/tester.rs +++ b/crates/builder/op-rbuilder/src/tester/tester.rs @@ -300,7 +300,7 @@ async fn main() { .payload_inner .timestamp; - // send an FCU wihtout payload attributes to lock in the block for the builder + // send an FCU without payload attributes to lock in the block for the builder let _result = engine_api .update_forkchoice(latest, new_block_hash, None) .await From 97be7b394d81df8774c64705e1708442f69f1451 Mon Sep 17 00:00:00 2001 From: ZanCorDX <126988525+ZanCorDX@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:53:12 -0300 Subject: [PATCH 013/262] Update dep to reth115 (#368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Lots of small changes for reth1.1.5 ## 💡 Motivation and Context Critical for pectra --- ## ✅ I have completed the following steps: * [X] Run `make lint` * [X] Run `make test` * [ ] Added tests (if applicable) --------- Co-authored-by: Ryan Schneider Co-authored-by: Ferran Borreguero --- crates/builder/op-rbuilder/Cargo.toml | 2 +- crates/builder/op-rbuilder/src/generator.rs | 30 +- crates/builder/op-rbuilder/src/main.rs | 76 +-- .../op-rbuilder/src/payload_builder.rs | 182 +++---- .../src/payload_builder_vanilla.rs | 9 +- .../transaction-pool-bundle-ext/Cargo.toml | 16 - .../bundle_pool_ops/rbuilder/Cargo.toml | 26 - .../bundle_pool_ops/rbuilder/src/lib.rs | 308 ------------ .../src/bundle_supported_pool.rs | 456 ------------------ .../transaction-pool-bundle-ext/src/lib.rs | 28 -- .../transaction-pool-bundle-ext/src/traits.rs | 51 -- 11 files changed, 153 insertions(+), 1031 deletions(-) delete mode 100644 crates/builder/transaction-pool-bundle-ext/Cargo.toml delete mode 100644 crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml delete mode 100644 crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs delete mode 100644 crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs delete mode 100644 crates/builder/transaction-pool-bundle-ext/src/lib.rs delete mode 100644 crates/builder/transaction-pool-bundle-ext/src/traits.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index a996ab35..d7e4743b 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -11,6 +11,7 @@ reth-optimism-chainspec.workspace = true reth-optimism-payload-builder.workspace = true reth-optimism-evm.workspace = true reth-optimism-consensus.workspace = true +reth-optimism-primitives.workspace = true reth-cli-util.workspace = true reth-payload-primitives.workspace = true reth-evm.workspace = true @@ -37,7 +38,6 @@ alloy-eips.workspace = true alloy-rpc-types-beacon.workspace = true alloy-rpc-types-engine.workspace = true op-alloy-consensus.workspace = true -op-alloy-provider.workspace = true op-alloy-rpc-types-engine.workspace = true alloy-transport-http.workspace = true alloy-rpc-types-eth.workspace = true diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index cc6e2d51..8eadc135 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -1,8 +1,6 @@ -use alloy_eips::BlockNumberOrTag; -use alloy_primitives::Bytes; use futures_util::Future; use futures_util::FutureExt; -use reth::providers::{BlockReaderIdExt, BlockSource}; +use reth::providers::BlockReaderIdExt; use reth::{ providers::StateProviderFactory, tasks::TaskSpawner, transaction_pool::TransactionPool, }; @@ -13,7 +11,6 @@ use reth_node_api::PayloadKind; use reth_payload_builder::PayloadJobGenerator; use reth_payload_builder::{KeepPayloadJobAlive, PayloadBuilderError, PayloadJob}; use reth_payload_primitives::BuiltPayload; -use reth_primitives::SealedHeader; use std::sync::{Arc, Mutex}; use tokio::sync::oneshot; use tokio::sync::Notify; @@ -96,7 +93,11 @@ impl EmptyBlockPayloadJobGenerator PayloadJobGenerator for EmptyBlockPayloadJobGenerator where - Client: StateProviderFactory + BlockReaderIdExt + Clone + Unpin + 'static, + Client: StateProviderFactory + + BlockReaderIdExt
+ + Clone + + Unpin + + 'static, Pool: TransactionPool + Unpin + 'static, Tasks: TaskSpawner + Clone + Unpin + 'static, Builder: PayloadBuilder + Unpin + 'static, @@ -111,28 +112,21 @@ where &self, attributes: >::Attributes, ) -> Result { - let parent_block = if attributes.parent().is_zero() { + let parent_header = if attributes.parent().is_zero() { // use latest block if parent is zero: genesis block self.client - .block_by_number_or_tag(BlockNumberOrTag::Latest)? + .latest_header()? .ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))? - .seal_slow() } else { - let block = self - .client - .find_block_by_hash(attributes.parent(), BlockSource::Any)? - .ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))?; - - // we already know the hash, so we can seal it - block.seal(attributes.parent()) + self.client + .sealed_header_by_hash(attributes.parent())? + .ok_or_else(|| PayloadBuilderError::MissingParentBlock(attributes.parent()))? }; - let hash = parent_block.hash(); - let header = SealedHeader::new(parent_block.header().clone(), hash); info!("Spawn block building job"); let deadline = Box::pin(tokio::time::sleep(Duration::from_secs(2))); // Or another appropriate timeout - let config = PayloadConfig::new(Arc::new(header), Bytes::default(), attributes); + let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); let mut job = EmptyBlockPayloadJob { client: self.client.clone(), diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index c54061e8..ee9b04d9 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -2,6 +2,7 @@ use clap::Parser; use generator::EmptyBlockPayloadJobGenerator; use payload_builder::OpPayloadBuilder as FBPayloadBuilder; use payload_builder_vanilla::VanillaOpPayloadBuilder; +use reth::builder::Node; use reth::{ builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}, payload::PayloadBuilderHandle, @@ -14,12 +15,16 @@ use reth::{ }; use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; use reth_node_api::NodeTypesWithEngine; +use reth_node_api::TxTy; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; use reth_optimism_evm::OpEvmConfig; +use reth_optimism_node::args::RollupArgs; use reth_optimism_node::OpEngineTypes; -use reth_optimism_node::{args::RollupArgs, node::OpAddOns, OpNode}; +use reth_optimism_node::OpNode; +use reth_optimism_primitives::OpPrimitives; use reth_payload_builder::PayloadBuilderService; +use reth_transaction_pool::PoolTransaction; pub mod generator; pub mod payload_builder; @@ -31,9 +36,16 @@ pub struct CustomPayloadBuilder; impl PayloadServiceBuilder for CustomPayloadBuilder where - Node: - FullNodeTypes>, - Pool: TransactionPool + Unpin + 'static, + Node: FullNodeTypes< + Types: NodeTypesWithEngine< + Engine = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + >, + Pool: TransactionPool>> + + Unpin + + 'static, { async fn spawn_payload_service( self, @@ -67,42 +79,30 @@ where fn main() { Cli::::parse() .run(|builder, rollup_args| async move { - if rollup_args.experimental { - tracing::warn!(target: "reth::cli", "Experimental engine is default now, and the --engine.experimental flag is deprecated. To enable the legacy functionality, use --engine.legacy."); - } - let use_legacy_engine = rollup_args.legacy; - let sequencer_http_arg = rollup_args.sequencer_http.clone(); + let engine_tree_config = TreeConfig::default() + .with_persistence_threshold(rollup_args.persistence_threshold) + .with_memory_block_buffer_target(rollup_args.memory_block_buffer_target); - match use_legacy_engine { - false => { - let engine_tree_config = TreeConfig::default() - .with_persistence_threshold(rollup_args.persistence_threshold) - .with_memory_block_buffer_target(rollup_args.memory_block_buffer_target); - let handle = builder - .with_types_and_provider::>() - .with_components( - OpNode::components(rollup_args).payload(CustomPayloadBuilder::default()), - ) - .with_add_ons(OpAddOns::new(sequencer_http_arg)) - .launch_with_fn(|builder| { - let launcher = EngineNodeLauncher::new( - builder.task_executor().clone(), - builder.config().datadir(), - engine_tree_config, - ); - builder.launch_with(launcher) - }) - .await?; + let op_node = OpNode::new(rollup_args.clone()); + let handle = builder + .with_types_and_provider::>() + .with_components( + op_node + .components() + .payload(CustomPayloadBuilder::default()), + ) + .with_add_ons(op_node.add_ons()) + .launch_with_fn(|builder| { + let launcher = EngineNodeLauncher::new( + builder.task_executor().clone(), + builder.config().datadir(), + engine_tree_config, + ); + builder.launch_with(launcher) + }) + .await?; - handle.node_exit_future.await - }, - true => { - let handle = - builder.node(OpNode::new(rollup_args.clone())).launch().await?; - - handle.node_exit_future.await - }, - } + handle.node_exit_future.await }) .unwrap(); } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index fc080968..3292dbca 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -1,26 +1,32 @@ use std::{fmt::Display, sync::Arc}; use crate::generator::{BlockCell, PayloadBuilder}; -use alloy_consensus::{Header, Transaction, EMPTY_OMMER_ROOT_HASH}; +use alloy_consensus::{Eip658Value, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::merge::BEACON_NONCE; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rpc_types_engine::PayloadId; +use op_alloy_consensus::{OpDepositReceipt, OpTxType}; use reth_basic_payload_builder::*; -use reth_chain_state::ExecutedBlock; use reth_chainspec::ChainSpecProvider; -use reth_evm::{system_calls::SystemCaller, ConfigureEvm, NextBlockEnvAttributes}; +use reth_evm::{env::EvmEnv, system_calls::SystemCaller, ConfigureEvm, NextBlockEnvAttributes}; use reth_execution_types::ExecutionOutcome; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; use reth_optimism_forks::OpHardforks; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; use reth_payload_util::PayloadTransactions; -use reth_primitives::{proofs, Block, BlockBody, Receipt, SealedHeader, TransactionSigned, TxType}; -use reth_provider::{ProviderError, StateProviderFactory, StateRootProvider}; +use reth_primitives::BlockExt; +use reth_primitives::{ + proofs, transaction::SignedTransactionIntoRecoveredExt, Block, BlockBody, SealedHeader, TxType, +}; +use reth_provider::{ + HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, +}; use reth_revm::database::StateProviderDatabase; +use reth_transaction_pool::PoolTransaction; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; -use reth_trie::HashedPostState; use revm::{ db::{states::bundle_state::BundleRetention, BundleState, State}, primitives::{ @@ -31,7 +37,6 @@ use revm::{ }; use tracing::{debug, trace, warn}; -use op_alloy_consensus::DepositTransaction; use reth_optimism_payload_builder::error::OpPayloadBuilderError; use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_transaction_pool::pool::BestPayloadTransactions; @@ -62,7 +67,7 @@ impl OpPayloadBuilder { impl OpPayloadBuilder where - EvmConfig: ConfigureEvm
, + EvmConfig: ConfigureEvm
, Txs: OpPayloadTransactions, { /// Constructs an Optimism payload from the transactions sent via the @@ -80,12 +85,14 @@ where ) -> Result<(), PayloadBuilderError> where Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool, + Pool: TransactionPool>, { - let (initialized_cfg, initialized_block_env) = self + let EvmEnv { + cfg_env_with_handler_cfg, + block_env, + } = self .cfg_and_block_env(&args.config.attributes, &args.config.parent_header) .map_err(PayloadBuilderError::other)?; - let BuildArguments { client, pool, @@ -98,8 +105,8 @@ where evm_config: self.evm_config.clone(), chain_spec: client.chain_spec(), config, - initialized_cfg, - initialized_block_env, + initialized_cfg: cfg_env_with_handler_cfg, + initialized_block_env: block_env, cancel, best_payload: None, // remove }; @@ -163,12 +170,7 @@ where .build(); let best_txs = best.best_transactions(&pool, ctx.best_transaction_attributes()); - ctx.execute_best_transactions::<_, Pool>( - &mut info, - &mut db, - best_txs, - total_gas_per_batch, - )?; + ctx.execute_best_transactions(&mut info, &mut db, best_txs, total_gas_per_batch)?; if ctx.cancel.is_cancelled() { tracing::info!( @@ -202,11 +204,12 @@ where &self, attributes: &OpPayloadBuilderAttributes, parent: &Header, - ) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), EvmConfig::Error> { + ) -> Result { let next_attributes = NextBlockEnvAttributes { timestamp: attributes.timestamp(), suggested_fee_recipient: attributes.suggested_fee_recipient(), prev_randao: attributes.prev_randao(), + gas_limit: parent.gas_limit, }; self.evm_config .next_cfg_and_block_env(parent, next_attributes) @@ -216,8 +219,8 @@ where impl PayloadBuilder for OpPayloadBuilder where Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool, - EvmConfig: ConfigureEvm
, + Pool: TransactionPool>, + EvmConfig: ConfigureEvm
, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; @@ -239,14 +242,9 @@ pub fn build_block( where EvmConfig: ConfigureEvm
, DB: Database + AsRef

, - P: StateRootProvider, + P: StateRootProvider + HashedPostStateProvider, { - let withdrawals_outcome = ctx.commit_withdrawals(&mut state)?; - - let WithdrawalsOutcome { - withdrawals, - withdrawals_root, - } = withdrawals_outcome; + let withdrawals_root = ctx.commit_withdrawals(&mut state)?; // TODO: We must run this only once per block, but we are running it on every flashblock // merge all transitions into bundle state, this would apply the withdrawal balance changes @@ -260,7 +258,7 @@ where let execution_outcome = ExecutionOutcome::new( new_bundle.clone(), - vec![info.receipts.clone()].into(), + info.receipts.clone().into(), block_number, Vec::new(), ); @@ -278,8 +276,9 @@ where .expect("Number is in range"); // // calculate the state root - let hashed_state = HashedPostState::from_bundle_state(&execution_outcome.state().state); - let (state_root, trie_output) = { + let state_provider = state.database.as_ref(); + let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); + let (state_root, _trie_output) = { state .database .as_ref() @@ -326,6 +325,7 @@ where requests_hash: None, }; + let withdrawals = Some(ctx.attributes().payload_attributes.withdrawals().clone()); // seal the block let block = Block { header, @@ -336,18 +336,11 @@ where }, }; - let sealed_block = Arc::new(block.seal_slow()); + let sealed_block: Arc< + reth_primitives::SealedBlock>, + > = Arc::new(block.seal_slow()); debug!(target: "payload_builder", ?sealed_block, "sealed built block"); - // create the executed block data - let _executed = ExecutedBlock { - block: sealed_block.clone(), - senders: Arc::new(info.executed_senders.clone()), - execution_output: Arc::new(execution_outcome.clone()), - hashed_state: Arc::new(hashed_state.clone()), - trie: Arc::new(trie_output.clone()), - }; - Ok(( OpBuiltPayload::new( ctx.payload_id(), @@ -370,7 +363,7 @@ fn execute_pre_steps( ctx: &OpPayloadBuilderCtx, ) -> Result where - EvmConfig: ConfigureEvm

, + EvmConfig: ConfigureEvm
, DB: Database, { // 1. apply eip-4788 pre block contract call @@ -389,19 +382,23 @@ where pub 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( + fn best_transactions< + Pool: TransactionPool>, + >( &self, pool: Pool, attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions; + ) -> impl PayloadTransactions; } impl OpPayloadTransactions for () { - fn best_transactions( + fn best_transactions< + Pool: TransactionPool>, + >( &self, pool: Pool, attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions { + ) -> impl PayloadTransactions { BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) } } @@ -411,19 +408,19 @@ impl OpPayloadTransactions for () { pub struct ExecutedPayload { /// Tracked execution info pub info: ExecutionInfo, - /// Outcome after committing withdrawals. - pub withdrawals_outcome: WithdrawalsOutcome, + /// Withdrawal hash. + pub withdrawals_root: Option, } /// This acts as the container for executed transactions and its byproducts (receipts, gas used) #[derive(Default, Debug)] pub struct ExecutionInfo { /// All executed transactions (unrecovered). - pub executed_transactions: Vec, + pub executed_transactions: Vec, /// The recovered senders for the executed transactions. pub executed_senders: Vec
, /// The transaction receipts - pub receipts: Vec>, + pub receipts: Vec, /// All gas used so far pub cumulative_gas_used: u64, /// Tracks fees from executed mempool transactions @@ -524,7 +521,7 @@ impl OpPayloadBuilderCtx { ) .map_err(PayloadBuilderError::other) } else { - Ok(self.config.extra_data.clone()) + Ok(Default::default()) } } @@ -568,10 +565,7 @@ impl OpPayloadBuilderCtx { } /// Commits the withdrawals from the payload attributes to the state. - pub fn commit_withdrawals( - &self, - db: &mut State, - ) -> Result + pub fn commit_withdrawals(&self, db: &mut State) -> Result, ProviderError> where DB: Database, { @@ -579,7 +573,7 @@ impl OpPayloadBuilderCtx { db, &self.chain_spec, self.attributes().payload_attributes.timestamp, - self.attributes().payload_attributes.withdrawals.clone(), + &self.attributes().payload_attributes.withdrawals, ) } @@ -606,7 +600,7 @@ impl OpPayloadBuilderCtx { impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvm
, + EvmConfig: ConfigureEvm
, { /// apply eip-4788 pre block contract call pub fn apply_pre_beacon_root_contract_call( @@ -695,7 +689,7 @@ where *evm.tx_mut() = self .evm_config - .tx_env(sequencer_tx.as_signed(), sequencer_tx.signer()); + .tx_env(sequencer_tx.tx(), sequencer_tx.signer()); let ResultAndState { result, state } = match evm.transact() { Ok(res) => res, @@ -721,22 +715,32 @@ where // add gas used by the transaction to cumulative gas used, before creating the receipt info.cumulative_gas_used += gas_used; - // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts.push(Some(Receipt { - tx_type: sequencer_tx.tx_type(), - success: result.is_success(), + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(result.is_success()), cumulative_gas_used: info.cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: depositor.map(|account| account.nonce), - // The deposit receipt version was introduced in Canyon to indicate an update to how - // receipt hashes should be computed when set. The state transition process - // ensures this is only set for post-Canyon deposit transactions. - deposit_receipt_version: self.is_canyon_active().then_some(1), - })); + logs: result.into_logs().into_iter().collect(), + }; + + // Push transaction changeset and calculate header bloom filter for receipt. + info.receipts.push(match sequencer_tx.tx_type() { + OpTxType::Legacy => OpReceipt::Legacy(receipt), + OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), + OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), + OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), + OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { + inner: receipt, + deposit_nonce: depositor.map(|account| account.nonce), + // The deposit receipt version was introduced in Canyon to indicate an update to + // how receipt hashes should be computed when set. The state + // transition process ensures this is only set for + // post-Canyon deposit transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + }), + }); // append sender and transaction to the respective lists info.executed_senders.push(sequencer_tx.signer()); - info.executed_transactions.push(sequencer_tx.into_signed()); + info.executed_transactions.push(sequencer_tx.into_tx()); } Ok(info) @@ -745,16 +749,15 @@ where /// Executes the given best transactions and updates the execution info. /// /// Returns `Ok(Some(())` if the job was cancelled. - pub fn execute_best_transactions( + pub fn execute_best_transactions( &self, info: &mut ExecutionInfo, db: &mut State, - mut best_txs: impl PayloadTransactions, + mut best_txs: impl PayloadTransactions, batch_gas_limit: u64, ) -> Result, PayloadBuilderError> where DB: Database, - Pool: TransactionPool, { let base_fee = self.base_fee(); @@ -792,7 +795,7 @@ where } // Configure the environment for the tx. - *evm.tx_mut() = self.evm_config.tx_env(tx.as_signed(), tx.signer()); + *evm.tx_mut() = self.evm_config.tx_env(tx.tx(), tx.signer()); let ResultAndState { result, state } = match evm.transact() { Ok(res) => res, @@ -828,15 +831,24 @@ where // receipt info.cumulative_gas_used += gas_used; - // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts.push(Some(Receipt { - tx_type: tx.tx_type(), - success: result.is_success(), + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(result.is_success()), cumulative_gas_used: info.cumulative_gas_used, - logs: result.into_logs().into_iter().map(Into::into).collect(), - deposit_nonce: None, - deposit_receipt_version: None, - })); + logs: result.into_logs().into_iter().collect(), + }; + + // Push transaction changeset and calculate header bloom filter for receipt. + info.receipts.push(match tx.tx_type() { + OpTxType::Legacy => OpReceipt::Legacy(receipt), + OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), + OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), + OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), + OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { + inner: receipt, + deposit_nonce: None, + deposit_receipt_version: None, + }), + }); // update add to total fees let miner_fee = tx @@ -846,7 +858,7 @@ where // append sender and transaction to the respective lists info.executed_senders.push(tx.signer()); - info.executed_transactions.push(tx.into_signed()); + info.executed_transactions.push(tx.into_tx()); } Ok(None) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index dda42c54..c64878c5 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -6,9 +6,10 @@ use reth_chainspec::ChainSpecProvider; use reth_evm::ConfigureEvm; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_optimism_primitives::OpTransactionSigned; use reth_payload_builder_primitives::PayloadBuilderError; use reth_provider::StateProviderFactory; -use reth_transaction_pool::TransactionPool; +use reth_transaction_pool::{PoolTransaction, TransactionPool}; #[derive(Clone)] pub struct VanillaOpPayloadBuilder { @@ -17,7 +18,7 @@ pub struct VanillaOpPayloadBuilder { impl VanillaOpPayloadBuilder { /// `OpPayloadBuilder` constructor. - pub const fn new(evm_config: EvmConfig) -> Self { + pub fn new(evm_config: EvmConfig) -> Self { Self { inner: reth_optimism_payload_builder::OpPayloadBuilder::new(evm_config), } @@ -27,8 +28,8 @@ impl VanillaOpPayloadBuilder { impl PayloadBuilder for VanillaOpPayloadBuilder where Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool, - EvmConfig: ConfigureEvm
, + Pool: TransactionPool>, + EvmConfig: ConfigureEvm
, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; diff --git a/crates/builder/transaction-pool-bundle-ext/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/Cargo.toml deleted file mode 100644 index 83b64ddd..00000000 --- a/crates/builder/transaction-pool-bundle-ext/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "transaction-pool-bundle-ext" -version.workspace = true -edition.workspace = true - -[dependencies] -reth = { workspace = true } -reth-eth-wire-types = { workspace = true } -reth-primitives = { workspace = true } -reth-transaction-pool = { workspace = true } -alloy-primitives = { workspace = true, default-features = false } -alloy-rpc-types-beacon = { workspace = true, features = ["ssz"] } -alloy-eips = { workspace = true } - - -tokio = { workspace = true } diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml deleted file mode 100644 index 7a4528c2..00000000 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "rbuilder-bundle-pool-operations" -version = "0.1.0" -edition = "2021" - -[dependencies] -transaction-pool-bundle-ext = { path = "../.." } -rbuilder = { path = "../../../rbuilder" } - -alloy-primitives.workspace = true -alloy-rpc-types-beacon.workspace = true -reth-primitives = { workspace = true } -reth-transaction-pool = { workspace = true } -reth-provider = { workspace = true } - -derive_more = { workspace = true } -eyre = { workspace = true } -tokio = { workspace = true } -tokio-util = { workspace = true } -tracing = { workspace = true } - -[features] -optimism = [ - "rbuilder/optimism", - "reth-primitives/optimism", -] diff --git a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs deleted file mode 100644 index c4de66e5..00000000 --- a/crates/builder/transaction-pool-bundle-ext/bundle_pool_ops/rbuilder/src/lib.rs +++ /dev/null @@ -1,308 +0,0 @@ -//! Implementation of [`BundlePoolOperations`] for the classic rbuilder that -//! supports [`EthSendBundle`]s. - -#![cfg_attr(not(test), warn(unused_crate_dependencies))] - -use core::fmt; -use std::{fmt::Formatter, sync::Arc, time::Duration}; - -use alloy_primitives::U256; -use alloy_rpc_types_beacon::events::PayloadAttributesEvent; -use derive_more::From; -use rbuilder::{ - building::{ - builders::{ - block_building_helper::BlockBuildingHelper, ordering_builder::OrderingBuilderConfig, - UnfinishedBlockBuildingSink, UnfinishedBlockBuildingSinkFactory, - }, - Sorting, - }, - live_builder::{ - cli::LiveBuilderConfig, - config::{create_builders, BuilderConfig, Config, SpecificBuilderConfig}, - order_input::{rpc_server::RawCancelBundle, ReplaceableOrderPoolCommand}, - payload_events::MevBoostSlotData, - SlotSource, - }, - primitives::{Bundle, BundleReplacementKey, Order}, - provider::reth_prov::StateProviderFactoryFromRethProvider, - telemetry, -}; -use reth_primitives::TransactionSigned; -use reth_provider::{BlockReader, DatabaseProviderFactory, HeaderProvider}; -use reth_transaction_pool::{ - BlobStore, EthPooledTransaction, Pool, TransactionOrdering, TransactionValidator, -}; -use tokio::{ - sync::{ - mpsc::{self, error::SendError}, - watch, - }, - task, - time::sleep, -}; -use tokio_util::sync::CancellationToken; -use tracing::error; -use transaction_pool_bundle_ext::BundlePoolOperations; - -/// [`BundlePoolOperations`] implementation which uses components of the -/// [`rbuilder`] under the hood to handle classic [`EthSendBundle`]s. -pub struct BundlePoolOps { - // Channel to stream new [`OrderPool`] events to the rbuilder - orderpool_tx: mpsc::Sender, - // Channel to stream new payload attribute events to rbuilder - payload_attributes_tx: mpsc::UnboundedSender<(PayloadAttributesEvent, Option)>, - /// Channel containing the latest [`BlockBuildingHelper`] recieved from the rbuilder - block_building_helper_rx: watch::Receiver>>, -} - -#[derive(Debug)] -struct OurSlotSource { - /// Channel [`OurSlotSource`] uses to receive payload attributes from reth - payload_attributes_rx: mpsc::UnboundedReceiver<(PayloadAttributesEvent, Option)>, -} - -impl SlotSource for OurSlotSource { - fn recv_slot_channel(self) -> mpsc::UnboundedReceiver { - let (slot_sender, slot_receiver) = mpsc::unbounded_channel(); - - // Spawn a task that receives payload attributes, converts them - // into [`MevBoostSlotData`] for rbuilder, then forwards them. - tokio::spawn(async move { - let mut recv = self.payload_attributes_rx; - while let Some((payload_event, gas_limit)) = recv.recv().await { - let mev_boost_data = MevBoostSlotData { - payload_attributes_event: payload_event, - suggested_gas_limit: gas_limit.unwrap_or(0), - relays: vec![], - slot_data: Default::default(), - }; - - if slot_sender.send(mev_boost_data).is_err() { - error!("Error sending MevBoostSlotData through channel"); - break; - } - } - }); - - // Return the receiver end for SlotSource trait - slot_receiver - } -} - -impl BundlePoolOps { - pub async fn new( - provider: P, - pool: Pool, - config: Config, - ) -> Result - where - P: DatabaseProviderFactory - + reth_provider::StateProviderFactory - + HeaderProvider - + Clone - + 'static, - V: TransactionValidator + 'static, - T: TransactionOrdering::Transaction>, - S: BlobStore, - { - // Create the payload source to trigger new block building - let cancellation_token = CancellationToken::new(); - let (payload_attributes_tx, payload_attributes_rx) = mpsc::unbounded_channel(); - let slot_source = OurSlotSource { - payload_attributes_rx, - }; - - let (block_building_helper_tx, block_building_helper_rx) = watch::channel(None); - let sink_factory = SinkFactory { - block_building_helper_tx, - }; - - let builder_strategy = BuilderConfig { - name: "mp-ordering".to_string(), - builder: SpecificBuilderConfig::OrderingBuilder(OrderingBuilderConfig { - discard_txs: true, - sorting: Sorting::MaxProfit, - failed_order_retries: 1, - drop_failed_orders: true, - coinbase_payment: false, - build_duration_deadline_ms: None, - }), - }; - let provider = StateProviderFactoryFromRethProvider::new( - provider, - config.base_config().live_root_hash_config()?, - ); - - let builders = create_builders(vec![builder_strategy]); - - // Build and run the process - let builder = config - .base_config - .create_builder_with_provider_factory( - cancellation_token, - Box::new(sink_factory), - slot_source, - provider, - ) - .await - .unwrap() - .with_builders(builders); - let orderpool_tx = builder.orderpool_sender.clone(); - - // Spawn in separate thread - let _handle = task::spawn(async move { - // Wait for 5 seconds for reth to init - sleep(Duration::from_secs(5)).await; - - // Spawn redacted server that is safe for tdx builders to expose - telemetry::servers::redacted::spawn( - config.base_config().redacted_telemetry_server_address(), - ) - .await - .expect("Failed to start redacted telemetry server"); - - // Spawn debug server that exposes detailed operational information - telemetry::servers::full::spawn( - config.base_config().full_telemetry_server_address(), - config.version_for_telemetry(), - config.base_config().log_enable_dynamic, - ) - .await - .expect("Failed to start full telemetry server"); - - builder - .connect_to_transaction_pool(pool) - .await - .expect("Failed to connect to reth pool"); - builder.run().await.unwrap(); - - Ok::<(), ()> - }); - - Ok(BundlePoolOps { - block_building_helper_rx, - payload_attributes_tx, - orderpool_tx, - }) - } -} - -impl BundlePoolOperations for BundlePoolOps { - /// Signed eth transaction - type Transaction = TransactionSigned; - type Bundle = Bundle; - type CancelBundleReq = RawCancelBundle; - type Error = Error; - - async fn add_bundle(&self, bundle: Self::Bundle) -> Result<(), Self::Error> { - self.orderpool_tx - .send(ReplaceableOrderPoolCommand::Order(Order::Bundle(bundle))) - .await?; - Ok(()) - } - - async fn cancel_bundle( - &self, - cancel_bundle_request: Self::CancelBundleReq, - ) -> Result<(), Self::Error> { - let key = BundleReplacementKey::new( - cancel_bundle_request.replacement_uuid, - cancel_bundle_request.signing_address, - ); - self.orderpool_tx - .send(ReplaceableOrderPoolCommand::CancelBundle(key)) - .await?; - Ok(()) - } - - fn get_transactions( - &self, - requested_slot: U256, - ) -> Result, Self::Error> { - match *self.block_building_helper_rx.borrow() { - Some(ref block_builder) => { - let rbuilder_slot = block_builder.building_context().block_env.number; - if rbuilder_slot != requested_slot { - return Ok(vec![]); - } - let orders = block_builder.built_block_trace().included_orders.clone(); - let orders = orders - .iter() - .flat_map(|order| order.txs.iter()) - .map(|er| er.clone().into_internal_tx_unsecure().into_signed()) - .collect::>(); - Ok(orders) - } - None => Ok(vec![]), - } - } - - fn notify_payload_attributes_event( - &self, - payload_attributes: PayloadAttributesEvent, - gas_limit: Option, - ) -> Result<(), Self::Error> { - self.payload_attributes_tx - .send((payload_attributes, gas_limit))?; - Ok(()) - } -} - -struct Sink { - /// Channel for rbuilder to notify us of new [`BlockBuildingHelper`]s as it builds blocks - block_building_helper_tx: watch::Sender>>, -} - -impl derive_more::Debug for Sink { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Sink").finish() - } -} - -struct SinkFactory { - /// Channel for rbuilder to notify us of new [`BlockBuildingHelper`]s as it builds blocks - block_building_helper_tx: watch::Sender>>, -} - -impl derive_more::Debug for SinkFactory { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("SinkFactory").finish() - } -} - -impl UnfinishedBlockBuildingSinkFactory for SinkFactory { - fn create_sink( - &mut self, - _slot_data: MevBoostSlotData, - _cancel: CancellationToken, - ) -> Arc { - Arc::new(Sink { - block_building_helper_tx: self.block_building_helper_tx.clone(), - }) - } -} - -impl UnfinishedBlockBuildingSink for Sink { - fn new_block(&self, block: Box) { - self.block_building_helper_tx.send(Some(block)).unwrap() - } - - fn can_use_suggested_fee_recipient_as_coinbase(&self) -> bool { - true - } -} - -#[allow(clippy::large_enum_variant)] -/// [`BundlePoolOperations`] error type. -#[derive(Debug, From)] -pub enum Error { - #[from] - Eyre(eyre::Error), - - #[from] - SendPayloadAttributes(SendError<(PayloadAttributesEvent, Option)>), - - #[from] - SendReplaceableOrderPoolCommand(SendError), -} diff --git a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs b/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs deleted file mode 100644 index c82df5e0..00000000 --- a/crates/builder/transaction-pool-bundle-ext/src/bundle_supported_pool.rs +++ /dev/null @@ -1,456 +0,0 @@ -//! Houses [`BundleSupportedPool`]. - -use alloy_eips::eip4844::{BlobAndProofV1, BlobTransactionSidecar}; -use alloy_primitives::{Address, TxHash, B256, U256}; -use alloy_rpc_types_beacon::events::PayloadAttributesEvent; -use reth::providers::ChangedAccount; -use reth_eth_wire_types::HandleMempoolData; -use reth_primitives::PooledTransactionsElement; -use reth_transaction_pool::{ - AllPoolTransactions, AllTransactionsEvents, BestTransactions, BestTransactionsAttributes, - BlobStore, BlobStoreError, BlockInfo, CanonicalStateUpdate, EthPoolTransaction, - GetPooledTransactionLimit, NewBlobSidecar, NewTransactionEvent, Pool, PoolResult, PoolSize, - PropagatedTransactions, TransactionEvents, TransactionListenerKind, TransactionOrdering, - TransactionOrigin, TransactionPool, TransactionPoolExt as TransactionPoolBlockInfoExt, - TransactionValidator, ValidPoolTransaction, -}; -use std::{collections::HashSet, future::Future, sync::Arc}; -use tokio::sync::mpsc::Receiver; - -use crate::{traits::BundlePoolOperations, TransactionPoolBundleExt}; - -/// Allows easily creating a `Pool` type for reth's `PoolBuilder` that supports -/// a new [`BundlePool`] running alongside [`Pool`]. -/// -/// To be used in place of the reth [`Pool`] when defining the `Pool` type -/// for reth's `PoolBuilder`. -/// -/// Just as logic for the [`Pool`] is generic based on `V`, `T`, `S` generics, bundle pool -/// logic is generic based on a new `B` generic that implements [`BundlePoolOperations`]. -/// -/// Achieves this by implementing [`TransactionPool`] and [`BundlePoolOperations`], -/// and therefore also [`TransactionPoolBundleExt`]. -/// -/// ## Example -/// -/// ```ignore -/// /// An extended optimism transaction pool with bundle support. -/// #[derive(Debug, Default, Clone, Copy)] -/// pub struct CustomPoolBuilder; -/// -/// pub type MyCustomTransactionPoolWithBundleSupport = BundleSupportedPool< -/// TransactionValidationTaskExecutor>, -/// CoinbaseTipOrdering, -/// S, -/// MyBundlePoolOperationsImplementation, -/// >; -/// -/// impl PoolBuilder for CustomPoolBuilder -/// where -/// Node: FullNodeTypes, -/// { -/// type Pool = MyCustomTransactionPoolWithBundleSupport; -/// // the rest of the PoolBuilder implementation... -/// ``` -#[derive(Debug)] -pub struct BundleSupportedPool { - /// Arc'ed instance of [`Pool`] internals - tx_pool: Pool, - /// Arc'ed instance of the [`BundlePool`] internals - bundle_pool: Arc>, -} - -impl BundleSupportedPool -where - V: TransactionValidator, - T: TransactionOrdering::Transaction>, - S: BlobStore, - B: BundlePoolOperations, -{ - pub fn new(pool: Pool, bundle_ops: B) -> Self { - Self { - tx_pool: pool, - bundle_pool: Arc::new(BundlePool::::new(bundle_ops)), - } - } -} - -/// Houses generic bundle logic. -#[derive(Debug, Default)] -struct BundlePool { - pub ops: B, -} - -impl BundlePool { - fn new(ops: B) -> Self { - Self { ops } - } -} - -/// [`TransactionPool`] requires implementors to be [`Clone`]. -impl Clone for BundleSupportedPool { - fn clone(&self) -> Self { - Self { - tx_pool: self.tx_pool.clone(), - bundle_pool: Arc::clone(&self.bundle_pool), - } - } -} - -/// Implements the [`TransactionPool`] interface by delegating to the inner `tx_pool`. -/// TODO: Use a crate like `delegate!` or `ambassador` to automate this. -impl TransactionPool for BundleSupportedPool -where - V: TransactionValidator, - T: TransactionOrdering::Transaction>, - S: BlobStore, - B: BundlePoolOperations, -{ - type Transaction = T::Transaction; - - fn pool_size(&self) -> PoolSize { - self.tx_pool.pool_size() - } - - fn block_info(&self) -> BlockInfo { - self.tx_pool.block_info() - } - - async fn add_transaction_and_subscribe( - &self, - origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> PoolResult { - self.tx_pool - .add_transaction_and_subscribe(origin, transaction) - .await - } - - async fn add_transaction( - &self, - origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> PoolResult { - self.tx_pool.add_transaction(origin, transaction).await - } - - async fn add_transactions( - &self, - origin: TransactionOrigin, - transactions: Vec, - ) -> Vec> { - self.tx_pool.add_transactions(origin, transactions).await - } - - fn transaction_event_listener(&self, tx_hash: TxHash) -> Option { - self.tx_pool.transaction_event_listener(tx_hash) - } - - fn all_transactions_event_listener(&self) -> AllTransactionsEvents { - self.tx_pool.all_transactions_event_listener() - } - - fn pending_transactions_listener_for(&self, kind: TransactionListenerKind) -> Receiver { - self.tx_pool.pending_transactions_listener_for(kind) - } - - fn blob_transaction_sidecars_listener(&self) -> Receiver { - self.tx_pool.blob_transaction_sidecars_listener() - } - - fn get_pending_transactions_by_origin( - &self, - origin: TransactionOrigin, - ) -> Vec>> { - self.tx_pool.get_pending_transactions_by_origin(origin) - } - - fn new_transactions_listener_for( - &self, - kind: TransactionListenerKind, - ) -> Receiver> { - self.tx_pool.new_transactions_listener_for(kind) - } - - fn pooled_transaction_hashes(&self) -> Vec { - self.tx_pool.pooled_transaction_hashes() - } - - fn pooled_transaction_hashes_max(&self, max: usize) -> Vec { - self.tx_pool.pooled_transaction_hashes_max(max) - } - - fn pooled_transactions(&self) -> Vec>> { - self.tx_pool.pooled_transactions() - } - - fn pooled_transactions_max( - &self, - max: usize, - ) -> Vec>> { - self.tx_pool.pooled_transactions_max(max) - } - - fn get_pooled_transaction_elements( - &self, - tx_hashes: Vec, - limit: GetPooledTransactionLimit, - ) -> Vec { - self.tx_pool - .get_pooled_transaction_elements(tx_hashes, limit) - } - - fn get_pooled_transaction_element(&self, tx_hash: TxHash) -> Option { - self.tx_pool.get_pooled_transaction_element(tx_hash) - } - - fn best_transactions( - &self, - ) -> Box>>> { - self.tx_pool.best_transactions() - } - - fn best_transactions_with_attributes( - &self, - best_transactions_attributes: BestTransactionsAttributes, - ) -> Box>>> { - self.tx_pool - .best_transactions_with_attributes(best_transactions_attributes) - } - - fn pending_transactions(&self) -> Vec>> { - self.tx_pool.pending_transactions() - } - - fn queued_transactions(&self) -> Vec>> { - self.tx_pool.queued_transactions() - } - - fn all_transactions(&self) -> AllPoolTransactions { - self.tx_pool.all_transactions() - } - - fn remove_transactions( - &self, - hashes: Vec, - ) -> Vec>> { - self.tx_pool.remove_transactions(hashes) - } - - fn retain_unknown(&self, announcement: &mut A) - where - A: HandleMempoolData, - { - self.tx_pool.retain_unknown(announcement) - } - - fn get(&self, tx_hash: &TxHash) -> Option>> { - self.tx_pool.get(tx_hash) - } - - fn get_all(&self, txs: Vec) -> Vec>> { - self.tx_pool.get_all(txs) - } - - fn on_propagated(&self, txs: PropagatedTransactions) { - self.tx_pool.on_propagated(txs) - } - - fn get_transactions_by_sender( - &self, - sender: Address, - ) -> Vec>> { - self.tx_pool.get_transactions_by_sender(sender) - } - - fn get_transaction_by_sender_and_nonce( - &self, - sender: Address, - nonce: u64, - ) -> Option>> { - self.tx_pool - .get_transaction_by_sender_and_nonce(sender, nonce) - } - - fn get_transactions_by_origin( - &self, - origin: TransactionOrigin, - ) -> Vec>> { - self.tx_pool.get_transactions_by_origin(origin) - } - - fn unique_senders(&self) -> HashSet
{ - self.tx_pool.unique_senders() - } - - fn get_blob( - &self, - tx_hash: TxHash, - ) -> Result>, BlobStoreError> { - self.tx_pool.get_blob(tx_hash) - } - - fn get_all_blobs( - &self, - tx_hashes: Vec, - ) -> Result)>, BlobStoreError> { - self.tx_pool.get_all_blobs(tx_hashes) - } - - fn get_all_blobs_exact( - &self, - tx_hashes: Vec, - ) -> Result>, BlobStoreError> { - self.tx_pool.get_all_blobs_exact(tx_hashes) - } - - fn remove_transactions_and_descendants( - &self, - hashes: Vec, - ) -> Vec>> { - self.tx_pool.remove_transactions_and_descendants(hashes) - } - - fn remove_transactions_by_sender( - &self, - sender: Address, - ) -> Vec>> { - self.tx_pool.remove_transactions_by_sender(sender) - } - - fn get_pending_transactions_with_predicate( - &self, - predicate: impl FnMut(&ValidPoolTransaction) -> bool, - ) -> Vec>> { - self.tx_pool - .get_pending_transactions_with_predicate(predicate) - } - - fn get_pending_transactions_by_sender( - &self, - sender: Address, - ) -> Vec>> { - self.tx_pool.get_pending_transactions_by_sender(sender) - } - - fn get_queued_transactions_by_sender( - &self, - sender: Address, - ) -> Vec>> { - self.tx_pool.get_queued_transactions_by_sender(sender) - } - - fn get_highest_transaction_by_sender( - &self, - sender: Address, - ) -> Option>> { - self.tx_pool.get_highest_transaction_by_sender(sender) - } - - fn get_highest_consecutive_transaction_by_sender( - &self, - sender: Address, - on_chain_nonce: u64, - ) -> Option>> { - self.tx_pool - .get_highest_consecutive_transaction_by_sender(sender, on_chain_nonce) - } - - fn get_blobs_for_versioned_hashes( - &self, - versioned_hashes: &[B256], - ) -> Result>, BlobStoreError> { - self.tx_pool - .get_blobs_for_versioned_hashes(versioned_hashes) - } -} - -/// Implements the [`BundlePoolOperations`] interface by delegating to the inner `bundle_pool`. -/// TODO: Use a crate like `delegate!` or `ambassador` to automate this. -impl BundlePoolOperations for BundleSupportedPool -where - V: TransactionValidator, - T: TransactionOrdering::Transaction>, - S: BlobStore, - B: BundlePoolOperations, -{ - type Bundle = ::Bundle; - type CancelBundleReq = ::CancelBundleReq; - type Transaction = ::Transaction; - type Error = ::Error; - - fn add_bundle( - &self, - bundle: Self::Bundle, - ) -> impl Future> + Send { - self.bundle_pool.ops.add_bundle(bundle) - } - - fn cancel_bundle( - &self, - cancel_bundle_req: Self::CancelBundleReq, - ) -> impl Future> + Send { - self.bundle_pool.ops.cancel_bundle(cancel_bundle_req) - } - - fn get_transactions( - &self, - slot: U256, - ) -> Result, Self::Error> { - self.bundle_pool.ops.get_transactions(slot) - } - - fn notify_payload_attributes_event( - &self, - payload_attributes: PayloadAttributesEvent, - gas_limit: Option, - ) -> Result<(), Self::Error> { - self.bundle_pool - .ops - .notify_payload_attributes_event(payload_attributes, gas_limit) - } -} - -// Finally, now that [`BundleSupportedPool`] has both [`TransactionPool`] and -// [`BundlePoolOperations`] implemented, it can implement [`TransactionPoolBundleExt`]. -impl TransactionPoolBundleExt for BundleSupportedPool -where - V: TransactionValidator, - T: TransactionOrdering::Transaction>, - S: BlobStore, - B: BundlePoolOperations, -{ -} - -/// [`TransactionPool`] often requires implementing the block info extension. -impl TransactionPoolBlockInfoExt for BundleSupportedPool -where - V: TransactionValidator, - T: TransactionOrdering::Transaction>, - S: BlobStore, - B: BundlePoolOperations, -{ - fn set_block_info(&self, info: BlockInfo) { - self.tx_pool.set_block_info(info) - } - - fn on_canonical_state_change(&self, update: CanonicalStateUpdate<'_>) { - self.tx_pool.on_canonical_state_change(update); - } - - fn update_accounts(&self, accounts: Vec) { - self.tx_pool.update_accounts(accounts); - } - - fn delete_blob(&self, tx: TxHash) { - self.tx_pool.delete_blob(tx) - } - - fn delete_blobs(&self, txs: Vec) { - self.tx_pool.delete_blobs(txs) - } - - fn cleanup_blobs(&self) { - self.tx_pool.cleanup_blobs() - } -} diff --git a/crates/builder/transaction-pool-bundle-ext/src/lib.rs b/crates/builder/transaction-pool-bundle-ext/src/lib.rs deleted file mode 100644 index 57ff22c1..00000000 --- a/crates/builder/transaction-pool-bundle-ext/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Crate facilitating simple extension of the reth `TransationPool` with -//! arbitrary bundle support. -//! -//! Contains -//! - A reth `TransactionPool` trait extension ([`TransactionPoolBundleExt`]) -//! allowing bundle support to be added into reth nodes. -//! -//! - A [`TransactionPoolBundleExt`] implementation [`BundleSupportedPool`], -//! which encapsulates the reth `TransactionPool` and a generic implementation -//! of the [`BundlePoolOperations`] trait. -//! -//! ## Usage -//! -//! 1. When implementing `PoolBuilder` on your node, pass a custom `type Pool ...` -//! that is a [`BundleSupportedPool`] type. Your [`BundleSupportedPool`] type -//! must specify your chosen [`BundlePoolOperations`] implementation, which you -//! can initialise inside `fn build_pool` when you build the pool. -//! 2. Whereever you require access to bundles, e.g. when implementing RPCs or -//! `PayloadServiceBuilder` on your node, modify the `Pool` trait bound -//! replacing `TransactionPool` with [`TransactionPoolBundleExt`]. This allows access to -//! [`BundlePoolOperations`] methods almost everywhere in the node. - -#![cfg_attr(not(test), warn(unused_crate_dependencies))] - -mod bundle_supported_pool; -mod traits; -pub use bundle_supported_pool::BundleSupportedPool; -pub use traits::{BundlePoolOperations, TransactionPoolBundleExt}; diff --git a/crates/builder/transaction-pool-bundle-ext/src/traits.rs b/crates/builder/transaction-pool-bundle-ext/src/traits.rs deleted file mode 100644 index 1a68bc30..00000000 --- a/crates/builder/transaction-pool-bundle-ext/src/traits.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! [`TransactionPoolBundleExt`] implementation generic over any bundle and network type. - -use alloy_primitives::U256; -use alloy_rpc_types_beacon::events::PayloadAttributesEvent; -use reth_transaction_pool::TransactionPool; -use std::{fmt::Debug, future::Future}; - -/// Bundle-related operations. -/// -/// This API is under active development. -pub trait BundlePoolOperations: Sync + Send { - /// Bundle type - type Bundle: Send; - - /// Cancel bundle request type - type CancelBundleReq; - - /// Error type - type Error: Debug; - - /// Transactions type - type Transaction: Debug; - - /// Add a bundle to the pool, returning an Error if invalid. - fn add_bundle( - &self, - bundle: Self::Bundle, - ) -> impl Future> + Send; - - /// Make a best-effort attempt to cancel a bundle - fn cancel_bundle( - &self, - hash: Self::CancelBundleReq, - ) -> impl Future> + Send; - - /// Get transactions to be included in the head of the next block - fn get_transactions( - &self, - slot: U256, - ) -> Result, Self::Error>; - - /// Notify new payload attributes to use - fn notify_payload_attributes_event( - &self, - payload_attributes: PayloadAttributesEvent, - gas_limit: Option, - ) -> Result<(), Self::Error>; -} - -/// Extension for [TransactionPool] trait adding support for [BundlePoolOperations]. -pub trait TransactionPoolBundleExt: TransactionPool + BundlePoolOperations {} From cd6c9c92f49dc47a886d1bf2c893582d0bef5188 Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 23 Jan 2025 06:45:15 +1100 Subject: [PATCH 014/262] add builder tx to new op-rbuilder (#361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --------- Co-authored-by: Ferran Borreguero --- crates/builder/op-rbuilder/Cargo.toml | 5 +- crates/builder/op-rbuilder/src/args.rs | 20 + crates/builder/op-rbuilder/src/main.rs | 35 +- .../src/payload_builder_vanilla.rs | 1018 ++++++++++++++++- crates/builder/op-rbuilder/src/tx_signer.rs | 106 ++ 5 files changed, 1157 insertions(+), 27 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/args.rs create mode 100644 crates/builder/op-rbuilder/src/tx_signer.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index d7e4743b..38d35906 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -52,12 +52,15 @@ futures-util = "0.3.31" eyre.workspace = true alloy-provider.workspace = true tower = "0.4" - +serde_with.workspace = true +serde.workspace = true +secp256k1.workspace = true tokio.workspace = true jsonrpsee = { workspace = true } async-trait = { workspace = true } clap_builder = { workspace = true } clap.workspace = true +derive_more.workspace = true [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs new file mode 100644 index 00000000..dd465c1c --- /dev/null +++ b/crates/builder/op-rbuilder/src/args.rs @@ -0,0 +1,20 @@ +//! Additional Node command arguments. +//! +//! Copied from OptimismNode to allow easy extension. + +//! clap [Args](clap::Args) for optimism rollup configuration +use reth_optimism_node::args::RollupArgs; + +use crate::tx_signer::Signer; + +/// Parameters for rollup configuration +#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +#[command(next_help_heading = "Rollup")] +pub struct OpRbuilderArgs { + /// Rollup configuration + #[command(flatten)] + pub rollup_args: RollupArgs, + /// Builder secret key for signing last transaction in block + #[arg(long = "rollup.builder-secret-key", env = "BUILDER_SECRET_KEY")] + pub builder_signer: Option, +} diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index ee9b04d9..e20f7e89 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use generator::EmptyBlockPayloadJobGenerator; use payload_builder::OpPayloadBuilder as FBPayloadBuilder; -use payload_builder_vanilla::VanillaOpPayloadBuilder; +use payload_builder_vanilla::OpPayloadBuilderVanilla; use reth::builder::Node; use reth::{ builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}, @@ -19,20 +19,32 @@ use reth_node_api::TxTy; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; use reth_optimism_evm::OpEvmConfig; -use reth_optimism_node::args::RollupArgs; use reth_optimism_node::OpEngineTypes; use reth_optimism_node::OpNode; -use reth_optimism_primitives::OpPrimitives; use reth_payload_builder::PayloadBuilderService; +use tx_signer::Signer; + +/// CLI argument parsing. +pub mod args; + +use reth_optimism_primitives::OpPrimitives; use reth_transaction_pool::PoolTransaction; pub mod generator; pub mod payload_builder; mod payload_builder_vanilla; - +mod tx_signer; #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] -pub struct CustomPayloadBuilder; +pub struct CustomPayloadBuilder { + builder_secret_key: Option, +} + +impl CustomPayloadBuilder { + pub fn new(builder_secret_key: Option) -> Self { + Self { builder_secret_key } + } +} impl PayloadServiceBuilder for CustomPayloadBuilder where @@ -54,7 +66,10 @@ where ) -> eyre::Result::Engine>> { tracing::info!("Spawning a custom payload builder"); let _fb_builder = FBPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())); - let vanilla_builder = VanillaOpPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())); + let vanilla_builder = OpPayloadBuilderVanilla::new( + OpEvmConfig::new(ctx.chain_spec()), + self.builder_secret_key, + ); let payload_job_config = BasicPayloadJobGeneratorConfig::default(); let payload_generator = EmptyBlockPayloadJobGenerator::with_builder( @@ -77,8 +92,10 @@ where } fn main() { - Cli::::parse() - .run(|builder, rollup_args| async move { + Cli::::parse() + .run(|builder, builder_args| async move { + let rollup_args = builder_args.rollup_args; + let engine_tree_config = TreeConfig::default() .with_persistence_threshold(rollup_args.persistence_threshold) .with_memory_block_buffer_target(rollup_args.memory_block_buffer_target); @@ -89,7 +106,7 @@ fn main() { .with_components( op_node .components() - .payload(CustomPayloadBuilder::default()), + .payload(CustomPayloadBuilder::new(builder_args.builder_signer)), ) .with_add_ons(op_node.add_ons()) .launch_with_fn(|builder| { diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index c64878c5..aa2475b3 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1,31 +1,83 @@ -use crate::generator::{BlockCell, PayloadBuilder}; -use alloy_consensus::Header; -use reth_basic_payload_builder::PayloadBuilder as RethPayloadBuilder; // Used to import the trait +use alloy_rpc_types_eth::Withdrawals; +use reth_transaction_pool::PoolTransaction; +use std::{fmt::Display, sync::Arc}; + +use crate::{ + generator::{BlockCell, PayloadBuilder}, + tx_signer::Signer, +}; +use alloy_consensus::{ + Eip658Value, Header, Transaction, TxEip1559, Typed2718, EMPTY_OMMER_ROOT_HASH, +}; +use alloy_eips::merge::BEACON_NONCE; +use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; +use alloy_rpc_types_engine::PayloadId; use reth_basic_payload_builder::*; -use reth_chainspec::ChainSpecProvider; -use reth_evm::ConfigureEvm; +use reth_chain_state::ExecutedBlock; +use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; +use reth_evm::{env::EvmEnv, system_calls::SystemCaller, ConfigureEvm, NextBlockEnvAttributes}; +use reth_execution_types::ExecutionOutcome; use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; -use reth_optimism_primitives::OpTransactionSigned; +use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; +use reth_optimism_forks::OpHardforks; +use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; use reth_payload_builder_primitives::PayloadBuilderError; -use reth_provider::StateProviderFactory; -use reth_transaction_pool::{PoolTransaction, TransactionPool}; +use reth_payload_primitives::PayloadBuilderAttributes; +use reth_payload_util::PayloadTransactions; +use reth_primitives::{ + proofs, transaction::SignedTransactionIntoRecoveredExt, Block, BlockBody, BlockExt, + SealedHeader, TxType, +}; +use reth_provider::{ + HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, +}; +use reth_revm::database::StateProviderDatabase; +use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; +use revm::{ + db::{states::bundle_state::BundleRetention, State}, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, + ResultAndState, TxEnv, + }, + Database, DatabaseCommit, +}; +use tracing::{debug, trace, warn}; + +use op_alloy_consensus::{OpDepositReceipt, OpTxType, OpTypedTransaction}; +use reth_optimism_payload_builder::{ + error::OpPayloadBuilderError, + payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, +}; +use reth_transaction_pool::pool::BestPayloadTransactions; -#[derive(Clone)] -pub struct VanillaOpPayloadBuilder { - inner: reth_optimism_payload_builder::OpPayloadBuilder, +/// Optimism's payload builder +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OpPayloadBuilderVanilla { + /// The rollup's compute pending block configuration option. + // TODO(clabby): Implement this feature. + pub compute_pending_block: bool, + /// The type responsible for creating the evm. + pub evm_config: EvmConfig, + /// The builder's signer key to use for an end of block tx + pub builder_signer: Option, + /// The type responsible for yielding the best transactions for the payload if mempool + /// transactions are allowed. + pub best_transactions: Txs, } -impl VanillaOpPayloadBuilder { +impl OpPayloadBuilderVanilla { /// `OpPayloadBuilder` constructor. - pub fn new(evm_config: EvmConfig) -> Self { + pub const fn new(evm_config: EvmConfig, builder_signer: Option) -> Self { Self { - inner: reth_optimism_payload_builder::OpPayloadBuilder::new(evm_config), + compute_pending_block: true, + evm_config, + builder_signer, + best_transactions: (), } } } -impl PayloadBuilder for VanillaOpPayloadBuilder +impl PayloadBuilder for OpPayloadBuilderVanilla where Client: StateProviderFactory + ChainSpecProvider, Pool: TransactionPool>, @@ -39,7 +91,9 @@ where args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { - match self.inner.try_build(args)? { + let pool = args.pool.clone(); + + match self.build_payload(args, |attrs| ().best_transactions(pool, attrs))? { BuildOutcome::Better { payload, .. } => { best_payload.set(payload); Ok(()) @@ -59,3 +113,933 @@ where } } } + +impl OpPayloadBuilderVanilla +where + EvmConfig: ConfigureEvm
, +{ + /// 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, Client, Pool, Txs>( + &self, + args: BuildArguments, + best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, + ) -> Result, PayloadBuilderError> + where + Client: StateProviderFactory + ChainSpecProvider, + Pool: TransactionPool, + Txs: PayloadTransactions, + { + let evm_env = self + .cfg_and_block_env(&args.config.attributes, &args.config.parent_header) + .map_err(PayloadBuilderError::other)?; + let EvmEnv { + cfg_env_with_handler_cfg, + block_env, + } = evm_env; + + let BuildArguments { + client, + pool: _, + mut cached_reads, + config, + cancel, + best_payload, + } = args; + + let ctx = OpPayloadBuilderCtx { + evm_config: self.evm_config.clone(), + chain_spec: client.chain_spec(), + config, + initialized_cfg: cfg_env_with_handler_cfg, + initialized_block_env: block_env, + cancel, + best_payload, + builder_signer: self.builder_signer, + }; + + let builder = OpBuilder::new(best); + + let state_provider = client.state_by_block_hash(ctx.parent().hash())?; + let state = StateProviderDatabase::new(state_provider); + + if ctx.attributes().no_tx_pool { + let db = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + builder.build(db, ctx) + } else { + // sequencer mode we can reuse cachedreads from previous runs + let db = State::builder() + .with_database(cached_reads.as_db_mut(state)) + .with_bundle_update() + .build(); + builder.build(db, ctx) + } + .map(|out| out.with_cached_reads(cached_reads)) + } +} + +impl OpPayloadBuilderVanilla +where + EvmConfig: ConfigureEvm
, +{ + /// Returns the configured [`EvmEnv`] for the targeted payload + /// (that has the `parent` as its parent). + pub fn cfg_and_block_env( + &self, + attributes: &OpPayloadBuilderAttributes, + parent: &Header, + ) -> Result { + let next_attributes = NextBlockEnvAttributes { + timestamp: attributes.timestamp(), + suggested_fee_recipient: attributes.suggested_fee_recipient(), + prev_randao: attributes.prev_randao(), + gas_limit: attributes.gas_limit.unwrap_or(parent.gas_limit), + }; + self.evm_config + .next_cfg_and_block_env(parent, next_attributes) + } +} + +/// 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 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), + } + } +} + +impl OpBuilder<'_, Txs> +where + Txs: PayloadTransactions, +{ + /// Executes the payload and returns the outcome. + pub fn execute( + self, + state: &mut State, + ctx: &OpPayloadBuilderCtx, + ) -> Result, PayloadBuilderError> + where + EvmConfig: ConfigureEvm
, + DB: Database, + { + let Self { best } = self; + debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); + + // 1. apply eip-4788 pre block contract call + ctx.apply_pre_beacon_root_contract_call(state)?; + + // 2. ensure create2deployer is force deployed + ctx.ensure_create2_deployer(state)?; + + // 3. execute sequencer transactions + let mut info = ctx.execute_sequencer_transactions(state)?; + + // 4. if mem pool transactions are requested we execute them + + // gas reserved for builder tx + let message = format!("Block Number: {}", ctx.block_number()) + .as_bytes() + .to_vec(); + let builder_tx_gas = ctx.builder_signer().map_or(0, |_| { + OpPayloadBuilderCtx::::estimate_gas_for_builder_tx(message.clone()) + }); + let block_gas_limit = ctx.block_gas_limit() - builder_tx_gas; + if !ctx.attributes().no_tx_pool { + let best_txs = best(ctx.best_transaction_attributes()); + if ctx + .execute_best_transactions(&mut info, state, best_txs, block_gas_limit)? + .is_some() + { + return Ok(BuildOutcomeKind::Cancelled); + } + + // check if the new payload is even more valuable + if !ctx.is_better_payload(info.total_fees) { + // can skip building the block + return Ok(BuildOutcomeKind::Aborted { + fees: info.total_fees, + }); + } + } + + // Add builder tx to the block + ctx.add_builder_tx(&mut info, state, builder_tx_gas, message); + + let withdrawals_root = ctx.commit_withdrawals(state)?; + + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + state.merge_transitions(BundleRetention::Reverts); + + Ok(BuildOutcomeKind::Better { + payload: ExecutedPayload { + info, + withdrawals_root, + }, + }) + } + + /// Builds the payload on top of the state. + pub fn build( + self, + mut state: State, + ctx: OpPayloadBuilderCtx, + ) -> Result, PayloadBuilderError> + where + EvmConfig: ConfigureEvm
, + DB: Database + AsRef

, + P: StateRootProvider + HashedPostStateProvider, + { + let ExecutedPayload { + info, + withdrawals_root, + } = match self.execute(&mut state, &ctx)? { + 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(); + let execution_outcome = ExecutionOutcome::new( + state.take_bundle(), + info.receipts.into(), + 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_provider = state.database.as_ref(); + let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); + let (state_root, trie_output) = { + state + .database + .as_ref() + .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" + ); + })? + }; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); + + // 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(); + let extra_data = ctx.extra_data()?; + + let header = Header { + parent_hash: ctx.parent().hash(), + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: ctx.initialized_block_env.coinbase, + 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: None, + }; + + // seal the block + let block = Block { + header, + body: BlockBody { + transactions: info.executed_transactions, + ommers: vec![], + withdrawals: ctx.withdrawals().cloned(), + }, + }; + + let sealed_block = Arc::new(block.seal_slow()); + debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header, "sealed built block"); + + // create the executed block data + let executed: ExecutedBlock = ExecutedBlock { + block: sealed_block.clone(), + senders: Arc::new(info.executed_senders), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + trie: 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, + ctx.chain_spec.clone(), + ctx.config.attributes, + Some(executed), + ); + + 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 }) + } + } +} + +/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. +pub 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< + Pool: TransactionPool>, + >( + &self, + pool: Pool, + attr: BestTransactionsAttributes, + ) -> impl PayloadTransactions; +} + +impl OpPayloadTransactions for () { + fn best_transactions< + Pool: TransactionPool>, + >( + &self, + pool: Pool, + attr: BestTransactionsAttributes, + ) -> impl PayloadTransactions { + BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) + } +} + +/// Holds the state after execution +#[derive(Debug)] +pub struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, + /// Withdrawal hash. + pub withdrawals_root: Option, +} + +/// This acts as the container for executed transactions and its byproducts (receipts, gas used) +#[derive(Default, Debug)] +pub struct ExecutionInfo { + /// All executed transactions (unrecovered). + pub executed_transactions: Vec, + /// The recovered senders for the executed transactions. + pub executed_senders: Vec

, + /// The transaction receipts + pub receipts: Vec, + /// All gas used so far + pub cumulative_gas_used: u64, + /// Tracks fees from executed mempool transactions + pub total_fees: U256, +} + +impl ExecutionInfo { + /// Create a new instance with allocated slots. + pub fn with_capacity(capacity: usize) -> Self { + Self { + executed_transactions: Vec::with_capacity(capacity), + executed_senders: Vec::with_capacity(capacity), + receipts: Vec::with_capacity(capacity), + cumulative_gas_used: 0, + total_fees: U256::ZERO, + } + } +} + +/// Container type that holds all necessities to build a new payload. +#[derive(Debug)] +pub struct OpPayloadBuilderCtx { + /// The type that knows how to perform system calls and configure the evm. + pub evm_config: EvmConfig, + /// The chainspec + pub chain_spec: Arc, + /// How to build the payload. + pub config: PayloadConfig, + /// Evm Settings + pub initialized_cfg: CfgEnvWithHandlerCfg, + /// Block config + pub initialized_block_env: BlockEnv, + /// Marker to check whether the job has been cancelled. + pub cancel: Cancelled, + /// The currently best payload. + pub best_payload: Option, + /// The builder signer + pub builder_signer: Option, +} + +impl OpPayloadBuilderCtx { + /// Returns the parent block the payload will be build on. + pub fn parent(&self) -> &SealedHeader { + &self.config.parent_header + } + + /// Returns the builder attributes. + pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { + &self.config.attributes + } + + /// Returns the withdrawals if shanghai is active. + pub fn withdrawals(&self) -> Option<&Withdrawals> { + self.chain_spec + .is_shanghai_active_at_timestamp(self.attributes().timestamp()) + .then(|| &self.attributes().payload_attributes.withdrawals) + } + + /// Returns the block gas limit to target. + pub fn block_gas_limit(&self) -> u64 { + self.attributes() + .gas_limit + .unwrap_or_else(|| self.initialized_block_env.gas_limit.saturating_to()) + } + + /// Returns the block number for the block. + pub fn block_number(&self) -> u64 { + self.initialized_block_env.number.to() + } + + /// Returns the current base fee + pub fn base_fee(&self) -> u64 { + self.initialized_block_env.basefee.to() + } + + /// Returns the current blob gas price. + pub fn get_blob_gasprice(&self) -> Option { + self.initialized_block_env + .get_blob_gasprice() + .map(|gasprice| gasprice as u64) + } + + /// Returns the blob fields for the header. + /// + /// This will always return `Some(0)` after ecotone. + pub fn blob_fields(&self) -> (Option, Option) { + // 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. + if self.is_ecotone_active() { + (Some(0), Some(0)) + } else { + (None, None) + } + } + + /// Returns the extra data for the block. + /// + /// After holocene this extracts the extradata from the paylpad + pub fn extra_data(&self) -> Result { + if self.is_holocene_active() { + self.attributes() + .get_holocene_extra_data( + self.chain_spec.base_fee_params_at_timestamp( + self.attributes().payload_attributes.timestamp, + ), + ) + .map_err(PayloadBuilderError::other) + } else { + Ok(Default::default()) + } + } + + /// Returns the current fee settings for transactions from the mempool + pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { + BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) + } + + /// Returns the unique id for this payload job. + pub fn payload_id(&self) -> PayloadId { + self.attributes().payload_id() + } + + /// Returns true if regolith is active for the payload. + pub fn is_regolith_active(&self) -> bool { + self.chain_spec + .is_regolith_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if ecotone is active for the payload. + pub fn is_ecotone_active(&self) -> bool { + self.chain_spec + .is_ecotone_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if canyon is active for the payload. + pub fn is_canyon_active(&self) -> bool { + self.chain_spec + .is_canyon_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if holocene is active for the payload. + pub fn is_holocene_active(&self) -> bool { + self.chain_spec + .is_holocene_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns the chain id + pub fn chain_id(&self) -> u64 { + self.chain_spec.chain.id() + } + + /// Returns the builder signer + pub fn builder_signer(&self) -> Option { + self.builder_signer + } + + /// Returns true if the fees are higher than the previous payload. + pub fn is_better_payload(&self, total_fees: U256) -> bool { + is_better_payload(self.best_payload.as_ref(), total_fees) + } + + /// Commits the withdrawals from the payload attributes to the state. + pub fn commit_withdrawals(&self, db: &mut State) -> Result, ProviderError> + where + DB: Database, + { + commit_withdrawals( + db, + &self.chain_spec, + self.attributes().payload_attributes.timestamp, + &self.attributes().payload_attributes.withdrawals, + ) + } + + /// Ensure that the create2deployer is force-deployed at the canyon transition. Optimism + /// blocks will always have at least a single transaction in them (the L1 info transaction), + /// so we can safely assume that this will always be triggered upon the transition and that + /// the above check for empty blocks will never be hit on OP chains. + pub fn ensure_create2_deployer(&self, db: &mut State) -> Result<(), PayloadBuilderError> + where + DB: Database, + DB::Error: Display, + { + reth_optimism_evm::ensure_create2_deployer( + self.chain_spec.clone(), + self.attributes().payload_attributes.timestamp, + db, + ) + .map_err(|err| { + warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); + PayloadBuilderError::other(OpPayloadBuilderError::ForceCreate2DeployerFail) + }) + } +} + +impl OpPayloadBuilderCtx +where + EvmConfig: ConfigureEvm
, +{ + /// apply eip-4788 pre block contract call + pub fn apply_pre_beacon_root_contract_call( + &self, + db: &mut DB, + ) -> Result<(), PayloadBuilderError> + where + DB: Database + DatabaseCommit, + DB::Error: Display, + { + SystemCaller::new(self.evm_config.clone(), self.chain_spec.clone()) + .pre_block_beacon_root_contract_call( + db, + &self.initialized_cfg, + &self.initialized_block_env, + self.attributes() + .payload_attributes + .parent_beacon_block_root, + ) + .map_err(|err| { + warn!(target: "payload_builder", + parent_header=%self.parent().hash(), + %err, + "failed to apply beacon root contract call for payload" + ); + PayloadBuilderError::Internal(err.into()) + })?; + + Ok(()) + } + + /// Executes all sequencer transactions that are included in the payload attributes. + pub fn execute_sequencer_transactions( + &self, + db: &mut State, + ) -> Result + where + DB: Database, + { + let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), + TxEnv::default(), + ); + let mut evm = self.evm_config.evm_with_env(&mut *db, env); + + for sequencer_tx in &self.attributes().transactions { + // A sequencer's block should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + OpPayloadBuilderError::BlobTransactionRejected, + )); + } + + // Convert the transaction to a [TransactionSignedEcRecovered]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx + .value() + .clone() + .try_into_ecrecovered() + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + // Cache the depositor account prior to the state transition for the deposit nonce. + // + // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces + // were not introduced in Bedrock. In addition, regular transactions don't have deposit + // nonces, so we don't need to touch the DB for those. + let depositor = (self.is_regolith_active() && sequencer_tx.is_deposit()) + .then(|| { + evm.db_mut() + .load_cache_account(sequencer_tx.signer()) + .map(|acc| acc.account_info().unwrap_or_default()) + }) + .transpose() + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( + sequencer_tx.signer(), + )) + })?; + + *evm.tx_mut() = self + .evm_config + .tx_env(sequencer_tx.tx(), sequencer_tx.signer()); + + let ResultAndState { result, state } = match evm.transact() { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; + } + err => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(err)); + } + } + } + }; + + // commit changes + evm.db_mut().commit(state); + + let gas_used = result.gas_used(); + + // add gas used by the transaction to cumulative gas used, before creating the receipt + info.cumulative_gas_used += gas_used; + + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(result.is_success()), + cumulative_gas_used: info.cumulative_gas_used, + logs: result.into_logs().into_iter().collect(), + }; + + // Push transaction changeset and calculate header bloom filter for receipt. + info.receipts.push(match sequencer_tx.tx_type() { + OpTxType::Legacy => OpReceipt::Legacy(receipt), + OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), + OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), + OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), + OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { + inner: receipt, + deposit_nonce: depositor.map(|account| account.nonce), + // The deposit receipt version was introduced in Canyon to indicate an update to + // how receipt hashes should be computed when set. The state + // transition process ensures this is only set for + // post-Canyon deposit transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + }), + }); + + // append sender and transaction to the respective lists + info.executed_senders.push(sequencer_tx.signer()); + info.executed_transactions.push(sequencer_tx.into_tx()); + } + + Ok(info) + } + + /// Executes the given best transactions and updates the execution info. + /// + /// Returns `Ok(Some(())` if the job was cancelled. + pub fn execute_best_transactions( + &self, + info: &mut ExecutionInfo, + db: &mut State, + mut best_txs: impl PayloadTransactions, + block_gas_limit: u64, + ) -> Result, PayloadBuilderError> + where + DB: Database, + { + let base_fee = self.base_fee(); + + let env = EnvWithHandlerCfg::new_with_cfg_env( + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), + TxEnv::default(), + ); + let mut evm = self.evm_config.evm_with_env(&mut *db, env); + + while let Some(tx) = best_txs.next(()) { + // ensure we still have capacity for this transaction + if info.cumulative_gas_used + tx.gas_limit() > block_gas_limit { + // we can't fit this transaction into the block, so we need to mark it as + // invalid which also removes all dependent transaction from + // the iterator before we can continue + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // A sequencer's block should never contain blob or deposit transactions from the pool. + if tx.is_eip4844() || tx.tx_type() == TxType::Deposit as u8 { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // check if the job was cancelled, if so we can exit early + if self.cancel.is_cancelled() { + return Ok(Some(())); + } + + // Configure the environment for the tx. + *evm.tx_mut() = self.evm_config.tx_env(tx.tx(), tx.signer()); + + let ResultAndState { result, state } = match evm.transact() { + Ok(res) => res, + Err(err) => { + match err { + EVMError::Transaction(err) => { + if matches!(err, InvalidTransaction::NonceTooLow { .. }) { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + } + + continue; + } + err => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(err)); + } + } + } + }; + + // commit changes + evm.db_mut().commit(state); + + let gas_used = result.gas_used(); + + // add gas used by the transaction to cumulative gas used, before creating the + // receipt + info.cumulative_gas_used += gas_used; + + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(result.is_success()), + cumulative_gas_used: info.cumulative_gas_used, + logs: result.into_logs().into_iter().collect(), + }; + + // Push transaction changeset and calculate header bloom filter for receipt. + info.receipts.push(match tx.tx_type() { + OpTxType::Legacy => OpReceipt::Legacy(receipt), + OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), + OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), + OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), + OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { + inner: receipt, + deposit_nonce: None, + deposit_receipt_version: None, + }), + }); + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + + // append sender and transaction to the respective lists + info.executed_senders.push(tx.signer()); + info.executed_transactions.push(tx.into_tx()); + } + + Ok(None) + } + + pub fn add_builder_tx( + &self, + info: &mut ExecutionInfo, + db: &mut State, + builder_tx_gas: u64, + message: Vec, + ) -> Option<()> + where + DB: Database, + { + self.builder_signer() + .map(|signer| { + let base_fee = self.base_fee(); + // Create message with block number for the builder to sign + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( + signer.address, + )) + })?; + + // Create the EIP-1559 transaction + let eip1559 = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: self.chain_id(), + nonce, + gas_limit: builder_tx_gas, + max_fee_per_gas: 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() + }); + let tx = eip1559; + + // Sign the transaction + let builder_tx = signer.sign_tx(tx).map_err(PayloadBuilderError::other)?; + + let env = EnvWithHandlerCfg::new_with_cfg_env( + self.initialized_cfg.clone(), + self.initialized_block_env.clone(), + TxEnv::default(), + ); + let mut evm = self.evm_config.evm_with_env(&mut *db, env); + *evm.tx_mut() = self.evm_config.tx_env(builder_tx.tx(), builder_tx.signer()); + + let ResultAndState { result, state } = evm + .transact() + .map_err(PayloadBuilderError::EvmExecutionError)?; + + // Release the db reference by dropping evm + drop(evm); + // Commit changes + db.commit(state); + + let gas_used = result.gas_used(); + + // Add gas used by the transaction to cumulative gas used, before creating the receipt + info.cumulative_gas_used += gas_used; + + let receipt = alloy_consensus::Receipt { + status: Eip658Value::Eip658(result.is_success()), + cumulative_gas_used: info.cumulative_gas_used, + logs: result.into_logs().into_iter().collect(), + }; + + // Push transaction changeset and calculate header bloom filter for receipt + info.receipts.push(OpReceipt::Eip1559(receipt)); + + // Append sender and transaction to the respective lists + info.executed_senders.push(builder_tx.signer()); + info.executed_transactions.push(builder_tx.into_tx()); + Ok(()) + }) + .transpose() + .unwrap_or_else(|err: PayloadBuilderError| { + warn!(target: "payload_builder", %err, "Failed to add builder transaction"); + None + }) + } + + fn estimate_gas_for_builder_tx(input: Vec) -> 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; + + zero_cost + nonzero_cost + 21_000 + } +} diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs new file mode 100644 index 00000000..bad8d65d --- /dev/null +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -0,0 +1,106 @@ +use std::str::FromStr; + +use alloy_consensus::SignableTransaction; +use alloy_primitives::{Address, PrimitiveSignature as Signature, B256, U256}; +use op_alloy_consensus::OpTypedTransaction; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::{public_key_to_address, TransactionSignedEcRecovered}; +use secp256k1::{Message, SecretKey, SECP256K1}; + +/// Simple struct to sign txs/messages. +/// Mainly used to sign payout txs from the builder and to create test data. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Signer { + pub address: Address, + pub secret: SecretKey, +} + +impl Signer { + pub fn try_from_secret(secret: B256) -> Result { + let secret = SecretKey::from_slice(secret.as_ref())?; + let pubkey = secret.public_key(SECP256K1); + let address = public_key_to_address(pubkey); + + Ok(Self { address, secret }) + } + + pub fn sign_message(&self, message: B256) -> Result { + let s = SECP256K1 + .sign_ecdsa_recoverable(&Message::from_digest_slice(&message[..])?, &self.secret); + let (rec_id, data) = s.serialize_compact(); + + let signature = Signature::new( + U256::try_from_be_slice(&data[..32]).expect("The slice has at most 32 bytes"), + U256::try_from_be_slice(&data[32..64]).expect("The slice has at most 32 bytes"), + rec_id.to_i32() != 0, + ); + Ok(signature) + } + + pub fn sign_tx( + &self, + tx: OpTypedTransaction, + ) -> Result, secp256k1::Error> { + let signature_hash = match &tx { + OpTypedTransaction::Legacy(tx) => tx.signature_hash(), + OpTypedTransaction::Eip2930(tx) => tx.signature_hash(), + OpTypedTransaction::Eip1559(tx) => tx.signature_hash(), + OpTypedTransaction::Eip7702(tx) => tx.signature_hash(), + OpTypedTransaction::Deposit(_) => B256::ZERO, + }; + let signature = self.sign_message(signature_hash)?; + let signed = OpTransactionSigned::new_unhashed(tx, signature); + Ok( + TransactionSignedEcRecovered::::new_unchecked( + signed, + self.address, + ), + ) + } + + pub fn random() -> Self { + Self::try_from_secret(B256::random()).expect("failed to create random signer") + } +} + +impl FromStr for Signer { + type Err = eyre::Error; + + fn from_str(s: &str) -> Result { + Self::try_from_secret(B256::from_str(s)?) + .map_err(|e| eyre::eyre!("invalid secret key {:?}", e.to_string())) + } +} + +#[cfg(test)] +mod test { + use super::*; + use alloy_consensus::TxEip1559; + use alloy_primitives::{address, fixed_bytes, TxKind as TransactionKind}; + use reth::core::primitives::SignedTransaction; + #[test] + fn test_sign_transaction() { + let secret = + fixed_bytes!("7a3233fcd52c19f9ffce062fd620a8888930b086fba48cfea8fc14aac98a4dce"); + let address = address!("B2B9609c200CA9b7708c2a130b911dabf8B49B20"); + let signer = Signer::try_from_secret(secret).expect("signer creation"); + assert_eq!(signer.address, address); + + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: 1, + nonce: 2, + gas_limit: 21000, + max_fee_per_gas: 1000, + max_priority_fee_per_gas: 20000, + to: TransactionKind::Call(address), + value: U256::from(3000u128), + ..Default::default() + }); + + let signed_tx = signer.sign_tx(tx).expect("sign tx"); + assert_eq!(signed_tx.signer(), address); + + let signed = signed_tx.into_tx(); + assert_eq!(signed.recover_signer(), Some(address)); + } +} From 435b36fc0ff56effe0c6a9d6900ca591a9df122a Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 23 Jan 2025 07:46:15 +1100 Subject: [PATCH 015/262] Add monitoring exex for op-rbuilder (#365) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Adds execution extension for op-rbuilder for monitoring builder transactions in the block. This ingests the committed chain and emits metrics to see if the builder transaction has landed a block or not with the op-rbuilder. ## 💡 Motivation and Context Used for observability and monitoring blocks built by the op-rbuilder on optimism. --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 3 + crates/builder/op-rbuilder/src/main.rs | 7 + crates/builder/op-rbuilder/src/metrics.rs | 25 ++++ crates/builder/op-rbuilder/src/monitoring.rs | 129 +++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 crates/builder/op-rbuilder/src/metrics.rs create mode 100644 crates/builder/op-rbuilder/src/monitoring.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 38d35906..c43f1610 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -15,6 +15,7 @@ reth-optimism-primitives.workspace = true reth-cli-util.workspace = true reth-payload-primitives.workspace = true reth-evm.workspace = true +reth-exex.workspace = true reth-chainspec.workspace = true reth-primitives.workspace = true reth-node-api.workspace = true @@ -24,6 +25,7 @@ reth-node-ethereum.workspace = true reth-chain-state.workspace = true reth-execution-types.workspace = true reth-ethereum-payload-builder.workspace = true +reth-metrics.workspace = true reth-provider.workspace = true reth-revm.workspace = true reth-trie.workspace = true @@ -61,6 +63,7 @@ async-trait = { workspace = true } clap_builder = { workspace = true } clap.workspace = true derive_more.workspace = true +metrics.workspace = true [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index e20f7e89..c50cd767 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,5 +1,6 @@ use clap::Parser; use generator::EmptyBlockPayloadJobGenerator; +use monitoring::Monitoring; use payload_builder::OpPayloadBuilder as FBPayloadBuilder; use payload_builder_vanilla::OpPayloadBuilderVanilla; use reth::builder::Node; @@ -31,6 +32,8 @@ use reth_optimism_primitives::OpPrimitives; use reth_transaction_pool::PoolTransaction; pub mod generator; +mod metrics; +mod monitoring; pub mod payload_builder; mod payload_builder_vanilla; mod tx_signer; @@ -109,6 +112,10 @@ fn main() { .payload(CustomPayloadBuilder::new(builder_args.builder_signer)), ) .with_add_ons(op_node.add_ons()) + .install_exex("monitoring", move |ctx| { + let builder_signer = builder_args.builder_signer; + async move { Ok(Monitoring::new(ctx, builder_signer).start()) } + }) .launch_with_fn(|builder| { let launcher = EngineNodeLauncher::new( builder.task_executor().clone(), diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs new file mode 100644 index 00000000..8bb18702 --- /dev/null +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -0,0 +1,25 @@ +use reth_metrics::{metrics::Gauge, Metrics}; + +/// op-rbuilder metrics +#[derive(Metrics)] +#[metrics(scope = "op_rbuilder")] +pub struct OpRBuilderMetrics { + /// Number of builder built blocks + pub builder_built_blocks: Gauge, + /// Last built block height + pub last_built_block_height: Gauge, +} + +impl OpRBuilderMetrics { + pub fn inc_builder_built_blocks(&self) { + self.builder_built_blocks.increment(1); + } + + pub fn dec_builder_built_blocks(&self) { + self.builder_built_blocks.decrement(1); + } + + pub fn set_last_built_block_height(&self, height: u64) { + self.last_built_block_height.set(height as f64); + } +} diff --git a/crates/builder/op-rbuilder/src/monitoring.rs b/crates/builder/op-rbuilder/src/monitoring.rs new file mode 100644 index 00000000..00451e59 --- /dev/null +++ b/crates/builder/op-rbuilder/src/monitoring.rs @@ -0,0 +1,129 @@ +use alloy_consensus::{Transaction, TxReceipt}; +use futures_util::TryStreamExt; +use reth::core::primitives::SignedTransaction; +use reth_exex::{ExExContext, ExExEvent}; +use reth_node_api::{FullNodeComponents, NodeTypes}; +use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; +use reth_primitives::{Block, SealedBlockWithSenders}; +use reth_provider::Chain; +use tracing::info; + +use crate::{metrics::OpRBuilderMetrics, tx_signer::Signer}; + +const OP_BUILDER_TX_PREFIX: &[u8] = b"Block Number:"; + +pub struct Monitoring { + ctx: ExExContext, + builder_signer: Option, + metrics: OpRBuilderMetrics, +} + +impl Monitoring +where + Node: FullNodeComponents>, +{ + pub fn new(ctx: ExExContext, builder_signer: Option) -> Self { + Self { + ctx, + builder_signer, + metrics: Default::default(), + } + } + + pub async fn start(mut self) -> eyre::Result<()> { + // Process all new chain state notifications + while let Some(notification) = self.ctx.notifications.try_next().await? { + if let Some(reverted_chain) = notification.reverted_chain() { + self.revert(&reverted_chain).await?; + } + if let Some(committed_chain) = notification.committed_chain() { + self.commit(&committed_chain).await?; + self.ctx + .events + .send(ExExEvent::FinishedHeight(committed_chain.tip().num_hash()))?; + } + } + + Ok(()) + } + + /// Process a new chain commit. + /// + /// This function decodes the builder tx and then emits metrics + async fn commit(&mut self, chain: &Chain) -> eyre::Result<()> { + info!("Processing new chain commit"); + let txs = decode_chain_into_builder_txs(chain, self.builder_signer); + + for (block, _) in txs { + self.metrics.inc_builder_built_blocks(); + self.metrics.set_last_built_block_height(block.number); + info!( + block_number = block.number, + "Committed block built by builder" + ); + } + + Ok(()) + } + + /// Process a chain revert. + /// + /// This function decodes all transactions in the block, updates the metrics for builder built blocks + async fn revert(&mut self, chain: &Chain) -> eyre::Result<()> { + info!("Processing new chain revert"); + let mut txs = decode_chain_into_builder_txs(chain, self.builder_signer); + // Reverse the order of txs to start reverting from the tip + txs.reverse(); + + if let Some((block, _)) = txs.last() { + self.metrics.set_last_built_block_height(block.number - 1); + } + + for (block, _) in txs { + self.metrics.dec_builder_built_blocks(); + info!( + block_number = block.number, + "Reverted block built by builder" + ); + } + + Ok(()) + } +} + +/// Decode chain of blocks and filter list to builder txs +fn decode_chain_into_builder_txs( + chain: &Chain, + builder_signer: Option, +) -> Vec<( + SealedBlockWithSenders>, + OpTransactionSigned, +)> { + chain + // Get all blocks and receipts + .blocks_and_receipts() + // Get all receipts + .flat_map(|(block, receipts)| { + let block_clone = block.clone(); + block + .body() + .transactions + .iter() + .zip(receipts.iter().flatten()) + .filter_map(move |(tx, receipt)| { + let is_builder_tx = receipt.status() + && tx.input().starts_with(OP_BUILDER_TX_PREFIX) + && tx.recover_signer().is_some_and(|signer| { + builder_signer.is_some_and(|bs| signer == bs.address) + }); + + if is_builder_tx { + // Clone the entire block and the transaction + Some((block_clone.clone(), tx.clone())) + } else { + None + } + }) + }) + .collect() +} From e62e09b1905491998513a010592e79e45f0c0555 Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 28 Jan 2025 06:20:38 +1100 Subject: [PATCH 016/262] Add op-rbuilder metrics (#378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Adds variety of metrics to op-rbuilder around the block building process. ## 💡 Motivation and Context Adding observability and metrics for op-rbuilder. --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/src/main.rs | 7 ++ crates/builder/op-rbuilder/src/metrics.rs | 58 ++++++++-- crates/builder/op-rbuilder/src/monitoring.rs | 69 ++++++----- .../src/payload_builder_vanilla.rs | 107 ++++++++++++++++-- 4 files changed, 182 insertions(+), 59 deletions(-) diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index c50cd767..fef82803 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -90,6 +90,8 @@ where ctx.task_executor() .spawn_critical("custom payload builder service", Box::pin(payload_service)); + tracing::info!("Custom payload service started"); + Ok(payload_builder) } } @@ -114,6 +116,11 @@ fn main() { .with_add_ons(op_node.add_ons()) .install_exex("monitoring", move |ctx| { let builder_signer = builder_args.builder_signer; + if let Some(signer) = &builder_signer { + tracing::info!("Builder signer address is set to: {:?}", signer.address); + } else { + tracing::info!("Builder signer is not set"); + } async move { Ok(Monitoring::new(ctx, builder_signer).start()) } }) .launch_with_fn(|builder| { diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 8bb18702..5f8784f6 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -1,25 +1,61 @@ -use reth_metrics::{metrics::Gauge, Metrics}; +use reth_metrics::{metrics::Counter, metrics::Gauge, metrics::Histogram, Metrics}; /// op-rbuilder metrics -#[derive(Metrics)] +#[derive(Metrics, Clone)] #[metrics(scope = "op_rbuilder")] pub struct OpRBuilderMetrics { - /// Number of builder built blocks - pub builder_built_blocks: Gauge, + /// Number of builder landed blocks + pub builder_landed_blocks: Gauge, /// Last built block height - pub last_built_block_height: Gauge, + pub last_landed_block_height: Gauge, + /// Number of blocks the builder did not land + pub builder_landed_blocks_missed: Gauge, + /// Block built success + pub block_built_success: Counter, + /// Total duration of building a block + pub total_block_built_duration: Histogram, + /// Duration of fetching transactions from the pool + pub transaction_pool_fetch_duration: Histogram, + /// Duration of state root calculation + pub state_root_calculation_duration: Histogram, + /// Duration of sequencer transaction execution + pub sequencer_tx_duration: Histogram, + /// Duration of state merge transitions + pub state_transition_merge_duration: Histogram, + /// Duration of payload simulation of all transactions + pub payload_tx_simulation_duration: Histogram, + /// Number of transaction considered for inclusion in the block + pub payload_num_tx_considered: Histogram, + /// Payload byte size + pub payload_byte_size: Histogram, + /// Number of transactions in the payload + pub payload_num_tx: Histogram, + /// Number of transactions in the payload that were successfully simulated + pub payload_num_tx_simulated: Histogram, + /// Number of transactions in the payload that were successfully simulated + pub payload_num_tx_simulated_success: Histogram, + /// Number of transactions in the payload that failed simulation + pub payload_num_tx_simulated_fail: Histogram, + /// Duration of tx simulation + pub tx_simulation_duration: Histogram, + /// Byte size of transactions + pub tx_byte_size: Histogram, } impl OpRBuilderMetrics { - pub fn inc_builder_built_blocks(&self) { - self.builder_built_blocks.increment(1); + pub fn inc_builder_landed_blocks(&self) { + self.builder_landed_blocks.increment(1); } - pub fn dec_builder_built_blocks(&self) { - self.builder_built_blocks.decrement(1); + pub fn dec_builder_landed_blocks(&self) { + self.builder_landed_blocks.decrement(1); } - pub fn set_last_built_block_height(&self, height: u64) { - self.last_built_block_height.set(height as f64); + pub fn inc_builder_landed_blocks_missed(&self) { + self.builder_landed_blocks_missed.increment(1); + } + + pub fn set_last_landed_block_height(&self, height: u64) { + self.last_landed_block_height.set(height as f64); } } diff --git a/crates/builder/op-rbuilder/src/monitoring.rs b/crates/builder/op-rbuilder/src/monitoring.rs index 00451e59..cdb8bada 100644 --- a/crates/builder/op-rbuilder/src/monitoring.rs +++ b/crates/builder/op-rbuilder/src/monitoring.rs @@ -31,6 +31,7 @@ where } pub async fn start(mut self) -> eyre::Result<()> { + // TODO: add builder balance monitoring // Process all new chain state notifications while let Some(notification) = self.ctx.notifications.try_next().await? { if let Some(reverted_chain) = notification.reverted_chain() { @@ -52,15 +53,19 @@ where /// This function decodes the builder tx and then emits metrics async fn commit(&mut self, chain: &Chain) -> eyre::Result<()> { info!("Processing new chain commit"); - let txs = decode_chain_into_builder_txs(chain, self.builder_signer); + let blocks = decode_chain_into_builder_txs(chain, self.builder_signer); - for (block, _) in txs { - self.metrics.inc_builder_built_blocks(); - self.metrics.set_last_built_block_height(block.number); - info!( - block_number = block.number, - "Committed block built by builder" - ); + for (block, has_builder_tx) in blocks { + if has_builder_tx { + self.metrics.inc_builder_landed_blocks(); + self.metrics.set_last_landed_block_height(block.number); + info!( + block_number = block.number, + "Committed block built by builder" + ); + } else { + self.metrics.inc_builder_landed_blocks_missed(); + } } Ok(()) @@ -71,20 +76,22 @@ where /// This function decodes all transactions in the block, updates the metrics for builder built blocks async fn revert(&mut self, chain: &Chain) -> eyre::Result<()> { info!("Processing new chain revert"); - let mut txs = decode_chain_into_builder_txs(chain, self.builder_signer); + let mut blocks = decode_chain_into_builder_txs(chain, self.builder_signer); // Reverse the order of txs to start reverting from the tip - txs.reverse(); + blocks.reverse(); - if let Some((block, _)) = txs.last() { - self.metrics.set_last_built_block_height(block.number - 1); + if let Some((block, _)) = blocks.last() { + self.metrics.set_last_landed_block_height(block.number - 1); } - for (block, _) in txs { - self.metrics.dec_builder_built_blocks(); - info!( - block_number = block.number, - "Reverted block built by builder" - ); + for (block, has_builder_tx) in blocks { + if has_builder_tx { + self.metrics.dec_builder_landed_blocks(); + info!( + block_number = block.number, + "Reverted block built by builder" + ); + } } Ok(()) @@ -95,35 +102,25 @@ where fn decode_chain_into_builder_txs( chain: &Chain, builder_signer: Option, -) -> Vec<( - SealedBlockWithSenders>, - OpTransactionSigned, -)> { +) -> Vec<(&SealedBlockWithSenders>, bool)> { chain // Get all blocks and receipts .blocks_and_receipts() // Get all receipts - .flat_map(|(block, receipts)| { - let block_clone = block.clone(); - block + .map(|(block, receipts)| { + let has_builder_tx = block .body() .transactions .iter() .zip(receipts.iter().flatten()) - .filter_map(move |(tx, receipt)| { - let is_builder_tx = receipt.status() + .any(move |(tx, receipt)| { + receipt.status() && tx.input().starts_with(OP_BUILDER_TX_PREFIX) && tx.recover_signer().is_some_and(|signer| { builder_signer.is_some_and(|bs| signer == bs.address) - }); - - if is_builder_tx { - // Clone the entire block and the transaction - Some((block_clone.clone(), tx.clone())) - } else { - None - } - }) + }) + }); + (block, has_builder_tx) }) .collect() } diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index aa2475b3..00a8b2f2 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1,9 +1,11 @@ use alloy_rpc_types_eth::Withdrawals; +use reth::core::primitives::InMemorySize; use reth_transaction_pool::PoolTransaction; -use std::{fmt::Display, sync::Arc}; +use std::{fmt::Display, sync::Arc, time::Instant}; use crate::{ generator::{BlockCell, PayloadBuilder}, + metrics::OpRBuilderMetrics, tx_signer::Signer, }; use alloy_consensus::{ @@ -41,7 +43,7 @@ use revm::{ }, Database, DatabaseCommit, }; -use tracing::{debug, trace, warn}; +use tracing::{info, trace, warn}; use op_alloy_consensus::{OpDepositReceipt, OpTxType, OpTypedTransaction}; use reth_optimism_payload_builder::{ @@ -51,11 +53,11 @@ use reth_optimism_payload_builder::{ use reth_transaction_pool::pool::BestPayloadTransactions; /// Optimism's payload builder -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct OpPayloadBuilderVanilla { - /// The rollup's compute pending block configuration option. - // TODO(clabby): Implement this feature. - pub compute_pending_block: bool, + // /// The rollup's compute pending block configuration option. + // // TODO(clabby): Implement this feature. + // pub compute_pending_block: bool, /// The type responsible for creating the evm. pub evm_config: EvmConfig, /// The builder's signer key to use for an end of block tx @@ -63,16 +65,19 @@ pub struct OpPayloadBuilderVanilla { /// 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: OpRBuilderMetrics, } impl OpPayloadBuilderVanilla { /// `OpPayloadBuilder` constructor. - pub const fn new(evm_config: EvmConfig, builder_signer: Option) -> Self { + pub fn new(evm_config: EvmConfig, builder_signer: Option) -> Self { Self { - compute_pending_block: true, + // compute_pending_block: true, evm_config, builder_signer, best_transactions: (), + metrics: Default::default(), } } } @@ -92,14 +97,25 @@ where best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { let pool = args.pool.clone(); + let block_build_start_time = Instant::now(); - match self.build_payload(args, |attrs| ().best_transactions(pool, attrs))? { + match self.build_payload(args, |attrs| { + #[allow(clippy::unit_arg)] + self.best_transactions.best_transactions(pool, attrs) + })? { BuildOutcome::Better { payload, .. } => { best_payload.set(payload); + self.metrics + .total_block_built_duration + .record(block_build_start_time.elapsed()); + self.metrics.block_built_success.increment(1); Ok(()) } BuildOutcome::Freeze(payload) => { best_payload.set(payload); + self.metrics + .total_block_built_duration + .record(block_build_start_time.elapsed()); Ok(()) } BuildOutcome::Cancelled => { @@ -162,6 +178,7 @@ where cancel, best_payload, builder_signer: self.builder_signer, + metrics: Default::default(), }; let builder = OpBuilder::new(best); @@ -253,7 +270,7 @@ where DB: Database, { let Self { best } = self; - debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); + info!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); // 1. apply eip-4788 pre block contract call ctx.apply_pre_beacon_root_contract_call(state)?; @@ -261,9 +278,15 @@ where // 2. ensure create2deployer is force deployed ctx.ensure_create2_deployer(state)?; + let sequencer_tx_start_time = Instant::now(); + // 3. execute sequencer transactions let mut info = ctx.execute_sequencer_transactions(state)?; + ctx.metrics + .sequencer_tx_duration + .record(sequencer_tx_start_time.elapsed()); + // 4. if mem pool transactions are requested we execute them // gas reserved for builder tx @@ -275,7 +298,11 @@ where }); let block_gas_limit = ctx.block_gas_limit() - builder_tx_gas; if !ctx.attributes().no_tx_pool { + let best_txs_start_time = Instant::now(); let best_txs = best(ctx.best_transaction_attributes()); + ctx.metrics + .transaction_pool_fetch_duration + .record(best_txs_start_time.elapsed()); if ctx .execute_best_transactions(&mut info, state, best_txs, block_gas_limit)? .is_some() @@ -297,10 +324,19 @@ where let withdrawals_root = ctx.commit_withdrawals(state)?; + let state_merge_start_time = Instant::now(); + // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call state.merge_transitions(BundleRetention::Reverts); + ctx.metrics + .state_transition_merge_duration + .record(state_merge_start_time.elapsed()); + ctx.metrics + .payload_num_tx + .record(info.executed_transactions.len() as f64); + Ok(BuildOutcomeKind::Better { payload: ExecutedPayload { info, @@ -349,7 +385,9 @@ where .block_logs_bloom(block_number) .expect("Number is in range"); - // // calculate the state root + // calculate the state root + let state_root_start_time = Instant::now(); + let state_provider = state.database.as_ref(); let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); let (state_root, trie_output) = { @@ -366,6 +404,10 @@ where })? }; + ctx.metrics + .state_root_calculation_duration + .record(state_root_start_time.elapsed()); + // create the block header let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); @@ -410,7 +452,7 @@ where }; let sealed_block = Arc::new(block.seal_slow()); - debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header, "sealed built block"); + info!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header, "sealed built block"); // create the executed block data let executed: ExecutedBlock = ExecutedBlock { @@ -432,6 +474,10 @@ where Some(executed), ); + ctx.metrics + .payload_byte_size + .record(payload.block().size() 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 @@ -524,6 +570,8 @@ pub struct OpPayloadBuilderCtx { pub best_payload: Option, /// The builder signer pub builder_signer: Option, + /// The metrics for the builder + pub metrics: OpRBuilderMetrics, } impl OpPayloadBuilderCtx { @@ -843,6 +891,11 @@ where where DB: Database, { + let execute_txs_start_time = Instant::now(); + let mut num_txs_considered = 0; + let mut num_txs_simulated = 0; + let mut num_txs_simulated_success = 0; + let mut num_txs_simulated_fail = 0; let base_fee = self.base_fee(); let env = EnvWithHandlerCfg::new_with_cfg_env( @@ -853,6 +906,7 @@ where let mut evm = self.evm_config.evm_with_env(&mut *db, env); while let Some(tx) = best_txs.next(()) { + num_txs_considered += 1; // ensure we still have capacity for this transaction if info.cumulative_gas_used + tx.gas_limit() > block_gas_limit { // we can't fit this transaction into the block, so we need to mark it as @@ -876,6 +930,8 @@ where // Configure the environment for the tx. *evm.tx_mut() = self.evm_config.tx_env(tx.tx(), tx.signer()); + let tx_simulation_start_time = Instant::now(); + let ResultAndState { result, state } = match evm.transact() { Ok(res) => res, Err(err) => { @@ -901,6 +957,26 @@ where } }; + self.metrics + .tx_simulation_duration + .record(tx_simulation_start_time.elapsed()); + self.metrics.tx_byte_size.record(tx.tx().size() as f64); + num_txs_simulated += 1; + if result.is_success() { + num_txs_simulated_success += 1; + } else { + num_txs_simulated_fail += 1; + } + self.metrics + .payload_num_tx_simulated + .record(num_txs_simulated as f64); + self.metrics + .payload_num_tx_simulated_success + .record(num_txs_simulated_success as f64); + self.metrics + .payload_num_tx_simulated_fail + .record(num_txs_simulated_fail as f64); + // commit changes evm.db_mut().commit(state); @@ -940,6 +1016,13 @@ where info.executed_transactions.push(tx.into_tx()); } + self.metrics + .payload_tx_simulation_duration + .record(execute_txs_start_time.elapsed()); + self.metrics + .payload_num_tx_considered + .record(num_txs_considered as f64); + Ok(None) } From 814fd147476eb9650a7353ef0863525cf9792903 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 28 Jan 2025 18:45:47 +0000 Subject: [PATCH 017/262] Rename payload generator (#382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR renames the `EmptyBlockPayloadJobGenerator` generator. --- crates/builder/op-rbuilder/src/generator.rs | 6 +++--- crates/builder/op-rbuilder/src/main.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 8eadc135..e6bc144a 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -53,7 +53,7 @@ pub trait PayloadBuilder: Send + Sync + Clone { /// The generator type that creates new jobs that builds empty blocks. #[derive(Debug)] -pub struct EmptyBlockPayloadJobGenerator { +pub struct BlockPayloadJobGenerator { /// The client that can interact with the chain. client: Client, /// txpool @@ -70,7 +70,7 @@ pub struct EmptyBlockPayloadJobGenerator { // === impl EmptyBlockPayloadJobGenerator === -impl EmptyBlockPayloadJobGenerator { +impl BlockPayloadJobGenerator { /// Creates a new [EmptyBlockPayloadJobGenerator] with the given config and custom /// [PayloadBuilder] pub fn with_builder( @@ -91,7 +91,7 @@ impl EmptyBlockPayloadJobGenerator PayloadJobGenerator - for EmptyBlockPayloadJobGenerator + for BlockPayloadJobGenerator where Client: StateProviderFactory + BlockReaderIdExt
diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index fef82803..9c213ced 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,5 +1,5 @@ use clap::Parser; -use generator::EmptyBlockPayloadJobGenerator; +use generator::BlockPayloadJobGenerator; use monitoring::Monitoring; use payload_builder::OpPayloadBuilder as FBPayloadBuilder; use payload_builder_vanilla::OpPayloadBuilderVanilla; @@ -75,7 +75,7 @@ where ); let payload_job_config = BasicPayloadJobGeneratorConfig::default(); - let payload_generator = EmptyBlockPayloadJobGenerator::with_builder( + let payload_generator = BlockPayloadJobGenerator::with_builder( ctx.provider().clone(), pool, ctx.task_executor().clone(), From ed3c78f6e0769ac071e93b2734daee0b528c89e0 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 28 Jan 2025 20:09:14 +0000 Subject: [PATCH 018/262] Add integration test for op-rbuilder (#381) This PR introduces an integration test framework for op-rbuilder that enables comprehensive end-to-end testing. The framework allows us to: - Spin up a local op-rbuilder node with configurable parameters - Test block production and chain advancement - Capture and analyze logs for debugging - Clean up resources automatically after tests complete Key changes: - Added IntegrationFramework to manage test services lifecycle - Created test utilities for genesis file generation and block production - Added initial test case verifying block production - Configured CI workflow to run integration tests and the logs are saved as artifacts To run the integration test run: ``` $ cargo run -p op-rbuilder --bin tester --features optimism -- genesis --output genesis.json $ cargo build -p op-rbuilder --bin op-rbuilder --features optimism $ cargo test --package op-rbuilder --lib --features optimism,integration -- integration::integration_test::tests ``` This framework is based on the testing scripts we currently use for L1 testing. A follow-up task will be to extract this into a separate crate that can be shared between L1 and L2 testing, standardizing our integration testing approach across the codebase. --- crates/builder/op-rbuilder/Cargo.toml | 15 +- .../src/integration/integration_test.rs | 39 + .../op-rbuilder/src/integration/mod.rs | 182 ++++ .../src/integration/op_rbuilder.rs | 108 +++ crates/builder/op-rbuilder/src/lib.rs | 2 + crates/builder/op-rbuilder/src/main.rs | 5 + .../src/tester/fixtures/genesis.json.tmpl | 880 ++++++++++++++++++ crates/builder/op-rbuilder/src/tester/main.rs | 40 + crates/builder/op-rbuilder/src/tester/mod.rs | 402 ++++++++ .../builder/op-rbuilder/src/tester/tester.rs | 320 ------- 10 files changed, 1670 insertions(+), 323 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/integration/integration_test.rs create mode 100644 crates/builder/op-rbuilder/src/integration/mod.rs create mode 100644 crates/builder/op-rbuilder/src/integration/op_rbuilder.rs create mode 100644 crates/builder/op-rbuilder/src/lib.rs create mode 100644 crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl create mode 100644 crates/builder/op-rbuilder/src/tester/main.rs create mode 100644 crates/builder/op-rbuilder/src/tester/mod.rs delete mode 100644 crates/builder/op-rbuilder/src/tester/tester.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index c43f1610..4edf8c8f 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -39,14 +39,16 @@ alloy-consensus.workspace = true alloy-eips.workspace = true alloy-rpc-types-beacon.workspace = true alloy-rpc-types-engine.workspace = true -op-alloy-consensus.workspace = true -op-alloy-rpc-types-engine.workspace = true alloy-transport-http.workspace = true alloy-rpc-types-eth.workspace = true alloy-rpc-client.workspace = true alloy-transport.workspace = true alloy-network.workspace = true +# op +op-alloy-consensus.workspace = true +op-alloy-rpc-types-engine.workspace = true + revm.workspace = true tracing.workspace = true @@ -64,6 +66,11 @@ clap_builder = { workspace = true } clap.workspace = true derive_more.workspace = true metrics.workspace = true +serde_json.workspace = true + +time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } +chrono = "0.4" +uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -97,10 +104,12 @@ optimism = [ "reth-optimism-cli/optimism" ] +integration = [] + [[bin]] name = "op-rbuilder" path = "src/main.rs" [[bin]] name = "tester" -path = "src/tester/tester.rs" +path = "src/tester/main.rs" diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs new file mode 100644 index 00000000..76321108 --- /dev/null +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -0,0 +1,39 @@ +#[cfg(all(test, feature = "integration"))] +mod tests { + use crate::integration::{op_rbuilder::OpRbuilderConfig, IntegrationFramework}; + use crate::tester::{BlockGenerator, EngineApi}; + use std::path::PathBuf; + use uuid::Uuid; + + #[tokio::test] + async fn integration_test_chain_produces_blocks() { + // This is a simple test using the integration framework to test that the chain + // produces blocks. + let mut framework = IntegrationFramework::new().unwrap(); + + // we are going to use a genesis file pre-generated before the test + let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + genesis_path.push("../../genesis.json"); + assert!(genesis_path.exists()); + + // generate a random dir for the data dir + let data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + + // generate a rra + let reth = OpRbuilderConfig::new() + .chain_config_path(genesis_path) + .data_dir(data_dir) + .auth_rpc_port(1234) + .network_port(1235); + + framework.start("op-rbuilder", &reth).await.unwrap(); + + let engine_api = EngineApi::new("http://localhost:1234").unwrap(); + let mut generator = BlockGenerator::new(&engine_api, None, false); + generator.init().await.unwrap(); + + for _ in 0..10 { + generator.generate_block().await.unwrap(); + } + } +} diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs new file mode 100644 index 00000000..523237b3 --- /dev/null +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -0,0 +1,182 @@ +use std::future::Future; +use std::path::Path; +use std::{ + fs::{File, OpenOptions}, + io, + io::prelude::*, + path::PathBuf, + process::{Child, Command}, + time::{Duration, SystemTime}, +}; +use time::{format_description, OffsetDateTime}; +use tokio::time::sleep; + +/// Default JWT token for testing purposes +pub const DEFAULT_JWT_TOKEN: &str = + "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; + +mod integration_test; +pub mod op_rbuilder; + +#[derive(Debug)] +pub enum IntegrationError { + SpawnError, + BinaryNotFound, + SetupError, + LogError, + ServiceAlreadyRunning, +} + +pub struct ServiceInstance { + process: Option, + pub log_path: PathBuf, +} + +pub struct IntegrationFramework { + test_dir: PathBuf, + services: Vec, +} + +pub trait Service { + /// Configure and return the command to run the service + fn command(&self) -> Command; + + /// Return a future that resolves when the service is ready + fn ready(&self, log_path: &Path) -> impl Future> + Send; +} + +/// Helper function to poll logs periodically +pub async fn poll_logs( + log_path: &Path, + pattern: &str, + interval: Duration, + timeout: Duration, +) -> Result<(), IntegrationError> { + let start = std::time::Instant::now(); + + loop { + if start.elapsed() > timeout { + return Err(IntegrationError::SpawnError); + } + + let mut file = File::open(log_path).map_err(|_| IntegrationError::LogError)?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|_| IntegrationError::LogError)?; + + if contents.contains(pattern) { + return Ok(()); + } + + sleep(interval).await; + } +} + +impl ServiceInstance { + pub fn new(name: String, test_dir: PathBuf) -> Self { + let log_path = test_dir.join(format!("{}.log", name)); + Self { + process: None, + log_path, + } + } + + pub fn start(&mut self, command: Command) -> Result<(), IntegrationError> { + if self.process.is_some() { + return Err(IntegrationError::ServiceAlreadyRunning); + } + + let log = open_log_file(&self.log_path)?; + let stdout = log.try_clone().map_err(|_| IntegrationError::LogError)?; + let stderr = log.try_clone().map_err(|_| IntegrationError::LogError)?; + + let mut cmd = command; + cmd.stdout(stdout).stderr(stderr); + + let child = match cmd.spawn() { + Ok(child) => Ok(child), + Err(e) => match e.kind() { + io::ErrorKind::NotFound => Err(IntegrationError::BinaryNotFound), + _ => Err(IntegrationError::SpawnError), + }, + }?; + + self.process = Some(child); + Ok(()) + } + + pub fn stop(&mut self) -> Result<(), IntegrationError> { + if let Some(mut process) = self.process.take() { + process.kill().map_err(|_| IntegrationError::SpawnError)?; + } + Ok(()) + } + + /// Start a service using its configuration and wait for it to be ready + pub async fn start_with_config( + &mut self, + config: &T, + ) -> Result<(), IntegrationError> { + self.start(config.command())?; + config.ready(&self.log_path).await?; + Ok(()) + } +} + +impl IntegrationFramework { + pub fn new() -> Result { + let dt: OffsetDateTime = SystemTime::now().into(); + let format = format_description::parse("[year]_[month]_[day]_[hour]_[minute]_[second]") + .map_err(|_| IntegrationError::SetupError)?; + + let test_name = dt + .format(&format) + .map_err(|_| IntegrationError::SetupError)?; + + let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + test_dir.push("../../integration_logs"); + test_dir.push(test_name); + + std::fs::create_dir_all(&test_dir).map_err(|_| IntegrationError::SetupError)?; + + Ok(Self { + test_dir, + services: Vec::new(), + }) + } + + pub async fn start( + &mut self, + name: &str, + config: &T, + ) -> Result<&mut ServiceInstance, IntegrationError> { + let service = self.create_service(name)?; + service.start_with_config(config).await?; + Ok(service) + } + + pub fn create_service(&mut self, name: &str) -> Result<&mut ServiceInstance, IntegrationError> { + let service = ServiceInstance::new(name.to_string(), self.test_dir.clone()); + self.services.push(service); + Ok(self.services.last_mut().unwrap()) + } +} + +fn open_log_file(path: &PathBuf) -> Result { + let prefix = path.parent().unwrap(); + std::fs::create_dir_all(prefix).map_err(|_| IntegrationError::LogError)?; + + OpenOptions::new() + .append(true) + .create(true) + .open(path) + .map_err(|_| IntegrationError::LogError) +} + +impl Drop for IntegrationFramework { + fn drop(&mut self) { + for service in &mut self.services { + let _ = service.stop(); + } + } +} diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs new file mode 100644 index 00000000..3183be12 --- /dev/null +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -0,0 +1,108 @@ +use crate::integration::{poll_logs, IntegrationError, Service, DEFAULT_JWT_TOKEN}; +use futures_util::Future; +use std::{ + path::{Path, PathBuf}, + process::Command, + time::Duration, +}; + +fn get_or_create_jwt_path(jwt_path: Option<&PathBuf>) -> PathBuf { + jwt_path.cloned().unwrap_or_else(|| { + let tmp_dir = std::env::temp_dir(); + let jwt_path = tmp_dir.join("jwt.hex"); + std::fs::write(&jwt_path, DEFAULT_JWT_TOKEN).expect("Failed to write JWT secret file"); + jwt_path + }) +} + +#[derive(Default)] +pub struct OpRbuilderConfig { + auth_rpc_port: Option, + jwt_secret_path: Option, + chain_config_path: Option, + data_dir: Option, + http_port: Option, + network_port: Option, +} + +impl OpRbuilderConfig { + pub fn new() -> Self { + Self::default() + } + + pub fn auth_rpc_port(mut self, port: u16) -> Self { + self.auth_rpc_port = Some(port); + self + } + + pub fn chain_config_path>(mut self, path: P) -> Self { + self.chain_config_path = Some(path.into()); + self + } + + pub fn data_dir>(mut self, path: P) -> Self { + self.data_dir = Some(path.into()); + self + } + + pub fn network_port(mut self, port: u16) -> Self { + self.network_port = Some(port); + self + } +} + +impl Service for OpRbuilderConfig { + fn command(&self) -> Command { + let mut bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + bin_path.push("../../target/debug/op-rbuilder"); + + let mut cmd = Command::new(bin_path); + let jwt_path = get_or_create_jwt_path(self.jwt_secret_path.as_ref()); + + cmd.arg("node") + .arg("--authrpc.port") + .arg( + self.auth_rpc_port + .expect("auth_rpc_port not set") + .to_string(), + ) + .arg("--authrpc.jwtsecret") + .arg( + jwt_path + .to_str() + .expect("Failed to convert jwt_path to string"), + ) + .arg("--chain") + .arg( + self.chain_config_path + .as_ref() + .expect("chain_config_path not set"), + ) + .arg("--datadir") + .arg(self.data_dir.as_ref().expect("data_dir not set")) + .arg("--disable-discovery") + .arg("--port") + .arg(self.network_port.expect("network_port not set").to_string()); + + if let Some(http_port) = self.http_port { + cmd.arg("--http") + .arg("--http.port") + .arg(http_port.to_string()); + } + + cmd + } + + #[allow(clippy::manual_async_fn)] + fn ready(&self, log_path: &Path) -> impl Future> + Send { + async move { + poll_logs( + log_path, + "Starting consensus engine", + Duration::from_millis(100), + Duration::from_secs(60), + ) + .await + } + } +} diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs new file mode 100644 index 00000000..238758c1 --- /dev/null +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -0,0 +1,2 @@ +pub mod integration; +pub mod tester; diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 9c213ced..b78aef41 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -32,11 +32,16 @@ use reth_optimism_primitives::OpPrimitives; use reth_transaction_pool::PoolTransaction; pub mod generator; +#[cfg(test)] +mod integration; mod metrics; mod monitoring; pub mod payload_builder; mod payload_builder_vanilla; +#[cfg(test)] +mod tester; mod tx_signer; + #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] pub struct CustomPayloadBuilder { diff --git a/crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl b/crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl new file mode 100644 index 00000000..74f5d0a7 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl @@ -0,0 +1,880 @@ +{ + "config": { + "chainId": 901, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "mergeNetsplitBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "bedrockBlock": 0, + "regolithTime": 0, + "canyonTime": 0, + "ecotoneTime": 0, + "fjordTime": 0, + "graniteTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "depositContractAddress": "0x0000000000000000000000000000000000000000", + "optimism": { + "eip1559Elasticity": 6, + "eip1559Denominator": 50, + "eip1559DenominatorCanyon": 250 + } + }, + "nonce": "0x0", + "timestamp": "", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x4200000000000000000000000000000000000011", + "alloc": { + "0000000000000000000000000000000000000000": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000001": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000002": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000003": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000004": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000005": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000006": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000007": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000008": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000009": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000000f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000010": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000011": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000012": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000013": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000014": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000015": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000016": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000017": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000018": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000019": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000001f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000020": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000021": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000022": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000023": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000024": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000025": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000026": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000027": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000028": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000029": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000002f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000030": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000031": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000032": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000033": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000034": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000035": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000036": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000037": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000038": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000039": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000003f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000040": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000041": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000042": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000043": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000044": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000045": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000046": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000047": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000048": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000049": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000004f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000050": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000051": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000052": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000053": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000054": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000055": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000056": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000057": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000058": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000059": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000005f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000060": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000061": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000062": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000063": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000064": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000065": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000066": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000067": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000068": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000069": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000006f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000070": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000071": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000072": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000073": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000074": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000075": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000076": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000077": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000078": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000079": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000007f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000080": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000081": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000082": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000083": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000084": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000085": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000086": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000087": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000088": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000089": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000008f": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000090": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000091": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000092": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000093": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000094": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000095": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000096": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000097": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000098": { + "balance": "0x1" + }, + "0000000000000000000000000000000000000099": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009a": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009b": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009c": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009d": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009e": { + "balance": "0x1" + }, + "000000000000000000000000000000000000009f": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000a9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000aa": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ab": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ac": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ad": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ae": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000af": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000b9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ba": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000be": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000bf": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000c9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ca": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ce": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000cf": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000d9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000da": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000db": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000dc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000dd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000de": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000df": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000e9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ea": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000eb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ec": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ed": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ee": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ef": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f0": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f1": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f2": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f3": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f4": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f5": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f6": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f7": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f8": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000f9": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fa": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fb": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fc": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fd": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000fe": { + "balance": "0x1" + }, + "00000000000000000000000000000000000000ff": { + "balance": "0x1" + }, + "000000000022d473030f116ddee9f6b43ac78ba3": { + "code": "0x6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f000000000000000000000000000000000000000000000000000000000000038503611b69577f48deb34b39fb4b41f5c195008940d5ef510cdd7853eba5807b2fa08dfd58647590565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a", + "balance": "0x0", + "nonce": "0x1" + }, + "0000000071727de22e5e9d8baf0edac6f37da032": { + "code": "0x60806040526004361015610024575b361561001957600080fd5b61002233612748565b005b60003560e01c806242dc5314611b0057806301ffc9a7146119ae5780630396cb60146116765780630bd28e3b146115fa5780631b2e01b814611566578063205c2878146113d157806322cdde4c1461136b57806335567e1a146112b35780635287ce12146111a557806370a0823114611140578063765e827f14610e82578063850aaf6214610dc35780639b249f6914610c74578063b760faf914610c3a578063bb9fe6bf14610a68578063c23a5cea146107c4578063dbed18e0146101a15763fc7e286d0361000e573461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5773ffffffffffffffffffffffffffffffffffffffff61013a61229f565b16600052600060205260a0604060002065ffffffffffff6001825492015460405192835260ff8116151560208401526dffffffffffffffffffffffffffff8160081c16604084015263ffffffff8160781c16606084015260981c166080820152f35b600080fd5b3461019c576101af36612317565b906101b86129bd565b60009160005b82811061056f57506101d08493612588565b6000805b8481106102fc5750507fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972600080a16000809360005b81811061024757610240868660007f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d8180a2613ba7565b6001600255005b6102a261025582848a612796565b73ffffffffffffffffffffffffffffffffffffffff6102766020830161282a565b167f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d600080a2806127d6565b906000915b8083106102b957505050600101610209565b909194976102f36102ed6001926102e78c8b6102e0826102da8e8b8d61269d565b9261265a565b5191613597565b90612409565b99612416565b950191906102a7565b6020610309828789612796565b61031f61031682806127d6565b9390920161282a565b9160009273ffffffffffffffffffffffffffffffffffffffff8091165b8285106103505750505050506001016101d4565b909192939561037f83610378610366848c61265a565b516103728b898b61269d565b856129f6565b9290613dd7565b9116840361050a576104a5576103958491613dd7565b9116610440576103b5576103aa600191612416565b96019392919061033c565b60a487604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f65000000000000000000000000000000000000000000000000000000000000006084820152fd5b608488604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413334207369676e6174757265206572726f720000000000000000000000006064820152fd5b608488604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f414132322065787069726564206f72206e6f74206475650000000000000000006064820152fd5b608489604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413234207369676e6174757265206572726f720000000000000000000000006064820152fd5b61057a818487612796565b9361058585806127d6565b919095602073ffffffffffffffffffffffffffffffffffffffff6105aa82840161282a565b1697600192838a1461076657896105da575b5050505060019293949550906105d191612409565b939291016101be565b8060406105e892019061284b565b918a3b1561019c57929391906040519485937f2dd8113300000000000000000000000000000000000000000000000000000000855288604486016040600488015252606490818601918a60051b8701019680936000915b8c83106106e657505050505050838392610684927ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8560009803016024860152612709565b03818a5afa90816106d7575b506106c657602486604051907f86a9f7500000000000000000000000000000000000000000000000000000000082526004820152fd5b93945084936105d1600189806105bc565b6106e0906121bd565b88610690565b91939596977fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c908a9294969a0301865288357ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee18336030181121561019c57836107538793858394016128ec565b9a0196019301909189979695949261063f565b606483604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601760248201527f4141393620696e76616c69642061676772656761746f720000000000000000006044820152fd5b3461019c576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c576107fc61229f565b33600052600082526001604060002001908154916dffffffffffffffffffffffffffff8360081c16928315610a0a5765ffffffffffff8160981c1680156109ac57421061094e5760009373ffffffffffffffffffffffffffffffffffffffff859485947fffffffffffffff000000000000000000000000000000000000000000000000ff86951690556040517fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda33391806108da8786836020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b0390a2165af16108e8612450565b50156108f057005b606490604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601860248201527f6661696c656420746f207769746864726177207374616b6500000000000000006044820152fd5b606485604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601b60248201527f5374616b65207769746864726177616c206973206e6f742064756500000000006044820152fd5b606486604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601d60248201527f6d7573742063616c6c20756e6c6f636b5374616b6528292066697273740000006044820152fd5b606485604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601460248201527f4e6f207374616b6520746f2077697468647261770000000000000000000000006044820152fd5b3461019c5760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c573360005260006020526001604060002001805463ffffffff8160781c16908115610bdc5760ff1615610b7e5765ffffffffffff908142160191818311610b4f5780547fffffffffffffff000000000000ffffffffffffffffffffffffffffffffffff001678ffffffffffff00000000000000000000000000000000000000609885901b161790556040519116815233907ffa9b3c14cc825c412c9ed81b3ba365a5b459439403f18829e572ed53a4180f0a90602090a2005b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f616c726561647920756e7374616b696e670000000000000000000000000000006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f6e6f74207374616b6564000000000000000000000000000000000000000000006044820152fd5b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c57610022610c6f61229f565b612748565b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5760043567ffffffffffffffff811161019c576020610cc8610d1b9236906004016122c2565b919073ffffffffffffffffffffffffffffffffffffffff9260405194859283927f570e1a360000000000000000000000000000000000000000000000000000000084528560048501526024840191612709565b03816000857f000000000000000000000000efc2c1444ebcc4db75e7613d20c6a62ff67a167c165af1908115610db757602492600092610d86575b50604051917f6ca7b806000000000000000000000000000000000000000000000000000000008352166004820152fd5b610da991925060203d602011610db0575b610da181836121ed565b8101906126dd565b9083610d56565b503d610d97565b6040513d6000823e3d90fd5b3461019c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c57610dfa61229f565b60243567ffffffffffffffff811161019c57600091610e1e839236906004016122c2565b90816040519283928337810184815203915af4610e39612450565b90610e7e6040519283927f99410554000000000000000000000000000000000000000000000000000000008452151560048401526040602484015260448301906123c6565b0390fd5b3461019c57610e9036612317565b610e9b9291926129bd565b610ea483612588565b60005b848110610f1c57506000927fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972600080a16000915b858310610eec576102408585613ba7565b909193600190610f12610f0087898761269d565b610f0a888661265a565b519088613597565b0194019190610edb565b610f47610f40610f2e8385979561265a565b51610f3a84898761269d565b846129f6565b9190613dd7565b73ffffffffffffffffffffffffffffffffffffffff929183166110db5761107657610f7190613dd7565b911661101157610f8657600101929092610ea7565b60a490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f65000000000000000000000000000000000000000000000000000000000000006084820152fd5b608482604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413334207369676e6174757265206572726f720000000000000000000000006064820152fd5b608483604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f414132322065787069726564206f72206e6f74206475650000000000000000006064820152fd5b608484604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601460448201527f41413234207369676e6174757265206572726f720000000000000000000000006064820152fd5b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5773ffffffffffffffffffffffffffffffffffffffff61118c61229f565b1660005260006020526020604060002054604051908152f35b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5773ffffffffffffffffffffffffffffffffffffffff6111f161229f565b6000608060405161120181612155565b828152826020820152826040820152826060820152015216600052600060205260a06040600020608060405161123681612155565b6001835493848352015490602081019060ff8316151582526dffffffffffffffffffffffffffff60408201818560081c16815263ffffffff936060840193858760781c16855265ffffffffffff978891019660981c1686526040519788525115156020880152511660408601525116606084015251166080820152f35b3461019c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5760206112ec61229f565b73ffffffffffffffffffffffffffffffffffffffff6113096122f0565b911660005260018252604060002077ffffffffffffffffffffffffffffffffffffffffffffffff821660005282526040600020547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006040519260401b16178152f35b3461019c577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc60208136011261019c576004359067ffffffffffffffff821161019c5761012090823603011261019c576113c9602091600401612480565b604051908152f35b3461019c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5761140861229f565b60243590336000526000602052604060002090815491828411611508576000808573ffffffffffffffffffffffffffffffffffffffff8295839561144c848a612443565b90556040805173ffffffffffffffffffffffffffffffffffffffff831681526020810185905233917fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb91a2165af16114a2612450565b50156114aa57005b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6661696c656420746f20776974686472617700000000000000000000000000006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f576974686472617720616d6f756e7420746f6f206c61726765000000000000006044820152fd5b3461019c5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5761159d61229f565b73ffffffffffffffffffffffffffffffffffffffff6115ba6122f0565b9116600052600160205277ffffffffffffffffffffffffffffffffffffffffffffffff604060002091166000526020526020604060002054604051908152f35b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5760043577ffffffffffffffffffffffffffffffffffffffffffffffff811680910361019c5733600052600160205260406000209060005260205260406000206116728154612416565b9055005b6020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5760043563ffffffff9182821680920361019c5733600052600081526040600020928215611950576001840154908160781c1683106118f2576116f86dffffffffffffffffffffffffffff9182349160081c16612409565b93841561189457818511611836579065ffffffffffff61180592546040519061172082612155565b8152848101926001845260408201908816815260608201878152600160808401936000855233600052600089526040600020905181550194511515917fffffffffffffffffffffffffff0000000000000000000000000000000000000060ff72ffffffff0000000000000000000000000000006effffffffffffffffffffffffffff008954945160081b16945160781b1694169116171717835551167fffffffffffffff000000000000ffffffffffffffffffffffffffffffffffffff78ffffffffffff0000000000000000000000000000000000000083549260981b169116179055565b6040519283528201527fa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c0160403392a2005b606483604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152600e60248201527f7374616b65206f766572666c6f770000000000000000000000000000000000006044820152fd5b606483604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601260248201527f6e6f207374616b652073706563696669656400000000000000000000000000006044820152fd5b606482604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601c60248201527f63616e6e6f7420646563726561736520756e7374616b652074696d65000000006044820152fd5b606482604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601a60248201527f6d757374207370656369667920756e7374616b652064656c61790000000000006044820152fd5b3461019c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c576004357fffffffff00000000000000000000000000000000000000000000000000000000811680910361019c57807f60fc6b6e0000000000000000000000000000000000000000000000000000000060209214908115611ad6575b8115611aac575b8115611a82575b8115611a58575b506040519015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501482611a4d565b7f3e84f0210000000000000000000000000000000000000000000000000000000081149150611a46565b7fcf28ef970000000000000000000000000000000000000000000000000000000081149150611a3f565b7f915074d80000000000000000000000000000000000000000000000000000000081149150611a38565b3461019c576102007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019c5767ffffffffffffffff60043581811161019c573660238201121561019c57611b62903690602481600401359101612268565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc36016101c0811261019c5761014060405191611b9e83612155565b1261019c5760405192611bb0846121a0565b60243573ffffffffffffffffffffffffffffffffffffffff8116810361019c578452602093604435858201526064356040820152608435606082015260a435608082015260c43560a082015260e43560c08201526101043573ffffffffffffffffffffffffffffffffffffffff8116810361019c5760e08201526101243561010082015261014435610120820152825261016435848301526101843560408301526101a43560608301526101c43560808301526101e43590811161019c57611c7c9036906004016122c2565b905a3033036120f7578351606081015195603f5a0260061c61271060a0840151890101116120ce5760009681519182611ff0575b5050505090611cca915a9003608085015101923691612268565b925a90600094845193611cdc85613ccc565b9173ffffffffffffffffffffffffffffffffffffffff60e0870151168015600014611ea957505073ffffffffffffffffffffffffffffffffffffffff855116935b5a9003019360a06060820151910151016080860151850390818111611e95575b50508302604085015192818410600014611dce5750506003811015611da157600203611d79576113c99293508093611d7481613d65565b613cf6565b5050507fdeadaa51000000000000000000000000000000000000000000000000000000008152fd5b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526021600452fd5b81611dde92979396940390613c98565b506003841015611e6857507f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f60808683015192519473ffffffffffffffffffffffffffffffffffffffff865116948873ffffffffffffffffffffffffffffffffffffffff60e0890151169701519160405192835215898301528760408301526060820152a46113c9565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526021600452fd5b6064919003600a0204909301928780611d3d565b8095918051611eba575b5050611d1d565b6003861015611fc1576002860315611eb35760a088015190823b1561019c57600091611f2491836040519586809581947f7c627b210000000000000000000000000000000000000000000000000000000083528d60048401526080602484015260848301906123c6565b8b8b0260448301528b60648301520393f19081611fad575b50611fa65787893d610800808211611f9e575b506040519282828501016040528184528284013e610e7e6040519283927fad7954bc000000000000000000000000000000000000000000000000000000008452600484015260248301906123c6565b905083611f4f565b8980611eb3565b611fb89199506121bd565b6000978a611f3c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91600092918380938c73ffffffffffffffffffffffffffffffffffffffff885116910192f115612023575b808080611cb0565b611cca929195503d6108008082116120c6575b5060405190888183010160405280825260008983013e805161205f575b5050600194909161201b565b7f1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a20188870151918973ffffffffffffffffffffffffffffffffffffffff8551169401516120bc604051928392835260408d84015260408301906123c6565b0390a38680612053565b905088612036565b877fdeaddead000000000000000000000000000000000000000000000000000000006000526000fd5b606486604051907f08c379a00000000000000000000000000000000000000000000000000000000082526004820152601760248201527f4141393220696e7465726e616c2063616c6c206f6e6c790000000000000000006044820152fd5b60a0810190811067ffffffffffffffff82111761217157604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610140810190811067ffffffffffffffff82111761217157604052565b67ffffffffffffffff811161217157604052565b6060810190811067ffffffffffffffff82111761217157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761217157604052565b67ffffffffffffffff811161217157601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926122748261222e565b9161228260405193846121ed565b82948184528183011161019c578281602093846000960137010152565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361019c57565b9181601f8401121561019c5782359167ffffffffffffffff831161019c576020838186019501011161019c57565b6024359077ffffffffffffffffffffffffffffffffffffffffffffffff8216820361019c57565b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc83011261019c5760043567ffffffffffffffff9283821161019c578060238301121561019c57816004013593841161019c5760248460051b8301011161019c57602401919060243573ffffffffffffffffffffffffffffffffffffffff8116810361019c5790565b60005b8381106123b65750506000910152565b81810151838201526020016123a6565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602093612402815180928187528780880191016123a3565b0116010190565b91908201809211610b4f57565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610b4f5760010190565b91908203918211610b4f57565b3d1561247b573d906124618261222e565b9161246f60405193846121ed565b82523d6000602084013e565b606090565b604061248e8183018361284b565b90818351918237206124a3606084018461284b565b90818451918237209260c06124bb60e083018361284b565b908186519182372091845195602087019473ffffffffffffffffffffffffffffffffffffffff833516865260208301358789015260608801526080870152608081013560a087015260a081013582870152013560e08501526101009081850152835261012083019167ffffffffffffffff918484108385111761217157838252845190206101408501908152306101608601524661018086015260608452936101a00191821183831017612171575251902090565b67ffffffffffffffff81116121715760051b60200190565b9061259282612570565b6040906125a260405191826121ed565b8381527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06125d08295612570565b019160005b8381106125e25750505050565b60209082516125f081612155565b83516125fb816121a0565b600081526000849181838201528187820152816060818184015260809282848201528260a08201528260c08201528260e082015282610100820152826101208201528652818587015281898701528501528301528286010152016125d5565b805182101561266e5760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b919081101561266e5760051b810135907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee18136030182121561019c570190565b9081602091031261019c575173ffffffffffffffffffffffffffffffffffffffff8116810361019c5790565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b7f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4602073ffffffffffffffffffffffffffffffffffffffff61278a3485613c98565b936040519485521692a2565b919081101561266e5760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa18136030182121561019c570190565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561019c570180359067ffffffffffffffff821161019c57602001918160051b3603831361019c57565b3573ffffffffffffffffffffffffffffffffffffffff8116810361019c5790565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561019c570180359067ffffffffffffffff821161019c5760200191813603831361019c57565b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561019c57016020813591019167ffffffffffffffff821161019c57813603831361019c57565b61012091813573ffffffffffffffffffffffffffffffffffffffff811680910361019c576129626129476129ba9561299b93855260208601356020860152612937604087018761289c565b9091806040880152860191612709565b612954606086018661289c565b908583036060870152612709565b6080840135608084015260a084013560a084015260c084013560c084015261298d60e085018561289c565b9084830360e0860152612709565b916129ac610100918281019061289c565b929091818503910152612709565b90565b60028054146129cc5760028055565b60046040517f3ee5aeb5000000000000000000000000000000000000000000000000000000008152fd5b926000905a93805194843573ffffffffffffffffffffffffffffffffffffffff811680910361019c5786526020850135602087015260808501356fffffffffffffffffffffffffffffffff90818116606089015260801c604088015260a086013560c088015260c086013590811661010088015260801c610120870152612a8060e086018661284b565b801561357b576034811061351d578060141161019c578060241161019c5760341161019c57602481013560801c60a0880152601481013560801c60808801523560601c60e08701525b612ad285612480565b60208301526040860151946effffffffffffffffffffffffffffff8660c08901511760608901511760808901511760a0890151176101008901511761012089015117116134bf57604087015160608801510160808801510160a08801510160c0880151016101008801510296835173ffffffffffffffffffffffffffffffffffffffff81511690612b66604085018561284b565b806131e4575b505060e0015173ffffffffffffffffffffffffffffffffffffffff1690600082156131ac575b6020612bd7918b828a01516000868a604051978896879586937f19822f7c00000000000000000000000000000000000000000000000000000000855260048501613db5565b0393f160009181613178575b50612c8b573d8c610800808311612c83575b50604051916020818401016040528083526000602084013e610e7e6040519283927f65c8fd4d000000000000000000000000000000000000000000000000000000008452600484015260606024840152600d60648401527f4141323320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a48301906123c6565b915082612bf5565b9a92939495969798999a91156130f2575b509773ffffffffffffffffffffffffffffffffffffffff835116602084015190600052600160205260406000208160401c60005260205267ffffffffffffffff604060002091825492612cee84612416565b9055160361308d575a8503116130285773ffffffffffffffffffffffffffffffffffffffff60e0606093015116612d42575b509060a09184959697986040608096015260608601520135905a900301910152565b969550505a9683519773ffffffffffffffffffffffffffffffffffffffff60e08a01511680600052600060205260406000208054848110612fc3576080612dcd9a9b9c600093878094039055015192602089015183604051809d819582947f52b7512c0000000000000000000000000000000000000000000000000000000084528c60048501613db5565b039286f1978860009160009a612f36575b50612e86573d8b610800808311612e7e575b50604051916020818401016040528083526000602084013e610e7e6040519283927f65c8fd4d000000000000000000000000000000000000000000000000000000008452600484015260606024840152600d60648401527f4141333320726576657274656400000000000000000000000000000000000000608484015260a0604484015260a48301906123c6565b915082612df0565b9991929394959697989998925a900311612eab57509096959094939291906080612d20565b60a490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602760448201527f41413336206f766572207061796d6173746572566572696669636174696f6e4760648201527f61734c696d6974000000000000000000000000000000000000000000000000006084820152fd5b915098503d90816000823e612f4b82826121ed565b604081838101031261019c5780519067ffffffffffffffff821161019c57828101601f83830101121561019c578181015191612f868361222e565b93612f9460405195866121ed565b838552820160208483850101011161019c57602092612fba9184808701918501016123a3565b01519838612dde565b60848b604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413331207061796d6173746572206465706f73697420746f6f206c6f7700006064820152fd5b608490604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601e60448201527f41413236206f76657220766572696669636174696f6e4761734c696d697400006064820152fd5b608482604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601a60448201527f4141323520696e76616c6964206163636f756e74206e6f6e63650000000000006064820152fd5b600052600060205260406000208054808c11613113578b9003905538612c9c565b608484604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152fd5b9091506020813d6020116131a4575b81613194602093836121ed565b8101031261019c57519038612be3565b3d9150613187565b508060005260006020526040600020548a81116000146131d75750612bd7602060005b915050612b92565b6020612bd7918c036131cf565b833b61345a57604088510151602060405180927f570e1a360000000000000000000000000000000000000000000000000000000082528260048301528160008161323260248201898b612709565b039273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000efc2c1444ebcc4db75e7613d20c6a62ff67a167c1690f1908115610db75760009161343b575b5073ffffffffffffffffffffffffffffffffffffffff811680156133d6578503613371573b1561330c5760141161019c5773ffffffffffffffffffffffffffffffffffffffff9183887fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d604060e0958787602086015195510151168251913560601c82526020820152a391612b6c565b60848d604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f4141313520696e6974436f6465206d757374206372656174652073656e6465726064820152fd5b60848e604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152602060448201527f4141313420696e6974436f6465206d7573742072657475726e2073656e6465726064820152fd5b60848f604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601b60448201527f4141313320696e6974436f6465206661696c6564206f72204f4f4700000000006064820152fd5b613454915060203d602011610db057610da181836121ed565b3861327c565b60848d604051907f220266b6000000000000000000000000000000000000000000000000000000008252600482015260406024820152601f60448201527f414131302073656e64657220616c726561647920636f6e7374727563746564006064820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f41413934206761732076616c756573206f766572666c6f7700000000000000006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4141393320696e76616c6964207061796d6173746572416e64446174610000006044820152fd5b5050600060e087015260006080870152600060a0870152612ac9565b9092915a906060810151916040928351967fffffffff00000000000000000000000000000000000000000000000000000000886135d7606084018461284b565b600060038211613b9f575b7f8dd7712f0000000000000000000000000000000000000000000000000000000094168403613a445750505061379d6000926136b292602088015161363a8a5193849360208501528b602485015260648401906128ec565b90604483015203906136727fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0928381018352826121ed565b61379189519485927e42dc5300000000000000000000000000000000000000000000000000000000602085015261020060248501526102248401906123c6565b613760604484018b60806101a091805173ffffffffffffffffffffffffffffffffffffffff808251168652602082015160208701526040820151604087015260608201516060870152838201518487015260a082015160a087015260c082015160c087015260e08201511660e0860152610100808201519086015261012080910151908501526020810151610140850152604081015161016085015260608101516101808501520151910152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83820301610204840152876123c6565b039081018352826121ed565b6020918183809351910182305af1600051988652156137bf575b505050505050565b909192939495965060003d8214613a3a575b7fdeaddead00000000000000000000000000000000000000000000000000000000810361385b57608487878051917f220266b600000000000000000000000000000000000000000000000000000000835260048301526024820152600f60448201527f41413935206f7574206f662067617300000000000000000000000000000000006064820152fd5b7fdeadaa510000000000000000000000000000000000000000000000000000000091929395949650146000146138c55750506138a961389e6138b8935a90612443565b608085015190612409565b9083015183611d748295613d65565b905b3880808080806137b7565b909261395290828601518651907ff62676f440ff169a3a9afdbf812e89e7f95975ee8e5c31214ffdef631c5f479273ffffffffffffffffffffffffffffffffffffffff9580878551169401516139483d610800808211613a32575b508a519084818301018c5280825260008583013e8a805194859485528401528a8301906123c6565b0390a35a90612443565b916139636080860193845190612409565b926000905a94829488519761397789613ccc565b948260e08b0151168015600014613a1857505050875116955b5a9003019560a06060820151910151019051860390818111613a04575b5050840290850151928184106000146139de57505080611e68575090816139d89293611d7481613d65565b906138ba565b6139ee9082849397950390613c98565b50611e68575090826139ff92613cf6565b6139d8565b6064919003600a02049094019338806139ad565b90919892509751613a2a575b50613990565b955038613a24565b905038613920565b8181803e516137d1565b613b97945082935090613a8c917e42dc53000000000000000000000000000000000000000000000000000000006020613b6b9501526102006024860152610224850191612709565b613b3a604484018860806101a091805173ffffffffffffffffffffffffffffffffffffffff808251168652602082015160208701526040820151604087015260608201516060870152838201518487015260a082015160a087015260c082015160c087015260e08201511660e0860152610100808201519086015261012080910151908501526020810151610140850152604081015161016085015260608101516101808501520151910152565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83820301610204840152846123c6565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081018952886121ed565b60008761379d565b5081356135e2565b73ffffffffffffffffffffffffffffffffffffffff168015613c3a57600080809381935af1613bd4612450565b5015613bdc57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f41413931206661696c65642073656e6420746f2062656e6566696369617279006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4141393020696e76616c69642062656e656669636961727900000000000000006044820152fd5b73ffffffffffffffffffffffffffffffffffffffff166000526000602052613cc66040600020918254612409565b80915590565b610120610100820151910151808214613cf257480180821015613ced575090565b905090565b5090565b9190917f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f6080602083015192519473ffffffffffffffffffffffffffffffffffffffff946020868851169660e089015116970151916040519283526000602084015260408301526060820152a4565b60208101519051907f67b4fa9642f42120bf031f3051d1824b0fe25627945b27b8a6a65d5761d5482e60208073ffffffffffffffffffffffffffffffffffffffff855116940151604051908152a3565b613dcd604092959493956060835260608301906128ec565b9460208201520152565b8015613e6457600060408051613dec816121d1565b828152826020820152015273ffffffffffffffffffffffffffffffffffffffff811690604065ffffffffffff91828160a01c16908115613e5c575b60d01c92825191613e37836121d1565b8583528460208401521691829101524211908115613e5457509091565b905042109091565b839150613e27565b5060009060009056fea2646970667358221220b094fd69f04977ae9458e5ba422d01cd2d20dbcfca0992ff37f19aa07deec25464736f6c63430008170033", + "balance": "0x0", + "nonce": "0x1" + }, + "000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "balance": "0x0", + "nonce": "0x1" + }, + "02484cb50aac86eae85610d6f4bf026f30f6627d": { + "balance": "0x21e19e0c9bab2400000" + }, + "08135da0a343e492fa2d4282f2ae34c6c5cc1bbe": { + "balance": "0x21e19e0c9bab2400000" + }, + "09db0a93b389bef724429898f539aeb7ac2dd55f": { + "balance": "0x21e19e0c9bab2400000" + }, + "0b799c86a49deeb90402691f1041aa3af2d3c875": { + "balance": "0x0", + "nonce": "0x1" + }, + "13b0d85ccb8bf860b6b79af3029fca081ae9bef2": { + "code": "0x6080604052600436106100435760003560e01c8063076c37b21461004f578063481286e61461007157806356299481146100ba57806366cfa057146100da57600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5061006f61006a366004610327565b6100fa565b005b34801561007d57600080fd5b5061009161008c366004610327565b61014a565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100c657600080fd5b506100916100d5366004610349565b61015d565b3480156100e657600080fd5b5061006f6100f53660046103ca565b610172565b61014582826040518060200161010f9061031a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082820381018352601f90910116604052610183565b505050565b600061015683836102e7565b9392505050565b600061016a8484846102f0565b949350505050565b61017d838383610183565b50505050565b6000834710156101f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e636500000060448201526064015b60405180910390fd5b815160000361025f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f60448201526064016101eb565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116610156576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f790000000000000060448201526064016101eb565b60006101568383305b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b61014e806104ad83390190565b6000806040838503121561033a57600080fd5b50508035926020909101359150565b60008060006060848603121561035e57600080fd5b8335925060208401359150604084013573ffffffffffffffffffffffffffffffffffffffff8116811461039057600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156103df57600080fd5b8335925060208401359150604084013567ffffffffffffffff8082111561040557600080fd5b818601915086601f83011261041957600080fd5b81358181111561042b5761042b61039b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104715761047161039b565b8160405282815289602084870101111561048a57600080fd5b826020860160208301376000602084830101528095505050505050925092509256fe608060405234801561001057600080fd5b5061012e806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063249cb3fa14602d575b600080fd5b603c603836600460b1565b604e565b60405190815260200160405180910390f35b60008281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516845290915281205460ff16608857600060aa565b7fa2ef4600d742022d532d4747cb3547474667d6f13804902513b2ec01c848f4b45b9392505050565b6000806040838503121560c357600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811460ed57600080fd5b80915050925092905056fea26469706673582212205ffd4e6cede7d06a5daf93d48d0541fc68189eeb16608c1999a82063b666eb1164736f6c63430008130033a2646970667358221220fdc4a0fe96e3b21c108ca155438d37c9143fb01278a3c1d274948bad89c564ba64736f6c63430008130033", + "balance": "0x0", + "nonce": "0x1" + }, + "cd3b766ccdd6ae721141f452c550ca635964ce71": { + "balance": "0x21e19e0c9bab2400000" + }, + "dd2fd4581271e230360230f9337d5c0430bf44c0": { + "balance": "0x21e19e0c9bab2400000" + }, + "df37f81daad2b0327a0a50003740e1c935c70913": { + "balance": "0x21e19e0c9bab2400000" + }, + "df3e18d64bc6a983f673ab319ccae4f1a57c7097": { + "balance": "0x21e19e0c9bab2400000" + }, + "efc2c1444ebcc4db75e7613d20c6a62ff67a167c": { + "code": "0x6080600436101561000f57600080fd5b6000803560e01c63570e1a361461002557600080fd5b3461018a5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261018a576004359167ffffffffffffffff9081841161018657366023850112156101865783600401358281116101825736602482870101116101825780601411610182577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec810192808411610155577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f81600b8501160116830190838210908211176101555792846024819482600c60209a968b9960405286845289840196603889018837830101525193013560601c5af1908051911561014d575b5073ffffffffffffffffffffffffffffffffffffffff60405191168152f35b90503861012e565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b8380fd5b8280fd5b80fdfea26469706673582212207adef8895ad3393b02fab10a111d85ea80ff35366aa43995f4ea20e67f29200664736f6c63430008170033", + "balance": "0x0", + "nonce": "0x1" + }, + "f39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "balance": "0x21e19e0c9bab2400000" + }, + "fabb0ac9d68b0b445fb7357272ff202c5651694a": { + "balance": "0x21e19e0c9bab2400000" + }, + "fb1bffc9d739b8d520daf37df666da4c687191ea": { + "code": "0x6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611647578063f698da2514611698578063f8dc5dd9146116c3578063ffa1ad741461173e57610231565b8063e19a9dd91461139b578063e318b52b146113ec578063e75235b81461147d578063e86637db146114a857610231565b8063cc2f8452116100d1578063cc2f8452146110e8578063d4d9bdcd146111b5578063d8d11f78146111f0578063e009cfde1461132a57610231565b8063affed0e014610d94578063b4faba0914610dbf578063b63e800d14610ea7578063c4ca3a9c1461101757610231565b80635624b25b1161017a5780636a761202116101495780636a761202146109945780637d83297414610b50578063934f3a1114610bbf578063a0e67e2b14610d2857610231565b80635624b25b146107fb5780635ae6bd37146108b9578063610b592514610908578063694e80c31461095957610231565b80632f54bf6e116101b65780632f54bf6e146104d35780633408e4701461053a578063468721a7146105655780635229073f1461067a57610231565b80630d582f131461029e57806312fb68e0146102f95780632d9ad53d1461046c57610231565b36610231573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023d57600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061027257600080f35b36600080373360601b365260008060143601600080855af13d6000803e80610299573d6000fd5b3d6000f35b3480156102aa57600080fd5b506102f7600480360360408110156102c157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506117ce565b005b34801561030557600080fd5b5061046a6004803603608081101561031c57600080fd5b81019080803590602001909291908035906020019064010000000081111561034357600080fd5b82018360208201111561035557600080fd5b8035906020019184600183028401116401000000008311171561037757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103da57600080fd5b8201836020820111156103ec57600080fd5b8035906020019184600183028401116401000000008311171561040e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611bbe565b005b34801561047857600080fd5b506104bb6004803603602081101561048f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612440565b60405180821515815260200191505060405180910390f35b3480156104df57600080fd5b50610522600480360360208110156104f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612512565b60405180821515815260200191505060405180910390f35b34801561054657600080fd5b5061054f6125e4565b6040518082815260200191505060405180910390f35b34801561057157600080fd5b506106626004803603608081101561058857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105cf57600080fd5b8201836020820111156105e157600080fd5b8035906020019184600183028401116401000000008311171561060357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506125f1565b60405180821515815260200191505060405180910390f35b34801561068657600080fd5b506107776004803603608081101561069d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106e457600080fd5b8201836020820111156106f657600080fd5b8035906020019184600183028401116401000000008311171561071857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506126fc565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107bf5780820151818401526020810190506107a4565b50505050905090810190601f1680156107ec5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561080757600080fd5b5061083e6004803603604081101561081e57600080fd5b810190808035906020019092919080359060200190929190505050612732565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561087e578082015181840152602081019050610863565b50505050905090810190601f1680156108ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108c557600080fd5b506108f2600480360360208110156108dc57600080fd5b81019080803590602001909291905050506127b9565b6040518082815260200191505060405180910390f35b34801561091457600080fd5b506109576004803603602081101561092b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506127d1565b005b34801561096557600080fd5b506109926004803603602081101561097c57600080fd5b8101908080359060200190929190505050612b63565b005b610b3860048036036101408110156109ab57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109f257600080fd5b820183602082011115610a0457600080fd5b80359060200191846001830284011164010000000083111715610a2657600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610ab257600080fd5b820183602082011115610ac457600080fd5b80359060200191846001830284011164010000000083111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612c9d565b60405180821515815260200191505060405180910390f35b348015610b5c57600080fd5b50610ba960048036036040811015610b7357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050612edc565b6040518082815260200191505060405180910390f35b348015610bcb57600080fd5b50610d2660048036036060811015610be257600080fd5b810190808035906020019092919080359060200190640100000000811115610c0957600080fd5b820183602082011115610c1b57600080fd5b80359060200191846001830284011164010000000083111715610c3d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610ca057600080fd5b820183602082011115610cb257600080fd5b80359060200191846001830284011164010000000083111715610cd457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612f01565b005b348015610d3457600080fd5b50610d3d612f90565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d80578082015181840152602081019050610d65565b505050509050019250505060405180910390f35b348015610da057600080fd5b50610da9613139565b6040518082815260200191505060405180910390f35b348015610dcb57600080fd5b50610ea560048036036040811015610de257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1f57600080fd5b820183602082011115610e3157600080fd5b80359060200191846001830284011164010000000083111715610e5357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061313f565b005b348015610eb357600080fd5b506110156004803603610100811015610ecb57600080fd5b8101908080359060200190640100000000811115610ee857600080fd5b820183602082011115610efa57600080fd5b80359060200191846020830284011164010000000083111715610f1c57600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f6757600080fd5b820183602082011115610f7957600080fd5b80359060200191846001830284011164010000000083111715610f9b57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613161565b005b34801561102357600080fd5b506110d26004803603608081101561103a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561108157600080fd5b82018360208201111561109357600080fd5b803590602001918460018302840111640100000000831117156110b557600080fd5b9091929391929390803560ff16906020019092919050505061331f565b6040518082815260200191505060405180910390f35b3480156110f457600080fd5b506111416004803603604081101561110b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613447565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156111a0578082015181840152602081019050611185565b50505050905001935050505060405180910390f35b3480156111c157600080fd5b506111ee600480360360208110156111d857600080fd5b8101908080359060200190929190505050613639565b005b3480156111fc57600080fd5b50611314600480360361014081101561121457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561125b57600080fd5b82018360208201111561126d57600080fd5b8035906020019184600183028401116401000000008311171561128f57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506137d8565b6040518082815260200191505060405180910390f35b34801561133657600080fd5b506113996004803603604081101561134d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613805565b005b3480156113a757600080fd5b506113ea600480360360208110156113be57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613b96565b005b3480156113f857600080fd5b5061147b6004803603606081101561140f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613c1a565b005b34801561148957600080fd5b5061149261428c565b6040518082815260200191505060405180910390f35b3480156114b457600080fd5b506115cc60048036036101408110156114cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561151357600080fd5b82018360208201111561152557600080fd5b8035906020019184600183028401116401000000008311171561154757600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050614296565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561160c5780820151818401526020810190506115f1565b50505050905090810190601f1680156116395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561165357600080fd5b506116966004803603602081101561166a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061443e565b005b3480156116a457600080fd5b506116ad61449f565b6040518082815260200191505060405180910390f35b3480156116cf57600080fd5b5061173c600480360360608110156116e657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061451d565b005b34801561174a57600080fd5b50611753614950565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611793578082015181840152602081019050611778565b50505050905090810190601f1680156117c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6117d6614989565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156118405750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561187857503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6118ea576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146119eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055507f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2682604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414611bba57611bb981612b63565b5b5050565b611bd2604182614a2c90919063ffffffff16565b82511015611c48576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b8681101561243457611c648882614a66565b80945081955082965050505060008460ff16141561206d578260001c9450611c96604188614a2c90919063ffffffff16565b8260001c1015611d0e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611d2760208460001c614a9590919063ffffffff16565b1115611d9b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611dd182611dc360208760001c614a9590919063ffffffff16565b614a9590919063ffffffff16565b1115611e45576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611ee7578082015181840152602081019050611ecc565b50505050905090810190601f168015611f145780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611f4d578082015181840152602081019050611f32565b50505050905090810190601f168015611f7a5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f9957600080fd5b505afa158015611fad573d6000803e3d6000fd5b505050506040513d6020811015611fc357600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612066576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b50506122b2565b60018460ff161415612181578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061210a57506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b61217c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6122b1565b601e8460ff1611156122495760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612238573d6000803e3d6000fd5b5050506020604051035194506122b0565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156122a3573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161180156123795750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123b25750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b612424576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611c52565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff161415801561250b5750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125dd5750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e3386868686604051808673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018060200183600181111561266b57fe5b8152602001828103825284818151815260200191508051906020019080838360005b838110156126a857808201518184015260208101905061268d565b50505050905090810190601f1680156126d55780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a16126f285858585614ab4565b9050949350505050565b6000606061270c868686866125f1565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff8111801561275057600080fd5b506040519080825280601f01601f1916602001820160405280156127835781602001600182028036833780820191505090505b50905060005b838110156127ae57808501548060208302602085010152508080600101915050612789565b508091505092915050565b60076020528060005260406000206000915090505481565b6127d9614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156128435750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6128b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146129b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b612b6b614989565b600354811115612be3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612c5a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000606060055433600454604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c604051808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612d5057fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018060200184810384528e8e82818152602001925080828437600081840152601f19601f820116905080830192505050848103835286818151815260200191508051906020019080838360005b83811015612e0a578082015181840152602081019050612def565b50505050905090810190601f168015612e375780820380516001836020036101000a031916815260200191505b50848103825285818151815260200191508051906020019080838360005b83811015612e70578082015181840152602081019050612e55565b50505050905090810190601f168015612e9d5780820380516001836020036101000a031916815260200191505b509f5050505050505050505050505050505060405180910390a1612eca8d8d8d8d8d8d8d8d8d8d8d614c9a565b9150509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b6000600454905060008111612f7e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b612f8a84848484611bbe565b50505050565b6060600060035467ffffffffffffffff81118015612fad57600080fd5b50604051908082528060200260200182016040528015612fdc5781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613130578083838151811061308757fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508180600101925050613046565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6131ac8a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050896151d7565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146131ea576131e9846156d7565b5b6132388787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050615706565b60008211156132525761325082600060018685615941565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b6000805a9050613376878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050865a615b47565b61337f57600080fd5b60005a8203905080604051602001808281526020019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561340c5780820151818401526020810190506133f1565b50505050905090810190601f1680156134395780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561346257600080fd5b506040519080825280602002602001820160405280156134915781602001602082028036833780820191505090505b509150600080600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156135645750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561356f57508482105b1561362a578084838151811061358157fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081806001019250506134fa565b80925081845250509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561373b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b60006137ed8c8c8c8c8c8c8c8c8c8c8c614296565b8051906020012090509b9a5050505050505050505050565b61380d614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156138775750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6138e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146139e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613b9e614989565b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181557f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa282604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613c22614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613c8c5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015613cc457503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613d36576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613e37576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614158015613ea15750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b613f13576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614013576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a17f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561432757fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b6143b361449f565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b614446614989565b61444f816156d7565b7f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6144cd6125e4565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b614525614989565b8060016003540310156145a0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561460a5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b61467c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461477c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1806004541461494b5761494a81612b63565b5b505050565b6040518060400160405280600581526020017f312e332e3000000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614a2a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614a3f5760009050614a60565b6000828402905082848281614a5057fe5b0414614a5b57600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614aaa57600080fd5b8091505092915050565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614158015614b7f5750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b614bf1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b614bfe858585855a615b47565b90508015614c4e573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a2614c92565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b6000806000614cb48e8e8e8e8e8e8e8e8e8e600554614296565b905060056000815480929190600101919050555080805190602001209150614cdd828286612f01565b506000614ce8615b93565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614614ece578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115614d8b57fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015614e5d578082015181840152602081019050614e42565b50505050905090810190601f168015614e8a5780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015614eb557600080fd5b505af1158015614ec9573d6000803e3d6000fd5b505050505b6101f4614ef56109c48b01603f60408d0281614ee657fe5b04615bc490919063ffffffff16565b015a1015614f6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a9050614fd48f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d14614fc9578e614fcf565b6109c45a035b615b47565b9350614fe95a82615bde90919063ffffffff16565b90508380614ff8575060008a14155b80615004575060008814155b615076576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808911156150905761508d828b8b8b8b615941565b90505b84156150da577f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e8482604051808381526020018281526020019250505060405180910390a161511a565b7f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d238482604051808381526020018281526020019250505060405180910390a15b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146151c6578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b1580156151ad57600080fd5b505af11580156151c1573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b60006004541461524f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b81518111156152c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600181101561533d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b835181101561564357600084828151811061535d57fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156153d15750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561540957503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561544157508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b6154b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146155b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550809250508080600101915050615346565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614615808576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161461593d576158ca8260008360015a615b47565b61593c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461597e5782615980565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415615a98576159ea3a86106159c7573a6159c9565b855b6159dc888a614a9590919063ffffffff16565b614a2c90919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050615a93576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615b3d565b615abd85615aaf888a614a9590919063ffffffff16565b614a2c90919063ffffffff16565b9150615aca848284615bfe565b615b3c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600180811115615b5557fe5b836001811115615b6157fe5b1415615b7a576000808551602087018986f49050615b8a565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b600081831015615bd45781615bd6565b825b905092915050565b600082821115615bed57600080fd5b600082840390508091505092915050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d60008114615ca55760208114615cad5760009350615cb8565b819350615cb8565b600051158215171593505b505050939250505056fea2646970667358221220047fac33099ca576d1c4f1ac6a8abdb0396e42ad6a397d2cb2f4dc1624cc0c5b64736f6c63430007060033", + "balance": "0x0", + "nonce": "0x1" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": "0x1", + "excessBlobGas": "0x0", + "blobGasUsed": "0x0" +} + diff --git a/crates/builder/op-rbuilder/src/tester/main.rs b/crates/builder/op-rbuilder/src/tester/main.rs new file mode 100644 index 00000000..6c64d5dd --- /dev/null +++ b/crates/builder/op-rbuilder/src/tester/main.rs @@ -0,0 +1,40 @@ +use clap::Parser; +use op_rbuilder::tester::*; + +/// CLI Commands +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Cli { + #[clap(subcommand)] + command: Commands, +} + +#[derive(Parser, Debug)] +enum Commands { + /// Generate genesis configuration + Genesis { + #[clap(long, help = "Output path for genesis files")] + output: Option, + }, + /// Run the testing system + Run { + #[clap(long, short, action)] + validation: bool, + + #[clap(long, short, action, default_value = "false")] + no_tx_pool: bool, + }, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::Genesis { output } => generate_genesis(output).await, + Commands::Run { + validation, + no_tx_pool, + } => run_system(validation, no_tx_pool).await, + } +} diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs new file mode 100644 index 00000000..548eebe8 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -0,0 +1,402 @@ +use alloy_eips::BlockNumberOrTag; +use alloy_primitives::B256; +use alloy_primitives::U256; +use alloy_rpc_types_engine::ExecutionPayloadV1; +use alloy_rpc_types_engine::ExecutionPayloadV2; +use alloy_rpc_types_engine::PayloadAttributes; +use alloy_rpc_types_engine::PayloadStatusEnum; +use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatus}; +use jsonrpsee::core::RpcResult; +use jsonrpsee::http_client::{transport::HttpBackend, HttpClient}; +use jsonrpsee::proc_macros::rpc; +use op_alloy_rpc_types_engine::OpPayloadAttributes; +use reth::rpc::{api::EngineApiClient, types::engine::ForkchoiceState}; +use reth_node_api::{EngineTypes, PayloadTypes}; +use reth_optimism_node::OpEngineTypes; +use reth_payload_builder::PayloadId; +use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; +use serde_json; +use serde_json::Value; +use std::str::FromStr; + +/// Helper for engine api operations +pub struct EngineApi { + pub engine_api_client: HttpClient>, +} + +/// Builder for EngineApi configuration +pub struct EngineApiBuilder { + url: String, + jwt_secret: String, +} + +impl Default for EngineApiBuilder { + fn default() -> Self { + Self::new() + } +} + +impl EngineApiBuilder { + pub fn new() -> Self { + Self { + url: String::from("http://localhost:8551"), // default value + jwt_secret: String::from( + "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a", + ), // default value + } + } + + pub fn with_url(mut self, url: &str) -> Self { + self.url = url.to_string(); + self + } + + pub fn build(self) -> Result> { + let secret_layer = AuthClientLayer::new(JwtSecret::from_str(&self.jwt_secret)?); + let middleware = tower::ServiceBuilder::default().layer(secret_layer); + let client = jsonrpsee::http_client::HttpClientBuilder::default() + .set_http_middleware(middleware) + .build(&self.url) + .expect("Failed to create http client"); + + Ok(EngineApi { + engine_api_client: client, + }) + } +} + +impl EngineApi { + pub fn builder() -> EngineApiBuilder { + EngineApiBuilder::new() + } + + pub fn new(url: &str) -> Result> { + Self::builder().with_url(url).build() + } + + pub async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> eyre::Result<::ExecutionPayloadEnvelopeV3> { + Ok( + EngineApiClient::::get_payload_v3(&self.engine_api_client, payload_id) + .await?, + ) + } + + pub async fn new_payload( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> eyre::Result { + Ok(EngineApiClient::::new_payload_v3( + &self.engine_api_client, + payload, + versioned_hashes, + parent_beacon_block_root, + ) + .await?) + } + + pub async fn update_forkchoice( + &self, + current_head: B256, + new_head: B256, + payload_attributes: Option<::PayloadAttributes>, + ) -> eyre::Result { + Ok(EngineApiClient::::fork_choice_updated_v3( + &self.engine_api_client, + ForkchoiceState { + head_block_hash: new_head, + safe_block_hash: current_head, + finalized_block_hash: current_head, + }, + payload_attributes, + ) + .await?) + } + + pub async fn latest(&self) -> eyre::Result> { + self.get_block_by_number(BlockNumberOrTag::Latest, false) + .await + } + + pub async fn get_block_by_number( + &self, + number: BlockNumberOrTag, + include_txs: bool, + ) -> eyre::Result> { + Ok( + BlockApiClient::get_block_by_number(&self.engine_api_client, number, include_txs) + .await?, + ) + } +} + +#[rpc(server, client, namespace = "eth")] +pub trait BlockApi { + #[method(name = "getBlockByNumber")] + async fn get_block_by_number( + &self, + block_number: BlockNumberOrTag, + include_txs: bool, + ) -> RpcResult>; +} + +// TODO: This is not being recognized as used code by the main function +#[allow(dead_code)] +pub async fn generate_genesis(output: Option) -> eyre::Result<()> { + // Read the template file + let template = include_str!("fixtures/genesis.json.tmpl"); + + // Parse the JSON + let mut genesis: Value = serde_json::from_str(template)?; + + // Update the timestamp field - example using current timestamp + let timestamp = chrono::Utc::now().timestamp(); + if let Some(config) = genesis.as_object_mut() { + // Assuming timestamp is at the root level - adjust path as needed + config["timestamp"] = Value::String(format!("0x{:x}", timestamp)); + } + + // Write the result to the output file + if let Some(output) = output { + std::fs::write(&output, serde_json::to_string_pretty(&genesis)?)?; + println!("Generated genesis file at: {}", output); + } else { + println!("{}", serde_json::to_string_pretty(&genesis)?); + } + + Ok(()) +} + +/// A system that continuously generates blocks using the engine API +pub struct BlockGenerator<'a> { + engine_api: &'a EngineApi, + validation_api: Option<&'a EngineApi>, + latest_hash: B256, + timestamp: u64, + no_tx_pool: bool, +} + +impl<'a> BlockGenerator<'a> { + pub fn new( + engine_api: &'a EngineApi, + validation_api: Option<&'a EngineApi>, + no_tx_pool: bool, + ) -> Self { + Self { + engine_api, + validation_api, + latest_hash: B256::ZERO, // temporary value + timestamp: 0, // temporary value + no_tx_pool, + } + } + + /// Initialize the block generator by fetching the latest block + pub async fn init(&mut self) -> eyre::Result<()> { + let latest_block = self.engine_api.latest().await?.expect("block not found"); + self.latest_hash = latest_block.header.hash; + self.timestamp = latest_block.header.timestamp; + + // Sync validation node if it exists + if let Some(validation_api) = self.validation_api { + self.sync_validation_node(validation_api).await?; + } + + Ok(()) + } + + /// Sync the validation node to the current state + async fn sync_validation_node(&self, validation_api: &EngineApi) -> eyre::Result<()> { + let latest_validation_block = validation_api.latest().await?.expect("block not found"); + let latest_block = self.engine_api.latest().await?.expect("block not found"); + + if latest_validation_block.header.number > latest_block.header.number { + return Err(eyre::eyre!("validation node is ahead of the builder")); + } + + if latest_validation_block.header.number < latest_block.header.number { + println!( + "validation node {} is behind the builder {}, syncing up", + latest_validation_block.header.number, latest_block.header.number + ); + + let mut latest_hash = latest_validation_block.header.hash; + + for i in (latest_validation_block.header.number + 1)..=latest_block.header.number { + println!("syncing block {}", i); + + let block = self + .engine_api + .get_block_by_number(BlockNumberOrTag::Number(i), true) + .await? + .expect("block not found"); + + if block.header.parent_hash != latest_hash { + return Err(eyre::eyre!("unexpected parent hash during sync")); + } + + let payload_request = ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: block.header.parent_hash, + fee_recipient: block.header.beneficiary, + state_root: block.header.state_root, + receipts_root: block.header.receipts_root, + logs_bloom: block.header.logs_bloom, + prev_randao: B256::ZERO, + block_number: block.header.number, + gas_limit: block.header.gas_limit, + gas_used: block.header.gas_used, + timestamp: block.header.timestamp, + extra_data: block.header.extra_data.clone(), + base_fee_per_gas: U256::from(block.header.base_fee_per_gas.unwrap()), + block_hash: block.header.hash, + transactions: vec![], // there are no txns yet + }, + withdrawals: block.withdrawals.unwrap().to_vec(), + }, + blob_gas_used: block.header.inner.blob_gas_used.unwrap(), + excess_blob_gas: block.header.inner.excess_blob_gas.unwrap(), + }; + + let validation_status = validation_api + .new_payload(payload_request, vec![], B256::ZERO) + .await?; + + if validation_status.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!("invalid payload status during sync")); + } + + let new_chain_hash = validation_status + .latest_valid_hash + .ok_or_else(|| eyre::eyre!("missing latest valid hash"))?; + + if new_chain_hash != block.header.hash { + return Err(eyre::eyre!("hash mismatch during sync")); + } + + validation_api + .update_forkchoice(latest_hash, new_chain_hash, None) + .await?; + + latest_hash = new_chain_hash; + } + } + + Ok(()) + } + + /// Generate a single new block and return its hash + pub async fn generate_block(&mut self) -> eyre::Result { + // Request new block generation + let result = self + .engine_api + .update_forkchoice( + self.latest_hash, + self.latest_hash, + Some(OpPayloadAttributes { + payload_attributes: PayloadAttributes { + withdrawals: Some(vec![]), + parent_beacon_block_root: Some(B256::ZERO), + timestamp: self.timestamp + 1000, + prev_randao: B256::ZERO, + suggested_fee_recipient: Default::default(), + }, + transactions: None, + no_tx_pool: Some(self.no_tx_pool), + gas_limit: Some(10000000000), + eip_1559_params: None, + }), + ) + .await?; + + if result.payload_status.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!("Invalid payload status")); + } + + let payload_id = result.payload_id.unwrap(); + + if !self.no_tx_pool { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + + let payload = self.engine_api.get_payload_v3(payload_id).await?; + + // Validate with builder node + let validation_status = self + .engine_api + .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) + .await?; + + if validation_status.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!("Invalid validation status from builder")); + } + + // Validate with validation node if present + if let Some(validation_api) = self.validation_api { + let validation_status = validation_api + .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) + .await?; + + if validation_status.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!("Invalid validation status from validator")); + } + } + + let new_block_hash = payload + .execution_payload + .payload_inner + .payload_inner + .block_hash; + + // Update forkchoice on builder + self.engine_api + .update_forkchoice(self.latest_hash, new_block_hash, None) + .await?; + + // Update forkchoice on validator if present + if let Some(validation_api) = self.validation_api { + validation_api + .update_forkchoice(self.latest_hash, new_block_hash, None) + .await?; + } + + // Update internal state + self.latest_hash = new_block_hash; + self.timestamp = payload + .execution_payload + .payload_inner + .payload_inner + .timestamp; + + Ok(new_block_hash) + } +} + +// TODO: This is not being recognized as used code by the main function +#[allow(dead_code)] +pub async fn run_system(validation: bool, no_tx_pool: bool) -> eyre::Result<()> { + println!("Validation: {}", validation); + + let engine_api = EngineApi::new("http://localhost:4444").unwrap(); + let validation_api = if validation { + Some(EngineApi::new("http://localhost:5555").unwrap()) + } else { + None + }; + + let mut generator = BlockGenerator::new(&engine_api, validation_api.as_ref(), no_tx_pool); + + generator.init().await?; + + // Infinite loop generating blocks + loop { + println!("Generating new block..."); + let block_hash = generator.generate_block().await?; + println!("Generated block: {}", block_hash); + } +} diff --git a/crates/builder/op-rbuilder/src/tester/tester.rs b/crates/builder/op-rbuilder/src/tester/tester.rs deleted file mode 100644 index 9ebd013f..00000000 --- a/crates/builder/op-rbuilder/src/tester/tester.rs +++ /dev/null @@ -1,320 +0,0 @@ -use alloy_eips::BlockNumberOrTag; -use alloy_network::Ethereum; -use alloy_primitives::B256; -use alloy_primitives::U256; -use alloy_provider::Provider; -use alloy_provider::RootProvider; -use alloy_rpc_types_engine::ExecutionPayloadV1; -use alloy_rpc_types_engine::ExecutionPayloadV2; -use alloy_rpc_types_engine::PayloadAttributes; -use alloy_rpc_types_engine::PayloadStatusEnum; -use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatus}; -use alloy_transport::BoxTransport; -use clap::Parser; -use jsonrpsee::http_client::{transport::HttpBackend, HttpClient}; -use op_alloy_rpc_types_engine::OpPayloadAttributes; -use reth::{ - api::EngineTypes, - rpc::{ - api::EngineApiClient, - types::{engine::ForkchoiceState, BlockTransactionsKind}, - }, -}; -use reth_optimism_node::OpEngineTypes; -use reth_payload_builder::PayloadId; -use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; -use std::{marker::PhantomData, str::FromStr}; - -/// Helper for engine api operations -pub struct EngineApi { - url: String, - pub engine_api_client: HttpClient>, - pub _marker: PhantomData, -} - -pub type BoxedProvider = RootProvider; - -impl EngineApi { - pub fn new(url: &str, non_auth_url: &str) -> Result> { - let secret_layer = AuthClientLayer::new(JwtSecret::from_str( - "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a", - )?); - let middleware = tower::ServiceBuilder::default().layer(secret_layer); - let client = jsonrpsee::http_client::HttpClientBuilder::default() - .set_http_middleware(middleware) - .build(url) - .expect("Failed to create http client"); - - Ok(Self { - url: non_auth_url.to_string(), - engine_api_client: client, - _marker: PhantomData, - }) - } -} - -impl EngineApi { - /// Retrieves a v3 payload from the engine api - pub async fn get_payload_v3( - &self, - payload_id: PayloadId, - ) -> eyre::Result { - Ok(EngineApiClient::::get_payload_v3(&self.engine_api_client, payload_id).await?) - } - - pub async fn new_payload( - &self, - payload: ExecutionPayloadV3, - versioned_hashes: Vec, - parent_beacon_block_root: B256, - ) -> eyre::Result { - Ok(EngineApiClient::::new_payload_v3( - &self.engine_api_client, - payload, - versioned_hashes, - parent_beacon_block_root, - ) - .await?) - } - - /// Sends forkchoice update to the engine api - pub async fn update_forkchoice( - &self, - current_head: B256, - new_head: B256, - payload_attributes: Option, - ) -> eyre::Result { - Ok(EngineApiClient::::fork_choice_updated_v3( - &self.engine_api_client, - ForkchoiceState { - head_block_hash: new_head, - safe_block_hash: current_head, - finalized_block_hash: current_head, - }, - payload_attributes, - ) - .await?) - } - - pub async fn latest(&self) -> alloy_rpc_types_eth::Block { - self.get_block_by_number(BlockNumberOrTag::Latest, BlockTransactionsKind::Hashes) - .await - } - - pub async fn get_block_by_number( - &self, - number: BlockNumberOrTag, - kind: BlockTransactionsKind, - ) -> alloy_rpc_types_eth::Block { - // TODO: Do not know how to use the other auth provider for this rpc call - let provider: BoxedProvider = RootProvider::new_http(self.url.parse().unwrap()).boxed(); - provider - .get_block_by_number(number, kind) - .await - .unwrap() - .unwrap() - } -} - -/// This is a simple program -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - #[clap(long, short, action)] - validation: bool, - - #[clap(long, short, action, default_value = "false")] - no_tx_pool: bool, -} - -#[tokio::main] -async fn main() { - let args = Args::parse(); - println!("Validation: {}", args.validation); - - let engine_api = - EngineApi::::new("http://localhost:4444", "http://localhost:1111").unwrap(); - - let validation_node_api = if args.validation { - Some( - EngineApi::::new("http://localhost:5555", "http://localhost:2222") - .unwrap(), - ) - } else { - None - }; - - let latest_block = engine_api.latest().await; - - // latest hash and timestamp - let mut latest = latest_block.header.hash; - let mut timestamp = latest_block.header.timestamp; - - if let Some(validation_node_api) = &validation_node_api { - // if the validation node is behind the builder, try to sync it using the engine api - let latest_validation_block = validation_node_api.latest().await; - - if latest_validation_block.header.number > latest_block.header.number { - panic!("validation node is ahead of the builder") - } - if latest_validation_block.header.number < latest_block.header.number { - println!( - "validation node {} is behind the builder {}, syncing up", - latest_validation_block.header.number, latest_block.header.number - ); - - let mut latest_hash = latest_validation_block.header.hash; - - // sync them up using fcu requests - for i in (latest_validation_block.header.number + 1)..=latest_block.header.number { - println!("syncing block {}", i); - - let block = engine_api - .get_block_by_number(BlockNumberOrTag::Number(i), BlockTransactionsKind::Full) - .await; - - if block.header.parent_hash != latest_hash { - panic!("not expected") - } - - let payload_request = ExecutionPayloadV3 { - payload_inner: ExecutionPayloadV2 { - payload_inner: ExecutionPayloadV1 { - parent_hash: block.header.parent_hash, - fee_recipient: block.header.beneficiary, - state_root: block.header.state_root, - receipts_root: block.header.receipts_root, - logs_bloom: block.header.logs_bloom, - prev_randao: B256::ZERO, - block_number: block.header.number, - gas_limit: block.header.gas_limit, - gas_used: block.header.gas_used, - timestamp: block.header.timestamp, - extra_data: block.header.extra_data.clone(), - base_fee_per_gas: U256::from(block.header.base_fee_per_gas.unwrap()), - block_hash: block.header.hash, - transactions: vec![], // there are no txns yet - }, - withdrawals: block.withdrawals.unwrap().to_vec(), - }, - blob_gas_used: block.header.inner.blob_gas_used.unwrap(), - excess_blob_gas: block.header.inner.excess_blob_gas.unwrap(), - }; - - let validation_payload = validation_node_api - .new_payload(payload_request, vec![], B256::ZERO) - .await - .unwrap(); - - if validation_payload.status != PayloadStatusEnum::Valid { - panic!("not expected") - } - - let new_chain_hash = validation_payload.latest_valid_hash.unwrap(); - if new_chain_hash != block.header.hash { - println!("synced block {:?}", new_chain_hash); - println!("expected block {:?}", block.header.hash); - - panic!("stop") - } - - // send an fcu request to add to the canonical chain - let _result = validation_node_api - .update_forkchoice(latest_hash, new_chain_hash, None) - .await - .unwrap(); - - latest_hash = new_chain_hash; - } - } - } - - loop { - println!("latest block: {}", latest); - - let result = engine_api - .update_forkchoice( - latest, - latest, - Some(OpPayloadAttributes { - payload_attributes: PayloadAttributes { - withdrawals: Some(vec![]), - parent_beacon_block_root: Some(B256::ZERO), - timestamp: timestamp + 1000, - prev_randao: B256::ZERO, - suggested_fee_recipient: Default::default(), - }, - transactions: None, - no_tx_pool: Some(args.no_tx_pool), - gas_limit: Some(10000000000), - eip_1559_params: None, - }), - ) - .await - .unwrap(); - - if result.payload_status.status != PayloadStatusEnum::Valid { - panic!("not expected") - } - - let payload_id = result.payload_id.unwrap(); - - // Only wait for the block time to request the payload if the block builder - // is expected to use the txpool (this is, no_tx_pool is false) - if !args.no_tx_pool { - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - } - - // query the result, do nothing, just checks that we can get it back - let payload = engine_api.get_payload_v3(payload_id).await.unwrap(); - - // Send a new_payload request to the builder node again. THIS MUST BE DONE - let validation_status = engine_api - .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) - .await - .unwrap(); - if validation_status.status != PayloadStatusEnum::Valid { - panic!("not expected") - } - - if let Some(validation_node_api) = &validation_node_api { - // Validate the payload with the validation node - let validation_status = validation_node_api - .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) - .await - .unwrap(); - - if validation_status.status != PayloadStatusEnum::Valid { - panic!("not expected") - } - } - - let new_block_hash = payload - .execution_payload - .payload_inner - .payload_inner - .block_hash; - let new_timestamp = payload - .execution_payload - .payload_inner - .payload_inner - .timestamp; - - // send an FCU without payload attributes to lock in the block for the builder - let _result = engine_api - .update_forkchoice(latest, new_block_hash, None) - .await - .unwrap(); - - if let Some(validation_node_api) = &validation_node_api { - // do the same for the validator - let _result = validation_node_api - .update_forkchoice(latest, new_block_hash, None) - .await - .unwrap(); - } - - latest = new_block_hash; - timestamp = new_timestamp; - } -} From 1b304d53c695f8c19e41ecae0e0a7fc8be92f10a Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 30 Jan 2025 04:24:10 +1100 Subject: [PATCH 019/262] Add op-rbuilder README (#383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Add readme to op-rbuilder. Details: - how to build, run - builder monitoring and metrics - integration tests and how to run - running a devnet TODO: kurtosis section for the local devnet when op-rbuilder is integrated. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/README.md | 110 +++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 crates/builder/op-rbuilder/README.md diff --git a/crates/builder/op-rbuilder/README.md b/crates/builder/op-rbuilder/README.md new file mode 100644 index 00000000..a7e0606b --- /dev/null +++ b/crates/builder/op-rbuilder/README.md @@ -0,0 +1,110 @@ +# op-rbuilder + +[![CI status](https://github.com/flashbots/rbuilder/actions/workflows/checks.yaml/badge.svg?branch=develop)](https://github.com/flashbots/rbuilder/actions/workflows/integration.yaml) + + +`op-rbuilder` is a Rust-based block builder designed to build blocks for the Optimism stack. + +## Running op-rbuilder + +To run op-rbuilder with the op-stack, you need: +- CL node to sync the op-rbuilder with the canonical chain +- Sequencer with the [rollup-boost](https://github.com/flashbots/rollup-boost) setup + +To run the op-rbuilder, run: + +```bash +cargo run -p op-rbuilder --bin op-rbuilder --features optimism -- node \ + --chain /path/to/chain-config.json \ + --http \ + --authrpc.port 9551 \ + --authrpc.jwtsecret /path/to/jwt.hex +``` + +To build the op-rbuilder, run: + +```bash +cargo build -p op-rbuilder --bin op-rbuilder --features optimism +``` + +## Observability + +To verify whether a builder block has landed on-chain, you can add the `--rollup.builder-secret-key` flag or `BUILDER_SECRET_KEY` environment variable. +This will add an additional transaction to the end of the block from the builder key. The transaction will have `Block Number: {}` in the input data as a transfer to the zero address. Ensure that the key has sufficient balance to pay for the transaction at the end of the block. + +To enable metrics, set the `--metrics` flag like in [reth](https://reth.rs/run/observability.html) which will expose reth metrics in addition to op-rbuilder metrics. op-rbuilder exposes on-chain metrics via [reth execution extensions](https://reth.rs/developers/exex/exex.html) such as the number of blocks landed and builder balance. Note that the accuracy of the on-chain metrics will be dependent on the sync status of the builder node. There are also additional block building metrics such as: + +- Block building latency +- State root calculation latency +- Transaction fetch latency +- Transaction simulation latency +- Number of transactions included in the built block + +To see the full list of op-rbuilder metrics, see [`src/metrics.rs`](./src/metrics.rs). + +## Integration Testing + +op-rbuilder has an integration test framework that runs the builder against mock engine api payloads and ensures that the builder produces valid blocks. + +To run the integration tests, run: + +```bash +# Generate a genesis file +cargo run -p op-rbuilder --bin tester --features optimism -- genesis --output genesis.json + +# Build the op-rbuilder binary +cargo build -p op-rbuilder --bin op-rbuilder --features optimism + +# Run the integration tests +cargo run -p op-rbuilder --bin tester --features optimism -- run +``` + +## Local Devnet + +To run a local devnet, you can use the optimism docker compose tool and send test transactions with `mev-flood`. + +1. Clone [flashbots/optimism](https://github.com/flashbots/optimism) and checkout the `op-rbuilder` branch. + +```bash +git clone https://github.com/flashbots/optimism.git +cd optimism +git checkout op-rbuilder +``` + +2. Remove any existing `reth` chain db. The following are the default data directories: + +- Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` +- Windows: `{FOLDERID_RoamingAppData}/reth/` +- macOS: `$HOME/Library/Application Support/reth/` + +3. Run a clean OP stack in the `optimism` repo: + +```bash +make devnet-clean && make devnet-down && make devnet-up +``` + +4. Run `op-rbuilder` in the `rbuilder` repo on port 8547: + +```bash +cargo run -p op-rbuilder --bin op-rbuilder --features optimism -- node \ + --chain ../optimism/.devnet/genesis-l2.json \ + --http \ + --http.port 8547 \ + --authrpc.jwtsecret ../optimism/ops-bedrock/test-jwt-secret.txt \ + --metrics 127.0.0.1:9001 \ + --rollup.builder-secret-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +``` + +5. Init `mev-flood`: + +```bash +docker run mevflood init -r http://host.docker.internal:8547 -s local.json +``` + +6. Run `mev-flood`: + +```bash +docker run --init -v ${PWD}:/app/cli/deployments mevflood spam -p 3 -t 5 -r http://host.docker.internal:8547 -l local.json +``` + +And you should start to see blocks being built and landed on-chain with `mev-flood` transactions. From 0f4d085af6bcc5d75f40aef427929741e82aa6e3 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 30 Jan 2025 07:36:35 +0000 Subject: [PATCH 020/262] Add deposit command for op-rbuilder tester (#384) This PR adds a new `deposit` command to the op-rbuilder `tester` CLI tool that allows seeding accounts with ETH using deposit transactions. ## Changes - Added new `Deposit` command to the CLI that takes an address and amount - Reused existing `BlockGenerator` infrastructure to submit deposit transactions - Uses default engine API configuration for simplicity ## Usage To deposit 1 ETH to an account (once the op-rbuilder is running): ``` $ cargo run -- deposit --address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92200 --amount 1000000000000000000 ``` --- crates/builder/op-rbuilder/src/lib.rs | 1 + crates/builder/op-rbuilder/src/tester/main.rs | 18 ++++++++ crates/builder/op-rbuilder/src/tester/mod.rs | 43 +++++++++++++++++-- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index 238758c1..aee80672 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,2 +1,3 @@ pub mod integration; pub mod tester; +pub mod tx_signer; diff --git a/crates/builder/op-rbuilder/src/tester/main.rs b/crates/builder/op-rbuilder/src/tester/main.rs index 6c64d5dd..bb16d3b8 100644 --- a/crates/builder/op-rbuilder/src/tester/main.rs +++ b/crates/builder/op-rbuilder/src/tester/main.rs @@ -1,3 +1,4 @@ +use alloy_primitives::Address; use clap::Parser; use op_rbuilder::tester::*; @@ -24,6 +25,13 @@ enum Commands { #[clap(long, short, action, default_value = "false")] no_tx_pool: bool, }, + /// Deposit funds to the system + Deposit { + #[clap(long, help = "Address to deposit funds to")] + address: Address, + #[clap(long, help = "Amount to deposit")] + amount: u128, + }, } #[tokio::main] @@ -36,5 +44,15 @@ async fn main() -> eyre::Result<()> { validation, no_tx_pool, } => run_system(validation, no_tx_pool).await, + Commands::Deposit { address, amount } => { + let engine_api = EngineApi::builder().build().unwrap(); + let mut generator = BlockGenerator::new(&engine_api, None, false); + + generator.init().await?; + + let block_hash = generator.deposit(address, amount).await?; + println!("Deposit transaction included in block: {}", block_hash); + Ok(()) + } } } diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 548eebe8..80b8274d 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -1,4 +1,10 @@ +use crate::tx_signer::Signer; +use alloy_eips::eip2718::Encodable2718; use alloy_eips::BlockNumberOrTag; +use alloy_primitives::address; +use alloy_primitives::Address; +use alloy_primitives::Bytes; +use alloy_primitives::TxKind; use alloy_primitives::B256; use alloy_primitives::U256; use alloy_rpc_types_engine::ExecutionPayloadV1; @@ -9,6 +15,8 @@ use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatu use jsonrpsee::core::RpcResult; use jsonrpsee::http_client::{transport::HttpBackend, HttpClient}; use jsonrpsee::proc_macros::rpc; +use op_alloy_consensus::OpTypedTransaction; +use op_alloy_consensus::TxDeposit; use op_alloy_rpc_types_engine::OpPayloadAttributes; use reth::rpc::{api::EngineApiClient, types::engine::ForkchoiceState}; use reth_node_api::{EngineTypes, PayloadTypes}; @@ -290,9 +298,8 @@ impl<'a> BlockGenerator<'a> { Ok(()) } - /// Generate a single new block and return its hash - pub async fn generate_block(&mut self) -> eyre::Result { - // Request new block generation + /// Helper function to submit a payload and update chain state + async fn submit_payload(&mut self, transactions: Option>) -> eyre::Result { let result = self .engine_api .update_forkchoice( @@ -306,7 +313,7 @@ impl<'a> BlockGenerator<'a> { prev_randao: B256::ZERO, suggested_fee_recipient: Default::default(), }, - transactions: None, + transactions, no_tx_pool: Some(self.no_tx_pool), gas_limit: Some(10000000000), eip_1559_params: None, @@ -375,6 +382,34 @@ impl<'a> BlockGenerator<'a> { Ok(new_block_hash) } + + /// Generate a single new block and return its hash + pub async fn generate_block(&mut self) -> eyre::Result { + self.submit_payload(None).await + } + + /// Submit a deposit transaction to seed an account with ETH + #[allow(dead_code)] + pub async fn deposit(&mut self, to: Address, value: u128) -> eyre::Result { + // Create deposit transaction + let deposit_tx = TxDeposit { + source_hash: B256::default(), + from: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92200"), // Standard deposit source + to: TxKind::Call(to), // Recipient address + mint: Some(value), // Amount to deposit + value: U256::default(), + gas_limit: 210000, + is_system_transaction: true, + input: Bytes::default(), + }; + + // Create a temporary signer for the deposit + let signer = Signer::random(); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; + let signed_tx_rlp = signed_tx.encoded_2718(); + + self.submit_payload(Some(vec![signed_tx_rlp.into()])).await + } } // TODO: This is not being recognized as used code by the main function From 4e4b8f3fa1770ed917c5bbc294f594eb4fa385fd Mon Sep 17 00:00:00 2001 From: File Large Date: Thu, 30 Jan 2025 20:46:19 +0100 Subject: [PATCH 021/262] chore: workspace wide package settings (#390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary ## 💡 Motivation and Context Configure workspace wide package settings. --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 4edf8c8f..d9fa1fd0 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "op-rbuilder" -version = "0.1.0" -edition = "2021" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] reth.workspace = true From 50b60c6486247cc613885e3b14a02b6919ae61ad Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 10 Feb 2025 10:48:33 +0000 Subject: [PATCH 022/262] Add tests to paylaod generator (#409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR adds a simple test to the payload generator to test that the deadline works as expected. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 2 + crates/builder/op-rbuilder/src/generator.rs | 162 +++++++++++++++++++- 2 files changed, 158 insertions(+), 6 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index d9fa1fd0..d4978dc4 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -37,6 +37,7 @@ reth-rpc-layer.workspace = true reth-payload-builder-primitives.workspace = true reth-payload-util.workspace = true reth-transaction-pool.workspace = true +reth-testing-utils.workspace = true reth-optimism-forks.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true @@ -75,6 +76,7 @@ serde_json.workspace = true time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } +rand = "0.8.5" [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index e6bc144a..4ba2ea11 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -104,7 +104,7 @@ where >::Attributes: Unpin + Clone, >::BuiltPayload: Unpin + Clone, { - type Job = EmptyBlockPayloadJob; + type Job = BlockPayloadJob; /// This is invoked when the node receives payload attributes from the beacon node via /// `engine_forkchoiceUpdatedV1` @@ -128,7 +128,7 @@ where let deadline = Box::pin(tokio::time::sleep(Duration::from_secs(2))); // Or another appropriate timeout let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); - let mut job = EmptyBlockPayloadJob { + let mut job = BlockPayloadJob { client: self.client.clone(), pool: self.pool.clone(), executor: self.executor.clone(), @@ -152,7 +152,7 @@ use std::{ }; /// A [PayloadJob] that builds empty blocks. -pub struct EmptyBlockPayloadJob +pub struct BlockPayloadJob where Builder: PayloadBuilder, { @@ -176,7 +176,7 @@ where pub(crate) build_complete: Option>>, } -impl PayloadJob for EmptyBlockPayloadJob +impl PayloadJob for BlockPayloadJob where Client: StateProviderFactory + Clone + Unpin + 'static, Pool: TransactionPool + Unpin + 'static, @@ -212,7 +212,7 @@ where } /// A [PayloadJob] is a future that's being polled by the `PayloadBuilderService` -impl EmptyBlockPayloadJob +impl BlockPayloadJob where Client: StateProviderFactory + Clone + Unpin + 'static, Pool: TransactionPool + Unpin + 'static, @@ -251,7 +251,7 @@ where } /// A [PayloadJob] is a a future that's being polled by the `PayloadBuilderService` -impl Future for EmptyBlockPayloadJob +impl Future for BlockPayloadJob where Client: StateProviderFactory + Clone + Unpin + 'static, Pool: TransactionPool + Unpin + 'static, @@ -369,6 +369,18 @@ impl Default for BlockCell { #[cfg(test)] mod tests { use super::*; + use alloy_eips::eip7685::Requests; + use alloy_primitives::U256; + use rand::thread_rng; + use reth::tasks::TokioTaskExecutor; + use reth_chain_state::ExecutedBlock; + use reth_node_api::NodePrimitives; + use reth_optimism_payload_builder::payload::OpPayloadBuilderAttributes; + use reth_optimism_primitives::OpPrimitives; + use reth_primitives::SealedBlockFor; + use reth_provider::test_utils::MockEthProvider; + use reth_testing_utils::generators::{random_block_range, BlockRangeParams}; + use reth_transaction_pool::noop::NoopTransactionPool; use tokio::task; use tokio::time::{sleep, Duration}; @@ -438,4 +450,142 @@ mod tests { let result = cell.wait_for_value().await; assert_eq!(result, 43); } + + #[derive(Debug, Clone)] + struct MockBuilder { + events: Arc>>, + } + + impl MockBuilder { + fn new() -> Self { + Self { + events: Arc::new(Mutex::new(vec![])), + } + } + + fn new_event(&self, event: BlockEvent) { + let mut events = self.events.lock().unwrap(); + events.push(event); + } + + fn get_events(&self) -> Vec { + let mut events = self.events.lock().unwrap(); + std::mem::take(&mut *events) + } + } + + #[derive(Clone, Debug, Default)] + struct MockPayload; + + impl BuiltPayload for MockPayload { + type Primitives = OpPrimitives; + + fn block(&self) -> &SealedBlockFor<::Block> { + unimplemented!() + } + + /// Returns the fees collected for the built block + fn fees(&self) -> U256 { + unimplemented!() + } + + /// Returns the entire execution data for the built block, if available. + fn executed_block(&self) -> Option> { + None + } + + /// Returns the EIP-7865 requests for the payload if any. + fn requests(&self) -> Option { + unimplemented!() + } + } + + #[derive(Debug, PartialEq, Clone)] + enum BlockEvent { + Started, + Cancelled, + } + + impl PayloadBuilder for MockBuilder { + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = MockPayload; + + fn try_build( + &self, + args: BuildArguments, + _best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + self.new_event(BlockEvent::Started); + + loop { + if args.cancel.is_cancelled() { + self.new_event(BlockEvent::Cancelled); + return Ok(()); + } + + // Small sleep to prevent tight loop + std::thread::sleep(Duration::from_millis(10)); + } + } + } + + #[tokio::test] + async fn test_payload_generator() -> eyre::Result<()> { + let mut rng = thread_rng(); + + let pool = NoopTransactionPool::default(); + let client = MockEthProvider::default(); + let executor = TokioTaskExecutor::default(); + let config = BasicPayloadJobGeneratorConfig::default(); + let builder = MockBuilder::new(); + + let (start, count) = (1, 10); + let blocks = random_block_range( + &mut rng, + start..=start + count - 1, + BlockRangeParams { + tx_count: 0..2, + ..Default::default() + }, + ); + + client.extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.unseal()))); + + let generator = BlockPayloadJobGenerator::with_builder( + client.clone(), + pool, + executor, + config, + builder.clone(), + ); + + // this is not nice but necessary + let mut attr = OpPayloadBuilderAttributes::default(); + attr.payload_attributes.parent = client.latest_header()?.unwrap().hash(); + + { + let job = generator.new_payload_job(attr.clone())?; + let _ = job.await; + + // you need to give one second for the job to be dropped and cancelled the internal job + tokio::time::sleep(Duration::from_secs(1)).await; + + let events = builder.get_events(); + assert_eq!(events, vec![BlockEvent::Started, BlockEvent::Cancelled]); + } + + { + // job resolve triggers cancellations from the build task + let mut job = generator.new_payload_job(attr.clone())?; + let _ = job.resolve(); + let _ = job.await; + + tokio::time::sleep(Duration::from_secs(1)).await; + + let events = builder.get_events(); + assert_eq!(events, vec![BlockEvent::Started, BlockEvent::Cancelled]); + } + + Ok(()) + } } From 756a5403584c436219e9a5dc95f648b984c5a721 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 10 Feb 2025 18:20:24 +0000 Subject: [PATCH 023/262] Use payload attributes timestamp to calculate op payload deadline (#416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR removes the hardcoded deadline for the Op payload builder of 2 seconds and uses the payload attributes timestmap to calculate it. Closes #413 ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/generator.rs | 52 ++++++++++++++++++- .../src/integration/integration_test.rs | 2 +- crates/builder/op-rbuilder/src/tester/main.rs | 8 ++- crates/builder/op-rbuilder/src/tester/mod.rs | 40 +++++++++----- 4 files changed, 85 insertions(+), 17 deletions(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 4ba2ea11..11f02e8c 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -12,6 +12,8 @@ use reth_payload_builder::PayloadJobGenerator; use reth_payload_builder::{KeepPayloadJobAlive, PayloadBuilderError, PayloadJob}; use reth_payload_primitives::BuiltPayload; use std::sync::{Arc, Mutex}; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; use tokio::sync::oneshot; use tokio::sync::Notify; use tokio::time::Duration; @@ -125,7 +127,16 @@ where info!("Spawn block building job"); - let deadline = Box::pin(tokio::time::sleep(Duration::from_secs(2))); // Or another appropriate timeout + // 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. + let deadline = job_deadline(attributes.timestamp()) + Duration::from_millis(500); + + let deadline = Box::pin(tokio::time::sleep(deadline)); let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); let mut job = BlockPayloadJob { @@ -366,6 +377,23 @@ impl Default for BlockCell { } } +fn job_deadline(unix_timestamp_secs: u64) -> std::time::Duration { + let unix_now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + // Safe subtraction that handles the case where timestamp is in the past + let duration_until = unix_timestamp_secs.saturating_sub(unix_now); + + if duration_until == 0 { + // Enforce a minimum block time of 1 second by rounding up any duration less than 1 second + Duration::from_secs(1) + } else { + Duration::from_secs(duration_until) + } +} + #[cfg(test)] mod tests { use super::*; @@ -529,6 +557,28 @@ mod tests { } } + #[tokio::test] + async fn test_job_deadline() { + // Test future deadline + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let future_timestamp = now + Duration::from_secs(2); + // 2 seconds from now + let deadline = job_deadline(future_timestamp.as_secs()); + assert!(deadline <= Duration::from_secs(2)); + assert!(deadline > Duration::from_secs(0)); + + // Test past deadline + let past_timestamp = now - Duration::from_secs(10); + let deadline = job_deadline(past_timestamp.as_secs()); + // Should default to 1 second when timestamp is in the past + assert_eq!(deadline, Duration::from_secs(1)); + + // Test current timestamp + let deadline = job_deadline(now.as_secs()); + // Should use 1 second when timestamp is current + assert_eq!(deadline, Duration::from_secs(1)); + } + #[tokio::test] async fn test_payload_generator() -> eyre::Result<()> { let mut rng = thread_rng(); diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 76321108..c334e575 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -29,7 +29,7 @@ mod tests { framework.start("op-rbuilder", &reth).await.unwrap(); let engine_api = EngineApi::new("http://localhost:1234").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, None, false); + let mut generator = BlockGenerator::new(&engine_api, None, false, 1); generator.init().await.unwrap(); for _ in 0..10 { diff --git a/crates/builder/op-rbuilder/src/tester/main.rs b/crates/builder/op-rbuilder/src/tester/main.rs index bb16d3b8..c40aa173 100644 --- a/crates/builder/op-rbuilder/src/tester/main.rs +++ b/crates/builder/op-rbuilder/src/tester/main.rs @@ -24,6 +24,9 @@ enum Commands { #[clap(long, short, action, default_value = "false")] no_tx_pool: bool, + + #[clap(long, short, action, default_value = "1")] + block_time_secs: u64, }, /// Deposit funds to the system Deposit { @@ -43,10 +46,11 @@ async fn main() -> eyre::Result<()> { Commands::Run { validation, no_tx_pool, - } => run_system(validation, no_tx_pool).await, + block_time_secs, + } => run_system(validation, no_tx_pool, block_time_secs).await, Commands::Deposit { address, amount } => { let engine_api = EngineApi::builder().build().unwrap(); - let mut generator = BlockGenerator::new(&engine_api, None, false); + let mut generator = BlockGenerator::new(&engine_api, None, false, 1); generator.init().await?; diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 80b8274d..d5d54c3b 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -26,6 +26,8 @@ use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; use serde_json; use serde_json::Value; use std::str::FromStr; +use std::time::SystemTime; +use std::time::UNIX_EPOCH; /// Helper for engine api operations pub struct EngineApi { @@ -184,8 +186,8 @@ pub struct BlockGenerator<'a> { engine_api: &'a EngineApi, validation_api: Option<&'a EngineApi>, latest_hash: B256, - timestamp: u64, no_tx_pool: bool, + block_time_secs: u64, } impl<'a> BlockGenerator<'a> { @@ -193,13 +195,14 @@ impl<'a> BlockGenerator<'a> { engine_api: &'a EngineApi, validation_api: Option<&'a EngineApi>, no_tx_pool: bool, + block_time_secs: u64, ) -> Self { Self { engine_api, validation_api, latest_hash: B256::ZERO, // temporary value - timestamp: 0, // temporary value no_tx_pool, + block_time_secs, } } @@ -207,7 +210,6 @@ impl<'a> BlockGenerator<'a> { pub async fn init(&mut self) -> eyre::Result<()> { let latest_block = self.engine_api.latest().await?.expect("block not found"); self.latest_hash = latest_block.header.hash; - self.timestamp = latest_block.header.timestamp; // Sync validation node if it exists if let Some(validation_api) = self.validation_api { @@ -300,6 +302,15 @@ impl<'a> BlockGenerator<'a> { /// Helper function to submit a payload and update chain state async fn submit_payload(&mut self, transactions: Option>) -> eyre::Result { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + println!("now: {}", timestamp); + let timestamp = timestamp + self.block_time_secs; + + println!("timestamp: {}", timestamp); + let result = self .engine_api .update_forkchoice( @@ -309,7 +320,7 @@ impl<'a> BlockGenerator<'a> { payload_attributes: PayloadAttributes { withdrawals: Some(vec![]), parent_beacon_block_root: Some(B256::ZERO), - timestamp: self.timestamp + 1000, + timestamp, prev_randao: B256::ZERO, suggested_fee_recipient: Default::default(), }, @@ -328,7 +339,7 @@ impl<'a> BlockGenerator<'a> { let payload_id = result.payload_id.unwrap(); if !self.no_tx_pool { - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + tokio::time::sleep(tokio::time::Duration::from_secs(self.block_time_secs)).await; } let payload = self.engine_api.get_payload_v3(payload_id).await?; @@ -374,12 +385,6 @@ impl<'a> BlockGenerator<'a> { // Update internal state self.latest_hash = new_block_hash; - self.timestamp = payload - .execution_payload - .payload_inner - .payload_inner - .timestamp; - Ok(new_block_hash) } @@ -414,7 +419,11 @@ impl<'a> BlockGenerator<'a> { // TODO: This is not being recognized as used code by the main function #[allow(dead_code)] -pub async fn run_system(validation: bool, no_tx_pool: bool) -> eyre::Result<()> { +pub async fn run_system( + validation: bool, + no_tx_pool: bool, + block_time_secs: u64, +) -> eyre::Result<()> { println!("Validation: {}", validation); let engine_api = EngineApi::new("http://localhost:4444").unwrap(); @@ -424,7 +433,12 @@ pub async fn run_system(validation: bool, no_tx_pool: bool) -> eyre::Result<()> None }; - let mut generator = BlockGenerator::new(&engine_api, validation_api.as_ref(), no_tx_pool); + let mut generator = BlockGenerator::new( + &engine_api, + validation_api.as_ref(), + no_tx_pool, + block_time_secs, + ); generator.init().await?; From 483f98b4d2a5e0f9650cd28dc0140c440ec4b866 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 10 Feb 2025 20:03:12 +0000 Subject: [PATCH 024/262] Validate rbuilder with op-reth (#410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR extends the `op-rbuilder` integration test to use an off-the-shelf op-node to validate the blocks built. It was already possible to do as part of the engineAPI mocker command. This PR enables that behaviour on the integration test too. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- .../src/integration/integration_test.rs | 32 ++++-- .../op-rbuilder/src/integration/mod.rs | 1 + .../op-rbuilder/src/integration/op_reth.rs | 107 ++++++++++++++++++ 3 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/integration/op_reth.rs diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index c334e575..91b9614c 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -1,6 +1,8 @@ #[cfg(all(test, feature = "integration"))] mod tests { - use crate::integration::{op_rbuilder::OpRbuilderConfig, IntegrationFramework}; + use crate::integration::{ + op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, + }; use crate::tester::{BlockGenerator, EngineApi}; use std::path::PathBuf; use uuid::Uuid; @@ -16,20 +18,30 @@ mod tests { genesis_path.push("../../genesis.json"); assert!(genesis_path.exists()); - // generate a random dir for the data dir - let data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - - // generate a rra - let reth = OpRbuilderConfig::new() - .chain_config_path(genesis_path) - .data_dir(data_dir) + // create the builder + let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let builder = OpRbuilderConfig::new() + .chain_config_path(genesis_path.clone()) + .data_dir(builder_data_dir) .auth_rpc_port(1234) .network_port(1235); - framework.start("op-rbuilder", &reth).await.unwrap(); + framework.start("op-rbuilder", &builder).await.unwrap(); + + // create the validation reth node + let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let reth = OpRethConfig::new() + .chain_config_path(genesis_path) + .data_dir(reth_data_dir) + .auth_rpc_port(1236) + .network_port(1237); + + framework.start("op-reth", &reth).await.unwrap(); let engine_api = EngineApi::new("http://localhost:1234").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, None, false, 1); + let validation_api = EngineApi::new("http://localhost:1236").unwrap(); + + let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1); generator.init().await.unwrap(); for _ in 0..10 { diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index 523237b3..77009cc5 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -17,6 +17,7 @@ pub const DEFAULT_JWT_TOKEN: &str = mod integration_test; pub mod op_rbuilder; +pub mod op_reth; #[derive(Debug)] pub enum IntegrationError { diff --git a/crates/builder/op-rbuilder/src/integration/op_reth.rs b/crates/builder/op-rbuilder/src/integration/op_reth.rs new file mode 100644 index 00000000..2402eb26 --- /dev/null +++ b/crates/builder/op-rbuilder/src/integration/op_reth.rs @@ -0,0 +1,107 @@ +use crate::integration::{poll_logs, IntegrationError, Service, DEFAULT_JWT_TOKEN}; +use futures_util::Future; +use std::{ + path::{Path, PathBuf}, + process::Command, + time::Duration, +}; + +fn get_or_create_jwt_path(jwt_path: Option<&PathBuf>) -> PathBuf { + jwt_path.cloned().unwrap_or_else(|| { + let tmp_dir = std::env::temp_dir(); + let jwt_path = tmp_dir.join("jwt.hex"); + std::fs::write(&jwt_path, DEFAULT_JWT_TOKEN).expect("Failed to write JWT secret file"); + jwt_path + }) +} + +#[derive(Default)] +pub struct OpRethConfig { + auth_rpc_port: Option, + jwt_secret_path: Option, + chain_config_path: Option, + data_dir: Option, + http_port: Option, + network_port: Option, +} + +impl OpRethConfig { + pub fn new() -> Self { + Self::default() + } + + pub fn auth_rpc_port(mut self, port: u16) -> Self { + self.auth_rpc_port = Some(port); + self + } + + pub fn chain_config_path>(mut self, path: P) -> Self { + self.chain_config_path = Some(path.into()); + self + } + + pub fn data_dir>(mut self, path: P) -> Self { + self.data_dir = Some(path.into()); + self + } + + pub fn network_port(mut self, port: u16) -> Self { + self.network_port = Some(port); + self + } +} + +impl Service for OpRethConfig { + fn command(&self) -> Command { + let bin_path = PathBuf::from("op-reth"); + + let mut cmd = Command::new(bin_path); + let jwt_path = get_or_create_jwt_path(self.jwt_secret_path.as_ref()); + + cmd.arg("node") + .arg("--authrpc.port") + .arg( + self.auth_rpc_port + .expect("auth_rpc_port not set") + .to_string(), + ) + .arg("--authrpc.jwtsecret") + .arg( + jwt_path + .to_str() + .expect("Failed to convert jwt_path to string"), + ) + .arg("--chain") + .arg( + self.chain_config_path + .as_ref() + .expect("chain_config_path not set"), + ) + .arg("--datadir") + .arg(self.data_dir.as_ref().expect("data_dir not set")) + .arg("--disable-discovery") + .arg("--port") + .arg(self.network_port.expect("network_port not set").to_string()); + + if let Some(http_port) = self.http_port { + cmd.arg("--http") + .arg("--http.port") + .arg(http_port.to_string()); + } + + cmd + } + + #[allow(clippy::manual_async_fn)] + fn ready(&self, log_path: &Path) -> impl Future> + Send { + async move { + poll_logs( + log_path, + "Starting consensus engine", + Duration::from_millis(100), + Duration::from_secs(60), + ) + .await + } + } +} From 874a9dff16296669c5657bee39fa47a84b736dfe Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 11 Feb 2025 10:41:42 +0000 Subject: [PATCH 025/262] Spawn a task with the Op builder txn monitoring instead of ExEx (#417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Closes #412. Using the same scaffolding of the monitoring task I added support to track the node updates from a canonical stream from the provider (which is the same primitive used by the exex). Then, I use a spawned task to run the new monitoring with the stream once the node has been initialized. This is a patch until ExEx is fixed or we outsource the monitoring to an external entity. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- .../src/integration/integration_test.rs | 21 ++++++++-- .../op-rbuilder/src/integration/mod.rs | 14 +++++++ .../src/integration/op_rbuilder.rs | 11 +++++ crates/builder/op-rbuilder/src/main.rs | 21 +++++++++- crates/builder/op-rbuilder/src/monitoring.rs | 41 +++++++++++++------ .../src/tester/fixtures/genesis.json.tmpl | 3 ++ crates/builder/op-rbuilder/src/tester/mod.rs | 3 -- 7 files changed, 93 insertions(+), 21 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 91b9614c..93069937 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -7,6 +7,9 @@ mod tests { use std::path::PathBuf; use uuid::Uuid; + const BUILDER_PRIVATE_KEY: &str = + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; + #[tokio::test] async fn integration_test_chain_produces_blocks() { // This is a simple test using the integration framework to test that the chain @@ -20,13 +23,12 @@ mod tests { // create the builder let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let builder = OpRbuilderConfig::new() + let op_rbuilder_config = OpRbuilderConfig::new() .chain_config_path(genesis_path.clone()) .data_dir(builder_data_dir) .auth_rpc_port(1234) - .network_port(1235); - - framework.start("op-rbuilder", &builder).await.unwrap(); + .network_port(1235) + .with_builder_private_key(BUILDER_PRIVATE_KEY); // create the validation reth node let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); @@ -38,6 +40,11 @@ mod tests { framework.start("op-reth", &reth).await.unwrap(); + let op_rbuilder = framework + .start("op-rbuilder", &op_rbuilder_config) + .await + .unwrap(); + let engine_api = EngineApi::new("http://localhost:1234").unwrap(); let validation_api = EngineApi::new("http://localhost:1236").unwrap(); @@ -47,5 +54,11 @@ mod tests { for _ in 0..10 { generator.generate_block().await.unwrap(); } + + // there must be a line logging the monitoring transaction + op_rbuilder + .find_log_line("Committed block built by builder") + .await + .unwrap(); } } diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index 77009cc5..66d3c025 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -122,6 +122,20 @@ impl ServiceInstance { config.ready(&self.log_path).await?; Ok(()) } + + pub async fn find_log_line(&self, pattern: &str) -> eyre::Result<()> { + let mut file = + File::open(&self.log_path).map_err(|_| eyre::eyre!("Failed to open log file"))?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|_| eyre::eyre!("Failed to read log file"))?; + + if contents.contains(pattern) { + Ok(()) + } else { + Err(eyre::eyre!("Pattern not found in log file")) + } + } } impl IntegrationFramework { diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index 3183be12..d2a1e2ec 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -23,6 +23,7 @@ pub struct OpRbuilderConfig { data_dir: Option, http_port: Option, network_port: Option, + builder_private_key: Option, } impl OpRbuilderConfig { @@ -49,6 +50,11 @@ impl OpRbuilderConfig { self.network_port = Some(port); self } + + pub fn with_builder_private_key(mut self, private_key: &str) -> Self { + self.builder_private_key = Some(private_key.to_string()); + self + } } impl Service for OpRbuilderConfig { @@ -84,6 +90,11 @@ impl Service for OpRbuilderConfig { .arg("--port") .arg(self.network_port.expect("network_port not set").to_string()); + if let Some(builder_private_key) = &self.builder_private_key { + cmd.arg("--rollup.builder-secret-key") + .arg(builder_private_key); + } + if let Some(http_port) = self.http_port { cmd.arg("--http") .arg("--http.port") diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index b78aef41..847fa6de 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -119,6 +119,10 @@ fn main() { .payload(CustomPayloadBuilder::new(builder_args.builder_signer)), ) .with_add_ons(op_node.add_ons()) + /* + // TODO: ExEx in Op-reth fails from time to time when restarting the node. + // Switching to the spawn task on the meantime. + // https://github.com/paradigmxyz/reth/issues/14360 .install_exex("monitoring", move |ctx| { let builder_signer = builder_args.builder_signer; if let Some(signer) = &builder_signer { @@ -126,7 +130,22 @@ fn main() { } else { tracing::info!("Builder signer is not set"); } - async move { Ok(Monitoring::new(ctx, builder_signer).start()) } + async move { Ok(Monitoring::new(builder_signer).run_with_exex(ctx)) } + }) + */ + .on_node_started(move |ctx| { + let new_canonical_blocks = ctx.provider().canonical_state_stream(); + let builder_signer = builder_args.builder_signer; + + ctx.task_executor.spawn_critical( + "monitoring", + Box::pin(async move { + let monitoring = Monitoring::new(builder_signer); + let _ = monitoring.run_with_stream(new_canonical_blocks).await; + }), + ); + + Ok(()) }) .launch_with_fn(|builder| { let launcher = EngineNodeLauncher::new( diff --git a/crates/builder/op-rbuilder/src/monitoring.rs b/crates/builder/op-rbuilder/src/monitoring.rs index cdb8bada..15e4b7d6 100644 --- a/crates/builder/op-rbuilder/src/monitoring.rs +++ b/crates/builder/op-rbuilder/src/monitoring.rs @@ -1,6 +1,7 @@ use alloy_consensus::{Transaction, TxReceipt}; -use futures_util::TryStreamExt; +use futures_util::{Stream, StreamExt, TryStreamExt}; use reth::core::primitives::SignedTransaction; +use reth_chain_state::CanonStateNotification; use reth_exex::{ExExContext, ExExEvent}; use reth_node_api::{FullNodeComponents, NodeTypes}; use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; @@ -12,35 +13,33 @@ use crate::{metrics::OpRBuilderMetrics, tx_signer::Signer}; const OP_BUILDER_TX_PREFIX: &[u8] = b"Block Number:"; -pub struct Monitoring { - ctx: ExExContext, +pub struct Monitoring { builder_signer: Option, metrics: OpRBuilderMetrics, } -impl Monitoring -where - Node: FullNodeComponents>, -{ - pub fn new(ctx: ExExContext, builder_signer: Option) -> Self { +impl Monitoring { + pub fn new(builder_signer: Option) -> Self { Self { - ctx, builder_signer, metrics: Default::default(), } } - pub async fn start(mut self) -> eyre::Result<()> { + #[allow(dead_code)] + pub async fn run_with_exex(mut self, mut ctx: ExExContext) -> eyre::Result<()> + where + Node: FullNodeComponents>, + { // TODO: add builder balance monitoring // Process all new chain state notifications - while let Some(notification) = self.ctx.notifications.try_next().await? { + while let Some(notification) = ctx.notifications.try_next().await? { if let Some(reverted_chain) = notification.reverted_chain() { self.revert(&reverted_chain).await?; } if let Some(committed_chain) = notification.committed_chain() { self.commit(&committed_chain).await?; - self.ctx - .events + ctx.events .send(ExExEvent::FinishedHeight(committed_chain.tip().num_hash()))?; } } @@ -48,6 +47,22 @@ where Ok(()) } + pub async fn run_with_stream(mut self, mut events: St) -> eyre::Result<()> + where + St: Stream> + Unpin + 'static, + { + while let Some(event) = events.next().await { + if let Some(reverted) = event.reverted() { + self.revert(&reverted).await?; + } + + let committed = event.committed(); + self.commit(&committed).await?; + } + + Ok(()) + } + /// Process a new chain commit. /// /// This function decodes the builder tx and then emits metrics diff --git a/crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl b/crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl index 74f5d0a7..a1ab0146 100644 --- a/crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl +++ b/crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl @@ -40,6 +40,9 @@ "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x4200000000000000000000000000000000000011", "alloc": { + "70997970C51812dc3A010C7d01b50e0d17dc79C8": { + "balance": "0x21e19e0c9bab2400000" + }, "0000000000000000000000000000000000000000": { "balance": "0x1" }, diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index d5d54c3b..9edd2791 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -306,11 +306,8 @@ impl<'a> BlockGenerator<'a> { .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); - println!("now: {}", timestamp); let timestamp = timestamp + self.block_time_secs; - println!("timestamp: {}", timestamp); - let result = self .engine_api .update_forkchoice( From 3e642858ab9fe28b7e4b9e3060c1d70e52a997d4 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 11 Feb 2025 13:39:53 +0000 Subject: [PATCH 026/262] Add Op L1 block info txn in op CL tester (#420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary The OP CL tester was missing a block info transaction as part of the payload attributes for the block. Reth uses this info when it returns the receipt of any transaction in the block. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 1 + .../src/integration/integration_test.rs | 32 +++++++++++--- .../src/integration/op_rbuilder.rs | 5 +++ crates/builder/op-rbuilder/src/tester/mod.rs | 43 ++++++++++++++++++- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index d4978dc4..01e8ab5e 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -53,6 +53,7 @@ alloy-network.workspace = true # op op-alloy-consensus.workspace = true op-alloy-rpc-types-engine.workspace = true +op-alloy-network.workspace = true revm.workspace = true diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 93069937..65f2221c 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -4,6 +4,9 @@ mod tests { op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, }; use crate::tester::{BlockGenerator, EngineApi}; + use alloy_provider::{Provider, ProviderBuilder}; + use alloy_rpc_types_eth::BlockTransactionsKind; + use op_alloy_network::Optimism; use std::path::PathBuf; use uuid::Uuid; @@ -11,7 +14,7 @@ mod tests { "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; #[tokio::test] - async fn integration_test_chain_produces_blocks() { + async fn integration_test_chain_produces_blocks() -> eyre::Result<()> { // This is a simple test using the integration framework to test that the chain // produces blocks. let mut framework = IntegrationFramework::new().unwrap(); @@ -28,6 +31,7 @@ mod tests { .data_dir(builder_data_dir) .auth_rpc_port(1234) .network_port(1235) + .http_port(1238) .with_builder_private_key(BUILDER_PRIVATE_KEY); // create the validation reth node @@ -49,16 +53,34 @@ mod tests { let validation_api = EngineApi::new("http://localhost:1236").unwrap(); let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1); - generator.init().await.unwrap(); + generator.init().await?; + + let provider = ProviderBuilder::new() + .network::() + .on_http("http://localhost:1238".parse()?); for _ in 0..10 { - generator.generate_block().await.unwrap(); + let block_hash = generator.generate_block().await?; + + // query the block and the transactions inside the block + let block = provider + .get_block_by_hash(block_hash, BlockTransactionsKind::Hashes) + .await? + .expect("block"); + + for hash in block.transactions.hashes() { + let _ = provider + .get_transaction_receipt(hash) + .await? + .expect("receipt"); + } } // there must be a line logging the monitoring transaction op_rbuilder .find_log_line("Committed block built by builder") - .await - .unwrap(); + .await?; + + Ok(()) } } diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index d2a1e2ec..279889c1 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -51,6 +51,11 @@ impl OpRbuilderConfig { self } + pub fn http_port(mut self, port: u16) -> Self { + self.http_port = Some(port); + self + } + pub fn with_builder_private_key(mut self, private_key: &str) -> Self { self.builder_private_key = Some(private_key.to_string()); self diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 9edd2791..592f86ef 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -6,7 +6,7 @@ use alloy_primitives::Address; use alloy_primitives::Bytes; use alloy_primitives::TxKind; use alloy_primitives::B256; -use alloy_primitives::U256; +use alloy_primitives::{hex, U256}; use alloy_rpc_types_engine::ExecutionPayloadV1; use alloy_rpc_types_engine::ExecutionPayloadV2; use alloy_rpc_types_engine::PayloadAttributes; @@ -181,6 +181,11 @@ pub async fn generate_genesis(output: Option) -> eyre::Result<()> { Ok(()) } +// L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) +// +// https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1 +const FJORD_DATA: &[u8] = &hex!("440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"); + /// A system that continuously generates blocks using the engine API pub struct BlockGenerator<'a> { engine_api: &'a EngineApi, @@ -308,6 +313,40 @@ impl<'a> BlockGenerator<'a> { .as_secs(); let timestamp = timestamp + self.block_time_secs; + // Add L1 block info as the first transaction in every L2 block + // This deposit transaction contains L1 block metadata required by the L2 chain + // Currently using hardcoded data from L1 block 124665056 + // If this info is not provided, Reth cannot decode the receipt for any transaction + // in the block since it also includes this info as part of the result. + // It does not matter if the to address (4200000000000000000000000000000000000015) is + // not deployed on the L2 chain since Reth queries the block to get the info and not the contract. + let block_info_tx: Bytes = { + let deposit_tx = TxDeposit { + source_hash: B256::default(), + from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"), + to: TxKind::Call(address!("4200000000000000000000000000000000000015")), + mint: None, + value: U256::default(), + gas_limit: 210000, + is_system_transaction: true, + input: FJORD_DATA.into(), + }; + + // Create a temporary signer for the deposit + let signer = Signer::random(); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; + signed_tx.encoded_2718().into() + }; + + let transactions = if let Some(transactions) = transactions { + // prepend the block info transaction + let mut all_transactions = vec![block_info_tx]; + all_transactions.extend(transactions.into_iter()); + all_transactions + } else { + vec![block_info_tx] + }; + let result = self .engine_api .update_forkchoice( @@ -321,7 +360,7 @@ impl<'a> BlockGenerator<'a> { prev_randao: B256::ZERO, suggested_fee_recipient: Default::default(), }, - transactions, + transactions: Some(transactions), no_tx_pool: Some(self.no_tx_pool), gas_limit: Some(10000000000), eip_1559_params: None, From c22a3464c48a5c199f3d0dff13630a3116629135 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 12 Feb 2025 17:56:52 +0000 Subject: [PATCH 027/262] Add cancellation token + fb-rb integration (#424) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR replaces the Cancellation bool behind a mutex from Reth with a tokio Cancellation token that we can cancel from different places. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 3 + crates/builder/op-rbuilder/src/generator.rs | 63 ++++++++++-- crates/builder/op-rbuilder/src/main.rs | 15 ++- .../op-rbuilder/src/payload_builder.rs | 99 +++++++++++++++++-- .../src/payload_builder_vanilla.rs | 17 ++-- .../src/tester/fixtures/test-jwt-secret.txt | 1 + 6 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/tester/fixtures/test-jwt-secret.txt diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 01e8ab5e..7c49203a 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -22,6 +22,7 @@ reth-evm.workspace = true reth-exex.workspace = true reth-chainspec.workspace = true reth-primitives.workspace = true +reth-primitives-traits.workspace = true reth-node-api.workspace = true reth-basic-payload-builder.workspace = true reth-payload-builder.workspace = true @@ -73,10 +74,12 @@ clap.workspace = true derive_more.workspace = true metrics.workspace = true serde_json.workspace = true +tokio-util.workspace = true time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } +tokio-tungstenite = "0.26.1" rand = "0.8.5" [target.'cfg(unix)'.dependencies] diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 11f02e8c..8a700a86 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -5,12 +5,12 @@ use reth::{ providers::StateProviderFactory, tasks::TaskSpawner, transaction_pool::TransactionPool, }; use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadConfig}; -use reth_basic_payload_builder::{BuildArguments, Cancelled}; use reth_node_api::PayloadBuilderAttributes; use reth_node_api::PayloadKind; use reth_payload_builder::PayloadJobGenerator; use reth_payload_builder::{KeepPayloadJobAlive, PayloadBuilderError, PayloadJob}; use reth_payload_primitives::BuiltPayload; +use reth_revm::cached::CachedReads; use std::sync::{Arc, Mutex}; use std::time::SystemTime; use std::time::UNIX_EPOCH; @@ -18,6 +18,7 @@ use tokio::sync::oneshot; use tokio::sync::Notify; use tokio::time::Duration; use tokio::time::Sleep; +use tokio_util::sync::CancellationToken; use tracing::info; /// A trait for building payloads that encapsulate Ethereum transactions. @@ -68,6 +69,10 @@ pub struct BlockPayloadJobGenerator { /// /// See [PayloadBuilder] builder: Builder, + /// Whether to ensure only one payload is being processed at a time + ensure_only_one_payload: bool, + /// The last payload being processed + last_payload: Arc>, } // === impl EmptyBlockPayloadJobGenerator === @@ -81,6 +86,7 @@ impl BlockPayloadJobGenerator Self { Self { client, @@ -88,6 +94,8 @@ impl BlockPayloadJobGenerator>::Attributes, ) -> Result { + let cancel_token = if self.ensure_only_one_payload { + // Cancel existing payload + { + let last_payload = self.last_payload.lock().unwrap(); + last_payload.cancel(); + } + + // Create and set new cancellation token with a fresh lock + let cancel_token = CancellationToken::new(); + { + let mut last_payload = self.last_payload.lock().unwrap(); + *last_payload = cancel_token.clone(); + } + cancel_token + } else { + CancellationToken::new() + }; + let parent_header = if attributes.parent().is_zero() { // use latest block if parent is zero: genesis block self.client @@ -146,7 +172,7 @@ where builder: self.builder.clone(), config, cell: BlockCell::new(), - cancel: None, + cancel: cancel_token, deadline, build_complete: None, }; @@ -182,7 +208,7 @@ where /// The cell that holds the built payload. pub(crate) cell: BlockCell, /// Cancellation token for the running job - pub(crate) cancel: Option, + pub(crate) cancel: CancellationToken, pub(crate) deadline: Pin>, // Add deadline pub(crate) build_complete: Option>>, } @@ -212,16 +238,33 @@ where &mut self, kind: PayloadKind, ) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) { - tracing::debug!("Resolve kind {:?} {:?}", kind, self.cell.is_some()); + tracing::info!("Resolve kind {:?}", kind); // check if self.cell has a payload - self.cancel.take(); + self.cancel.cancel(); let resolve_future = ResolvePayload::new(self.cell.wait_for_value()); (resolve_future, KeepPayloadJobAlive::No) } } +pub struct BuildArguments { + /// How to interact with the chain. + pub client: Client, + /// The transaction pool. + /// + /// Or the type that provides the transactions to build the payload. + pub pool: Pool, + /// Previously cached disk reads + pub cached_reads: CachedReads, + /// How to configure the payload. + pub config: PayloadConfig, + /// A marker that can be used to cancel the job. + pub cancel: CancellationToken, + /// The best payload achieved so far. + pub best_payload: Option, +} + /// A [PayloadJob] is a future that's being polled by the `PayloadBuilderService` impl BlockPayloadJob where @@ -236,22 +279,20 @@ where let builder = self.builder.clone(); let client = self.client.clone(); let pool = self.pool.clone(); - let cancel = Cancelled::default(); - let _cancel = cancel.clone(); // Clone for the task let payload_config = self.config.clone(); let cell = self.cell.clone(); + let cancel = self.cancel.clone(); let (tx, rx) = oneshot::channel(); self.build_complete = Some(rx); - self.cancel = Some(cancel); self.executor.spawn_blocking(Box::pin(async move { let args = BuildArguments { client, pool, cached_reads: Default::default(), config: payload_config, - cancel: _cancel, + cancel, best_payload: None, }; @@ -279,12 +320,13 @@ where // Check if deadline is reached if this.deadline.as_mut().poll(cx).is_ready() { + this.cancel.cancel(); tracing::debug!("Deadline reached"); return Poll::Ready(Ok(())); } // If cancelled via resolve_kind() - if this.cancel.is_none() { + if this.cancel.is_cancelled() { tracing::debug!("Job cancelled"); return Poll::Ready(Ok(())); } @@ -607,6 +649,7 @@ mod tests { executor, config, builder.clone(), + false, ); // this is not nice but necessary diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 847fa6de..b0030b47 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,7 +1,6 @@ use clap::Parser; use generator::BlockPayloadJobGenerator; use monitoring::Monitoring; -use payload_builder::OpPayloadBuilder as FBPayloadBuilder; use payload_builder_vanilla::OpPayloadBuilderVanilla; use reth::builder::Node; use reth::{ @@ -73,20 +72,30 @@ where pool: Pool, ) -> eyre::Result::Engine>> { tracing::info!("Spawning a custom payload builder"); - let _fb_builder = FBPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())); let vanilla_builder = OpPayloadBuilderVanilla::new( OpEvmConfig::new(ctx.chain_spec()), self.builder_secret_key, ); let payload_job_config = BasicPayloadJobGeneratorConfig::default(); + /* + let payload_builder = FBPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())); + + // Start WebSocket server + if let Err(e) = payload_builder.start_ws("127.0.0.1:1111").await { + tracing::warn!("Failed to start WebSocket server: {}", e); + } else { + tracing::info!("FB websocket server started on 127.0.0.1:1111"); + } + */ + let payload_generator = BlockPayloadJobGenerator::with_builder( ctx.provider().clone(), pool, ctx.task_executor().clone(), payload_job_config, - // FBPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())), vanilla_builder, + true, ); let (payload_service, payload_builder) = diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 3292dbca..b53d7cab 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -1,6 +1,6 @@ -use std::{fmt::Display, sync::Arc}; +use std::{fmt::Display, sync::Arc, sync::Mutex}; -use crate::generator::{BlockCell, PayloadBuilder}; +use crate::generator::{BlockCell, BuildArguments, PayloadBuilder}; use alloy_consensus::{Eip658Value, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::merge::BEACON_NONCE; use alloy_primitives::{Address, Bytes, B256, U256}; @@ -35,14 +35,23 @@ use revm::{ }, Database, DatabaseCommit, }; +use tokio_util::sync::CancellationToken; use tracing::{debug, trace, warn}; +use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelopeV3; use reth_optimism_payload_builder::error::OpPayloadBuilderError; use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_transaction_pool::pool::BestPayloadTransactions; +use futures_util::FutureExt; +use futures_util::SinkExt; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::mpsc; +use tokio_tungstenite::accept_async; +use tokio_tungstenite::WebSocketStream; + /// Optimism's payload builder -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct OpPayloadBuilder { /// The rollup's compute pending block configuration option. // TODO(clabby): Implement this feature. @@ -52,17 +61,80 @@ pub struct OpPayloadBuilder { /// The type responsible for yielding the best transactions for the payload if mempool /// transactions are allowed. pub best_transactions: Txs, + /// WebSocket subscribers + pub subscribers: Arc>>>, + /// Channel sender for publishing messages + pub tx: mpsc::UnboundedSender, } impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. - pub const fn new(evm_config: EvmConfig) -> Self { + pub fn new(evm_config: EvmConfig) -> Self { + let (tx, rx) = mpsc::unbounded_channel(); + let subscribers = Arc::new(Mutex::new(Vec::new())); + + Self::publish_task(rx, subscribers.clone()); + Self { compute_pending_block: true, evm_config, best_transactions: (), + subscribers, + tx, } } + + /// Start the WebSocket server + pub async fn start_ws(&self, addr: &str) -> Result<(), Box> { + let listener = TcpListener::bind(addr).await?; + let subscribers = self.subscribers.clone(); + + tokio::spawn(async move { + while let Ok((stream, _)) = listener.accept().await { + tracing::info!("Accepted websocket connection"); + let subscribers = subscribers.clone(); + + tokio::spawn(async move { + match accept_async(stream).await { + Ok(ws_stream) => { + let mut subs = subscribers.lock().unwrap(); + subs.push(ws_stream); + } + Err(e) => eprintln!("Error accepting websocket connection: {}", e), + } + }); + } + }); + + Ok(()) + } + + /// Background task that handles publishing messages to WebSocket subscribers + fn publish_task( + mut rx: mpsc::UnboundedReceiver, + subscribers: Arc>>>, + ) { + tokio::spawn(async move { + while let Some(message) = rx.recv().await { + let mut subscribers = subscribers.lock().unwrap(); + + // Remove disconnected subscribers and send message to connected ones + subscribers.retain_mut(|ws_stream| { + let message = message.clone(); + async move { + ws_stream + .send(tokio_tungstenite::tungstenite::Message::Text( + message.into(), + )) + .await + .is_ok() + } + .now_or_never() + .unwrap_or(false) + }); + } + }); + } } impl OpPayloadBuilder @@ -70,6 +142,11 @@ where EvmConfig: ConfigureEvm
, Txs: OpPayloadTransactions, { + /// Send a message to be published + pub fn send_message(&self, message: String) -> Result<(), Box> { + self.tx.send(message).map_err(|e| e.into()) + } + /// 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 @@ -124,7 +201,11 @@ where // 1. execute the pre steps and seal an early block with that let mut info = execute_pre_steps(&mut db, &ctx)?; let (payload, mut bundle_state) = build_block(db, &ctx, &info)?; - best_payload.set(payload); + + best_payload.set(payload.clone()); + let _ = self.send_message( + serde_json::to_string(&OpExecutionPayloadEnvelopeV3::from(payload)).unwrap_or_default(), + ); tracing::info!(target: "payload_builder", "Fallback block built"); @@ -183,7 +264,11 @@ where let (payload, new_bundle_state) = build_block(db, &ctx, &info)?; - best_payload.set(payload); + best_payload.set(payload.clone()); + let _ = self.send_message( + serde_json::to_string(&OpExecutionPayloadEnvelopeV3::from(payload)) + .unwrap_or_default(), + ); bundle_state = new_bundle_state; total_gas_per_batch += gas_per_batch; @@ -454,7 +539,7 @@ pub struct OpPayloadBuilderCtx { /// Block config pub initialized_block_env: BlockEnv, /// Marker to check whether the job has been cancelled. - pub cancel: Cancelled, + pub cancel: CancellationToken, /// The currently best payload. pub best_payload: Option, } diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 00a8b2f2..d1694bf6 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -3,6 +3,7 @@ use reth::core::primitives::InMemorySize; use reth_transaction_pool::PoolTransaction; use std::{fmt::Display, sync::Arc, time::Instant}; +use crate::generator::BuildArguments; use crate::{ generator::{BlockCell, PayloadBuilder}, metrics::OpRBuilderMetrics, @@ -14,6 +15,7 @@ use alloy_consensus::{ use alloy_eips::merge::BEACON_NONCE; use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; use alloy_rpc_types_engine::PayloadId; +use op_alloy_consensus::{OpDepositReceipt, OpTxType, OpTypedTransaction}; use reth_basic_payload_builder::*; use reth_chain_state::ExecutedBlock; use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; @@ -22,6 +24,10 @@ use reth_execution_types::ExecutionOutcome; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; use reth_optimism_forks::OpHardforks; +use reth_optimism_payload_builder::{ + error::OpPayloadBuilderError, + payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, +}; use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; @@ -34,6 +40,7 @@ use reth_provider::{ HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, }; use reth_revm::database::StateProviderDatabase; +use reth_transaction_pool::pool::BestPayloadTransactions; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; use revm::{ db::{states::bundle_state::BundleRetention, State}, @@ -43,15 +50,9 @@ use revm::{ }, Database, DatabaseCommit, }; +use tokio_util::sync::CancellationToken; use tracing::{info, trace, warn}; -use op_alloy_consensus::{OpDepositReceipt, OpTxType, OpTypedTransaction}; -use reth_optimism_payload_builder::{ - error::OpPayloadBuilderError, - payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, -}; -use reth_transaction_pool::pool::BestPayloadTransactions; - /// Optimism's payload builder #[derive(Debug, Clone)] pub struct OpPayloadBuilderVanilla { @@ -565,7 +566,7 @@ pub struct OpPayloadBuilderCtx { /// Block config pub initialized_block_env: BlockEnv, /// Marker to check whether the job has been cancelled. - pub cancel: Cancelled, + pub cancel: CancellationToken, /// The currently best payload. pub best_payload: Option, /// The builder signer diff --git a/crates/builder/op-rbuilder/src/tester/fixtures/test-jwt-secret.txt b/crates/builder/op-rbuilder/src/tester/fixtures/test-jwt-secret.txt new file mode 100644 index 00000000..6e72091c --- /dev/null +++ b/crates/builder/op-rbuilder/src/tester/fixtures/test-jwt-secret.txt @@ -0,0 +1 @@ +688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a From 1fa0f46029f7f5f45e70ab6b13d371b4ccd5d2ab Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 12 Feb 2025 18:12:14 +0000 Subject: [PATCH 028/262] Make PayloadServiceBuilder work with a Builder trait (#425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR generalizes the PayloadServiceLauncher with a trait with a generic Builder. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/src/generator.rs | 77 ++++++++++++++++- crates/builder/op-rbuilder/src/main.rs | 95 ++------------------- 2 files changed, 84 insertions(+), 88 deletions(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 8a700a86..84d0efea 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -2,15 +2,28 @@ use futures_util::Future; use futures_util::FutureExt; use reth::providers::BlockReaderIdExt; use reth::{ - providers::StateProviderFactory, tasks::TaskSpawner, transaction_pool::TransactionPool, + builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}, + payload::PayloadBuilderHandle, + providers::CanonStateSubscriptions, + transaction_pool::TransactionPool, }; +use reth::{providers::StateProviderFactory, tasks::TaskSpawner}; use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadConfig}; +use reth_node_api::NodeTypesWithEngine; use reth_node_api::PayloadBuilderAttributes; use reth_node_api::PayloadKind; +use reth_node_api::PayloadTypes; +use reth_node_api::TxTy; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_node::OpEngineTypes; +use reth_optimism_payload_builder::OpPayloadBuilderAttributes; +use reth_optimism_primitives::OpPrimitives; +use reth_payload_builder::PayloadBuilderService; use reth_payload_builder::PayloadJobGenerator; use reth_payload_builder::{KeepPayloadJobAlive, PayloadBuilderError, PayloadJob}; use reth_payload_primitives::BuiltPayload; use reth_revm::cached::CachedReads; +use reth_transaction_pool::PoolTransaction; use std::sync::{Arc, Mutex}; use std::time::SystemTime; use std::time::UNIX_EPOCH; @@ -21,6 +34,68 @@ use tokio::time::Sleep; use tokio_util::sync::CancellationToken; use tracing::info; +#[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] +pub struct CustomOpPayloadBuilder { + builder: Builder, +} + +impl CustomOpPayloadBuilder { + pub fn new(builder: Builder) -> Self { + Self { builder } + } +} + +impl PayloadServiceBuilder for CustomOpPayloadBuilder +where + Node: FullNodeTypes< + Types: NodeTypesWithEngine< + Engine = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + >, + Pool: TransactionPool>> + + Unpin + + 'static, + Builder: PayloadBuilder< + Pool, + ::Provider, + Attributes = OpPayloadBuilderAttributes, + > + Unpin + + 'static, + >::BuiltPayload: + Into<::BuiltPayload> + Unpin + Clone, +{ + async fn spawn_payload_service( + self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result::Engine>> { + tracing::info!("Spawning a custom payload builder"); + let payload_job_config = BasicPayloadJobGeneratorConfig::default(); + + let payload_generator = BlockPayloadJobGenerator::with_builder( + ctx.provider().clone(), + pool, + ctx.task_executor().clone(), + payload_job_config, + self.builder, + false, + ); + + let (payload_service, payload_builder) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical("custom payload builder service", Box::pin(payload_service)); + + tracing::info!("Custom payload service started"); + + Ok(payload_builder) + } +} + /// A trait for building payloads that encapsulate Ethereum transactions. /// /// This trait provides the `try_build` method to construct a transaction payload diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index b0030b47..49e1e4cf 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,35 +1,20 @@ use clap::Parser; -use generator::BlockPayloadJobGenerator; +use generator::CustomOpPayloadBuilder; use monitoring::Monitoring; use payload_builder_vanilla::OpPayloadBuilderVanilla; use reth::builder::Node; -use reth::{ - builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}, - payload::PayloadBuilderHandle, - providers::CanonStateSubscriptions, - transaction_pool::TransactionPool, -}; +use reth::providers::CanonStateSubscriptions; use reth::{ builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}, providers::providers::BlockchainProvider2, }; -use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; -use reth_node_api::NodeTypesWithEngine; -use reth_node_api::TxTy; -use reth_optimism_chainspec::OpChainSpec; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; use reth_optimism_evm::OpEvmConfig; -use reth_optimism_node::OpEngineTypes; use reth_optimism_node::OpNode; -use reth_payload_builder::PayloadBuilderService; -use tx_signer::Signer; /// CLI argument parsing. pub mod args; -use reth_optimism_primitives::OpPrimitives; -use reth_transaction_pool::PoolTransaction; - pub mod generator; #[cfg(test)] mod integration; @@ -41,80 +26,16 @@ mod payload_builder_vanilla; mod tester; mod tx_signer; -#[derive(Debug, Clone, Copy, Default)] -#[non_exhaustive] -pub struct CustomPayloadBuilder { - builder_secret_key: Option, -} - -impl CustomPayloadBuilder { - pub fn new(builder_secret_key: Option) -> Self { - Self { builder_secret_key } - } -} - -impl PayloadServiceBuilder for CustomPayloadBuilder -where - Node: FullNodeTypes< - Types: NodeTypesWithEngine< - Engine = OpEngineTypes, - ChainSpec = OpChainSpec, - Primitives = OpPrimitives, - >, - >, - Pool: TransactionPool>> - + Unpin - + 'static, -{ - async fn spawn_payload_service( - self, - ctx: &BuilderContext, - pool: Pool, - ) -> eyre::Result::Engine>> { - tracing::info!("Spawning a custom payload builder"); - let vanilla_builder = OpPayloadBuilderVanilla::new( - OpEvmConfig::new(ctx.chain_spec()), - self.builder_secret_key, - ); - let payload_job_config = BasicPayloadJobGeneratorConfig::default(); - - /* - let payload_builder = FBPayloadBuilder::new(OpEvmConfig::new(ctx.chain_spec())); - - // Start WebSocket server - if let Err(e) = payload_builder.start_ws("127.0.0.1:1111").await { - tracing::warn!("Failed to start WebSocket server: {}", e); - } else { - tracing::info!("FB websocket server started on 127.0.0.1:1111"); - } - */ - - let payload_generator = BlockPayloadJobGenerator::with_builder( - ctx.provider().clone(), - pool, - ctx.task_executor().clone(), - payload_job_config, - vanilla_builder, - true, - ); - - let (payload_service, payload_builder) = - PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - - ctx.task_executor() - .spawn_critical("custom payload builder service", Box::pin(payload_service)); - - tracing::info!("Custom payload service started"); - - Ok(payload_builder) - } -} - fn main() { Cli::::parse() .run(|builder, builder_args| async move { let rollup_args = builder_args.rollup_args; + let vanilla_builder = OpPayloadBuilderVanilla::new( + OpEvmConfig::new(builder.config().chain.clone()), + builder_args.builder_signer, + ); + let engine_tree_config = TreeConfig::default() .with_persistence_threshold(rollup_args.persistence_threshold) .with_memory_block_buffer_target(rollup_args.memory_block_buffer_target); @@ -125,7 +46,7 @@ fn main() { .with_components( op_node .components() - .payload(CustomPayloadBuilder::new(builder_args.builder_signer)), + .payload(CustomOpPayloadBuilder::new(vanilla_builder)), ) .with_add_ons(op_node.add_ons()) /* From 473b62ab4abd0357d25e0d21742581730039de2a Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 12 Feb 2025 19:47:48 +0000 Subject: [PATCH 029/262] Tidy unused variables from op payload builder (#427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Cleanup PR to remove unused variables. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/generator.rs | 9 +++---- .../op-rbuilder/src/payload_builder.rs | 16 ++---------- .../src/payload_builder_vanilla.rs | 25 ++----------------- 3 files changed, 7 insertions(+), 43 deletions(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 84d0efea..7f3e19ff 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -124,7 +124,7 @@ pub trait PayloadBuilder: Send + Sync + Clone { /// A `Result` indicating the build outcome or an error. fn try_build( &self, - args: BuildArguments, + args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError>; } @@ -323,7 +323,7 @@ where } } -pub struct BuildArguments { +pub struct BuildArguments { /// How to interact with the chain. pub client: Client, /// The transaction pool. @@ -336,8 +336,6 @@ pub struct BuildArguments { pub config: PayloadConfig, /// A marker that can be used to cancel the job. pub cancel: CancellationToken, - /// The best payload achieved so far. - pub best_payload: Option, } /// A [PayloadJob] is a future that's being polled by the `PayloadBuilderService` @@ -368,7 +366,6 @@ where cached_reads: Default::default(), config: payload_config, cancel, - best_payload: None, }; let result = builder.try_build(args, cell); @@ -657,7 +654,7 @@ mod tests { fn try_build( &self, - args: BuildArguments, + args: BuildArguments, _best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { self.new_event(BlockEvent::Started); diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index b53d7cab..433a1b04 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -53,9 +53,6 @@ use tokio_tungstenite::WebSocketStream; /// Optimism's payload builder #[derive(Debug, Clone)] pub struct OpPayloadBuilder { - /// The rollup's compute pending block configuration option. - // TODO(clabby): Implement this feature. - pub compute_pending_block: bool, /// The type responsible for creating the evm. pub evm_config: EvmConfig, /// The type responsible for yielding the best transactions for the payload if mempool @@ -76,7 +73,6 @@ impl OpPayloadBuilder { Self::publish_task(rx, subscribers.clone()); Self { - compute_pending_block: true, evm_config, best_transactions: (), subscribers, @@ -157,7 +153,7 @@ where /// a result indicating success with the payload or an error in case of failure. fn build_payload( &self, - args: BuildArguments, + args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> where @@ -185,7 +181,6 @@ where initialized_cfg: cfg_env_with_handler_cfg, initialized_block_env: block_env, cancel, - best_payload: None, // remove }; let best = self.best_transactions.clone(); @@ -312,7 +307,7 @@ where fn try_build( &self, - args: BuildArguments, + args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { self.build_payload(args, best_payload) @@ -540,8 +535,6 @@ pub struct OpPayloadBuilderCtx { pub initialized_block_env: BlockEnv, /// Marker to check whether the job has been cancelled. pub cancel: CancellationToken, - /// The currently best payload. - pub best_payload: Option, } impl OpPayloadBuilderCtx { @@ -644,11 +637,6 @@ impl OpPayloadBuilderCtx { .is_holocene_active_at_timestamp(self.attributes().timestamp()) } - /// Returns true if the fees are higher than the previous payload. - pub fn is_better_payload(&self, total_fees: U256) -> bool { - is_better_payload(self.best_payload.as_ref(), total_fees) - } - /// Commits the withdrawals from the payload attributes to the state. pub fn commit_withdrawals(&self, db: &mut State) -> Result, ProviderError> where diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index d1694bf6..54c2d3c9 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -56,9 +56,6 @@ use tracing::{info, trace, warn}; /// Optimism's payload builder #[derive(Debug, Clone)] pub struct OpPayloadBuilderVanilla { - // /// The rollup's compute pending block configuration option. - // // TODO(clabby): Implement this feature. - // pub compute_pending_block: bool, /// The type responsible for creating the evm. pub evm_config: EvmConfig, /// The builder's signer key to use for an end of block tx @@ -74,7 +71,6 @@ impl OpPayloadBuilderVanilla { /// `OpPayloadBuilder` constructor. pub fn new(evm_config: EvmConfig, builder_signer: Option) -> Self { Self { - // compute_pending_block: true, evm_config, builder_signer, best_transactions: (), @@ -94,7 +90,7 @@ where fn try_build( &self, - args: BuildArguments, + args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { let pool = args.pool.clone(); @@ -145,7 +141,7 @@ where /// a result indicating success with the payload or an error in case of failure. fn build_payload<'a, Client, Pool, Txs>( &self, - args: BuildArguments, + args: BuildArguments, best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, ) -> Result, PayloadBuilderError> where @@ -167,7 +163,6 @@ where mut cached_reads, config, cancel, - best_payload, } = args; let ctx = OpPayloadBuilderCtx { @@ -177,7 +172,6 @@ where initialized_cfg: cfg_env_with_handler_cfg, initialized_block_env: block_env, cancel, - best_payload, builder_signer: self.builder_signer, metrics: Default::default(), }; @@ -310,14 +304,6 @@ where { return Ok(BuildOutcomeKind::Cancelled); } - - // check if the new payload is even more valuable - if !ctx.is_better_payload(info.total_fees) { - // can skip building the block - return Ok(BuildOutcomeKind::Aborted { - fees: info.total_fees, - }); - } } // Add builder tx to the block @@ -567,8 +553,6 @@ pub struct OpPayloadBuilderCtx { pub initialized_block_env: BlockEnv, /// Marker to check whether the job has been cancelled. pub cancel: CancellationToken, - /// The currently best payload. - pub best_payload: Option, /// The builder signer pub builder_signer: Option, /// The metrics for the builder @@ -692,11 +676,6 @@ impl OpPayloadBuilderCtx { self.builder_signer } - /// Returns true if the fees are higher than the previous payload. - pub fn is_better_payload(&self, total_fees: U256) -> bool { - is_better_payload(self.best_payload.as_ref(), total_fees) - } - /// Commits the withdrawals from the payload attributes to the state. pub fn commit_withdrawals(&self, db: &mut State) -> Result, ProviderError> where From 5474a0303304b00d6eb967444804b983da86cf04 Mon Sep 17 00:00:00 2001 From: ZanCorDX <126988525+ZanCorDX@users.noreply.github.com> Date: Wed, 26 Feb 2025 14:08:38 -0300 Subject: [PATCH 030/262] Reth 1.2.0 (#429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Lovely reth changes. ## 💡 Motivation and Context Pectra ## ✅ I have completed the following steps: * [X] Run `make lint` * [X] Run `make test` * [ ] Added tests (if applicable) --------- Co-authored-by: Ferran Borreguero Co-authored-by: shana Co-authored-by: Solar Mithril Co-authored-by: Vitaly Drogan --- crates/builder/op-rbuilder/src/generator.rs | 193 +--- .../src/integration/integration_test.rs | 139 ++- .../op-rbuilder/src/integration/mod.rs | 6 +- .../src/integration/op_rbuilder.rs | 3 +- .../op-rbuilder/src/integration/op_reth.rs | 3 +- crates/builder/op-rbuilder/src/main.rs | 55 +- crates/builder/op-rbuilder/src/monitoring.rs | 29 +- .../op-rbuilder/src/payload_builder.rs | 12 +- .../src/payload_builder_vanilla.rs | 863 +++++++++++------- crates/builder/op-rbuilder/src/tester/mod.rs | 5 +- crates/builder/op-rbuilder/src/tx_signer.rs | 13 +- 11 files changed, 787 insertions(+), 534 deletions(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 7f3e19ff..b55def99 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -1,29 +1,16 @@ use futures_util::Future; use futures_util::FutureExt; use reth::providers::BlockReaderIdExt; -use reth::{ - builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}, - payload::PayloadBuilderHandle, - providers::CanonStateSubscriptions, - transaction_pool::TransactionPool, -}; use reth::{providers::StateProviderFactory, tasks::TaskSpawner}; +use reth_basic_payload_builder::HeaderForPayload; use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadConfig}; -use reth_node_api::NodeTypesWithEngine; use reth_node_api::PayloadBuilderAttributes; use reth_node_api::PayloadKind; -use reth_node_api::PayloadTypes; -use reth_node_api::TxTy; -use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_node::OpEngineTypes; -use reth_optimism_payload_builder::OpPayloadBuilderAttributes; -use reth_optimism_primitives::OpPrimitives; -use reth_payload_builder::PayloadBuilderService; use reth_payload_builder::PayloadJobGenerator; use reth_payload_builder::{KeepPayloadJobAlive, PayloadBuilderError, PayloadJob}; use reth_payload_primitives::BuiltPayload; +use reth_primitives_traits::HeaderTy; use reth_revm::cached::CachedReads; -use reth_transaction_pool::PoolTransaction; use std::sync::{Arc, Mutex}; use std::time::SystemTime; use std::time::UNIX_EPOCH; @@ -34,68 +21,6 @@ use tokio::time::Sleep; use tokio_util::sync::CancellationToken; use tracing::info; -#[derive(Debug, Clone, Copy, Default)] -#[non_exhaustive] -pub struct CustomOpPayloadBuilder { - builder: Builder, -} - -impl CustomOpPayloadBuilder { - pub fn new(builder: Builder) -> Self { - Self { builder } - } -} - -impl PayloadServiceBuilder for CustomOpPayloadBuilder -where - Node: FullNodeTypes< - Types: NodeTypesWithEngine< - Engine = OpEngineTypes, - ChainSpec = OpChainSpec, - Primitives = OpPrimitives, - >, - >, - Pool: TransactionPool>> - + Unpin - + 'static, - Builder: PayloadBuilder< - Pool, - ::Provider, - Attributes = OpPayloadBuilderAttributes, - > + Unpin - + 'static, - >::BuiltPayload: - Into<::BuiltPayload> + Unpin + Clone, -{ - async fn spawn_payload_service( - self, - ctx: &BuilderContext, - pool: Pool, - ) -> eyre::Result::Engine>> { - tracing::info!("Spawning a custom payload builder"); - let payload_job_config = BasicPayloadJobGeneratorConfig::default(); - - let payload_generator = BlockPayloadJobGenerator::with_builder( - ctx.provider().clone(), - pool, - ctx.task_executor().clone(), - payload_job_config, - self.builder, - false, - ); - - let (payload_service, payload_builder) = - PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - - ctx.task_executor() - .spawn_critical("custom payload builder service", Box::pin(payload_service)); - - tracing::info!("Custom payload service started"); - - Ok(payload_builder) - } -} - /// A trait for building payloads that encapsulate Ethereum transactions. /// /// This trait provides the `try_build` method to construct a transaction payload @@ -104,7 +29,7 @@ where /// /// Generic parameters `Pool` and `Client` represent the transaction pool and /// Ethereum client types. -pub trait PayloadBuilder: Send + Sync + Clone { +pub trait PayloadBuilder: Send + Sync + Clone { /// The payload attributes type to accept for building. type Attributes: PayloadBuilderAttributes; /// The type of the built payload. @@ -124,18 +49,16 @@ pub trait PayloadBuilder: Send + Sync + Clone { /// A `Result` indicating the build outcome or an error. fn try_build( &self, - args: BuildArguments, + args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError>; } /// The generator type that creates new jobs that builds empty blocks. #[derive(Debug)] -pub struct BlockPayloadJobGenerator { +pub struct BlockPayloadJobGenerator { /// The client that can interact with the chain. client: Client, - /// txpool - pool: Pool, /// How to spawn building tasks executor: Tasks, /// The configuration for the job generator. @@ -152,12 +75,11 @@ pub struct BlockPayloadJobGenerator { // === impl EmptyBlockPayloadJobGenerator === -impl BlockPayloadJobGenerator { +impl BlockPayloadJobGenerator { /// Creates a new [EmptyBlockPayloadJobGenerator] with the given config and custom /// [PayloadBuilder] pub fn with_builder( client: Client, - pool: Pool, executor: Tasks, config: BasicPayloadJobGeneratorConfig, builder: Builder, @@ -165,7 +87,6 @@ impl BlockPayloadJobGenerator Self { Self { client, - pool, executor, _config: config, builder, @@ -175,27 +96,26 @@ impl BlockPayloadJobGenerator PayloadJobGenerator - for BlockPayloadJobGenerator +impl PayloadJobGenerator + for BlockPayloadJobGenerator where Client: StateProviderFactory - + BlockReaderIdExt
+ + BlockReaderIdExt
> + Clone + Unpin + 'static, - Pool: TransactionPool + Unpin + 'static, Tasks: TaskSpawner + Clone + Unpin + 'static, - Builder: PayloadBuilder + Unpin + 'static, - >::Attributes: Unpin + Clone, - >::BuiltPayload: Unpin + Clone, + Builder: PayloadBuilder + Unpin + 'static, + Builder::Attributes: Unpin + Clone, + Builder::BuiltPayload: Unpin + Clone, { - type Job = BlockPayloadJob; + type Job = BlockPayloadJob; /// This is invoked when the node receives payload attributes from the beacon node via /// `engine_forkchoiceUpdatedV1` fn new_payload_job( &self, - attributes: >::Attributes, + attributes: ::Attributes, ) -> Result { let cancel_token = if self.ensure_only_one_payload { // Cancel existing payload @@ -241,8 +161,6 @@ where let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); let mut job = BlockPayloadJob { - client: self.client.clone(), - pool: self.pool.clone(), executor: self.executor.clone(), builder: self.builder.clone(), config, @@ -264,16 +182,12 @@ use std::{ }; /// A [PayloadJob] that builds empty blocks. -pub struct BlockPayloadJob +pub struct BlockPayloadJob where - Builder: PayloadBuilder, + Builder: PayloadBuilder, { /// The configuration for how the payload will be created. - pub(crate) config: PayloadConfig, - /// The client that can interact with the chain. - pub(crate) client: Client, - /// The transaction pool. - pub(crate) pool: Pool, + pub(crate) config: PayloadConfig>, /// How to spawn building tasks pub(crate) executor: Tasks, /// The type responsible for building payloads. @@ -288,14 +202,12 @@ where pub(crate) build_complete: Option>>, } -impl PayloadJob for BlockPayloadJob +impl PayloadJob for BlockPayloadJob where - Client: StateProviderFactory + Clone + Unpin + 'static, - Pool: TransactionPool + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, - Builder: PayloadBuilder + Unpin + 'static, - >::Attributes: Unpin + Clone, - >::BuiltPayload: Unpin + Clone, + Builder: PayloadBuilder + Unpin + 'static, + Builder::Attributes: Unpin + Clone, + Builder::BuiltPayload: Unpin + Clone, { type PayloadAttributes = Builder::Attributes; type ResolvePayloadFuture = ResolvePayload; @@ -323,35 +235,25 @@ where } } -pub struct BuildArguments { - /// How to interact with the chain. - pub client: Client, - /// The transaction pool. - /// - /// Or the type that provides the transactions to build the payload. - pub pool: Pool, +pub struct BuildArguments { /// Previously cached disk reads pub cached_reads: CachedReads, /// How to configure the payload. - pub config: PayloadConfig, + pub config: PayloadConfig>, /// A marker that can be used to cancel the job. pub cancel: CancellationToken, } /// A [PayloadJob] is a future that's being polled by the `PayloadBuilderService` -impl BlockPayloadJob +impl BlockPayloadJob where - Client: StateProviderFactory + Clone + Unpin + 'static, - Pool: TransactionPool + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, - Builder: PayloadBuilder + Unpin + 'static, - >::Attributes: Unpin + Clone, - >::BuiltPayload: Unpin + Clone, + Builder: PayloadBuilder + Unpin + 'static, + Builder::Attributes: Unpin + Clone, + Builder::BuiltPayload: Unpin + Clone, { pub fn spawn_build_job(&mut self) { let builder = self.builder.clone(); - let client = self.client.clone(); - let pool = self.pool.clone(); let payload_config = self.config.clone(); let cell = self.cell.clone(); let cancel = self.cancel.clone(); @@ -361,8 +263,6 @@ where self.executor.spawn_blocking(Box::pin(async move { let args = BuildArguments { - client, - pool, cached_reads: Default::default(), config: payload_config, cancel, @@ -375,14 +275,12 @@ where } /// A [PayloadJob] is a a future that's being polled by the `PayloadBuilderService` -impl Future for BlockPayloadJob +impl Future for BlockPayloadJob where - Client: StateProviderFactory + Clone + Unpin + 'static, - Pool: TransactionPool + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, - Builder: PayloadBuilder + Unpin + 'static, - >::Attributes: Unpin + Clone, - >::BuiltPayload: Unpin + Clone, + Builder: PayloadBuilder + Unpin + 'static, + Builder::Attributes: Unpin + Clone, + Builder::BuiltPayload: Unpin + Clone, { type Output = Result<(), PayloadBuilderError>; @@ -515,14 +413,14 @@ mod tests { use alloy_primitives::U256; use rand::thread_rng; use reth::tasks::TokioTaskExecutor; - use reth_chain_state::ExecutedBlock; + use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_node_api::NodePrimitives; use reth_optimism_payload_builder::payload::OpPayloadBuilderAttributes; + use reth_optimism_payload_builder::OpPayloadPrimitives; use reth_optimism_primitives::OpPrimitives; - use reth_primitives::SealedBlockFor; + use reth_primitives::SealedBlock; use reth_provider::test_utils::MockEthProvider; use reth_testing_utils::generators::{random_block_range, BlockRangeParams}; - use reth_transaction_pool::noop::NoopTransactionPool; use tokio::task; use tokio::time::{sleep, Duration}; @@ -594,14 +492,16 @@ mod tests { } #[derive(Debug, Clone)] - struct MockBuilder { + struct MockBuilder { events: Arc>>, + _marker: std::marker::PhantomData, } - impl MockBuilder { + impl MockBuilder { fn new() -> Self { Self { events: Arc::new(Mutex::new(vec![])), + _marker: std::marker::PhantomData, } } @@ -622,7 +522,7 @@ mod tests { impl BuiltPayload for MockPayload { type Primitives = OpPrimitives; - fn block(&self) -> &SealedBlockFor<::Block> { + fn block(&self) -> &SealedBlock<::Block> { unimplemented!() } @@ -632,7 +532,7 @@ mod tests { } /// Returns the entire execution data for the built block, if available. - fn executed_block(&self) -> Option> { + fn executed_block(&self) -> Option> { None } @@ -648,13 +548,16 @@ mod tests { Cancelled, } - impl PayloadBuilder for MockBuilder { - type Attributes = OpPayloadBuilderAttributes; + impl PayloadBuilder for MockBuilder + where + N: OpPayloadPrimitives, + { + type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = MockPayload; fn try_build( &self, - args: BuildArguments, + args: BuildArguments, _best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { self.new_event(BlockEvent::Started); @@ -697,11 +600,10 @@ mod tests { async fn test_payload_generator() -> eyre::Result<()> { let mut rng = thread_rng(); - let pool = NoopTransactionPool::default(); let client = MockEthProvider::default(); let executor = TokioTaskExecutor::default(); let config = BasicPayloadJobGeneratorConfig::default(); - let builder = MockBuilder::new(); + let builder = MockBuilder::::new(); let (start, count) = (1, 10); let blocks = random_block_range( @@ -717,7 +619,6 @@ mod tests { let generator = BlockPayloadJobGenerator::with_builder( client.clone(), - pool, executor, config, builder.clone(), diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 65f2221c..1880de87 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -4,9 +4,17 @@ mod tests { op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, }; use crate::tester::{BlockGenerator, EngineApi}; + use crate::tx_signer::Signer; + use alloy_consensus::TxEip1559; + use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; + use alloy_eips::eip2718::Encodable2718; + use alloy_primitives::hex; + use alloy_provider::Identity; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types_eth::BlockTransactionsKind; + use op_alloy_consensus::OpTypedTransaction; use op_alloy_network::Optimism; + use std::cmp::max; use std::path::PathBuf; use uuid::Uuid; @@ -17,7 +25,8 @@ mod tests { async fn integration_test_chain_produces_blocks() -> eyre::Result<()> { // This is a simple test using the integration framework to test that the chain // produces blocks. - let mut framework = IntegrationFramework::new().unwrap(); + let mut framework = + IntegrationFramework::new("integration_test_chain_produces_blocks").unwrap(); // we are going to use a genesis file pre-generated before the test let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -55,8 +64,7 @@ mod tests { let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1); generator.init().await?; - let provider = ProviderBuilder::new() - .network::() + let provider = ProviderBuilder::::default() .on_http("http://localhost:1238".parse()?); for _ in 0..10 { @@ -83,4 +91,129 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn integration_test_revert_protection() -> eyre::Result<()> { + // This is a simple test using the integration framework to test that the chain + // produces blocks. + let mut framework = + IntegrationFramework::new("integration_test_revert_protection").unwrap(); + + // we are going to use a genesis file pre-generated before the test + let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + genesis_path.push("../../genesis.json"); + assert!(genesis_path.exists()); + + // create the builder + let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let op_rbuilder_config = OpRbuilderConfig::new() + .chain_config_path(genesis_path.clone()) + .data_dir(builder_data_dir) + .auth_rpc_port(1244) + .network_port(1245) + .http_port(1248) + .with_builder_private_key(BUILDER_PRIVATE_KEY); + + // create the validation reth node + let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let reth = OpRethConfig::new() + .chain_config_path(genesis_path) + .data_dir(reth_data_dir) + .auth_rpc_port(1246) + .network_port(1247); + + framework.start("op-reth", &reth).await.unwrap(); + + let _ = framework + .start("op-rbuilder", &op_rbuilder_config) + .await + .unwrap(); + + let engine_api = EngineApi::new("http://localhost:1244").unwrap(); + let validation_api = EngineApi::new("http://localhost:1246").unwrap(); + + let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1); + let latest_block = generator.init().await?; + + let provider = ProviderBuilder::::default() + .on_http("http://localhost:1248".parse()?); + + let mut base_fee = max( + latest_block.header.base_fee_per_gas.unwrap(), + MIN_PROTOCOL_BASE_FEE, + ); + for _ in 0..10 { + // Get builder's address + let known_wallet = Signer::try_from_secret(BUILDER_PRIVATE_KEY.parse()?)?; + let builder_address = known_wallet.address; + // Get current nonce from chain + let nonce = provider.get_transaction_count(builder_address).await?; + // Transaction from builder should succeed + let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: 901, + nonce, + gas_limit: 210000, + max_fee_per_gas: base_fee.into(), + ..Default::default() + }); + let signed_tx = known_wallet.sign_tx(tx_request)?; + let known_tx = provider + .send_raw_transaction(signed_tx.encoded_2718().as_slice()) + .await?; + + // Create a reverting transaction + let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: 901, + nonce: nonce + 1, + gas_limit: 300000, + max_fee_per_gas: base_fee.into(), + input: hex!("60006000fd").into(), // PUSH1 0x00 PUSH1 0x00 REVERT + ..Default::default() + }); + let signed_tx = known_wallet.sign_tx(tx_request)?; + let reverting_tx = provider + .send_raw_transaction(signed_tx.encoded_2718().as_slice()) + .await?; + + let block_hash = generator.generate_block().await?; + + // query the block and the transactions inside the block + let block = provider + .get_block_by_hash(block_hash, BlockTransactionsKind::Hashes) + .await? + .expect("block"); + + // Verify known transaction is included + assert!( + block + .transactions + .hashes() + .any(|hash| hash == *known_tx.tx_hash()), + "successful transaction missing from block" + ); + + // Verify reverted transaction is NOT included + assert!( + !block + .transactions + .hashes() + .any(|hash| hash == *reverting_tx.tx_hash()), + "reverted transaction unexpectedly included in block" + ); + for hash in block.transactions.hashes() { + let receipt = provider + .get_transaction_receipt(hash) + .await? + .expect("receipt"); + let success = receipt.inner.inner.status(); + assert!(success); + } + base_fee = max( + block.header.base_fee_per_gas.unwrap(), + MIN_PROTOCOL_BASE_FEE, + ); + } + + Ok(()) + } } diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index 66d3c025..26a86d35 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -139,18 +139,18 @@ impl ServiceInstance { } impl IntegrationFramework { - pub fn new() -> Result { + pub fn new(test_name: &str) -> Result { let dt: OffsetDateTime = SystemTime::now().into(); let format = format_description::parse("[year]_[month]_[day]_[hour]_[minute]_[second]") .map_err(|_| IntegrationError::SetupError)?; - let test_name = dt + let date_format = dt .format(&format) .map_err(|_| IntegrationError::SetupError)?; let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_dir.push("../../integration_logs"); - test_dir.push(test_name); + test_dir.push(format!("{}_{}", date_format, test_name)); std::fs::create_dir_all(&test_dir).map_err(|_| IntegrationError::SetupError)?; diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index 279889c1..319f2254 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -93,7 +93,8 @@ impl Service for OpRbuilderConfig { .arg(self.data_dir.as_ref().expect("data_dir not set")) .arg("--disable-discovery") .arg("--port") - .arg(self.network_port.expect("network_port not set").to_string()); + .arg(self.network_port.expect("network_port not set").to_string()) + .arg("--ipcdisable"); if let Some(builder_private_key) = &self.builder_private_key { cmd.arg("--rollup.builder-secret-key") diff --git a/crates/builder/op-rbuilder/src/integration/op_reth.rs b/crates/builder/op-rbuilder/src/integration/op_reth.rs index 2402eb26..b809f479 100644 --- a/crates/builder/op-rbuilder/src/integration/op_reth.rs +++ b/crates/builder/op-rbuilder/src/integration/op_reth.rs @@ -81,7 +81,8 @@ impl Service for OpRethConfig { .arg(self.data_dir.as_ref().expect("data_dir not set")) .arg("--disable-discovery") .arg("--port") - .arg(self.network_port.expect("network_port not set").to_string()); + .arg(self.network_port.expect("network_port not set").to_string()) + .arg("--ipcdisable"); if let Some(http_port) = self.http_port { cmd.arg("--http") diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 49e1e4cf..f6ce402b 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,15 +1,9 @@ use clap::Parser; -use generator::CustomOpPayloadBuilder; use monitoring::Monitoring; -use payload_builder_vanilla::OpPayloadBuilderVanilla; -use reth::builder::Node; +use payload_builder_vanilla::CustomOpPayloadBuilder; use reth::providers::CanonStateSubscriptions; -use reth::{ - builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}, - providers::providers::BlockchainProvider2, -}; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; -use reth_optimism_evm::OpEvmConfig; +use reth_optimism_node::node::OpAddOnsBuilder; use reth_optimism_node::OpNode; /// CLI argument parsing. @@ -20,7 +14,7 @@ pub mod generator; mod integration; mod metrics; mod monitoring; -pub mod payload_builder; +// pub mod payload_builder; mod payload_builder_vanilla; #[cfg(test)] mod tester; @@ -31,38 +25,20 @@ fn main() { .run(|builder, builder_args| async move { let rollup_args = builder_args.rollup_args; - let vanilla_builder = OpPayloadBuilderVanilla::new( - OpEvmConfig::new(builder.config().chain.clone()), - builder_args.builder_signer, - ); - - let engine_tree_config = TreeConfig::default() - .with_persistence_threshold(rollup_args.persistence_threshold) - .with_memory_block_buffer_target(rollup_args.memory_block_buffer_target); - let op_node = OpNode::new(rollup_args.clone()); let handle = builder - .with_types_and_provider::>() + .with_types::() .with_components( op_node .components() - .payload(CustomOpPayloadBuilder::new(vanilla_builder)), + .payload(CustomOpPayloadBuilder::new(builder_args.builder_signer)), + ) + .with_add_ons( + OpAddOnsBuilder::default() + .with_sequencer(rollup_args.sequencer_http.clone()) + .with_enable_tx_conditional(rollup_args.enable_tx_conditional) + .build(), ) - .with_add_ons(op_node.add_ons()) - /* - // TODO: ExEx in Op-reth fails from time to time when restarting the node. - // Switching to the spawn task on the meantime. - // https://github.com/paradigmxyz/reth/issues/14360 - .install_exex("monitoring", move |ctx| { - let builder_signer = builder_args.builder_signer; - if let Some(signer) = &builder_signer { - tracing::info!("Builder signer address is set to: {:?}", signer.address); - } else { - tracing::info!("Builder signer is not set"); - } - async move { Ok(Monitoring::new(builder_signer).run_with_exex(ctx)) } - }) - */ .on_node_started(move |ctx| { let new_canonical_blocks = ctx.provider().canonical_state_stream(); let builder_signer = builder_args.builder_signer; @@ -77,14 +53,7 @@ fn main() { Ok(()) }) - .launch_with_fn(|builder| { - let launcher = EngineNodeLauncher::new( - builder.task_executor().clone(), - builder.config().datadir(), - engine_tree_config, - ); - builder.launch_with(launcher) - }) + .launch() .await?; handle.node_exit_future.await diff --git a/crates/builder/op-rbuilder/src/monitoring.rs b/crates/builder/op-rbuilder/src/monitoring.rs index 15e4b7d6..9aa51e65 100644 --- a/crates/builder/op-rbuilder/src/monitoring.rs +++ b/crates/builder/op-rbuilder/src/monitoring.rs @@ -5,7 +5,7 @@ use reth_chain_state::CanonStateNotification; use reth_exex::{ExExContext, ExExEvent}; use reth_node_api::{FullNodeComponents, NodeTypes}; use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; -use reth_primitives::{Block, SealedBlockWithSenders}; +use reth_primitives::{Block, RecoveredBlock}; use reth_provider::Chain; use tracing::info; @@ -117,24 +117,25 @@ impl Monitoring { fn decode_chain_into_builder_txs( chain: &Chain, builder_signer: Option, -) -> Vec<(&SealedBlockWithSenders>, bool)> { +) -> Vec<(&RecoveredBlock>, bool)> { chain // Get all blocks and receipts .blocks_and_receipts() // Get all receipts .map(|(block, receipts)| { - let has_builder_tx = block - .body() - .transactions - .iter() - .zip(receipts.iter().flatten()) - .any(move |(tx, receipt)| { - receipt.status() - && tx.input().starts_with(OP_BUILDER_TX_PREFIX) - && tx.recover_signer().is_some_and(|signer| { - builder_signer.is_some_and(|bs| signer == bs.address) - }) - }); + let has_builder_tx = + block + .body() + .transactions + .iter() + .zip(receipts.iter()) + .any(move |(tx, receipt)| { + receipt.status() + && tx.input().starts_with(OP_BUILDER_TX_PREFIX) + && tx.recover_signer().is_ok_and(|signer| { + builder_signer.is_some_and(|bs| signer == bs.address) + }) + }); (block, has_builder_tx) }) .collect() diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 433a1b04..7a9c25aa 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -6,7 +6,6 @@ use alloy_eips::merge::BEACON_NONCE; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rpc_types_engine::PayloadId; use op_alloy_consensus::{OpDepositReceipt, OpTxType}; -use reth_basic_payload_builder::*; use reth_chainspec::ChainSpecProvider; use reth_evm::{env::EvmEnv, system_calls::SystemCaller, ConfigureEvm, NextBlockEnvAttributes}; use reth_execution_types::ExecutionOutcome; @@ -31,7 +30,7 @@ use revm::{ db::{states::bundle_state::BundleRetention, BundleState, State}, primitives::{ BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, - ResultAndState, TxEnv, + ResultAndState, SpecId, TxEnv, }, Database, DatabaseCommit, }; @@ -276,7 +275,12 @@ where impl OpPayloadBuilder where - EvmConfig: ConfigureEvm
, + EvmConfig: ConfigureEvm< + Spec = SpecId, + Header = Header, + Transaction = OpTransactionSigned, + EvmError = EVMError, + >, { /// Returns the configured [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the targeted payload /// (that has the `parent` as its parent). @@ -730,7 +734,7 @@ where )); } - // Convert the transaction to a [TransactionSignedEcRecovered]. This is + // Convert the transaction to a [Recovered]. This is // purely for the purposes of utilizing the `evm_config.tx_env`` function. // Deposit transactions do not have signatures, so if the tx is a deposit, this // will just pull in its `from` address. diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 54c2d3c9..f983b151 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1,99 +1,262 @@ -use alloy_rpc_types_eth::Withdrawals; -use reth::core::primitives::InMemorySize; -use reth_transaction_pool::PoolTransaction; -use std::{fmt::Display, sync::Arc, time::Instant}; - +use crate::generator::BlockPayloadJobGenerator; use crate::generator::BuildArguments; use crate::{ generator::{BlockCell, PayloadBuilder}, metrics::OpRBuilderMetrics, tx_signer::Signer, }; +use alloy_consensus::constants::EMPTY_WITHDRAWALS; +use alloy_consensus::transaction::Recovered; use alloy_consensus::{ Eip658Value, Header, Transaction, TxEip1559, Typed2718, EMPTY_OMMER_ROOT_HASH, }; use alloy_eips::merge::BEACON_NONCE; +use alloy_primitives::private::alloy_rlp::Encodable; use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; use alloy_rpc_types_engine::PayloadId; -use op_alloy_consensus::{OpDepositReceipt, OpTxType, OpTypedTransaction}; -use reth_basic_payload_builder::*; -use reth_chain_state::ExecutedBlock; -use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; -use reth_evm::{env::EvmEnv, system_calls::SystemCaller, ConfigureEvm, NextBlockEnvAttributes}; +use alloy_rpc_types_eth::Withdrawals; +use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; +use reth::builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}; +use reth::core::primitives::InMemorySize; +use reth::payload::PayloadBuilderHandle; +use reth_basic_payload_builder::{ + BasicPayloadJobGeneratorConfig, BuildOutcome, BuildOutcomeKind, PayloadConfig, +}; +use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; +use reth_evm::{ + env::EvmEnv, system_calls::SystemCaller, ConfigureEvmEnv, ConfigureEvmFor, Database, Evm, + EvmError, InvalidTxError, NextBlockEnvAttributes, +}; use reth_execution_types::ExecutionOutcome; +use reth_node_api::NodePrimitives; +use reth_node_api::NodeTypesWithEngine; +use reth_node_api::TxTy; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; +use reth_optimism_evm::BasicOpReceiptBuilder; +use reth_optimism_evm::OpEvmConfig; +use reth_optimism_evm::{OpReceiptBuilder, ReceiptBuilderCtx}; use reth_optimism_forks::OpHardforks; +use reth_optimism_node::OpEngineTypes; +use reth_optimism_payload_builder::config::{OpBuilderConfig, OpDAConfig}; +use reth_optimism_payload_builder::OpPayloadPrimitives; use reth_optimism_payload_builder::{ error::OpPayloadBuilderError, payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, }; -use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; +use reth_optimism_primitives::{ + OpPrimitives, OpTransactionSigned, ADDRESS_L2_TO_L1_MESSAGE_PASSER, +}; +use reth_payload_builder::PayloadBuilderService; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; +use reth_payload_util::BestPayloadTransactions; use reth_payload_util::PayloadTransactions; -use reth_primitives::{ - proofs, transaction::SignedTransactionIntoRecoveredExt, Block, BlockBody, BlockExt, - SealedHeader, TxType, -}; +use reth_primitives::{transaction::SignedTransactionIntoRecoveredExt, BlockBody, SealedHeader}; +use reth_primitives_traits::proofs; +use reth_primitives_traits::Block; +use reth_primitives_traits::RecoveredBlock; +use reth_provider::CanonStateSubscriptions; use reth_provider::{ HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, + StorageRootProvider, }; use reth_revm::database::StateProviderDatabase; -use reth_transaction_pool::pool::BestPayloadTransactions; -use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; +use reth_transaction_pool::BestTransactionsAttributes; +use reth_transaction_pool::PoolTransaction; +use reth_transaction_pool::TransactionPool; use revm::{ db::{states::bundle_state::BundleRetention, State}, - primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, - ResultAndState, TxEnv, - }, - Database, DatabaseCommit, + primitives::{ExecutionResult, ResultAndState}, + DatabaseCommit, }; +use std::error::Error as StdError; +use std::{fmt::Display, sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{info, trace, warn}; +#[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] +pub struct CustomOpPayloadBuilder { + builder_signer: Option, +} + +impl CustomOpPayloadBuilder { + pub fn new(builder_signer: Option) -> Self { + Self { builder_signer } + } +} + +impl PayloadServiceBuilder for CustomOpPayloadBuilder +where + Node: FullNodeTypes< + Types: NodeTypesWithEngine< + Engine = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + >, + Pool: TransactionPool>> + + Unpin + + 'static, +{ + type PayloadBuilder = OpPayloadBuilderVanilla; + + async fn build_payload_builder( + &self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result { + Ok(OpPayloadBuilderVanilla::new( + OpEvmConfig::new(ctx.chain_spec()), + self.builder_signer, + pool, + ctx.provider().clone(), + Arc::new(BasicOpReceiptBuilder::default()), + )) + } + + fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + payload_builder: Self::PayloadBuilder, + ) -> eyre::Result::Engine>> { + tracing::info!("Spawning a custom payload builder"); + let payload_job_config = BasicPayloadJobGeneratorConfig::default(); + + let payload_generator = BlockPayloadJobGenerator::with_builder( + ctx.provider().clone(), + ctx.task_executor().clone(), + payload_job_config, + payload_builder, + false, + ); + + let (payload_service, payload_builder) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical("custom payload builder service", Box::pin(payload_service)); + + tracing::info!("Custom payload service started"); + + Ok(payload_builder) + } +} + +impl reth_basic_payload_builder::PayloadBuilder + for OpPayloadBuilderVanilla +where + Pool: Clone + Send + Sync, + Client: Clone + Send + Sync, + EvmConfig: Clone + Send + Sync, + N: NodePrimitives, + Txs: Clone + Send + Sync, +{ + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + _args: reth_basic_payload_builder::BuildArguments, + ) -> Result, PayloadBuilderError> { + unimplemented!() + } + + fn build_empty_payload( + &self, + _config: reth_basic_payload_builder::PayloadConfig< + Self::Attributes, + reth_basic_payload_builder::HeaderForPayload, + >, + ) -> Result { + unimplemented!() + } +} + /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct OpPayloadBuilderVanilla { +pub struct OpPayloadBuilderVanilla { /// The type responsible for creating the evm. pub evm_config: EvmConfig, /// The builder's signer key to use for an end of block tx pub builder_signer: Option, + /// The transaction pool + pub pool: Pool, + /// Node client + pub client: Client, + /// Settings for the builder, e.g. DA settings. + pub config: OpBuilderConfig, /// 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: OpRBuilderMetrics, + /// Node primitive types. + pub receipt_builder: Arc>, } -impl OpPayloadBuilderVanilla { +impl + OpPayloadBuilderVanilla +{ /// `OpPayloadBuilder` constructor. - pub fn new(evm_config: EvmConfig, builder_signer: Option) -> Self { - Self { + pub fn new( + evm_config: EvmConfig, + builder_signer: Option, + pool: Pool, + client: Client, + receipt_builder: Arc>, + ) -> Self { + Self::with_builder_config( evm_config, builder_signer, + pool, + client, + receipt_builder, + Default::default(), + ) + } + + pub fn with_builder_config( + evm_config: EvmConfig, + builder_signer: Option, + pool: Pool, + client: Client, + receipt_builder: Arc>, + config: OpBuilderConfig, + ) -> Self { + Self { + pool, + client, + receipt_builder, + config, + evm_config, best_transactions: (), metrics: Default::default(), + builder_signer, } } } -impl PayloadBuilder for OpPayloadBuilderVanilla +impl PayloadBuilder + for OpPayloadBuilderVanilla where - Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool>, - EvmConfig: ConfigureEvm
, + Client: StateProviderFactory + ChainSpecProvider + Clone, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, + Pool: TransactionPool>, + EvmConfig: ConfigureEvmFor, + Txs: OpPayloadTransactions, { - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; fn try_build( &self, - args: BuildArguments, + args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { - let pool = args.pool.clone(); + let pool = self.pool.clone(); let block_build_start_time = Instant::now(); match self.build_payload(args, |attrs| { @@ -127,9 +290,12 @@ where } } -impl OpPayloadBuilderVanilla +impl OpPayloadBuilderVanilla where - EvmConfig: ConfigureEvm
, + Pool: TransactionPool>, + Client: StateProviderFactory + ChainSpecProvider, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, + EvmConfig: ConfigureEvmFor, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -139,27 +305,19 @@ where /// 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, Client, Pool, Txs>( + fn build_payload<'a, Txs>( &self, - args: BuildArguments, + args: BuildArguments, OpBuiltPayload>, best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, - ) -> Result, PayloadBuilderError> + ) -> Result>, PayloadBuilderError> where - Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool, - Txs: PayloadTransactions, + Txs: PayloadTransactions>, { let evm_env = self - .cfg_and_block_env(&args.config.attributes, &args.config.parent_header) + .evm_env(&args.config.attributes, &args.config.parent_header) .map_err(PayloadBuilderError::other)?; - let EvmEnv { - cfg_env_with_handler_cfg, - block_env, - } = evm_env; let BuildArguments { - client, - pool: _, mut cached_reads, config, cancel, @@ -167,18 +325,19 @@ where let ctx = OpPayloadBuilderCtx { evm_config: self.evm_config.clone(), - chain_spec: client.chain_spec(), + da_config: self.config.da_config.clone(), + chain_spec: self.client.chain_spec(), config, - initialized_cfg: cfg_env_with_handler_cfg, - initialized_block_env: block_env, + evm_env, cancel, + receipt_builder: self.receipt_builder.clone(), builder_signer: self.builder_signer, metrics: Default::default(), }; let builder = OpBuilder::new(best); - let state_provider = client.state_by_block_hash(ctx.parent().hash())?; + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; let state = StateProviderDatabase::new(state_provider); if ctx.attributes().no_tx_pool { @@ -197,27 +356,21 @@ where } .map(|out| out.with_cached_reads(cached_reads)) } -} -impl OpPayloadBuilderVanilla -where - EvmConfig: ConfigureEvm
, -{ /// Returns the configured [`EvmEnv`] for the targeted payload /// (that has the `parent` as its parent). - pub fn cfg_and_block_env( + pub fn evm_env( &self, - attributes: &OpPayloadBuilderAttributes, + attributes: &OpPayloadBuilderAttributes, parent: &Header, - ) -> Result { + ) -> Result, EvmConfig::Error> { let next_attributes = NextBlockEnvAttributes { timestamp: attributes.timestamp(), suggested_fee_recipient: attributes.suggested_fee_recipient(), prev_randao: attributes.prev_randao(), gas_limit: attributes.gas_limit.unwrap_or(parent.gas_limit), }; - self.evm_config - .next_cfg_and_block_env(parent, next_attributes) + self.evm_config.next_evm_env(parent, next_attributes) } } @@ -250,19 +403,20 @@ impl<'a, Txs> OpBuilder<'a, Txs> { } } -impl OpBuilder<'_, Txs> -where - Txs: PayloadTransactions, -{ +impl OpBuilder<'_, Txs> { /// Executes the payload and returns the outcome. - pub fn execute( + pub fn execute( self, state: &mut State, - ctx: &OpPayloadBuilderCtx, - ) -> Result, PayloadBuilderError> + ctx: &OpPayloadBuilderCtx, + ) -> Result>, PayloadBuilderError> where - EvmConfig: ConfigureEvm
, - DB: Database, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, + Txs: PayloadTransactions>, + EvmConfig: ConfigureEvmFor, + ChainSpec: EthChainSpec + OpHardforks, + DB: Database + AsRef

, + P: StorageRootProvider, { 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"); @@ -288,10 +442,27 @@ where let message = format!("Block Number: {}", ctx.block_number()) .as_bytes() .to_vec(); - let builder_tx_gas = ctx.builder_signer().map_or(0, |_| { - OpPayloadBuilderCtx::::estimate_gas_for_builder_tx(message.clone()) - }); + let builder_tx_gas = ctx + .builder_signer() + .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); let block_gas_limit = ctx.block_gas_limit() - builder_tx_gas; + // Save some space in the block_da_limit for builder tx + let builder_tx_da_size = ctx + .estimate_builder_tx_da_size(state, builder_tx_gas, message.clone()) + .unwrap_or(0); + let block_da_limit = ctx + .da_config + .max_da_block_size() + .map(|da_size| da_size - builder_tx_da_size as u64); + // Check that it's possible to create builder tx, considering max_da_tx_size, otherwise panic + if let Some(tx_da_limit) = ctx.da_config.max_da_tx_size() { + // Panic indicate max_da_tx_size misconfiguration + assert!( + tx_da_limit >= builder_tx_da_size as u64, + "The configured da_config.max_da_tx_size is too small to accommodate builder tx." + ); + } + if !ctx.attributes().no_tx_pool { let best_txs_start_time = Instant::now(); let best_txs = best(ctx.best_transaction_attributes()); @@ -299,7 +470,13 @@ where .transaction_pool_fetch_duration .record(best_txs_start_time.elapsed()); if ctx - .execute_best_transactions(&mut info, state, best_txs, block_gas_limit)? + .execute_best_transactions( + &mut info, + state, + best_txs, + block_gas_limit, + block_da_limit, + )? .is_some() { return Ok(BuildOutcomeKind::Cancelled); @@ -309,8 +486,6 @@ where // Add builder tx to the block ctx.add_builder_tx(&mut info, state, builder_tx_gas, message); - let withdrawals_root = ctx.commit_withdrawals(state)?; - let state_merge_start_time = Instant::now(); // merge all transitions into bundle state, this would apply the withdrawal balance changes @@ -324,24 +499,42 @@ where .payload_num_tx .record(info.executed_transactions.len() as f64); - Ok(BuildOutcomeKind::Better { - payload: ExecutedPayload { - info, - withdrawals_root, - }, - }) + let withdrawals_root = if ctx.is_isthmus_active() { + // withdrawals root field in block header is used for storage root of L2 predeploy + // `l2tol1-message-passer` + Some( + state + .database + .as_ref() + .storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, Default::default())?, + ) + } else if ctx.is_canyon_active() { + Some(EMPTY_WITHDRAWALS) + } else { + None + }; + + let payload = ExecutedPayload { + info, + withdrawals_root, + }; + + Ok(BuildOutcomeKind::Better { payload }) } /// Builds the payload on top of the state. - pub fn build( + pub fn build( self, mut state: State, - ctx: OpPayloadBuilderCtx, - ) -> Result, PayloadBuilderError> + ctx: OpPayloadBuilderCtx, + ) -> Result>, PayloadBuilderError> where - EvmConfig: ConfigureEvm

, + EvmConfig: ConfigureEvmFor, + ChainSpec: EthChainSpec + OpHardforks, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, + Txs: PayloadTransactions>, DB: Database + AsRef

, - P: StateRootProvider + HashedPostStateProvider, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { let ExecutedPayload { info, @@ -355,7 +548,7 @@ where let block_number = ctx.block_number(); let execution_outcome = ExecutionOutcome::new( state.take_bundle(), - info.receipts.into(), + vec![info.receipts], block_number, Vec::new(), ); @@ -407,7 +600,7 @@ where let header = Header { parent_hash: ctx.parent().hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: ctx.initialized_block_env.coinbase, + beneficiary: ctx.evm_env.block_env.coinbase, state_root, transactions_root, receipts_root, @@ -429,24 +622,28 @@ where }; // seal the block - let block = Block { + let block = N::Block::new( header, - body: BlockBody { + 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_block_header = ?sealed_block.header, "sealed built block"); + info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); // create the executed block data - let executed: ExecutedBlock = ExecutedBlock { - block: sealed_block.clone(), - senders: Arc::new(info.executed_senders), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), + let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { + block: 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: Arc::new(trie_output), }; @@ -456,8 +653,6 @@ where ctx.payload_id(), sealed_block, info.total_fees, - ctx.chain_spec.clone(), - ctx.config.attributes, Some(executed), ); @@ -477,55 +672,53 @@ where } /// A type that returns a the [`PayloadTransactions`] that should be included in the pool. -pub trait OpPayloadTransactions: Clone + Send + Sync + Unpin + 'static { +pub 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< - Pool: TransactionPool>, - >( + fn best_transactions>( &self, pool: Pool, attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions; + ) -> impl PayloadTransactions; } -impl OpPayloadTransactions for () { - fn best_transactions< - Pool: TransactionPool>, - >( +impl OpPayloadTransactions for () { + fn best_transactions>( &self, pool: Pool, attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions { + ) -> impl PayloadTransactions { BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) } } /// Holds the state after execution #[derive(Debug)] -pub struct ExecutedPayload { +pub struct ExecutedPayload { /// Tracked execution info - pub info: ExecutionInfo, + pub info: ExecutionInfo, /// Withdrawal hash. pub withdrawals_root: Option, } /// This acts as the container for executed transactions and its byproducts (receipts, gas used) #[derive(Default, Debug)] -pub struct ExecutionInfo { +pub struct ExecutionInfo { /// All executed transactions (unrecovered). - pub executed_transactions: Vec, + pub executed_transactions: Vec, /// The recovered senders for the executed transactions. pub executed_senders: Vec

, /// The transaction receipts - pub receipts: Vec, + pub receipts: Vec, /// All gas used so far pub cumulative_gas_used: u64, + /// Estimated DA size + pub cumulative_da_bytes_used: u64, /// Tracks fees from executed mempool transactions pub total_fees: U256, } -impl ExecutionInfo { +impl ExecutionInfo { /// Create a new instance with allocated slots. pub fn with_capacity(capacity: usize) -> Self { Self { @@ -533,40 +726,74 @@ impl ExecutionInfo { executed_senders: Vec::with_capacity(capacity), receipts: Vec::with_capacity(capacity), cumulative_gas_used: 0, + cumulative_da_bytes_used: 0, total_fees: U256::ZERO, } } + + /// Returns true if the transaction would exceed the block limits: + /// - block gas limit: ensures the transaction still fits into the block. + /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit + /// per tx. + /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the + /// maximum allowed DA limit per block. + pub fn is_tx_over_limits( + &self, + tx: &N::SignedTx, + block_gas_limit: u64, + tx_data_limit: Option, + block_data_limit: Option, + ) -> bool { + if tx_data_limit.is_some_and(|da_limit| tx.length() as u64 > da_limit) { + return true; + } + + if block_data_limit + .is_some_and(|da_limit| self.cumulative_da_bytes_used + (tx.length() as u64) > da_limit) + { + return true; + } + + self.cumulative_gas_used + tx.gas_limit() > block_gas_limit + } } /// 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: EvmConfig, + /// The DA config for the payload builder + pub da_config: OpDAConfig, /// The chainspec - pub chain_spec: Arc, + pub chain_spec: Arc, /// How to build the payload. - pub config: PayloadConfig, + pub config: PayloadConfig>, /// Evm Settings - pub initialized_cfg: CfgEnvWithHandlerCfg, - /// Block config - pub initialized_block_env: BlockEnv, + pub evm_env: EvmEnv, /// Marker to check whether the job has been cancelled. pub cancel: CancellationToken, + /// Receipt builder. + pub receipt_builder: Arc>, /// The builder signer pub builder_signer: Option, /// The metrics for the builder pub metrics: OpRBuilderMetrics, } -impl OpPayloadBuilderCtx { +impl OpPayloadBuilderCtx +where + EvmConfig: ConfigureEvmEnv, + ChainSpec: EthChainSpec + OpHardforks, + N: NodePrimitives, +{ /// Returns the parent block the payload will be build on. pub fn parent(&self) -> &SealedHeader { &self.config.parent_header } /// Returns the builder attributes. - pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { + pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { &self.config.attributes } @@ -581,22 +808,23 @@ impl OpPayloadBuilderCtx { pub fn block_gas_limit(&self) -> u64 { self.attributes() .gas_limit - .unwrap_or_else(|| self.initialized_block_env.gas_limit.saturating_to()) + .unwrap_or_else(|| self.evm_env.block_env.gas_limit.saturating_to()) } /// Returns the block number for the block. pub fn block_number(&self) -> u64 { - self.initialized_block_env.number.to() + self.evm_env.block_env.number.to() } /// Returns the current base fee pub fn base_fee(&self) -> u64 { - self.initialized_block_env.basefee.to() + self.evm_env.block_env.basefee.to() } /// Returns the current blob gas price. pub fn get_blob_gasprice(&self) -> Option { - self.initialized_block_env + self.evm_env + .block_env .get_blob_gasprice() .map(|gasprice| gasprice as u64) } @@ -666,9 +894,15 @@ impl OpPayloadBuilderCtx { .is_holocene_active_at_timestamp(self.attributes().timestamp()) } + /// Returns true if isthmus is active for the payload. + pub fn is_isthmus_active(&self) -> bool { + self.chain_spec + .is_isthmus_active_at_timestamp(self.attributes().timestamp()) + } + /// Returns the chain id pub fn chain_id(&self) -> u64 { - self.chain_spec.chain.id() + self.chain_spec.chain_id() } /// Returns the builder signer @@ -676,19 +910,6 @@ impl OpPayloadBuilderCtx { self.builder_signer } - /// Commits the withdrawals from the payload attributes to the state. - pub fn commit_withdrawals(&self, db: &mut State) -> Result, ProviderError> - where - DB: Database, - { - commit_withdrawals( - db, - &self.chain_spec, - self.attributes().payload_attributes.timestamp, - &self.attributes().payload_attributes.withdrawals, - ) - } - /// Ensure that the create2deployer is force-deployed at the canyon transition. Optimism /// blocks will always have at least a single transaction in them (the L1 info transaction), /// so we can safely assume that this will always be triggered upon the transition and that @@ -710,9 +931,11 @@ impl OpPayloadBuilderCtx { } } -impl OpPayloadBuilderCtx +impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvm
, + EvmConfig: ConfigureEvmFor, + ChainSpec: EthChainSpec + OpHardforks, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, { /// apply eip-4788 pre block contract call pub fn apply_pre_beacon_root_contract_call( @@ -722,12 +945,12 @@ where where DB: Database + DatabaseCommit, DB::Error: Display, + ::Error: StdError, { SystemCaller::new(self.evm_config.clone(), self.chain_spec.clone()) .pre_block_beacon_root_contract_call( db, - &self.initialized_cfg, - &self.initialized_block_env, + &self.evm_env, self.attributes() .payload_attributes .parent_beacon_block_root, @@ -744,22 +967,55 @@ where Ok(()) } + /// Constructs a receipt for the given transaction. + fn build_receipt( + &self, + info: &ExecutionInfo, + result: ExecutionResult, + deposit_nonce: Option, + tx: &N::SignedTx, + ) -> N::Receipt { + match self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx, + result, + cumulative_gas_used: info.cumulative_gas_used, + }) { + Ok(receipt) => receipt, + Err(ctx) => { + let receipt = alloy_consensus::Receipt { + // Success flag was added in `EIP-658: Embedding transaction status code + // in receipts`. + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + }; + + self.receipt_builder + .build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_nonce, + // The deposit receipt version was introduced in Canyon to indicate an + // update to how receipt hashes should be computed + // when set. The state transition process ensures + // this is only set for post-Canyon deposit + // transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + }) + } + } + } + /// Executes all sequencer transactions that are included in the payload attributes. pub fn execute_sequencer_transactions( &self, db: &mut State, - ) -> Result + ) -> Result, PayloadBuilderError> where DB: Database, { let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); - let env = EnvWithHandlerCfg::new_with_cfg_env( - self.initialized_cfg.clone(), - self.initialized_block_env.clone(), - TxEnv::default(), - ); - let mut evm = self.evm_config.evm_with_env(&mut *db, env); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); for sequencer_tx in &self.attributes().transactions { // A sequencer's block should never contain blob transactions. @@ -769,14 +1025,13 @@ where )); } - // Convert the transaction to a [TransactionSignedEcRecovered]. This is + // Convert the transaction to a [Recovered]. This is // purely for the purposes of utilizing the `evm_config.tx_env`` function. // Deposit transactions do not have signatures, so if the tx is a deposit, this // will just pull in its `from` address. let sequencer_tx = sequencer_tx .value() - .clone() - .try_into_ecrecovered() + .try_clone_into_recovered() .map_err(|_| { PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) })?; @@ -786,11 +1041,11 @@ where // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces // were not introduced in Bedrock. In addition, regular transactions don't have deposit // nonces, so we don't need to touch the DB for those. - let depositor = (self.is_regolith_active() && sequencer_tx.is_deposit()) + let depositor_nonce = (self.is_regolith_active() && sequencer_tx.is_deposit()) .then(|| { evm.db_mut() .load_cache_account(sequencer_tx.signer()) - .map(|acc| acc.account_info().unwrap_or_default()) + .map(|acc| acc.account_info().unwrap_or_default().nonce) }) .transpose() .map_err(|_| { @@ -799,23 +1054,19 @@ where )) })?; - *evm.tx_mut() = self + let tx_env = self .evm_config .tx_env(sequencer_tx.tx(), sequencer_tx.signer()); - let ResultAndState { result, state } = match evm.transact() { + let ResultAndState { result, state } = match evm.transact(tx_env) { Ok(res) => res, Err(err) => { - match err { - EVMError::Transaction(err) => { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue; - } - err => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)); - } + if err.is_invalid_tx_err() { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; } + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); } }; @@ -827,28 +1078,12 @@ where // add gas used by the transaction to cumulative gas used, before creating the receipt info.cumulative_gas_used += gas_used; - let receipt = alloy_consensus::Receipt { - status: Eip658Value::Eip658(result.is_success()), - cumulative_gas_used: info.cumulative_gas_used, - logs: result.into_logs().into_iter().collect(), - }; - - // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts.push(match sequencer_tx.tx_type() { - OpTxType::Legacy => OpReceipt::Legacy(receipt), - OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), - OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), - OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), - OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { - inner: receipt, - deposit_nonce: depositor.map(|account| account.nonce), - // The deposit receipt version was introduced in Canyon to indicate an update to - // how receipt hashes should be computed when set. The state - // transition process ensures this is only set for - // post-Canyon deposit transactions. - deposit_receipt_version: self.is_canyon_active().then_some(1), - }), - }); + info.receipts.push(self.build_receipt( + &info, + result, + depositor_nonce, + sequencer_tx.tx(), + )); // append sender and transaction to the respective lists info.executed_senders.push(sequencer_tx.signer()); @@ -863,10 +1098,13 @@ where /// Returns `Ok(Some(())` if the job was cancelled. pub fn execute_best_transactions( &self, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, db: &mut State, - mut best_txs: impl PayloadTransactions, + mut best_txs: impl PayloadTransactions< + Transaction: PoolTransaction, + >, block_gas_limit: u64, + block_da_limit: Option, ) -> Result, PayloadBuilderError> where DB: Database, @@ -877,18 +1115,14 @@ where let mut num_txs_simulated_success = 0; let mut num_txs_simulated_fail = 0; let base_fee = self.base_fee(); - - let env = EnvWithHandlerCfg::new_with_cfg_env( - self.initialized_cfg.clone(), - self.initialized_block_env.clone(), - TxEnv::default(), - ); - let mut evm = self.evm_config.evm_with_env(&mut *db, env); + let tx_da_limit = self.da_config.max_da_tx_size(); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); while let Some(tx) = best_txs.next(()) { + let tx = tx.into_consensus(); num_txs_considered += 1; // ensure we still have capacity for this transaction - if info.cumulative_gas_used + tx.gas_limit() > block_gas_limit { + if info.is_tx_over_limits(tx.tx(), block_gas_limit, tx_da_limit, block_da_limit) { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from // the iterator before we can continue @@ -897,7 +1131,7 @@ where } // A sequencer's block should never contain blob or deposit transactions from the pool. - if tx.is_eip4844() || tx.tx_type() == TxType::Deposit as u8 { + if tx.is_eip4844() || tx.is_deposit() { best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } @@ -908,32 +1142,28 @@ where } // Configure the environment for the tx. - *evm.tx_mut() = self.evm_config.tx_env(tx.tx(), tx.signer()); + let tx_env = self.evm_config.tx_env(tx.tx(), tx.signer()); let tx_simulation_start_time = Instant::now(); - let ResultAndState { result, state } = match evm.transact() { + let ResultAndState { result, state } = match evm.transact(tx_env) { Ok(res) => res, Err(err) => { - match err { - EVMError::Transaction(err) => { - if matches!(err, InvalidTransaction::NonceTooLow { .. }) { - // if the nonce is too low, we can skip this transaction - trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - } - - continue; - } - err => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)); + if let Some(err) = err.as_invalid_tx_err() { + if err.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); } + + continue; } + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); } }; @@ -946,16 +1176,8 @@ where num_txs_simulated_success += 1; } else { num_txs_simulated_fail += 1; + continue; } - self.metrics - .payload_num_tx_simulated - .record(num_txs_simulated as f64); - self.metrics - .payload_num_tx_simulated_success - .record(num_txs_simulated_success as f64); - self.metrics - .payload_num_tx_simulated_fail - .record(num_txs_simulated_fail as f64); // commit changes evm.db_mut().commit(state); @@ -966,24 +1188,9 @@ where // receipt info.cumulative_gas_used += gas_used; - let receipt = alloy_consensus::Receipt { - status: Eip658Value::Eip658(result.is_success()), - cumulative_gas_used: info.cumulative_gas_used, - logs: result.into_logs().into_iter().collect(), - }; - // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts.push(match tx.tx_type() { - OpTxType::Legacy => OpReceipt::Legacy(receipt), - OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), - OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), - OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), - OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { - inner: receipt, - deposit_nonce: None, - deposit_receipt_version: None, - }), - }); + info.receipts + .push(self.build_receipt(info, result, None, &tx)); // update add to total fees let miner_fee = tx @@ -1002,13 +1209,22 @@ where self.metrics .payload_num_tx_considered .record(num_txs_considered as f64); + self.metrics + .payload_num_tx_simulated + .record(num_txs_simulated as f64); + self.metrics + .payload_num_tx_simulated_success + .record(num_txs_simulated_success as f64); + self.metrics + .payload_num_tx_simulated_fail + .record(num_txs_simulated_fail as f64); Ok(None) } pub fn add_builder_tx( &self, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, db: &mut State, builder_tx_gas: u64, message: Vec, @@ -1019,44 +1235,17 @@ where self.builder_signer() .map(|signer| { let base_fee = self.base_fee(); - // Create message with block number for the builder to sign - let nonce = db - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( - signer.address, - )) - })?; - - // Create the EIP-1559 transaction - let eip1559 = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: self.chain_id(), - nonce, - gas_limit: builder_tx_gas, - max_fee_per_gas: 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() - }); - let tx = eip1559; - - // Sign the transaction - let builder_tx = signer.sign_tx(tx).map_err(PayloadBuilderError::other)?; - - let env = EnvWithHandlerCfg::new_with_cfg_env( - self.initialized_cfg.clone(), - self.initialized_block_env.clone(), - TxEnv::default(), - ); - let mut evm = self.evm_config.evm_with_env(&mut *db, env); - *evm.tx_mut() = self.evm_config.tx_env(builder_tx.tx(), builder_tx.signer()); + let chain_id = self.chain_id(); + // Create and sign the transaction + let builder_tx = + signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; + + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + let tx_env = self.evm_config.tx_env(builder_tx.tx(), builder_tx.signer()); let ResultAndState { result, state } = evm - .transact() - .map_err(PayloadBuilderError::EvmExecutionError)?; + .transact(tx_env) + .map_err(|err| PayloadBuilderError::EvmExecutionError(Box::new(err)))?; // Release the db reference by dropping evm drop(evm); @@ -1068,14 +1257,8 @@ where // Add gas used by the transaction to cumulative gas used, before creating the receipt info.cumulative_gas_used += gas_used; - let receipt = alloy_consensus::Receipt { - status: Eip658Value::Eip658(result.is_success()), - cumulative_gas_used: info.cumulative_gas_used, - logs: result.into_logs().into_iter().collect(), - }; - - // Push transaction changeset and calculate header bloom filter for receipt - info.receipts.push(OpReceipt::Eip1559(receipt)); + info.receipts + .push(self.build_receipt(info, result, None, &builder_tx)); // Append sender and transaction to the respective lists info.executed_senders.push(builder_tx.signer()); @@ -1089,20 +1272,84 @@ where }) } - fn estimate_gas_for_builder_tx(input: Vec) -> 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) - } - }); + /// Calculates EIP 2718 builder transaction size + pub fn estimate_builder_tx_da_size( + &self, + db: &mut State, + builder_tx_gas: u64, + message: Vec, + ) -> Option + where + DB: Database, + { + self.builder_signer() + .map(|signer| { + let base_fee = self.base_fee(); + let chain_id = self.chain_id(); + // Create and sign the transaction + let builder_tx = + signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; + Ok(builder_tx.length()) + }) + .transpose() + .unwrap_or_else(|err: PayloadBuilderError| { + warn!(target: "payload_builder", %err, "Failed to add builder transaction"); + None + }) + } +} + +/// Creates signed builder tx to Address::ZERO and specified message as input +pub fn signed_builder_tx( + db: &mut State, + builder_tx_gas: u64, + message: Vec, + signer: Signer, + base_fee: u64, + chain_id: u64, +) -> Result, PayloadBuilderError> +where + DB: Database, +{ + // Create message with block number for the builder to sign + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed(signer.address)) + })?; + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id, + nonce, + gas_limit: builder_tx_gas, + max_fee_per_gas: 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(PayloadBuilderError::other)?; + + Ok(builder_tx) +} - // 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; +fn estimate_gas_for_builder_tx(input: Vec) -> 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) + } + }); - zero_cost + nonzero_cost + 21_000 - } + // 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; + + zero_cost + nonzero_cost + 21_000 } diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 592f86ef..7261a3b5 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -12,6 +12,7 @@ use alloy_rpc_types_engine::ExecutionPayloadV2; use alloy_rpc_types_engine::PayloadAttributes; use alloy_rpc_types_engine::PayloadStatusEnum; use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatus}; +use alloy_rpc_types_eth::Block; use jsonrpsee::core::RpcResult; use jsonrpsee::http_client::{transport::HttpBackend, HttpClient}; use jsonrpsee::proc_macros::rpc; @@ -212,7 +213,7 @@ impl<'a> BlockGenerator<'a> { } /// Initialize the block generator by fetching the latest block - pub async fn init(&mut self) -> eyre::Result<()> { + pub async fn init(&mut self) -> eyre::Result { let latest_block = self.engine_api.latest().await?.expect("block not found"); self.latest_hash = latest_block.header.hash; @@ -221,7 +222,7 @@ impl<'a> BlockGenerator<'a> { self.sync_validation_node(validation_api).await?; } - Ok(()) + Ok(latest_block) } /// Sync the validation node to the current state diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs index bad8d65d..c1d0ab06 100644 --- a/crates/builder/op-rbuilder/src/tx_signer.rs +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -4,7 +4,7 @@ use alloy_consensus::SignableTransaction; use alloy_primitives::{Address, PrimitiveSignature as Signature, B256, U256}; use op_alloy_consensus::OpTypedTransaction; use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::{public_key_to_address, TransactionSignedEcRecovered}; +use reth_primitives::{public_key_to_address, Recovered}; use secp256k1::{Message, SecretKey, SECP256K1}; /// Simple struct to sign txs/messages. @@ -40,7 +40,7 @@ impl Signer { pub fn sign_tx( &self, tx: OpTypedTransaction, - ) -> Result, secp256k1::Error> { + ) -> Result, secp256k1::Error> { let signature_hash = match &tx { OpTypedTransaction::Legacy(tx) => tx.signature_hash(), OpTypedTransaction::Eip2930(tx) => tx.signature_hash(), @@ -50,12 +50,7 @@ impl Signer { }; let signature = self.sign_message(signature_hash)?; let signed = OpTransactionSigned::new_unhashed(tx, signature); - Ok( - TransactionSignedEcRecovered::::new_unchecked( - signed, - self.address, - ), - ) + Ok(Recovered::new_unchecked(signed, self.address)) } pub fn random() -> Self { @@ -101,6 +96,6 @@ mod test { assert_eq!(signed_tx.signer(), address); let signed = signed_tx.into_tx(); - assert_eq!(signed.recover_signer(), Some(address)); + assert_eq!(signed.recover_signer().ok(), Some(address)); } } From 4dcce3d253a9a37d3dd5870518e5daa1af47bb66 Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 27 Feb 2025 21:04:43 +1100 Subject: [PATCH 031/262] Add metrics for reverted transactions and builder balance in exex (#451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/src/metrics.rs | 12 ++++ crates/builder/op-rbuilder/src/monitoring.rs | 65 ++++++++++++++++++-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 5f8784f6..4451d0b5 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -4,6 +4,8 @@ use reth_metrics::{metrics::Counter, metrics::Gauge, metrics::Histogram, Metrics #[derive(Metrics, Clone)] #[metrics(scope = "op_rbuilder")] pub struct OpRBuilderMetrics { + /// Builder balance of the last block + pub builder_balance: Gauge, /// Number of builder landed blocks pub builder_landed_blocks: Gauge, /// Last built block height @@ -40,9 +42,15 @@ pub struct OpRBuilderMetrics { pub tx_simulation_duration: Histogram, /// Byte size of transactions pub tx_byte_size: Histogram, + /// Number of reverted transactions + pub num_reverted_tx: Counter, } impl OpRBuilderMetrics { + pub fn inc_num_reverted_tx(&self, num_reverted_tx: usize) { + self.num_reverted_tx.increment(num_reverted_tx as u64); + } + pub fn inc_builder_landed_blocks(&self) { self.builder_landed_blocks.increment(1); } @@ -58,4 +66,8 @@ impl OpRBuilderMetrics { pub fn set_last_landed_block_height(&self, height: u64) { self.last_landed_block_height.set(height as f64); } + + pub fn set_builder_balance(&self, balance: f64) { + self.builder_balance.set(balance); + } } diff --git a/crates/builder/op-rbuilder/src/monitoring.rs b/crates/builder/op-rbuilder/src/monitoring.rs index 9aa51e65..f94c2754 100644 --- a/crates/builder/op-rbuilder/src/monitoring.rs +++ b/crates/builder/op-rbuilder/src/monitoring.rs @@ -1,13 +1,14 @@ use alloy_consensus::{Transaction, TxReceipt}; +use alloy_primitives::U256; use futures_util::{Stream, StreamExt, TryStreamExt}; use reth::core::primitives::SignedTransaction; use reth_chain_state::CanonStateNotification; use reth_exex::{ExExContext, ExExEvent}; use reth_node_api::{FullNodeComponents, NodeTypes}; -use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; +use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; use reth_primitives::{Block, RecoveredBlock}; -use reth_provider::Chain; -use tracing::info; +use reth_provider::{Chain, ExecutionOutcome}; +use tracing::{info, warn}; use crate::{metrics::OpRBuilderMetrics, tx_signer::Signer}; @@ -16,6 +17,7 @@ const OP_BUILDER_TX_PREFIX: &[u8] = b"Block Number:"; pub struct Monitoring { builder_signer: Option, metrics: OpRBuilderMetrics, + execution_outcome: ExecutionOutcome, } impl Monitoring { @@ -23,6 +25,7 @@ impl Monitoring { Self { builder_signer, metrics: Default::default(), + execution_outcome: Default::default(), } } @@ -31,7 +34,6 @@ impl Monitoring { where Node: FullNodeComponents>, { - // TODO: add builder balance monitoring // Process all new chain state notifications while let Some(notification) = ctx.notifications.try_next().await? { if let Some(reverted_chain) = notification.reverted_chain() { @@ -83,6 +85,27 @@ impl Monitoring { } } + let num_reverted_tx = decode_chain_into_reverted_txs(chain); + self.metrics.inc_num_reverted_tx(num_reverted_tx); + + self.execution_outcome + .extend(chain.execution_outcome().clone()); + let builder_balance = + decode_state_into_builder_balance(&self.execution_outcome, self.builder_signer) + .and_then(|balance| { + balance + .to_string() + .parse::() + .map_err(|e| { + warn!("Failed to parse builder balance: {}", e); + e + }) + .ok() + }); + if let Some(balance) = builder_balance { + self.metrics.set_builder_balance(balance); + } + Ok(()) } @@ -91,6 +114,7 @@ impl Monitoring { /// This function decodes all transactions in the block, updates the metrics for builder built blocks async fn revert(&mut self, chain: &Chain) -> eyre::Result<()> { info!("Processing new chain revert"); + self.execution_outcome.revert_to(chain.first().number - 1); let mut blocks = decode_chain_into_builder_txs(chain, self.builder_signer); // Reverse the order of txs to start reverting from the tip blocks.reverse(); @@ -140,3 +164,36 @@ fn decode_chain_into_builder_txs( }) .collect() } + +/// Decode chain of blocks and check if any transactions has reverted +fn decode_chain_into_reverted_txs(chain: &Chain) -> usize { + chain + // Get all blocks and receipts + .blocks_and_receipts() + // Get all receipts + .map(|(block, receipts)| { + block + .body() + .transactions + .iter() + .zip(receipts.iter()) + .filter(|(_, receipt)| !receipt.status()) + .count() + }) + .sum() +} + +/// Decode state and find the last builder balance +fn decode_state_into_builder_balance( + execution_outcome: &ExecutionOutcome, + builder_signer: Option, +) -> Option { + builder_signer.and_then(|signer| { + execution_outcome + .bundle + .state + .iter() + .find(|(address, _)| *address == &signer.address) + .and_then(|(_, account)| account.info.as_ref().map(|info| info.balance)) + }) +} From bbb95f361f3fc44a323cb3da2b8f3fbe9a545ad8 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 27 Feb 2025 18:42:59 +0000 Subject: [PATCH 032/262] Flashblocks with incremental blocks + support in tester (#454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR changes uses the incremental format for Flashblocks and adds support for it on tester. Generate a genesis file: ``` $ cargo run -p op-rbuilder --bin tester --features optimism -- genesis --output genesis.json ``` Run op-rbuilder with flashblocks ``` $ cargo run -p op-rbuilder --bin op-rbuilder --features optimism,flashblocks node --authrpc.port 4444 --authrpc.jwtsecret ./crates/op-rbuilder/src/tester/fixtures/test-jwt-secret.txt --chain ./genesis.json --datadir /tmp/builder2 --disable-discovery --http --http.port 8545 ``` Run the tester with flashblocks enabled: ``` $ cargo run -p op-rbuilder --bin tester --features optimism -- run --flashblocks-endpoint ws://localhost:1111 ``` Now, there are two streams available, one from the builder (websocat ws://localhost:1111) and another one from the tester which mimicks the Rollup-boost preconfirmations (websocat ws://localhost:1112). ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --------- Co-authored-by: Daniel Xifra --- crates/builder/op-rbuilder/Cargo.toml | 16 +- crates/builder/op-rbuilder/src/generator.rs | 3 + .../src/integration/integration_test.rs | 4 +- crates/builder/op-rbuilder/src/main.rs | 11 +- .../op-rbuilder/src/payload_builder.rs | 689 +++++++++++------- crates/builder/op-rbuilder/src/tester/main.rs | 16 +- crates/builder/op-rbuilder/src/tester/mod.rs | 37 +- 7 files changed, 505 insertions(+), 271 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 7c49203a..16ac91cb 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -79,8 +79,12 @@ tokio-util.workspace = true time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } -tokio-tungstenite = "0.26.1" +tokio-tungstenite = "0.26.2" rand = "0.8.5" +alloy-serde = "0.7" +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } + +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "e74a1fd01366e4ddd13515da4efda59cdc8fbce0" } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -94,13 +98,13 @@ default = ["jemalloc"] jemalloc = [ "dep:tikv-jemallocator", "reth-cli-util/jemalloc", - "reth-optimism-cli/jemalloc" + "reth-optimism-cli/jemalloc", ] jemalloc-prof = [ "jemalloc", "tikv-jemallocator?/profiling", "reth/jemalloc-prof", - "reth-cli-util/jemalloc-prof" + "reth-cli-util/jemalloc-prof", ] min-error-logs = ["tracing/release_max_level_error"] @@ -109,12 +113,10 @@ min-info-logs = ["tracing/release_max_level_info"] min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] -optimism = [ - "reth-optimism-node/optimism", - "reth-optimism-cli/optimism" -] +optimism = ["reth-optimism-node/optimism", "reth-optimism-cli/optimism"] integration = [] +flashblocks = [] [[bin]] name = "op-rbuilder" diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index b55def99..1b3a8a50 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -157,6 +157,9 @@ where // that cancels existing jobs when receiving new block building requests. let deadline = job_deadline(attributes.timestamp()) + Duration::from_millis(500); + println!("attributes timestmap: {:?}", attributes.timestamp()); + println!("Deadline: {:?}", deadline); + let deadline = Box::pin(tokio::time::sleep(deadline)); let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 1880de87..5cdc4998 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -61,7 +61,7 @@ mod tests { let engine_api = EngineApi::new("http://localhost:1234").unwrap(); let validation_api = EngineApi::new("http://localhost:1236").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1); + let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None); generator.init().await?; let provider = ProviderBuilder::::default() @@ -132,7 +132,7 @@ mod tests { let engine_api = EngineApi::new("http://localhost:1244").unwrap(); let validation_api = EngineApi::new("http://localhost:1246").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1); + let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None); let latest_block = generator.init().await?; let provider = ProviderBuilder::::default() diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index f6ce402b..770c3610 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,20 +1,25 @@ use clap::Parser; use monitoring::Monitoring; -use payload_builder_vanilla::CustomOpPayloadBuilder; use reth::providers::CanonStateSubscriptions; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; use reth_optimism_node::node::OpAddOnsBuilder; use reth_optimism_node::OpNode; +#[cfg(feature = "flashblocks")] +use payload_builder::CustomOpPayloadBuilder; +#[cfg(not(feature = "flashblocks"))] +use payload_builder_vanilla::CustomOpPayloadBuilder; + /// CLI argument parsing. pub mod args; - pub mod generator; #[cfg(test)] mod integration; mod metrics; mod monitoring; -// pub mod payload_builder; +#[cfg(feature = "flashblocks")] +pub mod payload_builder; +#[cfg(not(feature = "flashblocks"))] mod payload_builder_vanilla; #[cfg(test)] mod tester; diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 7a9c25aa..9fba8e21 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -1,47 +1,72 @@ use std::{fmt::Display, sync::Arc, sync::Mutex}; +use crate::generator::BlockPayloadJobGenerator; use crate::generator::{BlockCell, BuildArguments, PayloadBuilder}; +use crate::tx_signer::Signer; use alloy_consensus::{Eip658Value, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::merge::BEACON_NONCE; +use alloy_eips::Encodable2718; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rpc_types_engine::PayloadId; -use op_alloy_consensus::{OpDepositReceipt, OpTxType}; +use alloy_rpc_types_eth::Withdrawals; +use op_alloy_consensus::OpDepositReceipt; +use reth::builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}; +use reth::payload::PayloadBuilderHandle; +use reth_basic_payload_builder::commit_withdrawals; +use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; +use reth_basic_payload_builder::{BuildOutcome, PayloadConfig}; use reth_chainspec::ChainSpecProvider; -use reth_evm::{env::EvmEnv, system_calls::SystemCaller, ConfigureEvm, NextBlockEnvAttributes}; +use reth_chainspec::EthChainSpec; +use reth_evm::{ + env::EvmEnv, system_calls::SystemCaller, ConfigureEvmEnv, ConfigureEvmFor, Database, Evm, + EvmError, InvalidTxError, NextBlockEnvAttributes, +}; use reth_execution_types::ExecutionOutcome; +use reth_node_api::NodePrimitives; +use reth_node_api::NodeTypesWithEngine; +use reth_node_api::TxTy; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; +use reth_optimism_evm::BasicOpReceiptBuilder; +use reth_optimism_evm::OpEvmConfig; +use reth_optimism_evm::{OpReceiptBuilder, ReceiptBuilderCtx}; use reth_optimism_forks::OpHardforks; -use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +use reth_optimism_node::OpEngineTypes; +use reth_optimism_payload_builder::error::OpPayloadBuilderError; +use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_optimism_payload_builder::OpPayloadPrimitives; +use reth_optimism_primitives::OpPrimitives; +use reth_optimism_primitives::OpTransactionSigned; +use reth_payload_builder::PayloadBuilderService; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; +use reth_payload_util::BestPayloadTransactions; use reth_payload_util::PayloadTransactions; -use reth_primitives::BlockExt; -use reth_primitives::{ - proofs, transaction::SignedTransactionIntoRecoveredExt, Block, BlockBody, SealedHeader, TxType, -}; +use reth_primitives::{transaction::SignedTransactionIntoRecoveredExt, BlockBody, SealedHeader}; +use reth_primitives_traits::proofs; +use reth_primitives_traits::Block as _; +use reth_provider::CanonStateSubscriptions; +use reth_provider::StorageRootProvider; use reth_provider::{ HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, }; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::PoolTransaction; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; +use revm::primitives::ExecutionResult; use revm::{ db::{states::bundle_state::BundleRetention, BundleState, State}, - primitives::{ - BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, InvalidTransaction, - ResultAndState, SpecId, TxEnv, - }, - Database, DatabaseCommit, + primitives::ResultAndState, + DatabaseCommit, }; +use rollup_boost::{ + ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, +}; +use serde_json::Value; +use std::error::Error as StdError; use tokio_util::sync::CancellationToken; use tracing::{debug, trace, warn}; -use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelopeV3; -use reth_optimism_payload_builder::error::OpPayloadBuilderError; -use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; -use reth_transaction_pool::pool::BestPayloadTransactions; - use futures_util::FutureExt; use futures_util::SinkExt; use tokio::net::{TcpListener, TcpStream}; @@ -49,59 +74,166 @@ use tokio::sync::mpsc; use tokio_tungstenite::accept_async; use tokio_tungstenite::WebSocketStream; +#[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] +pub struct CustomOpPayloadBuilder { + #[allow(dead_code)] + builder_signer: Option, +} + +impl CustomOpPayloadBuilder { + pub fn new(builder_signer: Option) -> Self { + Self { builder_signer } + } +} + +impl PayloadServiceBuilder for CustomOpPayloadBuilder +where + Node: FullNodeTypes< + Types: NodeTypesWithEngine< + Engine = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + >, + Pool: TransactionPool>> + + Unpin + + 'static, +{ + type PayloadBuilder = OpPayloadBuilder; + + async fn build_payload_builder( + &self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result { + Ok(OpPayloadBuilder::new( + OpEvmConfig::new(ctx.chain_spec()), + pool, + ctx.provider().clone(), + Arc::new(BasicOpReceiptBuilder::default()), + )) + } + + fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + payload_builder: Self::PayloadBuilder, + ) -> eyre::Result::Engine>> { + tracing::info!("Spawning a custom payload builder"); + let payload_job_config = BasicPayloadJobGeneratorConfig::default(); + + let payload_generator = BlockPayloadJobGenerator::with_builder( + ctx.provider().clone(), + ctx.task_executor().clone(), + payload_job_config, + payload_builder, + true, + ); + + let (payload_service, payload_builder) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical("custom payload builder service", Box::pin(payload_service)); + + tracing::info!("Custom payload service started"); + + Ok(payload_builder) + } +} + +impl reth_basic_payload_builder::PayloadBuilder + for OpPayloadBuilder +where + Pool: Clone + Send + Sync, + Client: Clone + Send + Sync, + EvmConfig: Clone + Send + Sync, + N: NodePrimitives, +{ + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + _args: reth_basic_payload_builder::BuildArguments, + ) -> Result, PayloadBuilderError> { + unimplemented!() + } + + fn build_empty_payload( + &self, + _config: reth_basic_payload_builder::PayloadConfig< + Self::Attributes, + reth_basic_payload_builder::HeaderForPayload, + >, + ) -> Result { + unimplemented!() + } +} + /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct OpPayloadBuilder { +pub struct OpPayloadBuilder { /// The type responsible for creating the evm. pub evm_config: EvmConfig, - /// The type responsible for yielding the best transactions for the payload if mempool - /// transactions are allowed. - pub best_transactions: Txs, - /// WebSocket subscribers - pub subscribers: Arc>>>, + /// The transaction pool + pub pool: Pool, + /// Node client + pub client: Client, /// Channel sender for publishing messages pub tx: mpsc::UnboundedSender, + /// Node primitive types. + pub receipt_builder: Arc>, } -impl OpPayloadBuilder { +impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. - pub fn new(evm_config: EvmConfig) -> Self { + pub fn new( + evm_config: EvmConfig, + pool: Pool, + client: Client, + receipt_builder: Arc>, + ) -> Self { let (tx, rx) = mpsc::unbounded_channel(); let subscribers = Arc::new(Mutex::new(Vec::new())); Self::publish_task(rx, subscribers.clone()); + tokio::spawn(async move { + Self::start_ws(subscribers, "127.0.0.1:1111").await; + }); + Self { evm_config, - best_transactions: (), - subscribers, + pool, + client, tx, + receipt_builder, } } /// Start the WebSocket server - pub async fn start_ws(&self, addr: &str) -> Result<(), Box> { - let listener = TcpListener::bind(addr).await?; - let subscribers = self.subscribers.clone(); + pub async fn start_ws(subscribers: Arc>>>, addr: &str) { + let listener = TcpListener::bind(addr).await.unwrap(); + let subscribers = subscribers.clone(); - tokio::spawn(async move { - while let Ok((stream, _)) = listener.accept().await { - tracing::info!("Accepted websocket connection"); - let subscribers = subscribers.clone(); - - tokio::spawn(async move { - match accept_async(stream).await { - Ok(ws_stream) => { - let mut subs = subscribers.lock().unwrap(); - subs.push(ws_stream); - } - Err(e) => eprintln!("Error accepting websocket connection: {}", e), - } - }); - } - }); + tracing::info!("Starting WebSocket server on {}", addr); - Ok(()) + while let Ok((stream, _)) = listener.accept().await { + tracing::info!("Accepted websocket connection"); + let subscribers = subscribers.clone(); + + tokio::spawn(async move { + match accept_async(stream).await { + Ok(ws_stream) => { + let mut subs = subscribers.lock().unwrap(); + subs.push(ws_stream); + } + Err(e) => eprintln!("Error accepting websocket connection: {}", e), + } + }); + } } /// Background task that handles publishing messages to WebSocket subscribers @@ -132,10 +264,12 @@ impl OpPayloadBuilder { } } -impl OpPayloadBuilder +impl OpPayloadBuilder where - EvmConfig: ConfigureEvm
, - Txs: OpPayloadTransactions, + Pool: TransactionPool>, + Client: StateProviderFactory + ChainSpecProvider, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, + EvmConfig: ConfigureEvmFor, { /// Send a message to be published pub fn send_message(&self, message: String) -> Result<(), Box> { @@ -150,41 +284,27 @@ where /// 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( + fn build_payload<'a>( &self, - args: BuildArguments, - best_payload: BlockCell, - ) -> Result<(), PayloadBuilderError> - where - Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool>, - { - let EvmEnv { - cfg_env_with_handler_cfg, - block_env, - } = self - .cfg_and_block_env(&args.config.attributes, &args.config.parent_header) + args: BuildArguments, OpBuiltPayload>, + best_payload: BlockCell>, + ) -> Result<(), PayloadBuilderError> { + let evm_env = self + .evm_env(&args.config.attributes, &args.config.parent_header) .map_err(PayloadBuilderError::other)?; - let BuildArguments { - client, - pool, - config, - cancel, - .. - } = args; + + let BuildArguments { config, cancel, .. } = args; let ctx = OpPayloadBuilderCtx { evm_config: self.evm_config.clone(), - chain_spec: client.chain_spec(), + chain_spec: self.client.chain_spec(), config, - initialized_cfg: cfg_env_with_handler_cfg, - initialized_block_env: block_env, + evm_env, cancel, + receipt_builder: self.receipt_builder.clone(), }; - let best = self.best_transactions.clone(); - - let state_provider = client.state_by_block_hash(ctx.parent().hash())?; + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; let state = StateProviderDatabase::new(&state_provider); let mut db = State::builder() @@ -194,12 +314,10 @@ where // 1. execute the pre steps and seal an early block with that let mut info = execute_pre_steps(&mut db, &ctx)?; - let (payload, mut bundle_state) = build_block(db, &ctx, &info)?; + let (payload, fb_payload, mut bundle_state) = build_block(db, &ctx, &mut info)?; best_payload.set(payload.clone()); - let _ = self.send_message( - serde_json::to_string(&OpExecutionPayloadEnvelopeV3::from(payload)).unwrap_or_default(), - ); + let _ = self.send_message(serde_json::to_string(&fb_payload).unwrap_or_default()); tracing::info!(target: "payload_builder", "Fallback block built"); @@ -230,6 +348,12 @@ where return Ok(()); } + println!( + "Building flashblock {} {}", + ctx.payload_id(), + flashblock_count, + ); + tracing::info!( target: "payload_builder", "Building flashblock {}", @@ -244,7 +368,10 @@ where .with_bundle_prestate(bundle_state) .build(); - let best_txs = best.best_transactions(&pool, ctx.best_transaction_attributes()); + let best_txs = BestPayloadTransactions::new( + self.pool + .best_transactions_with_attributes(ctx.best_transaction_attributes()), + ); ctx.execute_best_transactions(&mut info, &mut db, best_txs, total_gas_per_batch)?; if ctx.cancel.is_cancelled() { @@ -256,13 +383,13 @@ where return Ok(()); } - let (payload, new_bundle_state) = build_block(db, &ctx, &info)?; + let (payload, mut fb_payload, new_bundle_state) = build_block(db, &ctx, &mut info)?; best_payload.set(payload.clone()); - let _ = self.send_message( - serde_json::to_string(&OpExecutionPayloadEnvelopeV3::from(payload)) - .unwrap_or_default(), - ); + + fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 + fb_payload.base = None; + let _ = self.send_message(serde_json::to_string(&fb_payload).unwrap_or_default()); bundle_state = new_bundle_state; total_gas_per_batch += gas_per_batch; @@ -271,62 +398,54 @@ where std::thread::sleep(std::time::Duration::from_millis(250)); } } -} -impl OpPayloadBuilder -where - EvmConfig: ConfigureEvm< - Spec = SpecId, - Header = Header, - Transaction = OpTransactionSigned, - EvmError = EVMError, - >, -{ - /// Returns the configured [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the targeted payload + /// Returns the configured [`EvmEnv`] for the targeted payload /// (that has the `parent` as its parent). - pub fn cfg_and_block_env( + pub fn evm_env( &self, - attributes: &OpPayloadBuilderAttributes, + attributes: &OpPayloadBuilderAttributes, parent: &Header, - ) -> Result { + ) -> Result, EvmConfig::Error> { let next_attributes = NextBlockEnvAttributes { timestamp: attributes.timestamp(), suggested_fee_recipient: attributes.suggested_fee_recipient(), prev_randao: attributes.prev_randao(), - gas_limit: parent.gas_limit, + gas_limit: attributes.gas_limit.unwrap_or(parent.gas_limit), }; - self.evm_config - .next_cfg_and_block_env(parent, next_attributes) + self.evm_config.next_evm_env(parent, next_attributes) } } -impl PayloadBuilder for OpPayloadBuilder +impl PayloadBuilder for OpPayloadBuilder where - Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool>, - EvmConfig: ConfigureEvm
, + Client: StateProviderFactory + ChainSpecProvider + Clone, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, + Pool: TransactionPool>, + EvmConfig: ConfigureEvmFor, { - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; fn try_build( &self, - args: BuildArguments, + args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { self.build_payload(args, best_payload) } } -pub fn build_block( +pub fn build_block( mut state: State, - ctx: &OpPayloadBuilderCtx, - info: &ExecutionInfo, -) -> Result<(OpBuiltPayload, BundleState), PayloadBuilderError> + ctx: &OpPayloadBuilderCtx, + info: &mut ExecutionInfo, +) -> Result<(OpBuiltPayload, FlashblocksPayloadV1, BundleState), PayloadBuilderError> where - EvmConfig: ConfigureEvm
, + EvmConfig: ConfigureEvmFor, + ChainSpec: EthChainSpec + OpHardforks, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, DB: Database + AsRef

, - P: StateRootProvider + HashedPostStateProvider, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { let withdrawals_root = ctx.commit_withdrawals(&mut state)?; @@ -342,9 +461,9 @@ where let execution_outcome = ExecutionOutcome::new( new_bundle.clone(), - info.receipts.clone().into(), + vec![info.receipts.clone()], block_number, - Vec::new(), + vec![], ); let receipts_root = execution_outcome .generic_receipts_root_slow(block_number, |receipts| { @@ -388,7 +507,7 @@ where let header = Header { parent_hash: ctx.parent().hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: ctx.initialized_block_env.coinbase, + beneficiary: ctx.evm_env.block_env.coinbase, state_root, transactions_root, receipts_root, @@ -409,45 +528,85 @@ where requests_hash: None, }; - let withdrawals = Some(ctx.attributes().payload_attributes.withdrawals().clone()); // seal the block - let block = Block { + let block = N::Block::new( header, - body: BlockBody { + BlockBody { transactions: info.executed_transactions.clone(), ommers: vec![], - withdrawals, + withdrawals: ctx.withdrawals().cloned(), }, - }; + ); - let sealed_block: Arc< - reth_primitives::SealedBlock>, - > = Arc::new(block.seal_slow()); + let sealed_block = Arc::new(block.seal_slow()); debug!(target: "payload_builder", ?sealed_block, "sealed built block"); + let block_hash = sealed_block.hash(); + + // pick the new transactions from the info field and update the last flashblock index + let new_transactions = info.executed_transactions[info.last_flashblock_index..].to_vec(); + info.last_flashblock_index = info.executed_transactions.len(); + + let new_transactions_encoded = new_transactions + .into_iter() + .map(|tx| tx.encoded_2718().into()) + .collect::>(); + + // Prepare the flashblocks message + let fb_payload = FlashblocksPayloadV1 { + payload_id: ctx.payload_id(), + index: 0, + base: Some(ExecutionPayloadBaseV1 { + parent_beacon_block_root: ctx + .attributes() + .payload_attributes + .parent_beacon_block_root + .unwrap(), + parent_hash: ctx.parent().hash(), + fee_recipient: ctx.attributes().suggested_fee_recipient(), + prev_randao: ctx.attributes().payload_attributes.prev_randao, + block_number: ctx.parent().number + 1, + gas_limit: ctx.block_gas_limit(), + timestamp: ctx.attributes().payload_attributes.timestamp, + extra_data: ctx.extra_data()?, + base_fee_per_gas: ctx.base_fee().try_into().unwrap(), + }), + diff: ExecutionPayloadFlashblockDeltaV1 { + state_root, + receipts_root, + logs_bloom, + gas_used: info.cumulative_gas_used, + block_hash, + transactions: new_transactions_encoded, + withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), + }, + metadata: Value::Null, + }; + Ok(( OpBuiltPayload::new( ctx.payload_id(), sealed_block, info.total_fees, - ctx.chain_spec.clone(), - ctx.config.attributes.clone(), // This must be set to NONE for now because we are doing merge transitions on every flashblock // when it should only happen once per block, thus, it returns a confusing state back to op-reth. // We can live without this for now because Op syncs up the executed block using new_payload // calls, but eventually we would want to return the executed block here. None, ), + fb_payload, new_bundle, )) } -fn execute_pre_steps( +fn execute_pre_steps( state: &mut State, - ctx: &OpPayloadBuilderCtx, -) -> Result + ctx: &OpPayloadBuilderCtx, +) -> Result, PayloadBuilderError> where - EvmConfig: ConfigureEvm

, + EvmConfig: ConfigureEvmFor, + ChainSpec: EthChainSpec + OpHardforks, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, DB: Database, { // 1. apply eip-4788 pre block contract call @@ -463,55 +622,53 @@ where } /// A type that returns a the [`PayloadTransactions`] that should be included in the pool. -pub trait OpPayloadTransactions: Clone + Send + Sync + Unpin + 'static { +pub 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< - Pool: TransactionPool>, - >( + fn best_transactions>( &self, pool: Pool, attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions; + ) -> impl PayloadTransactions; } -impl OpPayloadTransactions for () { - fn best_transactions< - Pool: TransactionPool>, - >( +impl OpPayloadTransactions for () { + fn best_transactions>( &self, pool: Pool, attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions { + ) -> impl PayloadTransactions { BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) } } /// Holds the state after execution #[derive(Debug)] -pub struct ExecutedPayload { +pub struct ExecutedPayload { /// Tracked execution info - pub info: ExecutionInfo, + pub info: ExecutionInfo, /// Withdrawal hash. pub withdrawals_root: Option, } /// This acts as the container for executed transactions and its byproducts (receipts, gas used) -#[derive(Default, Debug)] -pub struct ExecutionInfo { +#[derive(Default, Debug, Clone)] +pub struct ExecutionInfo { /// All executed transactions (unrecovered). - pub executed_transactions: Vec, + pub executed_transactions: Vec, /// The recovered senders for the executed transactions. pub executed_senders: Vec
, /// The transaction receipts - pub receipts: Vec, + pub receipts: Vec, /// All gas used so far pub cumulative_gas_used: u64, /// Tracks fees from executed mempool transactions pub total_fees: U256, + /// Index of the last consumed flashblock + pub last_flashblock_index: usize, } -impl ExecutionInfo { +impl ExecutionInfo { /// Create a new instance with allocated slots. pub fn with_capacity(capacity: usize) -> Self { Self { @@ -520,58 +677,72 @@ impl ExecutionInfo { receipts: Vec::with_capacity(capacity), cumulative_gas_used: 0, total_fees: U256::ZERO, + last_flashblock_index: 0, } } } /// 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: EvmConfig, /// The chainspec - pub chain_spec: Arc, + pub chain_spec: Arc, /// How to build the payload. - pub config: PayloadConfig, + pub config: PayloadConfig>, /// Evm Settings - pub initialized_cfg: CfgEnvWithHandlerCfg, - /// Block config - pub initialized_block_env: BlockEnv, + pub evm_env: EvmEnv, /// Marker to check whether the job has been cancelled. pub cancel: CancellationToken, + /// Receipt builder. + pub receipt_builder: Arc>, } -impl OpPayloadBuilderCtx { +impl OpPayloadBuilderCtx +where + EvmConfig: ConfigureEvmEnv, + ChainSpec: EthChainSpec + OpHardforks, + N: NodePrimitives, +{ /// Returns the parent block the payload will be build on. pub fn parent(&self) -> &SealedHeader { &self.config.parent_header } /// Returns the builder attributes. - pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { + pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { &self.config.attributes } + /// Returns the withdrawals if shanghai is active. + pub fn withdrawals(&self) -> Option<&Withdrawals> { + self.chain_spec + .is_shanghai_active_at_timestamp(self.attributes().timestamp()) + .then(|| &self.attributes().payload_attributes.withdrawals) + } + /// Returns the block gas limit to target. pub fn block_gas_limit(&self) -> u64 { self.attributes() .gas_limit - .unwrap_or_else(|| self.initialized_block_env.gas_limit.saturating_to()) + .unwrap_or_else(|| self.evm_env.block_env.gas_limit.saturating_to()) } /// Returns the block number for the block. pub fn block_number(&self) -> u64 { - self.initialized_block_env.number.to() + self.evm_env.block_env.number.to() } /// Returns the current base fee pub fn base_fee(&self) -> u64 { - self.initialized_block_env.basefee.to() + self.evm_env.block_env.basefee.to() } /// Returns the current blob gas price. pub fn get_blob_gasprice(&self) -> Option { - self.initialized_block_env + self.evm_env + .block_env .get_blob_gasprice() .map(|gasprice| gasprice as u64) } @@ -675,9 +846,11 @@ impl OpPayloadBuilderCtx { } } -impl OpPayloadBuilderCtx +impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvm
, + EvmConfig: ConfigureEvmFor, + ChainSpec: EthChainSpec + OpHardforks, + N: OpPayloadPrimitives<_TX = OpTransactionSigned>, { /// apply eip-4788 pre block contract call pub fn apply_pre_beacon_root_contract_call( @@ -687,12 +860,12 @@ where where DB: Database + DatabaseCommit, DB::Error: Display, + ::Error: StdError, { SystemCaller::new(self.evm_config.clone(), self.chain_spec.clone()) .pre_block_beacon_root_contract_call( db, - &self.initialized_cfg, - &self.initialized_block_env, + &self.evm_env, self.attributes() .payload_attributes .parent_beacon_block_root, @@ -709,22 +882,55 @@ where Ok(()) } + /// Constructs a receipt for the given transaction. + fn build_receipt( + &self, + info: &ExecutionInfo, + result: ExecutionResult, + deposit_nonce: Option, + tx: &N::SignedTx, + ) -> N::Receipt { + match self.receipt_builder.build_receipt(ReceiptBuilderCtx { + tx, + result, + cumulative_gas_used: info.cumulative_gas_used, + }) { + Ok(receipt) => receipt, + Err(ctx) => { + let receipt = alloy_consensus::Receipt { + // Success flag was added in `EIP-658: Embedding transaction status code + // in receipts`. + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + }; + + self.receipt_builder + .build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_nonce, + // The deposit receipt version was introduced in Canyon to indicate an + // update to how receipt hashes should be computed + // when set. The state transition process ensures + // this is only set for post-Canyon deposit + // transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + }) + } + } + } + /// Executes all sequencer transactions that are included in the payload attributes. pub fn execute_sequencer_transactions( &self, db: &mut State, - ) -> Result + ) -> Result, PayloadBuilderError> where DB: Database, { let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); - let env = EnvWithHandlerCfg::new_with_cfg_env( - self.initialized_cfg.clone(), - self.initialized_block_env.clone(), - TxEnv::default(), - ); - let mut evm = self.evm_config.evm_with_env(&mut *db, env); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); for sequencer_tx in &self.attributes().transactions { // A sequencer's block should never contain blob transactions. @@ -741,7 +947,7 @@ where let sequencer_tx = sequencer_tx .value() .clone() - .try_into_ecrecovered() + .try_clone_into_recovered() .map_err(|_| { PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) })?; @@ -751,11 +957,11 @@ where // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces // were not introduced in Bedrock. In addition, regular transactions don't have deposit // nonces, so we don't need to touch the DB for those. - let depositor = (self.is_regolith_active() && sequencer_tx.is_deposit()) + let depositor_nonce = (self.is_regolith_active() && sequencer_tx.is_deposit()) .then(|| { evm.db_mut() .load_cache_account(sequencer_tx.signer()) - .map(|acc| acc.account_info().unwrap_or_default()) + .map(|acc| acc.account_info().unwrap_or_default().nonce) }) .transpose() .map_err(|_| { @@ -764,23 +970,19 @@ where )) })?; - *evm.tx_mut() = self + let tx_env = self .evm_config .tx_env(sequencer_tx.tx(), sequencer_tx.signer()); - let ResultAndState { result, state } = match evm.transact() { + let ResultAndState { result, state } = match evm.transact(tx_env) { Ok(res) => res, Err(err) => { - match err { - EVMError::Transaction(err) => { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue; - } - err => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)); - } + if err.is_invalid_tx_err() { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; } + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); } }; @@ -792,28 +994,12 @@ where // add gas used by the transaction to cumulative gas used, before creating the receipt info.cumulative_gas_used += gas_used; - let receipt = alloy_consensus::Receipt { - status: Eip658Value::Eip658(result.is_success()), - cumulative_gas_used: info.cumulative_gas_used, - logs: result.into_logs().into_iter().collect(), - }; - - // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts.push(match sequencer_tx.tx_type() { - OpTxType::Legacy => OpReceipt::Legacy(receipt), - OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), - OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), - OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), - OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { - inner: receipt, - deposit_nonce: depositor.map(|account| account.nonce), - // The deposit receipt version was introduced in Canyon to indicate an update to - // how receipt hashes should be computed when set. The state - // transition process ensures this is only set for - // post-Canyon deposit transactions. - deposit_receipt_version: self.is_canyon_active().then_some(1), - }), - }); + info.receipts.push(self.build_receipt( + &info, + result, + depositor_nonce, + sequencer_tx.tx(), + )); // append sender and transaction to the respective lists info.executed_senders.push(sequencer_tx.signer()); @@ -828,24 +1014,32 @@ where /// Returns `Ok(Some(())` if the job was cancelled. pub fn execute_best_transactions( &self, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, db: &mut State, - mut best_txs: impl PayloadTransactions, + mut best_txs: impl PayloadTransactions< + Transaction: PoolTransaction, + >, batch_gas_limit: u64, ) -> Result, PayloadBuilderError> where DB: Database, { + println!("Executing best transactions"); let base_fee = self.base_fee(); - let env = EnvWithHandlerCfg::new_with_cfg_env( - self.initialized_cfg.clone(), - self.initialized_block_env.clone(), - TxEnv::default(), - ); - let mut evm = self.evm_config.evm_with_env(&mut *db, env); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); while let Some(tx) = best_txs.next(()) { + let tx = tx.into_consensus(); + println!("tx: {:?}", tx); + println!( + "gas limit: {:?}, batch gas limit: {:?} cummulative gas used: {:?}", + tx.gas_limit(), + batch_gas_limit, + info.cumulative_gas_used + ); + // gas limit: 100816112, batch gas limit: 2500000000 cummulative gas used: 100062216 + // check in info if the txn has been executed already if info.executed_transactions.contains(&tx) { continue; @@ -853,6 +1047,7 @@ where // ensure we still have capacity for this transaction if info.cumulative_gas_used + tx.gas_limit() > batch_gas_limit { + println!("A"); // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from // the iterator before we can continue @@ -861,43 +1056,43 @@ where } // A sequencer's block should never contain blob or deposit transactions from the pool. - if tx.is_eip4844() || tx.tx_type() == TxType::Deposit as u8 { + if tx.is_eip4844() || tx.is_deposit() { + println!("B"); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } // check if the job was cancelled, if so we can exit early if self.cancel.is_cancelled() { + println!("C"); return Ok(Some(())); } // Configure the environment for the tx. - *evm.tx_mut() = self.evm_config.tx_env(tx.tx(), tx.signer()); + let tx_env = self.evm_config.tx_env(tx.tx(), tx.signer()); - let ResultAndState { result, state } = match evm.transact() { + println!("Start transaction"); + let ResultAndState { result, state } = match evm.transact(tx_env) { Ok(res) => res, Err(err) => { - match err { - EVMError::Transaction(err) => { - if matches!(err, InvalidTransaction::NonceTooLow { .. }) { - // if the nonce is too low, we can skip this transaction - trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - } - - continue; - } - err => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(err)); + if let Some(err) = err.as_invalid_tx_err() { + if err.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); } + + continue; } + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); } }; + println!("Finish transaction"); // commit changes evm.db_mut().commit(state); @@ -908,24 +1103,8 @@ where // receipt info.cumulative_gas_used += gas_used; - let receipt = alloy_consensus::Receipt { - status: Eip658Value::Eip658(result.is_success()), - cumulative_gas_used: info.cumulative_gas_used, - logs: result.into_logs().into_iter().collect(), - }; - - // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts.push(match tx.tx_type() { - OpTxType::Legacy => OpReceipt::Legacy(receipt), - OpTxType::Eip2930 => OpReceipt::Eip2930(receipt), - OpTxType::Eip1559 => OpReceipt::Eip1559(receipt), - OpTxType::Eip7702 => OpReceipt::Eip7702(receipt), - OpTxType::Deposit => OpReceipt::Deposit(OpDepositReceipt { - inner: receipt, - deposit_nonce: None, - deposit_receipt_version: None, - }), - }); + info.receipts + .push(self.build_receipt(info, result, None, &tx)); // update add to total fees let miner_fee = tx diff --git a/crates/builder/op-rbuilder/src/tester/main.rs b/crates/builder/op-rbuilder/src/tester/main.rs index c40aa173..8946c4c8 100644 --- a/crates/builder/op-rbuilder/src/tester/main.rs +++ b/crates/builder/op-rbuilder/src/tester/main.rs @@ -27,6 +27,9 @@ enum Commands { #[clap(long, short, action, default_value = "1")] block_time_secs: u64, + + #[clap(long, short, action)] + flashblocks_endpoint: Option, }, /// Deposit funds to the system Deposit { @@ -47,10 +50,19 @@ async fn main() -> eyre::Result<()> { validation, no_tx_pool, block_time_secs, - } => run_system(validation, no_tx_pool, block_time_secs).await, + flashblocks_endpoint, + } => { + run_system( + validation, + no_tx_pool, + block_time_secs, + flashblocks_endpoint, + ) + .await + } Commands::Deposit { address, amount } => { let engine_api = EngineApi::builder().build().unwrap(); - let mut generator = BlockGenerator::new(&engine_api, None, false, 1); + let mut generator = BlockGenerator::new(&engine_api, None, false, 1, None); generator.init().await?; diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 7261a3b5..a17b5228 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -24,6 +24,8 @@ use reth_node_api::{EngineTypes, PayloadTypes}; use reth_optimism_node::OpEngineTypes; use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; +use rollup_boost::flashblocks::FlashblocksService; +use rollup_boost::Flashblocks; use serde_json; use serde_json::Value; use std::str::FromStr; @@ -194,6 +196,10 @@ pub struct BlockGenerator<'a> { latest_hash: B256, no_tx_pool: bool, block_time_secs: u64, + + // flashblocks service + flashblocks_endpoint: Option, + flashblocks_service: Option, } impl<'a> BlockGenerator<'a> { @@ -202,6 +208,7 @@ impl<'a> BlockGenerator<'a> { validation_api: Option<&'a EngineApi>, no_tx_pool: bool, block_time_secs: u64, + flashblocks_endpoint: Option, ) -> Self { Self { engine_api, @@ -209,6 +216,8 @@ impl<'a> BlockGenerator<'a> { latest_hash: B256::ZERO, // temporary value no_tx_pool, block_time_secs, + flashblocks_endpoint, + flashblocks_service: None, } } @@ -222,6 +231,19 @@ impl<'a> BlockGenerator<'a> { self.sync_validation_node(validation_api).await?; } + // Initialize flashblocks service + if let Some(flashblocks_endpoint) = &self.flashblocks_endpoint { + println!( + "Initializing flashblocks service at {}", + flashblocks_endpoint + ); + + self.flashblocks_service = Some(Flashblocks::run( + flashblocks_endpoint.to_string(), + "127.0.0.1:1112".to_string(), // output address for the preconfirmations from rb + )?); + } + Ok(latest_block) } @@ -363,7 +385,7 @@ impl<'a> BlockGenerator<'a> { }, transactions: Some(transactions), no_tx_pool: Some(self.no_tx_pool), - gas_limit: Some(10000000000), + gas_limit: Some(10000000), eip_1559_params: None, }), ) @@ -375,11 +397,20 @@ impl<'a> BlockGenerator<'a> { let payload_id = result.payload_id.unwrap(); + // update the payload id in the flashblocks service if present + if let Some(flashblocks_service) = &self.flashblocks_service { + flashblocks_service.set_current_payload_id(payload_id).await; + } + if !self.no_tx_pool { tokio::time::sleep(tokio::time::Duration::from_secs(self.block_time_secs)).await; } - let payload = self.engine_api.get_payload_v3(payload_id).await?; + let payload = if let Some(flashblocks_service) = &self.flashblocks_service { + flashblocks_service.get_best_payload().await?.unwrap() + } else { + self.engine_api.get_payload_v3(payload_id).await? + }; // Validate with builder node let validation_status = self @@ -460,6 +491,7 @@ pub async fn run_system( validation: bool, no_tx_pool: bool, block_time_secs: u64, + flashblocks_endpoint: Option, ) -> eyre::Result<()> { println!("Validation: {}", validation); @@ -475,6 +507,7 @@ pub async fn run_system( validation_api.as_ref(), no_tx_pool, block_time_secs, + flashblocks_endpoint, ); generator.init().await?; From b26742df2019e1a68c24d24d0c305bb1569ff47f Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 6 Mar 2025 06:12:08 +1100 Subject: [PATCH 033/262] Remove reverting transactions from pool in op-rbuilder (#456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Marks reverting transactions as invalid in the payload building process and removed dependents. Also removes reverting hashes from the tx pool. This would result in transactions that revert not be considered for future blocks once it's simulated once. ## 💡 Motivation and Context Remove the need to resimulate reverting transactions for revert protection. --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- .../src/payload_builder_vanilla.rs | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index f983b151..03f3e469 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -12,7 +12,7 @@ use alloy_consensus::{ }; use alloy_eips::merge::BEACON_NONCE; use alloy_primitives::private::alloy_rlp::Encodable; -use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawals; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; @@ -57,6 +57,7 @@ use reth_primitives::{transaction::SignedTransactionIntoRecoveredExt, BlockBody, use reth_primitives_traits::proofs; use reth_primitives_traits::Block; use reth_primitives_traits::RecoveredBlock; +use reth_primitives_traits::SignedTransaction; use reth_provider::CanonStateSubscriptions; use reth_provider::{ HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, @@ -71,6 +72,7 @@ use revm::{ primitives::{ExecutionResult, ResultAndState}, DatabaseCommit, }; +use std::collections::HashSet; use std::error::Error as StdError; use std::{fmt::Display, sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; @@ -259,10 +261,18 @@ where let pool = self.pool.clone(); let block_build_start_time = Instant::now(); - match self.build_payload(args, |attrs| { - #[allow(clippy::unit_arg)] - self.best_transactions.best_transactions(pool, attrs) - })? { + match self.build_payload( + args, + |attrs| { + #[allow(clippy::unit_arg)] + self.best_transactions + .best_transactions(pool.clone(), attrs) + }, + |hashes| { + #[allow(clippy::unit_arg)] + self.best_transactions.remove_reverted(pool.clone(), hashes) + }, + )? { BuildOutcome::Better { payload, .. } => { best_payload.set(payload); self.metrics @@ -309,6 +319,7 @@ where &self, args: BuildArguments, OpBuiltPayload>, best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, + remove_reverted: impl FnOnce(Vec), ) -> Result>, PayloadBuilderError> where Txs: PayloadTransactions>, @@ -335,7 +346,7 @@ where metrics: Default::default(), }; - let builder = OpBuilder::new(best); + let builder = OpBuilder::new(best, remove_reverted); let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; let state = StateProviderDatabase::new(state_provider); @@ -393,12 +404,19 @@ where pub struct OpBuilder<'a, Txs> { /// Yields the best transaction to include if transactions from the mempool are allowed. best: Box Txs + 'a>, + /// Removes reverted transactions from the tx pool + #[debug(skip)] + remove_reverted: Box) + 'a>, } impl<'a, Txs> OpBuilder<'a, Txs> { - fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self { + fn new( + best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, + remove_reverted: impl FnOnce(Vec) + 'a, + ) -> Self { Self { best: Box::new(best), + remove_reverted: Box::new(remove_reverted), } } } @@ -418,7 +436,10 @@ impl OpBuilder<'_, Txs> { DB: Database + AsRef

, P: StorageRootProvider, { - let Self { best } = self; + let Self { + best, + remove_reverted, + } = self; info!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); // 1. apply eip-4788 pre block contract call @@ -514,6 +535,8 @@ impl OpBuilder<'_, Txs> { None }; + remove_reverted(info.reverted_tx_hashes.iter().copied().collect()); + let payload = ExecutedPayload { info, withdrawals_root, @@ -680,6 +703,13 @@ pub trait OpPayloadTransactions: Clone + Send + Sync + Unpin + 'sta pool: Pool, attr: BestTransactionsAttributes, ) -> impl PayloadTransactions; + + /// Removes reverted transactions from the tx pool + fn remove_reverted>( + &self, + pool: Pool, + hashes: Vec, + ); } impl OpPayloadTransactions for () { @@ -690,6 +720,14 @@ impl OpPayloadTransactions for () { ) -> impl PayloadTransactions { BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) } + + fn remove_reverted>( + &self, + pool: Pool, + hashes: Vec, + ) { + pool.remove_transactions(hashes); + } } /// Holds the state after execution @@ -716,6 +754,8 @@ pub struct ExecutionInfo { pub cumulative_da_bytes_used: u64, /// Tracks fees from executed mempool transactions pub total_fees: U256, + /// Tracks the reverted transaction hashes to remove from the transaction pool + pub reverted_tx_hashes: HashSet, } impl ExecutionInfo { @@ -728,6 +768,7 @@ impl ExecutionInfo { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO, + reverted_tx_hashes: HashSet::new(), } } @@ -1176,6 +1217,9 @@ where num_txs_simulated_success += 1; } else { num_txs_simulated_fail += 1; + trace!(target: "payload_builder", ?tx, "skipping reverted transaction"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + info.reverted_tx_hashes.insert(*tx.tx_hash()); continue; } From 51edfb9b7878c579ce41b70a0f9bff8afeaa58da Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 6 Mar 2025 20:23:31 +1100 Subject: [PATCH 034/262] Fix deposit command to right address (#468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Deposit command was not funding the address correctly ## 💡 Motivation and Context Tested deposits in integration tests --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/src/tester/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index a17b5228..75e60b00 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -463,13 +463,13 @@ impl<'a> BlockGenerator<'a> { /// Submit a deposit transaction to seed an account with ETH #[allow(dead_code)] - pub async fn deposit(&mut self, to: Address, value: u128) -> eyre::Result { + pub async fn deposit(&mut self, address: Address, value: u128) -> eyre::Result { // Create deposit transaction let deposit_tx = TxDeposit { source_hash: B256::default(), - from: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92200"), // Standard deposit source - to: TxKind::Call(to), // Recipient address - mint: Some(value), // Amount to deposit + from: address, // Set the sender to the address of the account to seed + to: TxKind::Create, + mint: Some(value), // Amount to deposit value: U256::default(), gas_limit: 210000, is_system_transaction: true, From 7e9c55132cf0d7d1cc24be8f6adb395f99c827bc Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 7 Mar 2025 05:31:41 +1100 Subject: [PATCH 035/262] Add priority fee integration test (#463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Add integration test to op-rbuilder that asserts that the block is built with priority fee ordering ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- .../src/integration/integration_test.rs | 126 +++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 5cdc4998..250475df 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -5,7 +5,7 @@ mod tests { }; use crate::tester::{BlockGenerator, EngineApi}; use crate::tx_signer::Signer; - use alloy_consensus::TxEip1559; + use alloy_consensus::{Transaction, TxEip1559}; use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; use alloy_eips::eip2718::Encodable2718; use alloy_primitives::hex; @@ -216,4 +216,128 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn integration_test_fee_priority_ordering() -> eyre::Result<()> { + // This test validates that transactions are ordered by fee priority in blocks + let mut framework = + IntegrationFramework::new("integration_test_fee_priority_ordering").unwrap(); + + // we are going to use a genesis file pre-generated before the test + let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + genesis_path.push("../../genesis.json"); + assert!(genesis_path.exists()); + + // create the builder + let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let op_rbuilder_config = OpRbuilderConfig::new() + .chain_config_path(genesis_path.clone()) + .data_dir(builder_data_dir) + .auth_rpc_port(1264) + .network_port(1265) + .http_port(1268) + .with_builder_private_key(BUILDER_PRIVATE_KEY); + + // create the validation reth node + let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let reth = OpRethConfig::new() + .chain_config_path(genesis_path) + .data_dir(reth_data_dir) + .auth_rpc_port(1266) + .network_port(1267); + + framework.start("op-reth", &reth).await.unwrap(); + + let _ = framework + .start("op-rbuilder", &op_rbuilder_config) + .await + .unwrap(); + + let engine_api = EngineApi::new("http://localhost:1264").unwrap(); + let validation_api = EngineApi::new("http://localhost:1266").unwrap(); + + let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None); + let latest_block = generator.init().await?; + + let provider = ProviderBuilder::::default() + .on_http("http://localhost:1268".parse()?); + + let base_fee = max( + latest_block.header.base_fee_per_gas.unwrap(), + MIN_PROTOCOL_BASE_FEE, + ); + + // Create transactions with increasing fee values + let priority_fees: [u128; 5] = [1, 3, 5, 2, 4]; // Deliberately not in order + let signers = vec![ + Signer::random(), + Signer::random(), + Signer::random(), + Signer::random(), + Signer::random(), + ]; + let mut txs = Vec::new(); + + // Fund test accounts with deposits + for signer in &signers { + generator + .deposit(signer.address, 1000000000000000000) + .await?; + } + + // Send transactions in non-optimal fee order + for (i, priority_fee) in priority_fees.iter().enumerate() { + let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: 901, + nonce: 1, + gas_limit: 210000, + max_fee_per_gas: base_fee as u128 + *priority_fee, + max_priority_fee_per_gas: *priority_fee, + ..Default::default() + }); + let signed_tx = signers[i].sign_tx(tx_request)?; + let tx = provider + .send_raw_transaction(signed_tx.encoded_2718().as_slice()) + .await?; + txs.push(tx); + } + + // Generate a block that should include these transactions + let block_hash = generator.generate_block().await?; + + // Query the block and check transaction ordering + let block = provider + .get_block_by_hash(block_hash, BlockTransactionsKind::Full) + .await? + .expect("block"); + + // Verify all transactions are included + for tx in &txs { + assert!( + block + .transactions + .hashes() + .any(|hash| hash == *tx.tx_hash()), + "transaction missing from block" + ); + } + + let tx_fees: Vec<_> = block + .transactions + .into_transactions() + .map(|tx| tx.effective_tip_per_gas(base_fee.into())) + .collect(); + + // Verify transactions are ordered by decreasing fee (highest fee first) + // Skip the first deposit transaction and last builder transaction + for i in 1..tx_fees.len() - 2 { + assert!( + tx_fees[i] >= tx_fees[i + 1], + "Transactions not ordered by decreasing fee: {:?}", + tx_fees + ); + } + + Ok(()) + } } From b95efbbb2bd5984d5bc14f4dc909962a5ad07a54 Mon Sep 17 00:00:00 2001 From: cody-wang-cb Date: Mon, 10 Mar 2025 13:21:54 -0400 Subject: [PATCH 036/262] Make flashblock ws url as a flag and add more data (#442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Making flashblock websocket as flag so that it can be customizable. Also added receipts, account balance, block number into the metadata field, such that the RPC node can build the RPC response for `eth_transactionReceipt` and `eth_getBalance` for pending blocks. (https://github.com/danyalprout/reth-flashblocks/pull/4) Added an integration test to test that the metadata field indeed has the new data. ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/src/args.rs | 7 + .../src/integration/integration_test.rs | 131 ++++++++++++++++++ .../src/integration/op_rbuilder.rs | 11 ++ crates/builder/op-rbuilder/src/main.rs | 9 +- .../op-rbuilder/src/payload_builder.rs | 48 ++++++- .../src/payload_builder_vanilla.rs | 15 +- 6 files changed, 207 insertions(+), 14 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index dd465c1c..ee2b6e2a 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -17,4 +17,11 @@ pub struct OpRbuilderArgs { /// Builder secret key for signing last transaction in block #[arg(long = "rollup.builder-secret-key", env = "BUILDER_SECRET_KEY")] pub builder_signer: Option, + /// Websocket port for flashblock payload builder + #[arg( + long = "rollup.flashblocks-ws-url", + env = "FLASHBLOCKS_WS_URL", + default_value = "127.0.0.1:1111" + )] + pub flashblocks_ws_url: String, } diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 250475df..bde67222 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -12,16 +12,21 @@ mod tests { use alloy_provider::Identity; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types_eth::BlockTransactionsKind; + use futures_util::StreamExt; use op_alloy_consensus::OpTypedTransaction; use op_alloy_network::Optimism; use std::cmp::max; use std::path::PathBuf; + use std::sync::{Arc, Mutex}; + use std::time::Duration; + use tokio_tungstenite::connect_async; use uuid::Uuid; const BUILDER_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; #[tokio::test] + #[cfg(not(feature = "flashblocks"))] async fn integration_test_chain_produces_blocks() -> eyre::Result<()> { // This is a simple test using the integration framework to test that the chain // produces blocks. @@ -93,6 +98,7 @@ mod tests { } #[tokio::test] + #[cfg(not(feature = "flashblocks"))] async fn integration_test_revert_protection() -> eyre::Result<()> { // This is a simple test using the integration framework to test that the chain // produces blocks. @@ -218,6 +224,7 @@ mod tests { } #[tokio::test] + #[cfg(not(feature = "flashblocks"))] async fn integration_test_fee_priority_ordering() -> eyre::Result<()> { // This test validates that transactions are ordered by fee priority in blocks let mut framework = @@ -340,4 +347,128 @@ mod tests { Ok(()) } + + #[tokio::test] + #[cfg(feature = "flashblocks")] + async fn integration_test_chain_produces_blocks() -> eyre::Result<()> { + // This is a simple test using the integration framework to test that the chain + // produces blocks. + let mut framework = + IntegrationFramework::new("integration_test_chain_produces_blocks").unwrap(); + + // we are going to use a genesis file pre-generated before the test + let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + genesis_path.push("../../genesis.json"); + assert!(genesis_path.exists()); + + // create the builder + let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let op_rbuilder_config = OpRbuilderConfig::new() + .chain_config_path(genesis_path.clone()) + .data_dir(builder_data_dir) + .auth_rpc_port(1234) + .network_port(1235) + .http_port(1238) + .with_builder_private_key(BUILDER_PRIVATE_KEY) + .with_flashblocks_ws_url("localhost:1239"); + + // create the validation reth node + let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let reth = OpRethConfig::new() + .chain_config_path(genesis_path) + .data_dir(reth_data_dir) + .auth_rpc_port(1236) + .network_port(1237); + + framework.start("op-reth", &reth).await.unwrap(); + + let op_rbuilder = framework + .start("op-rbuilder", &op_rbuilder_config) + .await + .unwrap(); + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + + // Spawn WebSocket listener task + let ws_handle = tokio::spawn(async move { + let (ws_stream, _) = connect_async("ws://localhost:1239").await?; + let (_, mut read) = ws_stream.split(); + + while let Some(Ok(msg)) = read.next().await { + if let Ok(text) = msg.into_text() { + messages_clone.lock().unwrap().push(text); + } + } + Ok::<_, eyre::Error>(()) + }); + + let engine_api = EngineApi::new("http://localhost:1234").unwrap(); + let validation_api = EngineApi::new("http://localhost:1236").unwrap(); + + let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None); + generator.init().await?; + + let provider = ProviderBuilder::::default() + .on_http("http://localhost:1238".parse()?); + + for _ in 0..10 { + let block_hash = generator.generate_block().await?; + + // query the block and the transactions inside the block + let block = provider + .get_block_by_hash(block_hash, BlockTransactionsKind::Hashes) + .await? + .expect("block"); + + for hash in block.transactions.hashes() { + let _ = provider + .get_transaction_receipt(hash) + .await? + .expect("receipt"); + } + } + // there must be a line logging the monitoring transaction + op_rbuilder + .find_log_line("Processing new chain commit") // no builder tx for flashblocks builder + .await?; + + // Process websocket messages + let timeout_duration = Duration::from_secs(10); + tokio::time::timeout(timeout_duration, async { + let mut message_count = 0; + loop { + if message_count >= 10 { + break; + } + let messages = received_messages.lock().unwrap(); + let messages_json: Vec = messages + .iter() + .map(|msg| serde_json::from_str(msg).unwrap()) + .collect(); + for msg in messages_json.iter() { + let metadata = msg.get("metadata"); + assert!(metadata.is_some(), "metadata field missing"); + let metadata = metadata.unwrap(); + assert!( + metadata.get("block_number").is_some(), + "block_number missing" + ); + assert!( + metadata.get("new_account_balances").is_some(), + "new_account_balances missing" + ); + assert!(metadata.get("receipts").is_some(), "receipts missing"); + message_count += 1; + } + drop(messages); + tokio::time::sleep(Duration::from_millis(100)).await; + } + }) + .await?; + ws_handle.abort(); + + Ok(()) + } } diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index 319f2254..e201365d 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -24,6 +24,7 @@ pub struct OpRbuilderConfig { http_port: Option, network_port: Option, builder_private_key: Option, + flashblocks_ws_url: Option, } impl OpRbuilderConfig { @@ -60,6 +61,11 @@ impl OpRbuilderConfig { self.builder_private_key = Some(private_key.to_string()); self } + + pub fn with_flashblocks_ws_url(mut self, url: &str) -> Self { + self.flashblocks_ws_url = Some(url.to_string()); + self + } } impl Service for OpRbuilderConfig { @@ -107,6 +113,11 @@ impl Service for OpRbuilderConfig { .arg(http_port.to_string()); } + if let Some(flashblocks_ws_url) = &self.flashblocks_ws_url { + cmd.arg("--rollup.flashblocks-ws-url") + .arg(flashblocks_ws_url); + } + cmd } diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 770c3610..fb9b3401 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -33,11 +33,10 @@ fn main() { let op_node = OpNode::new(rollup_args.clone()); let handle = builder .with_types::() - .with_components( - op_node - .components() - .payload(CustomOpPayloadBuilder::new(builder_args.builder_signer)), - ) + .with_components(op_node.components().payload(CustomOpPayloadBuilder::new( + builder_args.builder_signer, + builder_args.flashblocks_ws_url, + ))) .with_add_ons( OpAddOnsBuilder::default() .with_sequencer(rollup_args.sequencer_http.clone()) diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 9fba8e21..e6ecf074 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -6,7 +6,7 @@ use crate::tx_signer::Signer; use alloy_consensus::{Eip658Value, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::merge::BEACON_NONCE; use alloy_eips::Encodable2718; -use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawals; use op_alloy_consensus::OpDepositReceipt; @@ -45,6 +45,7 @@ use reth_payload_util::PayloadTransactions; use reth_primitives::{transaction::SignedTransactionIntoRecoveredExt, BlockBody, SealedHeader}; use reth_primitives_traits::proofs; use reth_primitives_traits::Block as _; +use reth_primitives_traits::SignedTransaction; use reth_provider::CanonStateSubscriptions; use reth_provider::StorageRootProvider; use reth_provider::{ @@ -62,7 +63,6 @@ use revm::{ use rollup_boost::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, }; -use serde_json::Value; use std::error::Error as StdError; use tokio_util::sync::CancellationToken; use tracing::{debug, trace, warn}; @@ -74,16 +74,29 @@ use tokio::sync::mpsc; use tokio_tungstenite::accept_async; use tokio_tungstenite::WebSocketStream; -#[derive(Debug, Clone, Copy, Default)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +struct FlashblocksMetadata { + receipts: HashMap, + new_account_balances: HashMap, + block_number: u64, +} + +#[derive(Debug, Clone, Default)] #[non_exhaustive] pub struct CustomOpPayloadBuilder { #[allow(dead_code)] builder_signer: Option, + flashblocks_ws_url: String, } impl CustomOpPayloadBuilder { - pub fn new(builder_signer: Option) -> Self { - Self { builder_signer } + pub fn new(builder_signer: Option, flashblocks_ws_url: String) -> Self { + Self { + builder_signer, + flashblocks_ws_url, + } } } @@ -112,6 +125,7 @@ where pool, ctx.provider().clone(), Arc::new(BasicOpReceiptBuilder::default()), + self.flashblocks_ws_url.clone(), )) } @@ -194,6 +208,7 @@ impl OpPayloadBuilder>, + flashblocks_ws_url: String, ) -> Self { let (tx, rx) = mpsc::unbounded_channel(); let subscribers = Arc::new(Mutex::new(Vec::new())); @@ -201,7 +216,7 @@ impl OpPayloadBuilder>(); + let new_receipts = info.receipts[info.last_flashblock_index..].to_vec(); + let receipts_with_hash = new_transactions + .iter() + .zip(new_receipts.iter()) + .map(|(tx, receipt)| (*tx.tx_hash(), receipt.clone())) + .collect::>(); + let new_account_balances = new_bundle + .state + .iter() + .filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance))) + .collect::>(); + + let metadata: FlashblocksMetadata = FlashblocksMetadata { + receipts: receipts_with_hash, + new_account_balances, + block_number: ctx.parent().number + 1, + }; + // Prepare the flashblocks message let fb_payload = FlashblocksPayloadV1 { payload_id: ctx.payload_id(), @@ -580,7 +614,7 @@ where transactions: new_transactions_encoded, withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), }, - metadata: Value::Null, + metadata: serde_json::to_value(&metadata).unwrap_or_default(), }; Ok(( diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 03f3e469..1cfe1082 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -78,14 +78,25 @@ use std::{fmt::Display, sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{info, trace, warn}; -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Default)] #[non_exhaustive] pub struct CustomOpPayloadBuilder { builder_signer: Option, + #[cfg(feature = "flashblocks")] + flashblocks_ws_url: String, } impl CustomOpPayloadBuilder { - pub fn new(builder_signer: Option) -> Self { + #[cfg(feature = "flashblocks")] + pub fn new(builder_signer: Option, flashblocks_ws_url: String) -> Self { + Self { + builder_signer, + flashblocks_ws_url, + } + } + + #[cfg(not(feature = "flashblocks"))] + pub fn new(builder_signer: Option, _flashblocks_ws_url: String) -> Self { Self { builder_signer } } } From 9982017deea4c5e5ad8240433d61b9b0a2bb9adb Mon Sep 17 00:00:00 2001 From: cody-wang-cb Date: Mon, 10 Mar 2025 17:40:20 -0400 Subject: [PATCH 037/262] Make block times dynamic in flashblock builder (#482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Currently the block time is hard coded to assume the chain block time is 1s and flashblock time is 250ms. But for Base the chain block time is 2s. Thus fixing this by adding args to make it dynamic. --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/src/args.rs | 14 ++++++++++ .../src/integration/integration_test.rs | 9 +++++-- .../src/integration/op_rbuilder.rs | 22 +++++++++++++++ crates/builder/op-rbuilder/src/main.rs | 2 ++ .../op-rbuilder/src/payload_builder.rs | 27 ++++++++++++++++--- .../src/payload_builder_vanilla.rs | 20 ++++++++++++-- 6 files changed, 86 insertions(+), 8 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index ee2b6e2a..461af0d4 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -24,4 +24,18 @@ pub struct OpRbuilderArgs { default_value = "127.0.0.1:1111" )] pub flashblocks_ws_url: String, + /// chain block time in milliseconds + #[arg( + long = "rollup.chain-block-time", + default_value = "1000", + env = "CHAIN_BLOCK_TIME" + )] + pub chain_block_time: u64, + /// flashblock block time in milliseconds + #[arg( + long = "rollup.flashblock-block-time", + default_value = "250", + env = "FLASHBLOCK_BLOCK_TIME" + )] + pub flashblock_block_time: u64, } diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index bde67222..deb75fe6 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -370,7 +370,9 @@ mod tests { .network_port(1235) .http_port(1238) .with_builder_private_key(BUILDER_PRIVATE_KEY) - .with_flashblocks_ws_url("localhost:1239"); + .with_flashblocks_ws_url("localhost:1239") + .with_chain_block_time(2000) + .with_flashbots_block_time(200); // create the validation reth node let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); @@ -407,7 +409,7 @@ mod tests { let engine_api = EngineApi::new("http://localhost:1234").unwrap(); let validation_api = EngineApi::new("http://localhost:1236").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None); + let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 2, None); generator.init().await?; let provider = ProviderBuilder::::default() @@ -434,6 +436,9 @@ mod tests { .find_log_line("Processing new chain commit") // no builder tx for flashblocks builder .await?; + // check there's 10 flashblocks log lines (2000ms / 200ms) + op_rbuilder.find_log_line("Building flashblock 9").await?; + // Process websocket messages let timeout_duration = Duration::from_secs(10); tokio::time::timeout(timeout_duration, async { diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index e201365d..28f9532d 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -25,6 +25,8 @@ pub struct OpRbuilderConfig { network_port: Option, builder_private_key: Option, flashblocks_ws_url: Option, + chain_block_time: Option, + flashbots_block_time: Option, } impl OpRbuilderConfig { @@ -66,6 +68,16 @@ impl OpRbuilderConfig { self.flashblocks_ws_url = Some(url.to_string()); self } + + pub fn with_chain_block_time(mut self, time: u64) -> Self { + self.chain_block_time = Some(time); + self + } + + pub fn with_flashbots_block_time(mut self, time: u64) -> Self { + self.flashbots_block_time = Some(time); + self + } } impl Service for OpRbuilderConfig { @@ -118,6 +130,16 @@ impl Service for OpRbuilderConfig { .arg(flashblocks_ws_url); } + if let Some(chain_block_time) = self.chain_block_time { + cmd.arg("--rollup.chain-block-time") + .arg(chain_block_time.to_string()); + } + + if let Some(flashbots_block_time) = self.flashbots_block_time { + cmd.arg("--rollup.flashblock-block-time") + .arg(flashbots_block_time.to_string()); + } + cmd } diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index fb9b3401..2bf83fc7 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -36,6 +36,8 @@ fn main() { .with_components(op_node.components().payload(CustomOpPayloadBuilder::new( builder_args.builder_signer, builder_args.flashblocks_ws_url, + builder_args.chain_block_time, + builder_args.flashblock_block_time, ))) .with_add_ons( OpAddOnsBuilder::default() diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index e6ecf074..ab145402 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -89,13 +89,22 @@ pub struct CustomOpPayloadBuilder { #[allow(dead_code)] builder_signer: Option, flashblocks_ws_url: String, + chain_block_time: u64, + flashblock_block_time: u64, } impl CustomOpPayloadBuilder { - pub fn new(builder_signer: Option, flashblocks_ws_url: String) -> Self { + pub fn new( + builder_signer: Option, + flashblocks_ws_url: String, + chain_block_time: u64, + flashblock_block_time: u64, + ) -> Self { Self { builder_signer, flashblocks_ws_url, + chain_block_time, + flashblock_block_time, } } } @@ -126,6 +135,8 @@ where ctx.provider().clone(), Arc::new(BasicOpReceiptBuilder::default()), self.flashblocks_ws_url.clone(), + self.chain_block_time, + self.flashblock_block_time, )) } @@ -199,6 +210,10 @@ pub struct OpPayloadBuilder { pub tx: mpsc::UnboundedSender, /// Node primitive types. pub receipt_builder: Arc>, + /// chain block time + pub chain_block_time: u64, + /// Flashblock block time + pub flashblock_block_time: u64, } impl OpPayloadBuilder { @@ -209,6 +224,8 @@ impl OpPayloadBuilder>, flashblocks_ws_url: String, + chain_block_time: u64, + flashblock_block_time: u64, ) -> Self { let (tx, rx) = mpsc::unbounded_channel(); let subscribers = Arc::new(Mutex::new(Vec::new())); @@ -225,6 +242,8 @@ impl OpPayloadBuilder, #[cfg(feature = "flashblocks")] flashblocks_ws_url: String, + #[cfg(feature = "flashblocks")] + chain_block_time: u64, + #[cfg(feature = "flashblocks")] + flashblock_block_time: u64, } impl CustomOpPayloadBuilder { #[cfg(feature = "flashblocks")] - pub fn new(builder_signer: Option, flashblocks_ws_url: String) -> Self { + pub fn new( + builder_signer: Option, + flashblocks_ws_url: String, + chain_block_time: u64, + flashblock_block_time: u64, + ) -> Self { Self { builder_signer, flashblocks_ws_url, + chain_block_time, + flashblock_block_time, } } #[cfg(not(feature = "flashblocks"))] - pub fn new(builder_signer: Option, _flashblocks_ws_url: String) -> Self { + pub fn new( + builder_signer: Option, + _flashblocks_ws_url: String, + _chain_block_time: u64, + _flashblock_block_time: u64, + ) -> Self { Self { builder_signer } } } From 57005dfe4f4fe8d29ebfa4768ed633c08a3c8738 Mon Sep 17 00:00:00 2001 From: cody-wang-cb Date: Wed, 12 Mar 2025 17:57:46 -0400 Subject: [PATCH 038/262] Fix flashblocks receipt index (#494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary The index for getting the receipt is incorrect as it's using an updated index, fix it. Added an additional assertion in integration test to test this. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- .../op-rbuilder/src/integration/integration_test.rs | 13 +++++++++++++ crates/builder/op-rbuilder/src/payload_builder.rs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index deb75fe6..8d0ab7fb 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -465,6 +465,19 @@ mod tests { "new_account_balances missing" ); assert!(metadata.get("receipts").is_some(), "receipts missing"); + // also check if the length of the receipts is the same as the number of transactions + assert!( + metadata.get("receipts").unwrap().as_object().unwrap().len() + == msg + .get("diff") + .unwrap() + .get("transactions") + .unwrap() + .as_array() + .unwrap() + .len(), + "receipts length mismatch" + ); message_count += 1; } drop(messages); diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index ab145402..fedd86db 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -579,7 +579,6 @@ where // pick the new transactions from the info field and update the last flashblock index let new_transactions = info.executed_transactions[info.last_flashblock_index..].to_vec(); - info.last_flashblock_index = info.executed_transactions.len(); let new_transactions_encoded = new_transactions .clone() @@ -588,6 +587,7 @@ where .collect::>(); let new_receipts = info.receipts[info.last_flashblock_index..].to_vec(); + info.last_flashblock_index = info.executed_transactions.len(); let receipts_with_hash = new_transactions .iter() .zip(new_receipts.iter()) From 8c2d12401def8e952ab2c1e2392a459fcd117c17 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 13 Mar 2025 16:51:57 +0700 Subject: [PATCH 039/262] Refactor ExecutedPayload and ExecutionInfo (#477) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Moved ExecutionInfo and ExecutingPayload into primitives, combining them Now we could implement revert protection for Flashblocks too ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/main.rs | 1 + .../op-rbuilder/src/payload_builder.rs | 50 ++---------- .../src/payload_builder_vanilla.rs | 73 +---------------- .../builder/op-rbuilder/src/primitives/mod.rs | 1 + .../src/primitives/reth/execution.rs | 79 +++++++++++++++++++ .../op-rbuilder/src/primitives/reth/mod.rs | 2 + crates/builder/op-rbuilder/src/tester/mod.rs | 1 - 7 files changed, 91 insertions(+), 116 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/primitives/mod.rs create mode 100644 crates/builder/op-rbuilder/src/primitives/reth/execution.rs create mode 100644 crates/builder/op-rbuilder/src/primitives/reth/mod.rs diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 2bf83fc7..62eea9f4 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -21,6 +21,7 @@ mod monitoring; pub mod payload_builder; #[cfg(not(feature = "flashblocks"))] mod payload_builder_vanilla; +mod primitives; #[cfg(test)] mod tester; mod tx_signer; diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index fedd86db..ab2fa9af 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -1,8 +1,10 @@ use std::{fmt::Display, sync::Arc, sync::Mutex}; -use crate::generator::BlockPayloadJobGenerator; -use crate::generator::{BlockCell, BuildArguments, PayloadBuilder}; -use crate::tx_signer::Signer; +use crate::{ + generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, + primitives::reth::{ExecutedPayload, ExecutionInfo}, + tx_signer::Signer, +}; use alloy_consensus::{Eip658Value, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; use alloy_eips::merge::BEACON_NONCE; use alloy_eips::Encodable2718; @@ -695,46 +697,6 @@ impl OpPayloadTransactions for () { } } -/// Holds the state after execution -#[derive(Debug)] -pub struct ExecutedPayload { - /// Tracked execution info - pub info: ExecutionInfo, - /// Withdrawal hash. - pub withdrawals_root: Option, -} - -/// This acts as the container for executed transactions and its byproducts (receipts, gas used) -#[derive(Default, Debug, Clone)] -pub struct ExecutionInfo { - /// All executed transactions (unrecovered). - pub executed_transactions: Vec, - /// The recovered senders for the executed transactions. - pub executed_senders: Vec

, - /// The transaction receipts - pub receipts: Vec, - /// All gas used so far - pub cumulative_gas_used: u64, - /// Tracks fees from executed mempool transactions - pub total_fees: U256, - /// Index of the last consumed flashblock - pub last_flashblock_index: usize, -} - -impl ExecutionInfo { - /// Create a new instance with allocated slots. - pub fn with_capacity(capacity: usize) -> Self { - Self { - executed_transactions: Vec::with_capacity(capacity), - executed_senders: Vec::with_capacity(capacity), - receipts: Vec::with_capacity(capacity), - cumulative_gas_used: 0, - total_fees: U256::ZERO, - last_flashblock_index: 0, - } - } -} - /// Container type that holds all necessities to build a new payload. #[derive(Debug)] pub struct OpPayloadBuilderCtx { @@ -1099,7 +1061,7 @@ where } // ensure we still have capacity for this transaction - if info.cumulative_gas_used + tx.gas_limit() > batch_gas_limit { + if info.is_tx_over_limits(tx.tx(), batch_gas_limit, None, None) { println!("A"); // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 30f193db..820c32a4 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -3,6 +3,7 @@ use crate::generator::BuildArguments; use crate::{ generator::{BlockCell, PayloadBuilder}, metrics::OpRBuilderMetrics, + primitives::reth::{ExecutedPayload, ExecutionInfo}, tx_signer::Signer, }; use alloy_consensus::constants::EMPTY_WITHDRAWALS; @@ -12,7 +13,7 @@ use alloy_consensus::{ }; use alloy_eips::merge::BEACON_NONCE; use alloy_primitives::private::alloy_rlp::Encodable; -use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawals; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; @@ -72,7 +73,6 @@ use revm::{ primitives::{ExecutionResult, ResultAndState}, DatabaseCommit, }; -use std::collections::HashSet; use std::error::Error as StdError; use std::{fmt::Display, sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; @@ -757,75 +757,6 @@ impl OpPayloadTransactions for () { } } -/// Holds the state after execution -#[derive(Debug)] -pub struct ExecutedPayload { - /// Tracked execution info - pub info: ExecutionInfo, - /// Withdrawal hash. - pub withdrawals_root: Option, -} - -/// This acts as the container for executed transactions and its byproducts (receipts, gas used) -#[derive(Default, Debug)] -pub struct ExecutionInfo { - /// All executed transactions (unrecovered). - pub executed_transactions: Vec, - /// The recovered senders for the executed transactions. - pub executed_senders: Vec
, - /// The transaction receipts - pub receipts: Vec, - /// All gas used so far - pub cumulative_gas_used: u64, - /// Estimated DA size - pub cumulative_da_bytes_used: u64, - /// Tracks fees from executed mempool transactions - pub total_fees: U256, - /// Tracks the reverted transaction hashes to remove from the transaction pool - pub reverted_tx_hashes: HashSet, -} - -impl ExecutionInfo { - /// Create a new instance with allocated slots. - pub fn with_capacity(capacity: usize) -> Self { - Self { - executed_transactions: Vec::with_capacity(capacity), - executed_senders: Vec::with_capacity(capacity), - receipts: Vec::with_capacity(capacity), - cumulative_gas_used: 0, - cumulative_da_bytes_used: 0, - total_fees: U256::ZERO, - reverted_tx_hashes: HashSet::new(), - } - } - - /// Returns true if the transaction would exceed the block limits: - /// - block gas limit: ensures the transaction still fits into the block. - /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit - /// per tx. - /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the - /// maximum allowed DA limit per block. - pub fn is_tx_over_limits( - &self, - tx: &N::SignedTx, - block_gas_limit: u64, - tx_data_limit: Option, - block_data_limit: Option, - ) -> bool { - if tx_data_limit.is_some_and(|da_limit| tx.length() as u64 > da_limit) { - return true; - } - - if block_data_limit - .is_some_and(|da_limit| self.cumulative_da_bytes_used + (tx.length() as u64) > da_limit) - { - return true; - } - - self.cumulative_gas_used + tx.gas_limit() > block_gas_limit - } -} - /// Container type that holds all necessities to build a new payload. #[derive(Debug)] pub struct OpPayloadBuilderCtx { diff --git a/crates/builder/op-rbuilder/src/primitives/mod.rs b/crates/builder/op-rbuilder/src/primitives/mod.rs new file mode 100644 index 00000000..02615de6 --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/mod.rs @@ -0,0 +1 @@ +pub mod reth; diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs new file mode 100644 index 00000000..37880810 --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -0,0 +1,79 @@ +//! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570) +use alloy_consensus::Transaction; +use alloy_primitives::private::alloy_rlp::Encodable; +use alloy_primitives::{Address, TxHash, B256, U256}; +use reth_node_api::NodePrimitives; +use std::collections::HashSet; + +/// Holds the state after execution +#[derive(Debug)] +pub struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, + /// Withdrawal hash. + pub withdrawals_root: Option, +} + +#[derive(Default, Debug)] +pub struct ExecutionInfo { + /// All executed transactions (unrecovered). + pub executed_transactions: Vec, + /// The recovered senders for the executed transactions. + pub executed_senders: Vec
, + /// The transaction receipts + pub receipts: Vec, + /// All gas used so far + pub cumulative_gas_used: u64, + /// Estimated DA size + pub cumulative_da_bytes_used: u64, + /// Tracks fees from executed mempool transactions + pub total_fees: U256, + /// Tracks the reverted transaction hashes to remove from the transaction pool + pub reverted_tx_hashes: HashSet, + #[cfg(feature = "flashblocks")] + /// Index of the last consumed flashblock + pub last_flashblock_index: usize, +} + +impl ExecutionInfo { + /// Create a new instance with allocated slots. + pub fn with_capacity(capacity: usize) -> Self { + Self { + executed_transactions: Vec::with_capacity(capacity), + executed_senders: Vec::with_capacity(capacity), + receipts: Vec::with_capacity(capacity), + cumulative_gas_used: 0, + cumulative_da_bytes_used: 0, + total_fees: U256::ZERO, + reverted_tx_hashes: HashSet::new(), + #[cfg(feature = "flashblocks")] + last_flashblock_index: 0, + } + } + + /// Returns true if the transaction would exceed the block limits: + /// - block gas limit: ensures the transaction still fits into the block. + /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit + /// per tx. + /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the + /// maximum allowed DA limit per block. + pub fn is_tx_over_limits( + &self, + tx: &N::SignedTx, + block_gas_limit: u64, + tx_data_limit: Option, + block_data_limit: Option, + ) -> bool { + if tx_data_limit.is_some_and(|da_limit| tx.length() as u64 > da_limit) { + return true; + } + + if block_data_limit + .is_some_and(|da_limit| self.cumulative_da_bytes_used + (tx.length() as u64) > da_limit) + { + return true; + } + + self.cumulative_gas_used + tx.gas_limit() > block_gas_limit + } +} diff --git a/crates/builder/op-rbuilder/src/primitives/reth/mod.rs b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs new file mode 100644 index 00000000..4b6de4c5 --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs @@ -0,0 +1,2 @@ +mod execution; +pub use execution::{ExecutedPayload, ExecutionInfo}; diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 75e60b00..855e2480 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -26,7 +26,6 @@ use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; use rollup_boost::flashblocks::FlashblocksService; use rollup_boost::Flashblocks; -use serde_json; use serde_json::Value; use std::str::FromStr; use std::time::SystemTime; From 1c0ccd8e2ee21ed67b655ee90ec9c450c09f66c5 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 14 Mar 2025 14:33:44 +0000 Subject: [PATCH 040/262] Remove prints in op-rbuilder (#502) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/generator.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 1b3a8a50..b55def99 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -157,9 +157,6 @@ where // that cancels existing jobs when receiving new block building requests. let deadline = job_deadline(attributes.timestamp()) + Duration::from_millis(500); - println!("attributes timestmap: {:?}", attributes.timestamp()); - println!("Deadline: {:?}", deadline); - let deadline = Box::pin(tokio::time::sleep(deadline)); let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); From 372afa7286625966275fecfc08e9c17d93b25db7 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 17 Mar 2025 08:41:32 +0100 Subject: [PATCH 041/262] Add txn monitoring in op-rbuilder pool (#500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR introduces a monitoring service that prints all the transactions that arrive to the transaction pool in op-rbuilder. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/args.rs | 4 ++ crates/builder/op-rbuilder/src/main.rs | 13 ++++ .../op-rbuilder/src/monitor_tx_pool.rs | 62 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 crates/builder/op-rbuilder/src/monitor_tx_pool.rs diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index 461af0d4..99178049 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -38,4 +38,8 @@ pub struct OpRbuilderArgs { env = "FLASHBLOCK_BLOCK_TIME" )] pub flashblock_block_time: u64, + + /// Signals whether to log pool transactions events + #[arg(long = "builder.log-pool-transactions", default_value = "false")] + pub log_pool_transactions: bool, } diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 62eea9f4..58c0e9c8 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -9,6 +9,7 @@ use reth_optimism_node::OpNode; use payload_builder::CustomOpPayloadBuilder; #[cfg(not(feature = "flashblocks"))] use payload_builder_vanilla::CustomOpPayloadBuilder; +use reth_transaction_pool::TransactionPool; /// CLI argument parsing. pub mod args; @@ -16,6 +17,7 @@ pub mod generator; #[cfg(test)] mod integration; mod metrics; +mod monitor_tx_pool; mod monitoring; #[cfg(feature = "flashblocks")] pub mod payload_builder; @@ -25,6 +27,7 @@ mod primitives; #[cfg(test)] mod tester; mod tx_signer; +use monitor_tx_pool::monitor_tx_pool; fn main() { Cli::::parse() @@ -50,6 +53,16 @@ fn main() { let new_canonical_blocks = ctx.provider().canonical_state_stream(); let builder_signer = builder_args.builder_signer; + if builder_args.log_pool_transactions { + tracing::info!("Logging pool transactions"); + ctx.task_executor.spawn_critical( + "txlogging", + Box::pin(async move { + monitor_tx_pool(ctx.pool.all_transactions_event_listener()).await; + }), + ); + } + ctx.task_executor.spawn_critical( "monitoring", Box::pin(async move { diff --git a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs new file mode 100644 index 00000000..4e7268cf --- /dev/null +++ b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs @@ -0,0 +1,62 @@ +use futures_util::StreamExt; +use reth_optimism_node::txpool::OpPooledTransaction; +use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent}; +use tracing::debug; + +pub async fn monitor_tx_pool(mut new_transactions: AllTransactionsEvents) { + while let Some(event) = new_transactions.next().await { + transaction_event_log(event); + } +} + +fn transaction_event_log(event: FullTransactionEvent) { + match event { + FullTransactionEvent::Pending(hash) => { + debug!( + tx_hash = hash.to_string(), + kind = "pending", + "Transaction event received" + ) + } + FullTransactionEvent::Queued(hash) => { + debug!( + tx_hash = hash.to_string(), + kind = "queued", + "Transaction event received" + ) + } + FullTransactionEvent::Mined { + tx_hash, + block_hash, + } => debug!( + tx_hash = tx_hash.to_string(), + kind = "mined", + block_hash = block_hash.to_string(), + "Transaction event received" + ), + FullTransactionEvent::Replaced { + transaction, + replaced_by, + } => debug!( + tx_hash = transaction.hash().to_string(), + kind = "replaced", + replaced_by = replaced_by.to_string(), + "Transaction event received" + ), + FullTransactionEvent::Discarded(hash) => { + debug!( + tx_hash = hash.to_string(), + kind = "discarded", + "Transaction event received" + ) + } + FullTransactionEvent::Invalid(hash) => { + debug!( + tx_hash = hash.to_string(), + kind = "invalid", + "Transaction event received" + ) + } + FullTransactionEvent::Propagated(_propagated) => {} + } +} From 06ed75b87366cccee2867398d0875f109680ec06 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 18 Mar 2025 15:31:23 +0700 Subject: [PATCH 042/262] Interop support (#462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement SupervisorValidator to be used in rbuilder Add additional primitives crate, that is used for storing external ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 7 +- crates/builder/op-rbuilder/src/args.rs | 12 +- crates/builder/op-rbuilder/src/lib.rs | 1 + crates/builder/op-rbuilder/src/main.rs | 2 + crates/builder/op-rbuilder/src/metrics.rs | 24 +++ .../op-rbuilder/src/payload_builder.rs | 11 +- .../src/payload_builder_vanilla.rs | 201 ++++++++++++++++-- .../builder/op-rbuilder/src/primitives/mod.rs | 2 + .../src/primitives/reth/execution.rs | 4 +- .../op-rbuilder/src/primitives/supervisor.rs | 25 +++ 10 files changed, 269 insertions(+), 20 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/primitives/supervisor.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 16ac91cb..f18c1402 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -56,6 +56,10 @@ op-alloy-consensus.workspace = true op-alloy-rpc-types-engine.workspace = true op-alloy-network.workspace = true +# kona +kona-rpc = {workspace = true, optional = true} +kona-interop = {workspace = true, optional = true} + revm.workspace = true tracing.workspace = true @@ -85,6 +89,7 @@ alloy-serde = "0.7" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "e74a1fd01366e4ddd13515da4efda59cdc8fbce0" } +url = "2.5.3" [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -113,7 +118,7 @@ min-info-logs = ["tracing/release_max_level_info"] min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] -optimism = ["reth-optimism-node/optimism", "reth-optimism-cli/optimism"] +optimism = ["reth-optimism-node/optimism", "reth-optimism-cli/optimism", "kona-rpc", "kona-interop"] integration = [] flashblocks = [] diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index 99178049..7c145511 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -6,6 +6,7 @@ use reth_optimism_node::args::RollupArgs; use crate::tx_signer::Signer; +use alloy_transport_http::reqwest::Url; /// Parameters for rollup configuration #[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] @@ -38,7 +39,16 @@ pub struct OpRbuilderArgs { env = "FLASHBLOCK_BLOCK_TIME" )] pub flashblock_block_time: u64, - + /// URL of the supervisor service for transaction validation + #[arg(long = "rollup.supervisor-url", env = "SUPERVISOR_URL")] + pub supervisor_url: Option, + /// URL of the supervisor service for transaction validation + #[arg( + long = "rollup.supervisor_safety_level", + env = "SUPERVISOR_SAFETY_LEVEL", + help = "Safety level to pass to supervisor, values: finalized, safe, local-safe, cross-unsafe, unsafe, invalid" + )] + pub supervisor_safety_level: Option, /// Signals whether to log pool transactions events #[arg(long = "builder.log-pool-transactions", default_value = "false")] pub log_pool_transactions: bool, diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index aee80672..211b1ddb 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,3 +1,4 @@ pub mod integration; +pub mod primitives; pub mod tester; pub mod tx_signer; diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 58c0e9c8..a248f0e4 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -42,6 +42,8 @@ fn main() { builder_args.flashblocks_ws_url, builder_args.chain_block_time, builder_args.flashblock_block_time, + builder_args.supervisor_url, + builder_args.supervisor_safety_level, ))) .with_add_ons( OpAddOnsBuilder::default() diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 4451d0b5..5e8e426f 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -44,6 +44,14 @@ pub struct OpRBuilderMetrics { pub tx_byte_size: Histogram, /// Number of reverted transactions pub num_reverted_tx: Counter, + /// Number of cross-chain transactions + pub num_cross_chain_tx: Counter, + /// Number of cross-chain transactions that didn't pass supervisor validation + pub num_cross_chain_tx_fail: Counter, + /// Number of cross-chain transactions that weren't verified because of the timeout + pub num_cross_chain_tx_timeout: Counter, + /// Number of cross-chain transactions that weren't verified because of the server error + pub num_cross_chain_tx_server_error: Counter, } impl OpRBuilderMetrics { @@ -70,4 +78,20 @@ impl OpRBuilderMetrics { pub fn set_builder_balance(&self, balance: f64) { self.builder_balance.set(balance); } + + pub fn inc_num_cross_chain_tx_fail(&self) { + self.num_cross_chain_tx_fail.increment(1); + } + + pub fn inc_num_cross_chain_tx(&self) { + self.num_cross_chain_tx.increment(1); + } + + pub fn inc_num_cross_chain_tx_timeout(&self) { + self.num_cross_chain_tx_timeout.increment(1); + } + + pub fn inc_num_cross_chain_tx_server_error(&self) { + self.num_cross_chain_tx_server_error.increment(1); + } } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index ab2fa9af..82613c1a 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -75,6 +75,7 @@ use tokio::net::{TcpListener, TcpStream}; use tokio::sync::mpsc; use tokio_tungstenite::accept_async; use tokio_tungstenite::WebSocketStream; +use url::Url; use serde::{Deserialize, Serialize}; @@ -88,11 +89,15 @@ struct FlashblocksMetadata { #[derive(Debug, Clone, Default)] #[non_exhaustive] pub struct CustomOpPayloadBuilder { - #[allow(dead_code)] + #[expect(dead_code)] builder_signer: Option, flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, + #[expect(dead_code)] + supervisor_url: Option, + #[expect(dead_code)] + supervisor_safety_level: Option, } impl CustomOpPayloadBuilder { @@ -101,12 +106,16 @@ impl CustomOpPayloadBuilder { flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, + supervisor_url: Option, + supervisor_safety_level: Option, ) -> Self { Self { builder_signer, flashblocks_ws_url, chain_block_time, flashblock_block_time, + supervisor_url, + supervisor_safety_level, } } } diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 820c32a4..4113b762 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1,9 +1,8 @@ -use crate::generator::BlockPayloadJobGenerator; -use crate::generator::BuildArguments; use crate::{ - generator::{BlockCell, PayloadBuilder}, + generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, metrics::OpRBuilderMetrics, primitives::reth::{ExecutedPayload, ExecutionInfo}, + primitives::supervisor::SupervisorValidator, tx_signer::Signer, }; use alloy_consensus::constants::EMPTY_WITHDRAWALS; @@ -13,9 +12,12 @@ use alloy_consensus::{ }; use alloy_eips::merge::BEACON_NONCE; use alloy_primitives::private::alloy_rlp::Encodable; -use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawals; +use jsonrpsee::http_client::HttpClientBuilder; +use kona_interop::{ExecutingDescriptor, SafetyLevel}; +use kona_rpc::{InteropTxValidator, InteropTxValidatorError}; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; use reth::builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}; use reth::core::primitives::InMemorySize; @@ -76,7 +78,8 @@ use revm::{ use std::error::Error as StdError; use std::{fmt::Display, sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; -use tracing::{info, trace, warn}; +use tracing::{error, info, trace, warn}; +use url::Url; #[derive(Debug, Clone, Default)] #[non_exhaustive] @@ -88,6 +91,8 @@ pub struct CustomOpPayloadBuilder { chain_block_time: u64, #[cfg(feature = "flashblocks")] flashblock_block_time: u64, + supervisor_url: Option, + supervisor_safety_level: Option, } impl CustomOpPayloadBuilder { @@ -97,12 +102,16 @@ impl CustomOpPayloadBuilder { flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, + supervisor_url: Option, + supervisor_safety_level: Option, ) -> Self { Self { builder_signer, flashblocks_ws_url, chain_block_time, flashblock_block_time, + supervisor_url, + supervisor_safety_level, } } @@ -112,8 +121,14 @@ impl CustomOpPayloadBuilder { _flashblocks_ws_url: String, _chain_block_time: u64, _flashblock_block_time: u64, + supervisor_url: Option, + supervisor_safety_level: Option, ) -> Self { - Self { builder_signer } + Self { + builder_signer, + supervisor_url, + supervisor_safety_level, + } } } @@ -143,6 +158,8 @@ where pool, ctx.provider().clone(), Arc::new(BasicOpReceiptBuilder::default()), + self.supervisor_url.clone(), + self.supervisor_safety_level.clone(), )) } @@ -224,6 +241,10 @@ pub struct OpPayloadBuilderVanilla>, + /// Client to execute supervisor validation + pub supervisor_client: Option, + /// Level to use in supervisor validation + pub supervisor_safety_level: SafetyLevel, } impl @@ -236,6 +257,8 @@ impl pool: Pool, client: Client, receipt_builder: Arc>, + supervisor_url: Option, + supervisor_safety_level: Option, ) -> Self { Self::with_builder_config( evm_config, @@ -243,18 +266,38 @@ impl pool, client, receipt_builder, + supervisor_url, + supervisor_safety_level, Default::default(), ) } + // TODO: we will move supervisor_url and supervisor_safety_level into OpBuilderConfig to reduce + // number of args + #[allow(clippy::too_many_arguments)] pub fn with_builder_config( evm_config: EvmConfig, builder_signer: Option, pool: Pool, client: Client, receipt_builder: Arc>, + supervisor_url: Option, + supervisor_safety_level: Option, config: OpBuilderConfig, ) -> Self { + // TODO: we should make this client required if interop hardfork enabled, add this after spec rebase + let supervisor_client = supervisor_url.map(|url| { + SupervisorValidator::new( + HttpClientBuilder::default() + .build(url) + .expect("building supervisor http client"), + ) + }); + let supervisor_safety_level = supervisor_safety_level + .map(|level| { + serde_json::from_str(level.as_str()).expect("parsing supervisor_safety_level") + }) + .unwrap_or(SafetyLevel::CrossUnsafe); Self { pool, client, @@ -264,6 +307,8 @@ impl best_transactions: (), metrics: Default::default(), builder_signer, + supervisor_client, + supervisor_safety_level, } } } @@ -297,7 +342,7 @@ where }, |hashes| { #[allow(clippy::unit_arg)] - self.best_transactions.remove_reverted(pool.clone(), hashes) + self.best_transactions.remove_invalid(pool.clone(), hashes) }, )? { BuildOutcome::Better { payload, .. } => { @@ -371,6 +416,8 @@ where receipt_builder: self.receipt_builder.clone(), builder_signer: self.builder_signer, metrics: Default::default(), + supervisor_client: self.supervisor_client.clone(), + supervisor_safety_level: self.supervisor_safety_level, }; let builder = OpBuilder::new(best, remove_reverted); @@ -433,7 +480,7 @@ pub struct OpBuilder<'a, Txs> { best: Box Txs + 'a>, /// Removes reverted transactions from the tx pool #[debug(skip)] - remove_reverted: Box) + 'a>, + remove_invalid: Box) + 'a>, } impl<'a, Txs> OpBuilder<'a, Txs> { @@ -443,7 +490,7 @@ impl<'a, Txs> OpBuilder<'a, Txs> { ) -> Self { Self { best: Box::new(best), - remove_reverted: Box::new(remove_reverted), + remove_invalid: Box::new(remove_reverted), } } } @@ -465,7 +512,7 @@ impl OpBuilder<'_, Txs> { { let Self { best, - remove_reverted, + remove_invalid, } = self; info!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); @@ -562,7 +609,7 @@ impl OpBuilder<'_, Txs> { None }; - remove_reverted(info.reverted_tx_hashes.iter().copied().collect()); + remove_invalid(info.invalid_tx_hashes.iter().copied().collect()); let payload = ExecutedPayload { info, @@ -731,8 +778,8 @@ pub trait OpPayloadTransactions: Clone + Send + Sync + Unpin + 'sta attr: BestTransactionsAttributes, ) -> impl PayloadTransactions; - /// Removes reverted transactions from the tx pool - fn remove_reverted>( + /// Removes invalid transactions from the tx pool + fn remove_invalid>( &self, pool: Pool, hashes: Vec, @@ -748,7 +795,7 @@ impl OpPayloadTransactions for () { BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) } - fn remove_reverted>( + fn remove_invalid>( &self, pool: Pool, hashes: Vec, @@ -778,6 +825,10 @@ pub struct OpPayloadBuilderCtx, /// The metrics for the builder pub metrics: OpRBuilderMetrics, + /// Client to execute supervisor validation + pub supervisor_client: Option, + /// Level to use in supervisor validation + pub supervisor_safety_level: SafetyLevel, } impl OpPayloadBuilderCtx @@ -1035,6 +1086,18 @@ where PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) })?; + // Check transactions against supervisor if it's cross chain + if let (false, _) = is_cross_tx_valid::( + sequencer_tx.tx(), + self.supervisor_client.as_ref(), + self.supervisor_safety_level, + self.config.attributes.timestamp(), + &self.metrics, + ) { + // We skip this transaction because it's not possible to verify it's validity + continue; + } + // Cache the depositor account prior to the state transition for the deposit nonce. // // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces @@ -1135,6 +1198,24 @@ where continue; } + // Check transactions against supervisor if it's cross chain + if let (false, is_recoverable) = is_cross_tx_valid::( + tx.tx(), + self.supervisor_client.as_ref(), + self.supervisor_safety_level, + self.config.attributes.timestamp(), + &self.metrics, + ) { + // We mark the tx invalid to ensure that it won't clog out pipeline + // in case there is bug in supervisor. + best_txs.mark_invalid(tx.signer(), tx.nonce()); + if !is_recoverable { + // For some subset of errors we remove transaction from txpool + info.invalid_tx_hashes.insert(*tx.tx_hash()); + } + continue; + } + // check if the job was cancelled, if so we can exit early if self.cancel.is_cancelled() { return Ok(Some(())); @@ -1177,7 +1258,7 @@ where num_txs_simulated_fail += 1; trace!(target: "payload_builder", ?tx, "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); - info.reverted_tx_hashes.insert(*tx.tx_hash()); + info.invalid_tx_hashes.insert(*tx.tx_hash()); continue; } @@ -1355,3 +1436,93 @@ fn estimate_gas_for_builder_tx(input: Vec) -> u64 { zero_cost + nonzero_cost + 21_000 } + +/// Extracts commitment from access list entries, pointing to 0x420..022 and validates them +/// against supervisor. +/// +/// If commitment present pre-interop tx rejected. +/// +/// Returns (is_valid, is_recoverable) +pub fn is_cross_tx_valid( + tx: &N::SignedTx, + // TODO: after spec rebase we must make this field not optional + client: Option<&SupervisorValidator>, + safety_level: SafetyLevel, + timestamp: u64, + metrics: &OpRBuilderMetrics, +) -> (bool, bool) { + if tx.access_list().is_none() { + return (true, true); + } + let access_list = tx.access_list().unwrap(); + let inbox_entries = SupervisorValidator::parse_access_list(access_list) + .cloned() + .collect::>(); + if !inbox_entries.is_empty() { + metrics.inc_num_cross_chain_tx(); + if client.is_none() { + return (false, true); + } + match validate_supervisor_messages(inbox_entries, client.unwrap(), safety_level, timestamp) + { + Ok(res) => match res { + Ok(()) => (true, true), + Err(err) => { + match err { + // TODO: we should add reconnecting to supervisor in case of disconnect + InteropTxValidatorError::SupervisorServerError(err) => { + warn!(target: "payload_builder", %err, ?tx, "Supervisor error, skipping."); + metrics.inc_num_cross_chain_tx_server_error(); + (false, true) + } + InteropTxValidatorError::ValidationTimeout(_) => { + warn!(target: "payload_builder", %err, ?tx, "Cross tx validation timed out, skipping."); + metrics.inc_num_cross_chain_tx_timeout(); + (false, true) + } + err => { + trace!(target: "payload_builder", %err, ?tx, "Cross tx rejected."); + metrics.inc_num_cross_chain_tx_fail(); + // It's possible that transaction invalid now, but would be valid later. + // We should keep limited queue for transactions that could become valid. + // We should have the limit to ensure that builder won't get overwhelmed. + (false, false) + } + } + } + }, + Err(err) => { + error!(target: "payload_builder", %err, ?tx, "Client side error during cross tx validation, skipping."); + (false, true) + } + } + } else { + (true, true) + } +} + +/// Validate inbox_entries against supervisor. +pub fn validate_supervisor_messages( + inbox_entries: Vec, + client: &SupervisorValidator, + safety_level: SafetyLevel, + timestamp: u64, +) -> Result, PayloadBuilderError> { + // For block building the timestamp should be `expected time of inclusion` and timeout 0 + let descriptor = ExecutingDescriptor::new(timestamp, None); + let (channel_tx, rx) = std::sync::mpsc::channel(); + tokio::task::block_in_place(move || { + let res = tokio::runtime::Handle::current().block_on(async { + client + .validate_messages( + inbox_entries.as_slice(), + safety_level, + descriptor, + Some(core::time::Duration::from_millis(100)), + ) + .await + }); + let _ = channel_tx.send(res); + }); + rx.recv().map_err(|_| PayloadBuilderError::ChannelClosed) +} diff --git a/crates/builder/op-rbuilder/src/primitives/mod.rs b/crates/builder/op-rbuilder/src/primitives/mod.rs index 02615de6..6524a59b 100644 --- a/crates/builder/op-rbuilder/src/primitives/mod.rs +++ b/crates/builder/op-rbuilder/src/primitives/mod.rs @@ -1 +1,3 @@ pub mod reth; +#[cfg(feature = "optimism")] +pub mod supervisor; diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 37880810..8acbce0b 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -29,7 +29,7 @@ pub struct ExecutionInfo { /// Tracks fees from executed mempool transactions pub total_fees: U256, /// Tracks the reverted transaction hashes to remove from the transaction pool - pub reverted_tx_hashes: HashSet, + pub invalid_tx_hashes: HashSet, #[cfg(feature = "flashblocks")] /// Index of the last consumed flashblock pub last_flashblock_index: usize, @@ -45,7 +45,7 @@ impl ExecutionInfo { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO, - reverted_tx_hashes: HashSet::new(), + invalid_tx_hashes: HashSet::new(), #[cfg(feature = "flashblocks")] last_flashblock_index: 0, } diff --git a/crates/builder/op-rbuilder/src/primitives/supervisor.rs b/crates/builder/op-rbuilder/src/primitives/supervisor.rs new file mode 100644 index 00000000..05cbdb4d --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/supervisor.rs @@ -0,0 +1,25 @@ +//! This is our custom implementation of validator struct + +use jsonrpsee::http_client::HttpClient; +use kona_rpc::InteropTxValidator; +use std::time::Duration; + +#[derive(Debug, Clone)] +pub struct SupervisorValidator { + inner: HttpClient, +} + +impl SupervisorValidator { + pub fn new(client: HttpClient) -> Self { + Self { inner: client } + } +} + +impl InteropTxValidator for SupervisorValidator { + type SupervisorClient = HttpClient; + const DEFAULT_TIMEOUT: Duration = Duration::from_millis(100); + + fn supervisor_client(&self) -> &Self::SupervisorClient { + &self.inner + } +} From e2cb917315111b3253a61f583e6b795f57ab8c7c Mon Sep 17 00:00:00 2001 From: Hopium <135053852+Hopium21@users.noreply.github.com> Date: Wed, 19 Mar 2025 16:30:21 +0100 Subject: [PATCH 043/262] Fixed grammatical errors for improved readability (#511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrected grammar: "transactions events" → "transaction events" for proper noun-adjective agreement. Preposition correction: "on" → "at" for referring to a website link properly. Corrected article usage: "any" → "a" for clarity. --- crates/builder/op-rbuilder/src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index 7c145511..1d41726a 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -49,7 +49,7 @@ pub struct OpRbuilderArgs { help = "Safety level to pass to supervisor, values: finalized, safe, local-safe, cross-unsafe, unsafe, invalid" )] pub supervisor_safety_level: Option, - /// Signals whether to log pool transactions events + /// Signals whether to log pool transaction events #[arg(long = "builder.log-pool-transactions", default_value = "false")] pub log_pool_transactions: bool, } From b09e71edee5a8e6d318e8b7983ede52588429000 Mon Sep 17 00:00:00 2001 From: File Large Date: Wed, 26 Mar 2025 15:43:50 +0100 Subject: [PATCH 044/262] deps: reth v1.3.4 (#507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Bump deps to reth `v1.3.3` ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [ ] Added tests (if applicable) --------- Co-authored-by: Daniel Xifra --- crates/builder/op-rbuilder/Cargo.toml | 21 +- .../src/integration/integration_test.rs | 33 +- .../op-rbuilder/src/payload_builder.rs | 442 +++++++---------- .../src/payload_builder_vanilla.rs | 449 ++++++++---------- .../builder/op-rbuilder/src/primitives/mod.rs | 1 - .../src/primitives/reth/execution.rs | 6 +- crates/builder/op-rbuilder/src/tx_signer.rs | 4 +- 7 files changed, 414 insertions(+), 542 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index f18c1402..5f2d8800 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -16,6 +16,7 @@ reth-optimism-payload-builder.workspace = true reth-optimism-evm.workspace = true reth-optimism-consensus.workspace = true reth-optimism-primitives.workspace = true +reth-optimism-txpool.workspace = true reth-cli-util.workspace = true reth-payload-primitives.workspace = true reth-evm.workspace = true @@ -29,7 +30,6 @@ reth-payload-builder.workspace = true reth-node-ethereum.workspace = true reth-chain-state.workspace = true reth-execution-types.workspace = true -reth-ethereum-payload-builder.workspace = true reth-metrics.workspace = true reth-provider.workspace = true reth-revm.workspace = true @@ -40,6 +40,7 @@ reth-payload-util.workspace = true reth-transaction-pool.workspace = true reth-testing-utils.workspace = true reth-optimism-forks.workspace = true + alloy-primitives.workspace = true alloy-consensus.workspace = true alloy-eips.workspace = true @@ -50,22 +51,25 @@ alloy-rpc-types-eth.workspace = true alloy-rpc-client.workspace = true alloy-transport.workspace = true alloy-network.workspace = true +alloy-provider.workspace = true +alloy-serde.workspace = true # op +alloy-op-evm.workspace = true op-alloy-consensus.workspace = true op-alloy-rpc-types-engine.workspace = true op-alloy-network.workspace = true # kona -kona-rpc = {workspace = true, optional = true} -kona-interop = {workspace = true, optional = true} +kona-rpc.workspace = true +kona-interop.workspace = true revm.workspace = true +op-revm.workspace = true tracing.workspace = true futures-util = "0.3.31" eyre.workspace = true -alloy-provider.workspace = true tower = "0.4" serde_with.workspace = true serde.workspace = true @@ -85,18 +89,15 @@ chrono = "0.4" uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } tokio-tungstenite = "0.26.2" rand = "0.8.5" -alloy-serde = "0.7" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "e74a1fd01366e4ddd13515da4efda59cdc8fbce0" } +# `flashblocks` branch +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "6b94e1037f41e0817f2bcb1f1ca5013a5359616a" } url = "2.5.3" [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } -[dev-dependencies] -reth-discv4.workspace = true - [features] default = ["jemalloc"] @@ -118,8 +119,6 @@ min-info-logs = ["tracing/release_max_level_info"] min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] -optimism = ["reth-optimism-node/optimism", "reth-optimism-cli/optimism", "kona-rpc", "kona-interop"] - integration = [] flashblocks = [] diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 8d0ab7fb..5aa13467 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -1,24 +1,24 @@ #[cfg(all(test, feature = "integration"))] mod tests { - use crate::integration::{ - op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, + use crate::{ + integration::{op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework}, + tester::{BlockGenerator, EngineApi}, + tx_signer::Signer, }; - use crate::tester::{BlockGenerator, EngineApi}; - use crate::tx_signer::Signer; use alloy_consensus::{Transaction, TxEip1559}; - use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; - use alloy_eips::eip2718::Encodable2718; + use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718}; use alloy_primitives::hex; - use alloy_provider::Identity; - use alloy_provider::{Provider, ProviderBuilder}; + use alloy_provider::{Identity, Provider, ProviderBuilder}; use alloy_rpc_types_eth::BlockTransactionsKind; use futures_util::StreamExt; use op_alloy_consensus::OpTypedTransaction; use op_alloy_network::Optimism; - use std::cmp::max; - use std::path::PathBuf; - use std::sync::{Arc, Mutex}; - use std::time::Duration; + use std::{ + cmp::max, + path::PathBuf, + sync::{Arc, Mutex}, + time::Duration, + }; use tokio_tungstenite::connect_async; use uuid::Uuid; @@ -77,7 +77,7 @@ mod tests { // query the block and the transactions inside the block let block = provider - .get_block_by_hash(block_hash, BlockTransactionsKind::Hashes) + .get_block_by_hash(block_hash) .await? .expect("block"); @@ -185,7 +185,7 @@ mod tests { // query the block and the transactions inside the block let block = provider - .get_block_by_hash(block_hash, BlockTransactionsKind::Hashes) + .get_block_by_hash(block_hash) .await? .expect("block"); @@ -314,7 +314,8 @@ mod tests { // Query the block and check transaction ordering let block = provider - .get_block_by_hash(block_hash, BlockTransactionsKind::Full) + .get_block_by_hash(block_hash) + .full() .await? .expect("block"); @@ -420,7 +421,7 @@ mod tests { // query the block and the transactions inside the block let block = provider - .get_block_by_hash(block_hash, BlockTransactionsKind::Hashes) + .get_block_by_hash(block_hash) .await? .expect("block"); diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 82613c1a..11521d49 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -1,84 +1,75 @@ -use std::{fmt::Display, sync::Arc, sync::Mutex}; - use crate::{ generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, - primitives::reth::{ExecutedPayload, ExecutionInfo}, + primitives::reth::ExecutionInfo, tx_signer::Signer, }; use alloy_consensus::{Eip658Value, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; -use alloy_eips::merge::BEACON_NONCE; -use alloy_eips::Encodable2718; +use alloy_eips::{merge::BEACON_NONCE, Encodable2718}; +use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawals; +use futures_util::{FutureExt, SinkExt}; use op_alloy_consensus::OpDepositReceipt; -use reth::builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}; -use reth::payload::PayloadBuilderHandle; -use reth_basic_payload_builder::commit_withdrawals; -use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; -use reth_basic_payload_builder::{BuildOutcome, PayloadConfig}; -use reth_chainspec::ChainSpecProvider; -use reth_chainspec::EthChainSpec; +use op_revm::OpSpecId; +use reth::{ + builder::{ + components::{PayloadBuilderBuilder, PayloadServiceBuilder}, + node::FullNodeTypes, + BuilderContext, + }, + payload::PayloadBuilderHandle, +}; +use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, BuildOutcome, PayloadConfig}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::{ - env::EvmEnv, system_calls::SystemCaller, ConfigureEvmEnv, ConfigureEvmFor, Database, Evm, - EvmError, InvalidTxError, NextBlockEnvAttributes, + env::EvmEnv, eth::receipt_builder::ReceiptBuilderCtx, execute::BlockBuilder, ConfigureEvm, + Database, Evm, EvmError, InvalidTxError, }; use reth_execution_types::ExecutionOutcome; -use reth_node_api::NodePrimitives; -use reth_node_api::NodeTypesWithEngine; -use reth_node_api::TxTy; +use reth_node_api::{NodePrimitives, NodeTypesWithEngine, TxTy}; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; -use reth_optimism_evm::BasicOpReceiptBuilder; -use reth_optimism_evm::OpEvmConfig; -use reth_optimism_evm::{OpReceiptBuilder, ReceiptBuilderCtx}; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; use reth_optimism_node::OpEngineTypes; -use reth_optimism_payload_builder::error::OpPayloadBuilderError; -use reth_optimism_payload_builder::payload::{OpBuiltPayload, OpPayloadBuilderAttributes}; -use reth_optimism_payload_builder::OpPayloadPrimitives; -use reth_optimism_primitives::OpPrimitives; -use reth_optimism_primitives::OpTransactionSigned; +use reth_optimism_payload_builder::{ + error::OpPayloadBuilderError, + payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, +}; +use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; +use reth_optimism_txpool::OpPooledTx; use reth_payload_builder::PayloadBuilderService; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; -use reth_payload_util::BestPayloadTransactions; -use reth_payload_util::PayloadTransactions; -use reth_primitives::{transaction::SignedTransactionIntoRecoveredExt, BlockBody, SealedHeader}; -use reth_primitives_traits::proofs; -use reth_primitives_traits::Block as _; -use reth_primitives_traits::SignedTransaction; -use reth_provider::CanonStateSubscriptions; -use reth_provider::StorageRootProvider; +use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; +use reth_primitives::{BlockBody, SealedHeader}; +use reth_primitives_traits::{proofs, Block as _, SignedTransaction}; use reth_provider::{ - HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, + CanonStateSubscriptions, HashedPostStateProvider, ProviderError, StateProviderFactory, + StateRootProvider, StorageRootProvider, }; use reth_revm::database::StateProviderDatabase; -use reth_transaction_pool::PoolTransaction; -use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; -use revm::primitives::ExecutionResult; +use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use revm::{ - db::{states::bundle_state::BundleRetention, BundleState, State}, - primitives::ResultAndState, + context::{result::ResultAndState, Block as _}, + database::{states::bundle_state::BundleRetention, BundleState, State}, DatabaseCommit, }; use rollup_boost::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, }; -use std::error::Error as StdError; +use serde::{Deserialize, Serialize}; +use std::sync::{Arc, Mutex}; +use tokio::{ + net::{TcpListener, TcpStream}, + sync::mpsc, +}; +use tokio_tungstenite::{accept_async, WebSocketStream}; use tokio_util::sync::CancellationToken; use tracing::{debug, trace, warn}; - -use futures_util::FutureExt; -use futures_util::SinkExt; -use tokio::net::{TcpListener, TcpStream}; -use tokio::sync::mpsc; -use tokio_tungstenite::accept_async; -use tokio_tungstenite::WebSocketStream; use url::Url; -use serde::{Deserialize, Serialize}; - #[derive(Debug, Serialize, Deserialize)] struct FlashblocksMetadata { receipts: HashMap, @@ -120,7 +111,7 @@ impl CustomOpPayloadBuilder { } } -impl PayloadServiceBuilder for CustomOpPayloadBuilder +impl PayloadBuilderBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< Types: NodeTypesWithEngine< @@ -133,30 +124,45 @@ where + Unpin + 'static, { - type PayloadBuilder = OpPayloadBuilder; + type PayloadBuilder = OpPayloadBuilder; async fn build_payload_builder( - &self, + self, ctx: &BuilderContext, pool: Pool, ) -> eyre::Result { Ok(OpPayloadBuilder::new( - OpEvmConfig::new(ctx.chain_spec()), + OpEvmConfig::optimism(ctx.chain_spec()), pool, ctx.provider().clone(), - Arc::new(BasicOpReceiptBuilder::default()), self.flashblocks_ws_url.clone(), self.chain_block_time, self.flashblock_block_time, )) } +} - fn spawn_payload_builder_service( +impl PayloadServiceBuilder for CustomOpPayloadBuilder +where + Node: FullNodeTypes< + Types: NodeTypesWithEngine< + Engine = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + >, + Pool: TransactionPool>> + + Unpin + + 'static, + ::Transaction: OpPooledTx, +{ + async fn spawn_payload_builder_service( self, ctx: &BuilderContext, - payload_builder: Self::PayloadBuilder, + pool: Pool, ) -> eyre::Result::Engine>> { tracing::info!("Spawning a custom payload builder"); + let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); let payload_generator = BlockPayloadJobGenerator::with_builder( @@ -179,16 +185,13 @@ where } } -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, - EvmConfig: Clone + Send + Sync, - N: NodePrimitives, { - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; fn try_build( &self, @@ -210,30 +213,27 @@ where /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct OpPayloadBuilder { +pub struct OpPayloadBuilder { /// The type responsible for creating the evm. - pub evm_config: EvmConfig, + pub evm_config: OpEvmConfig, /// The transaction pool pub pool: Pool, /// Node client pub client: Client, /// Channel sender for publishing messages pub tx: mpsc::UnboundedSender, - /// Node primitive types. - pub receipt_builder: Arc>, /// chain block time pub chain_block_time: u64, /// Flashblock block time pub flashblock_block_time: u64, } -impl OpPayloadBuilder { +impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. pub fn new( - evm_config: EvmConfig, + evm_config: OpEvmConfig, pool: Pool, client: Client, - receipt_builder: Arc>, flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, @@ -252,7 +252,6 @@ impl OpPayloadBuilder OpPayloadBuilder OpPayloadBuilder +impl OpPayloadBuilder where - Pool: TransactionPool>, + Pool: TransactionPool>, Client: StateProviderFactory + ChainSpecProvider, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, - EvmConfig: ConfigureEvmFor, { /// Send a message to be published pub fn send_message(&self, message: String) -> Result<(), Box> { @@ -329,24 +326,49 @@ where /// 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>( + fn build_payload( &self, - args: BuildArguments, OpBuiltPayload>, - best_payload: BlockCell>, + args: BuildArguments, OpBuiltPayload>, + best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { + let BuildArguments { config, cancel, .. } = args; + + let chain_spec = self.client.chain_spec(); + let timestamp = config.attributes.timestamp(); + 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: 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 evm_env = self - .evm_env(&args.config.attributes, &args.config.parent_header) + .evm_config + .next_evm_env(&config.parent_header, &block_env_attributes) .map_err(PayloadBuilderError::other)?; - let BuildArguments { config, cancel, .. } = args; - let ctx = OpPayloadBuilderCtx { evm_config: self.evm_config.clone(), chain_spec: self.client.chain_spec(), config, evm_env, + block_env_attributes, cancel, - receipt_builder: self.receipt_builder.clone(), }; let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; @@ -443,33 +465,15 @@ where std::thread::sleep(std::time::Duration::from_millis(self.flashblock_block_time)); } } - - /// Returns the configured [`EvmEnv`] for the targeted payload - /// (that has the `parent` as its parent). - pub fn evm_env( - &self, - attributes: &OpPayloadBuilderAttributes, - parent: &Header, - ) -> Result, EvmConfig::Error> { - let next_attributes = NextBlockEnvAttributes { - timestamp: attributes.timestamp(), - suggested_fee_recipient: attributes.suggested_fee_recipient(), - prev_randao: attributes.prev_randao(), - gas_limit: attributes.gas_limit.unwrap_or(parent.gas_limit), - }; - self.evm_config.next_evm_env(parent, next_attributes) - } } -impl PayloadBuilder for OpPayloadBuilder +impl PayloadBuilder for OpPayloadBuilder where Client: StateProviderFactory + ChainSpecProvider + Clone, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, - Pool: TransactionPool>, - EvmConfig: ConfigureEvmFor, + Pool: TransactionPool>, { - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; fn try_build( &self, @@ -480,20 +484,16 @@ where } } -pub fn build_block( +pub fn build_block( mut state: State, - ctx: &OpPayloadBuilderCtx, - info: &mut ExecutionInfo, -) -> Result<(OpBuiltPayload, FlashblocksPayloadV1, BundleState), PayloadBuilderError> + ctx: &OpPayloadBuilderCtx, + info: &mut ExecutionInfo, +) -> Result<(OpBuiltPayload, FlashblocksPayloadV1, BundleState), PayloadBuilderError> where - EvmConfig: ConfigureEvmFor, ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { - let withdrawals_root = ctx.commit_withdrawals(&mut state)?; - // TODO: We must run this only once per block, but we are running it on every flashblock // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call @@ -552,11 +552,11 @@ where let header = Header { parent_hash: ctx.parent().hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: ctx.evm_env.block_env.coinbase, + beneficiary: ctx.evm_env.block_env.beneficiary, state_root, transactions_root, receipts_root, - withdrawals_root, + withdrawals_root: None, logs_bloom, timestamp: ctx.attributes().payload_attributes.timestamp, mix_hash: ctx.attributes().payload_attributes.prev_randao, @@ -574,7 +574,7 @@ where }; // seal the block - let block = N::Block::new( + let block = alloy_consensus::Block::::new( header, BlockBody { transactions: info.executed_transactions.clone(), @@ -603,14 +603,14 @@ where .iter() .zip(new_receipts.iter()) .map(|(tx, receipt)| (*tx.tx_hash(), receipt.clone())) - .collect::>(); + .collect::>(); let new_account_balances = new_bundle .state .iter() .filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance))) .collect::>(); - let metadata: FlashblocksMetadata = FlashblocksMetadata { + let metadata: FlashblocksMetadata = FlashblocksMetadata { receipts: receipts_with_hash, new_account_balances, block_number: ctx.parent().number + 1, @@ -663,21 +663,19 @@ where )) } -fn execute_pre_steps( +fn execute_pre_steps( state: &mut State, - ctx: &OpPayloadBuilderCtx, -) -> Result, PayloadBuilderError> + ctx: &OpPayloadBuilderCtx, +) -> Result, PayloadBuilderError> where - EvmConfig: ConfigureEvmFor, ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, DB: Database, { - // 1. apply eip-4788 pre block contract call - ctx.apply_pre_beacon_root_contract_call(state)?; - - // 2. ensure create2deployer is force deployed - ctx.ensure_create2_deployer(state)?; + // 1. apply pre-execution changes + ctx.evm_config + .builder_for_next_block(state, ctx.parent(), ctx.block_env_attributes.clone()) + .map_err(PayloadBuilderError::other)? + .apply_pre_execution_changes()?; // 3. execute sequencer transactions let info = ctx.execute_sequencer_transactions(state)?; @@ -708,26 +706,24 @@ impl OpPayloadTransactions for () { /// 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: EvmConfig, + pub evm_config: OpEvmConfig, /// The chainspec pub chain_spec: Arc, /// How to build the payload. - pub config: PayloadConfig>, + pub config: PayloadConfig>, /// Evm Settings - pub evm_env: EvmEnv, + pub evm_env: EvmEnv, + /// Block env attributes for the current block. + pub block_env_attributes: OpNextBlockEnvAttributes, /// Marker to check whether the job has been cancelled. pub cancel: CancellationToken, - /// Receipt builder. - pub receipt_builder: Arc>, } -impl OpPayloadBuilderCtx +impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvmEnv, ChainSpec: EthChainSpec + OpHardforks, - N: NodePrimitives, { /// Returns the parent block the payload will be build on. pub fn parent(&self) -> &SealedHeader { @@ -735,7 +731,7 @@ where } /// Returns the builder attributes. - pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { + pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { &self.config.attributes } @@ -750,24 +746,24 @@ where pub fn block_gas_limit(&self) -> u64 { self.attributes() .gas_limit - .unwrap_or_else(|| self.evm_env.block_env.gas_limit.saturating_to()) + .unwrap_or(self.evm_env.block_env.gas_limit) } /// Returns the block number for the block. pub fn block_number(&self) -> u64 { - self.evm_env.block_env.number.to() + self.evm_env.block_env.number } /// Returns the current base fee pub fn base_fee(&self) -> u64 { - self.evm_env.block_env.basefee.to() + self.evm_env.block_env.basefee } /// Returns the current blob gas price. pub fn get_blob_gasprice(&self) -> Option { self.evm_env .block_env - .get_blob_gasprice() + .blob_gasprice() .map(|gasprice| gasprice as u64) } @@ -835,90 +831,20 @@ where self.chain_spec .is_holocene_active_at_timestamp(self.attributes().timestamp()) } - - /// Commits the withdrawals from the payload attributes to the state. - pub fn commit_withdrawals(&self, db: &mut State) -> Result, ProviderError> - where - DB: Database, - { - commit_withdrawals( - db, - &self.chain_spec, - self.attributes().payload_attributes.timestamp, - &self.attributes().payload_attributes.withdrawals, - ) - } - - /// Ensure that the create2deployer is force-deployed at the canyon transition. Optimism - /// blocks will always have at least a single transaction in them (the L1 info transaction), - /// so we can safely assume that this will always be triggered upon the transition and that - /// the above check for empty blocks will never be hit on OP chains. - pub fn ensure_create2_deployer(&self, db: &mut State) -> Result<(), PayloadBuilderError> - where - DB: Database, - DB::Error: Display, - { - reth_optimism_evm::ensure_create2_deployer( - self.chain_spec.clone(), - self.attributes().payload_attributes.timestamp, - db, - ) - .map_err(|err| { - warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); - PayloadBuilderError::other(OpPayloadBuilderError::ForceCreate2DeployerFail) - }) - } } -impl OpPayloadBuilderCtx +impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvmFor, ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, { - /// apply eip-4788 pre block contract call - pub fn apply_pre_beacon_root_contract_call( - &self, - db: &mut DB, - ) -> Result<(), PayloadBuilderError> - where - DB: Database + DatabaseCommit, - DB::Error: Display, - ::Error: StdError, - { - SystemCaller::new(self.evm_config.clone(), self.chain_spec.clone()) - .pre_block_beacon_root_contract_call( - db, - &self.evm_env, - self.attributes() - .payload_attributes - .parent_beacon_block_root, - ) - .map_err(|err| { - warn!(target: "payload_builder", - parent_header=%self.parent().hash(), - %err, - "failed to apply beacon root contract call for payload" - ); - PayloadBuilderError::Internal(err.into()) - })?; - - Ok(()) - } - /// Constructs a receipt for the given transaction. - fn build_receipt( + fn build_receipt( &self, - info: &ExecutionInfo, - result: ExecutionResult, + ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, deposit_nonce: Option, - tx: &N::SignedTx, - ) -> N::Receipt { - match self.receipt_builder.build_receipt(ReceiptBuilderCtx { - tx, - result, - cumulative_gas_used: info.cumulative_gas_used, - }) { + ) -> OpReceipt { + let receipt_builder = self.evm_config.block_executor_factory().receipt_builder(); + match receipt_builder.build_receipt(ctx) { Ok(receipt) => receipt, Err(ctx) => { let receipt = alloy_consensus::Receipt { @@ -929,17 +855,16 @@ where logs: ctx.result.into_logs(), }; - self.receipt_builder - .build_deposit_receipt(OpDepositReceipt { - inner: receipt, - deposit_nonce, - // The deposit receipt version was introduced in Canyon to indicate an - // update to how receipt hashes should be computed - // when set. The state transition process ensures - // this is only set for post-Canyon deposit - // transactions. - deposit_receipt_version: self.is_canyon_active().then_some(1), - }) + receipt_builder.build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_nonce, + // The deposit receipt version was introduced in Canyon to indicate an + // update to how receipt hashes should be computed + // when set. The state transition process ensures + // this is only set for post-Canyon deposit + // transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + }) } } } @@ -948,7 +873,7 @@ where pub fn execute_sequencer_transactions( &self, db: &mut State, - ) -> Result, PayloadBuilderError> + ) -> Result, PayloadBuilderError> where DB: Database, { @@ -994,11 +919,7 @@ where )) })?; - let tx_env = self - .evm_config - .tx_env(sequencer_tx.tx(), sequencer_tx.signer()); - - let ResultAndState { result, state } = match evm.transact(tx_env) { + let ResultAndState { result, state } = match evm.transact(&sequencer_tx) { Ok(res) => res, Err(err) => { if err.is_invalid_tx_err() { @@ -1010,24 +931,25 @@ where } }; - // commit changes - evm.db_mut().commit(state); - - let gas_used = result.gas_used(); - // add gas used by the transaction to cumulative gas used, before creating the receipt + let gas_used = result.gas_used(); info.cumulative_gas_used += gas_used; - info.receipts.push(self.build_receipt( - &info, + let ctx = ReceiptBuilderCtx { + tx: sequencer_tx.inner(), + evm: &evm, result, - depositor_nonce, - sequencer_tx.tx(), - )); + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, depositor_nonce)); + + // commit changes + evm.db_mut().commit(state); // append sender and transaction to the respective lists info.executed_senders.push(sequencer_tx.signer()); - info.executed_transactions.push(sequencer_tx.into_tx()); + info.executed_transactions.push(sequencer_tx.into_inner()); } Ok(info) @@ -1038,10 +960,10 @@ where /// Returns `Ok(Some(())` if the job was cancelled. pub fn execute_best_transactions( &self, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, db: &mut State, mut best_txs: impl PayloadTransactions< - Transaction: PoolTransaction, + Transaction: PoolTransaction, >, batch_gas_limit: u64, ) -> Result, PayloadBuilderError> @@ -1070,7 +992,7 @@ where } // ensure we still have capacity for this transaction - if info.is_tx_over_limits(tx.tx(), batch_gas_limit, None, None) { + if info.is_tx_over_limits(tx.inner(), batch_gas_limit, None, None) { println!("A"); // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from @@ -1092,11 +1014,8 @@ where return Ok(Some(())); } - // Configure the environment for the tx. - let tx_env = self.evm_config.tx_env(tx.tx(), tx.signer()); - println!("Start transaction"); - let ResultAndState { result, state } = match evm.transact(tx_env) { + let ResultAndState { result, state } = match evm.transact(&tx) { Ok(res) => res, Err(err) => { if let Some(err) = err.as_invalid_tx_err() { @@ -1118,17 +1037,22 @@ where }; println!("Finish transaction"); - // commit changes - evm.db_mut().commit(state); - - let gas_used = result.gas_used(); - // add gas used by the transaction to cumulative gas used, before creating the // receipt + let gas_used = result.gas_used(); info.cumulative_gas_used += gas_used; - info.receipts - .push(self.build_receipt(info, result, None, &tx)); + let ctx = ReceiptBuilderCtx { + tx: tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, None)); + + // commit changes + evm.db_mut().commit(state); // update add to total fees let miner_fee = tx @@ -1138,7 +1062,7 @@ where // append sender and transaction to the respective lists info.executed_senders.push(tx.signer()); - info.executed_transactions.push(tx.into_tx()); + info.executed_transactions.push(tx.into_inner()); } Ok(None) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 4113b762..c95bc059 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1,84 +1,81 @@ use crate::{ generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, metrics::OpRBuilderMetrics, - primitives::reth::{ExecutedPayload, ExecutionInfo}, - primitives::supervisor::SupervisorValidator, + primitives::{ + reth::{ExecutedPayload, ExecutionInfo}, + supervisor::SupervisorValidator, + }, tx_signer::Signer, }; -use alloy_consensus::constants::EMPTY_WITHDRAWALS; -use alloy_consensus::transaction::Recovered; use alloy_consensus::{ - Eip658Value, Header, Transaction, TxEip1559, Typed2718, EMPTY_OMMER_ROOT_HASH, + constants::EMPTY_WITHDRAWALS, transaction::Recovered, Eip658Value, Header, Transaction, + TxEip1559, Typed2718, EMPTY_OMMER_ROOT_HASH, }; use alloy_eips::merge::BEACON_NONCE; -use alloy_primitives::private::alloy_rlp::Encodable; -use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; +use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxHash, TxKind, B256, U256}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawals; use jsonrpsee::http_client::HttpClientBuilder; use kona_interop::{ExecutingDescriptor, SafetyLevel}; use kona_rpc::{InteropTxValidator, InteropTxValidatorError}; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; -use reth::builder::{components::PayloadServiceBuilder, node::FullNodeTypes, BuilderContext}; -use reth::core::primitives::InMemorySize; -use reth::payload::PayloadBuilderHandle; +use op_revm::OpSpecId; +use reth::{ + builder::{ + components::{PayloadBuilderBuilder, PayloadServiceBuilder}, + node::FullNodeTypes, + BuilderContext, + }, + core::primitives::InMemorySize, + payload::PayloadBuilderHandle, +}; use reth_basic_payload_builder::{ BasicPayloadJobGeneratorConfig, BuildOutcome, BuildOutcomeKind, PayloadConfig, }; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_evm::{ - env::EvmEnv, system_calls::SystemCaller, ConfigureEvmEnv, ConfigureEvmFor, Database, Evm, - EvmError, InvalidTxError, NextBlockEnvAttributes, + env::EvmEnv, eth::receipt_builder::ReceiptBuilderCtx, execute::BlockBuilder, ConfigureEvm, + Database, Evm, EvmError, InvalidTxError, }; use reth_execution_types::ExecutionOutcome; -use reth_node_api::NodePrimitives; -use reth_node_api::NodeTypesWithEngine; -use reth_node_api::TxTy; +use reth_node_api::{NodePrimitives, NodeTypesWithEngine, TxTy}; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; -use reth_optimism_evm::BasicOpReceiptBuilder; -use reth_optimism_evm::OpEvmConfig; -use reth_optimism_evm::{OpReceiptBuilder, ReceiptBuilderCtx}; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; use reth_optimism_node::OpEngineTypes; -use reth_optimism_payload_builder::config::{OpBuilderConfig, OpDAConfig}; -use reth_optimism_payload_builder::OpPayloadPrimitives; use reth_optimism_payload_builder::{ + config::{OpBuilderConfig, OpDAConfig}, error::OpPayloadBuilderError, payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, + OpPayloadPrimitives, }; use reth_optimism_primitives::{ - OpPrimitives, OpTransactionSigned, ADDRESS_L2_TO_L1_MESSAGE_PASSER, + OpPrimitives, OpReceipt, OpTransactionSigned, ADDRESS_L2_TO_L1_MESSAGE_PASSER, }; +use reth_optimism_txpool::OpPooledTx; use reth_payload_builder::PayloadBuilderService; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; -use reth_payload_util::BestPayloadTransactions; -use reth_payload_util::PayloadTransactions; -use reth_primitives::{transaction::SignedTransactionIntoRecoveredExt, BlockBody, SealedHeader}; -use reth_primitives_traits::proofs; -use reth_primitives_traits::Block; -use reth_primitives_traits::RecoveredBlock; -use reth_primitives_traits::SignedTransaction; -use reth_provider::CanonStateSubscriptions; +use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; +use reth_primitives::{BlockBody, SealedHeader}; +use reth_primitives_traits::{proofs, Block as _, RecoveredBlock, SignedTransaction}; use reth_provider::{ - HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, - StorageRootProvider, + CanonStateSubscriptions, HashedPostStateProvider, ProviderError, StateProviderFactory, + StateRootProvider, StorageRootProvider, }; use reth_revm::database::StateProviderDatabase; -use reth_transaction_pool::BestTransactionsAttributes; -use reth_transaction_pool::PoolTransaction; -use reth_transaction_pool::TransactionPool; +use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use revm::{ - db::{states::bundle_state::BundleRetention, State}, - primitives::{ExecutionResult, ResultAndState}, + context::{result::ResultAndState, Block as _}, + database::{states::bundle_state::BundleRetention, State}, DatabaseCommit, }; -use std::error::Error as StdError; -use std::{fmt::Display, sync::Arc, time::Instant}; +use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; -use tracing::{error, info, trace, warn}; +use tracing::*; use url::Url; #[derive(Debug, Clone, Default)] @@ -132,7 +129,7 @@ impl CustomOpPayloadBuilder { } } -impl PayloadServiceBuilder for CustomOpPayloadBuilder +impl PayloadBuilderBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< Types: NodeTypesWithEngine< @@ -144,31 +141,47 @@ where Pool: TransactionPool>> + Unpin + 'static, + ::Transaction: OpPooledTx, { - type PayloadBuilder = OpPayloadBuilderVanilla; + type PayloadBuilder = OpPayloadBuilderVanilla; async fn build_payload_builder( - &self, + self, ctx: &BuilderContext, pool: Pool, ) -> eyre::Result { Ok(OpPayloadBuilderVanilla::new( - OpEvmConfig::new(ctx.chain_spec()), + OpEvmConfig::optimism(ctx.chain_spec()), self.builder_signer, pool, ctx.provider().clone(), - Arc::new(BasicOpReceiptBuilder::default()), self.supervisor_url.clone(), self.supervisor_safety_level.clone(), )) } +} - fn spawn_payload_builder_service( +impl PayloadServiceBuilder for CustomOpPayloadBuilder +where + Node: FullNodeTypes< + Types: NodeTypesWithEngine< + Engine = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + >, + Pool: TransactionPool>> + + Unpin + + 'static, + ::Transaction: OpPooledTx, +{ + async fn spawn_payload_builder_service( self, ctx: &BuilderContext, - payload_builder: Self::PayloadBuilder, + pool: Pool, ) -> eyre::Result::Engine>> { tracing::info!("Spawning a custom payload builder"); + let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); let payload_generator = BlockPayloadJobGenerator::with_builder( @@ -191,17 +204,15 @@ where } } -impl reth_basic_payload_builder::PayloadBuilder - for OpPayloadBuilderVanilla +impl reth_basic_payload_builder::PayloadBuilder + for OpPayloadBuilderVanilla where Pool: Clone + Send + Sync, Client: Clone + Send + Sync, - EvmConfig: Clone + Send + Sync, - N: NodePrimitives, Txs: Clone + Send + Sync, { - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; fn try_build( &self, @@ -223,9 +234,9 @@ where /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct OpPayloadBuilderVanilla { +pub struct OpPayloadBuilderVanilla { /// The type responsible for creating the evm. - pub evm_config: EvmConfig, + pub evm_config: OpEvmConfig, /// The builder's signer key to use for an end of block tx pub builder_signer: Option, /// The transaction pool @@ -239,24 +250,19 @@ pub struct OpPayloadBuilderVanilla>, /// Client to execute supervisor validation pub supervisor_client: Option, /// Level to use in supervisor validation pub supervisor_safety_level: SafetyLevel, } -impl - OpPayloadBuilderVanilla -{ +impl OpPayloadBuilderVanilla { /// `OpPayloadBuilder` constructor. pub fn new( - evm_config: EvmConfig, + evm_config: OpEvmConfig, builder_signer: Option, pool: Pool, client: Client, - receipt_builder: Arc>, supervisor_url: Option, supervisor_safety_level: Option, ) -> Self { @@ -265,7 +271,6 @@ impl builder_signer, pool, client, - receipt_builder, supervisor_url, supervisor_safety_level, Default::default(), @@ -276,11 +281,10 @@ impl // number of args #[allow(clippy::too_many_arguments)] pub fn with_builder_config( - evm_config: EvmConfig, + evm_config: OpEvmConfig, builder_signer: Option, pool: Pool, client: Client, - receipt_builder: Arc>, supervisor_url: Option, supervisor_safety_level: Option, config: OpBuilderConfig, @@ -301,7 +305,6 @@ impl Self { pool, client, - receipt_builder, config, evm_config, best_transactions: (), @@ -313,17 +316,14 @@ impl } } -impl PayloadBuilder - for OpPayloadBuilderVanilla +impl PayloadBuilder for OpPayloadBuilderVanilla where Client: StateProviderFactory + ChainSpecProvider + Clone, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, - Pool: TransactionPool>, - EvmConfig: ConfigureEvmFor, + Pool: TransactionPool>, Txs: OpPayloadTransactions, { - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; fn try_build( &self, @@ -372,12 +372,10 @@ where } } -impl OpPayloadBuilderVanilla +impl OpPayloadBuilderVanilla where - Pool: TransactionPool>, + Pool: TransactionPool>, Client: StateProviderFactory + ChainSpecProvider, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, - EvmConfig: ConfigureEvmFor, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -389,31 +387,56 @@ where /// a result indicating success with the payload or an error in case of failure. fn build_payload<'a, Txs>( &self, - args: BuildArguments, OpBuiltPayload>, + args: BuildArguments, OpBuiltPayload>, best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, remove_reverted: impl FnOnce(Vec), - ) -> Result>, PayloadBuilderError> + ) -> Result, PayloadBuilderError> where - Txs: PayloadTransactions>, + Txs: PayloadTransactions>, { - let evm_env = self - .evm_env(&args.config.attributes, &args.config.parent_header) - .map_err(PayloadBuilderError::other)?; - let BuildArguments { mut cached_reads, config, cancel, } = args; + let chain_spec = self.client.chain_spec(); + let timestamp = config.attributes.timestamp(); + 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: 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 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(), - chain_spec: self.client.chain_spec(), + chain_spec, config, evm_env, + block_env_attributes, cancel, - receipt_builder: self.receipt_builder.clone(), builder_signer: self.builder_signer, metrics: Default::default(), supervisor_client: self.supervisor_client.clone(), @@ -441,22 +464,6 @@ where } .map(|out| out.with_cached_reads(cached_reads)) } - - /// Returns the configured [`EvmEnv`] for the targeted payload - /// (that has the `parent` as its parent). - pub fn evm_env( - &self, - attributes: &OpPayloadBuilderAttributes, - parent: &Header, - ) -> Result, EvmConfig::Error> { - let next_attributes = NextBlockEnvAttributes { - timestamp: attributes.timestamp(), - suggested_fee_recipient: attributes.suggested_fee_recipient(), - prev_randao: attributes.prev_randao(), - gas_limit: attributes.gas_limit.unwrap_or(parent.gas_limit), - }; - self.evm_config.next_evm_env(parent, next_attributes) - } } /// The type that builds the payload. @@ -497,15 +504,14 @@ impl<'a, Txs> OpBuilder<'a, Txs> { impl OpBuilder<'_, Txs> { /// Executes the payload and returns the outcome. - pub fn execute( + pub fn execute( self, state: &mut State, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, ) -> Result>, PayloadBuilderError> where N: OpPayloadPrimitives<_TX = OpTransactionSigned>, Txs: PayloadTransactions>, - EvmConfig: ConfigureEvmFor, ChainSpec: EthChainSpec + OpHardforks, DB: Database + AsRef

, P: StorageRootProvider, @@ -516,11 +522,11 @@ impl OpBuilder<'_, Txs> { } = self; info!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number, "building new payload"); - // 1. apply eip-4788 pre block contract call - ctx.apply_pre_beacon_root_contract_call(state)?; - - // 2. ensure create2deployer is force deployed - ctx.ensure_create2_deployer(state)?; + // 1. apply pre-execution changes + ctx.evm_config + .builder_for_next_block(state, ctx.parent(), ctx.block_env_attributes.clone()) + .map_err(PayloadBuilderError::other)? + .apply_pre_execution_changes()?; let sequencer_tx_start_time = Instant::now(); @@ -620,16 +626,14 @@ impl OpBuilder<'_, Txs> { } /// Builds the payload on top of the state. - pub fn build( + pub fn build( self, mut state: State, - ctx: OpPayloadBuilderCtx, - ) -> Result>, PayloadBuilderError> + ctx: OpPayloadBuilderCtx, + ) -> Result, PayloadBuilderError> where - EvmConfig: ConfigureEvmFor, ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, - Txs: PayloadTransactions>, + Txs: PayloadTransactions>, DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { @@ -697,7 +701,7 @@ impl OpBuilder<'_, Txs> { let header = Header { parent_hash: ctx.parent().hash(), ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: ctx.evm_env.block_env.coinbase, + beneficiary: ctx.evm_env.block_env.beneficiary, state_root, transactions_root, receipts_root, @@ -719,7 +723,7 @@ impl OpBuilder<'_, Txs> { }; // seal the block - let block = N::Block::new( + let block = alloy_consensus::Block::::new( header, BlockBody { transactions: info.executed_transactions, @@ -732,11 +736,12 @@ impl OpBuilder<'_, Txs> { info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); // create the executed block data - let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { + let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { block: ExecutedBlock { - recovered_block: Arc::new(RecoveredBlock::new_sealed( - sealed_block.as_ref().clone(), - info.executed_senders, + recovered_block: Arc::new(RecoveredBlock::< + alloy_consensus::Block, + >::new_sealed( + sealed_block.as_ref().clone(), info.executed_senders )), execution_output: Arc::new(execution_outcome), hashed_state: Arc::new(hashed_state), @@ -806,9 +811,9 @@ impl OpPayloadTransactions for () { /// 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: EvmConfig, + pub evm_config: OpEvmConfig, /// The DA config for the payload builder pub da_config: OpDAConfig, /// The chainspec @@ -816,11 +821,11 @@ pub struct OpPayloadBuilderCtx>, /// Evm Settings - pub evm_env: EvmEnv, + pub evm_env: EvmEnv, + /// Block env attributes for the current block. + pub block_env_attributes: OpNextBlockEnvAttributes, /// Marker to check whether the job has been cancelled. pub cancel: CancellationToken, - /// Receipt builder. - pub receipt_builder: Arc>, /// The builder signer pub builder_signer: Option, /// The metrics for the builder @@ -831,9 +836,8 @@ pub struct OpPayloadBuilderCtx OpPayloadBuilderCtx +impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvmEnv, ChainSpec: EthChainSpec + OpHardforks, N: NodePrimitives, { @@ -858,24 +862,24 @@ where pub fn block_gas_limit(&self) -> u64 { self.attributes() .gas_limit - .unwrap_or_else(|| self.evm_env.block_env.gas_limit.saturating_to()) + .unwrap_or(self.evm_env.block_env.gas_limit) } /// Returns the block number for the block. pub fn block_number(&self) -> u64 { - self.evm_env.block_env.number.to() + self.evm_env.block_env.number } /// Returns the current base fee pub fn base_fee(&self) -> u64 { - self.evm_env.block_env.basefee.to() + self.evm_env.block_env.basefee } /// Returns the current blob gas price. pub fn get_blob_gasprice(&self) -> Option { self.evm_env .block_env - .get_blob_gasprice() + .blob_gasprice() .map(|gasprice| gasprice as u64) } @@ -959,77 +963,21 @@ where pub fn builder_signer(&self) -> Option { self.builder_signer } - - /// Ensure that the create2deployer is force-deployed at the canyon transition. Optimism - /// blocks will always have at least a single transaction in them (the L1 info transaction), - /// so we can safely assume that this will always be triggered upon the transition and that - /// the above check for empty blocks will never be hit on OP chains. - pub fn ensure_create2_deployer(&self, db: &mut State) -> Result<(), PayloadBuilderError> - where - DB: Database, - DB::Error: Display, - { - reth_optimism_evm::ensure_create2_deployer( - self.chain_spec.clone(), - self.attributes().payload_attributes.timestamp, - db, - ) - .map_err(|err| { - warn!(target: "payload_builder", %err, "missing create2 deployer, skipping block."); - PayloadBuilderError::other(OpPayloadBuilderError::ForceCreate2DeployerFail) - }) - } } -impl OpPayloadBuilderCtx +impl OpPayloadBuilderCtx where - EvmConfig: ConfigureEvmFor, ChainSpec: EthChainSpec + OpHardforks, N: OpPayloadPrimitives<_TX = OpTransactionSigned>, { - /// apply eip-4788 pre block contract call - pub fn apply_pre_beacon_root_contract_call( - &self, - db: &mut DB, - ) -> Result<(), PayloadBuilderError> - where - DB: Database + DatabaseCommit, - DB::Error: Display, - ::Error: StdError, - { - SystemCaller::new(self.evm_config.clone(), self.chain_spec.clone()) - .pre_block_beacon_root_contract_call( - db, - &self.evm_env, - self.attributes() - .payload_attributes - .parent_beacon_block_root, - ) - .map_err(|err| { - warn!(target: "payload_builder", - parent_header=%self.parent().hash(), - %err, - "failed to apply beacon root contract call for payload" - ); - PayloadBuilderError::Internal(err.into()) - })?; - - Ok(()) - } - /// Constructs a receipt for the given transaction. - fn build_receipt( + fn build_receipt( &self, - info: &ExecutionInfo, - result: ExecutionResult, + ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, deposit_nonce: Option, - tx: &N::SignedTx, - ) -> N::Receipt { - match self.receipt_builder.build_receipt(ReceiptBuilderCtx { - tx, - result, - cumulative_gas_used: info.cumulative_gas_used, - }) { + ) -> OpReceipt { + let receipt_builder = self.evm_config.block_executor_factory().receipt_builder(); + match receipt_builder.build_receipt(ctx) { Ok(receipt) => receipt, Err(ctx) => { let receipt = alloy_consensus::Receipt { @@ -1040,17 +988,16 @@ where logs: ctx.result.into_logs(), }; - self.receipt_builder - .build_deposit_receipt(OpDepositReceipt { - inner: receipt, - deposit_nonce, - // The deposit receipt version was introduced in Canyon to indicate an - // update to how receipt hashes should be computed - // when set. The state transition process ensures - // this is only set for post-Canyon deposit - // transactions. - deposit_receipt_version: self.is_canyon_active().then_some(1), - }) + receipt_builder.build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_nonce, + // The deposit receipt version was introduced in Canyon to indicate an + // update to how receipt hashes should be computed + // when set. The state transition process ensures + // this is only set for post-Canyon deposit + // transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + }) } } } @@ -1088,7 +1035,7 @@ where // Check transactions against supervisor if it's cross chain if let (false, _) = is_cross_tx_valid::( - sequencer_tx.tx(), + sequencer_tx.inner(), self.supervisor_client.as_ref(), self.supervisor_safety_level, self.config.attributes.timestamp(), @@ -1116,11 +1063,7 @@ where )) })?; - let tx_env = self - .evm_config - .tx_env(sequencer_tx.tx(), sequencer_tx.signer()); - - let ResultAndState { result, state } = match evm.transact(tx_env) { + let ResultAndState { result, state } = match evm.transact(&sequencer_tx) { Ok(res) => res, Err(err) => { if err.is_invalid_tx_err() { @@ -1132,24 +1075,25 @@ where } }; - // commit changes - evm.db_mut().commit(state); - - let gas_used = result.gas_used(); - // add gas used by the transaction to cumulative gas used, before creating the receipt + let gas_used = result.gas_used(); info.cumulative_gas_used += gas_used; - info.receipts.push(self.build_receipt( - &info, + let ctx = ReceiptBuilderCtx { + tx: sequencer_tx.inner(), + evm: &evm, result, - depositor_nonce, - sequencer_tx.tx(), - )); + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, depositor_nonce)); + + // commit changes + evm.db_mut().commit(state); // append sender and transaction to the respective lists info.executed_senders.push(sequencer_tx.signer()); - info.executed_transactions.push(sequencer_tx.into_tx()); + info.executed_transactions.push(sequencer_tx.into_inner()); } Ok(info) @@ -1163,7 +1107,7 @@ where info: &mut ExecutionInfo, db: &mut State, mut best_txs: impl PayloadTransactions< - Transaction: PoolTransaction, + Transaction: PoolTransaction, >, block_gas_limit: u64, block_da_limit: Option, @@ -1184,7 +1128,7 @@ where let tx = tx.into_consensus(); num_txs_considered += 1; // ensure we still have capacity for this transaction - if info.is_tx_over_limits(tx.tx(), block_gas_limit, tx_da_limit, block_da_limit) { + if info.is_tx_over_limits(tx.inner(), block_gas_limit, tx_da_limit, block_da_limit) { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from // the iterator before we can continue @@ -1200,7 +1144,7 @@ where // Check transactions against supervisor if it's cross chain if let (false, is_recoverable) = is_cross_tx_valid::( - tx.tx(), + tx.inner(), self.supervisor_client.as_ref(), self.supervisor_safety_level, self.config.attributes.timestamp(), @@ -1221,12 +1165,8 @@ where return Ok(Some(())); } - // Configure the environment for the tx. - let tx_env = self.evm_config.tx_env(tx.tx(), tx.signer()); - let tx_simulation_start_time = Instant::now(); - - let ResultAndState { result, state } = match evm.transact(tx_env) { + let ResultAndState { result, state } = match evm.transact(&tx) { Ok(res) => res, Err(err) => { if let Some(err) = err.as_invalid_tx_err() { @@ -1250,7 +1190,7 @@ where self.metrics .tx_simulation_duration .record(tx_simulation_start_time.elapsed()); - self.metrics.tx_byte_size.record(tx.tx().size() as f64); + self.metrics.tx_byte_size.record(tx.inner().size() as f64); num_txs_simulated += 1; if result.is_success() { num_txs_simulated_success += 1; @@ -1262,18 +1202,23 @@ where continue; } - // commit changes - evm.db_mut().commit(state); - - let gas_used = result.gas_used(); - // add gas used by the transaction to cumulative gas used, before creating the // receipt + let gas_used = result.gas_used(); info.cumulative_gas_used += gas_used; // Push transaction changeset and calculate header bloom filter for receipt. - info.receipts - .push(self.build_receipt(info, result, None, &tx)); + let ctx = ReceiptBuilderCtx { + tx: tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, None)); + + // commit changes + evm.db_mut().commit(state); // update add to total fees let miner_fee = tx @@ -1283,7 +1228,7 @@ where // append sender and transaction to the respective lists info.executed_senders.push(tx.signer()); - info.executed_transactions.push(tx.into_tx()); + info.executed_transactions.push(tx.into_inner()); } self.metrics @@ -1324,28 +1269,32 @@ where signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); - let tx_env = self.evm_config.tx_env(builder_tx.tx(), builder_tx.signer()); let ResultAndState { result, state } = evm - .transact(tx_env) + .transact(&builder_tx) .map_err(|err| PayloadBuilderError::EvmExecutionError(Box::new(err)))?; + // Add gas used by the transaction to cumulative gas used, before creating the receipt + let gas_used = result.gas_used(); + info.cumulative_gas_used += gas_used; + + let ctx = ReceiptBuilderCtx { + tx: builder_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, None)); + // Release the db reference by dropping evm drop(evm); // Commit changes db.commit(state); - let gas_used = result.gas_used(); - - // Add gas used by the transaction to cumulative gas used, before creating the receipt - info.cumulative_gas_used += gas_used; - - info.receipts - .push(self.build_receipt(info, result, None, &builder_tx)); - // Append sender and transaction to the respective lists info.executed_senders.push(builder_tx.signer()); - info.executed_transactions.push(builder_tx.into_tx()); + info.executed_transactions.push(builder_tx.into_inner()); Ok(()) }) .transpose() diff --git a/crates/builder/op-rbuilder/src/primitives/mod.rs b/crates/builder/op-rbuilder/src/primitives/mod.rs index 6524a59b..8a1a2f16 100644 --- a/crates/builder/op-rbuilder/src/primitives/mod.rs +++ b/crates/builder/op-rbuilder/src/primitives/mod.rs @@ -1,3 +1,2 @@ pub mod reth; -#[cfg(feature = "optimism")] pub mod supervisor; diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 8acbce0b..292ce90c 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -1,8 +1,8 @@ //! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570) use alloy_consensus::Transaction; -use alloy_primitives::private::alloy_rlp::Encodable; -use alloy_primitives::{Address, TxHash, B256, U256}; +use alloy_primitives::{private::alloy_rlp::Encodable, Address, TxHash, B256, U256}; use reth_node_api::NodePrimitives; +use reth_optimism_primitives::OpReceipt; use std::collections::HashSet; /// Holds the state after execution @@ -21,7 +21,7 @@ pub struct ExecutionInfo { /// The recovered senders for the executed transactions. pub executed_senders: Vec

, /// The transaction receipts - pub receipts: Vec, + pub receipts: Vec, /// All gas used so far pub cumulative_gas_used: u64, /// Estimated DA size diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs index c1d0ab06..6c2b39ef 100644 --- a/crates/builder/op-rbuilder/src/tx_signer.rs +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -32,7 +32,7 @@ impl Signer { let signature = Signature::new( U256::try_from_be_slice(&data[..32]).expect("The slice has at most 32 bytes"), U256::try_from_be_slice(&data[32..64]).expect("The slice has at most 32 bytes"), - rec_id.to_i32() != 0, + i32::from(rec_id) != 0, ); Ok(signature) } @@ -95,7 +95,7 @@ mod test { let signed_tx = signer.sign_tx(tx).expect("sign tx"); assert_eq!(signed_tx.signer(), address); - let signed = signed_tx.into_tx(); + let signed = signed_tx.into_inner(); assert_eq!(signed.recover_signer().ok(), Some(address)); } } From 5e533ebb0d1545461fa9612e807787e9b731b067 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 31 Mar 2025 19:06:02 +0700 Subject: [PATCH 045/262] Remove previous interop impl (#526) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary I will remove previous interop version in favor of newly written reth implementation. After this PR i'll make a follow-up PR with dependency bump and additional logic for tx filtering. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 5 - crates/builder/op-rbuilder/src/args.rs | 11 - crates/builder/op-rbuilder/src/main.rs | 2 - crates/builder/op-rbuilder/src/metrics.rs | 24 --- .../op-rbuilder/src/payload_builder.rs | 9 - .../src/payload_builder_vanilla.rs | 189 +----------------- .../builder/op-rbuilder/src/primitives/mod.rs | 1 - .../op-rbuilder/src/primitives/supervisor.rs | 25 --- 8 files changed, 4 insertions(+), 262 deletions(-) delete mode 100644 crates/builder/op-rbuilder/src/primitives/supervisor.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 5f2d8800..6532d129 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -60,10 +60,6 @@ op-alloy-consensus.workspace = true op-alloy-rpc-types-engine.workspace = true op-alloy-network.workspace = true -# kona -kona-rpc.workspace = true -kona-interop.workspace = true - revm.workspace = true op-revm.workspace = true @@ -93,7 +89,6 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } # `flashblocks` branch rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "6b94e1037f41e0817f2bcb1f1ca5013a5359616a" } -url = "2.5.3" [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index 1d41726a..1e99c7fd 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -6,7 +6,6 @@ use reth_optimism_node::args::RollupArgs; use crate::tx_signer::Signer; -use alloy_transport_http::reqwest::Url; /// Parameters for rollup configuration #[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] @@ -39,16 +38,6 @@ pub struct OpRbuilderArgs { env = "FLASHBLOCK_BLOCK_TIME" )] pub flashblock_block_time: u64, - /// URL of the supervisor service for transaction validation - #[arg(long = "rollup.supervisor-url", env = "SUPERVISOR_URL")] - pub supervisor_url: Option, - /// URL of the supervisor service for transaction validation - #[arg( - long = "rollup.supervisor_safety_level", - env = "SUPERVISOR_SAFETY_LEVEL", - help = "Safety level to pass to supervisor, values: finalized, safe, local-safe, cross-unsafe, unsafe, invalid" - )] - pub supervisor_safety_level: Option, /// Signals whether to log pool transaction events #[arg(long = "builder.log-pool-transactions", default_value = "false")] pub log_pool_transactions: bool, diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index a248f0e4..58c0e9c8 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -42,8 +42,6 @@ fn main() { builder_args.flashblocks_ws_url, builder_args.chain_block_time, builder_args.flashblock_block_time, - builder_args.supervisor_url, - builder_args.supervisor_safety_level, ))) .with_add_ons( OpAddOnsBuilder::default() diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 5e8e426f..4451d0b5 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -44,14 +44,6 @@ pub struct OpRBuilderMetrics { pub tx_byte_size: Histogram, /// Number of reverted transactions pub num_reverted_tx: Counter, - /// Number of cross-chain transactions - pub num_cross_chain_tx: Counter, - /// Number of cross-chain transactions that didn't pass supervisor validation - pub num_cross_chain_tx_fail: Counter, - /// Number of cross-chain transactions that weren't verified because of the timeout - pub num_cross_chain_tx_timeout: Counter, - /// Number of cross-chain transactions that weren't verified because of the server error - pub num_cross_chain_tx_server_error: Counter, } impl OpRBuilderMetrics { @@ -78,20 +70,4 @@ impl OpRBuilderMetrics { pub fn set_builder_balance(&self, balance: f64) { self.builder_balance.set(balance); } - - pub fn inc_num_cross_chain_tx_fail(&self) { - self.num_cross_chain_tx_fail.increment(1); - } - - pub fn inc_num_cross_chain_tx(&self) { - self.num_cross_chain_tx.increment(1); - } - - pub fn inc_num_cross_chain_tx_timeout(&self) { - self.num_cross_chain_tx_timeout.increment(1); - } - - pub fn inc_num_cross_chain_tx_server_error(&self) { - self.num_cross_chain_tx_server_error.increment(1); - } } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 11521d49..6a0c67a5 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -68,7 +68,6 @@ use tokio::{ use tokio_tungstenite::{accept_async, WebSocketStream}; use tokio_util::sync::CancellationToken; use tracing::{debug, trace, warn}; -use url::Url; #[derive(Debug, Serialize, Deserialize)] struct FlashblocksMetadata { @@ -85,10 +84,6 @@ pub struct CustomOpPayloadBuilder { flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, - #[expect(dead_code)] - supervisor_url: Option, - #[expect(dead_code)] - supervisor_safety_level: Option, } impl CustomOpPayloadBuilder { @@ -97,16 +92,12 @@ impl CustomOpPayloadBuilder { flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, - supervisor_url: Option, - supervisor_safety_level: Option, ) -> Self { Self { builder_signer, flashblocks_ws_url, chain_block_time, flashblock_block_time, - supervisor_url, - supervisor_safety_level, } } } diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index c95bc059..f10a8950 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1,10 +1,7 @@ use crate::{ generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, metrics::OpRBuilderMetrics, - primitives::{ - reth::{ExecutedPayload, ExecutionInfo}, - supervisor::SupervisorValidator, - }, + primitives::reth::{ExecutedPayload, ExecutionInfo}, tx_signer::Signer, }; use alloy_consensus::{ @@ -13,12 +10,9 @@ use alloy_consensus::{ }; use alloy_eips::merge::BEACON_NONCE; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxHash, TxKind, U256}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawals; -use jsonrpsee::http_client::HttpClientBuilder; -use kona_interop::{ExecutingDescriptor, SafetyLevel}; -use kona_rpc::{InteropTxValidator, InteropTxValidatorError}; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; use op_revm::OpSpecId; use reth::{ @@ -76,7 +70,6 @@ use revm::{ use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::*; -use url::Url; #[derive(Debug, Clone, Default)] #[non_exhaustive] @@ -88,8 +81,6 @@ pub struct CustomOpPayloadBuilder { chain_block_time: u64, #[cfg(feature = "flashblocks")] flashblock_block_time: u64, - supervisor_url: Option, - supervisor_safety_level: Option, } impl CustomOpPayloadBuilder { @@ -99,16 +90,12 @@ impl CustomOpPayloadBuilder { flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, - supervisor_url: Option, - supervisor_safety_level: Option, ) -> Self { Self { builder_signer, flashblocks_ws_url, chain_block_time, flashblock_block_time, - supervisor_url, - supervisor_safety_level, } } @@ -118,14 +105,8 @@ impl CustomOpPayloadBuilder { _flashblocks_ws_url: String, _chain_block_time: u64, _flashblock_block_time: u64, - supervisor_url: Option, - supervisor_safety_level: Option, ) -> Self { - Self { - builder_signer, - supervisor_url, - supervisor_safety_level, - } + Self { builder_signer } } } @@ -155,8 +136,6 @@ where self.builder_signer, pool, ctx.provider().clone(), - self.supervisor_url.clone(), - self.supervisor_safety_level.clone(), )) } } @@ -250,10 +229,6 @@ pub struct OpPayloadBuilderVanilla { pub best_transactions: Txs, /// The metrics for the builder pub metrics: OpRBuilderMetrics, - /// Client to execute supervisor validation - pub supervisor_client: Option, - /// Level to use in supervisor validation - pub supervisor_safety_level: SafetyLevel, } impl OpPayloadBuilderVanilla { @@ -263,45 +238,17 @@ impl OpPayloadBuilderVanilla { builder_signer: Option, pool: Pool, client: Client, - supervisor_url: Option, - supervisor_safety_level: Option, ) -> Self { - Self::with_builder_config( - evm_config, - builder_signer, - pool, - client, - supervisor_url, - supervisor_safety_level, - Default::default(), - ) + Self::with_builder_config(evm_config, builder_signer, pool, client, Default::default()) } - // TODO: we will move supervisor_url and supervisor_safety_level into OpBuilderConfig to reduce - // number of args - #[allow(clippy::too_many_arguments)] pub fn with_builder_config( evm_config: OpEvmConfig, builder_signer: Option, pool: Pool, client: Client, - supervisor_url: Option, - supervisor_safety_level: Option, config: OpBuilderConfig, ) -> Self { - // TODO: we should make this client required if interop hardfork enabled, add this after spec rebase - let supervisor_client = supervisor_url.map(|url| { - SupervisorValidator::new( - HttpClientBuilder::default() - .build(url) - .expect("building supervisor http client"), - ) - }); - let supervisor_safety_level = supervisor_safety_level - .map(|level| { - serde_json::from_str(level.as_str()).expect("parsing supervisor_safety_level") - }) - .unwrap_or(SafetyLevel::CrossUnsafe); Self { pool, client, @@ -310,8 +257,6 @@ impl OpPayloadBuilderVanilla { best_transactions: (), metrics: Default::default(), builder_signer, - supervisor_client, - supervisor_safety_level, } } } @@ -439,8 +384,6 @@ where cancel, builder_signer: self.builder_signer, metrics: Default::default(), - supervisor_client: self.supervisor_client.clone(), - supervisor_safety_level: self.supervisor_safety_level, }; let builder = OpBuilder::new(best, remove_reverted); @@ -830,10 +773,6 @@ pub struct OpPayloadBuilderCtx { pub builder_signer: Option, /// The metrics for the builder pub metrics: OpRBuilderMetrics, - /// Client to execute supervisor validation - pub supervisor_client: Option, - /// Level to use in supervisor validation - pub supervisor_safety_level: SafetyLevel, } impl OpPayloadBuilderCtx @@ -1033,18 +972,6 @@ where PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) })?; - // Check transactions against supervisor if it's cross chain - if let (false, _) = is_cross_tx_valid::( - sequencer_tx.inner(), - self.supervisor_client.as_ref(), - self.supervisor_safety_level, - self.config.attributes.timestamp(), - &self.metrics, - ) { - // We skip this transaction because it's not possible to verify it's validity - continue; - } - // Cache the depositor account prior to the state transition for the deposit nonce. // // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces @@ -1142,24 +1069,6 @@ where continue; } - // Check transactions against supervisor if it's cross chain - if let (false, is_recoverable) = is_cross_tx_valid::( - tx.inner(), - self.supervisor_client.as_ref(), - self.supervisor_safety_level, - self.config.attributes.timestamp(), - &self.metrics, - ) { - // We mark the tx invalid to ensure that it won't clog out pipeline - // in case there is bug in supervisor. - best_txs.mark_invalid(tx.signer(), tx.nonce()); - if !is_recoverable { - // For some subset of errors we remove transaction from txpool - info.invalid_tx_hashes.insert(*tx.tx_hash()); - } - continue; - } - // check if the job was cancelled, if so we can exit early if self.cancel.is_cancelled() { return Ok(Some(())); @@ -1385,93 +1294,3 @@ fn estimate_gas_for_builder_tx(input: Vec) -> u64 { zero_cost + nonzero_cost + 21_000 } - -/// Extracts commitment from access list entries, pointing to 0x420..022 and validates them -/// against supervisor. -/// -/// If commitment present pre-interop tx rejected. -/// -/// Returns (is_valid, is_recoverable) -pub fn is_cross_tx_valid( - tx: &N::SignedTx, - // TODO: after spec rebase we must make this field not optional - client: Option<&SupervisorValidator>, - safety_level: SafetyLevel, - timestamp: u64, - metrics: &OpRBuilderMetrics, -) -> (bool, bool) { - if tx.access_list().is_none() { - return (true, true); - } - let access_list = tx.access_list().unwrap(); - let inbox_entries = SupervisorValidator::parse_access_list(access_list) - .cloned() - .collect::>(); - if !inbox_entries.is_empty() { - metrics.inc_num_cross_chain_tx(); - if client.is_none() { - return (false, true); - } - match validate_supervisor_messages(inbox_entries, client.unwrap(), safety_level, timestamp) - { - Ok(res) => match res { - Ok(()) => (true, true), - Err(err) => { - match err { - // TODO: we should add reconnecting to supervisor in case of disconnect - InteropTxValidatorError::SupervisorServerError(err) => { - warn!(target: "payload_builder", %err, ?tx, "Supervisor error, skipping."); - metrics.inc_num_cross_chain_tx_server_error(); - (false, true) - } - InteropTxValidatorError::ValidationTimeout(_) => { - warn!(target: "payload_builder", %err, ?tx, "Cross tx validation timed out, skipping."); - metrics.inc_num_cross_chain_tx_timeout(); - (false, true) - } - err => { - trace!(target: "payload_builder", %err, ?tx, "Cross tx rejected."); - metrics.inc_num_cross_chain_tx_fail(); - // It's possible that transaction invalid now, but would be valid later. - // We should keep limited queue for transactions that could become valid. - // We should have the limit to ensure that builder won't get overwhelmed. - (false, false) - } - } - } - }, - Err(err) => { - error!(target: "payload_builder", %err, ?tx, "Client side error during cross tx validation, skipping."); - (false, true) - } - } - } else { - (true, true) - } -} - -/// Validate inbox_entries against supervisor. -pub fn validate_supervisor_messages( - inbox_entries: Vec, - client: &SupervisorValidator, - safety_level: SafetyLevel, - timestamp: u64, -) -> Result, PayloadBuilderError> { - // For block building the timestamp should be `expected time of inclusion` and timeout 0 - let descriptor = ExecutingDescriptor::new(timestamp, None); - let (channel_tx, rx) = std::sync::mpsc::channel(); - tokio::task::block_in_place(move || { - let res = tokio::runtime::Handle::current().block_on(async { - client - .validate_messages( - inbox_entries.as_slice(), - safety_level, - descriptor, - Some(core::time::Duration::from_millis(100)), - ) - .await - }); - let _ = channel_tx.send(res); - }); - rx.recv().map_err(|_| PayloadBuilderError::ChannelClosed) -} diff --git a/crates/builder/op-rbuilder/src/primitives/mod.rs b/crates/builder/op-rbuilder/src/primitives/mod.rs index 8a1a2f16..02615de6 100644 --- a/crates/builder/op-rbuilder/src/primitives/mod.rs +++ b/crates/builder/op-rbuilder/src/primitives/mod.rs @@ -1,2 +1 @@ pub mod reth; -pub mod supervisor; diff --git a/crates/builder/op-rbuilder/src/primitives/supervisor.rs b/crates/builder/op-rbuilder/src/primitives/supervisor.rs deleted file mode 100644 index 05cbdb4d..00000000 --- a/crates/builder/op-rbuilder/src/primitives/supervisor.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! This is our custom implementation of validator struct - -use jsonrpsee::http_client::HttpClient; -use kona_rpc::InteropTxValidator; -use std::time::Duration; - -#[derive(Debug, Clone)] -pub struct SupervisorValidator { - inner: HttpClient, -} - -impl SupervisorValidator { - pub fn new(client: HttpClient) -> Self { - Self { inner: client } - } -} - -impl InteropTxValidator for SupervisorValidator { - type SupervisorClient = HttpClient; - const DEFAULT_TIMEOUT: Duration = Duration::from_millis(100); - - fn supervisor_client(&self) -> &Self::SupervisorClient { - &self.inner - } -} From 9edb8da52aeaa451cf615884e51861ca7a357494 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 2 Apr 2025 10:19:56 +0200 Subject: [PATCH 046/262] fix: don't miss blocks on batcher updates (#529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary When batcher's max channel duration is big enough (e.g. 10m), the batcher would be pushing its updates at specified intervals. This causes the sequencer to send an avalanche of FCUs (and getBlockByNumber) that push safe head step-by-step. As a consequence it can happen that the time b/w FCU and ensuing getPayload would be on the scale of ~2.5s. This means that we should "remember" the payloads long enough to accommodate that corner-case. --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/generator.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index b55def99..7dc9670b 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -155,7 +155,16 @@ where // 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. - let deadline = job_deadline(attributes.timestamp()) + Duration::from_millis(500); + // + // 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. + let deadline = job_deadline(attributes.timestamp()) + Duration::from_millis(5000); let deadline = Box::pin(tokio::time::sleep(deadline)); let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); From d7c0c233c8634231d63e317247514cc462f52969 Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 4 Apr 2025 04:11:54 +1100 Subject: [PATCH 047/262] Fix integration tests (#536) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Fix deposit and flashblocks integration tests. Resolves https://github.com/flashbots/rbuilder/issues/533 and https://github.com/flashbots/rbuilder/issues/521 ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 1 + .../op-rbuilder/src/payload_builder.rs | 210 ++++++++++++------ crates/builder/op-rbuilder/src/tester/mod.rs | 4 +- 3 files changed, 143 insertions(+), 72 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 6532d129..8ac6062b 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -79,6 +79,7 @@ derive_more.workspace = true metrics.workspace = true serde_json.workspace = true tokio-util.workspace = true +thiserror.workspace = true time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 6a0c67a5..de3cd60f 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -3,7 +3,10 @@ use crate::{ primitives::reth::ExecutionInfo, tx_signer::Signer, }; -use alloy_consensus::{Eip658Value, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH}; +use alloy_consensus::{ + constants::EMPTY_WITHDRAWALS, Eip658Value, Header, Transaction, Typed2718, + EMPTY_OMMER_ROOT_HASH, +}; use alloy_eips::{merge::BEACON_NONCE, Encodable2718}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256}; @@ -29,7 +32,7 @@ use reth_evm::{ use reth_execution_types::ExecutionOutcome; use reth_node_api::{NodePrimitives, NodeTypesWithEngine, TxTy}; use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; +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::OpEngineTypes; @@ -60,14 +63,25 @@ use rollup_boost::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, }; use serde::{Deserialize, Serialize}; -use std::sync::{Arc, Mutex}; +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; use tokio::{ net::{TcpListener, TcpStream}, sync::mpsc, }; use tokio_tungstenite::{accept_async, WebSocketStream}; use tokio_util::sync::CancellationToken; -use tracing::{debug, trace, warn}; +use tracing::{debug, error, trace, warn}; + +/// Flashblocks specific payload building errors. +#[derive(Debug, thiserror::Error)] +pub enum FlashblockPayloadBuilderError { + /// Thrown when the job was cancelled. + #[error("error sending build signal")] + SendBuildSignalError, +} #[derive(Debug, Serialize, Deserialize)] struct FlashblocksMetadata { @@ -395,65 +409,115 @@ where let mut flashblock_count = 0; - // 2. loop every n time and try to build an increasing block - loop { - if ctx.cancel.is_cancelled() { - tracing::info!( - target: "payload_builder", - "Job cancelled, stopping payload building", - ); - // if the job was cancelled, stop - return Ok(()); - } - - println!( - "Building flashblock {} {}", - ctx.payload_id(), - flashblock_count, - ); + // Create a channel to coordinate flashblock building + let (build_tx, mut build_rx) = mpsc::channel(1); - tracing::info!( - target: "payload_builder", - "Building flashblock {}", - flashblock_count, - ); - - let state = StateProviderDatabase::new(&state_provider); - - let mut db = State::builder() - .with_database(state) - .with_bundle_update() - .with_bundle_prestate(bundle_state) - .build(); + // Spawn the timer task that signals when to build a new flashblock + let cancel_clone = ctx.cancel.clone(); + let flashblock_block_time = self.flashblock_block_time; + tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_millis(flashblock_block_time)); + loop { + tokio::select! { + // Add a cancellation check that only runs every 10ms to avoid tight polling + _ = tokio::time::sleep(Duration::from_millis(10)) => { + if cancel_clone.is_cancelled() { + tracing::info!(target: "payload_builder", "Job cancelled during sleep, stopping payload building"); + drop(build_tx); + break; + } + } + _ = interval.tick() => { + if let Err(err) = build_tx.send(()).await { + error!(target: "payload_builder", "Error sending build signal: {}", err); + break; + } + } + } + } + }); - let best_txs = BestPayloadTransactions::new( - self.pool - .best_transactions_with_attributes(ctx.best_transaction_attributes()), - ); - ctx.execute_best_transactions(&mut info, &mut db, best_txs, total_gas_per_batch)?; + // Process flashblocks in a blocking loop + loop { + // Block on receiving a message, break on cancellation or closed channel + let received = tokio::task::block_in_place(|| { + // Get runtime handle + let rt = tokio::runtime::Handle::current(); + + // Run the async operation to completion, blocking the current thread + rt.block_on(async { + // Check for cancellation first + if ctx.cancel.is_cancelled() { + tracing::info!( + target: "payload_builder", + "Job cancelled, stopping payload building", + ); + return None; + } - if ctx.cancel.is_cancelled() { - tracing::info!( - target: "payload_builder", - "Job cancelled, stopping payload building", - ); - // if the job was cancelled, stop - return Ok(()); - } + // Wait for next message + build_rx.recv().await + }) + }); - let (payload, mut fb_payload, new_bundle_state) = build_block(db, &ctx, &mut info)?; + // Exit loop if channel closed or cancelled + match received { + Some(()) => { + // Continue with flashblock building + tracing::info!( + target: "payload_builder", + "Building flashblock {}", + flashblock_count, + ); + + let state = StateProviderDatabase::new(&state_provider); + + let mut db = State::builder() + .with_database(state) + .with_bundle_update() + .with_bundle_prestate(bundle_state) + .build(); + + let best_txs = BestPayloadTransactions::new( + self.pool + .best_transactions_with_attributes(ctx.best_transaction_attributes()), + ); + ctx.execute_best_transactions( + &mut info, + &mut db, + best_txs, + total_gas_per_batch, + )?; + + if ctx.cancel.is_cancelled() { + tracing::info!( + target: "payload_builder", + "Job cancelled, stopping payload building", + ); + // if the job was cancelled, stop + return Ok(()); + } - best_payload.set(payload.clone()); + let (new_payload, mut fb_payload, new_bundle_state) = + build_block(db, &ctx, &mut info)?; - fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 - fb_payload.base = None; - let _ = self.send_message(serde_json::to_string(&fb_payload).unwrap_or_default()); + fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 + fb_payload.base = None; + let _ = + self.send_message(serde_json::to_string(&fb_payload).unwrap_or_default()); - bundle_state = new_bundle_state; - total_gas_per_batch += gas_per_batch; - flashblock_count += 1; + best_payload.set(new_payload.clone()); + bundle_state = new_bundle_state; + total_gas_per_batch += gas_per_batch; + flashblock_count += 1; - std::thread::sleep(std::time::Duration::from_millis(self.flashblock_block_time)); + tracing::info!(target: "payload_builder", "Flashblock {} built", flashblock_count); + } + None => { + // Exit loop if channel closed or cancelled + return Ok(()); + } + } } } } @@ -501,6 +565,7 @@ where block_number, vec![], ); + let receipts_root = execution_outcome .generic_receipts_root_slow(block_number, |receipts| { calculate_receipt_root_no_memo_optimism( @@ -531,6 +596,25 @@ where })? }; + let withdrawals_root = if ctx + .chain_spec + .is_isthmus_active_at_timestamp(ctx.attributes().timestamp()) + { + // 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.database.as_ref()) + .map_err(PayloadBuilderError::other)?, + ) + } else if ctx + .chain_spec + .is_canyon_active_at_timestamp(ctx.attributes().timestamp()) + { + Some(EMPTY_WITHDRAWALS) + } else { + None + }; + // create the block header let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); @@ -547,7 +631,7 @@ where state_root, transactions_root, receipts_root, - withdrawals_root: None, + withdrawals_root, logs_bloom, timestamp: ctx.attributes().payload_attributes.timestamp, mix_hash: ctx.attributes().payload_attributes.prev_randao, @@ -961,22 +1045,12 @@ where where DB: Database, { - println!("Executing best transactions"); let base_fee = self.base_fee(); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); while let Some(tx) = best_txs.next(()) { let tx = tx.into_consensus(); - println!("tx: {:?}", tx); - println!( - "gas limit: {:?}, batch gas limit: {:?} cummulative gas used: {:?}", - tx.gas_limit(), - batch_gas_limit, - info.cumulative_gas_used - ); - // gas limit: 100816112, batch gas limit: 2500000000 cummulative gas used: 100062216 - // check in info if the txn has been executed already if info.executed_transactions.contains(&tx) { continue; @@ -984,7 +1058,6 @@ where // ensure we still have capacity for this transaction if info.is_tx_over_limits(tx.inner(), batch_gas_limit, None, None) { - println!("A"); // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from // the iterator before we can continue @@ -1001,11 +1074,9 @@ where // check if the job was cancelled, if so we can exit early if self.cancel.is_cancelled() { - println!("C"); return Ok(Some(())); } - println!("Start transaction"); let ResultAndState { result, state } = match evm.transact(&tx) { Ok(res) => res, Err(err) => { @@ -1026,7 +1097,6 @@ where return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); } }; - println!("Finish transaction"); // add gas used by the transaction to cumulative gas used, before creating the // receipt diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 855e2480..3ca8b152 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -350,7 +350,7 @@ impl<'a> BlockGenerator<'a> { mint: None, value: U256::default(), gas_limit: 210000, - is_system_transaction: true, + is_system_transaction: false, input: FJORD_DATA.into(), }; @@ -471,7 +471,7 @@ impl<'a> BlockGenerator<'a> { mint: Some(value), // Amount to deposit value: U256::default(), gas_limit: 210000, - is_system_transaction: true, + is_system_transaction: false, input: Bytes::default(), }; From 6ee84223ab6bf8662f6506e4a3f9c7b10eec6e81 Mon Sep 17 00:00:00 2001 From: Joe Parks Date: Thu, 10 Apr 2025 10:24:02 -0700 Subject: [PATCH 048/262] Add metrics for flashblock and message tracking in OpRBuilder (#543) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhanced the OpRBuilderMetrics struct to include new metrics for tracking flashblocks, messages sent, and invalid blocks. Updated the OpPayloadBuilder to utilize these metrics during block building and transaction simulation processes. - Added `flashblock_count`, `messages_sent_count`, and `invalid_blocks_count` to OpRBuilderMetrics. - Integrated metrics tracking in OpPayloadBuilder for message sending, block building success, and transaction simulation. - Recorded durations for various operations to improve performance monitoring. ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/metrics.rs | 12 ++ .../op-rbuilder/src/payload_builder.rs | 148 +++++++++++++++--- 2 files changed, 140 insertions(+), 20 deletions(-) diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 4451d0b5..83659aa9 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -14,8 +14,20 @@ pub struct OpRBuilderMetrics { pub builder_landed_blocks_missed: Gauge, /// Block built success pub block_built_success: Counter, + /// Number of flashblocks added to block (Total per block) + #[cfg(feature = "flashblocks")] + pub flashblock_count: Histogram, + /// Number of messages sent + #[cfg(feature = "flashblocks")] + pub messages_sent_count: Counter, /// Total duration of building a block pub total_block_built_duration: Histogram, + /// Flashblock build duration + #[cfg(feature = "flashblocks")] + pub flashblock_build_duration: Histogram, + /// Number of invalid blocks + #[cfg(feature = "flashblocks")] + pub invalid_blocks_count: Counter, /// Duration of fetching transactions from the pool pub transaction_pool_fetch_duration: Histogram, /// Duration of state root calculation diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index de3cd60f..8cee72f6 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -1,5 +1,6 @@ use crate::{ generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, + metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, tx_signer::Signer, }; @@ -47,7 +48,7 @@ use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; use reth_primitives::{BlockBody, SealedHeader}; -use reth_primitives_traits::{proofs, Block as _, SignedTransaction}; +use reth_primitives_traits::{proofs, Block as _, InMemorySize, SignedTransaction}; use reth_provider::{ CanonStateSubscriptions, HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, StorageRootProvider, @@ -65,7 +66,7 @@ use rollup_boost::{ use serde::{Deserialize, Serialize}; use std::{ sync::{Arc, Mutex}, - time::Duration, + time::{Duration, Instant}, }; use tokio::{ net::{TcpListener, TcpStream}, @@ -231,6 +232,8 @@ pub struct OpPayloadBuilder { pub chain_block_time: u64, /// Flashblock block time pub flashblock_block_time: u64, + /// The metrics for the builder + pub metrics: OpRBuilderMetrics, } impl OpPayloadBuilder { @@ -259,6 +262,7 @@ impl OpPayloadBuilder { tx, chain_block_time, flashblock_block_time, + metrics: Default::default(), } } @@ -320,7 +324,9 @@ where { /// Send a message to be published pub fn send_message(&self, message: String) -> Result<(), Box> { - self.tx.send(message).map_err(|e| e.into()) + self.tx.send(message)?; + self.metrics.messages_sent_count.increment(1); + Ok(()) } /// Constructs an Optimism payload from the transactions sent via the @@ -336,6 +342,7 @@ where args: BuildArguments, OpBuiltPayload>, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { + let block_build_start_time = Instant::now(); let BuildArguments { config, cancel, .. } = args; let chain_spec = self.client.chain_spec(); @@ -374,24 +381,33 @@ where evm_env, block_env_attributes, cancel, + metrics: Default::default(), }; let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; let state = StateProviderDatabase::new(&state_provider); + // 1. execute the pre steps and seal an early block with that + let sequencer_tx_start_time = Instant::now(); let mut db = State::builder() .with_database(state) .with_bundle_update() .build(); - // 1. execute the pre steps and seal an early block with that let mut info = execute_pre_steps(&mut db, &ctx)?; + ctx.metrics + .sequencer_tx_duration + .record(sequencer_tx_start_time.elapsed()); + let (payload, fb_payload, mut bundle_state) = build_block(db, &ctx, &mut info)?; best_payload.set(payload.clone()); let _ = self.send_message(serde_json::to_string(&fb_payload).unwrap_or_default()); tracing::info!(target: "payload_builder", "Fallback block built"); + ctx.metrics + .payload_num_tx + .record(info.executed_transactions.len() as f64); if ctx.attributes().no_tx_pool { tracing::info!( @@ -399,6 +415,10 @@ where "No transaction pool, skipping transaction pool processing", ); + self.metrics + .total_block_built_duration + .record(block_build_start_time.elapsed()); + // return early since we don't need to build a block with transactions from the pool return Ok(()); } @@ -408,7 +428,6 @@ where let mut total_gas_per_batch = gas_per_batch; let mut flashblock_count = 0; - // Create a channel to coordinate flashblock building let (build_tx, mut build_rx) = mpsc::channel(1); @@ -470,6 +489,7 @@ where flashblock_count, ); + let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); let mut db = State::builder() @@ -478,16 +498,25 @@ where .with_bundle_prestate(bundle_state) .build(); + let best_txs_start_time = Instant::now(); let best_txs = BestPayloadTransactions::new( self.pool .best_transactions_with_attributes(ctx.best_transaction_attributes()), ); + ctx.metrics + .transaction_pool_fetch_duration + .record(best_txs_start_time.elapsed()); + + let tx_execution_start_time = Instant::now(); ctx.execute_best_transactions( &mut info, &mut db, best_txs, total_gas_per_batch, )?; + ctx.metrics + .payload_tx_simulation_duration + .record(tx_execution_start_time.elapsed()); if ctx.cancel.is_cancelled() { tracing::info!( @@ -498,23 +527,57 @@ where return Ok(()); } - let (new_payload, mut fb_payload, new_bundle_state) = - build_block(db, &ctx, &mut info)?; - - fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 - fb_payload.base = None; - let _ = - self.send_message(serde_json::to_string(&fb_payload).unwrap_or_default()); - - best_payload.set(new_payload.clone()); - bundle_state = new_bundle_state; - total_gas_per_batch += gas_per_batch; - flashblock_count += 1; + let total_block_built_duration = Instant::now(); + let build_result = build_block(db, &ctx, &mut info); + ctx.metrics + .total_block_built_duration + .record(total_block_built_duration.elapsed()); + + // Handle build errors with match pattern + match build_result { + Err(err) => { + // Track invalid/bad block + self.metrics.invalid_blocks_count.increment(1); + error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), flashblock_count, err); + // Return the error + return Err(err); + } + Ok((new_payload, mut fb_payload, new_bundle_state)) => { + fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 + fb_payload.base = None; + + if let Err(err) = self.send_message( + serde_json::to_string(&fb_payload).unwrap_or_default(), + ) { + error!(target: "payload_builder", "Failed to send flashblock message: {}", err); + } - tracing::info!(target: "payload_builder", "Flashblock {} built", flashblock_count); + // Record flashblock build duration + self.metrics + .flashblock_build_duration + .record(flashblock_build_start_time.elapsed()); + ctx.metrics + .payload_byte_size + .record(new_payload.block().size() as f64); + ctx.metrics + .payload_num_tx + .record(info.executed_transactions.len() as f64); + + best_payload.set(new_payload.clone()); + // Update bundle_state for next iteration + bundle_state = new_bundle_state; + total_gas_per_batch += gas_per_batch; + flashblock_count += 1; + tracing::info!(target: "payload_builder", "Flashblock {} built", flashblock_count); + } + } } None => { // Exit loop if channel closed or cancelled + self.metrics.block_built_success.increment(1); + self.metrics + .flashblock_count + .record(flashblock_count as f64); return Ok(()); } } @@ -552,7 +615,11 @@ where // TODO: We must run this only once per block, but we are running it on every flashblock // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call + let state_merge_start_time = Instant::now(); state.merge_transitions(BundleRetention::Reverts); + ctx.metrics + .state_transition_merge_duration + .record(state_merge_start_time.elapsed()); let new_bundle = state.take_bundle(); @@ -580,6 +647,7 @@ where .expect("Number is in range"); // // calculate the state root + let state_root_start_time = Instant::now(); let state_provider = state.database.as_ref(); let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); let (state_root, _trie_output) = { @@ -595,6 +663,9 @@ where ); })? }; + ctx.metrics + .state_root_calculation_duration + .record(state_root_start_time.elapsed()); let withdrawals_root = if ctx .chain_spec @@ -794,6 +865,8 @@ pub struct OpPayloadBuilderCtx { pub block_env_attributes: OpNextBlockEnvAttributes, /// Marker to check whether the job has been cancelled. pub cancel: CancellationToken, + /// The metrics for the builder + pub metrics: OpRBuilderMetrics, } impl OpPayloadBuilderCtx @@ -1046,11 +1119,17 @@ where DB: Database, { let base_fee = self.base_fee(); + let mut num_txs_considered = 0; + let mut num_txs_simulated = 0; + let mut num_txs_simulated_success = 0; + let mut num_txs_simulated_fail = 0; let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); while let Some(tx) = best_txs.next(()) { let tx = tx.into_consensus(); + num_txs_considered += 1; + // check in info if the txn has been executed already if info.executed_transactions.contains(&tx) { continue; @@ -1077,6 +1156,7 @@ where return Ok(Some(())); } + let tx_simulation_start_time = Instant::now(); let ResultAndState { result, state } = match evm.transact(&tx) { Ok(res) => res, Err(err) => { @@ -1098,8 +1178,23 @@ where } }; - // add gas used by the transaction to cumulative gas used, before creating the - // receipt + self.metrics + .tx_simulation_duration + .record(tx_simulation_start_time.elapsed()); + self.metrics.tx_byte_size.record(tx.inner().size() as f64); + num_txs_simulated += 1; + + if result.is_success() { + num_txs_simulated_success += 1; + } else { + num_txs_simulated_fail += 1; + trace!(target: "payload_builder", ?tx, "skipping reverted transaction"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + info.invalid_tx_hashes.insert(*tx.tx_hash()); + continue; + } + + // add gas used by the transaction to cumulative gas used, before creating the receipt let gas_used = result.gas_used(); info.cumulative_gas_used += gas_used; @@ -1126,6 +1221,19 @@ where info.executed_transactions.push(tx.into_inner()); } + self.metrics + .payload_num_tx_considered + .record(num_txs_considered as f64); + self.metrics + .payload_num_tx_simulated + .record(num_txs_simulated as f64); + self.metrics + .payload_num_tx_simulated_success + .record(num_txs_simulated_success as f64); + self.metrics + .payload_num_tx_simulated_fail + .record(num_txs_simulated_fail as f64); + Ok(None) } } From 5230230e72f3268c197ce17ee3b5e38658323ce9 Mon Sep 17 00:00:00 2001 From: File Large Date: Tue, 15 Apr 2025 21:50:27 +0200 Subject: [PATCH 049/262] deps: reth v1.3.8 (#553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Bumps reth dependencies to `1.3.8`. ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 2 +- .../op-rbuilder/src/payload_builder.rs | 14 +++---- .../src/payload_builder_vanilla.rs | 12 +++--- crates/builder/op-rbuilder/src/tester/mod.rs | 40 ++++++++----------- 4 files changed, 31 insertions(+), 37 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 8ac6062b..2406c5f0 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -89,7 +89,7 @@ rand = "0.8.5" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } # `flashblocks` branch -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "6b94e1037f41e0817f2bcb1f1ca5013a5359616a" } +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "e20dbc5c3d7b5223fc2a3a6cfe4c99ed9692caf7" } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 8cee72f6..d5876c43 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -31,7 +31,7 @@ use reth_evm::{ Database, Evm, EvmError, InvalidTxError, }; use reth_execution_types::ExecutionOutcome; -use reth_node_api::{NodePrimitives, NodeTypesWithEngine, TxTy}; +use reth_node_api::{NodePrimitives, NodeTypes, TxTy}; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; @@ -60,7 +60,7 @@ use revm::{ database::{states::bundle_state::BundleRetention, BundleState, State}, DatabaseCommit, }; -use rollup_boost::{ +use rollup_boost::primitives::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, }; use serde::{Deserialize, Serialize}; @@ -120,8 +120,8 @@ impl CustomOpPayloadBuilder { impl PayloadBuilderBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< - Types: NodeTypesWithEngine< - Engine = OpEngineTypes, + Types: NodeTypes< + Payload = OpEngineTypes, ChainSpec = OpChainSpec, Primitives = OpPrimitives, >, @@ -151,8 +151,8 @@ where impl PayloadServiceBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< - Types: NodeTypesWithEngine< - Engine = OpEngineTypes, + Types: NodeTypes< + Payload = OpEngineTypes, ChainSpec = OpChainSpec, Primitives = OpPrimitives, >, @@ -166,7 +166,7 @@ where self, ctx: &BuilderContext, pool: Pool, - ) -> eyre::Result::Engine>> { + ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index f10a8950..4137a728 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -34,7 +34,7 @@ use reth_evm::{ Database, Evm, EvmError, InvalidTxError, }; use reth_execution_types::ExecutionOutcome; -use reth_node_api::{NodePrimitives, NodeTypesWithEngine, TxTy}; +use reth_node_api::{NodePrimitives, NodeTypes, TxTy}; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; @@ -113,8 +113,8 @@ impl CustomOpPayloadBuilder { impl PayloadBuilderBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< - Types: NodeTypesWithEngine< - Engine = OpEngineTypes, + Types: NodeTypes< + Payload = OpEngineTypes, ChainSpec = OpChainSpec, Primitives = OpPrimitives, >, @@ -143,8 +143,8 @@ where impl PayloadServiceBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< - Types: NodeTypesWithEngine< - Engine = OpEngineTypes, + Types: NodeTypes< + Payload = OpEngineTypes, ChainSpec = OpChainSpec, Primitives = OpPrimitives, >, @@ -158,7 +158,7 @@ where self, ctx: &BuilderContext, pool: Pool, - ) -> eyre::Result::Engine>> { + ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 3ca8b152..0b879700 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -1,35 +1,29 @@ use crate::tx_signer::Signer; -use alloy_eips::eip2718::Encodable2718; -use alloy_eips::BlockNumberOrTag; -use alloy_primitives::address; -use alloy_primitives::Address; -use alloy_primitives::Bytes; -use alloy_primitives::TxKind; -use alloy_primitives::B256; -use alloy_primitives::{hex, U256}; -use alloy_rpc_types_engine::ExecutionPayloadV1; -use alloy_rpc_types_engine::ExecutionPayloadV2; -use alloy_rpc_types_engine::PayloadAttributes; -use alloy_rpc_types_engine::PayloadStatusEnum; -use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatus}; +use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; +use alloy_primitives::{address, hex, Address, Bytes, TxKind, B256, U256}; +use alloy_rpc_types_engine::{ + ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, ForkchoiceUpdated, + PayloadAttributes, PayloadStatus, PayloadStatusEnum, +}; use alloy_rpc_types_eth::Block; -use jsonrpsee::core::RpcResult; -use jsonrpsee::http_client::{transport::HttpBackend, HttpClient}; -use jsonrpsee::proc_macros::rpc; -use op_alloy_consensus::OpTypedTransaction; -use op_alloy_consensus::TxDeposit; +use jsonrpsee::{ + core::RpcResult, + http_client::{transport::HttpBackend, HttpClient}, + proc_macros::rpc, +}; +use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; use op_alloy_rpc_types_engine::OpPayloadAttributes; use reth::rpc::{api::EngineApiClient, types::engine::ForkchoiceState}; use reth_node_api::{EngineTypes, PayloadTypes}; use reth_optimism_node::OpEngineTypes; use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; -use rollup_boost::flashblocks::FlashblocksService; -use rollup_boost::Flashblocks; +use rollup_boost::{Flashblocks, FlashblocksService}; use serde_json::Value; -use std::str::FromStr; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; +use std::{ + str::FromStr, + time::{SystemTime, UNIX_EPOCH}, +}; /// Helper for engine api operations pub struct EngineApi { From 52b8a6272579cec806b3d1fc83715826ae6135c8 Mon Sep 17 00:00:00 2001 From: cody-wang-cb Date: Thu, 17 Apr 2025 09:48:13 -0400 Subject: [PATCH 050/262] Fix Isthmus request hash; support reth 1.3.11 (#564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Adding reth 1.3.11 for the upcoming op chain sepolia Isthmus hardfork for op-rbuilder flashblock builder Also fixing request hash for Isthmus validity rules https://specs.optimism.io/protocol/isthmus/exec-engine.html#header-validity-rules. Tested deploying on Base devnet, it's able to sync again ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 4 ++-- crates/builder/op-rbuilder/src/payload_builder.rs | 12 ++++++++---- .../op-rbuilder/src/payload_builder_vanilla.rs | 2 +- crates/builder/op-rbuilder/src/tester/mod.rs | 3 ++- crates/builder/op-rbuilder/src/tx_signer.rs | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 2406c5f0..2159c04e 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -85,11 +85,11 @@ time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } tokio-tungstenite = "0.26.2" -rand = "0.8.5" +rand = "0.9.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } # `flashblocks` branch -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "e20dbc5c3d7b5223fc2a3a6cfe4c99ed9692caf7" } +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "60885346d4cf7f241de82790478195747433d472" } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index d5876c43..7535782e 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -8,7 +8,7 @@ use alloy_consensus::{ constants::EMPTY_WITHDRAWALS, Eip658Value, Header, Transaction, Typed2718, EMPTY_OMMER_ROOT_HASH, }; -use alloy_eips::{merge::BEACON_NONCE, Encodable2718}; +use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE, Encodable2718}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256}; use alloy_rpc_types_engine::PayloadId; @@ -667,10 +667,14 @@ where .state_root_calculation_duration .record(state_root_start_time.elapsed()); + let mut requests_hash = None; let withdrawals_root = if ctx .chain_spec .is_isthmus_active_at_timestamp(ctx.attributes().timestamp()) { + // always empty requests hash post isthmus + requests_hash = Some(EMPTY_REQUESTS_HASH); + // withdrawals root field in block header is used for storage root of L2 predeploy // `l2tol1-message-passer` Some( @@ -716,7 +720,7 @@ where parent_beacon_block_root: ctx.attributes().payload_attributes.parent_beacon_block_root, blob_gas_used, excess_blob_gas, - requests_hash: None, + requests_hash, }; // seal the block @@ -748,7 +752,7 @@ where let receipts_with_hash = new_transactions .iter() .zip(new_receipts.iter()) - .map(|(tx, receipt)| (*tx.tx_hash(), receipt.clone())) + .map(|(tx, receipt)| (tx.tx_hash(), receipt.clone())) .collect::>(); let new_account_balances = new_bundle .state @@ -1190,7 +1194,7 @@ where num_txs_simulated_fail += 1; trace!(target: "payload_builder", ?tx, "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); - info.invalid_tx_hashes.insert(*tx.tx_hash()); + info.invalid_tx_hashes.insert(tx.tx_hash()); continue; } diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 4137a728..a33d8164 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1107,7 +1107,7 @@ where num_txs_simulated_fail += 1; trace!(target: "payload_builder", ?tx, "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); - info.invalid_tx_hashes.insert(*tx.tx_hash()); + info.invalid_tx_hashes.insert(tx.tx_hash()); continue; } diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 0b879700..ecfde1a2 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -18,7 +18,8 @@ use reth_node_api::{EngineTypes, PayloadTypes}; use reth_optimism_node::OpEngineTypes; use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; -use rollup_boost::{Flashblocks, FlashblocksService}; +use rollup_boost::Flashblocks; +use rollup_boost::FlashblocksService; use serde_json::Value; use std::{ str::FromStr, diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs index 6c2b39ef..6a6a2c5b 100644 --- a/crates/builder/op-rbuilder/src/tx_signer.rs +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use alloy_consensus::SignableTransaction; -use alloy_primitives::{Address, PrimitiveSignature as Signature, B256, U256}; +use alloy_primitives::{Address, Signature, B256, U256}; use op_alloy_consensus::OpTypedTransaction; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::{public_key_to_address, Recovered}; From b72f58d8cb384977c0037136ccfebed67eaef598 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Thu, 17 Apr 2025 15:04:53 -0500 Subject: [PATCH 051/262] chore: fix op-rbuilder devnet docs (#562) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Update the op-rbuilder devnet documentation: * Update it to use the builder-playground * Various fixes (e.g. remove optimism feature, use fully qualified docker image so it pulls automatically) ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- crates/builder/op-rbuilder/README.md | 45 +++++++++++++--------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/crates/builder/op-rbuilder/README.md b/crates/builder/op-rbuilder/README.md index a7e0606b..46a0e059 100644 --- a/crates/builder/op-rbuilder/README.md +++ b/crates/builder/op-rbuilder/README.md @@ -14,7 +14,7 @@ To run op-rbuilder with the op-stack, you need: To run the op-rbuilder, run: ```bash -cargo run -p op-rbuilder --bin op-rbuilder --features optimism -- node \ +cargo run -p op-rbuilder --bin op-rbuilder --features flashblocks -- node \ --chain /path/to/chain-config.json \ --http \ --authrpc.port 9551 \ @@ -61,14 +61,12 @@ cargo run -p op-rbuilder --bin tester --features optimism -- run ## Local Devnet -To run a local devnet, you can use the optimism docker compose tool and send test transactions with `mev-flood`. - -1. Clone [flashbots/optimism](https://github.com/flashbots/optimism) and checkout the `op-rbuilder` branch. +1. Clone [flashbots/builder-playground](https://github.com/flashbots/builder-playground) and start an OPStack chain. ```bash -git clone https://github.com/flashbots/optimism.git -cd optimism -git checkout op-rbuilder +git clone https://github.com/flashbots/builder-playground.git +cd builder-playground +go run main.go cook opstack --external-builder http://host.docker.internal:4444 ``` 2. Remove any existing `reth` chain db. The following are the default data directories: @@ -77,34 +75,31 @@ git checkout op-rbuilder - Windows: `{FOLDERID_RoamingAppData}/reth/` - macOS: `$HOME/Library/Application Support/reth/` -3. Run a clean OP stack in the `optimism` repo: - -```bash -make devnet-clean && make devnet-down && make devnet-up -``` - -4. Run `op-rbuilder` in the `rbuilder` repo on port 8547: +3. Run `op-rbuilder` in the `rbuilder` repo on port 4444: ```bash -cargo run -p op-rbuilder --bin op-rbuilder --features optimism -- node \ - --chain ../optimism/.devnet/genesis-l2.json \ - --http \ - --http.port 8547 \ - --authrpc.jwtsecret ../optimism/ops-bedrock/test-jwt-secret.txt \ +cargo run -p op-rbuilder --bin op-rbuilder -- node \ + --chain $HOME/.playground/devnet/l2-genesis.json \ + --http --http.port 2222 \ + --authrpc.port 4444 --authrpc.jwtsecret $HOME/.playground/devnet/jwtsecret \ + --port 30333 --disable-discovery \ --metrics 127.0.0.1:9001 \ - --rollup.builder-secret-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + --rollup.builder-secret-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --trusted-peers enode://3479db4d9217fb5d7a8ed4d61ac36e120b05d36c2eefb795dc42ff2e971f251a2315f5649ea1833271e020b9adc98d5db9973c7ed92d6b2f1f2223088c3d852f@127.0.0.1:30304 ``` -5. Init `mev-flood`: +4. Init `contender`: ```bash -docker run mevflood init -r http://host.docker.internal:8547 -s local.json +git clone https://github.com/flashbots/contender +cd contender +cargo run -- setup ./scenarios/simple.toml http://localhost:2222 ``` -6. Run `mev-flood`: +6. Run `contender`: ```bash -docker run --init -v ${PWD}:/app/cli/deployments mevflood spam -p 3 -t 5 -r http://host.docker.internal:8547 -l local.json +cargo run -- spam ./scenarios/simple.toml http://localhost:2222 --tpb 10 --duration 10 ``` -And you should start to see blocks being built and landed on-chain with `mev-flood` transactions. +And you should start to see blocks being built and landed on-chain with `contender` transactions. From f3083cd64638368d8be8a59a84e2db0ab5d91f39 Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 22 Apr 2025 06:29:25 +1000 Subject: [PATCH 052/262] Use latest reth for op-rbuilder (#570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/main.rs | 2 +- .../src/payload_builder_vanilla.rs | 34 +++++++++++++------ .../src/primitives/reth/execution.rs | 2 ++ crates/builder/op-rbuilder/src/tester/mod.rs | 4 +-- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 58c0e9c8..c4d43f9f 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -45,7 +45,7 @@ fn main() { ))) .with_add_ons( OpAddOnsBuilder::default() - .with_sequencer(rollup_args.sequencer_http.clone()) + .with_sequencer(rollup_args.sequencer.clone()) .with_enable_tx_conditional(rollup_args.enable_tx_conditional) .build(), ) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index a33d8164..f90f6cb5 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -8,7 +8,7 @@ use alloy_consensus::{ constants::EMPTY_WITHDRAWALS, transaction::Recovered, Eip658Value, Header, Transaction, TxEip1559, Typed2718, EMPTY_OMMER_ROOT_HASH, }; -use alloy_eips::merge::BEACON_NONCE; +use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxHash, TxKind, U256}; use alloy_rpc_types_engine::PayloadId; @@ -71,6 +71,9 @@ use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::*; +// From https://eips.ethereum.org/EIPS/eip-7623 +const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10; + #[derive(Debug, Clone, Default)] #[non_exhaustive] pub struct CustomOpPayloadBuilder { @@ -543,19 +546,22 @@ impl OpBuilder<'_, Txs> { .payload_num_tx .record(info.executed_transactions.len() as f64); - let withdrawals_root = if ctx.is_isthmus_active() { + 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( - state - .database - .as_ref() - .storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, Default::default())?, + ( + Some( + state + .database + .as_ref() + .storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, Default::default())?, + ), + Some(EMPTY_REQUESTS_HASH), ) } else if ctx.is_canyon_active() { - Some(EMPTY_WITHDRAWALS) + (Some(EMPTY_WITHDRAWALS), None) } else { - None + (None, None) }; remove_invalid(info.invalid_tx_hashes.iter().copied().collect()); @@ -563,6 +569,7 @@ impl OpBuilder<'_, Txs> { let payload = ExecutedPayload { info, withdrawals_root, + requests_hash, }; Ok(BuildOutcomeKind::Better { payload }) @@ -583,6 +590,7 @@ impl OpBuilder<'_, Txs> { let ExecutedPayload { info, withdrawals_root, + requests_hash, } = match self.execute(&mut state, &ctx)? { BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), @@ -662,7 +670,7 @@ impl OpBuilder<'_, Txs> { parent_beacon_block_root: ctx.attributes().payload_attributes.parent_beacon_block_root, blob_gas_used, excess_blob_gas, - requests_hash: None, + requests_hash, }; // seal the block @@ -1292,5 +1300,9 @@ fn estimate_gas_for_builder_tx(input: Vec) -> u64 { let zero_cost = zero_bytes * 4; let nonzero_cost = nonzero_bytes * 16; - zero_cost + nonzero_cost + 21_000 + // 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) } diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 292ce90c..4f4d26fe 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -12,6 +12,8 @@ pub struct ExecutedPayload { pub info: ExecutionInfo, /// Withdrawal hash. pub withdrawals_root: Option, + /// Requests hash. + pub requests_hash: Option, } #[derive(Default, Debug)] diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index ecfde1a2..145a4bf3 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -18,8 +18,7 @@ use reth_node_api::{EngineTypes, PayloadTypes}; use reth_optimism_node::OpEngineTypes; use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; -use rollup_boost::Flashblocks; -use rollup_boost::FlashblocksService; +use rollup_boost::{Flashblocks, FlashblocksService}; use serde_json::Value; use std::{ str::FromStr, @@ -190,7 +189,6 @@ pub struct BlockGenerator<'a> { latest_hash: B256, no_tx_pool: bool, block_time_secs: u64, - // flashblocks service flashblocks_endpoint: Option, flashblocks_service: Option, From 1cf957ce3025748a82829e96ab669a2beff5c13b Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 25 Apr 2025 01:17:05 +1000 Subject: [PATCH 053/262] Fix isthmus withdrawals hash on payload builder vanilla (#571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Add isthmus withdrawal hash fix on the vanilla builder. Previously was added to the flashblocks builder. ## 💡 Motivation and Context Fix errors around invalid blocks due to incorrect withdrawals hash --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- .../src/payload_builder_vanilla.rs | 52 +++++++------------ .../src/primitives/reth/execution.rs | 6 +-- 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index f90f6cb5..3dcb0c17 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -36,7 +36,7 @@ use reth_evm::{ use reth_execution_types::ExecutionOutcome; use reth_node_api::{NodePrimitives, NodeTypes, TxTy}; use reth_optimism_chainspec::OpChainSpec; -use reth_optimism_consensus::calculate_receipt_root_no_memo_optimism; +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::OpEngineTypes; @@ -46,9 +46,7 @@ use reth_optimism_payload_builder::{ payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, OpPayloadPrimitives, }; -use reth_optimism_primitives::{ - OpPrimitives, OpReceipt, OpTransactionSigned, ADDRESS_L2_TO_L1_MESSAGE_PASSER, -}; +use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; use reth_optimism_txpool::OpPooledTx; use reth_payload_builder::PayloadBuilderService; use reth_payload_builder_primitives::PayloadBuilderError; @@ -546,31 +544,9 @@ impl OpBuilder<'_, Txs> { .payload_num_tx .record(info.executed_transactions.len() as f64); - 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( - state - .database - .as_ref() - .storage_root(ADDRESS_L2_TO_L1_MESSAGE_PASSER, Default::default())?, - ), - Some(EMPTY_REQUESTS_HASH), - ) - } else if ctx.is_canyon_active() { - (Some(EMPTY_WITHDRAWALS), None) - } else { - (None, None) - }; - remove_invalid(info.invalid_tx_hashes.iter().copied().collect()); - let payload = ExecutedPayload { - info, - withdrawals_root, - requests_hash, - }; + let payload = ExecutedPayload { info }; Ok(BuildOutcomeKind::Better { payload }) } @@ -587,11 +563,7 @@ impl OpBuilder<'_, Txs> { DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { - let ExecutedPayload { - info, - withdrawals_root, - requests_hash, - } = match self.execute(&mut state, &ctx)? { + let ExecutedPayload { info } = match self.execute(&mut state, &ctx)? { BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), BuildOutcomeKind::Aborted { fees } => return Ok(BuildOutcomeKind::Aborted { fees }), @@ -640,6 +612,22 @@ impl OpBuilder<'_, Txs> { .state_root_calculation_duration .record(state_root_start_time.elapsed()); + 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.database.as_ref()) + .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); diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 4f4d26fe..ff95f8dd 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -1,6 +1,6 @@ //! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570) use alloy_consensus::Transaction; -use alloy_primitives::{private::alloy_rlp::Encodable, Address, TxHash, B256, U256}; +use alloy_primitives::{private::alloy_rlp::Encodable, Address, TxHash, U256}; use reth_node_api::NodePrimitives; use reth_optimism_primitives::OpReceipt; use std::collections::HashSet; @@ -10,10 +10,6 @@ use std::collections::HashSet; pub struct ExecutedPayload { /// Tracked execution info pub info: ExecutionInfo, - /// Withdrawal hash. - pub withdrawals_root: Option, - /// Requests hash. - pub requests_hash: Option, } #[derive(Default, Debug)] From c6764368a7547118993c634123c6095d388b11d4 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Sat, 26 Apr 2025 00:31:41 +0700 Subject: [PATCH 054/262] Fix resource usage in monitoring task (#588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Fixes unbounded mem growth in monitoring task ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/monitoring.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/builder/op-rbuilder/src/monitoring.rs b/crates/builder/op-rbuilder/src/monitoring.rs index f94c2754..40c42db1 100644 --- a/crates/builder/op-rbuilder/src/monitoring.rs +++ b/crates/builder/op-rbuilder/src/monitoring.rs @@ -17,7 +17,6 @@ const OP_BUILDER_TX_PREFIX: &[u8] = b"Block Number:"; pub struct Monitoring { builder_signer: Option, metrics: OpRBuilderMetrics, - execution_outcome: ExecutionOutcome, } impl Monitoring { @@ -25,7 +24,6 @@ impl Monitoring { Self { builder_signer, metrics: Default::default(), - execution_outcome: Default::default(), } } @@ -88,10 +86,8 @@ impl Monitoring { let num_reverted_tx = decode_chain_into_reverted_txs(chain); self.metrics.inc_num_reverted_tx(num_reverted_tx); - self.execution_outcome - .extend(chain.execution_outcome().clone()); let builder_balance = - decode_state_into_builder_balance(&self.execution_outcome, self.builder_signer) + decode_state_into_builder_balance(chain.execution_outcome(), self.builder_signer) .and_then(|balance| { balance .to_string() @@ -114,7 +110,6 @@ impl Monitoring { /// This function decodes all transactions in the block, updates the metrics for builder built blocks async fn revert(&mut self, chain: &Chain) -> eyre::Result<()> { info!("Processing new chain revert"); - self.execution_outcome.revert_to(chain.first().number - 1); let mut blocks = decode_chain_into_builder_txs(chain, self.builder_signer); // Reverse the order of txs to start reverting from the tip blocks.reverse(); From c8c9af323392e3f470b2f25164c65733a9f22539 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Fri, 25 Apr 2025 16:19:37 -0500 Subject: [PATCH 055/262] fix: don't build flashblocks with more gas than block gas limit (#567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Currently it's possible for the builder to make more Flashblocks than intended during a block (likely due to delays). If more flashblocks are made than desired (e.g. 11 flashblocks for a 2s block w/ 200ms Flashblocks), the 11th flashblock will have a gas limit that is over the blocks gas limit and will cause invalid blocks on the sequencer. ## 💡 Motivation and Context If the builder produces a full block that uses more gas than the gas limit, the local EL client will treat it as invalid. --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --- .../src/integration/integration_test.rs | 88 +++++++++++++++++++ .../op-rbuilder/src/integration/mod.rs | 2 +- .../op-rbuilder/src/payload_builder.rs | 22 ++++- crates/builder/op-rbuilder/src/tester/mod.rs | 18 +++- 4 files changed, 121 insertions(+), 9 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 5aa13467..187d49a2 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -490,4 +490,92 @@ mod tests { Ok(()) } + + #[tokio::test] + #[cfg(feature = "flashblocks")] + async fn integration_test_flashblocks_respects_gas_limit() -> eyre::Result<()> { + // This is a simple test using the integration framework to test that the chain + // produces blocks. + let mut framework = + IntegrationFramework::new("integration_test_flashblocks_respects_gas_limit").unwrap(); + + // we are going to use a genesis file pre-generated before the test + let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + genesis_path.push("../../genesis.json"); + assert!(genesis_path.exists()); + + let block_time_ms = 1000; + let flashblock_time_ms = 100; + + // create the builder + let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let op_rbuilder_config = OpRbuilderConfig::new() + .chain_config_path(genesis_path.clone()) + .data_dir(builder_data_dir) + .auth_rpc_port(1244) + .network_port(1245) + .http_port(1248) + .with_builder_private_key(BUILDER_PRIVATE_KEY) + .with_flashblocks_ws_url("localhost:1249") + .with_chain_block_time(block_time_ms) + .with_flashbots_block_time(flashblock_time_ms); + + // create the validation reth node + let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let reth = OpRethConfig::new() + .chain_config_path(genesis_path) + .data_dir(reth_data_dir) + .auth_rpc_port(1246) + .network_port(1247); + + framework.start("op-reth", &reth).await.unwrap(); + + let op_rbuilder = framework + .start("op-rbuilder", &op_rbuilder_config) + .await + .unwrap(); + + let engine_api = EngineApi::new("http://localhost:1244").unwrap(); + let validation_api = EngineApi::new("http://localhost:1246").unwrap(); + + let mut generator = BlockGenerator::new( + &engine_api, + Some(&validation_api), + false, + block_time_ms / 1000, + None, + ); + generator.init().await?; + + let provider = ProviderBuilder::::default() + .on_http("http://localhost:1248".parse()?); + + // Delay the payload building by 4s, ensure that the correct number of flashblocks are built + let block_hash = generator.generate_block_with_delay(4).await?; + + // query the block and the transactions inside the block + let block = provider + .get_block_by_hash(block_hash) + .await? + .expect("block"); + + for hash in block.transactions.hashes() { + let _ = provider + .get_transaction_receipt(hash) + .await? + .expect("receipt"); + } + + op_rbuilder + .find_log_line("Processing new chain commit") // no builder tx for flashblocks builder + .await?; + + // check there's no more than 10 flashblocks log lines (2000ms / 200ms) + op_rbuilder.find_log_line("Building flashblock 9").await?; + op_rbuilder + .find_log_line("Skipping flashblock reached target=10 idx=10") + .await?; + + Ok(()) + } } diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index 26a86d35..1bc7748a 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -133,7 +133,7 @@ impl ServiceInstance { if contents.contains(pattern) { Ok(()) } else { - Err(eyre::eyre!("Pattern not found in log file")) + Err(eyre::eyre!("Pattern not found in log file: {}", pattern)) } } } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 7535782e..a0ba9cfe 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -232,6 +232,8 @@ pub struct OpPayloadBuilder { pub chain_block_time: u64, /// Flashblock block time pub flashblock_block_time: u64, + /// Number of flashblocks per block + pub flashblocks_per_block: u64, /// The metrics for the builder pub metrics: OpRBuilderMetrics, } @@ -262,6 +264,7 @@ impl OpPayloadBuilder { tx, chain_block_time, flashblock_block_time, + flashblocks_per_block: chain_block_time / flashblock_block_time, metrics: Default::default(), } } @@ -423,8 +426,8 @@ where return Ok(()); } - let gas_per_batch = - ctx.block_gas_limit() / (self.chain_block_time / self.flashblock_block_time); + let gas_per_batch = ctx.block_gas_limit() / self.flashblocks_per_block; + let mut total_gas_per_batch = gas_per_batch; let mut flashblock_count = 0; @@ -482,11 +485,22 @@ where // Exit loop if channel closed or cancelled match received { Some(()) => { + if flashblock_count >= self.flashblocks_per_block { + tracing::info!( + target: "payload_builder", + "Skipping flashblock reached target={} idx={}", + self.flashblocks_per_block, + flashblock_count + ); + continue; + } + // Continue with flashblock building tracing::info!( target: "payload_builder", - "Building flashblock {}", + "Building flashblock {} {}", flashblock_count, + total_gas_per_batch, ); let flashblock_build_start_time = Instant::now(); @@ -512,7 +526,7 @@ where &mut info, &mut db, best_txs, - total_gas_per_batch, + total_gas_per_batch.min(ctx.block_gas_limit()), )?; ctx.metrics .payload_tx_simulation_duration diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 145a4bf3..a107f845 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -321,7 +321,11 @@ impl<'a> BlockGenerator<'a> { } /// Helper function to submit a payload and update chain state - async fn submit_payload(&mut self, transactions: Option>) -> eyre::Result { + async fn submit_payload( + &mut self, + transactions: Option>, + block_building_delay_secs: u64, + ) -> eyre::Result { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -395,7 +399,8 @@ impl<'a> BlockGenerator<'a> { } if !self.no_tx_pool { - tokio::time::sleep(tokio::time::Duration::from_secs(self.block_time_secs)).await; + let sleep_time = self.block_time_secs + block_building_delay_secs; + tokio::time::sleep(tokio::time::Duration::from_secs(sleep_time)).await; } let payload = if let Some(flashblocks_service) = &self.flashblocks_service { @@ -450,7 +455,11 @@ impl<'a> BlockGenerator<'a> { /// Generate a single new block and return its hash pub async fn generate_block(&mut self) -> eyre::Result { - self.submit_payload(None).await + self.submit_payload(None, 0).await + } + + pub async fn generate_block_with_delay(&mut self, delay: u64) -> eyre::Result { + self.submit_payload(None, delay).await } /// Submit a deposit transaction to seed an account with ETH @@ -473,7 +482,8 @@ impl<'a> BlockGenerator<'a> { let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; let signed_tx_rlp = signed_tx.encoded_2718(); - self.submit_payload(Some(vec![signed_tx_rlp.into()])).await + self.submit_payload(Some(vec![signed_tx_rlp.into()]), 0) + .await } } From 8cee172cc2d2ae123f038938b1badcb8ca7ba35c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Fri, 25 Apr 2025 16:19:59 -0500 Subject: [PATCH 056/262] op-rbuilder: Update Documentation / CI Script (#575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary 1. Update the version of op-reth used by CI 2. Make the CI script work on macOS as well as linux 3. Update the instructions for running/testing op-rbuilder ## 💡 Motivation and Context 1. The CI script was handy for downloading Reth 2. The instructions didn't work for me (due to the optimism feature being removed etc). --- ## ✅ I have completed the following steps: * [x] Run `make lint` * [x] Run `make test` * [x] Added tests (if applicable) --------- Co-authored-by: shana --- crates/builder/op-rbuilder/README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/builder/op-rbuilder/README.md b/crates/builder/op-rbuilder/README.md index 46a0e059..13d93057 100644 --- a/crates/builder/op-rbuilder/README.md +++ b/crates/builder/op-rbuilder/README.md @@ -49,14 +49,20 @@ op-rbuilder has an integration test framework that runs the builder against mock To run the integration tests, run: ```bash +# Ensure you have op-reth installed in your path, +# you can download it with the command below and move it to a location in your path +./scripts/ci/download-op-reth.sh + # Generate a genesis file -cargo run -p op-rbuilder --bin tester --features optimism -- genesis --output genesis.json +cargo run -p op-rbuilder --bin tester -- genesis --output genesis.json # Build the op-rbuilder binary -cargo build -p op-rbuilder --bin op-rbuilder --features optimism +# To test flashblocks add flashblocks as a feature +cargo build -p op-rbuilder --bin op-rbuilder # Run the integration tests -cargo run -p op-rbuilder --bin tester --features optimism -- run +# To test flashblocks add flashblocks as a feature +cargo test --package op-rbuilder --lib --features integration -- integration::integration_test::tests ``` ## Local Devnet From 5f78693f9a0b889fe248592c1bbf0b57035fb6e2 Mon Sep 17 00:00:00 2001 From: cody-wang-cb Date: Wed, 30 Apr 2025 14:11:17 -0400 Subject: [PATCH 057/262] Revert revert protection in op-rbuilder (#602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary The builder block building times always slowly increase to a very high number on Base Sepolia, suspecting revert protection is causing failed txs to get backed up, which causes slow down in op-rbuilder block building. ``` # HELP reth_op_rbuilder_payload_num_tx_simulated_fail Number of transactions in the payload that failed simulation # TYPE reth_op_rbuilder_payload_num_tx_simulated_fail summary reth_op_rbuilder_payload_num_tx_simulated_fail{quantile="0"} 77 reth_op_rbuilder_payload_num_tx_simulated_fail{quantile="0.5"} 3198.061173550786 reth_op_rbuilder_payload_num_tx_simulated_fail{quantile="0.9"} 3223.1035892442424 reth_op_rbuilder_payload_num_tx_simulated_fail{quantile="0.95"} 3223.748274430612 reth_op_rbuilder_payload_num_tx_simulated_fail{quantile="0.99"} 3226.973635127358 reth_op_rbuilder_payload_num_tx_simulated_fail{quantile="0.999"} 3226.973635127358 reth_op_rbuilder_payload_num_tx_simulated_fail{quantile="1"} 3228 reth_op_rbuilder_payload_num_tx_simulated_fail_sum 64118848 reth_op_rbuilder_payload_num_tx_simulated_fail_count 21069 # HELP reth_op_rbuilder_payload_tx_simulation_duration Duration of payload simulation of all transactions # TYPE reth_op_rbuilder_payload_tx_simulation_duration summary reth_op_rbuilder_payload_tx_simulation_duration{quantile="0"} 0.018329514 reth_op_rbuilder_payload_tx_simulation_duration{quantile="0.5"} 0.49002444205448686 reth_op_rbuilder_payload_tx_simulation_duration{quantile="0.9"} 0.5026304818952316 reth_op_rbuilder_payload_tx_simulation_duration{quantile="0.95"} 0.5079867028401951 reth_op_rbuilder_payload_tx_simulation_duration{quantile="0.99"} 0.553164415015488 reth_op_rbuilder_payload_tx_simulation_duration{quantile="0.999"} 0.553164415015488 reth_op_rbuilder_payload_tx_simulation_duration{quantile="1"} 0.555461782 reth_op_rbuilder_payload_tx_simulation_duration_sum 9654.159540928 reth_op_rbuilder_payload_tx_simulation_duration_count 25870 # HELP reth_op_rbuilder_flashblock_build_duration Flashblock build duration # TYPE reth_op_rbuilder_flashblock_build_duration summary reth_op_rbuilder_flashblock_build_duration{quantile="0"} 0.055144161 reth_op_rbuilder_flashblock_build_duration{quantile="0.5"} 0.5166962986266059 reth_op_rbuilder_flashblock_build_duration{quantile="0.9"} 0.5348868728374531 reth_op_rbuilder_flashblock_build_duration{quantile="0.95"} 0.5672812838109652 reth_op_rbuilder_flashblock_build_duration{quantile="0.99"} 0.583272957920482 reth_op_rbuilder_flashblock_build_duration{quantile="0.999"} 0.583272957920482 reth_op_rbuilder_flashblock_build_duration{quantile="1"} 0.60984729 reth_op_rbuilder_flashblock_build_duration_sum 9081.926654393968 reth_op_rbuilder_flashblock_build_duration_count 21065 # HELP reth_op_rbuilder_payload_num_tx_simulated_success Number of transactions in the payload that were successfully simulated # TYPE reth_op_rbuilder_payload_num_tx_simulated_success summary reth_op_rbuilder_payload_num_tx_simulated_success{quantile="0"} 1 reth_op_rbuilder_payload_num_tx_simulated_success{quantile="0.5"} 11.000052032263987 reth_op_rbuilder_payload_num_tx_simulated_success{quantile="0.9"} 20.999528921206892 reth_op_rbuilder_payload_num_tx_simulated_success{quantile="0.95"} 24.001108231483542 reth_op_rbuilder_payload_num_tx_simulated_success{quantile="0.99"} 29.00012111115082 reth_op_rbuilder_payload_num_tx_simulated_success{quantile="0.999"} 29.00012111115082 reth_op_rbuilder_payload_num_tx_simulated_success{quantile="1"} 60 reth_op_rbuilder_payload_num_tx_simulated_success_sum 136029 reth_op_rbuilder_payload_num_tx_simulated_success_count 21069 ``` ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/payload_builder.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index a0ba9cfe..b39057e5 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -1206,10 +1206,7 @@ where num_txs_simulated_success += 1; } else { num_txs_simulated_fail += 1; - trace!(target: "payload_builder", ?tx, "skipping reverted transaction"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - info.invalid_tx_hashes.insert(tx.tx_hash()); - continue; + trace!(target: "payload_builder", ?tx, "reverted transaction"); } // add gas used by the transaction to cumulative gas used, before creating the receipt From 2e38edb38563252e73517f3cac0f81d0ffa3d69f Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 9 May 2025 11:21:04 +0200 Subject: [PATCH 058/262] Finish block building process even when cancel request is found (#606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary This PR introduces three changes: - It refactors some of the common utilities shared among the tests into a single `TestHarness` struct which spins the test framework with all the components. Note that it does not update the tests to use this new utility. - It fixes an issue with the block builder that would stop the block building process and not return any block if a cancel request was found. This happens when an FCU and a getPayload request are called to close to each other, the getPayload cancels the block building process, and getPayload waits forever for a block that will never be built. Now, the block building finishes. - It adds an integration test to cover this use case with the new utility. ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/Cargo.toml | 1 + .../src/integration/integration_test.rs | 35 ++++- .../op-rbuilder/src/integration/mod.rs | 136 ++++++++++++++++++ .../src/payload_builder_vanilla.rs | 19 +-- crates/builder/op-rbuilder/src/tester/main.rs | 2 +- crates/builder/op-rbuilder/src/tester/mod.rs | 49 ++++--- 6 files changed, 206 insertions(+), 36 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 2159c04e..413fb358 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -80,6 +80,7 @@ metrics.workspace = true serde_json.workspace = true tokio-util.workspace = true thiserror.workspace = true +parking_lot.workspace = true time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 187d49a2..86c2b033 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -1,7 +1,9 @@ #[cfg(all(test, feature = "integration"))] mod tests { use crate::{ - integration::{op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework}, + integration::{ + op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, TestHarness, + }, tester::{BlockGenerator, EngineApi}, tx_signer::Signer, }; @@ -66,7 +68,7 @@ mod tests { let engine_api = EngineApi::new("http://localhost:1234").unwrap(); let validation_api = EngineApi::new("http://localhost:1236").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None); + let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None); generator.init().await?; let provider = ProviderBuilder::::default() @@ -138,7 +140,7 @@ mod tests { let engine_api = EngineApi::new("http://localhost:1244").unwrap(); let validation_api = EngineApi::new("http://localhost:1246").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None); + let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None); let latest_block = generator.init().await?; let provider = ProviderBuilder::::default() @@ -263,7 +265,7 @@ mod tests { let engine_api = EngineApi::new("http://localhost:1264").unwrap(); let validation_api = EngineApi::new("http://localhost:1266").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 1, None); + let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None); let latest_block = generator.init().await?; let provider = ProviderBuilder::::default() @@ -349,6 +351,25 @@ mod tests { Ok(()) } + #[tokio::test] + #[cfg(not(feature = "flashblocks"))] + async fn integration_test_get_payload_close_to_fcu() -> eyre::Result<()> { + let test_harness = TestHarness::new("integration_test_get_payload_close_to_fcu").await; + let mut block_generator = test_harness.block_generator().await?; + + // add some transactions to the pool so that the builder is busy when we send the fcu/getPayload requests + for _ in 0..10 { + // Note, for this test it is okay if they are not valid + test_harness.send_valid_transaction().await?; + } + + // TODO: In the fail case scenario, this hangs forever, but it should return an error + // Figure out how to do timeout (i.e. 1s) on the engine api. + block_generator.submit_payload(None, 0, true).await?; + + Ok(()) + } + #[tokio::test] #[cfg(feature = "flashblocks")] async fn integration_test_chain_produces_blocks() -> eyre::Result<()> { @@ -410,7 +431,7 @@ mod tests { let engine_api = EngineApi::new("http://localhost:1234").unwrap(); let validation_api = EngineApi::new("http://localhost:1236").unwrap(); - let mut generator = BlockGenerator::new(&engine_api, Some(&validation_api), false, 2, None); + let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 2, None); generator.init().await?; let provider = ProviderBuilder::::default() @@ -539,8 +560,8 @@ mod tests { let validation_api = EngineApi::new("http://localhost:1246").unwrap(); let mut generator = BlockGenerator::new( - &engine_api, - Some(&validation_api), + engine_api, + Some(validation_api), false, block_time_ms / 1000, None, diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index 1bc7748a..d62b0d48 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -1,5 +1,18 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::BlockNumberOrTag; +use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718}; +use alloy_provider::{Identity, Provider, ProviderBuilder}; +use op_alloy_consensus::OpTypedTransaction; +use op_alloy_network::Optimism; +use op_rbuilder::OpRbuilderConfig; +use op_reth::OpRethConfig; +use parking_lot::Mutex; +use std::cmp::max; +use std::collections::HashSet; use std::future::Future; +use std::net::TcpListener; use std::path::Path; +use std::sync::LazyLock; use std::{ fs::{File, OpenOptions}, io, @@ -10,6 +23,10 @@ use std::{ }; use time::{format_description, OffsetDateTime}; use tokio::time::sleep; +use uuid::Uuid; + +use crate::tester::{BlockGenerator, EngineApi}; +use crate::tx_signer::Signer; /// Default JWT token for testing purposes pub const DEFAULT_JWT_TOKEN: &str = @@ -195,3 +212,122 @@ impl Drop for IntegrationFramework { } } } + +const BUILDER_PRIVATE_KEY: &str = + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; + +pub struct TestHarness { + _framework: IntegrationFramework, + builder_auth_rpc_port: u16, + builder_http_port: u16, + validator_auth_rpc_port: u16, +} + +impl TestHarness { + pub async fn new(name: &str) -> Self { + let mut framework = IntegrationFramework::new(name).unwrap(); + + // we are going to use a genesis file pre-generated before the test + let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + genesis_path.push("../../genesis.json"); + assert!(genesis_path.exists()); + + // create the builder + let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let builder_auth_rpc_port = get_available_port(); + let builder_http_port = get_available_port(); + let op_rbuilder_config = OpRbuilderConfig::new() + .chain_config_path(genesis_path.clone()) + .data_dir(builder_data_dir) + .auth_rpc_port(builder_auth_rpc_port) + .network_port(get_available_port()) + .http_port(builder_http_port) + .with_builder_private_key(BUILDER_PRIVATE_KEY); + + // create the validation reth node + + let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let validator_auth_rpc_port = get_available_port(); + let reth = OpRethConfig::new() + .chain_config_path(genesis_path) + .data_dir(reth_data_dir) + .auth_rpc_port(validator_auth_rpc_port) + .network_port(get_available_port()); + + framework.start("op-reth", &reth).await.unwrap(); + + let _ = framework + .start("op-rbuilder", &op_rbuilder_config) + .await + .unwrap(); + + Self { + _framework: framework, + builder_auth_rpc_port, + builder_http_port, + validator_auth_rpc_port, + } + } + + pub async fn send_valid_transaction(&self) -> eyre::Result<()> { + // Get builder's address + let known_wallet = Signer::try_from_secret(BUILDER_PRIVATE_KEY.parse()?)?; + let builder_address = known_wallet.address; + + let url = format!("http://localhost:{}", self.builder_http_port); + let provider = + ProviderBuilder::::default().on_http(url.parse()?); + + // Get current nonce includeing the ones from the txpool + let nonce = provider + .get_transaction_count(builder_address) + .pending() + .await?; + + let latest_block = provider + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .unwrap(); + + let base_fee = max( + latest_block.header.base_fee_per_gas.unwrap(), + MIN_PROTOCOL_BASE_FEE, + ); + + // Transaction from builder should succeed + let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: 901, + nonce, + gas_limit: 210000, + max_fee_per_gas: base_fee.into(), + ..Default::default() + }); + let signed_tx = known_wallet.sign_tx(tx_request)?; + let _ = provider + .send_raw_transaction(signed_tx.encoded_2718().as_slice()) + .await?; + + Ok(()) + } + + pub async fn block_generator(&self) -> eyre::Result { + let engine_api = EngineApi::new_with_port(self.builder_auth_rpc_port).unwrap(); + let validation_api = Some(EngineApi::new_with_port(self.validator_auth_rpc_port).unwrap()); + + let mut generator = BlockGenerator::new(engine_api, validation_api, false, 1, None); + generator.init().await?; + + Ok(generator) + } +} + +pub fn get_available_port() -> u16 { + static CLAIMED_PORTS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); + loop { + let port: u16 = rand::random_range(1000..20000); + if TcpListener::bind(("127.0.0.1", port)).is_ok() && CLAIMED_PORTS.lock().insert(port) { + return port; + } + } +} diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 3dcb0c17..38b8d67b 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -514,18 +514,13 @@ impl OpBuilder<'_, Txs> { ctx.metrics .transaction_pool_fetch_duration .record(best_txs_start_time.elapsed()); - if ctx - .execute_best_transactions( - &mut info, - state, - best_txs, - block_gas_limit, - block_da_limit, - )? - .is_some() - { - return Ok(BuildOutcomeKind::Cancelled); - } + ctx.execute_best_transactions( + &mut info, + state, + best_txs, + block_gas_limit, + block_da_limit, + )?; } // Add builder tx to the block diff --git a/crates/builder/op-rbuilder/src/tester/main.rs b/crates/builder/op-rbuilder/src/tester/main.rs index 8946c4c8..f5094490 100644 --- a/crates/builder/op-rbuilder/src/tester/main.rs +++ b/crates/builder/op-rbuilder/src/tester/main.rs @@ -62,7 +62,7 @@ async fn main() -> eyre::Result<()> { } Commands::Deposit { address, amount } => { let engine_api = EngineApi::builder().build().unwrap(); - let mut generator = BlockGenerator::new(&engine_api, None, false, 1, None); + let mut generator = BlockGenerator::new(engine_api, None, false, 1, None); generator.init().await?; diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index a107f845..5fb18352 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -80,10 +80,22 @@ impl EngineApi { Self::builder().with_url(url).build() } + pub fn new_with_port(port: u16) -> Result> { + Self::builder() + .with_url(&format!("http://localhost:{}", port)) + .build() + } + pub async fn get_payload_v3( &self, payload_id: PayloadId, ) -> eyre::Result<::ExecutionPayloadEnvelopeV3> { + println!( + "Fetching payload with id: {} at {}", + payload_id, + chrono::Utc::now() + ); + Ok( EngineApiClient::::get_payload_v3(&self.engine_api_client, payload_id) .await?, @@ -96,6 +108,8 @@ impl EngineApi { versioned_hashes: Vec, parent_beacon_block_root: B256, ) -> eyre::Result { + println!("Submitting new payload at {}...", chrono::Utc::now()); + Ok(EngineApiClient::::new_payload_v3( &self.engine_api_client, payload, @@ -111,6 +125,8 @@ impl EngineApi { new_head: B256, payload_attributes: Option<::PayloadAttributes>, ) -> eyre::Result { + println!("Updating forkchoice at {}...", chrono::Utc::now()); + Ok(EngineApiClient::::fork_choice_updated_v3( &self.engine_api_client, ForkchoiceState { @@ -183,9 +199,9 @@ pub async fn generate_genesis(output: Option) -> eyre::Result<()> { const FJORD_DATA: &[u8] = &hex!("440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"); /// A system that continuously generates blocks using the engine API -pub struct BlockGenerator<'a> { - engine_api: &'a EngineApi, - validation_api: Option<&'a EngineApi>, +pub struct BlockGenerator { + engine_api: EngineApi, + validation_api: Option, latest_hash: B256, no_tx_pool: bool, block_time_secs: u64, @@ -194,10 +210,10 @@ pub struct BlockGenerator<'a> { flashblocks_service: Option, } -impl<'a> BlockGenerator<'a> { +impl BlockGenerator { pub fn new( - engine_api: &'a EngineApi, - validation_api: Option<&'a EngineApi>, + engine_api: EngineApi, + validation_api: Option, no_tx_pool: bool, block_time_secs: u64, flashblocks_endpoint: Option, @@ -219,7 +235,7 @@ impl<'a> BlockGenerator<'a> { self.latest_hash = latest_block.header.hash; // Sync validation node if it exists - if let Some(validation_api) = self.validation_api { + if let Some(validation_api) = &self.validation_api { self.sync_validation_node(validation_api).await?; } @@ -321,10 +337,11 @@ impl<'a> BlockGenerator<'a> { } /// Helper function to submit a payload and update chain state - async fn submit_payload( + pub async fn submit_payload( &mut self, transactions: Option>, block_building_delay_secs: u64, + no_sleep: bool, // TODO: Change this, too many parameters we can tweak here to put as a function arguments ) -> eyre::Result { let timestamp = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -398,7 +415,7 @@ impl<'a> BlockGenerator<'a> { flashblocks_service.set_current_payload_id(payload_id).await; } - if !self.no_tx_pool { + if !self.no_tx_pool && !no_sleep { let sleep_time = self.block_time_secs + block_building_delay_secs; tokio::time::sleep(tokio::time::Duration::from_secs(sleep_time)).await; } @@ -420,7 +437,7 @@ impl<'a> BlockGenerator<'a> { } // Validate with validation node if present - if let Some(validation_api) = self.validation_api { + if let Some(validation_api) = &self.validation_api { let validation_status = validation_api .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) .await?; @@ -442,7 +459,7 @@ impl<'a> BlockGenerator<'a> { .await?; // Update forkchoice on validator if present - if let Some(validation_api) = self.validation_api { + if let Some(validation_api) = &self.validation_api { validation_api .update_forkchoice(self.latest_hash, new_block_hash, None) .await?; @@ -455,11 +472,11 @@ impl<'a> BlockGenerator<'a> { /// Generate a single new block and return its hash pub async fn generate_block(&mut self) -> eyre::Result { - self.submit_payload(None, 0).await + self.submit_payload(None, 0, false).await } pub async fn generate_block_with_delay(&mut self, delay: u64) -> eyre::Result { - self.submit_payload(None, delay).await + self.submit_payload(None, delay, false).await } /// Submit a deposit transaction to seed an account with ETH @@ -482,7 +499,7 @@ impl<'a> BlockGenerator<'a> { let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; let signed_tx_rlp = signed_tx.encoded_2718(); - self.submit_payload(Some(vec![signed_tx_rlp.into()]), 0) + self.submit_payload(Some(vec![signed_tx_rlp.into()]), 0, false) .await } } @@ -505,8 +522,8 @@ pub async fn run_system( }; let mut generator = BlockGenerator::new( - &engine_api, - validation_api.as_ref(), + engine_api, + validation_api, no_tx_pool, block_time_secs, flashblocks_endpoint, From f5bf1b00a08f890d4177a5d09ec9ac2d0c32dc4a Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Fri, 9 May 2025 20:04:35 +0700 Subject: [PATCH 059/262] Add usage of jemalloc in op-rbuilder when feature is enabled + improve debug-fast profile (#617) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Summary Adding `lto = "thin"` for debug fast profile sped up my build time from 5 min to 4 min Enabled jemalloc in op-rbuilder ## 💡 Motivation and Context --- ## ✅ I have completed the following steps: * [ ] Run `make lint` * [ ] Run `make test` * [ ] Added tests (if applicable) --- crates/builder/op-rbuilder/src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index c4d43f9f..6ceb936f 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -29,6 +29,11 @@ mod tester; mod tx_signer; use monitor_tx_pool::monitor_tx_pool; +// Prefer jemalloc for performance reasons. +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + fn main() { Cli::::parse() .run(|builder, builder_args| async move { From f042224615e992cef9d8a4be784cdc40e56be343 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Mon, 12 May 2025 09:12:26 +0200 Subject: [PATCH 060/262] fix: set an address for authrpc to the op-rbuilder readme (#581) This PR fixes the launch instructions for `op-rbuilder`, making the AuthRPC accessible on all network interfaces. This is necessary to allow `op-rbuilder` to be reachable from the virtual network interface created by Docker. Additionally, the metrics port has been changed to `9011`, since `9001` is used by `builder-playground` for the `beacon`: ``` go run main.go cook opstack --external-builder http://host.docker.internal:4444 2025/04/23 20:21:02 Log level: info 2025/04/23 20:21:02 Genesis block hash: 0x301f9f301f868d587fec26c2a1b5f7d9896736842ab1787923722c3213b61e05 true ========= Services started ========= - el (authrpc: 8551/8551, http: 8545/8545, rpc: 30303/30303) - beacon (http: 3500/3500, p2p: 9000/9000/udp, p2p: 9000/9001, quic-p2p: 9100/9100) - validator () - rollup-boost (authrpc: 8551/8552) - op-node (http: 8549/8549, metrics: 7300/7300, p2p: 9003/9003, p2p: 9003/9004/udp) - op-geth (authrpc: 8551/8553, http: 8545/8546, metrics: 6061/6061, rpc: 30303/30304, ws: 8546/8547) - op-batcher () ========= Output ========= - op-geth-enode: enode://3479db4d9217fb5d7a8ed4d61ac36e120b05d36c2eefb795dc42ff2e971f251a2315f5649ea1833271e020b9adc98d5db9973c7ed92d6b2f1f2223088c3d852f@127.0.0.1:30304?discport=0 ``` Trailing spaces have also been removed in some places. --- crates/builder/op-rbuilder/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/builder/op-rbuilder/README.md b/crates/builder/op-rbuilder/README.md index 13d93057..500aca5d 100644 --- a/crates/builder/op-rbuilder/README.md +++ b/crates/builder/op-rbuilder/README.md @@ -3,7 +3,7 @@ [![CI status](https://github.com/flashbots/rbuilder/actions/workflows/checks.yaml/badge.svg?branch=develop)](https://github.com/flashbots/rbuilder/actions/workflows/integration.yaml) -`op-rbuilder` is a Rust-based block builder designed to build blocks for the Optimism stack. +`op-rbuilder` is a Rust-based block builder designed to build blocks for the Optimism stack. ## Running op-rbuilder @@ -18,7 +18,7 @@ cargo run -p op-rbuilder --bin op-rbuilder --features flashblocks -- node \ --chain /path/to/chain-config.json \ --http \ --authrpc.port 9551 \ - --authrpc.jwtsecret /path/to/jwt.hex + --authrpc.jwtsecret /path/to/jwt.hex ``` To build the op-rbuilder, run: @@ -29,7 +29,7 @@ cargo build -p op-rbuilder --bin op-rbuilder --features optimism ## Observability -To verify whether a builder block has landed on-chain, you can add the `--rollup.builder-secret-key` flag or `BUILDER_SECRET_KEY` environment variable. +To verify whether a builder block has landed on-chain, you can add the `--rollup.builder-secret-key` flag or `BUILDER_SECRET_KEY` environment variable. This will add an additional transaction to the end of the block from the builder key. The transaction will have `Block Number: {}` in the input data as a transfer to the zero address. Ensure that the key has sufficient balance to pay for the transaction at the end of the block. To enable metrics, set the `--metrics` flag like in [reth](https://reth.rs/run/observability.html) which will expose reth metrics in addition to op-rbuilder metrics. op-rbuilder exposes on-chain metrics via [reth execution extensions](https://reth.rs/developers/exex/exex.html) such as the number of blocks landed and builder balance. Note that the accuracy of the on-chain metrics will be dependent on the sync status of the builder node. There are also additional block building metrics such as: @@ -87,9 +87,9 @@ go run main.go cook opstack --external-builder http://host.docker.internal:4444 cargo run -p op-rbuilder --bin op-rbuilder -- node \ --chain $HOME/.playground/devnet/l2-genesis.json \ --http --http.port 2222 \ - --authrpc.port 4444 --authrpc.jwtsecret $HOME/.playground/devnet/jwtsecret \ + --authrpc.addr 0.0.0.0 --authrpc.port 4444 --authrpc.jwtsecret $HOME/.playground/devnet/jwtsecret \ --port 30333 --disable-discovery \ - --metrics 127.0.0.1:9001 \ + --metrics 127.0.0.1:9011 \ --rollup.builder-secret-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --trusted-peers enode://3479db4d9217fb5d7a8ed4d61ac36e120b05d36c2eefb795dc42ff2e971f251a2315f5649ea1833271e020b9adc98d5db9973c7ed92d6b2f1f2223088c3d852f@127.0.0.1:30304 ``` From 75a7dd3ae13e597d13df1d20d11c3356cef556c1 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 12 May 2025 09:35:46 +0100 Subject: [PATCH 061/262] Clean up the repo --- crates/builder/op-rbuilder/README.md | 111 --------------------------- 1 file changed, 111 deletions(-) delete mode 100644 crates/builder/op-rbuilder/README.md diff --git a/crates/builder/op-rbuilder/README.md b/crates/builder/op-rbuilder/README.md deleted file mode 100644 index 500aca5d..00000000 --- a/crates/builder/op-rbuilder/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# op-rbuilder - -[![CI status](https://github.com/flashbots/rbuilder/actions/workflows/checks.yaml/badge.svg?branch=develop)](https://github.com/flashbots/rbuilder/actions/workflows/integration.yaml) - - -`op-rbuilder` is a Rust-based block builder designed to build blocks for the Optimism stack. - -## Running op-rbuilder - -To run op-rbuilder with the op-stack, you need: -- CL node to sync the op-rbuilder with the canonical chain -- Sequencer with the [rollup-boost](https://github.com/flashbots/rollup-boost) setup - -To run the op-rbuilder, run: - -```bash -cargo run -p op-rbuilder --bin op-rbuilder --features flashblocks -- node \ - --chain /path/to/chain-config.json \ - --http \ - --authrpc.port 9551 \ - --authrpc.jwtsecret /path/to/jwt.hex -``` - -To build the op-rbuilder, run: - -```bash -cargo build -p op-rbuilder --bin op-rbuilder --features optimism -``` - -## Observability - -To verify whether a builder block has landed on-chain, you can add the `--rollup.builder-secret-key` flag or `BUILDER_SECRET_KEY` environment variable. -This will add an additional transaction to the end of the block from the builder key. The transaction will have `Block Number: {}` in the input data as a transfer to the zero address. Ensure that the key has sufficient balance to pay for the transaction at the end of the block. - -To enable metrics, set the `--metrics` flag like in [reth](https://reth.rs/run/observability.html) which will expose reth metrics in addition to op-rbuilder metrics. op-rbuilder exposes on-chain metrics via [reth execution extensions](https://reth.rs/developers/exex/exex.html) such as the number of blocks landed and builder balance. Note that the accuracy of the on-chain metrics will be dependent on the sync status of the builder node. There are also additional block building metrics such as: - -- Block building latency -- State root calculation latency -- Transaction fetch latency -- Transaction simulation latency -- Number of transactions included in the built block - -To see the full list of op-rbuilder metrics, see [`src/metrics.rs`](./src/metrics.rs). - -## Integration Testing - -op-rbuilder has an integration test framework that runs the builder against mock engine api payloads and ensures that the builder produces valid blocks. - -To run the integration tests, run: - -```bash -# Ensure you have op-reth installed in your path, -# you can download it with the command below and move it to a location in your path -./scripts/ci/download-op-reth.sh - -# Generate a genesis file -cargo run -p op-rbuilder --bin tester -- genesis --output genesis.json - -# Build the op-rbuilder binary -# To test flashblocks add flashblocks as a feature -cargo build -p op-rbuilder --bin op-rbuilder - -# Run the integration tests -# To test flashblocks add flashblocks as a feature -cargo test --package op-rbuilder --lib --features integration -- integration::integration_test::tests -``` - -## Local Devnet - -1. Clone [flashbots/builder-playground](https://github.com/flashbots/builder-playground) and start an OPStack chain. - -```bash -git clone https://github.com/flashbots/builder-playground.git -cd builder-playground -go run main.go cook opstack --external-builder http://host.docker.internal:4444 -``` - -2. Remove any existing `reth` chain db. The following are the default data directories: - -- Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` -- Windows: `{FOLDERID_RoamingAppData}/reth/` -- macOS: `$HOME/Library/Application Support/reth/` - -3. Run `op-rbuilder` in the `rbuilder` repo on port 4444: - -```bash -cargo run -p op-rbuilder --bin op-rbuilder -- node \ - --chain $HOME/.playground/devnet/l2-genesis.json \ - --http --http.port 2222 \ - --authrpc.addr 0.0.0.0 --authrpc.port 4444 --authrpc.jwtsecret $HOME/.playground/devnet/jwtsecret \ - --port 30333 --disable-discovery \ - --metrics 127.0.0.1:9011 \ - --rollup.builder-secret-key ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - --trusted-peers enode://3479db4d9217fb5d7a8ed4d61ac36e120b05d36c2eefb795dc42ff2e971f251a2315f5649ea1833271e020b9adc98d5db9973c7ed92d6b2f1f2223088c3d852f@127.0.0.1:30304 -``` - -4. Init `contender`: - -```bash -git clone https://github.com/flashbots/contender -cd contender -cargo run -- setup ./scenarios/simple.toml http://localhost:2222 -``` - -6. Run `contender`: - -```bash -cargo run -- spam ./scenarios/simple.toml http://localhost:2222 --tpb 10 --duration 10 -``` - -And you should start to see blocks being built and landed on-chain with `contender` transactions. From 0474933022069a2e51c40a1c2290fd97db14444f Mon Sep 17 00:00:00 2001 From: avalonche Date: Mon, 12 May 2025 19:25:23 +1000 Subject: [PATCH 062/262] Add info log for reverting tx hashes --- crates/builder/op-rbuilder/src/payload_builder_vanilla.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 38b8d67b..08c310a5 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -667,7 +667,7 @@ impl OpBuilder<'_, Txs> { ); let sealed_block = Arc::new(block.seal_slow()); - info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); + tracing::info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); // create the executed block data let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { @@ -1096,7 +1096,7 @@ where num_txs_simulated_success += 1; } else { num_txs_simulated_fail += 1; - trace!(target: "payload_builder", ?tx, "skipping reverted transaction"); + info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); info.invalid_tx_hashes.insert(tx.tx_hash()); continue; From 9cfada0b1c58887dbead5b9059f64167b0029a61 Mon Sep 17 00:00:00 2001 From: avalonche Date: Mon, 12 May 2025 19:38:04 +1000 Subject: [PATCH 063/262] remove unecessary import --- crates/builder/op-rbuilder/src/payload_builder_vanilla.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 08c310a5..407593b0 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -667,7 +667,7 @@ impl OpBuilder<'_, Txs> { ); let sealed_block = Arc::new(block.seal_slow()); - tracing::info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); + info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); // create the executed block data let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { From a5ba1a20eacbe7e27d3630aca6874d72dbc20dad Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 12 May 2025 11:23:20 +0100 Subject: [PATCH 064/262] Remove monitoring tx task --- crates/builder/op-rbuilder/src/main.rs | 9 - crates/builder/op-rbuilder/src/monitoring.rs | 194 ------------------- 2 files changed, 203 deletions(-) delete mode 100644 crates/builder/op-rbuilder/src/monitoring.rs diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 6ceb936f..05038136 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -18,7 +18,6 @@ pub mod generator; mod integration; mod metrics; mod monitor_tx_pool; -mod monitoring; #[cfg(feature = "flashblocks")] pub mod payload_builder; #[cfg(not(feature = "flashblocks"))] @@ -68,14 +67,6 @@ fn main() { ); } - ctx.task_executor.spawn_critical( - "monitoring", - Box::pin(async move { - let monitoring = Monitoring::new(builder_signer); - let _ = monitoring.run_with_stream(new_canonical_blocks).await; - }), - ); - Ok(()) }) .launch() diff --git a/crates/builder/op-rbuilder/src/monitoring.rs b/crates/builder/op-rbuilder/src/monitoring.rs deleted file mode 100644 index 40c42db1..00000000 --- a/crates/builder/op-rbuilder/src/monitoring.rs +++ /dev/null @@ -1,194 +0,0 @@ -use alloy_consensus::{Transaction, TxReceipt}; -use alloy_primitives::U256; -use futures_util::{Stream, StreamExt, TryStreamExt}; -use reth::core::primitives::SignedTransaction; -use reth_chain_state::CanonStateNotification; -use reth_exex::{ExExContext, ExExEvent}; -use reth_node_api::{FullNodeComponents, NodeTypes}; -use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; -use reth_primitives::{Block, RecoveredBlock}; -use reth_provider::{Chain, ExecutionOutcome}; -use tracing::{info, warn}; - -use crate::{metrics::OpRBuilderMetrics, tx_signer::Signer}; - -const OP_BUILDER_TX_PREFIX: &[u8] = b"Block Number:"; - -pub struct Monitoring { - builder_signer: Option, - metrics: OpRBuilderMetrics, -} - -impl Monitoring { - pub fn new(builder_signer: Option) -> Self { - Self { - builder_signer, - metrics: Default::default(), - } - } - - #[allow(dead_code)] - pub async fn run_with_exex(mut self, mut ctx: ExExContext) -> eyre::Result<()> - where - Node: FullNodeComponents>, - { - // Process all new chain state notifications - while let Some(notification) = ctx.notifications.try_next().await? { - if let Some(reverted_chain) = notification.reverted_chain() { - self.revert(&reverted_chain).await?; - } - if let Some(committed_chain) = notification.committed_chain() { - self.commit(&committed_chain).await?; - ctx.events - .send(ExExEvent::FinishedHeight(committed_chain.tip().num_hash()))?; - } - } - - Ok(()) - } - - pub async fn run_with_stream(mut self, mut events: St) -> eyre::Result<()> - where - St: Stream> + Unpin + 'static, - { - while let Some(event) = events.next().await { - if let Some(reverted) = event.reverted() { - self.revert(&reverted).await?; - } - - let committed = event.committed(); - self.commit(&committed).await?; - } - - Ok(()) - } - - /// Process a new chain commit. - /// - /// This function decodes the builder tx and then emits metrics - async fn commit(&mut self, chain: &Chain) -> eyre::Result<()> { - info!("Processing new chain commit"); - let blocks = decode_chain_into_builder_txs(chain, self.builder_signer); - - for (block, has_builder_tx) in blocks { - if has_builder_tx { - self.metrics.inc_builder_landed_blocks(); - self.metrics.set_last_landed_block_height(block.number); - info!( - block_number = block.number, - "Committed block built by builder" - ); - } else { - self.metrics.inc_builder_landed_blocks_missed(); - } - } - - let num_reverted_tx = decode_chain_into_reverted_txs(chain); - self.metrics.inc_num_reverted_tx(num_reverted_tx); - - let builder_balance = - decode_state_into_builder_balance(chain.execution_outcome(), self.builder_signer) - .and_then(|balance| { - balance - .to_string() - .parse::() - .map_err(|e| { - warn!("Failed to parse builder balance: {}", e); - e - }) - .ok() - }); - if let Some(balance) = builder_balance { - self.metrics.set_builder_balance(balance); - } - - Ok(()) - } - - /// Process a chain revert. - /// - /// This function decodes all transactions in the block, updates the metrics for builder built blocks - async fn revert(&mut self, chain: &Chain) -> eyre::Result<()> { - info!("Processing new chain revert"); - let mut blocks = decode_chain_into_builder_txs(chain, self.builder_signer); - // Reverse the order of txs to start reverting from the tip - blocks.reverse(); - - if let Some((block, _)) = blocks.last() { - self.metrics.set_last_landed_block_height(block.number - 1); - } - - for (block, has_builder_tx) in blocks { - if has_builder_tx { - self.metrics.dec_builder_landed_blocks(); - info!( - block_number = block.number, - "Reverted block built by builder" - ); - } - } - - Ok(()) - } -} - -/// Decode chain of blocks and filter list to builder txs -fn decode_chain_into_builder_txs( - chain: &Chain, - builder_signer: Option, -) -> Vec<(&RecoveredBlock>, bool)> { - chain - // Get all blocks and receipts - .blocks_and_receipts() - // Get all receipts - .map(|(block, receipts)| { - let has_builder_tx = - block - .body() - .transactions - .iter() - .zip(receipts.iter()) - .any(move |(tx, receipt)| { - receipt.status() - && tx.input().starts_with(OP_BUILDER_TX_PREFIX) - && tx.recover_signer().is_ok_and(|signer| { - builder_signer.is_some_and(|bs| signer == bs.address) - }) - }); - (block, has_builder_tx) - }) - .collect() -} - -/// Decode chain of blocks and check if any transactions has reverted -fn decode_chain_into_reverted_txs(chain: &Chain) -> usize { - chain - // Get all blocks and receipts - .blocks_and_receipts() - // Get all receipts - .map(|(block, receipts)| { - block - .body() - .transactions - .iter() - .zip(receipts.iter()) - .filter(|(_, receipt)| !receipt.status()) - .count() - }) - .sum() -} - -/// Decode state and find the last builder balance -fn decode_state_into_builder_balance( - execution_outcome: &ExecutionOutcome, - builder_signer: Option, -) -> Option { - builder_signer.and_then(|signer| { - execution_outcome - .bundle - .state - .iter() - .find(|(address, _)| *address == &signer.address) - .and_then(|(_, account)| account.info.as_ref().map(|info| info.balance)) - }) -} From a3ca4af9c40ba86f52db8b084f8fcee16f6ecc9a Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 12 May 2025 11:24:26 +0100 Subject: [PATCH 065/262] Remove more things --- crates/builder/op-rbuilder/src/main.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 05038136..f08b2ce0 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,6 +1,4 @@ use clap::Parser; -use monitoring::Monitoring; -use reth::providers::CanonStateSubscriptions; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; use reth_optimism_node::node::OpAddOnsBuilder; use reth_optimism_node::OpNode; @@ -54,9 +52,6 @@ fn main() { .build(), ) .on_node_started(move |ctx| { - let new_canonical_blocks = ctx.provider().canonical_state_stream(); - let builder_signer = builder_args.builder_signer; - if builder_args.log_pool_transactions { tracing::info!("Logging pool transactions"); ctx.task_executor.spawn_critical( From 89689bd6658cdd951361c5bc79274b2530e701d4 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 12 May 2025 11:28:42 +0100 Subject: [PATCH 066/262] Remove more stuff --- crates/builder/op-rbuilder/src/metrics.rs | 38 +---------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 83659aa9..2c8b179a 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -1,17 +1,9 @@ -use reth_metrics::{metrics::Counter, metrics::Gauge, metrics::Histogram, Metrics}; +use reth_metrics::{metrics::Counter, metrics::Histogram, Metrics}; /// op-rbuilder metrics #[derive(Metrics, Clone)] #[metrics(scope = "op_rbuilder")] pub struct OpRBuilderMetrics { - /// Builder balance of the last block - pub builder_balance: Gauge, - /// Number of builder landed blocks - pub builder_landed_blocks: Gauge, - /// Last built block height - pub last_landed_block_height: Gauge, - /// Number of blocks the builder did not land - pub builder_landed_blocks_missed: Gauge, /// Block built success pub block_built_success: Counter, /// Number of flashblocks added to block (Total per block) @@ -54,32 +46,4 @@ pub struct OpRBuilderMetrics { pub tx_simulation_duration: Histogram, /// Byte size of transactions pub tx_byte_size: Histogram, - /// Number of reverted transactions - pub num_reverted_tx: Counter, -} - -impl OpRBuilderMetrics { - pub fn inc_num_reverted_tx(&self, num_reverted_tx: usize) { - self.num_reverted_tx.increment(num_reverted_tx as u64); - } - - pub fn inc_builder_landed_blocks(&self) { - self.builder_landed_blocks.increment(1); - } - - pub fn dec_builder_landed_blocks(&self) { - self.builder_landed_blocks.decrement(1); - } - - pub fn inc_builder_landed_blocks_missed(&self) { - self.builder_landed_blocks_missed.increment(1); - } - - pub fn set_last_landed_block_height(&self, height: u64) { - self.last_landed_block_height.set(height as f64); - } - - pub fn set_builder_balance(&self, balance: f64) { - self.builder_balance.set(balance); - } } From dd2250e508e2791c04dafffdaa07abf6ecd63e85 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 12 May 2025 12:29:29 +0100 Subject: [PATCH 067/262] Last clean --- .../builder/op-rbuilder/src/integration/integration_test.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 86c2b033..2189bad2 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -91,11 +91,6 @@ mod tests { } } - // there must be a line logging the monitoring transaction - op_rbuilder - .find_log_line("Committed block built by builder") - .await?; - Ok(()) } From fefefd811ab5dedbf3229ec36c667562e0bbe744 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 12 May 2025 13:21:44 +0100 Subject: [PATCH 068/262] more cleaning --- .../op-rbuilder/src/integration/integration_test.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 2189bad2..da08ffb1 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -448,10 +448,6 @@ mod tests { .expect("receipt"); } } - // there must be a line logging the monitoring transaction - op_rbuilder - .find_log_line("Processing new chain commit") // no builder tx for flashblocks builder - .await?; // check there's 10 flashblocks log lines (2000ms / 200ms) op_rbuilder.find_log_line("Building flashblock 9").await?; @@ -582,10 +578,6 @@ mod tests { .expect("receipt"); } - op_rbuilder - .find_log_line("Processing new chain commit") // no builder tx for flashblocks builder - .await?; - // check there's no more than 10 flashblocks log lines (2000ms / 200ms) op_rbuilder.find_log_line("Building flashblock 9").await?; op_rbuilder From 63e087e867826d5562aa6f70a154184c86ee3a50 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 13 May 2025 18:03:29 +0600 Subject: [PATCH 069/262] Use nightly clippy --- crates/builder/op-rbuilder/src/generator.rs | 44 ++++++++++--------- .../op-rbuilder/src/integration/mod.rs | 26 +++++------ crates/builder/op-rbuilder/src/main.rs | 3 +- crates/builder/op-rbuilder/src/metrics.rs | 5 ++- crates/builder/op-rbuilder/src/tester/main.rs | 2 +- crates/builder/op-rbuilder/src/tester/mod.rs | 17 +++---- 6 files changed, 49 insertions(+), 48 deletions(-) diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 7dc9670b..7a9c48fe 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -1,23 +1,24 @@ -use futures_util::Future; -use futures_util::FutureExt; -use reth::providers::BlockReaderIdExt; -use reth::{providers::StateProviderFactory, tasks::TaskSpawner}; -use reth_basic_payload_builder::HeaderForPayload; -use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, PayloadConfig}; -use reth_node_api::PayloadBuilderAttributes; -use reth_node_api::PayloadKind; -use reth_payload_builder::PayloadJobGenerator; -use reth_payload_builder::{KeepPayloadJobAlive, PayloadBuilderError, PayloadJob}; +use futures_util::{Future, FutureExt}; +use reth::{ + providers::{BlockReaderIdExt, StateProviderFactory}, + tasks::TaskSpawner, +}; +use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, HeaderForPayload, PayloadConfig}; +use reth_node_api::{PayloadBuilderAttributes, PayloadKind}; +use reth_payload_builder::{ + KeepPayloadJobAlive, PayloadBuilderError, PayloadJob, PayloadJobGenerator, +}; use reth_payload_primitives::BuiltPayload; use reth_primitives_traits::HeaderTy; use reth_revm::cached::CachedReads; -use std::sync::{Arc, Mutex}; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; -use tokio::sync::oneshot; -use tokio::sync::Notify; -use tokio::time::Duration; -use tokio::time::Sleep; +use std::{ + sync::{Arc, Mutex}, + time::{SystemTime, UNIX_EPOCH}, +}; +use tokio::{ + sync::{oneshot, Notify}, + time::{Duration, Sleep}, +}; use tokio_util::sync::CancellationToken; use tracing::info; @@ -424,14 +425,15 @@ mod tests { use reth::tasks::TokioTaskExecutor; use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_node_api::NodePrimitives; - use reth_optimism_payload_builder::payload::OpPayloadBuilderAttributes; - use reth_optimism_payload_builder::OpPayloadPrimitives; + use reth_optimism_payload_builder::{payload::OpPayloadBuilderAttributes, OpPayloadPrimitives}; use reth_optimism_primitives::OpPrimitives; use reth_primitives::SealedBlock; use reth_provider::test_utils::MockEthProvider; use reth_testing_utils::generators::{random_block_range, BlockRangeParams}; - use tokio::task; - use tokio::time::{sleep, Duration}; + use tokio::{ + task, + time::{sleep, Duration}, + }; #[tokio::test] async fn test_block_cell_wait_for_value() { diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index d62b0d48..4cc0cbdc 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -1,32 +1,32 @@ use alloy_consensus::TxEip1559; -use alloy_eips::BlockNumberOrTag; -use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718}; +use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718, BlockNumberOrTag}; use alloy_provider::{Identity, Provider, ProviderBuilder}; use op_alloy_consensus::OpTypedTransaction; use op_alloy_network::Optimism; use op_rbuilder::OpRbuilderConfig; use op_reth::OpRethConfig; use parking_lot::Mutex; -use std::cmp::max; -use std::collections::HashSet; -use std::future::Future; -use std::net::TcpListener; -use std::path::Path; -use std::sync::LazyLock; use std::{ + cmp::max, + collections::HashSet, fs::{File, OpenOptions}, + future::Future, io, io::prelude::*, - path::PathBuf, + net::TcpListener, + path::{Path, PathBuf}, process::{Child, Command}, + sync::LazyLock, time::{Duration, SystemTime}, }; use time::{format_description, OffsetDateTime}; use tokio::time::sleep; use uuid::Uuid; -use crate::tester::{BlockGenerator, EngineApi}; -use crate::tx_signer::Signer; +use crate::{ + tester::{BlockGenerator, EngineApi}, + tx_signer::Signer, +}; /// Default JWT token for testing purposes pub const DEFAULT_JWT_TOKEN: &str = @@ -92,7 +92,7 @@ pub async fn poll_logs( impl ServiceInstance { pub fn new(name: String, test_dir: PathBuf) -> Self { - let log_path = test_dir.join(format!("{}.log", name)); + let log_path = test_dir.join(format!("{name}.log")); Self { process: None, log_path, @@ -167,7 +167,7 @@ impl IntegrationFramework { let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); test_dir.push("../../integration_logs"); - test_dir.push(format!("{}_{}", date_format, test_name)); + test_dir.push(format!("{date_format}_{test_name}")); std::fs::create_dir_all(&test_dir).map_err(|_| IntegrationError::SetupError)?; diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index f08b2ce0..edeebf42 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,7 +1,6 @@ use clap::Parser; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; -use reth_optimism_node::node::OpAddOnsBuilder; -use reth_optimism_node::OpNode; +use reth_optimism_node::{node::OpAddOnsBuilder, OpNode}; #[cfg(feature = "flashblocks")] use payload_builder::CustomOpPayloadBuilder; diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 2c8b179a..82c48b93 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -1,4 +1,7 @@ -use reth_metrics::{metrics::Counter, metrics::Histogram, Metrics}; +use reth_metrics::{ + metrics::{Counter, Histogram}, + Metrics, +}; /// op-rbuilder metrics #[derive(Metrics, Clone)] diff --git a/crates/builder/op-rbuilder/src/tester/main.rs b/crates/builder/op-rbuilder/src/tester/main.rs index f5094490..d81b078d 100644 --- a/crates/builder/op-rbuilder/src/tester/main.rs +++ b/crates/builder/op-rbuilder/src/tester/main.rs @@ -67,7 +67,7 @@ async fn main() -> eyre::Result<()> { generator.init().await?; let block_hash = generator.deposit(address, amount).await?; - println!("Deposit transaction included in block: {}", block_hash); + println!("Deposit transaction included in block: {block_hash}"); Ok(()) } } diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 5fb18352..5bcc1b8f 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -82,7 +82,7 @@ impl EngineApi { pub fn new_with_port(port: u16) -> Result> { Self::builder() - .with_url(&format!("http://localhost:{}", port)) + .with_url(&format!("http://localhost:{port}")) .build() } @@ -179,13 +179,13 @@ pub async fn generate_genesis(output: Option) -> eyre::Result<()> { let timestamp = chrono::Utc::now().timestamp(); if let Some(config) = genesis.as_object_mut() { // Assuming timestamp is at the root level - adjust path as needed - config["timestamp"] = Value::String(format!("0x{:x}", timestamp)); + config["timestamp"] = Value::String(format!("0x{timestamp:x}")); } // Write the result to the output file if let Some(output) = output { std::fs::write(&output, serde_json::to_string_pretty(&genesis)?)?; - println!("Generated genesis file at: {}", output); + println!("Generated genesis file at: {output}"); } else { println!("{}", serde_json::to_string_pretty(&genesis)?); } @@ -241,10 +241,7 @@ impl BlockGenerator { // Initialize flashblocks service if let Some(flashblocks_endpoint) = &self.flashblocks_endpoint { - println!( - "Initializing flashblocks service at {}", - flashblocks_endpoint - ); + println!("Initializing flashblocks service at {flashblocks_endpoint}"); self.flashblocks_service = Some(Flashblocks::run( flashblocks_endpoint.to_string(), @@ -273,7 +270,7 @@ impl BlockGenerator { let mut latest_hash = latest_validation_block.header.hash; for i in (latest_validation_block.header.number + 1)..=latest_block.header.number { - println!("syncing block {}", i); + println!("syncing block {i}"); let block = self .engine_api @@ -512,7 +509,7 @@ pub async fn run_system( block_time_secs: u64, flashblocks_endpoint: Option, ) -> eyre::Result<()> { - println!("Validation: {}", validation); + println!("Validation: {validation}"); let engine_api = EngineApi::new("http://localhost:4444").unwrap(); let validation_api = if validation { @@ -535,6 +532,6 @@ pub async fn run_system( loop { println!("Generating new block..."); let block_hash = generator.generate_block().await?; - println!("Generated block: {}", block_hash); + println!("Generated block: {block_hash}"); } } From 3eb4d3bb0e177eceea4a54805d97fef6431ca052 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 15:05:43 +0100 Subject: [PATCH 070/262] Add variable builder deadline --- crates/builder/op-rbuilder/Cargo.toml | 1 + crates/builder/op-rbuilder/src/args.rs | 3 +++ crates/builder/op-rbuilder/src/generator.rs | 7 ++++++- crates/builder/op-rbuilder/src/main.rs | 1 + .../builder/op-rbuilder/src/payload_builder_vanilla.rs | 9 ++++++++- 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 413fb358..f98d5b53 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -88,6 +88,7 @@ uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } tokio-tungstenite = "0.26.2" rand = "0.9.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } +humantime = "2.1.0" # `flashblocks` branch rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "60885346d4cf7f241de82790478195747433d472" } diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index 1e99c7fd..c12a1eb8 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -41,4 +41,7 @@ pub struct OpRbuilderArgs { /// Signals whether to log pool transaction events #[arg(long = "builder.log-pool-transactions", default_value = "false")] pub log_pool_transactions: bool, + /// How much time extra to wait for the block building job to complete and not get garbage collected + #[arg(long = "builder.extra-block-deadline", default_value = "10s", value_parser = humantime::parse_duration)] + pub extra_block_deadline: std::time::Duration, } diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 7dc9670b..a4895e94 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -71,6 +71,8 @@ pub struct BlockPayloadJobGenerator { ensure_only_one_payload: bool, /// The last payload being processed last_payload: Arc>, + /// The extra block deadline in seconds + extra_block_deadline: std::time::Duration, } // === impl EmptyBlockPayloadJobGenerator === @@ -84,6 +86,7 @@ impl BlockPayloadJobGenerator { config: BasicPayloadJobGeneratorConfig, builder: Builder, ensure_only_one_payload: bool, + extra_block_deadline: std::time::Duration, ) -> Self { Self { client, @@ -92,6 +95,7 @@ impl BlockPayloadJobGenerator { builder, ensure_only_one_payload, last_payload: Arc::new(Mutex::new(CancellationToken::new())), + extra_block_deadline, } } } @@ -164,7 +168,7 @@ where // "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. - let deadline = job_deadline(attributes.timestamp()) + Duration::from_millis(5000); + let deadline = job_deadline(attributes.timestamp()) + self.extra_block_deadline; let deadline = Box::pin(tokio::time::sleep(deadline)); let config = PayloadConfig::new(Arc::new(parent_header.clone()), attributes); @@ -632,6 +636,7 @@ mod tests { config, builder.clone(), false, + std::time::Duration::from_secs(1), ); // this is not nice but necessary diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index f08b2ce0..4bec9f88 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -41,6 +41,7 @@ fn main() { .with_types::() .with_components(op_node.components().payload(CustomOpPayloadBuilder::new( builder_args.builder_signer, + builder_args.extra_block_deadline, builder_args.flashblocks_ws_url, builder_args.chain_block_time, builder_args.flashblock_block_time, diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 407593b0..5ee8ff0d 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -76,6 +76,7 @@ const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10; #[non_exhaustive] pub struct CustomOpPayloadBuilder { builder_signer: Option, + extra_block_deadline: std::time::Duration, #[cfg(feature = "flashblocks")] flashblocks_ws_url: String, #[cfg(feature = "flashblocks")] @@ -103,11 +104,15 @@ impl CustomOpPayloadBuilder { #[cfg(not(feature = "flashblocks"))] pub fn new( builder_signer: Option, + extra_block_deadline: std::time::Duration, _flashblocks_ws_url: String, _chain_block_time: u64, _flashblock_block_time: u64, ) -> Self { - Self { builder_signer } + Self { + builder_signer, + extra_block_deadline, + } } } @@ -161,6 +166,7 @@ where pool: Pool, ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); + let extra_block_deadline = self.extra_block_deadline.clone(); let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); @@ -170,6 +176,7 @@ where payload_job_config, payload_builder, false, + extra_block_deadline, ); let (payload_service, payload_builder) = From bdf827a7047a86b0fdaab9a5a0df1dc822a1bedd Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 15:14:15 +0100 Subject: [PATCH 071/262] Fix lint --- crates/builder/op-rbuilder/src/payload_builder_vanilla.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 5ee8ff0d..7d992d7d 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -166,7 +166,7 @@ where pool: Pool, ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); - let extra_block_deadline = self.extra_block_deadline.clone(); + let extra_block_deadline = self.extra_block_deadline; let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); From 52c7ff19373edaf0a8e1e961945c3d2a3d3ce6d9 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 15:22:44 +0100 Subject: [PATCH 072/262] Fix on FB --- crates/builder/op-rbuilder/src/payload_builder.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index b39057e5..66e3c517 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -99,11 +99,13 @@ pub struct CustomOpPayloadBuilder { flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, + extra_block_deadline: std::time::Duration, } impl CustomOpPayloadBuilder { pub fn new( builder_signer: Option, + extra_block_deadline: std::time::Duration, flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, @@ -113,6 +115,7 @@ impl CustomOpPayloadBuilder { flashblocks_ws_url, chain_block_time, flashblock_block_time, + extra_block_deadline, } } } @@ -168,6 +171,7 @@ where pool: Pool, ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); + let extra_block_deadline = self.extra_block_deadline; let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); @@ -177,6 +181,7 @@ where payload_job_config, payload_builder, true, + extra_block_deadline, ); let (payload_service, payload_builder) = From 7bb2cff08aaa3d4d2638fbb114ba39e04b2d791d Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 15:30:18 +0100 Subject: [PATCH 073/262] Remove dep --- crates/builder/op-rbuilder/Cargo.toml | 1 - crates/builder/op-rbuilder/src/args.rs | 4 ++-- crates/builder/op-rbuilder/src/main.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index f98d5b53..413fb358 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -88,7 +88,6 @@ uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } tokio-tungstenite = "0.26.2" rand = "0.9.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } -humantime = "2.1.0" # `flashblocks` branch rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "60885346d4cf7f241de82790478195747433d472" } diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index c12a1eb8..98ef747a 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -42,6 +42,6 @@ pub struct OpRbuilderArgs { #[arg(long = "builder.log-pool-transactions", default_value = "false")] pub log_pool_transactions: bool, /// How much time extra to wait for the block building job to complete and not get garbage collected - #[arg(long = "builder.extra-block-deadline", default_value = "10s", value_parser = humantime::parse_duration)] - pub extra_block_deadline: std::time::Duration, + #[arg(long = "builder.extra-block-deadline-secs", default_value = "20")] + pub extra_block_deadline_secs: u64, } diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 4bec9f88..a1c4a695 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -41,7 +41,7 @@ fn main() { .with_types::() .with_components(op_node.components().payload(CustomOpPayloadBuilder::new( builder_args.builder_signer, - builder_args.extra_block_deadline, + std::time::Duration::from_secs(builder_args.extra_block_deadline_secs), builder_args.flashblocks_ws_url, builder_args.chain_block_time, builder_args.flashblock_block_time, From e30875b2c6bef90d24fa5c317c3d5560006bf167 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 18:19:51 +0100 Subject: [PATCH 074/262] Change to info --- crates/builder/op-rbuilder/src/monitor_tx_pool.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs index 4e7268cf..4fdbf231 100644 --- a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs +++ b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs @@ -12,14 +12,14 @@ pub async fn monitor_tx_pool(mut new_transactions: AllTransactionsEvents) { match event { FullTransactionEvent::Pending(hash) => { - debug!( + info!( tx_hash = hash.to_string(), kind = "pending", "Transaction event received" ) } FullTransactionEvent::Queued(hash) => { - debug!( + info!( tx_hash = hash.to_string(), kind = "queued", "Transaction event received" @@ -28,7 +28,7 @@ fn transaction_event_log(event: FullTransactionEvent) { FullTransactionEvent::Mined { tx_hash, block_hash, - } => debug!( + } => info!( tx_hash = tx_hash.to_string(), kind = "mined", block_hash = block_hash.to_string(), @@ -37,21 +37,21 @@ fn transaction_event_log(event: FullTransactionEvent) { FullTransactionEvent::Replaced { transaction, replaced_by, - } => debug!( + } => info!( tx_hash = transaction.hash().to_string(), kind = "replaced", replaced_by = replaced_by.to_string(), "Transaction event received" ), FullTransactionEvent::Discarded(hash) => { - debug!( + info!( tx_hash = hash.to_string(), kind = "discarded", "Transaction event received" ) } FullTransactionEvent::Invalid(hash) => { - debug!( + info!( tx_hash = hash.to_string(), kind = "invalid", "Transaction event received" From 4eabe5fe4fd71c6ba348f7ba70b7e5bea8042c78 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:00:06 +0100 Subject: [PATCH 075/262] Add flag to enable revert protection --- crates/builder/op-rbuilder/src/args.rs | 3 + crates/builder/op-rbuilder/src/generator.rs | 12 ++++ .../src/integration/integration_test.rs | 39 ++++++++++- .../op-rbuilder/src/integration/mod.rs | 66 +++++++++++++++++-- .../src/integration/op_rbuilder.rs | 12 ++++ crates/builder/op-rbuilder/src/main.rs | 1 + .../op-rbuilder/src/payload_builder.rs | 1 + .../src/payload_builder_vanilla.rs | 27 ++++++-- 8 files changed, 151 insertions(+), 10 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args.rs index 98ef747a..0238a376 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args.rs @@ -44,4 +44,7 @@ pub struct OpRbuilderArgs { /// How much time extra to wait for the block building job to complete and not get garbage collected #[arg(long = "builder.extra-block-deadline-secs", default_value = "20")] pub extra_block_deadline_secs: u64, + /// Whether to enable revert protection by default + #[arg(long = "builder.enable-revert-protection", default_value = "false")] + pub enable_revert_protection: bool, } diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 3407710f..0fa83e4d 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -74,6 +74,8 @@ pub struct BlockPayloadJobGenerator { last_payload: Arc>, /// The extra block deadline in seconds extra_block_deadline: std::time::Duration, + /// Whether to enable revert protection + enable_revert_protection: bool, } // === impl EmptyBlockPayloadJobGenerator === @@ -88,6 +90,7 @@ impl BlockPayloadJobGenerator { builder: Builder, ensure_only_one_payload: bool, extra_block_deadline: std::time::Duration, + enable_revert_protection: bool, ) -> Self { Self { client, @@ -97,6 +100,7 @@ impl BlockPayloadJobGenerator { ensure_only_one_payload, last_payload: Arc::new(Mutex::new(CancellationToken::new())), extra_block_deadline, + enable_revert_protection, } } } @@ -182,6 +186,7 @@ where cancel: cancel_token, deadline, build_complete: None, + enable_revert_protection: self.enable_revert_protection, }; job.spawn_build_job(); @@ -214,6 +219,8 @@ where pub(crate) cancel: CancellationToken, pub(crate) deadline: Pin>, // Add deadline pub(crate) build_complete: Option>>, + /// Block building options + pub(crate) enable_revert_protection: bool, } impl PayloadJob for BlockPayloadJob @@ -256,6 +263,8 @@ pub struct BuildArguments { pub config: PayloadConfig>, /// A marker that can be used to cancel the job. pub cancel: CancellationToken, + /// Whether to enable revert protection + pub enable_revert_protection: bool, } /// A [PayloadJob] is a future that's being polled by the `PayloadBuilderService` @@ -271,6 +280,7 @@ where let payload_config = self.config.clone(); let cell = self.cell.clone(); let cancel = self.cancel.clone(); + let enable_revert_protection = self.enable_revert_protection; let (tx, rx) = oneshot::channel(); self.build_complete = Some(rx); @@ -280,6 +290,7 @@ where cached_reads: Default::default(), config: payload_config, cancel, + enable_revert_protection, }; let result = builder.try_build(args, cell); @@ -639,6 +650,7 @@ mod tests { builder.clone(), false, std::time::Duration::from_secs(1), + false, ); // this is not nice but necessary diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index da08ffb1..32aa15e9 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -94,6 +94,42 @@ mod tests { Ok(()) } + #[tokio::test] + #[cfg(not(feature = "flashblocks"))] + async fn integration_test_revert_protection_disabled() -> eyre::Result<()> { + let harness = TestHarness::new("integration_test_revert_protection_disabled").await; + let mut generator = harness.block_generator().await?; + + let txn1 = harness.send_valid_transaction().await?; + let txn2 = harness.send_revert_transaction().await?; + let pending_txn = vec![txn1, txn2]; + + let block_hash = generator.generate_block().await?; + + // the transactions should be included in the block now + let pending_txn = { + let mut transaction_hashes = Vec::new(); + for txn in pending_txn { + let txn_hash = txn.with_timeout(None).watch().await?; + transaction_hashes.push(txn_hash); + } + transaction_hashes + }; + + // validate that all the transaction hashes are included in the block + let provider = harness.provider()?; + let block = provider + .get_block_by_hash(block_hash) + .await? + .expect("block"); + + for txn in pending_txn { + assert!(block.transactions.hashes().any(|hash| hash == txn)); + } + + Ok(()) + } + #[tokio::test] #[cfg(not(feature = "flashblocks"))] async fn integration_test_revert_protection() -> eyre::Result<()> { @@ -115,7 +151,8 @@ mod tests { .auth_rpc_port(1244) .network_port(1245) .http_port(1248) - .with_builder_private_key(BUILDER_PRIVATE_KEY); + .with_builder_private_key(BUILDER_PRIVATE_KEY) + .with_revert_protection(true); // create the validation reth node let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index 4cc0cbdc..f2eba091 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -1,6 +1,9 @@ use alloy_consensus::TxEip1559; use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718, BlockNumberOrTag}; -use alloy_provider::{Identity, Provider, ProviderBuilder}; +use alloy_primitives::{hex, TxHash, TxNonce}; +use alloy_provider::{ + Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, +}; use op_alloy_consensus::OpTypedTransaction; use op_alloy_network::Optimism; use op_rbuilder::OpRbuilderConfig; @@ -269,7 +272,9 @@ impl TestHarness { } } - pub async fn send_valid_transaction(&self) -> eyre::Result<()> { + pub async fn send_valid_transaction( + &self, + ) -> eyre::Result> { // Get builder's address let known_wallet = Signer::try_from_secret(BUILDER_PRIVATE_KEY.parse()?)?; let builder_address = known_wallet.address; @@ -303,11 +308,64 @@ impl TestHarness { ..Default::default() }); let signed_tx = known_wallet.sign_tx(tx_request)?; - let _ = provider + let pending_tx = provider .send_raw_transaction(signed_tx.encoded_2718().as_slice()) .await?; - Ok(()) + Ok(pending_tx) + } + + pub async fn send_revert_transaction( + &self, + ) -> eyre::Result> { + // TODO: Merge this with send_valid_transaction + // Get builder's address + let known_wallet = Signer::try_from_secret(BUILDER_PRIVATE_KEY.parse()?)?; + let builder_address = known_wallet.address; + + let url = format!("http://localhost:{}", self.builder_http_port); + let provider = + ProviderBuilder::::default().on_http(url.parse()?); + + // Get current nonce includeing the ones from the txpool + let nonce = provider + .get_transaction_count(builder_address) + .pending() + .await?; + + let latest_block = provider + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .unwrap(); + + let base_fee = max( + latest_block.header.base_fee_per_gas.unwrap(), + MIN_PROTOCOL_BASE_FEE, + ); + + // Transaction from builder should succeed + let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: 901, + nonce, + gas_limit: 210000, + max_fee_per_gas: base_fee.into(), + input: hex!("60006000fd").into(), // PUSH1 0x00 PUSH1 0x00 REVERT + ..Default::default() + }); + let signed_tx = known_wallet.sign_tx(tx_request)?; + let pending_tx = provider + .send_raw_transaction(signed_tx.encoded_2718().as_slice()) + .await?; + + Ok(pending_tx) + } + + pub fn provider(&self) -> eyre::Result> { + let url = format!("http://localhost:{}", self.builder_http_port); + let provider = + ProviderBuilder::::default().on_http(url.parse()?); + + Ok(provider) } pub async fn block_generator(&self) -> eyre::Result { diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index 28f9532d..8ac6b938 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -27,6 +27,7 @@ pub struct OpRbuilderConfig { flashblocks_ws_url: Option, chain_block_time: Option, flashbots_block_time: Option, + with_revert_protection: Option, } impl OpRbuilderConfig { @@ -64,6 +65,11 @@ impl OpRbuilderConfig { self } + pub fn with_revert_protection(mut self, revert_protection: bool) -> Self { + self.with_revert_protection = Some(revert_protection); + self + } + pub fn with_flashblocks_ws_url(mut self, url: &str) -> Self { self.flashblocks_ws_url = Some(url.to_string()); self @@ -114,6 +120,12 @@ impl Service for OpRbuilderConfig { .arg(self.network_port.expect("network_port not set").to_string()) .arg("--ipcdisable"); + if let Some(revert_protection) = self.with_revert_protection { + if revert_protection { + cmd.arg("--builder.enable-revert-protection"); + } + } + if let Some(builder_private_key) = &self.builder_private_key { cmd.arg("--rollup.builder-secret-key") .arg(builder_private_key); diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index fe8aa372..c13c23d0 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -41,6 +41,7 @@ fn main() { .with_components(op_node.components().payload(CustomOpPayloadBuilder::new( builder_args.builder_signer, std::time::Duration::from_secs(builder_args.extra_block_deadline_secs), + builder_args.enable_revert_protection, builder_args.flashblocks_ws_url, builder_args.chain_block_time, builder_args.flashblock_block_time, diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 66e3c517..badb4e09 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -106,6 +106,7 @@ impl CustomOpPayloadBuilder { pub fn new( builder_signer: Option, extra_block_deadline: std::time::Duration, + enable_revert_protection: bool, flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 7d992d7d..fca2f270 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -77,6 +77,7 @@ const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10; pub struct CustomOpPayloadBuilder { builder_signer: Option, extra_block_deadline: std::time::Duration, + enable_revert_protection: bool, #[cfg(feature = "flashblocks")] flashblocks_ws_url: String, #[cfg(feature = "flashblocks")] @@ -105,6 +106,7 @@ impl CustomOpPayloadBuilder { pub fn new( builder_signer: Option, extra_block_deadline: std::time::Duration, + enable_revert_protection: bool, _flashblocks_ws_url: String, _chain_block_time: u64, _flashblock_block_time: u64, @@ -112,6 +114,7 @@ impl CustomOpPayloadBuilder { Self { builder_signer, extra_block_deadline, + enable_revert_protection, } } } @@ -167,6 +170,7 @@ where ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); let extra_block_deadline = self.extra_block_deadline; + let enable_revert_protection = self.enable_revert_protection; let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); @@ -177,6 +181,7 @@ where payload_builder, false, extra_block_deadline, + enable_revert_protection, ); let (payload_service, payload_builder) = @@ -351,6 +356,7 @@ where mut cached_reads, config, cancel, + enable_revert_protection, } = args; let chain_spec = self.client.chain_spec(); @@ -392,6 +398,7 @@ where cancel, builder_signer: self.builder_signer, metrics: Default::default(), + enable_revert_protection, }; let builder = OpBuilder::new(best, remove_reverted); @@ -771,6 +778,8 @@ pub struct OpPayloadBuilderCtx { pub builder_signer: Option, /// The metrics for the builder pub metrics: OpRBuilderMetrics, + /// Whether we enabled revert protection + pub enable_revert_protection: bool, } impl OpPayloadBuilderCtx @@ -1094,6 +1103,12 @@ where } }; + println!("include reverted txn: {}", result.is_success()); + println!( + "self.enable_revert_protection: {}", + self.enable_revert_protection + ); + self.metrics .tx_simulation_duration .record(tx_simulation_start_time.elapsed()); @@ -1102,11 +1117,13 @@ where if result.is_success() { num_txs_simulated_success += 1; } else { - num_txs_simulated_fail += 1; - info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - info.invalid_tx_hashes.insert(tx.tx_hash()); - continue; + if self.enable_revert_protection { + num_txs_simulated_fail += 1; + info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + info.invalid_tx_hashes.insert(tx.tx_hash()); + continue; + } } // add gas used by the transaction to cumulative gas used, before creating the From 08df5cb928e29d5887162d26ef3ed0d07f23801c Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:06:44 +0100 Subject: [PATCH 076/262] Update --- crates/builder/op-rbuilder/src/monitor_tx_pool.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs index 4fdbf231..eba1cdde 100644 --- a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs +++ b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs @@ -1,7 +1,7 @@ use futures_util::StreamExt; use reth_optimism_node::txpool::OpPooledTransaction; use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent}; -use tracing::debug; +use tracing::info; pub async fn monitor_tx_pool(mut new_transactions: AllTransactionsEvents) { while let Some(event) = new_transactions.next().await { @@ -13,6 +13,7 @@ fn transaction_event_log(event: FullTransactionEvent) { match event { FullTransactionEvent::Pending(hash) => { info!( + target = "monitoring", tx_hash = hash.to_string(), kind = "pending", "Transaction event received" @@ -20,6 +21,7 @@ fn transaction_event_log(event: FullTransactionEvent) { } FullTransactionEvent::Queued(hash) => { info!( + target = "monitoring", tx_hash = hash.to_string(), kind = "queued", "Transaction event received" @@ -29,6 +31,7 @@ fn transaction_event_log(event: FullTransactionEvent) { tx_hash, block_hash, } => info!( + target = "monitoring", tx_hash = tx_hash.to_string(), kind = "mined", block_hash = block_hash.to_string(), @@ -38,6 +41,7 @@ fn transaction_event_log(event: FullTransactionEvent) { transaction, replaced_by, } => info!( + target = "monitoring", tx_hash = transaction.hash().to_string(), kind = "replaced", replaced_by = replaced_by.to_string(), @@ -45,6 +49,7 @@ fn transaction_event_log(event: FullTransactionEvent) { ), FullTransactionEvent::Discarded(hash) => { info!( + target = "monitoring", tx_hash = hash.to_string(), kind = "discarded", "Transaction event received" @@ -52,6 +57,7 @@ fn transaction_event_log(event: FullTransactionEvent) { } FullTransactionEvent::Invalid(hash) => { info!( + target = "monitoring", tx_hash = hash.to_string(), kind = "invalid", "Transaction event received" From 7714cf84a531310cb2016f1db4057c1998c1628f Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:18:44 +0100 Subject: [PATCH 077/262] add metric --- crates/builder/op-rbuilder/src/payload_builder_vanilla.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index fca2f270..65458bb0 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1117,8 +1117,8 @@ where if result.is_success() { num_txs_simulated_success += 1; } else { + num_txs_simulated_fail += 1; if self.enable_revert_protection { - num_txs_simulated_fail += 1; info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); info.invalid_tx_hashes.insert(tx.tx_hash()); From 6259db1c7060c7fd3fc310fe6c7ed933a36a4269 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:20:04 +0100 Subject: [PATCH 078/262] Fix --- crates/builder/op-rbuilder/src/integration/mod.rs | 2 +- crates/builder/op-rbuilder/src/payload_builder.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index f2eba091..28d55eed 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -1,6 +1,6 @@ use alloy_consensus::TxEip1559; use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718, BlockNumberOrTag}; -use alloy_primitives::{hex, TxHash, TxNonce}; +use alloy_primitives::hex; use alloy_provider::{ Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, }; diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index badb4e09..026236e5 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -173,6 +173,7 @@ where ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); let extra_block_deadline = self.extra_block_deadline; + let enable_revert_protection = self.enable_revert_protection; let payload_builder = self.build_payload_builder(ctx, pool).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); @@ -183,6 +184,7 @@ where payload_builder, true, extra_block_deadline, + enable_revert_protection, ); let (payload_service, payload_builder) = From 4bba2d101a18b816b829e896a45547f2ca9c4f1a Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:26:27 +0100 Subject: [PATCH 079/262] Fix lint --- crates/builder/op-rbuilder/src/payload_builder.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 026236e5..16ba2439 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -100,6 +100,7 @@ pub struct CustomOpPayloadBuilder { chain_block_time: u64, flashblock_block_time: u64, extra_block_deadline: std::time::Duration, + enable_revert_protection: bool, } impl CustomOpPayloadBuilder { @@ -117,6 +118,7 @@ impl CustomOpPayloadBuilder { chain_block_time, flashblock_block_time, extra_block_deadline, + enable_revert_protection, } } } From a73fc53c3a064b95b2944c5ca3c6f7f2d4793392 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:56:02 +0100 Subject: [PATCH 080/262] Remove print statement --- crates/builder/op-rbuilder/src/payload_builder_vanilla.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 65458bb0..c96f4f20 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1103,12 +1103,6 @@ where } }; - println!("include reverted txn: {}", result.is_success()); - println!( - "self.enable_revert_protection: {}", - self.enable_revert_protection - ); - self.metrics .tx_simulation_duration .record(tx_simulation_start_time.elapsed()); From f7db9ba0f4995adb8d5306e4cb53f2d3e3abf659 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:56:37 +0100 Subject: [PATCH 081/262] Remove print --- crates/builder/op-rbuilder/src/payload_builder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 16ba2439..69bc998b 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -1174,7 +1174,6 @@ where // A sequencer's block should never contain blob or deposit transactions from the pool. if tx.is_eip4844() || tx.is_deposit() { - println!("B"); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } From 3beb8bfff80422547b48257a4a91bf6b6b83cf23 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:49:15 +0100 Subject: [PATCH 082/262] Add e2e test for monitor txn --- .../src/integration/integration_test.rs | 49 +++++++++++++++++-- .../op-rbuilder/src/integration/mod.rs | 42 +++++++++++----- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 32aa15e9..d23f786f 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -2,7 +2,8 @@ mod tests { use crate::{ integration::{ - op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, TestHarness, + op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, + TestHarness, TestHarnessBuilder, }, tester::{BlockGenerator, EngineApi}, tx_signer::Signer, @@ -94,10 +95,48 @@ mod tests { Ok(()) } + #[tokio::test] + #[cfg(not(feature = "flashblocks"))] + async fn integration_test_monitor_transaction_drops() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("integration_test_monitor_transaction_drops") + .with_revert_protection() + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + // send 10 reverting transactions + let mut pending_txn = Vec::new(); + for _ in 0..10 { + let txn = harness.send_revert_transaction().await?; + pending_txn.push(txn); + } + + // generate 10 blocks + for _ in 0..10 { + let block_hash = generator.generate_block().await?; + + // query the block and the transactions inside the block + let block = harness + .provider()? + .get_block_by_hash(block_hash) + .await? + .expect("block"); + + // blocks should only include two + println!("block: {:?}", block.transactions.len()); + } + + Ok(()) + } + #[tokio::test] #[cfg(not(feature = "flashblocks"))] async fn integration_test_revert_protection_disabled() -> eyre::Result<()> { - let harness = TestHarness::new("integration_test_revert_protection_disabled").await; + let harness = TestHarnessBuilder::new("integration_test_revert_protection_disabled") + .build() + .await?; + let mut generator = harness.block_generator().await?; let txn1 = harness.send_valid_transaction().await?; @@ -386,13 +425,15 @@ mod tests { #[tokio::test] #[cfg(not(feature = "flashblocks"))] async fn integration_test_get_payload_close_to_fcu() -> eyre::Result<()> { - let test_harness = TestHarness::new("integration_test_get_payload_close_to_fcu").await; + let test_harness = TestHarnessBuilder::new("integration_test_get_payload_close_to_fcu") + .build() + .await?; let mut block_generator = test_harness.block_generator().await?; // add some transactions to the pool so that the builder is busy when we send the fcu/getPayload requests for _ in 0..10 { // Note, for this test it is okay if they are not valid - test_harness.send_valid_transaction().await?; + let _ = test_harness.send_valid_transaction().await?; } // TODO: In the fail case scenario, this hangs forever, but it should return an error diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index 28d55eed..fa365800 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -219,16 +219,26 @@ impl Drop for IntegrationFramework { const BUILDER_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; -pub struct TestHarness { - _framework: IntegrationFramework, - builder_auth_rpc_port: u16, - builder_http_port: u16, - validator_auth_rpc_port: u16, +pub struct TestHarnessBuilder { + name: String, + use_revert_protection: bool, } -impl TestHarness { - pub async fn new(name: &str) -> Self { - let mut framework = IntegrationFramework::new(name).unwrap(); +impl TestHarnessBuilder { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + use_revert_protection: false, + } + } + + pub fn with_revert_protection(mut self) -> Self { + self.use_revert_protection = true; + self + } + + pub async fn build(self) -> eyre::Result { + let mut framework = IntegrationFramework::new(&self.name).unwrap(); // we are going to use a genesis file pre-generated before the test let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -245,7 +255,8 @@ impl TestHarness { .auth_rpc_port(builder_auth_rpc_port) .network_port(get_available_port()) .http_port(builder_http_port) - .with_builder_private_key(BUILDER_PRIVATE_KEY); + .with_builder_private_key(BUILDER_PRIVATE_KEY) + .with_revert_protection(self.use_revert_protection); // create the validation reth node @@ -264,14 +275,23 @@ impl TestHarness { .await .unwrap(); - Self { + Ok(TestHarness { _framework: framework, builder_auth_rpc_port, builder_http_port, validator_auth_rpc_port, - } + }) } +} + +pub struct TestHarness { + _framework: IntegrationFramework, + builder_auth_rpc_port: u16, + builder_http_port: u16, + validator_auth_rpc_port: u16, +} +impl TestHarness { pub async fn send_valid_transaction( &self, ) -> eyre::Result> { From ca4e8944541efef36fcda486c2914d337e6dd4ca Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 19:55:42 +0100 Subject: [PATCH 083/262] Partial --- crates/builder/op-rbuilder/src/integration/integration_test.rs | 2 ++ crates/builder/op-rbuilder/src/integration/op_rbuilder.rs | 2 ++ crates/builder/op-rbuilder/src/integration/op_reth.rs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index d23f786f..e23dced4 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -98,6 +98,8 @@ mod tests { #[tokio::test] #[cfg(not(feature = "flashblocks"))] async fn integration_test_monitor_transaction_drops() -> eyre::Result<()> { + // This test ensures that the transactions that get reverted an not included in the block + // are emitted as a log on the builder. let harness = TestHarnessBuilder::new("integration_test_monitor_transaction_drops") .with_revert_protection() .build() diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index 8ac6b938..3bc99f71 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -116,6 +116,8 @@ impl Service for OpRbuilderConfig { .arg("--datadir") .arg(self.data_dir.as_ref().expect("data_dir not set")) .arg("--disable-discovery") + .arg("--color") + .arg("never") .arg("--port") .arg(self.network_port.expect("network_port not set").to_string()) .arg("--ipcdisable"); diff --git a/crates/builder/op-rbuilder/src/integration/op_reth.rs b/crates/builder/op-rbuilder/src/integration/op_reth.rs index b809f479..154ba119 100644 --- a/crates/builder/op-rbuilder/src/integration/op_reth.rs +++ b/crates/builder/op-rbuilder/src/integration/op_reth.rs @@ -80,6 +80,8 @@ impl Service for OpRethConfig { .arg("--datadir") .arg(self.data_dir.as_ref().expect("data_dir not set")) .arg("--disable-discovery") + .arg("--color") + .arg("never") .arg("--port") .arg(self.network_port.expect("network_port not set").to_string()) .arg("--ipcdisable"); From 7777de6a1f271e3dd1e919fc9329e6ddf8470da2 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 20:06:48 +0100 Subject: [PATCH 084/262] Add test --- .../src/integration/integration_test.rs | 20 +++++++++++++++++-- .../op-rbuilder/src/integration/mod.rs | 6 +++++- .../src/integration/op_rbuilder.rs | 1 + 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index e23dced4..dffc7d80 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -125,8 +125,24 @@ mod tests { .await? .expect("block"); - // blocks should only include two - println!("block: {:?}", block.transactions.len()); + // blocks should only include two transactions (deposit + builder) + assert_eq!(block.transactions.len(), 2); + } + + // check that the builder emitted logs for the reverted transactions + // with the monitoring logic + // TODO: this is not ideal, lets find a different way to detect this + // Each time a transaction is dropped, it emits a log like this + // 'Transaction event received target="monitoring" tx_hash="" kind="discarded"' + let builder_logs = std::fs::read_to_string(harness.builder_log_path)?; + + for txn in pending_txn { + let txn_log = format!( + "Transaction event received target=\"monitoring\" tx_hash=\"{}\" kind=\"discarded\"", + txn.tx_hash() + ); + + assert!(builder_logs.contains(txn_log.as_str())); } Ok(()) diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index fa365800..d901cd81 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -270,16 +270,19 @@ impl TestHarnessBuilder { framework.start("op-reth", &reth).await.unwrap(); - let _ = framework + let builder = framework .start("op-rbuilder", &op_rbuilder_config) .await .unwrap(); + let builder_log_path = builder.log_path.clone(); + Ok(TestHarness { _framework: framework, builder_auth_rpc_port, builder_http_port, validator_auth_rpc_port, + builder_log_path, }) } } @@ -289,6 +292,7 @@ pub struct TestHarness { builder_auth_rpc_port: u16, builder_http_port: u16, validator_auth_rpc_port: u16, + builder_log_path: PathBuf, } impl TestHarness { diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index 3bc99f71..4170a89c 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -118,6 +118,7 @@ impl Service for OpRbuilderConfig { .arg("--disable-discovery") .arg("--color") .arg("never") + .arg("--builder.log-pool-transactions") .arg("--port") .arg(self.network_port.expect("network_port not set").to_string()) .arg("--ipcdisable"); From 95fc529e3471d43a8e22014d55157baf4b206d78 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 13 May 2025 20:32:06 +0100 Subject: [PATCH 085/262] Fix lint --- crates/builder/op-rbuilder/src/integration/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index d901cd81..11cd6d75 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -292,6 +292,7 @@ pub struct TestHarness { builder_auth_rpc_port: u16, builder_http_port: u16, validator_auth_rpc_port: u16, + #[allow(dead_code)] // I think this is due to some feature flag conflicts builder_log_path: PathBuf, } From ce5be3d5cf44da22ec1935e3b48db979904ef90c Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 14 May 2025 12:01:49 +0100 Subject: [PATCH 086/262] Test new generator --- crates/builder/op-rbuilder/Cargo.toml | 1 + .../src/payload_builder_vanilla.rs | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 413fb358..7b459437 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -40,6 +40,7 @@ reth-payload-util.workspace = true reth-transaction-pool.workspace = true reth-testing-utils.workspace = true reth-optimism-forks.workspace = true +reth-node-builder.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index c96f4f20..572fca30 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -224,6 +224,41 @@ where } } +pub struct OpPayloadBuilderVanillaBuilder { + builder_signer: Option, +} + +impl PayloadBuilderBuilder for OpPayloadBuilderVanillaBuilder +where + Node: FullNodeTypes< + Types: NodeTypes< + Payload = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + >, + Txs: OpPayloadTransactions + Send + Sync + 'static, +{ + /// Payload builder implementation. + type PayloadBuilder = OpPayloadBuilderVanilla; + + /// Spawns the payload service and returns the handle to it. + /// + /// The [`BuilderContext`] is provided to allow access to the node's configuration. + async fn build_payload_builder( + self, + ctx: &BuilderContext, + pool: Pool, + ) -> eyre::Result { + Ok(OpPayloadBuilderVanilla::new( + OpEvmConfig::optimism(ctx.chain_spec()), + self.builder_signer, + pool, + ctx.provider().clone(), + )) + } +} + /// Optimism's payload builder #[derive(Debug, Clone)] pub struct OpPayloadBuilderVanilla { From 59a3e3c11b5a41c125b745246c489f0feae50a77 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 14 May 2025 13:37:44 +0100 Subject: [PATCH 087/262] More changes --- .../src/integration/integration_test.rs | 20 +++ .../src/payload_builder_vanilla.rs | 141 +++++++++++------- crates/builder/op-rbuilder/src/tester/main.rs | 5 + crates/builder/op-rbuilder/src/tester/mod.rs | 19 ++- 4 files changed, 120 insertions(+), 65 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index dffc7d80..4608905c 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -461,6 +461,26 @@ mod tests { Ok(()) } + #[tokio::test] + #[cfg(not(feature = "flashblocks"))] + async fn integration_test_transaction_flood_no_sleep() -> eyre::Result<()> { + // This test validates that if we flood the builder with many transactions + // and we request short block times, the builder can still eventually resolve all the transactions + let mut test_harness = + TestHarnessBuilder::new("integration_test_transaction_flood_no_sleep") + .build() + .await?; + + // Send 500 valid transactions to the builder + let mut transactions = Vec::new(); + for _ in 0..500 { + let tx = test_harness.send_valid_transaction().await?; + transactions.push(tx); + } + + Ok(()) + } + #[tokio::test] #[cfg(feature = "flashblocks")] async fn integration_test_chain_produces_blocks() -> eyre::Result<()> { diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 572fca30..14ce2012 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -25,7 +25,8 @@ use reth::{ payload::PayloadBuilderHandle, }; use reth_basic_payload_builder::{ - BasicPayloadJobGeneratorConfig, BuildOutcome, BuildOutcomeKind, PayloadConfig, + BasicPayloadJobGeneratorConfig, BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour, + PayloadConfig, }; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; @@ -35,6 +36,7 @@ use reth_evm::{ }; use reth_execution_types::ExecutionOutcome; use reth_node_api::{NodePrimitives, NodeTypes, TxTy}; +use reth_node_builder::components::BasicPayloadServiceBuilder; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; @@ -51,7 +53,7 @@ use reth_optimism_txpool::OpPooledTx; use reth_payload_builder::PayloadBuilderService; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; -use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; +use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; use reth_primitives::{BlockBody, SealedHeader}; use reth_primitives_traits::{proofs, Block as _, RecoveredBlock, SignedTransaction}; use reth_provider::{ @@ -110,12 +112,12 @@ impl CustomOpPayloadBuilder { _flashblocks_ws_url: String, _chain_block_time: u64, _flashblock_block_time: u64, - ) -> Self { - Self { + ) -> BasicPayloadServiceBuilder { + BasicPayloadServiceBuilder::new(CustomOpPayloadBuilder { builder_signer, extra_block_deadline, enable_revert_protection, - } + }) } } @@ -145,6 +147,7 @@ where self.builder_signer, pool, ctx.provider().clone(), + self.enable_revert_protection, )) } } @@ -199,63 +202,74 @@ where impl reth_basic_payload_builder::PayloadBuilder for OpPayloadBuilderVanilla where - Pool: Clone + Send + Sync, - Client: Clone + Send + Sync, - Txs: Clone + Send + Sync, + Pool: TransactionPool>, + Client: StateProviderFactory + ChainSpecProvider + Clone, + Txs: OpPayloadTransactions, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; fn try_build( &self, - _args: reth_basic_payload_builder::BuildArguments, + args: reth_basic_payload_builder::BuildArguments, ) -> Result, PayloadBuilderError> { - unimplemented!() + 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, + enable_revert_protection: self.enable_revert_protection, + cancel: CancellationToken::new(), + }; + + self.build_payload( + args, + |attrs| { + #[allow(clippy::unit_arg)] + self.best_transactions + .best_transactions(pool.clone(), attrs) + }, + |hashes| { + #[allow(clippy::unit_arg)] + self.best_transactions.remove_invalid(pool.clone(), hashes) + }, + ) + } + + 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< + config: reth_basic_payload_builder::PayloadConfig< Self::Attributes, reth_basic_payload_builder::HeaderForPayload, >, ) -> Result { - unimplemented!() - } -} - -pub struct OpPayloadBuilderVanillaBuilder { - builder_signer: Option, -} - -impl PayloadBuilderBuilder for OpPayloadBuilderVanillaBuilder -where - Node: FullNodeTypes< - Types: NodeTypes< - Payload = OpEngineTypes, - ChainSpec = OpChainSpec, - Primitives = OpPrimitives, - >, - >, - Txs: OpPayloadTransactions + Send + Sync + 'static, -{ - /// Payload builder implementation. - type PayloadBuilder = OpPayloadBuilderVanilla; - - /// Spawns the payload service and returns the handle to it. - /// - /// The [`BuilderContext`] is provided to allow access to the node's configuration. - async fn build_payload_builder( - self, - ctx: &BuilderContext, - pool: Pool, - ) -> eyre::Result { - Ok(OpPayloadBuilderVanilla::new( - OpEvmConfig::optimism(ctx.chain_spec()), - self.builder_signer, - pool, - ctx.provider().clone(), - )) + let args = BuildArguments { + config, + cached_reads: Default::default(), + cancel: Default::default(), + enable_revert_protection: false, + }; + self.build_payload( + args, + |_| NoopPayloadTransactions::::default(), + |_| {}, + )? + .into_payload() + .ok_or_else(|| PayloadBuilderError::MissingPayload) } } @@ -277,6 +291,8 @@ pub struct OpPayloadBuilderVanilla { pub best_transactions: Txs, /// The metrics for the builder pub metrics: OpRBuilderMetrics, + /// Whether we enable revert protection + pub enable_revert_protection: bool, } impl OpPayloadBuilderVanilla { @@ -286,8 +302,16 @@ impl OpPayloadBuilderVanilla { builder_signer: Option, pool: Pool, client: Client, + enable_revert_protection: bool, ) -> Self { - Self::with_builder_config(evm_config, builder_signer, pool, client, Default::default()) + Self::with_builder_config( + evm_config, + builder_signer, + pool, + client, + Default::default(), + enable_revert_protection, + ) } pub fn with_builder_config( @@ -296,6 +320,7 @@ impl OpPayloadBuilderVanilla { pool: Pool, client: Client, config: OpBuilderConfig, + enable_revert_protection: bool, ) -> Self { Self { pool, @@ -305,6 +330,7 @@ impl OpPayloadBuilderVanilla { best_transactions: (), metrics: Default::default(), builder_signer, + enable_revert_protection, } } } @@ -563,13 +589,18 @@ impl OpBuilder<'_, Txs> { ctx.metrics .transaction_pool_fetch_duration .record(best_txs_start_time.elapsed()); - ctx.execute_best_transactions( - &mut info, - state, - best_txs, - block_gas_limit, - block_da_limit, - )?; + if ctx + .execute_best_transactions( + &mut info, + state, + best_txs, + block_gas_limit, + block_da_limit, + )? + .is_some() + { + return Ok(BuildOutcomeKind::Cancelled); + } } // Add builder tx to the block diff --git a/crates/builder/op-rbuilder/src/tester/main.rs b/crates/builder/op-rbuilder/src/tester/main.rs index d81b078d..c1041d98 100644 --- a/crates/builder/op-rbuilder/src/tester/main.rs +++ b/crates/builder/op-rbuilder/src/tester/main.rs @@ -30,6 +30,9 @@ enum Commands { #[clap(long, short, action)] flashblocks_endpoint: Option, + + #[clap(long, action, default_value = "false")] + no_sleep: bool, }, /// Deposit funds to the system Deposit { @@ -51,12 +54,14 @@ async fn main() -> eyre::Result<()> { no_tx_pool, block_time_secs, flashblocks_endpoint, + no_sleep, } => { run_system( validation, no_tx_pool, block_time_secs, flashblocks_endpoint, + no_sleep, ) .await } diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tester/mod.rs index 5bcc1b8f..08bbb792 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tester/mod.rs @@ -20,10 +20,7 @@ use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; use rollup_boost::{Flashblocks, FlashblocksService}; use serde_json::Value; -use std::{ - str::FromStr, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::str::FromStr; /// Helper for engine api operations pub struct EngineApi { @@ -205,6 +202,7 @@ pub struct BlockGenerator { latest_hash: B256, no_tx_pool: bool, block_time_secs: u64, + timestamp: u64, // flashblocks service flashblocks_endpoint: Option, flashblocks_service: Option, @@ -223,6 +221,7 @@ impl BlockGenerator { validation_api, latest_hash: B256::ZERO, // temporary value no_tx_pool, + timestamp: 0, block_time_secs, flashblocks_endpoint, flashblocks_service: None, @@ -233,6 +232,7 @@ impl BlockGenerator { pub async fn init(&mut self) -> eyre::Result { let latest_block = self.engine_api.latest().await?.expect("block not found"); self.latest_hash = latest_block.header.hash; + self.timestamp = latest_block.header.timestamp; // Sync validation node if it exists if let Some(validation_api) = &self.validation_api { @@ -340,11 +340,7 @@ impl BlockGenerator { block_building_delay_secs: u64, no_sleep: bool, // TODO: Change this, too many parameters we can tweak here to put as a function arguments ) -> eyre::Result { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - let timestamp = timestamp + self.block_time_secs; + let timestamp = self.timestamp + self.block_time_secs; // Add L1 block info as the first transaction in every L2 block // This deposit transaction contains L1 block metadata required by the L2 chain @@ -464,6 +460,8 @@ impl BlockGenerator { // Update internal state self.latest_hash = new_block_hash; + self.timestamp = payload.execution_payload.timestamp(); + Ok(new_block_hash) } @@ -508,6 +506,7 @@ pub async fn run_system( no_tx_pool: bool, block_time_secs: u64, flashblocks_endpoint: Option, + no_sleep: bool, ) -> eyre::Result<()> { println!("Validation: {validation}"); @@ -531,7 +530,7 @@ pub async fn run_system( // Infinite loop generating blocks loop { println!("Generating new block..."); - let block_hash = generator.generate_block().await?; + let block_hash = generator.submit_payload(None, 0, no_sleep).await?; println!("Generated block: {block_hash}"); } } From 47d606d033a63f69440c7a0ae839778e13e9d7a1 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 14 May 2025 14:40:27 +0100 Subject: [PATCH 088/262] add integration tet --- .../src/integration/integration_test.rs | 30 ++++++++++++++----- .../src/integration/op_rbuilder.rs | 3 +- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs index 4608905c..1a251d23 100644 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ b/crates/builder/op-rbuilder/src/integration/integration_test.rs @@ -466,16 +466,30 @@ mod tests { async fn integration_test_transaction_flood_no_sleep() -> eyre::Result<()> { // This test validates that if we flood the builder with many transactions // and we request short block times, the builder can still eventually resolve all the transactions - let mut test_harness = - TestHarnessBuilder::new("integration_test_transaction_flood_no_sleep") - .build() - .await?; - // Send 500 valid transactions to the builder - let mut transactions = Vec::new(); - for _ in 0..500 { + let test_harness = TestHarnessBuilder::new("integration_test_transaction_flood_no_sleep") + .build() + .await?; + + let mut block_generator = test_harness.block_generator().await?; + let provider = test_harness.provider()?; + + // Send 200 valid transactions to the builder + // More than this and there is an issue with the RPC endpoint not being able to handle the load + let mut transactions = vec![]; + for _ in 0..200 { let tx = test_harness.send_valid_transaction().await?; - transactions.push(tx); + let tx_hash = *tx.tx_hash(); + transactions.push(tx_hash); + } + + // After a 10 blocks all the transactions should be included in a block + for _ in 0..10 { + block_generator.submit_payload(None, 0, true).await.unwrap(); + } + + for tx in transactions { + provider.get_transaction_receipt(tx).await?; } Ok(()) diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs index 4170a89c..505fe520 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs @@ -121,7 +121,8 @@ impl Service for OpRbuilderConfig { .arg("--builder.log-pool-transactions") .arg("--port") .arg(self.network_port.expect("network_port not set").to_string()) - .arg("--ipcdisable"); + .arg("--ipcdisable") + .arg("-vvvv"); if let Some(revert_protection) = self.with_revert_protection { if revert_protection { From 4ec80356ec11c194c938a3493b4b22c7ac482dc5 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 15 May 2025 15:26:18 +0700 Subject: [PATCH 089/262] Was using wrong static for jemalloc (#51) --- crates/builder/op-rbuilder/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index c13c23d0..b1beffd9 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -28,7 +28,7 @@ use monitor_tx_pool::monitor_tx_pool; // Prefer jemalloc for performance reasons. #[cfg(all(feature = "jemalloc", unix))] #[global_allocator] -static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; fn main() { Cli::::parse() From 075ac32d1d661a414ed9f7e67cafebe6f459b73f Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 15 May 2025 15:26:44 +0700 Subject: [PATCH 090/262] Add version metric to op-rbuilder (#52) --- crates/builder/op-rbuilder/Cargo.toml | 4 + crates/builder/op-rbuilder/build.rs | 125 ++++++++++++++++++++++ crates/builder/op-rbuilder/src/main.rs | 14 +++ crates/builder/op-rbuilder/src/metrics.rs | 54 +++++++++- 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 crates/builder/op-rbuilder/build.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 7b459437..490bc4f6 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -96,6 +96,10 @@ rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "608853 [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } +[build-dependencies] +vergen = { workspace = true, features = ["build", "cargo", "emit_and_set"] } +vergen-git2.workspace = true + [features] default = ["jemalloc"] diff --git a/crates/builder/op-rbuilder/build.rs b/crates/builder/op-rbuilder/build.rs new file mode 100644 index 00000000..3e8c774f --- /dev/null +++ b/crates/builder/op-rbuilder/build.rs @@ -0,0 +1,125 @@ +// Taken from reth [https://github.com/paradigmxyz/reth/blob/main/crates/node/core/build.rs] +// The MIT License (MIT) +// +// Copyright (c) 2022-2025 Reth Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +use std::{env, error::Error}; +use vergen::{BuildBuilder, CargoBuilder, Emitter}; +use vergen_git2::Git2Builder; + +fn main() -> Result<(), Box> { + let mut emitter = Emitter::default(); + + let build_builder = BuildBuilder::default().build_timestamp(true).build()?; + + emitter.add_instructions(&build_builder)?; + + let cargo_builder = CargoBuilder::default() + .features(true) + .target_triple(true) + .build()?; + + emitter.add_instructions(&cargo_builder)?; + + let git_builder = Git2Builder::default() + .describe(false, true, None) + .dirty(true) + .sha(false) + .build()?; + + emitter.add_instructions(&git_builder)?; + + emitter.emit_and_set()?; + let sha = env::var("VERGEN_GIT_SHA")?; + let sha_short = &sha[0..7]; + + let is_dirty = env::var("VERGEN_GIT_DIRTY")? == "true"; + // > git describe --always --tags + // if not on a tag: v0.2.0-beta.3-82-g1939939b + // if on a tag: v0.2.0-beta.3 + let not_on_tag = env::var("VERGEN_GIT_DESCRIBE")?.ends_with(&format!("-g{sha_short}")); + let version_suffix = if is_dirty || not_on_tag { "-dev" } else { "" }; + println!("cargo:rustc-env=OP_RBUILDER_VERSION_SUFFIX={version_suffix}"); + + // Set short SHA + println!("cargo:rustc-env=VERGEN_GIT_SHA_SHORT={}", &sha[..8]); + + // Set the build profile + let out_dir = env::var("OUT_DIR").unwrap(); + let profile = out_dir.rsplit(std::path::MAIN_SEPARATOR).nth(3).unwrap(); + println!("cargo:rustc-env=OP_RBUILDER_BUILD_PROFILE={profile}"); + + // Set formatted version strings + let pkg_version = env!("CARGO_PKG_VERSION"); + + // The short version information for op-rbuilder. + // - The latest version from Cargo.toml + // - The short SHA of the latest commit. + // Example: 0.1.0 (defa64b2) + println!( + "cargo:rustc-env=OP_RBUILDER_SHORT_VERSION={pkg_version}{version_suffix} ({sha_short})" + ); + + // LONG_VERSION + // The long version information for op-rbuilder. + // + // - The latest version from Cargo.toml + version suffix (if any) + // - The full SHA of the latest commit + // - The build datetime + // - The build features + // - The build profile + // + // Example: + // + // ```text + // Version: 0.1.0 + // Commit SHA: defa64b2 + // Build Timestamp: 2023-05-19T01:47:19.815651705Z + // Build Features: jemalloc + // Build Profile: maxperf + // ``` + println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_0=Version: {pkg_version}{version_suffix}"); + println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_1=Commit SHA: {sha}"); + println!( + "cargo:rustc-env=OP_RBUILDER_LONG_VERSION_2=Build Timestamp: {}", + env::var("VERGEN_BUILD_TIMESTAMP")? + ); + println!( + "cargo:rustc-env=OP_RBUILDER_LONG_VERSION_3=Build Features: {}", + env::var("VERGEN_CARGO_FEATURES")? + ); + println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_4=Build Profile: {profile}"); + + // The version information for op-rbuilder formatted for P2P (devp2p). + // - The latest version from Cargo.toml + // - The target triple + // + // Example: op-rbuilder/v0.1.0-alpha.1-428a6dc2f/aarch64-apple-darwin + println!( + "cargo:rustc-env=OP_RBUILDER_P2P_CLIENT_VERSION={}", + format_args!( + "op-rbuilder/v{pkg_version}-{sha_short}/{}", + env::var("VERGEN_CARGO_TARGET_TRIPLE")? + ) + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index b1beffd9..09dbd073 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -23,6 +23,10 @@ mod primitives; #[cfg(test)] mod tester; mod tx_signer; +use metrics::{ + VersionInfo, BUILD_PROFILE_NAME, CARGO_PKG_VERSION, VERGEN_BUILD_TIMESTAMP, + VERGEN_CARGO_FEATURES, VERGEN_CARGO_TARGET_TRIPLE, VERGEN_GIT_SHA, +}; use monitor_tx_pool::monitor_tx_pool; // Prefer jemalloc for performance reasons. @@ -31,6 +35,15 @@ use monitor_tx_pool::monitor_tx_pool; static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; fn main() { + let version = VersionInfo { + version: CARGO_PKG_VERSION, + build_timestamp: VERGEN_BUILD_TIMESTAMP, + cargo_features: VERGEN_CARGO_FEATURES, + git_sha: VERGEN_GIT_SHA, + target_triple: VERGEN_CARGO_TARGET_TRIPLE, + build_profile: BUILD_PROFILE_NAME, + }; + Cli::::parse() .run(|builder, builder_args| async move { let rollup_args = builder_args.rollup_args; @@ -53,6 +66,7 @@ fn main() { .build(), ) .on_node_started(move |ctx| { + version.register_version_metrics(); if builder_args.log_pool_transactions { tracing::info!("Logging pool transactions"); ctx.task_executor.spawn_critical( diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 82c48b93..87b79579 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -1,8 +1,26 @@ use reth_metrics::{ - metrics::{Counter, Histogram}, + metrics::{gauge, Counter, Histogram}, Metrics, }; +/// The latest version from Cargo.toml. +pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// The 8 character short SHA of the latest commit. +pub const VERGEN_GIT_SHA: &str = env!("VERGEN_GIT_SHA_SHORT"); + +/// The build timestamp. +pub const VERGEN_BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP"); + +/// The target triple. +pub const VERGEN_CARGO_TARGET_TRIPLE: &str = env!("VERGEN_CARGO_TARGET_TRIPLE"); + +/// The build features. +pub const VERGEN_CARGO_FEATURES: &str = env!("VERGEN_CARGO_FEATURES"); + +/// The build profile name. +pub const BUILD_PROFILE_NAME: &str = env!("OP_RBUILDER_BUILD_PROFILE"); + /// op-rbuilder metrics #[derive(Metrics, Clone)] #[metrics(scope = "op_rbuilder")] @@ -50,3 +68,37 @@ pub struct OpRBuilderMetrics { /// Byte size of transactions pub tx_byte_size: Histogram, } + +/// Contains version information for the application. +#[derive(Debug, Clone)] +pub struct VersionInfo { + /// The version of the application. + pub version: &'static str, + /// The build timestamp of the application. + pub build_timestamp: &'static str, + /// The cargo features enabled for the build. + pub cargo_features: &'static str, + /// The Git SHA of the build. + pub git_sha: &'static str, + /// The target triple for the build. + pub target_triple: &'static str, + /// The build profile (e.g., debug or release). + pub build_profile: &'static str, +} + +impl VersionInfo { + /// This exposes reth's version information over prometheus. + pub fn register_version_metrics(&self) { + let labels: [(&str, &str); 6] = [ + ("version", self.version), + ("build_timestamp", self.build_timestamp), + ("cargo_features", self.cargo_features), + ("git_sha", self.git_sha), + ("target_triple", self.target_triple), + ("build_profile", self.build_profile), + ]; + + let gauge = gauge!("builder_info", &labels); + gauge.set(1); + } +} From b32cabd93d6cfe6743cf11543edc313ad17ff5d0 Mon Sep 17 00:00:00 2001 From: Karim Agha Date: Thu, 15 May 2025 10:58:01 +0200 Subject: [PATCH 091/262] Add a --playground flag on op-rbuilder to start with the flags required to run the builder on playground (#49) * Fix existing build warnings * Add a --playground flag on op-rbuilder to start with the flags required to run the builder on playground Issue: https://github.com/flashbots/op-rbuilder/issues/9 This change adds the ability to autoconfigure op-rbuilder to run a locally deployed builder-playground. Follow the instructions on how to start a builder playground under this repo: https://github.com/SozinM/builder-playground/tree/msozin/feat/flashblocks Now we can use the following startup parameters for op-rbuilder: - `./op-rbuilder node --builder.playground` This will start using the default $HOME/.playground/devnet directory - `./op-rbuilder node --builder.playground=` Will use the provided path as the working directory of the playground * Fixed build warning * added justfile recepe * Removing flashblocks and using vanilla builder for now * Updated readme --- crates/builder/op-rbuilder/Cargo.toml | 6 + crates/builder/op-rbuilder/src/args/mod.rs | 5 + .../op-rbuilder/src/{args.rs => args/op.rs} | 20 + .../op-rbuilder/src/args/playground.rs | 387 ++++++++++++++++++ crates/builder/op-rbuilder/src/main.rs | 2 + .../op-rbuilder/src/payload_builder.rs | 2 +- .../src/payload_builder_vanilla.rs | 9 +- .../src/primitives/reth/execution.rs | 7 - .../op-rbuilder/src/primitives/reth/mod.rs | 2 +- 9 files changed, 430 insertions(+), 10 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/args/mod.rs rename crates/builder/op-rbuilder/src/{args.rs => args/op.rs} (78%) create mode 100644 crates/builder/op-rbuilder/src/args/playground.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 490bc4f6..862f2a66 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -17,6 +17,7 @@ reth-optimism-evm.workspace = true reth-optimism-consensus.workspace = true reth-optimism-primitives.workspace = true reth-optimism-txpool.workspace = true +reth-cli.workspace = true reth-cli-util.workspace = true reth-payload-primitives.workspace = true reth-evm.workspace = true @@ -38,6 +39,7 @@ reth-rpc-layer.workspace = true reth-payload-builder-primitives.workspace = true reth-payload-util.workspace = true reth-transaction-pool.workspace = true +reth-network-peers.workspace = true reth-testing-utils.workspace = true reth-optimism-forks.workspace = true reth-node-builder.workspace = true @@ -82,6 +84,7 @@ serde_json.workspace = true tokio-util.workspace = true thiserror.workspace = true parking_lot.workspace = true +url.workspace = true time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" @@ -89,6 +92,9 @@ uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } tokio-tungstenite = "0.26.2" rand = "0.9.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } +shellexpand = "3.1" +serde_yaml = { version = "0.9" } + # `flashblocks` branch rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "60885346d4cf7f241de82790478195747433d472" } diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs new file mode 100644 index 00000000..25475206 --- /dev/null +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -0,0 +1,5 @@ +mod op; +mod playground; + +pub use op::OpRbuilderArgs; +pub use playground::CliExt; diff --git a/crates/builder/op-rbuilder/src/args.rs b/crates/builder/op-rbuilder/src/args/op.rs similarity index 78% rename from crates/builder/op-rbuilder/src/args.rs rename to crates/builder/op-rbuilder/src/args/op.rs index 0238a376..5818a5c1 100644 --- a/crates/builder/op-rbuilder/src/args.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -3,6 +3,8 @@ //! Copied from OptimismNode to allow easy extension. //! clap [Args](clap::Args) for optimism rollup configuration +use std::path::PathBuf; + use reth_optimism_node::args::RollupArgs; use crate::tx_signer::Signer; @@ -41,10 +43,28 @@ pub struct OpRbuilderArgs { /// Signals whether to log pool transaction events #[arg(long = "builder.log-pool-transactions", default_value = "false")] pub log_pool_transactions: bool, + /// How much time extra to wait for the block building job to complete and not get garbage collected #[arg(long = "builder.extra-block-deadline-secs", default_value = "20")] pub extra_block_deadline_secs: u64, /// Whether to enable revert protection by default #[arg(long = "builder.enable-revert-protection", default_value = "false")] pub enable_revert_protection: bool, + + #[arg( + long = "builder.playground", + num_args = 0..=1, + default_missing_value = "$HOME/.playground/devnet/", + value_parser = expand_path, + env = "PLAYGROUND_DIR", + )] + pub playground: Option, +} + +fn expand_path(s: &str) -> Result { + shellexpand::full(s) + .map_err(|e| format!("expansion error for `{s}`: {e}"))? + .into_owned() + .parse() + .map_err(|e| format!("invalid path after expansion: {e}")) } diff --git a/crates/builder/op-rbuilder/src/args/playground.rs b/crates/builder/op-rbuilder/src/args/playground.rs new file mode 100644 index 00000000..6898eb9e --- /dev/null +++ b/crates/builder/op-rbuilder/src/args/playground.rs @@ -0,0 +1,387 @@ +//! Autmoatic builder playground configuration. +//! +//! This module is used mostly for testing purposes. It allows op-rbuilder to +//! automatically configure itself to run against a running op-builder playground. +//! +//! To setup the playground, checkout this repository: +//! +//! https://github.com/flashbots/builder-playground +//! +//! Then run the following command: +//! +//! go run main.go cook opstack --external-builder http://host.docker.internal:4444 +//! +//! Wait until the playground is up and running, then run the following command to build +//! op-rbuilder with flashblocks support: +//! +//! cargo build --bin op-rbuilder -p op-rbuilder +//! +//! then run the following command to start op-rbuilder against the playground: +//! +//! target/debug/op-rbuilder node --builder.playground +//! +//! This will automatically try to detect the playground configuration and apply +//! it to the op-rbuilder startup settings. +//! +//! Optionally you can specify the `--builder.playground` flag with a different +//! directory to use. This is useful for testing against different playground +//! configurations. + +use super::OpRbuilderArgs; +use alloy_primitives::hex; +use clap::{parser::ValueSource, CommandFactory}; +use core::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + ops::Range, + time::Duration, +}; +use eyre::{eyre, Result}; +use reth_cli::chainspec::ChainSpecParser; +use reth_network_peers::TrustedPeer; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; +use secp256k1::SecretKey; +use serde_json::Value; +use std::{ + fs::read_to_string, + path::{Path, PathBuf}, + sync::Arc, +}; +use url::{Host, Url}; + +/// This trait is used to extend Reth's CLI with additional functionality that +/// populates the default values for the command line arguments when the user +/// specifies that they want to use the playground. +/// +/// The `--builder.playground` flag is used to populate the CLI arguments with +/// default values for running the builder against the playground environment. +/// +/// The values are populated from the default directory of the playground +/// configuration, which is `$HOME/.playground/devnet/` by default. +/// +/// Any manually specified CLI arguments by the user will override the defaults. +pub trait CliExt { + /// Populates the default values for the CLI arguments when the user specifies + /// the `--builder.playground` flag. + fn populate_defaults(self) -> Self; +} + +type Cli = reth_optimism_cli::Cli; + +impl CliExt for Cli { + fn populate_defaults(self) -> Self { + let Commands::Node(ref node_command) = self.command else { + // playground defaults are only relevant if running the node commands. + return self; + }; + + let Some(ref playground_dir) = node_command.ext.playground else { + // not running in playground mode. + return self; + }; + + let options = match PlaygroundOptions::new(playground_dir) { + Ok(options) => options, + Err(e) => exit(e), + }; + + options.apply(self) + } +} + +struct PlaygroundOptions { + /// Sets node.chain in NodeCommand + pub chain: Arc, + + /// Sets node.rpc.http_port in NodeCommand + pub http_port: u16, + + /// Sets node.rpc.auth_addr in NodeCommand + pub authrpc_addr: IpAddr, + + /// Sets node.rpc.authrpc_port in NodeCommand + pub authrpc_port: u16, + + /// Sets node.rpc.authrpc_jwtsecret in NodeCommand + pub authrpc_jwtsecret: PathBuf, + + /// Sets node.network.port in NodeCommand + pub port: u16, + + /// Sets the node.network.trusted_peers in NodeCommand + pub trusted_peer: TrustedPeer, + + /// Sets node.ext.flashblock_block_time in NodeCommand + pub chain_block_time: Duration, +} + +impl PlaygroundOptions { + /// Creates a new `PlaygroundOptions` instance with the specified genesis path. + pub fn new(path: &Path) -> Result { + if !path.exists() { + return Err(eyre!( + "Playground data directory {} does not exist", + path.display() + )); + } + + let chain = OpChainSpecParser::parse(&existing_path(path, "l2-genesis.json")?)?; + + let authrpc_addr = Ipv4Addr::UNSPECIFIED.into(); + let http_port = pick_preferred_port(2222, 3000..9999); + let authrpc_jwtsecret = existing_path(path, "jwtsecret")?.into(); + let port = pick_preferred_port(30333, 30000..65535); + let chain_block_time = extract_chain_block_time(path)?; + let authrpc_port = extract_authrpc_port(path)?; + let trusted_peer = TrustedPeer::from_secret_key( + Host::Ipv4(Ipv4Addr::LOCALHOST), + extract_trusted_peer_port(path)?, + &extract_deterministic_p2p_key(path)?, + ); + + Ok(Self { + chain, + http_port, + authrpc_addr, + authrpc_port, + authrpc_jwtsecret, + port, + trusted_peer, + chain_block_time, + }) + } + + pub fn apply(self, cli: Cli) -> Cli { + let mut cli = cli; + let Commands::Node(ref mut node) = cli.command else { + // playground defaults are only relevant if running the node commands. + return cli; + }; + + if !node.network.trusted_peers.contains(&self.trusted_peer) { + node.network.trusted_peers.push(self.trusted_peer); + } + + // populate the command line arguments only if they were never set by the user + // either via the command line or an environment variable. Otherwise, don't + // override the user provided values. + let matches = Cli::command().get_matches(); + let matches = matches + .subcommand_matches("node") + .expect("validated that we are in the node command"); + + if matches.value_source("chain").is_default() { + node.chain = self.chain; + } + + if matches.value_source("http").is_default() { + node.rpc.http = true; + } + + if matches.value_source("http_port").is_default() { + node.rpc.http_port = self.http_port; + } + + if matches.value_source("port").is_default() { + node.network.port = self.port; + } + + if matches.value_source("auth_addr").is_default() { + node.rpc.auth_addr = self.authrpc_addr; + } + + if matches.value_source("auth_port").is_default() { + node.rpc.auth_port = self.authrpc_port; + } + + if matches.value_source("auth_jwtsecret").is_default() { + node.rpc.auth_jwtsecret = Some(self.authrpc_jwtsecret); + } + + if matches.value_source("disable_discovery").is_default() { + node.network.discovery.disable_discovery = true; + } + + if matches.value_source("chain_block_time").is_default() { + node.ext.chain_block_time = self.chain_block_time.as_millis() as u64; + } + + cli + } +} + +/// Following clap's convention, a failure to parse the command line arguments +/// will result in terminating the program with a non-zero exit code. +fn exit(error: eyre::Report) -> ! { + eprintln!("{error}"); + std::process::exit(-1); +} + +fn existing_path(base: &Path, relative: &str) -> Result { + let path = base.join(relative); + if path.exists() { + Ok(path.to_string_lossy().to_string()) + } else { + Err(eyre::eyre!( + "Expected file {relative} is not present in playground directory {}", + base.display() + )) + } +} + +fn pick_random_port(range: Range) -> u16 { + use rand::Rng; + let mut rng = rand::rng(); + + loop { + // Generate a random port number between 30000 and 65535 + let port = rng.random_range(range.clone()); + + // Check if the port is already in use + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + if std::net::TcpListener::bind(socket).is_ok() { + return port; + } + } +} + +fn pick_preferred_port(preferred: u16, fallback_range: Range) -> u16 { + if !is_port_free(preferred) { + return pick_random_port(fallback_range); + } + + preferred +} + +fn is_port_free(port: u16) -> bool { + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + std::net::TcpListener::bind(socket).is_ok() +} + +fn extract_chain_block_time(basepath: &Path) -> Result { + Ok(Duration::from_secs( + serde_json::from_str::(&read_to_string(existing_path(basepath, "rollup.json")?)?)? + .get("block_time") + .and_then(|v| v.as_u64()) + .ok_or_else(|| eyre::eyre!("Missing chain_block_time in rollup.json"))?, + )) +} + +fn extract_deterministic_p2p_key(basepath: &Path) -> Result { + let key = read_to_string(existing_path(basepath, "enode-key-1.txt")?)?; + Ok(SecretKey::from_slice( + &hex::decode(key).map_err(|e| eyre!("Invalid hex key: {e}"))?, + )?) +} + +fn read_docker_compose(basepath: &Path) -> Result { + // this happens only once on statup so it's fine to read the file multiple times + let docker_compose = read_to_string(existing_path(basepath, "docker-compose.yaml")?)?; + serde_yaml::from_str(&docker_compose).map_err(|e| eyre!("Invalid docker-compose file: {e}")) +} + +fn extract_service_command_flag(basepath: &Path, service: &str, flag: &str) -> Result { + let docker_compose = read_docker_compose(basepath)?; + let args = docker_compose["services"][service]["command"] + .as_sequence() + .ok_or(eyre!( + "docker-compose.yaml is missing command line arguments for {service}" + ))? + .iter() + .map(|s| { + s.as_str().ok_or_else(|| { + eyre!("docker-compose.yaml service command line argument is not a string") + }) + }) + .collect::>>()?; + + let index = args + .iter() + .position(|arg| *arg == flag) + .ok_or_else(|| eyre!("docker_compose: {flag} not found on {service} service"))?; + + let value = args + .get(index + 1) + .ok_or_else(|| eyre!("docker_compose: {flag} value not found"))?; + + Ok(value.to_string()) +} + +fn extract_authrpc_port(basepath: &Path) -> Result { + let builder_url = extract_service_command_flag(basepath, "rollup-boost", "--builder-url")?; + let url = Url::parse(&builder_url).map_err(|e| eyre!("Invalid builder-url: {e}"))?; + url.port().ok_or_else(|| eyre!("missing builder-url port")) +} + +fn extract_trusted_peer_port(basepath: &Path) -> Result { + let docker_compose = read_docker_compose(basepath)?; + + // first we need to find the internal port of the op-geth service from the docker-compose.yaml + // command line arguments used to start the op-geth service + + let Some(opgeth_args) = docker_compose["services"]["op-geth"]["command"][1].as_str() else { + return Err(eyre!( + "docker-compose.yaml is missing command line arguments for op-geth" + )); + }; + + let opgeth_args = opgeth_args.split_whitespace().collect::>(); + let port_param_position = opgeth_args + .iter() + .position(|arg| *arg == "--port") + .ok_or_else(|| eyre!("docker_compose: --port param not found on op-geth service"))?; + + let port_value = opgeth_args + .get(port_param_position + 1) + .ok_or_else(|| eyre!("docker_compose: --port value not found"))?; + + let port_value = port_value + .parse::() + .map_err(|e| eyre!("Invalid port value: {e}"))?; + + // now we need to find the external port of the op-geth service from the docker-compose.yaml + // ports mapping used to start the op-geth service + let Some(opgeth_ports) = docker_compose["services"]["op-geth"]["ports"].as_sequence() else { + return Err(eyre!( + "docker-compose.yaml is missing ports mapping for op-geth" + )); + }; + let ports_mapping = opgeth_ports + .iter() + .map(|s| { + s.as_str().ok_or_else(|| { + eyre!("docker-compose.yaml service ports mapping in op-geth is not a string") + }) + }) + .collect::>>()?; + + // port mappings is in the format [..., "127.0.0.1:30304:30303", ...] + // we need to find the mapping that contains the port value we found earlier + // and extract the external port from it + let port_mapping = ports_mapping + .iter() + .find(|mapping| mapping.contains(&format!(":{port_value}"))) + .ok_or_else(|| { + eyre!("docker_compose: external port mapping not found for {port_value} for op-geth") + })?; + + // extract the external port from the mapping + let port_mapping = port_mapping + .split(':') + .nth(1) + .ok_or_else(|| eyre!("docker_compose: external port mapping for op-geth is not valid"))?; + + port_mapping + .parse::() + .map_err(|e| eyre!("Invalid external port mapping value for op-geth: {e}")) +} + +trait IsDefaultSource { + fn is_default(&self) -> bool; +} + +impl IsDefaultSource for Option { + fn is_default(&self) -> bool { + matches!(self, Some(ValueSource::DefaultValue)) || self.is_none() + } +} diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 09dbd073..c7db90a6 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,3 +1,4 @@ +use args::CliExt; use clap::Parser; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; use reth_optimism_node::{node::OpAddOnsBuilder, OpNode}; @@ -45,6 +46,7 @@ fn main() { }; Cli::::parse() + .populate_defaults() .run(|builder, builder_args| async move { let rollup_args = builder_args.rollup_args; diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 69bc998b..7065fc75 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -702,7 +702,7 @@ where // 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.database.as_ref()) + isthmus::withdrawals_root(execution_outcome.state(), state.database.as_ref()) .map_err(PayloadBuilderError::other)?, ) } else if ctx diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index 14ce2012..bcf99992 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1,7 +1,7 @@ use crate::{ generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, metrics::OpRBuilderMetrics, - primitives::reth::{ExecutedPayload, ExecutionInfo}, + primitives::reth::ExecutionInfo, tx_signer::Signer, }; use alloy_consensus::{ @@ -74,6 +74,13 @@ use tracing::*; // From https://eips.ethereum.org/EIPS/eip-7623 const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10; +/// Holds the state after execution +#[derive(Debug)] +pub struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, +} + #[derive(Debug, Clone, Default)] #[non_exhaustive] pub struct CustomOpPayloadBuilder { diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index ff95f8dd..6ffdd2d1 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -5,13 +5,6 @@ use reth_node_api::NodePrimitives; use reth_optimism_primitives::OpReceipt; use std::collections::HashSet; -/// Holds the state after execution -#[derive(Debug)] -pub struct ExecutedPayload { - /// Tracked execution info - pub info: ExecutionInfo, -} - #[derive(Default, Debug)] pub struct ExecutionInfo { /// All executed transactions (unrecovered). diff --git a/crates/builder/op-rbuilder/src/primitives/reth/mod.rs b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs index 4b6de4c5..916207bd 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/mod.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs @@ -1,2 +1,2 @@ mod execution; -pub use execution::{ExecutedPayload, ExecutionInfo}; +pub use execution::ExecutionInfo; From cea9ae8938b7d00bb4efc760b4086b2a66601acf Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 15 May 2025 19:33:12 +0100 Subject: [PATCH 092/262] Integration test uses genesis file (#44) * Integration test uses genesis file * Fix lint * Revert --- crates/builder/op-rbuilder/src/integration/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs index 11cd6d75..14ce3270 100644 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ b/crates/builder/op-rbuilder/src/integration/mod.rs @@ -240,10 +240,12 @@ impl TestHarnessBuilder { pub async fn build(self) -> eyre::Result { let mut framework = IntegrationFramework::new(&self.name).unwrap(); - // we are going to use a genesis file pre-generated before the test - let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - genesis_path.push("../../genesis.json"); - assert!(genesis_path.exists()); + // we are going to use the fixture genesis and copy it to each test folder + let genesis = include_str!("../tester/fixtures/genesis.json.tmpl"); + + let mut genesis_path = framework.test_dir.clone(); + genesis_path.push("genesis.json"); + std::fs::write(&genesis_path, genesis)?; // create the builder let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); From 653640a036f870d89c0be641ff0b26580a87f15c Mon Sep 17 00:00:00 2001 From: Karim Agha Date: Tue, 20 May 2025 11:14:07 +0200 Subject: [PATCH 093/262] Issue #36: Migrate the rest of the test to the new test utility (#53) * Issue #36 https://github.com/flashbots/op-rbuilder/issues/36 Migrate the rest of the test to the new test utility introduced in #606 --- This PR introduces the following changes: - Migrates all non-flashblocks integration tests to the new test harness framework - Fixes all build warnings - Adds few helper functions and types for testing - Few refactorings of the test infrastructure - Refactored the way the `tester` binary is linking to op-rbuilder dependencies - Moved all tests under `tests/` directory - Removed `integration` directory` now running `cargo test` will output ``` $ cargo test Finished `test` profile [unoptimized + debuginfo] target(s) in 0.36s Running unittests src/lib.rs (target/debug/deps/op_rbuilder-7bb461ec2888ee4b) running 8 tests test tx_signer::test::test_sign_transaction ... ok test tests::vanilla::smoke::get_payload_close_to_fcu ... ok test tests::vanilla::smoke::transaction_flood_no_sleep ... ok test tests::vanilla::ordering::fee_priority_ordering ... ok test tests::vanilla::revert::monitor_transaction_drops ... ok test tests::vanilla::revert::revert_protection ... ok test tests::vanilla::revert::revert_protection_disabled ... ok test tests::vanilla::smoke::chain_produces_blocks ... ok test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 23.67s Running unittests src/main.rs (target/debug/deps/op_rbuilder-87f69711ab7f0a5e) running 7 tests test generator::tests::test_block_cell_update_value ... ok test generator::tests::test_block_cell_immediate_value ... ok test generator::tests::test_job_deadline ... ok test tx_signer::test::test_sign_transaction ... ok test generator::tests::test_block_cell_wait_for_value ... ok test generator::tests::test_block_cell_multiple_waiters ... ok test generator::tests::test_payload_generator ... ok test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.70s Doc-tests op_rbuilder running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` * lint * update github actions * github actions update * update github actions * update github actions * review feedback * Review feedback and build improvements - `make tester` now builds - Ability to run GH Actions locally through `act` --- crates/builder/op-rbuilder/Cargo.toml | 9 +- .../op-rbuilder/src/{ => bin}/tester/main.rs | 37 +- crates/builder/op-rbuilder/src/generator.rs | 4 +- .../src/integration/integration_test.rs | 719 ------------------ .../op-rbuilder/src/integration/mod.rs | 418 ---------- .../op-rbuilder/src/integration/op_reth.rs | 110 --- crates/builder/op-rbuilder/src/lib.rs | 5 +- crates/builder/op-rbuilder/src/main.rs | 22 +- .../op-rbuilder/src/tests/flashblocks/mod.rs | 3 + .../src/tests/flashblocks/smoke.rs | 67 ++ .../op-rbuilder/src/tests/framework/apis.rs | 180 +++++ .../framework/artifacts}/genesis.json.tmpl | 0 .../framework/artifacts}/test-jwt-secret.txt | 0 .../mod.rs => tests/framework/blocks.rs} | 238 +----- .../src/tests/framework/harness.rs | 260 +++++++ .../op-rbuilder/src/tests/framework/mod.rs | 21 + .../op_rbuilder.rs => tests/framework/op.rs} | 155 +++- .../src/tests/framework/service.rs | 111 +++ .../op-rbuilder/src/tests/framework/txs.rs | 127 ++++ crates/builder/op-rbuilder/src/tests/mod.rs | 9 + .../op-rbuilder/src/tests/vanilla/mod.rs | 7 + .../op-rbuilder/src/tests/vanilla/ordering.rs | 66 ++ .../op-rbuilder/src/tests/vanilla/revert.rs | 127 ++++ .../op-rbuilder/src/tests/vanilla/smoke.rs | 130 ++++ 24 files changed, 1329 insertions(+), 1496 deletions(-) rename crates/builder/op-rbuilder/src/{ => bin}/tester/main.rs (69%) delete mode 100644 crates/builder/op-rbuilder/src/integration/integration_test.rs delete mode 100644 crates/builder/op-rbuilder/src/integration/mod.rs delete mode 100644 crates/builder/op-rbuilder/src/integration/op_reth.rs create mode 100644 crates/builder/op-rbuilder/src/tests/flashblocks/mod.rs create mode 100644 crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs create mode 100644 crates/builder/op-rbuilder/src/tests/framework/apis.rs rename crates/builder/op-rbuilder/src/{tester/fixtures => tests/framework/artifacts}/genesis.json.tmpl (100%) rename crates/builder/op-rbuilder/src/{tester/fixtures => tests/framework/artifacts}/test-jwt-secret.txt (100%) rename crates/builder/op-rbuilder/src/{tester/mod.rs => tests/framework/blocks.rs} (66%) create mode 100644 crates/builder/op-rbuilder/src/tests/framework/harness.rs create mode 100644 crates/builder/op-rbuilder/src/tests/framework/mod.rs rename crates/builder/op-rbuilder/src/{integration/op_rbuilder.rs => tests/framework/op.rs} (58%) create mode 100644 crates/builder/op-rbuilder/src/tests/framework/service.rs create mode 100644 crates/builder/op-rbuilder/src/tests/framework/txs.rs create mode 100644 crates/builder/op-rbuilder/src/tests/mod.rs create mode 100644 crates/builder/op-rbuilder/src/tests/vanilla/mod.rs create mode 100644 crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs create mode 100644 crates/builder/op-rbuilder/src/tests/vanilla/revert.rs create mode 100644 crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 862f2a66..109551f6 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -67,9 +67,7 @@ revm.workspace = true op-revm.workspace = true tracing.workspace = true -futures-util = "0.3.31" eyre.workspace = true -tower = "0.4" serde_with.workspace = true serde.workspace = true secp256k1.workspace = true @@ -86,6 +84,9 @@ thiserror.workspace = true parking_lot.workspace = true url.workspace = true +tower = "0.4" +futures = "0.3" +futures-util = "0.3.31" time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } @@ -127,7 +128,7 @@ min-info-logs = ["tracing/release_max_level_info"] min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] -integration = [] +testing = [] flashblocks = [] [[bin]] @@ -136,4 +137,4 @@ path = "src/main.rs" [[bin]] name = "tester" -path = "src/tester/main.rs" +required-features = ["testing"] diff --git a/crates/builder/op-rbuilder/src/tester/main.rs b/crates/builder/op-rbuilder/src/bin/tester/main.rs similarity index 69% rename from crates/builder/op-rbuilder/src/tester/main.rs rename to crates/builder/op-rbuilder/src/bin/tester/main.rs index c1041d98..b8613808 100644 --- a/crates/builder/op-rbuilder/src/tester/main.rs +++ b/crates/builder/op-rbuilder/src/bin/tester/main.rs @@ -1,6 +1,6 @@ use alloy_primitives::Address; use clap::Parser; -use op_rbuilder::tester::*; +use op_rbuilder::tests::*; /// CLI Commands #[derive(Parser, Debug)] @@ -77,3 +77,38 @@ async fn main() -> eyre::Result<()> { } } } + +#[allow(dead_code)] +pub async fn run_system( + validation: bool, + no_tx_pool: bool, + block_time_secs: u64, + flashblocks_endpoint: Option, + no_sleep: bool, +) -> eyre::Result<()> { + println!("Validation: {validation}"); + + let engine_api = EngineApi::new("http://localhost:4444").unwrap(); + let validation_api = if validation { + Some(EngineApi::new("http://localhost:5555").unwrap()) + } else { + None + }; + + let mut generator = BlockGenerator::new( + engine_api, + validation_api, + no_tx_pool, + block_time_secs, + flashblocks_endpoint, + ); + + generator.init().await?; + + // Infinite loop generating blocks + loop { + println!("Generating new block..."); + let block_hash = generator.submit_payload(None, 0, no_sleep).await?; + println!("Generated block: {block_hash}"); + } +} diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 0fa83e4d..586aa313 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -436,7 +436,7 @@ mod tests { use super::*; use alloy_eips::eip7685::Requests; use alloy_primitives::U256; - use rand::thread_rng; + use rand::rng; use reth::tasks::TokioTaskExecutor; use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_node_api::NodePrimitives; @@ -624,7 +624,7 @@ mod tests { #[tokio::test] async fn test_payload_generator() -> eyre::Result<()> { - let mut rng = thread_rng(); + let mut rng = rng(); let client = MockEthProvider::default(); let executor = TokioTaskExecutor::default(); diff --git a/crates/builder/op-rbuilder/src/integration/integration_test.rs b/crates/builder/op-rbuilder/src/integration/integration_test.rs deleted file mode 100644 index 1a251d23..00000000 --- a/crates/builder/op-rbuilder/src/integration/integration_test.rs +++ /dev/null @@ -1,719 +0,0 @@ -#[cfg(all(test, feature = "integration"))] -mod tests { - use crate::{ - integration::{ - op_rbuilder::OpRbuilderConfig, op_reth::OpRethConfig, IntegrationFramework, - TestHarness, TestHarnessBuilder, - }, - tester::{BlockGenerator, EngineApi}, - tx_signer::Signer, - }; - use alloy_consensus::{Transaction, TxEip1559}; - use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718}; - use alloy_primitives::hex; - use alloy_provider::{Identity, Provider, ProviderBuilder}; - use alloy_rpc_types_eth::BlockTransactionsKind; - use futures_util::StreamExt; - use op_alloy_consensus::OpTypedTransaction; - use op_alloy_network::Optimism; - use std::{ - cmp::max, - path::PathBuf, - sync::{Arc, Mutex}, - time::Duration, - }; - use tokio_tungstenite::connect_async; - use uuid::Uuid; - - const BUILDER_PRIVATE_KEY: &str = - "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; - - #[tokio::test] - #[cfg(not(feature = "flashblocks"))] - async fn integration_test_chain_produces_blocks() -> eyre::Result<()> { - // This is a simple test using the integration framework to test that the chain - // produces blocks. - let mut framework = - IntegrationFramework::new("integration_test_chain_produces_blocks").unwrap(); - - // we are going to use a genesis file pre-generated before the test - let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - genesis_path.push("../../genesis.json"); - assert!(genesis_path.exists()); - - // create the builder - let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let op_rbuilder_config = OpRbuilderConfig::new() - .chain_config_path(genesis_path.clone()) - .data_dir(builder_data_dir) - .auth_rpc_port(1234) - .network_port(1235) - .http_port(1238) - .with_builder_private_key(BUILDER_PRIVATE_KEY); - - // create the validation reth node - let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let reth = OpRethConfig::new() - .chain_config_path(genesis_path) - .data_dir(reth_data_dir) - .auth_rpc_port(1236) - .network_port(1237); - - framework.start("op-reth", &reth).await.unwrap(); - - let op_rbuilder = framework - .start("op-rbuilder", &op_rbuilder_config) - .await - .unwrap(); - - let engine_api = EngineApi::new("http://localhost:1234").unwrap(); - let validation_api = EngineApi::new("http://localhost:1236").unwrap(); - - let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None); - generator.init().await?; - - let provider = ProviderBuilder::::default() - .on_http("http://localhost:1238".parse()?); - - for _ in 0..10 { - let block_hash = generator.generate_block().await?; - - // query the block and the transactions inside the block - let block = provider - .get_block_by_hash(block_hash) - .await? - .expect("block"); - - for hash in block.transactions.hashes() { - let _ = provider - .get_transaction_receipt(hash) - .await? - .expect("receipt"); - } - } - - Ok(()) - } - - #[tokio::test] - #[cfg(not(feature = "flashblocks"))] - async fn integration_test_monitor_transaction_drops() -> eyre::Result<()> { - // This test ensures that the transactions that get reverted an not included in the block - // are emitted as a log on the builder. - let harness = TestHarnessBuilder::new("integration_test_monitor_transaction_drops") - .with_revert_protection() - .build() - .await?; - - let mut generator = harness.block_generator().await?; - - // send 10 reverting transactions - let mut pending_txn = Vec::new(); - for _ in 0..10 { - let txn = harness.send_revert_transaction().await?; - pending_txn.push(txn); - } - - // generate 10 blocks - for _ in 0..10 { - let block_hash = generator.generate_block().await?; - - // query the block and the transactions inside the block - let block = harness - .provider()? - .get_block_by_hash(block_hash) - .await? - .expect("block"); - - // blocks should only include two transactions (deposit + builder) - assert_eq!(block.transactions.len(), 2); - } - - // check that the builder emitted logs for the reverted transactions - // with the monitoring logic - // TODO: this is not ideal, lets find a different way to detect this - // Each time a transaction is dropped, it emits a log like this - // 'Transaction event received target="monitoring" tx_hash="" kind="discarded"' - let builder_logs = std::fs::read_to_string(harness.builder_log_path)?; - - for txn in pending_txn { - let txn_log = format!( - "Transaction event received target=\"monitoring\" tx_hash=\"{}\" kind=\"discarded\"", - txn.tx_hash() - ); - - assert!(builder_logs.contains(txn_log.as_str())); - } - - Ok(()) - } - - #[tokio::test] - #[cfg(not(feature = "flashblocks"))] - async fn integration_test_revert_protection_disabled() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("integration_test_revert_protection_disabled") - .build() - .await?; - - let mut generator = harness.block_generator().await?; - - let txn1 = harness.send_valid_transaction().await?; - let txn2 = harness.send_revert_transaction().await?; - let pending_txn = vec![txn1, txn2]; - - let block_hash = generator.generate_block().await?; - - // the transactions should be included in the block now - let pending_txn = { - let mut transaction_hashes = Vec::new(); - for txn in pending_txn { - let txn_hash = txn.with_timeout(None).watch().await?; - transaction_hashes.push(txn_hash); - } - transaction_hashes - }; - - // validate that all the transaction hashes are included in the block - let provider = harness.provider()?; - let block = provider - .get_block_by_hash(block_hash) - .await? - .expect("block"); - - for txn in pending_txn { - assert!(block.transactions.hashes().any(|hash| hash == txn)); - } - - Ok(()) - } - - #[tokio::test] - #[cfg(not(feature = "flashblocks"))] - async fn integration_test_revert_protection() -> eyre::Result<()> { - // This is a simple test using the integration framework to test that the chain - // produces blocks. - let mut framework = - IntegrationFramework::new("integration_test_revert_protection").unwrap(); - - // we are going to use a genesis file pre-generated before the test - let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - genesis_path.push("../../genesis.json"); - assert!(genesis_path.exists()); - - // create the builder - let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let op_rbuilder_config = OpRbuilderConfig::new() - .chain_config_path(genesis_path.clone()) - .data_dir(builder_data_dir) - .auth_rpc_port(1244) - .network_port(1245) - .http_port(1248) - .with_builder_private_key(BUILDER_PRIVATE_KEY) - .with_revert_protection(true); - - // create the validation reth node - let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let reth = OpRethConfig::new() - .chain_config_path(genesis_path) - .data_dir(reth_data_dir) - .auth_rpc_port(1246) - .network_port(1247); - - framework.start("op-reth", &reth).await.unwrap(); - - let _ = framework - .start("op-rbuilder", &op_rbuilder_config) - .await - .unwrap(); - - let engine_api = EngineApi::new("http://localhost:1244").unwrap(); - let validation_api = EngineApi::new("http://localhost:1246").unwrap(); - - let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None); - let latest_block = generator.init().await?; - - let provider = ProviderBuilder::::default() - .on_http("http://localhost:1248".parse()?); - - let mut base_fee = max( - latest_block.header.base_fee_per_gas.unwrap(), - MIN_PROTOCOL_BASE_FEE, - ); - for _ in 0..10 { - // Get builder's address - let known_wallet = Signer::try_from_secret(BUILDER_PRIVATE_KEY.parse()?)?; - let builder_address = known_wallet.address; - // Get current nonce from chain - let nonce = provider.get_transaction_count(builder_address).await?; - // Transaction from builder should succeed - let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: 901, - nonce, - gas_limit: 210000, - max_fee_per_gas: base_fee.into(), - ..Default::default() - }); - let signed_tx = known_wallet.sign_tx(tx_request)?; - let known_tx = provider - .send_raw_transaction(signed_tx.encoded_2718().as_slice()) - .await?; - - // Create a reverting transaction - let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: 901, - nonce: nonce + 1, - gas_limit: 300000, - max_fee_per_gas: base_fee.into(), - input: hex!("60006000fd").into(), // PUSH1 0x00 PUSH1 0x00 REVERT - ..Default::default() - }); - let signed_tx = known_wallet.sign_tx(tx_request)?; - let reverting_tx = provider - .send_raw_transaction(signed_tx.encoded_2718().as_slice()) - .await?; - - let block_hash = generator.generate_block().await?; - - // query the block and the transactions inside the block - let block = provider - .get_block_by_hash(block_hash) - .await? - .expect("block"); - - // Verify known transaction is included - assert!( - block - .transactions - .hashes() - .any(|hash| hash == *known_tx.tx_hash()), - "successful transaction missing from block" - ); - - // Verify reverted transaction is NOT included - assert!( - !block - .transactions - .hashes() - .any(|hash| hash == *reverting_tx.tx_hash()), - "reverted transaction unexpectedly included in block" - ); - for hash in block.transactions.hashes() { - let receipt = provider - .get_transaction_receipt(hash) - .await? - .expect("receipt"); - let success = receipt.inner.inner.status(); - assert!(success); - } - base_fee = max( - block.header.base_fee_per_gas.unwrap(), - MIN_PROTOCOL_BASE_FEE, - ); - } - - Ok(()) - } - - #[tokio::test] - #[cfg(not(feature = "flashblocks"))] - async fn integration_test_fee_priority_ordering() -> eyre::Result<()> { - // This test validates that transactions are ordered by fee priority in blocks - let mut framework = - IntegrationFramework::new("integration_test_fee_priority_ordering").unwrap(); - - // we are going to use a genesis file pre-generated before the test - let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - genesis_path.push("../../genesis.json"); - assert!(genesis_path.exists()); - - // create the builder - let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let op_rbuilder_config = OpRbuilderConfig::new() - .chain_config_path(genesis_path.clone()) - .data_dir(builder_data_dir) - .auth_rpc_port(1264) - .network_port(1265) - .http_port(1268) - .with_builder_private_key(BUILDER_PRIVATE_KEY); - - // create the validation reth node - let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let reth = OpRethConfig::new() - .chain_config_path(genesis_path) - .data_dir(reth_data_dir) - .auth_rpc_port(1266) - .network_port(1267); - - framework.start("op-reth", &reth).await.unwrap(); - - let _ = framework - .start("op-rbuilder", &op_rbuilder_config) - .await - .unwrap(); - - let engine_api = EngineApi::new("http://localhost:1264").unwrap(); - let validation_api = EngineApi::new("http://localhost:1266").unwrap(); - - let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 1, None); - let latest_block = generator.init().await?; - - let provider = ProviderBuilder::::default() - .on_http("http://localhost:1268".parse()?); - - let base_fee = max( - latest_block.header.base_fee_per_gas.unwrap(), - MIN_PROTOCOL_BASE_FEE, - ); - - // Create transactions with increasing fee values - let priority_fees: [u128; 5] = [1, 3, 5, 2, 4]; // Deliberately not in order - let signers = vec![ - Signer::random(), - Signer::random(), - Signer::random(), - Signer::random(), - Signer::random(), - ]; - let mut txs = Vec::new(); - - // Fund test accounts with deposits - for signer in &signers { - generator - .deposit(signer.address, 1000000000000000000) - .await?; - } - - // Send transactions in non-optimal fee order - for (i, priority_fee) in priority_fees.iter().enumerate() { - let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: 901, - nonce: 1, - gas_limit: 210000, - max_fee_per_gas: base_fee as u128 + *priority_fee, - max_priority_fee_per_gas: *priority_fee, - ..Default::default() - }); - let signed_tx = signers[i].sign_tx(tx_request)?; - let tx = provider - .send_raw_transaction(signed_tx.encoded_2718().as_slice()) - .await?; - txs.push(tx); - } - - // Generate a block that should include these transactions - let block_hash = generator.generate_block().await?; - - // Query the block and check transaction ordering - let block = provider - .get_block_by_hash(block_hash) - .full() - .await? - .expect("block"); - - // Verify all transactions are included - for tx in &txs { - assert!( - block - .transactions - .hashes() - .any(|hash| hash == *tx.tx_hash()), - "transaction missing from block" - ); - } - - let tx_fees: Vec<_> = block - .transactions - .into_transactions() - .map(|tx| tx.effective_tip_per_gas(base_fee.into())) - .collect(); - - // Verify transactions are ordered by decreasing fee (highest fee first) - // Skip the first deposit transaction and last builder transaction - for i in 1..tx_fees.len() - 2 { - assert!( - tx_fees[i] >= tx_fees[i + 1], - "Transactions not ordered by decreasing fee: {:?}", - tx_fees - ); - } - - Ok(()) - } - - #[tokio::test] - #[cfg(not(feature = "flashblocks"))] - async fn integration_test_get_payload_close_to_fcu() -> eyre::Result<()> { - let test_harness = TestHarnessBuilder::new("integration_test_get_payload_close_to_fcu") - .build() - .await?; - let mut block_generator = test_harness.block_generator().await?; - - // add some transactions to the pool so that the builder is busy when we send the fcu/getPayload requests - for _ in 0..10 { - // Note, for this test it is okay if they are not valid - let _ = test_harness.send_valid_transaction().await?; - } - - // TODO: In the fail case scenario, this hangs forever, but it should return an error - // Figure out how to do timeout (i.e. 1s) on the engine api. - block_generator.submit_payload(None, 0, true).await?; - - Ok(()) - } - - #[tokio::test] - #[cfg(not(feature = "flashblocks"))] - async fn integration_test_transaction_flood_no_sleep() -> eyre::Result<()> { - // This test validates that if we flood the builder with many transactions - // and we request short block times, the builder can still eventually resolve all the transactions - - let test_harness = TestHarnessBuilder::new("integration_test_transaction_flood_no_sleep") - .build() - .await?; - - let mut block_generator = test_harness.block_generator().await?; - let provider = test_harness.provider()?; - - // Send 200 valid transactions to the builder - // More than this and there is an issue with the RPC endpoint not being able to handle the load - let mut transactions = vec![]; - for _ in 0..200 { - let tx = test_harness.send_valid_transaction().await?; - let tx_hash = *tx.tx_hash(); - transactions.push(tx_hash); - } - - // After a 10 blocks all the transactions should be included in a block - for _ in 0..10 { - block_generator.submit_payload(None, 0, true).await.unwrap(); - } - - for tx in transactions { - provider.get_transaction_receipt(tx).await?; - } - - Ok(()) - } - - #[tokio::test] - #[cfg(feature = "flashblocks")] - async fn integration_test_chain_produces_blocks() -> eyre::Result<()> { - // This is a simple test using the integration framework to test that the chain - // produces blocks. - let mut framework = - IntegrationFramework::new("integration_test_chain_produces_blocks").unwrap(); - - // we are going to use a genesis file pre-generated before the test - let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - genesis_path.push("../../genesis.json"); - assert!(genesis_path.exists()); - - // create the builder - let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let op_rbuilder_config = OpRbuilderConfig::new() - .chain_config_path(genesis_path.clone()) - .data_dir(builder_data_dir) - .auth_rpc_port(1234) - .network_port(1235) - .http_port(1238) - .with_builder_private_key(BUILDER_PRIVATE_KEY) - .with_flashblocks_ws_url("localhost:1239") - .with_chain_block_time(2000) - .with_flashbots_block_time(200); - - // create the validation reth node - let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let reth = OpRethConfig::new() - .chain_config_path(genesis_path) - .data_dir(reth_data_dir) - .auth_rpc_port(1236) - .network_port(1237); - - framework.start("op-reth", &reth).await.unwrap(); - - let op_rbuilder = framework - .start("op-rbuilder", &op_rbuilder_config) - .await - .unwrap(); - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - - // Spawn WebSocket listener task - let ws_handle = tokio::spawn(async move { - let (ws_stream, _) = connect_async("ws://localhost:1239").await?; - let (_, mut read) = ws_stream.split(); - - while let Some(Ok(msg)) = read.next().await { - if let Ok(text) = msg.into_text() { - messages_clone.lock().unwrap().push(text); - } - } - Ok::<_, eyre::Error>(()) - }); - - let engine_api = EngineApi::new("http://localhost:1234").unwrap(); - let validation_api = EngineApi::new("http://localhost:1236").unwrap(); - - let mut generator = BlockGenerator::new(engine_api, Some(validation_api), false, 2, None); - generator.init().await?; - - let provider = ProviderBuilder::::default() - .on_http("http://localhost:1238".parse()?); - - for _ in 0..10 { - let block_hash = generator.generate_block().await?; - - // query the block and the transactions inside the block - let block = provider - .get_block_by_hash(block_hash) - .await? - .expect("block"); - - for hash in block.transactions.hashes() { - let _ = provider - .get_transaction_receipt(hash) - .await? - .expect("receipt"); - } - } - - // check there's 10 flashblocks log lines (2000ms / 200ms) - op_rbuilder.find_log_line("Building flashblock 9").await?; - - // Process websocket messages - let timeout_duration = Duration::from_secs(10); - tokio::time::timeout(timeout_duration, async { - let mut message_count = 0; - loop { - if message_count >= 10 { - break; - } - let messages = received_messages.lock().unwrap(); - let messages_json: Vec = messages - .iter() - .map(|msg| serde_json::from_str(msg).unwrap()) - .collect(); - for msg in messages_json.iter() { - let metadata = msg.get("metadata"); - assert!(metadata.is_some(), "metadata field missing"); - let metadata = metadata.unwrap(); - assert!( - metadata.get("block_number").is_some(), - "block_number missing" - ); - assert!( - metadata.get("new_account_balances").is_some(), - "new_account_balances missing" - ); - assert!(metadata.get("receipts").is_some(), "receipts missing"); - // also check if the length of the receipts is the same as the number of transactions - assert!( - metadata.get("receipts").unwrap().as_object().unwrap().len() - == msg - .get("diff") - .unwrap() - .get("transactions") - .unwrap() - .as_array() - .unwrap() - .len(), - "receipts length mismatch" - ); - message_count += 1; - } - drop(messages); - tokio::time::sleep(Duration::from_millis(100)).await; - } - }) - .await?; - ws_handle.abort(); - - Ok(()) - } - - #[tokio::test] - #[cfg(feature = "flashblocks")] - async fn integration_test_flashblocks_respects_gas_limit() -> eyre::Result<()> { - // This is a simple test using the integration framework to test that the chain - // produces blocks. - let mut framework = - IntegrationFramework::new("integration_test_flashblocks_respects_gas_limit").unwrap(); - - // we are going to use a genesis file pre-generated before the test - let mut genesis_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - genesis_path.push("../../genesis.json"); - assert!(genesis_path.exists()); - - let block_time_ms = 1000; - let flashblock_time_ms = 100; - - // create the builder - let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let op_rbuilder_config = OpRbuilderConfig::new() - .chain_config_path(genesis_path.clone()) - .data_dir(builder_data_dir) - .auth_rpc_port(1244) - .network_port(1245) - .http_port(1248) - .with_builder_private_key(BUILDER_PRIVATE_KEY) - .with_flashblocks_ws_url("localhost:1249") - .with_chain_block_time(block_time_ms) - .with_flashbots_block_time(flashblock_time_ms); - - // create the validation reth node - let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let reth = OpRethConfig::new() - .chain_config_path(genesis_path) - .data_dir(reth_data_dir) - .auth_rpc_port(1246) - .network_port(1247); - - framework.start("op-reth", &reth).await.unwrap(); - - let op_rbuilder = framework - .start("op-rbuilder", &op_rbuilder_config) - .await - .unwrap(); - - let engine_api = EngineApi::new("http://localhost:1244").unwrap(); - let validation_api = EngineApi::new("http://localhost:1246").unwrap(); - - let mut generator = BlockGenerator::new( - engine_api, - Some(validation_api), - false, - block_time_ms / 1000, - None, - ); - generator.init().await?; - - let provider = ProviderBuilder::::default() - .on_http("http://localhost:1248".parse()?); - - // Delay the payload building by 4s, ensure that the correct number of flashblocks are built - let block_hash = generator.generate_block_with_delay(4).await?; - - // query the block and the transactions inside the block - let block = provider - .get_block_by_hash(block_hash) - .await? - .expect("block"); - - for hash in block.transactions.hashes() { - let _ = provider - .get_transaction_receipt(hash) - .await? - .expect("receipt"); - } - - // check there's no more than 10 flashblocks log lines (2000ms / 200ms) - op_rbuilder.find_log_line("Building flashblock 9").await?; - op_rbuilder - .find_log_line("Skipping flashblock reached target=10 idx=10") - .await?; - - Ok(()) - } -} diff --git a/crates/builder/op-rbuilder/src/integration/mod.rs b/crates/builder/op-rbuilder/src/integration/mod.rs deleted file mode 100644 index 14ce3270..00000000 --- a/crates/builder/op-rbuilder/src/integration/mod.rs +++ /dev/null @@ -1,418 +0,0 @@ -use alloy_consensus::TxEip1559; -use alloy_eips::{eip1559::MIN_PROTOCOL_BASE_FEE, eip2718::Encodable2718, BlockNumberOrTag}; -use alloy_primitives::hex; -use alloy_provider::{ - Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, -}; -use op_alloy_consensus::OpTypedTransaction; -use op_alloy_network::Optimism; -use op_rbuilder::OpRbuilderConfig; -use op_reth::OpRethConfig; -use parking_lot::Mutex; -use std::{ - cmp::max, - collections::HashSet, - fs::{File, OpenOptions}, - future::Future, - io, - io::prelude::*, - net::TcpListener, - path::{Path, PathBuf}, - process::{Child, Command}, - sync::LazyLock, - time::{Duration, SystemTime}, -}; -use time::{format_description, OffsetDateTime}; -use tokio::time::sleep; -use uuid::Uuid; - -use crate::{ - tester::{BlockGenerator, EngineApi}, - tx_signer::Signer, -}; - -/// Default JWT token for testing purposes -pub const DEFAULT_JWT_TOKEN: &str = - "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; - -mod integration_test; -pub mod op_rbuilder; -pub mod op_reth; - -#[derive(Debug)] -pub enum IntegrationError { - SpawnError, - BinaryNotFound, - SetupError, - LogError, - ServiceAlreadyRunning, -} - -pub struct ServiceInstance { - process: Option, - pub log_path: PathBuf, -} - -pub struct IntegrationFramework { - test_dir: PathBuf, - services: Vec, -} - -pub trait Service { - /// Configure and return the command to run the service - fn command(&self) -> Command; - - /// Return a future that resolves when the service is ready - fn ready(&self, log_path: &Path) -> impl Future> + Send; -} - -/// Helper function to poll logs periodically -pub async fn poll_logs( - log_path: &Path, - pattern: &str, - interval: Duration, - timeout: Duration, -) -> Result<(), IntegrationError> { - let start = std::time::Instant::now(); - - loop { - if start.elapsed() > timeout { - return Err(IntegrationError::SpawnError); - } - - let mut file = File::open(log_path).map_err(|_| IntegrationError::LogError)?; - let mut contents = String::new(); - file.read_to_string(&mut contents) - .map_err(|_| IntegrationError::LogError)?; - - if contents.contains(pattern) { - return Ok(()); - } - - sleep(interval).await; - } -} - -impl ServiceInstance { - pub fn new(name: String, test_dir: PathBuf) -> Self { - let log_path = test_dir.join(format!("{name}.log")); - Self { - process: None, - log_path, - } - } - - pub fn start(&mut self, command: Command) -> Result<(), IntegrationError> { - if self.process.is_some() { - return Err(IntegrationError::ServiceAlreadyRunning); - } - - let log = open_log_file(&self.log_path)?; - let stdout = log.try_clone().map_err(|_| IntegrationError::LogError)?; - let stderr = log.try_clone().map_err(|_| IntegrationError::LogError)?; - - let mut cmd = command; - cmd.stdout(stdout).stderr(stderr); - - let child = match cmd.spawn() { - Ok(child) => Ok(child), - Err(e) => match e.kind() { - io::ErrorKind::NotFound => Err(IntegrationError::BinaryNotFound), - _ => Err(IntegrationError::SpawnError), - }, - }?; - - self.process = Some(child); - Ok(()) - } - - pub fn stop(&mut self) -> Result<(), IntegrationError> { - if let Some(mut process) = self.process.take() { - process.kill().map_err(|_| IntegrationError::SpawnError)?; - } - Ok(()) - } - - /// Start a service using its configuration and wait for it to be ready - pub async fn start_with_config( - &mut self, - config: &T, - ) -> Result<(), IntegrationError> { - self.start(config.command())?; - config.ready(&self.log_path).await?; - Ok(()) - } - - pub async fn find_log_line(&self, pattern: &str) -> eyre::Result<()> { - let mut file = - File::open(&self.log_path).map_err(|_| eyre::eyre!("Failed to open log file"))?; - let mut contents = String::new(); - file.read_to_string(&mut contents) - .map_err(|_| eyre::eyre!("Failed to read log file"))?; - - if contents.contains(pattern) { - Ok(()) - } else { - Err(eyre::eyre!("Pattern not found in log file: {}", pattern)) - } - } -} - -impl IntegrationFramework { - pub fn new(test_name: &str) -> Result { - let dt: OffsetDateTime = SystemTime::now().into(); - let format = format_description::parse("[year]_[month]_[day]_[hour]_[minute]_[second]") - .map_err(|_| IntegrationError::SetupError)?; - - let date_format = dt - .format(&format) - .map_err(|_| IntegrationError::SetupError)?; - - let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - test_dir.push("../../integration_logs"); - test_dir.push(format!("{date_format}_{test_name}")); - - std::fs::create_dir_all(&test_dir).map_err(|_| IntegrationError::SetupError)?; - - Ok(Self { - test_dir, - services: Vec::new(), - }) - } - - pub async fn start( - &mut self, - name: &str, - config: &T, - ) -> Result<&mut ServiceInstance, IntegrationError> { - let service = self.create_service(name)?; - service.start_with_config(config).await?; - Ok(service) - } - - pub fn create_service(&mut self, name: &str) -> Result<&mut ServiceInstance, IntegrationError> { - let service = ServiceInstance::new(name.to_string(), self.test_dir.clone()); - self.services.push(service); - Ok(self.services.last_mut().unwrap()) - } -} - -fn open_log_file(path: &PathBuf) -> Result { - let prefix = path.parent().unwrap(); - std::fs::create_dir_all(prefix).map_err(|_| IntegrationError::LogError)?; - - OpenOptions::new() - .append(true) - .create(true) - .open(path) - .map_err(|_| IntegrationError::LogError) -} - -impl Drop for IntegrationFramework { - fn drop(&mut self) { - for service in &mut self.services { - let _ = service.stop(); - } - } -} - -const BUILDER_PRIVATE_KEY: &str = - "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; - -pub struct TestHarnessBuilder { - name: String, - use_revert_protection: bool, -} - -impl TestHarnessBuilder { - pub fn new(name: &str) -> Self { - Self { - name: name.to_string(), - use_revert_protection: false, - } - } - - pub fn with_revert_protection(mut self) -> Self { - self.use_revert_protection = true; - self - } - - pub async fn build(self) -> eyre::Result { - let mut framework = IntegrationFramework::new(&self.name).unwrap(); - - // we are going to use the fixture genesis and copy it to each test folder - let genesis = include_str!("../tester/fixtures/genesis.json.tmpl"); - - let mut genesis_path = framework.test_dir.clone(); - genesis_path.push("genesis.json"); - std::fs::write(&genesis_path, genesis)?; - - // create the builder - let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let builder_auth_rpc_port = get_available_port(); - let builder_http_port = get_available_port(); - let op_rbuilder_config = OpRbuilderConfig::new() - .chain_config_path(genesis_path.clone()) - .data_dir(builder_data_dir) - .auth_rpc_port(builder_auth_rpc_port) - .network_port(get_available_port()) - .http_port(builder_http_port) - .with_builder_private_key(BUILDER_PRIVATE_KEY) - .with_revert_protection(self.use_revert_protection); - - // create the validation reth node - - let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let validator_auth_rpc_port = get_available_port(); - let reth = OpRethConfig::new() - .chain_config_path(genesis_path) - .data_dir(reth_data_dir) - .auth_rpc_port(validator_auth_rpc_port) - .network_port(get_available_port()); - - framework.start("op-reth", &reth).await.unwrap(); - - let builder = framework - .start("op-rbuilder", &op_rbuilder_config) - .await - .unwrap(); - - let builder_log_path = builder.log_path.clone(); - - Ok(TestHarness { - _framework: framework, - builder_auth_rpc_port, - builder_http_port, - validator_auth_rpc_port, - builder_log_path, - }) - } -} - -pub struct TestHarness { - _framework: IntegrationFramework, - builder_auth_rpc_port: u16, - builder_http_port: u16, - validator_auth_rpc_port: u16, - #[allow(dead_code)] // I think this is due to some feature flag conflicts - builder_log_path: PathBuf, -} - -impl TestHarness { - pub async fn send_valid_transaction( - &self, - ) -> eyre::Result> { - // Get builder's address - let known_wallet = Signer::try_from_secret(BUILDER_PRIVATE_KEY.parse()?)?; - let builder_address = known_wallet.address; - - let url = format!("http://localhost:{}", self.builder_http_port); - let provider = - ProviderBuilder::::default().on_http(url.parse()?); - - // Get current nonce includeing the ones from the txpool - let nonce = provider - .get_transaction_count(builder_address) - .pending() - .await?; - - let latest_block = provider - .get_block_by_number(BlockNumberOrTag::Latest) - .await? - .unwrap(); - - let base_fee = max( - latest_block.header.base_fee_per_gas.unwrap(), - MIN_PROTOCOL_BASE_FEE, - ); - - // Transaction from builder should succeed - let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: 901, - nonce, - gas_limit: 210000, - max_fee_per_gas: base_fee.into(), - ..Default::default() - }); - let signed_tx = known_wallet.sign_tx(tx_request)?; - let pending_tx = provider - .send_raw_transaction(signed_tx.encoded_2718().as_slice()) - .await?; - - Ok(pending_tx) - } - - pub async fn send_revert_transaction( - &self, - ) -> eyre::Result> { - // TODO: Merge this with send_valid_transaction - // Get builder's address - let known_wallet = Signer::try_from_secret(BUILDER_PRIVATE_KEY.parse()?)?; - let builder_address = known_wallet.address; - - let url = format!("http://localhost:{}", self.builder_http_port); - let provider = - ProviderBuilder::::default().on_http(url.parse()?); - - // Get current nonce includeing the ones from the txpool - let nonce = provider - .get_transaction_count(builder_address) - .pending() - .await?; - - let latest_block = provider - .get_block_by_number(BlockNumberOrTag::Latest) - .await? - .unwrap(); - - let base_fee = max( - latest_block.header.base_fee_per_gas.unwrap(), - MIN_PROTOCOL_BASE_FEE, - ); - - // Transaction from builder should succeed - let tx_request = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: 901, - nonce, - gas_limit: 210000, - max_fee_per_gas: base_fee.into(), - input: hex!("60006000fd").into(), // PUSH1 0x00 PUSH1 0x00 REVERT - ..Default::default() - }); - let signed_tx = known_wallet.sign_tx(tx_request)?; - let pending_tx = provider - .send_raw_transaction(signed_tx.encoded_2718().as_slice()) - .await?; - - Ok(pending_tx) - } - - pub fn provider(&self) -> eyre::Result> { - let url = format!("http://localhost:{}", self.builder_http_port); - let provider = - ProviderBuilder::::default().on_http(url.parse()?); - - Ok(provider) - } - - pub async fn block_generator(&self) -> eyre::Result { - let engine_api = EngineApi::new_with_port(self.builder_auth_rpc_port).unwrap(); - let validation_api = Some(EngineApi::new_with_port(self.validator_auth_rpc_port).unwrap()); - - let mut generator = BlockGenerator::new(engine_api, validation_api, false, 1, None); - generator.init().await?; - - Ok(generator) - } -} - -pub fn get_available_port() -> u16 { - static CLAIMED_PORTS: LazyLock>> = - LazyLock::new(|| Mutex::new(HashSet::new())); - loop { - let port: u16 = rand::random_range(1000..20000); - if TcpListener::bind(("127.0.0.1", port)).is_ok() && CLAIMED_PORTS.lock().insert(port) { - return port; - } - } -} diff --git a/crates/builder/op-rbuilder/src/integration/op_reth.rs b/crates/builder/op-rbuilder/src/integration/op_reth.rs deleted file mode 100644 index 154ba119..00000000 --- a/crates/builder/op-rbuilder/src/integration/op_reth.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::integration::{poll_logs, IntegrationError, Service, DEFAULT_JWT_TOKEN}; -use futures_util::Future; -use std::{ - path::{Path, PathBuf}, - process::Command, - time::Duration, -}; - -fn get_or_create_jwt_path(jwt_path: Option<&PathBuf>) -> PathBuf { - jwt_path.cloned().unwrap_or_else(|| { - let tmp_dir = std::env::temp_dir(); - let jwt_path = tmp_dir.join("jwt.hex"); - std::fs::write(&jwt_path, DEFAULT_JWT_TOKEN).expect("Failed to write JWT secret file"); - jwt_path - }) -} - -#[derive(Default)] -pub struct OpRethConfig { - auth_rpc_port: Option, - jwt_secret_path: Option, - chain_config_path: Option, - data_dir: Option, - http_port: Option, - network_port: Option, -} - -impl OpRethConfig { - pub fn new() -> Self { - Self::default() - } - - pub fn auth_rpc_port(mut self, port: u16) -> Self { - self.auth_rpc_port = Some(port); - self - } - - pub fn chain_config_path>(mut self, path: P) -> Self { - self.chain_config_path = Some(path.into()); - self - } - - pub fn data_dir>(mut self, path: P) -> Self { - self.data_dir = Some(path.into()); - self - } - - pub fn network_port(mut self, port: u16) -> Self { - self.network_port = Some(port); - self - } -} - -impl Service for OpRethConfig { - fn command(&self) -> Command { - let bin_path = PathBuf::from("op-reth"); - - let mut cmd = Command::new(bin_path); - let jwt_path = get_or_create_jwt_path(self.jwt_secret_path.as_ref()); - - cmd.arg("node") - .arg("--authrpc.port") - .arg( - self.auth_rpc_port - .expect("auth_rpc_port not set") - .to_string(), - ) - .arg("--authrpc.jwtsecret") - .arg( - jwt_path - .to_str() - .expect("Failed to convert jwt_path to string"), - ) - .arg("--chain") - .arg( - self.chain_config_path - .as_ref() - .expect("chain_config_path not set"), - ) - .arg("--datadir") - .arg(self.data_dir.as_ref().expect("data_dir not set")) - .arg("--disable-discovery") - .arg("--color") - .arg("never") - .arg("--port") - .arg(self.network_port.expect("network_port not set").to_string()) - .arg("--ipcdisable"); - - if let Some(http_port) = self.http_port { - cmd.arg("--http") - .arg("--http.port") - .arg(http_port.to_string()); - } - - cmd - } - - #[allow(clippy::manual_async_fn)] - fn ready(&self, log_path: &Path) -> impl Future> + Send { - async move { - poll_logs( - log_path, - "Starting consensus engine", - Duration::from_millis(100), - Duration::from_secs(60), - ) - .await - } - } -} diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index 211b1ddb..f377643f 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,4 +1,5 @@ -pub mod integration; pub mod primitives; -pub mod tester; pub mod tx_signer; + +#[cfg(any(test, feature = "testing"))] +pub mod tests; diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index c7db90a6..0da7a707 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -2,28 +2,28 @@ use args::CliExt; use clap::Parser; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; use reth_optimism_node::{node::OpAddOnsBuilder, OpNode}; - -#[cfg(feature = "flashblocks")] -use payload_builder::CustomOpPayloadBuilder; -#[cfg(not(feature = "flashblocks"))] -use payload_builder_vanilla::CustomOpPayloadBuilder; use reth_transaction_pool::TransactionPool; /// CLI argument parsing. pub mod args; pub mod generator; -#[cfg(test)] -mod integration; mod metrics; mod monitor_tx_pool; +mod primitives; +mod tx_signer; + #[cfg(feature = "flashblocks")] pub mod payload_builder; + #[cfg(not(feature = "flashblocks"))] mod payload_builder_vanilla; -mod primitives; -#[cfg(test)] -mod tester; -mod tx_signer; + +#[cfg(not(feature = "flashblocks"))] +use payload_builder_vanilla::CustomOpPayloadBuilder; + +#[cfg(feature = "flashblocks")] +use payload_builder::CustomOpPayloadBuilder; + use metrics::{ VersionInfo, BUILD_PROFILE_NAME, CARGO_PKG_VERSION, VERGEN_BUILD_TIMESTAMP, VERGEN_CARGO_FEATURES, VERGEN_CARGO_TARGET_TRIPLE, VERGEN_GIT_SHA, diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/tests/flashblocks/mod.rs new file mode 100644 index 00000000..6ca67633 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/flashblocks/mod.rs @@ -0,0 +1,3 @@ +#![cfg(test)] + +mod smoke; diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs new file mode 100644 index 00000000..63e71cb3 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs @@ -0,0 +1,67 @@ +use std::sync::Arc; + +use futures::StreamExt; +use parking_lot::Mutex; +use tokio::task::JoinHandle; +use tokio_tungstenite::{connect_async, tungstenite::Message}; +use tokio_util::sync::CancellationToken; + +use crate::tests::TestHarnessBuilder; + +#[tokio::test] +#[ignore = "Flashblocks tests need more work"] +async fn chain_produces_blocks() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("flashbots_chain_produces_blocks") + .with_flashblocks_ws_url("ws://localhost:1239") + .with_chain_block_time(2000) + .with_flashbots_block_time(200) + .build() + .await?; + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + let cancellation_token = CancellationToken::new(); + + // Spawn WebSocket listener task + let cancellation_token_clone = cancellation_token.clone(); + let ws_handle: JoinHandle> = tokio::spawn(async move { + let (ws_stream, _) = connect_async("ws://localhost:1239").await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + messages_clone.lock().push(text); + } + } + } + }); + + let mut generator = harness.block_generator().await?; + + for _ in 0..10 { + for _ in 0..5 { + // send a valid transaction + let _ = harness.send_valid_transaction().await?; + } + + generator.generate_block().await?; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + cancellation_token.cancel(); + assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + assert!( + !received_messages + .lock() + .iter() + .any(|msg| msg.contains("Building flashblock")), + "No messages received from WebSocket" + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs new file mode 100644 index 00000000..37ac84dc --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -0,0 +1,180 @@ +use super::DEFAULT_JWT_TOKEN; +use alloy_eips::BlockNumberOrTag; +use alloy_primitives::B256; +use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatus}; +use jsonrpsee::{ + core::RpcResult, + http_client::{transport::HttpBackend, HttpClient}, + proc_macros::rpc, +}; +use reth::rpc::{api::EngineApiClient, types::engine::ForkchoiceState}; +use reth_node_api::{EngineTypes, PayloadTypes}; +use reth_optimism_node::OpEngineTypes; +use reth_payload_builder::PayloadId; +use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; +use serde_json::Value; +use std::str::FromStr; + +/// Helper for engine api operations +pub struct EngineApi { + pub engine_api_client: HttpClient>, +} + +/// Builder for EngineApi configuration +pub struct EngineApiBuilder { + url: String, + jwt_secret: String, +} + +impl Default for EngineApiBuilder { + fn default() -> Self { + Self::new() + } +} + +impl EngineApiBuilder { + pub fn new() -> Self { + Self { + url: String::from("http://localhost:8551"), + jwt_secret: String::from(DEFAULT_JWT_TOKEN), + } + } + + pub fn with_url(mut self, url: &str) -> Self { + self.url = url.to_string(); + self + } + + pub fn build(self) -> Result> { + let secret_layer = AuthClientLayer::new(JwtSecret::from_str(&self.jwt_secret)?); + let middleware = tower::ServiceBuilder::default().layer(secret_layer); + let client = jsonrpsee::http_client::HttpClientBuilder::default() + .set_http_middleware(middleware) + .build(&self.url) + .expect("Failed to create http client"); + + Ok(EngineApi { + engine_api_client: client, + }) + } +} + +impl EngineApi { + pub fn builder() -> EngineApiBuilder { + EngineApiBuilder::new() + } + + pub fn new(url: &str) -> Result> { + Self::builder().with_url(url).build() + } + + pub fn new_with_port(port: u16) -> Result> { + Self::builder() + .with_url(&format!("http://localhost:{port}")) + .build() + } + + pub async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> eyre::Result<::ExecutionPayloadEnvelopeV3> { + println!( + "Fetching payload with id: {} at {}", + payload_id, + chrono::Utc::now() + ); + + Ok( + EngineApiClient::::get_payload_v3(&self.engine_api_client, payload_id) + .await?, + ) + } + + pub async fn new_payload( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> eyre::Result { + println!("Submitting new payload at {}...", chrono::Utc::now()); + + Ok(EngineApiClient::::new_payload_v3( + &self.engine_api_client, + payload, + versioned_hashes, + parent_beacon_block_root, + ) + .await?) + } + + pub async fn update_forkchoice( + &self, + current_head: B256, + new_head: B256, + payload_attributes: Option<::PayloadAttributes>, + ) -> eyre::Result { + println!("Updating forkchoice at {}...", chrono::Utc::now()); + + Ok(EngineApiClient::::fork_choice_updated_v3( + &self.engine_api_client, + ForkchoiceState { + head_block_hash: new_head, + safe_block_hash: current_head, + finalized_block_hash: current_head, + }, + payload_attributes, + ) + .await?) + } + + pub async fn latest(&self) -> eyre::Result> { + self.get_block_by_number(BlockNumberOrTag::Latest, false) + .await + } + + pub async fn get_block_by_number( + &self, + number: BlockNumberOrTag, + include_txs: bool, + ) -> eyre::Result> { + Ok( + BlockApiClient::get_block_by_number(&self.engine_api_client, number, include_txs) + .await?, + ) + } +} + +#[rpc(server, client, namespace = "eth")] +pub trait BlockApi { + #[method(name = "getBlockByNumber")] + async fn get_block_by_number( + &self, + block_number: BlockNumberOrTag, + include_txs: bool, + ) -> RpcResult>; +} + +pub async fn generate_genesis(output: Option) -> eyre::Result<()> { + // Read the template file + let template = include_str!("artifacts/genesis.json.tmpl"); + + // Parse the JSON + let mut genesis: Value = serde_json::from_str(template)?; + + // Update the timestamp field - example using current timestamp + let timestamp = chrono::Utc::now().timestamp(); + if let Some(config) = genesis.as_object_mut() { + // Assuming timestamp is at the root level - adjust path as needed + config["timestamp"] = Value::String(format!("0x{timestamp:x}")); + } + + // Write the result to the output file + if let Some(output) = output { + std::fs::write(&output, serde_json::to_string_pretty(&genesis)?)?; + println!("Generated genesis file at: {output}"); + } else { + println!("{}", serde_json::to_string_pretty(&genesis)?); + } + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl b/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl similarity index 100% rename from crates/builder/op-rbuilder/src/tester/fixtures/genesis.json.tmpl rename to crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl diff --git a/crates/builder/op-rbuilder/src/tester/fixtures/test-jwt-secret.txt b/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-jwt-secret.txt similarity index 100% rename from crates/builder/op-rbuilder/src/tester/fixtures/test-jwt-secret.txt rename to crates/builder/op-rbuilder/src/tests/framework/artifacts/test-jwt-secret.txt diff --git a/crates/builder/op-rbuilder/src/tester/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs similarity index 66% rename from crates/builder/op-rbuilder/src/tester/mod.rs rename to crates/builder/op-rbuilder/src/tests/framework/blocks.rs index 08bbb792..847025d7 100644 --- a/crates/builder/op-rbuilder/src/tester/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs @@ -2,193 +2,15 @@ use crate::tx_signer::Signer; use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; use alloy_primitives::{address, hex, Address, Bytes, TxKind, B256, U256}; use alloy_rpc_types_engine::{ - ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, ForkchoiceUpdated, - PayloadAttributes, PayloadStatus, PayloadStatusEnum, + ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadAttributes, + PayloadStatusEnum, }; use alloy_rpc_types_eth::Block; -use jsonrpsee::{ - core::RpcResult, - http_client::{transport::HttpBackend, HttpClient}, - proc_macros::rpc, -}; use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; use op_alloy_rpc_types_engine::OpPayloadAttributes; -use reth::rpc::{api::EngineApiClient, types::engine::ForkchoiceState}; -use reth_node_api::{EngineTypes, PayloadTypes}; -use reth_optimism_node::OpEngineTypes; -use reth_payload_builder::PayloadId; -use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; use rollup_boost::{Flashblocks, FlashblocksService}; -use serde_json::Value; -use std::str::FromStr; - -/// Helper for engine api operations -pub struct EngineApi { - pub engine_api_client: HttpClient>, -} - -/// Builder for EngineApi configuration -pub struct EngineApiBuilder { - url: String, - jwt_secret: String, -} - -impl Default for EngineApiBuilder { - fn default() -> Self { - Self::new() - } -} - -impl EngineApiBuilder { - pub fn new() -> Self { - Self { - url: String::from("http://localhost:8551"), // default value - jwt_secret: String::from( - "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a", - ), // default value - } - } - - pub fn with_url(mut self, url: &str) -> Self { - self.url = url.to_string(); - self - } - pub fn build(self) -> Result> { - let secret_layer = AuthClientLayer::new(JwtSecret::from_str(&self.jwt_secret)?); - let middleware = tower::ServiceBuilder::default().layer(secret_layer); - let client = jsonrpsee::http_client::HttpClientBuilder::default() - .set_http_middleware(middleware) - .build(&self.url) - .expect("Failed to create http client"); - - Ok(EngineApi { - engine_api_client: client, - }) - } -} - -impl EngineApi { - pub fn builder() -> EngineApiBuilder { - EngineApiBuilder::new() - } - - pub fn new(url: &str) -> Result> { - Self::builder().with_url(url).build() - } - - pub fn new_with_port(port: u16) -> Result> { - Self::builder() - .with_url(&format!("http://localhost:{port}")) - .build() - } - - pub async fn get_payload_v3( - &self, - payload_id: PayloadId, - ) -> eyre::Result<::ExecutionPayloadEnvelopeV3> { - println!( - "Fetching payload with id: {} at {}", - payload_id, - chrono::Utc::now() - ); - - Ok( - EngineApiClient::::get_payload_v3(&self.engine_api_client, payload_id) - .await?, - ) - } - - pub async fn new_payload( - &self, - payload: ExecutionPayloadV3, - versioned_hashes: Vec, - parent_beacon_block_root: B256, - ) -> eyre::Result { - println!("Submitting new payload at {}...", chrono::Utc::now()); - - Ok(EngineApiClient::::new_payload_v3( - &self.engine_api_client, - payload, - versioned_hashes, - parent_beacon_block_root, - ) - .await?) - } - - pub async fn update_forkchoice( - &self, - current_head: B256, - new_head: B256, - payload_attributes: Option<::PayloadAttributes>, - ) -> eyre::Result { - println!("Updating forkchoice at {}...", chrono::Utc::now()); - - Ok(EngineApiClient::::fork_choice_updated_v3( - &self.engine_api_client, - ForkchoiceState { - head_block_hash: new_head, - safe_block_hash: current_head, - finalized_block_hash: current_head, - }, - payload_attributes, - ) - .await?) - } - - pub async fn latest(&self) -> eyre::Result> { - self.get_block_by_number(BlockNumberOrTag::Latest, false) - .await - } - - pub async fn get_block_by_number( - &self, - number: BlockNumberOrTag, - include_txs: bool, - ) -> eyre::Result> { - Ok( - BlockApiClient::get_block_by_number(&self.engine_api_client, number, include_txs) - .await?, - ) - } -} - -#[rpc(server, client, namespace = "eth")] -pub trait BlockApi { - #[method(name = "getBlockByNumber")] - async fn get_block_by_number( - &self, - block_number: BlockNumberOrTag, - include_txs: bool, - ) -> RpcResult>; -} - -// TODO: This is not being recognized as used code by the main function -#[allow(dead_code)] -pub async fn generate_genesis(output: Option) -> eyre::Result<()> { - // Read the template file - let template = include_str!("fixtures/genesis.json.tmpl"); - - // Parse the JSON - let mut genesis: Value = serde_json::from_str(template)?; - - // Update the timestamp field - example using current timestamp - let timestamp = chrono::Utc::now().timestamp(); - if let Some(config) = genesis.as_object_mut() { - // Assuming timestamp is at the root level - adjust path as needed - config["timestamp"] = Value::String(format!("0x{timestamp:x}")); - } - - // Write the result to the output file - if let Some(output) = output { - std::fs::write(&output, serde_json::to_string_pretty(&genesis)?)?; - println!("Generated genesis file at: {output}"); - } else { - println!("{}", serde_json::to_string_pretty(&genesis)?); - } - - Ok(()) -} +use super::apis::EngineApi; // L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) // @@ -475,7 +297,6 @@ impl BlockGenerator { } /// Submit a deposit transaction to seed an account with ETH - #[allow(dead_code)] pub async fn deposit(&mut self, address: Address, value: u128) -> eyre::Result { // Create deposit transaction let deposit_tx = TxDeposit { @@ -497,40 +318,25 @@ impl BlockGenerator { self.submit_payload(Some(vec![signed_tx_rlp.into()]), 0, false) .await } -} -// TODO: This is not being recognized as used code by the main function -#[allow(dead_code)] -pub async fn run_system( - validation: bool, - no_tx_pool: bool, - block_time_secs: u64, - flashblocks_endpoint: Option, - no_sleep: bool, -) -> eyre::Result<()> { - println!("Validation: {validation}"); - - let engine_api = EngineApi::new("http://localhost:4444").unwrap(); - let validation_api = if validation { - Some(EngineApi::new("http://localhost:5555").unwrap()) - } else { - None - }; - - let mut generator = BlockGenerator::new( - engine_api, - validation_api, - no_tx_pool, - block_time_secs, - flashblocks_endpoint, - ); - - generator.init().await?; - - // Infinite loop generating blocks - loop { - println!("Generating new block..."); - let block_hash = generator.submit_payload(None, 0, no_sleep).await?; - println!("Generated block: {block_hash}"); + pub async fn create_funded_accounts( + &mut self, + count: usize, + amount: u128, + ) -> eyre::Result> { + let mut signers = Vec::with_capacity(count); + + for _ in 0..count { + // Create a new signer + let signer = Signer::random(); + let address = signer.address; + + // Deposit funds to the new account + self.deposit(address, amount).await?; + + signers.push(signer); + } + + Ok(signers) } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/harness.rs b/crates/builder/op-rbuilder/src/tests/framework/harness.rs new file mode 100644 index 00000000..c9f355c5 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/harness.rs @@ -0,0 +1,260 @@ +use super::{ + apis::EngineApi, + blocks::BlockGenerator, + op::{OpRbuilderConfig, OpRethConfig}, + service::{self, Service, ServiceInstance}, + TransactionBuilder, BUILDER_PRIVATE_KEY, +}; +use alloy_eips::BlockNumberOrTag; +use alloy_network::Network; +use alloy_primitives::hex; +use alloy_provider::{ + Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, +}; +use op_alloy_network::Optimism; +use parking_lot::Mutex; +use std::{ + collections::HashSet, net::TcpListener, path::PathBuf, sync::LazyLock, time::SystemTime, +}; +use time::{format_description, OffsetDateTime}; +use uuid::Uuid; + +pub struct TestHarnessBuilder { + name: String, + use_revert_protection: bool, + flashblocks_ws_url: Option, + chain_block_time: Option, + flashbots_block_time: Option, +} + +impl TestHarnessBuilder { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + use_revert_protection: false, + flashblocks_ws_url: None, + chain_block_time: None, + flashbots_block_time: None, + } + } + + pub fn with_revert_protection(mut self) -> Self { + self.use_revert_protection = true; + self + } + + pub fn with_flashblocks_ws_url(mut self, url: &str) -> Self { + self.flashblocks_ws_url = Some(url.to_string()); + self + } + + pub fn with_chain_block_time(mut self, block_time: u64) -> Self { + self.chain_block_time = Some(block_time); + self + } + + pub fn with_flashbots_block_time(mut self, block_time: u64) -> Self { + self.flashbots_block_time = Some(block_time); + self + } + + pub async fn build(self) -> eyre::Result { + let mut framework = IntegrationFramework::new(&self.name).unwrap(); + + // we are going to use the fixture genesis and copy it to each test folder + let genesis = include_str!("artifacts/genesis.json.tmpl"); + + let mut genesis_path = framework.test_dir.clone(); + genesis_path.push("genesis.json"); + std::fs::write(&genesis_path, genesis)?; + + // create the builder + let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let builder_auth_rpc_port = get_available_port(); + let builder_http_port = get_available_port(); + let mut op_rbuilder_config = OpRbuilderConfig::new() + .chain_config_path(genesis_path.clone()) + .data_dir(builder_data_dir) + .auth_rpc_port(builder_auth_rpc_port) + .network_port(get_available_port()) + .http_port(builder_http_port) + .with_builder_private_key(BUILDER_PRIVATE_KEY) + .with_revert_protection(self.use_revert_protection); + + if let Some(flashblocks_ws_url) = self.flashblocks_ws_url { + op_rbuilder_config = op_rbuilder_config.with_flashblocks_ws_url(&flashblocks_ws_url); + } + + if let Some(chain_block_time) = self.chain_block_time { + op_rbuilder_config = op_rbuilder_config.with_chain_block_time(chain_block_time); + } + + if let Some(flashbots_block_time) = self.flashbots_block_time { + op_rbuilder_config = op_rbuilder_config.with_flashbots_block_time(flashbots_block_time); + } + + // create the validation reth node + + let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let validator_auth_rpc_port = get_available_port(); + let reth = OpRethConfig::new() + .chain_config_path(genesis_path) + .data_dir(reth_data_dir) + .auth_rpc_port(validator_auth_rpc_port) + .network_port(get_available_port()); + + framework.start("op-reth", &reth).await.unwrap(); + + let builder = framework + .start("op-rbuilder", &op_rbuilder_config) + .await + .unwrap(); + + let builder_log_path = builder.log_path.clone(); + + Ok(TestHarness { + _framework: framework, + builder_auth_rpc_port, + builder_http_port, + validator_auth_rpc_port, + builder_log_path, + }) + } +} + +pub struct TestHarness { + _framework: IntegrationFramework, + builder_auth_rpc_port: u16, + builder_http_port: u16, + validator_auth_rpc_port: u16, + builder_log_path: PathBuf, +} + +impl TestHarness { + pub async fn send_valid_transaction( + &self, + ) -> eyre::Result> { + self.create_transaction().send().await + } + + pub async fn send_revert_transaction( + &self, + ) -> eyre::Result> { + self.create_transaction() + .with_input(hex!("60006000fd").into()) // PUSH1 0x00 PUSH1 0x00 REVERT + .send() + .await + } + + pub fn provider(&self) -> eyre::Result> { + let url = format!("http://localhost:{}", self.builder_http_port); + let provider = + ProviderBuilder::::default().on_http(url.parse()?); + + Ok(provider) + } + + pub async fn block_generator(&self) -> eyre::Result { + let engine_api = EngineApi::new_with_port(self.builder_auth_rpc_port).unwrap(); + let validation_api = Some(EngineApi::new_with_port(self.validator_auth_rpc_port).unwrap()); + + let mut generator = BlockGenerator::new(engine_api, validation_api, false, 1, None); + generator.init().await?; + + Ok(generator) + } + + pub fn create_transaction(&self) -> TransactionBuilder { + TransactionBuilder::new(self.provider().expect("provider not available")) + } + + pub async fn latest_block(&self) -> ::BlockResponse { + self.provider() + .expect("provider not available") + .get_block_by_number(BlockNumberOrTag::Latest) + .full() + .await + .expect("failed to get latest block by hash") + .expect("latest block should exist") + } + + pub async fn latest_base_fee(&self) -> u128 { + self.latest_block() + .await + .header + .base_fee_per_gas + .expect("Base fee per gas not found in the latest block header") as u128 + } + + pub const fn builder_private_key() -> &'static str { + BUILDER_PRIVATE_KEY + } + + pub const fn builder_log_path(&self) -> &PathBuf { + &self.builder_log_path + } +} + +pub fn get_available_port() -> u16 { + static CLAIMED_PORTS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); + loop { + let port: u16 = rand::random_range(1000..20000); + if TcpListener::bind(("127.0.0.1", port)).is_ok() && CLAIMED_PORTS.lock().insert(port) { + return port; + } + } +} + +#[derive(Debug)] +pub enum IntegrationError { + SpawnError, + BinaryNotFound, + SetupError, + LogError, + ServiceAlreadyRunning, +} + +struct IntegrationFramework { + test_dir: PathBuf, + services: Vec, +} + +impl IntegrationFramework { + pub fn new(test_name: &str) -> Result { + let dt: OffsetDateTime = SystemTime::now().into(); + let format = format_description::parse("[year]_[month]_[day]_[hour]_[minute]_[second]") + .map_err(|_| IntegrationError::SetupError)?; + + let date_format = dt + .format(&format) + .map_err(|_| IntegrationError::SetupError)?; + + let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + test_dir.push("../../integration_logs"); + test_dir.push(format!("{date_format}_{test_name}")); + + std::fs::create_dir_all(&test_dir).map_err(|_| IntegrationError::SetupError)?; + + Ok(Self { + test_dir, + services: Vec::new(), + }) + } + + pub async fn start( + &mut self, + name: &str, + config: &T, + ) -> Result<&mut ServiceInstance, service::Error> { + let service = self.create_service(name)?; + service.start_with_config(config).await?; + Ok(service) + } + + pub fn create_service(&mut self, name: &str) -> Result<&mut ServiceInstance, service::Error> { + let service = ServiceInstance::new(name.to_string(), self.test_dir.clone()); + self.services.push(service); + Ok(self.services.last_mut().unwrap()) + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs new file mode 100644 index 00000000..7ebab77f --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/mod.rs @@ -0,0 +1,21 @@ +mod apis; +mod blocks; +mod harness; +mod op; +mod service; +mod txs; + +pub use apis::*; +pub use blocks::*; +pub use harness::*; +pub use op::*; +pub use service::*; +pub use txs::*; + +const BUILDER_PRIVATE_KEY: &str = + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; + +pub const DEFAULT_JWT_TOKEN: &str = + "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; + +pub const ONE_ETH: u128 = 1_000_000_000_000_000_000; diff --git a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs b/crates/builder/op-rbuilder/src/tests/framework/op.rs similarity index 58% rename from crates/builder/op-rbuilder/src/integration/op_rbuilder.rs rename to crates/builder/op-rbuilder/src/tests/framework/op.rs index 505fe520..00a0f7f7 100644 --- a/crates/builder/op-rbuilder/src/integration/op_rbuilder.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/op.rs @@ -1,21 +1,20 @@ -use crate::integration::{poll_logs, IntegrationError, Service, DEFAULT_JWT_TOKEN}; -use futures_util::Future; use std::{ + fs::File, + future::Future, + io::{ErrorKind, Read}, path::{Path, PathBuf}, process::Command, - time::Duration, }; -fn get_or_create_jwt_path(jwt_path: Option<&PathBuf>) -> PathBuf { - jwt_path.cloned().unwrap_or_else(|| { - let tmp_dir = std::env::temp_dir(); - let jwt_path = tmp_dir.join("jwt.hex"); - std::fs::write(&jwt_path, DEFAULT_JWT_TOKEN).expect("Failed to write JWT secret file"); - jwt_path - }) -} +use std::time::Duration; +use tokio::time::sleep; -#[derive(Default)] +use super::{ + service::{self, Service}, + DEFAULT_JWT_TOKEN, +}; + +#[derive(Default, Debug)] pub struct OpRbuilderConfig { auth_rpc_port: Option, jwt_secret_path: Option, @@ -160,7 +159,7 @@ impl Service for OpRbuilderConfig { } #[allow(clippy::manual_async_fn)] - fn ready(&self, log_path: &Path) -> impl Future> + Send { + fn ready(&self, log_path: &Path) -> impl Future> + Send { async move { poll_logs( log_path, @@ -172,3 +171,133 @@ impl Service for OpRbuilderConfig { } } } + +#[derive(Default, Debug)] +pub struct OpRethConfig { + auth_rpc_port: Option, + jwt_secret_path: Option, + chain_config_path: Option, + data_dir: Option, + http_port: Option, + network_port: Option, +} + +impl OpRethConfig { + pub fn new() -> Self { + Self::default() + } + + pub fn auth_rpc_port(mut self, port: u16) -> Self { + self.auth_rpc_port = Some(port); + self + } + + pub fn chain_config_path>(mut self, path: P) -> Self { + self.chain_config_path = Some(path.into()); + self + } + + pub fn data_dir>(mut self, path: P) -> Self { + self.data_dir = Some(path.into()); + self + } + + pub fn network_port(mut self, port: u16) -> Self { + self.network_port = Some(port); + self + } +} + +impl Service for OpRethConfig { + fn command(&self) -> Command { + let bin_path = PathBuf::from("op-reth"); + + let mut cmd = Command::new(bin_path); + let jwt_path = get_or_create_jwt_path(self.jwt_secret_path.as_ref()); + + cmd.arg("node") + .arg("--authrpc.port") + .arg( + self.auth_rpc_port + .expect("auth_rpc_port not set") + .to_string(), + ) + .arg("--authrpc.jwtsecret") + .arg( + jwt_path + .to_str() + .expect("Failed to convert jwt_path to string"), + ) + .arg("--chain") + .arg( + self.chain_config_path + .as_ref() + .expect("chain_config_path not set"), + ) + .arg("--datadir") + .arg(self.data_dir.as_ref().expect("data_dir not set")) + .arg("--disable-discovery") + .arg("--color") + .arg("never") + .arg("--port") + .arg(self.network_port.expect("network_port not set").to_string()) + .arg("--ipcdisable"); + + if let Some(http_port) = self.http_port { + cmd.arg("--http") + .arg("--http.port") + .arg(http_port.to_string()); + } + + cmd + } + + #[allow(clippy::manual_async_fn)] + fn ready(&self, log_path: &Path) -> impl Future> + Send { + async move { + poll_logs( + log_path, + "Starting consensus engine", + Duration::from_millis(100), + Duration::from_secs(60), + ) + .await + } + } +} + +fn get_or_create_jwt_path(jwt_path: Option<&PathBuf>) -> PathBuf { + jwt_path.cloned().unwrap_or_else(|| { + let tmp_dir = std::env::temp_dir(); + let jwt_path = tmp_dir.join("jwt.hex"); + std::fs::write(&jwt_path, DEFAULT_JWT_TOKEN).expect("Failed to write JWT secret file"); + jwt_path + }) +} + +/// Helper function to poll logs periodically +pub async fn poll_logs( + log_path: &Path, + pattern: &str, + interval: Duration, + timeout: Duration, +) -> Result<(), service::Error> { + let start = std::time::Instant::now(); + + loop { + if start.elapsed() > timeout { + return Err(service::Error::Spawn(ErrorKind::TimedOut)); + } + + let mut file = File::open(log_path).map_err(|_| service::Error::Logs)?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|_| service::Error::Logs)?; + + if contents.contains(pattern) { + return Ok(()); + } + + sleep(interval).await; + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/service.rs b/crates/builder/op-rbuilder/src/tests/framework/service.rs new file mode 100644 index 00000000..6bc587c4 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/service.rs @@ -0,0 +1,111 @@ +use std::{ + fs::{File, OpenOptions}, + future::Future, + io::{ErrorKind, Read}, + path::{Path, PathBuf}, + process::{Child, Command}, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Binary not found")] + BinaryNotFound, + + #[error("Failed to spawn process")] + Spawn(ErrorKind), + + #[error("Failed initialize log streams")] + Logs, + + #[error("Service is already running")] + ServiceAlreadyRunning, +} + +pub struct ServiceInstance { + process: Option, + pub log_path: PathBuf, +} + +impl ServiceInstance { + pub fn new(name: String, test_dir: PathBuf) -> Self { + let log_path = test_dir.join(format!("{name}.log")); + Self { + process: None, + log_path, + } + } + + pub fn start(&mut self, command: Command) -> Result<(), Error> { + if self.process.is_some() { + return Err(Error::ServiceAlreadyRunning); + } + + let log = open_log_file(&self.log_path)?; + let stdout = log.try_clone().map_err(|_| Error::Logs)?; + let stderr = log.try_clone().map_err(|_| Error::Logs)?; + + let mut cmd = command; + cmd.stdout(stdout).stderr(stderr); + + let child = match cmd.spawn() { + Ok(child) => Ok(child), + Err(e) => match e.kind() { + ErrorKind::NotFound => Err(Error::BinaryNotFound), + e => Err(Error::Spawn(e)), + }, + }?; + + self.process = Some(child); + Ok(()) + } + + pub fn stop(&mut self) -> Result<(), Error> { + if let Some(mut process) = self.process.take() { + return process.kill().map_err(|e| Error::Spawn(e.kind())); + } + Ok(()) + } + + /// Start a service using its configuration and wait for it to be ready + pub async fn start_with_config(&mut self, config: &T) -> Result<(), Error> { + self.start(config.command())?; + config.ready(&self.log_path).await?; + Ok(()) + } + + pub async fn find_log_line(&self, pattern: &str) -> eyre::Result<()> { + let mut file = + File::open(&self.log_path).map_err(|_| eyre::eyre!("Failed to open log file"))?; + let mut contents = String::new(); + file.read_to_string(&mut contents) + .map_err(|_| eyre::eyre!("Failed to read log file"))?; + + if contents.contains(pattern) { + Ok(()) + } else { + Err(eyre::eyre!("Pattern not found in log file: {}", pattern)) + } + } +} + +pub struct IntegrationFramework; + +pub trait Service { + /// Configure and return the command to run the service + fn command(&self) -> Command; + + /// Return a future that resolves when the service is ready + fn ready(&self, log_path: &Path) -> impl Future> + Send; +} + +fn open_log_file(path: &PathBuf) -> Result { + let prefix = path.parent().unwrap(); + std::fs::create_dir_all(prefix).map_err(|_| Error::Logs)?; + + OpenOptions::new() + .append(true) + .create(true) + .open(path) + .map_err(|_| Error::Logs) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs new file mode 100644 index 00000000..5c55e060 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -0,0 +1,127 @@ +use crate::tx_signer::Signer; +use alloy_consensus::TxEip1559; +use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; +use alloy_primitives::Bytes; +use alloy_provider::{PendingTransactionBuilder, Provider, RootProvider}; +use core::cmp::max; +use op_alloy_consensus::{OpTxEnvelope, OpTypedTransaction}; +use op_alloy_network::Optimism; +use reth_primitives::Recovered; + +use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; + +use super::BUILDER_PRIVATE_KEY; + +#[derive(Clone)] +pub struct TransactionBuilder { + provider: RootProvider, + signer: Option, + nonce: Option, + base_fee: Option, + tx: TxEip1559, +} + +impl TransactionBuilder { + pub fn new(provider: RootProvider) -> Self { + Self { + provider, + signer: None, + nonce: None, + base_fee: None, + tx: TxEip1559 { + chain_id: 901, + gas_limit: 210000, + ..Default::default() + }, + } + } + + pub fn with_signer(mut self, signer: Signer) -> Self { + self.signer = Some(signer); + self + } + + pub fn with_chain_id(mut self, chain_id: u64) -> Self { + self.tx.chain_id = chain_id; + self + } + + pub fn with_nonce(mut self, nonce: u64) -> Self { + self.tx.nonce = nonce; + self + } + + pub fn with_gas_limit(mut self, gas_limit: u64) -> Self { + self.tx.gas_limit = gas_limit; + self + } + + pub fn with_max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self { + self.tx.max_fee_per_gas = max_fee_per_gas; + self + } + + pub fn with_max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self { + self.tx.max_priority_fee_per_gas = max_priority_fee_per_gas; + self + } + + pub fn with_input(mut self, input: Bytes) -> Self { + self.tx.input = input; + self + } + + pub async fn build(mut self) -> Recovered { + let signer = match self.signer { + Some(signer) => signer, + None => Signer::try_from_secret( + BUILDER_PRIVATE_KEY + .parse() + .expect("invalid hardcoded builder private key"), + ) + .expect("Failed to create signer from hardcoded private key"), + }; + + let nonce = match self.nonce { + Some(nonce) => nonce, + None => self + .provider + .get_transaction_count(signer.address) + .pending() + .await + .expect("Failed to get transaction count"), + }; + + let base_fee = match self.base_fee { + Some(base_fee) => base_fee, + None => { + let previous_base_fee = self + .provider + .get_block_by_number(BlockNumberOrTag::Latest) + .await + .expect("failed to get latest block") + .expect("latest block should exist") + .header + .base_fee_per_gas + .expect("base fee should be present in latest block"); + + max(previous_base_fee as u128, MIN_PROTOCOL_BASE_FEE as u128) + } + }; + + self.tx.nonce = nonce; + self.tx.max_fee_per_gas = base_fee + self.tx.max_priority_fee_per_gas; + + signer + .sign_tx(OpTypedTransaction::Eip1559(self.tx)) + .expect("Failed to sign transaction") + } + + pub async fn send(self) -> eyre::Result> { + let provider = self.provider.clone(); + let transaction = self.build().await; + Ok(provider + .send_raw_transaction(transaction.encoded_2718().as_slice()) + .await?) + } +} diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs new file mode 100644 index 00000000..05ed01b6 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -0,0 +1,9 @@ +// base +mod framework; +pub use framework::*; + +#[cfg(not(feature = "flashblocks"))] +mod vanilla; + +#[cfg(feature = "flashblocks")] +mod flashblocks; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs b/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs new file mode 100644 index 00000000..f45fa23f --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs @@ -0,0 +1,7 @@ +#![cfg(test)] + +mod ordering; +mod revert; +mod smoke; + +use super::*; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs b/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs new file mode 100644 index 00000000..f524e57d --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs @@ -0,0 +1,66 @@ +use crate::tests::framework::{TestHarnessBuilder, ONE_ETH}; +use alloy_consensus::Transaction; +use futures::{future::join_all, stream, StreamExt}; + +/// This test ensures that the transactions are ordered by fee priority in the block. +#[tokio::test] +async fn fee_priority_ordering() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("integration_test_fee_priority_ordering") + .build() + .await?; + + let mut generator = harness.block_generator().await?; + let accounts = generator.create_funded_accounts(10, ONE_ETH).await?; + let base_fee = harness.latest_base_fee().await; + + // generate transactions with randomized tips + let txs = join_all(accounts.iter().map(|signer| { + harness + .create_transaction() + .with_signer(*signer) + .with_max_priority_fee_per_gas(rand::random_range(1..50)) + .send() + })) + .await + .into_iter() + .collect::>>()? + .into_iter() + .map(|tx| *tx.tx_hash()) + .collect::>(); + + generator.generate_block().await?; + + // verify all transactions are included in the block + assert!( + stream::iter(txs.iter()) + .all(|tx_hash| async { + harness + .latest_block() + .await + .transactions + .hashes() + .any(|hash| hash == *tx_hash) + }) + .await, + "not all transactions included in the block" + ); + + // verify all transactions are ordered by fee priority + let txs_tips = harness + .latest_block() + .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 + .collect::>(); + + assert!( + txs_tips.is_sorted(), + "Transactions not ordered by fee priority" + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs new file mode 100644 index 00000000..6976ce57 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -0,0 +1,127 @@ +use crate::tests::TestHarnessBuilder; +use alloy_provider::Provider; + +/// This test ensures that the transactions that get reverted an not included in the block +/// are emitted as a log on the builder. +#[tokio::test] +async fn monitor_transaction_drops() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("monitor_transaction_drops") + .with_revert_protection() + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + // send 10 reverting transactions + let mut pending_txn = Vec::new(); + for _ in 0..10 { + let txn = harness.send_revert_transaction().await?; + pending_txn.push(txn); + } + + // generate 10 blocks + for _ in 0..10 { + generator.generate_block().await?; + let latest_block = harness.latest_block().await; + + // blocks should only include two transactions (deposit + builder) + assert_eq!(latest_block.transactions.len(), 2); + } + + // check that the builder emitted logs for the reverted transactions + // with the monitoring logic + // TODO: this is not ideal, lets find a different way to detect this + // Each time a transaction is dropped, it emits a log like this + // 'Transaction event received target="monitoring" tx_hash="" kind="discarded"' + let builder_logs = std::fs::read_to_string(harness.builder_log_path())?; + + for txn in pending_txn { + let txn_log = format!( + "Transaction event received target=\"monitoring\" tx_hash=\"{}\" kind=\"discarded\"", + txn.tx_hash() + ); + + assert!(builder_logs.contains(txn_log.as_str())); + } + + Ok(()) +} + +#[tokio::test] +async fn revert_protection_disabled() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("revert_protection_disabled") + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + for _ in 0..10 { + let valid_tx = harness.send_valid_transaction().await?; + let reverting_tx = harness.send_revert_transaction().await?; + let block_hash = generator.generate_block().await?; + + let block = harness + .provider()? + .get_block_by_hash(block_hash) + .await? + .expect("block"); + + assert!( + block + .transactions + .hashes() + .any(|hash| hash == *valid_tx.tx_hash()), + "successful transaction missing from block" + ); + + assert!( + block + .transactions + .hashes() + .any(|hash| hash == *reverting_tx.tx_hash()), + "reverted transaction missing from block" + ); + } + + Ok(()) +} + +#[tokio::test] +async fn revert_protection() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("revert_protection") + .with_revert_protection() + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + for _ in 0..10 { + let valid_tx = harness.send_valid_transaction().await?; + let reverting_tx = harness.send_revert_transaction().await?; + let block_hash = generator.generate_block().await?; + + let block = harness + .provider()? + .get_block_by_hash(block_hash) + .await? + .expect("block"); + + assert!( + block + .transactions + .hashes() + .any(|hash| hash == *valid_tx.tx_hash()), + "successful transaction missing from block" + ); + + assert!( + !block + .transactions + .hashes() + .any(|hash| hash == *reverting_tx.tx_hash()), + "reverted transaction unexpectedly included in block" + ); + } + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs b/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs new file mode 100644 index 00000000..33109c4a --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs @@ -0,0 +1,130 @@ +use super::framework::TestHarnessBuilder; +use alloy_provider::Provider; +use std::collections::HashSet; + +/// This is a smoke test that ensures that transactions are included in blocks +/// and that the block generator is functioning correctly. +#[tokio::test] +async fn chain_produces_blocks() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("chain_produces_blocks") + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + const SAMPLE_SIZE: usize = 10; + + // ensure that each block has at least two transactions when + // no user transactions are sent. + // the deposit transaction and the block generator's transaction + for _ in 0..SAMPLE_SIZE { + generator + .generate_block() + .await + .expect("Failed to generate block"); + let transactions = harness.latest_block().await.transactions; + assert!( + transactions.len() == 2, + "Block should have exactly two transactions" + ); + } + + // ensure that transactions are included in blocks and each block has all the transactions + // sent to it during its block time + the two mandatory transactions + for _ in 0..SAMPLE_SIZE { + let count = rand::random_range(1..8); + let mut tx_hashes = HashSet::new(); + for _ in 0..count { + let tx = harness + .send_valid_transaction() + .await + .expect("Failed to send transaction"); + let tx_hash = *tx.tx_hash(); + tx_hashes.insert(tx_hash); + } + generator + .generate_block() + .await + .expect("Failed to generate block"); + let transactions = harness.latest_block().await.transactions; + + assert!( + transactions.len() == 2 + count, + "Block should have {} transactions", + 2 + count + ); + + for tx_hash in tx_hashes { + assert!( + transactions.hashes().any(|hash| hash == *tx_hash), + "Transaction {} should be included in the block", + tx_hash + ); + } + } + + Ok(()) +} + +/// Ensures that payloads are generated correctly even when the builder is busy +/// with other requests, such as fcu or getPayload. +#[tokio::test] +async fn get_payload_close_to_fcu() -> eyre::Result<()> { + let test_harness = TestHarnessBuilder::new("get_payload_close_to_fcu") + .build() + .await?; + let mut block_generator = test_harness.block_generator().await?; + + // add some transactions to the pool so that the builder + // is busy when we send the fcu/getPayload requests + for _ in 0..10 { + // Note, for this test it is okay if they are not valid + let _ = test_harness.send_valid_transaction().await?; + } + + let result = tokio::time::timeout( + std::time::Duration::from_secs(1), + block_generator.submit_payload(None, 0, true), + ) + .await; + + // ensure we didn't timeout + let result = result.expect("Submit payload timed out"); + + // ensure we got a payload + assert!(result.is_ok(), "Failed to get payload: {:?}", result); + + Ok(()) +} + +/// This test validates that if we flood the builder with many transactions +/// and we request short block times, the builder can still eventually resolve all the transactions +#[tokio::test] +async fn transaction_flood_no_sleep() -> eyre::Result<()> { + let test_harness = TestHarnessBuilder::new("transaction_flood_no_sleep") + .build() + .await?; + + let mut block_generator = test_harness.block_generator().await?; + let provider = test_harness.provider()?; + + // Send 200 valid transactions to the builder + // More than this and there is an issue with the RPC endpoint not being able to handle the load + let mut transactions = vec![]; + for _ in 0..200 { + let tx = test_harness.send_valid_transaction().await?; + let tx_hash = *tx.tx_hash(); + transactions.push(tx_hash); + } + + // After a 10 blocks all the transactions should be included in a block + for _ in 0..10 { + block_generator.submit_payload(None, 0, true).await.unwrap(); + } + + for tx in transactions { + provider.get_transaction_receipt(tx).await?; + } + + Ok(()) +} From 4ee5a4e87143f35578664e9df74e4fd363d37906 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 20 May 2025 22:35:44 +0700 Subject: [PATCH 094/262] Fix withdrawals root (#56) --- crates/builder/op-rbuilder/Cargo.toml | 2 +- .../op-rbuilder/src/payload_builder.rs | 1 + .../op-rbuilder/src/tests/framework/blocks.rs | 31 +++++++++++-------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 109551f6..853b91dd 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -98,7 +98,7 @@ serde_yaml = { version = "0.9" } # `flashblocks` branch -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "60885346d4cf7f241de82790478195747433d472" } +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "6945842487ac0b0021800589d1660ec9c20cf254" } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 7065fc75..94602896 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -817,6 +817,7 @@ where block_hash, transactions: new_transactions_encoded, withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), + withdrawals_root, }, metadata: serde_json::to_value(&metadata).unwrap_or_default(), }; diff --git a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs index 847025d7..261ff2a5 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs @@ -2,13 +2,13 @@ use crate::tx_signer::Signer; use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; use alloy_primitives::{address, hex, Address, Bytes, TxKind, B256, U256}; use alloy_rpc_types_engine::{ - ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadAttributes, - PayloadStatusEnum, + ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, + PayloadAttributes, PayloadStatusEnum, }; use alloy_rpc_types_eth::Block; use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; use op_alloy_rpc_types_engine::OpPayloadAttributes; -use rollup_boost::{Flashblocks, FlashblocksService}; +use rollup_boost::{Flashblocks, FlashblocksService, OpExecutionPayloadEnvelope, Version}; use super::apis::EngineApi; @@ -236,15 +236,24 @@ impl BlockGenerator { } let payload = if let Some(flashblocks_service) = &self.flashblocks_service { - flashblocks_service.get_best_payload().await?.unwrap() + flashblocks_service + .get_best_payload(Version::V3) + .await? + .unwrap() } else { - self.engine_api.get_payload_v3(payload_id).await? + OpExecutionPayloadEnvelope::V3(self.engine_api.get_payload_v3(payload_id).await?) + }; + + let execution_payload = if let ExecutionPayload::V3(execution_payload) = payload.into() { + execution_payload + } else { + return Err(eyre::eyre!("execution_payload should be V3")); }; // Validate with builder node let validation_status = self .engine_api - .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) + .new_payload(execution_payload.clone(), vec![], B256::ZERO) .await?; if validation_status.status != PayloadStatusEnum::Valid { @@ -254,7 +263,7 @@ impl BlockGenerator { // Validate with validation node if present if let Some(validation_api) = &self.validation_api { let validation_status = validation_api - .new_payload(payload.execution_payload.clone(), vec![], B256::ZERO) + .new_payload(execution_payload.clone(), vec![], B256::ZERO) .await?; if validation_status.status != PayloadStatusEnum::Valid { @@ -262,11 +271,7 @@ impl BlockGenerator { } } - let new_block_hash = payload - .execution_payload - .payload_inner - .payload_inner - .block_hash; + let new_block_hash = execution_payload.payload_inner.payload_inner.block_hash; // Update forkchoice on builder self.engine_api @@ -282,7 +287,7 @@ impl BlockGenerator { // Update internal state self.latest_hash = new_block_hash; - self.timestamp = payload.execution_payload.timestamp(); + self.timestamp = execution_payload.timestamp(); Ok(new_block_hash) } From dbb03df8c547649f2928486c232b19cc17b429b9 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 21 May 2025 17:57:14 +0700 Subject: [PATCH 095/262] Bump reth to 1.4.1 (#54) * Bump reth to 1.4.1 * Fix integration * Fix test * Fix merge conflict --- crates/builder/op-rbuilder/Cargo.toml | 6 +-- .../op-rbuilder/src/payload_builder.rs | 18 +++++++-- .../src/payload_builder_vanilla.rs | 18 +++++++-- .../op-rbuilder/src/tests/framework/apis.rs | 39 ++++++++++--------- .../src/tests/framework/harness.rs | 2 +- crates/builder/op-rbuilder/src/tx_signer.rs | 3 +- 6 files changed, 53 insertions(+), 33 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 853b91dd..e3a0ecb2 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -84,7 +84,7 @@ thiserror.workspace = true parking_lot.workspace = true url.workspace = true -tower = "0.4" +tower = "0.5" futures = "0.3" futures-util = "0.3.31" time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } @@ -97,8 +97,8 @@ shellexpand = "3.1" serde_yaml = { version = "0.9" } -# `flashblocks` branch -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "6945842487ac0b0021800589d1660ec9c20cf254" } +# `msozin/flashblocks-v1.4.1` branch based on `flashblocks-rebase` +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "8506dfb7d84c65746f7c88d250983658438f59e8" } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 94602896..791a5657 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -31,7 +31,7 @@ use reth_evm::{ Database, Evm, EvmError, InvalidTxError, }; use reth_execution_types::ExecutionOutcome; -use reth_node_api::{NodePrimitives, NodeTypes, TxTy}; +use reth_node_api::{NodePrimitives, NodeTypes, PrimitivesTy, TxTy}; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; @@ -123,7 +123,7 @@ impl CustomOpPayloadBuilder { } } -impl PayloadBuilderBuilder for CustomOpPayloadBuilder +impl PayloadBuilderBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< Types: NodeTypes< @@ -135,6 +135,10 @@ where Pool: TransactionPool>> + Unpin + 'static, + Evm: ConfigureEvm< + Primitives = PrimitivesTy, + NextBlockEnvCtx = OpNextBlockEnvAttributes, + > + 'static, { type PayloadBuilder = OpPayloadBuilder; @@ -142,6 +146,7 @@ where self, ctx: &BuilderContext, pool: Pool, + _evm_config: Evm, ) -> eyre::Result { Ok(OpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), @@ -154,7 +159,7 @@ where } } -impl PayloadServiceBuilder for CustomOpPayloadBuilder +impl PayloadServiceBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< Types: NodeTypes< @@ -167,16 +172,21 @@ where + Unpin + 'static, ::Transaction: OpPooledTx, + Evm: ConfigureEvm< + Primitives = PrimitivesTy, + NextBlockEnvCtx = OpNextBlockEnvAttributes, + > + 'static, { async fn spawn_payload_builder_service( self, ctx: &BuilderContext, pool: Pool, + evm_config: Evm, ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); let extra_block_deadline = self.extra_block_deadline; let enable_revert_protection = self.enable_revert_protection; - let payload_builder = self.build_payload_builder(ctx, pool).await?; + let payload_builder = self.build_payload_builder(ctx, pool, evm_config).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); let payload_generator = BlockPayloadJobGenerator::with_builder( diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index bcf99992..e2b1e27c 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -35,7 +35,7 @@ use reth_evm::{ Database, Evm, EvmError, InvalidTxError, }; use reth_execution_types::ExecutionOutcome; -use reth_node_api::{NodePrimitives, NodeTypes, TxTy}; +use reth_node_api::{NodePrimitives, NodeTypes, PrimitivesTy, TxTy}; use reth_node_builder::components::BasicPayloadServiceBuilder; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; @@ -128,7 +128,7 @@ impl CustomOpPayloadBuilder { } } -impl PayloadBuilderBuilder for CustomOpPayloadBuilder +impl PayloadBuilderBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< Types: NodeTypes< @@ -141,6 +141,10 @@ where + Unpin + 'static, ::Transaction: OpPooledTx, + Evm: ConfigureEvm< + Primitives = PrimitivesTy, + NextBlockEnvCtx = OpNextBlockEnvAttributes, + > + 'static, { type PayloadBuilder = OpPayloadBuilderVanilla; @@ -148,6 +152,7 @@ where self, ctx: &BuilderContext, pool: Pool, + _evm_config: Evm, ) -> eyre::Result { Ok(OpPayloadBuilderVanilla::new( OpEvmConfig::optimism(ctx.chain_spec()), @@ -159,7 +164,7 @@ where } } -impl PayloadServiceBuilder for CustomOpPayloadBuilder +impl PayloadServiceBuilder for CustomOpPayloadBuilder where Node: FullNodeTypes< Types: NodeTypes< @@ -172,16 +177,21 @@ where + Unpin + 'static, ::Transaction: OpPooledTx, + Evm: ConfigureEvm< + Primitives = PrimitivesTy, + NextBlockEnvCtx = OpNextBlockEnvAttributes, + > + 'static, { async fn spawn_payload_builder_service( self, ctx: &BuilderContext, pool: Pool, + evm_config: Evm, ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); let extra_block_deadline = self.extra_block_deadline; let enable_revert_protection = self.enable_revert_protection; - let payload_builder = self.build_payload_builder(ctx, pool).await?; + let payload_builder = self.build_payload_builder(ctx, pool, evm_config).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); let payload_generator = BlockPayloadJobGenerator::with_builder( diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs index 37ac84dc..cdcba088 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -3,21 +3,21 @@ use alloy_eips::BlockNumberOrTag; use alloy_primitives::B256; use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatus}; use jsonrpsee::{ - core::RpcResult, - http_client::{transport::HttpBackend, HttpClient}, + core::{client::SubscriptionClientT, RpcResult}, proc_macros::rpc, }; use reth::rpc::{api::EngineApiClient, types::engine::ForkchoiceState}; use reth_node_api::{EngineTypes, PayloadTypes}; use reth_optimism_node::OpEngineTypes; use reth_payload_builder::PayloadId; -use reth_rpc_layer::{AuthClientLayer, AuthClientService, JwtSecret}; +use reth_rpc_layer::{AuthClientLayer, JwtSecret}; use serde_json::Value; use std::str::FromStr; /// Helper for engine api operations pub struct EngineApi { - pub engine_api_client: HttpClient>, + url: url::Url, + jwt_secret: JwtSecret, } /// Builder for EngineApi configuration @@ -46,15 +46,9 @@ impl EngineApiBuilder { } pub fn build(self) -> Result> { - let secret_layer = AuthClientLayer::new(JwtSecret::from_str(&self.jwt_secret)?); - let middleware = tower::ServiceBuilder::default().layer(secret_layer); - let client = jsonrpsee::http_client::HttpClientBuilder::default() - .set_http_middleware(middleware) - .build(&self.url) - .expect("Failed to create http client"); - Ok(EngineApi { - engine_api_client: client, + url: self.url.parse()?, + jwt_secret: JwtSecret::from_str(&self.jwt_secret)?, }) } } @@ -74,6 +68,16 @@ impl EngineApi { .build() } + pub fn http_client(&self) -> impl SubscriptionClientT + Clone + Send + Sync + Unpin + 'static { + // Create a middleware that adds a new JWT token to every request. + let secret_layer = AuthClientLayer::new(self.jwt_secret); + let middleware = tower::ServiceBuilder::default().layer(secret_layer); + jsonrpsee::http_client::HttpClientBuilder::default() + .set_http_middleware(middleware) + .build(&self.url) + .expect("Failed to create http client") + } + pub async fn get_payload_v3( &self, payload_id: PayloadId, @@ -85,7 +89,7 @@ impl EngineApi { ); Ok( - EngineApiClient::::get_payload_v3(&self.engine_api_client, payload_id) + EngineApiClient::::get_payload_v3(&self.http_client(), payload_id) .await?, ) } @@ -99,7 +103,7 @@ impl EngineApi { println!("Submitting new payload at {}...", chrono::Utc::now()); Ok(EngineApiClient::::new_payload_v3( - &self.engine_api_client, + &self.http_client(), payload, versioned_hashes, parent_beacon_block_root, @@ -116,7 +120,7 @@ impl EngineApi { println!("Updating forkchoice at {}...", chrono::Utc::now()); Ok(EngineApiClient::::fork_choice_updated_v3( - &self.engine_api_client, + &self.http_client(), ForkchoiceState { head_block_hash: new_head, safe_block_hash: current_head, @@ -137,10 +141,7 @@ impl EngineApi { number: BlockNumberOrTag, include_txs: bool, ) -> eyre::Result> { - Ok( - BlockApiClient::get_block_by_number(&self.engine_api_client, number, include_txs) - .await?, - ) + Ok(BlockApiClient::get_block_by_number(&self.http_client(), number, include_txs).await?) } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/harness.rs b/crates/builder/op-rbuilder/src/tests/framework/harness.rs index c9f355c5..e5a0195d 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/harness.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/harness.rs @@ -149,7 +149,7 @@ impl TestHarness { pub fn provider(&self) -> eyre::Result> { let url = format!("http://localhost:{}", self.builder_http_port); let provider = - ProviderBuilder::::default().on_http(url.parse()?); + ProviderBuilder::::default().connect_http(url.parse()?); Ok(provider) } diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs index 6a6a2c5b..bbfb77ff 100644 --- a/crates/builder/op-rbuilder/src/tx_signer.rs +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -70,9 +70,8 @@ impl FromStr for Signer { #[cfg(test)] mod test { use super::*; - use alloy_consensus::TxEip1559; + use alloy_consensus::{transaction::SignerRecoverable, TxEip1559}; use alloy_primitives::{address, fixed_bytes, TxKind as TransactionKind}; - use reth::core::primitives::SignedTransaction; #[test] fn test_sign_transaction() { let secret = From 02311cf5ee7d9165aaa72b9aec34c2568234165d Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 21 May 2025 12:26:19 +0100 Subject: [PATCH 096/262] Add helper utility to check for block inclusion in e2e tests (#60) * Add helper utility to check for block inclusion in e2e tests * Fix lint * Fix --- .../op-rbuilder/src/bin/tester/main.rs | 11 ++-- .../op-rbuilder/src/tests/framework/blocks.rs | 38 ++++++++++++-- .../op-rbuilder/src/tests/vanilla/revert.rs | 51 +++---------------- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/crates/builder/op-rbuilder/src/bin/tester/main.rs b/crates/builder/op-rbuilder/src/bin/tester/main.rs index b8613808..10768caa 100644 --- a/crates/builder/op-rbuilder/src/bin/tester/main.rs +++ b/crates/builder/op-rbuilder/src/bin/tester/main.rs @@ -71,8 +71,11 @@ async fn main() -> eyre::Result<()> { generator.init().await?; - let block_hash = generator.deposit(address, amount).await?; - println!("Deposit transaction included in block: {block_hash}"); + let block_generated = generator.deposit(address, amount).await?; + println!( + "Deposit transaction included in block: {:?}", + block_generated.block_hash() + ); Ok(()) } } @@ -108,7 +111,7 @@ pub async fn run_system( // Infinite loop generating blocks loop { println!("Generating new block..."); - let block_hash = generator.submit_payload(None, 0, no_sleep).await?; - println!("Generated block: {block_hash}"); + let block_generated = generator.submit_payload(None, 0, no_sleep).await?; + println!("Generated block: {:?}", block_generated.block_hash()); } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs index 261ff2a5..fb4012f0 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs @@ -161,7 +161,7 @@ impl BlockGenerator { transactions: Option>, block_building_delay_secs: u64, no_sleep: bool, // TODO: Change this, too many parameters we can tweak here to put as a function arguments - ) -> eyre::Result { + ) -> eyre::Result { let timestamp = self.timestamp + self.block_time_secs; // Add L1 block info as the first transaction in every L2 block @@ -289,20 +289,29 @@ impl BlockGenerator { self.latest_hash = new_block_hash; self.timestamp = execution_payload.timestamp(); - Ok(new_block_hash) + let block = self + .engine_api + .get_block_by_number(BlockNumberOrTag::Latest, false) + .await? + .expect("block not found"); + + assert_eq!(block.header.hash, new_block_hash); + + let generated_block = BlockGenerated { block }; + Ok(generated_block) } /// Generate a single new block and return its hash - pub async fn generate_block(&mut self) -> eyre::Result { + pub async fn generate_block(&mut self) -> eyre::Result { self.submit_payload(None, 0, false).await } - pub async fn generate_block_with_delay(&mut self, delay: u64) -> eyre::Result { + pub async fn generate_block_with_delay(&mut self, delay: u64) -> eyre::Result { self.submit_payload(None, delay, false).await } /// Submit a deposit transaction to seed an account with ETH - pub async fn deposit(&mut self, address: Address, value: u128) -> eyre::Result { + pub async fn deposit(&mut self, address: Address, value: u128) -> eyre::Result { // Create deposit transaction let deposit_tx = TxDeposit { source_hash: B256::default(), @@ -345,3 +354,22 @@ impl BlockGenerator { Ok(signers) } } + +#[derive(Debug)] +pub struct BlockGenerated { + pub block: Block, +} + +impl BlockGenerated { + pub fn block_hash(&self) -> B256 { + self.block.header.hash + } + + pub fn not_includes(&self, tx_hash: B256) -> bool { + !self.includes(tx_hash) + } + + pub fn includes(&self, tx_hash: B256) -> bool { + self.block.transactions.hashes().any(|hash| hash == tx_hash) + } +} diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs index 6976ce57..67d4177e 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -1,5 +1,4 @@ use crate::tests::TestHarnessBuilder; -use alloy_provider::Provider; /// This test ensures that the transactions that get reverted an not included in the block /// are emitted as a log on the builder. @@ -58,29 +57,10 @@ async fn revert_protection_disabled() -> eyre::Result<()> { for _ in 0..10 { let valid_tx = harness.send_valid_transaction().await?; let reverting_tx = harness.send_revert_transaction().await?; - let block_hash = generator.generate_block().await?; - - let block = harness - .provider()? - .get_block_by_hash(block_hash) - .await? - .expect("block"); - - assert!( - block - .transactions - .hashes() - .any(|hash| hash == *valid_tx.tx_hash()), - "successful transaction missing from block" - ); + let block_generated = generator.generate_block().await?; - assert!( - block - .transactions - .hashes() - .any(|hash| hash == *reverting_tx.tx_hash()), - "reverted transaction missing from block" - ); + assert!(block_generated.includes(*valid_tx.tx_hash())); + assert!(block_generated.includes(*reverting_tx.tx_hash())); } Ok(()) @@ -98,29 +78,10 @@ async fn revert_protection() -> eyre::Result<()> { for _ in 0..10 { let valid_tx = harness.send_valid_transaction().await?; let reverting_tx = harness.send_revert_transaction().await?; - let block_hash = generator.generate_block().await?; - - let block = harness - .provider()? - .get_block_by_hash(block_hash) - .await? - .expect("block"); - - assert!( - block - .transactions - .hashes() - .any(|hash| hash == *valid_tx.tx_hash()), - "successful transaction missing from block" - ); + let block_generated = generator.generate_block().await?; - assert!( - !block - .transactions - .hashes() - .any(|hash| hash == *reverting_tx.tx_hash()), - "reverted transaction unexpectedly included in block" - ); + assert!(block_generated.includes(*valid_tx.tx_hash())); + assert!(block_generated.not_includes(*reverting_tx.tx_hash())); } Ok(()) From bcc255bb5ad3ca7208be1b64ea239e050c7dbafa Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 23 May 2025 16:26:32 +0100 Subject: [PATCH 097/262] Add opt-in revert protection (#59) * Add revert protection endpoint * more stuff * Finish merge * A bit more stuff * Finish tests * Remove print * Fix lint * Fix test * Fix * Fix lint * Apply feedback * Bundle with more transactions + more safe checks * Rename bundle fields * Rename to a more appropiate bundle * Rename * Move TransactionConditional to Bundle file * Update test comments * Handle unwraps * Fix lint * Fix lint --- crates/builder/op-rbuilder/Cargo.toml | 3 + crates/builder/op-rbuilder/src/generator.rs | 12 - crates/builder/op-rbuilder/src/main.rs | 60 +++- crates/builder/op-rbuilder/src/metrics.rs | 1 + .../op-rbuilder/src/monitor_tx_pool.rs | 6 +- .../op-rbuilder/src/payload_builder.rs | 5 - .../src/payload_builder_vanilla.rs | 239 +++------------- .../op-rbuilder/src/primitives/bundle.rs | 26 ++ .../builder/op-rbuilder/src/primitives/mod.rs | 2 + .../src/primitives/reth/execution.rs | 6 +- .../op-rbuilder/src/revert_protection.rs | 97 +++++++ .../src/tests/framework/harness.rs | 72 ++++- .../op-rbuilder/src/tests/framework/mod.rs | 3 + .../op-rbuilder/src/tests/framework/op.rs | 4 +- .../op-rbuilder/src/tests/framework/txs.rs | 55 +++- .../op-rbuilder/src/tests/vanilla/revert.rs | 204 +++++++++++--- crates/builder/op-rbuilder/src/tx.rs | 259 ++++++++++++++++++ 17 files changed, 771 insertions(+), 283 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/primitives/bundle.rs create mode 100644 crates/builder/op-rbuilder/src/revert_protection.rs create mode 100644 crates/builder/op-rbuilder/src/tx.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index e3a0ecb2..595beaf4 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -43,6 +43,7 @@ reth-network-peers.workspace = true reth-testing-utils.workspace = true reth-optimism-forks.workspace = true reth-node-builder.workspace = true +reth-rpc-eth-types.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true @@ -61,6 +62,7 @@ alloy-serde.workspace = true alloy-op-evm.workspace = true op-alloy-consensus.workspace = true op-alloy-rpc-types-engine.workspace = true +op-alloy-rpc-types.workspace = true op-alloy-network.workspace = true revm.workspace = true @@ -73,6 +75,7 @@ serde.workspace = true secp256k1.workspace = true tokio.workspace = true jsonrpsee = { workspace = true } +jsonrpsee-types.workspace = true async-trait = { workspace = true } clap_builder = { workspace = true } clap.workspace = true diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/generator.rs index 586aa313..eefe6200 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/generator.rs @@ -74,8 +74,6 @@ pub struct BlockPayloadJobGenerator { last_payload: Arc>, /// The extra block deadline in seconds extra_block_deadline: std::time::Duration, - /// Whether to enable revert protection - enable_revert_protection: bool, } // === impl EmptyBlockPayloadJobGenerator === @@ -90,7 +88,6 @@ impl BlockPayloadJobGenerator { builder: Builder, ensure_only_one_payload: bool, extra_block_deadline: std::time::Duration, - enable_revert_protection: bool, ) -> Self { Self { client, @@ -100,7 +97,6 @@ impl BlockPayloadJobGenerator { ensure_only_one_payload, last_payload: Arc::new(Mutex::new(CancellationToken::new())), extra_block_deadline, - enable_revert_protection, } } } @@ -186,7 +182,6 @@ where cancel: cancel_token, deadline, build_complete: None, - enable_revert_protection: self.enable_revert_protection, }; job.spawn_build_job(); @@ -219,8 +214,6 @@ where pub(crate) cancel: CancellationToken, pub(crate) deadline: Pin>, // Add deadline pub(crate) build_complete: Option>>, - /// Block building options - pub(crate) enable_revert_protection: bool, } impl PayloadJob for BlockPayloadJob @@ -263,8 +256,6 @@ pub struct BuildArguments { pub config: PayloadConfig>, /// A marker that can be used to cancel the job. pub cancel: CancellationToken, - /// Whether to enable revert protection - pub enable_revert_protection: bool, } /// A [PayloadJob] is a future that's being polled by the `PayloadBuilderService` @@ -280,7 +271,6 @@ where let payload_config = self.config.clone(); let cell = self.cell.clone(); let cancel = self.cancel.clone(); - let enable_revert_protection = self.enable_revert_protection; let (tx, rx) = oneshot::channel(); self.build_complete = Some(rx); @@ -290,7 +280,6 @@ where cached_reads: Default::default(), config: payload_config, cancel, - enable_revert_protection, }; let result = builder.try_build(args, cell); @@ -650,7 +639,6 @@ mod tests { builder.clone(), false, std::time::Duration::from_secs(1), - false, ); // this is not nice but necessary diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 0da7a707..5477f1d2 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,7 +1,10 @@ use args::CliExt; use clap::Parser; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; -use reth_optimism_node::{node::OpAddOnsBuilder, OpNode}; +use reth_optimism_node::{ + node::{OpAddOnsBuilder, OpPoolBuilder}, + OpNode, +}; use reth_transaction_pool::TransactionPool; /// CLI argument parsing. @@ -9,11 +12,12 @@ pub mod args; pub mod generator; mod metrics; mod monitor_tx_pool; -mod primitives; -mod tx_signer; - #[cfg(feature = "flashblocks")] pub mod payload_builder; +mod primitives; +mod revert_protection; +mod tx; +mod tx_signer; #[cfg(not(feature = "flashblocks"))] mod payload_builder_vanilla; @@ -29,6 +33,8 @@ use metrics::{ VERGEN_CARGO_FEATURES, VERGEN_CARGO_TARGET_TRIPLE, VERGEN_GIT_SHA, }; use monitor_tx_pool::monitor_tx_pool; +use revert_protection::{EthApiOverrideServer, RevertProtectionExt}; +use tx::FBPooledTransaction; // Prefer jemalloc for performance reasons. #[cfg(all(feature = "jemalloc", unix))] @@ -53,20 +59,50 @@ fn main() { let op_node = OpNode::new(rollup_args.clone()); let handle = builder .with_types::() - .with_components(op_node.components().payload(CustomOpPayloadBuilder::new( - builder_args.builder_signer, - std::time::Duration::from_secs(builder_args.extra_block_deadline_secs), - builder_args.enable_revert_protection, - builder_args.flashblocks_ws_url, - builder_args.chain_block_time, - builder_args.flashblock_block_time, - ))) + .with_components( + op_node + .components() + .pool( + OpPoolBuilder::::default() + .with_enable_tx_conditional( + // Revert protection uses the same internal pool logic as conditional transactions + // to garbage collect transactions out of the bundle range. + rollup_args.enable_tx_conditional + || builder_args.enable_revert_protection, + ) + .with_supervisor( + rollup_args.supervisor_http.clone(), + rollup_args.supervisor_safety_level, + ), + ) + .payload(CustomOpPayloadBuilder::new( + builder_args.builder_signer, + std::time::Duration::from_secs(builder_args.extra_block_deadline_secs), + builder_args.flashblocks_ws_url, + builder_args.chain_block_time, + builder_args.flashblock_block_time, + )), + ) .with_add_ons( OpAddOnsBuilder::default() .with_sequencer(rollup_args.sequencer.clone()) .with_enable_tx_conditional(rollup_args.enable_tx_conditional) .build(), ) + .extend_rpc_modules(move |ctx| { + if builder_args.enable_revert_protection { + tracing::info!("Revert protection enabled"); + + let pool = ctx.pool().clone(); + let provider = ctx.provider().clone(); + let revert_protection_ext = RevertProtectionExt::new(pool, provider); + + ctx.modules + .merge_configured(revert_protection_ext.into_rpc())?; + } + + Ok(()) + }) .on_node_started(move |ctx| { version.register_version_metrics(); if builder_args.log_pool_transactions { diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 87b79579..e3efa5e0 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -34,6 +34,7 @@ pub struct OpRBuilderMetrics { #[cfg(feature = "flashblocks")] pub messages_sent_count: Counter, /// Total duration of building a block + #[cfg(feature = "flashblocks")] pub total_block_built_duration: Histogram, /// Flashblock build duration #[cfg(feature = "flashblocks")] diff --git a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs index eba1cdde..5bb3a4ea 100644 --- a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs +++ b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs @@ -1,15 +1,15 @@ +use crate::tx::FBPooledTransaction; use futures_util::StreamExt; -use reth_optimism_node::txpool::OpPooledTransaction; use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent}; use tracing::info; -pub async fn monitor_tx_pool(mut new_transactions: AllTransactionsEvents) { +pub async fn monitor_tx_pool(mut new_transactions: AllTransactionsEvents) { while let Some(event) = new_transactions.next().await { transaction_event_log(event); } } -fn transaction_event_log(event: FullTransactionEvent) { +fn transaction_event_log(event: FullTransactionEvent) { match event { FullTransactionEvent::Pending(hash) => { info!( diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs index 791a5657..053165c3 100644 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ b/crates/builder/op-rbuilder/src/payload_builder.rs @@ -100,14 +100,12 @@ pub struct CustomOpPayloadBuilder { chain_block_time: u64, flashblock_block_time: u64, extra_block_deadline: std::time::Duration, - enable_revert_protection: bool, } impl CustomOpPayloadBuilder { pub fn new( builder_signer: Option, extra_block_deadline: std::time::Duration, - enable_revert_protection: bool, flashblocks_ws_url: String, chain_block_time: u64, flashblock_block_time: u64, @@ -118,7 +116,6 @@ impl CustomOpPayloadBuilder { chain_block_time, flashblock_block_time, extra_block_deadline, - enable_revert_protection, } } } @@ -185,7 +182,6 @@ where ) -> eyre::Result::Payload>> { tracing::info!("Spawning a custom payload builder"); let extra_block_deadline = self.extra_block_deadline; - let enable_revert_protection = self.enable_revert_protection; let payload_builder = self.build_payload_builder(ctx, pool, evm_config).await?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); @@ -196,7 +192,6 @@ where payload_builder, true, extra_block_deadline, - enable_revert_protection, ); let (payload_service, payload_builder) = diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs index e2b1e27c..377aafce 100644 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs @@ -1,7 +1,8 @@ use crate::{ - generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, + generator::BuildArguments, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, + tx::{FBPoolTransaction, MaybeRevertingTransaction}, tx_signer::Signer, }; use alloy_consensus::{ @@ -10,23 +11,17 @@ use alloy_consensus::{ }; use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxHash, TxKind, U256}; +use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxKind, U256}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::Withdrawals; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; use op_revm::OpSpecId; use reth::{ - builder::{ - components::{PayloadBuilderBuilder, PayloadServiceBuilder}, - node::FullNodeTypes, - BuilderContext, - }, + builder::{components::PayloadBuilderBuilder, node::FullNodeTypes, BuilderContext}, core::primitives::InMemorySize, - payload::PayloadBuilderHandle, }; use reth_basic_payload_builder::{ - BasicPayloadJobGeneratorConfig, BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour, - PayloadConfig, + BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour, PayloadConfig, }; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; @@ -35,7 +30,7 @@ use reth_evm::{ Database, Evm, EvmError, InvalidTxError, }; use reth_execution_types::ExecutionOutcome; -use reth_node_api::{NodePrimitives, NodeTypes, PrimitivesTy, TxTy}; +use reth_node_api::{NodePrimitives, NodeTypes, PrimitivesTy}; use reth_node_builder::components::BasicPayloadServiceBuilder; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; @@ -49,16 +44,14 @@ use reth_optimism_payload_builder::{ OpPayloadPrimitives, }; use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; -use reth_optimism_txpool::OpPooledTx; -use reth_payload_builder::PayloadBuilderService; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::PayloadBuilderAttributes; use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; use reth_primitives::{BlockBody, SealedHeader}; use reth_primitives_traits::{proofs, Block as _, RecoveredBlock, SignedTransaction}; use reth_provider::{ - CanonStateSubscriptions, HashedPostStateProvider, ProviderError, StateProviderFactory, - StateRootProvider, StorageRootProvider, + HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, + StorageRootProvider, }; use reth_revm::database::StateProviderDatabase; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; @@ -85,8 +78,8 @@ pub struct ExecutedPayload { #[non_exhaustive] pub struct CustomOpPayloadBuilder { builder_signer: Option, + #[allow(dead_code)] extra_block_deadline: std::time::Duration, - enable_revert_protection: bool, #[cfg(feature = "flashblocks")] flashblocks_ws_url: String, #[cfg(feature = "flashblocks")] @@ -115,7 +108,6 @@ impl CustomOpPayloadBuilder { pub fn new( builder_signer: Option, extra_block_deadline: std::time::Duration, - enable_revert_protection: bool, _flashblocks_ws_url: String, _chain_block_time: u64, _flashblock_block_time: u64, @@ -123,7 +115,6 @@ impl CustomOpPayloadBuilder { BasicPayloadServiceBuilder::new(CustomOpPayloadBuilder { builder_signer, extra_block_deadline, - enable_revert_protection, }) } } @@ -137,10 +128,10 @@ where Primitives = OpPrimitives, >, >, - Pool: TransactionPool>> + Pool: TransactionPool> + Unpin + 'static, - ::Transaction: OpPooledTx, + ::Transaction: FBPoolTransaction, Evm: ConfigureEvm< Primitives = PrimitivesTy, NextBlockEnvCtx = OpNextBlockEnvAttributes, @@ -159,67 +150,14 @@ where self.builder_signer, pool, ctx.provider().clone(), - self.enable_revert_protection, )) } } -impl PayloadServiceBuilder for CustomOpPayloadBuilder -where - Node: FullNodeTypes< - Types: NodeTypes< - Payload = OpEngineTypes, - ChainSpec = OpChainSpec, - Primitives = OpPrimitives, - >, - >, - Pool: TransactionPool>> - + Unpin - + 'static, - ::Transaction: OpPooledTx, - Evm: ConfigureEvm< - Primitives = PrimitivesTy, - NextBlockEnvCtx = OpNextBlockEnvAttributes, - > + 'static, -{ - async fn spawn_payload_builder_service( - self, - ctx: &BuilderContext, - pool: Pool, - evm_config: Evm, - ) -> eyre::Result::Payload>> { - tracing::info!("Spawning a custom payload builder"); - let extra_block_deadline = self.extra_block_deadline; - let enable_revert_protection = self.enable_revert_protection; - let payload_builder = self.build_payload_builder(ctx, pool, evm_config).await?; - let payload_job_config = BasicPayloadJobGeneratorConfig::default(); - - let payload_generator = BlockPayloadJobGenerator::with_builder( - ctx.provider().clone(), - ctx.task_executor().clone(), - payload_job_config, - payload_builder, - false, - extra_block_deadline, - enable_revert_protection, - ); - - let (payload_service, payload_builder) = - PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - - ctx.task_executor() - .spawn_critical("custom payload builder service", Box::pin(payload_service)); - - tracing::info!("Custom payload service started"); - - Ok(payload_builder) - } -} - impl reth_basic_payload_builder::PayloadBuilder for OpPayloadBuilderVanilla where - Pool: TransactionPool>, + Pool: TransactionPool>, Client: StateProviderFactory + ChainSpecProvider + Clone, Txs: OpPayloadTransactions, { @@ -242,22 +180,14 @@ where let args = BuildArguments { cached_reads, config, - enable_revert_protection: self.enable_revert_protection, cancel: CancellationToken::new(), }; - self.build_payload( - args, - |attrs| { - #[allow(clippy::unit_arg)] - self.best_transactions - .best_transactions(pool.clone(), attrs) - }, - |hashes| { - #[allow(clippy::unit_arg)] - self.best_transactions.remove_invalid(pool.clone(), hashes) - }, - ) + self.build_payload(args, |attrs| { + #[allow(clippy::unit_arg)] + self.best_transactions + .best_transactions(pool.clone(), attrs) + }) } fn on_missing_payload( @@ -278,13 +208,10 @@ where config, cached_reads: Default::default(), cancel: Default::default(), - enable_revert_protection: false, }; - self.build_payload( - args, - |_| NoopPayloadTransactions::::default(), - |_| {}, - )? + self.build_payload(args, |_| { + NoopPayloadTransactions::::default() + })? .into_payload() .ok_or_else(|| PayloadBuilderError::MissingPayload) } @@ -308,8 +235,6 @@ pub struct OpPayloadBuilderVanilla { pub best_transactions: Txs, /// The metrics for the builder pub metrics: OpRBuilderMetrics, - /// Whether we enable revert protection - pub enable_revert_protection: bool, } impl OpPayloadBuilderVanilla { @@ -319,16 +244,8 @@ impl OpPayloadBuilderVanilla { builder_signer: Option, pool: Pool, client: Client, - enable_revert_protection: bool, ) -> Self { - Self::with_builder_config( - evm_config, - builder_signer, - pool, - client, - Default::default(), - enable_revert_protection, - ) + Self::with_builder_config(evm_config, builder_signer, pool, client, Default::default()) } pub fn with_builder_config( @@ -337,7 +254,6 @@ impl OpPayloadBuilderVanilla { pool: Pool, client: Client, config: OpBuilderConfig, - enable_revert_protection: bool, ) -> Self { Self { pool, @@ -347,70 +263,13 @@ impl OpPayloadBuilderVanilla { best_transactions: (), metrics: Default::default(), builder_signer, - enable_revert_protection, - } - } -} - -impl PayloadBuilder for OpPayloadBuilderVanilla -where - Client: StateProviderFactory + ChainSpecProvider + Clone, - Pool: TransactionPool>, - Txs: OpPayloadTransactions, -{ - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; - - fn try_build( - &self, - args: BuildArguments, - best_payload: BlockCell, - ) -> Result<(), PayloadBuilderError> { - let pool = self.pool.clone(); - let block_build_start_time = Instant::now(); - - match self.build_payload( - args, - |attrs| { - #[allow(clippy::unit_arg)] - self.best_transactions - .best_transactions(pool.clone(), attrs) - }, - |hashes| { - #[allow(clippy::unit_arg)] - self.best_transactions.remove_invalid(pool.clone(), hashes) - }, - )? { - BuildOutcome::Better { payload, .. } => { - best_payload.set(payload); - self.metrics - .total_block_built_duration - .record(block_build_start_time.elapsed()); - self.metrics.block_built_success.increment(1); - Ok(()) - } - BuildOutcome::Freeze(payload) => { - best_payload.set(payload); - self.metrics - .total_block_built_duration - .record(block_build_start_time.elapsed()); - Ok(()) - } - BuildOutcome::Cancelled => { - tracing::warn!("Payload build cancelled"); - Err(PayloadBuilderError::MissingPayload) - } - _ => { - tracing::warn!("No better payload found"); - Err(PayloadBuilderError::MissingPayload) - } } } } impl OpPayloadBuilderVanilla where - Pool: TransactionPool>, + Pool: TransactionPool>, Client: StateProviderFactory + ChainSpecProvider, { /// Constructs an Optimism payload from the transactions sent via the @@ -425,16 +284,14 @@ where &self, args: BuildArguments, OpBuiltPayload>, best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, - remove_reverted: impl FnOnce(Vec), ) -> Result, PayloadBuilderError> where - Txs: PayloadTransactions>, + Txs: PayloadTransactions>, { let BuildArguments { mut cached_reads, config, cancel, - enable_revert_protection, } = args; let chain_spec = self.client.chain_spec(); @@ -475,11 +332,10 @@ where block_env_attributes, cancel, builder_signer: self.builder_signer, - metrics: Default::default(), - enable_revert_protection, + metrics: self.metrics.clone(), }; - let builder = OpBuilder::new(best, remove_reverted); + let builder = OpBuilder::new(best); let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; let state = StateProviderDatabase::new(state_provider); @@ -521,19 +377,12 @@ where pub struct OpBuilder<'a, Txs> { /// Yields the best transaction to include if transactions from the mempool are allowed. best: Box Txs + 'a>, - /// Removes reverted transactions from the tx pool - #[debug(skip)] - remove_invalid: Box) + 'a>, } impl<'a, Txs> OpBuilder<'a, Txs> { - fn new( - best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, - remove_reverted: impl FnOnce(Vec) + 'a, - ) -> Self { + fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self { Self { best: Box::new(best), - remove_invalid: Box::new(remove_reverted), } } } @@ -547,15 +396,12 @@ impl OpBuilder<'_, Txs> { ) -> Result>, PayloadBuilderError> where N: OpPayloadPrimitives<_TX = OpTransactionSigned>, - Txs: PayloadTransactions>, + Txs: PayloadTransactions>, ChainSpec: EthChainSpec + OpHardforks, DB: Database + AsRef

, P: StorageRootProvider, { - let Self { - best, - remove_invalid, - } = self; + 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 @@ -636,10 +482,9 @@ impl OpBuilder<'_, Txs> { .payload_num_tx .record(info.executed_transactions.len() as f64); - remove_invalid(info.invalid_tx_hashes.iter().copied().collect()); - let payload = ExecutedPayload { info }; + ctx.metrics.block_built_success.increment(1); Ok(BuildOutcomeKind::Better { payload }) } @@ -651,7 +496,7 @@ impl OpBuilder<'_, Txs> { ) -> Result, PayloadBuilderError> where ChainSpec: EthChainSpec + OpHardforks, - Txs: PayloadTransactions>, + Txs: PayloadTransactions>, DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { @@ -813,13 +658,6 @@ pub trait OpPayloadTransactions: Clone + Send + Sync + Unpin + 'sta pool: Pool, attr: BestTransactionsAttributes, ) -> impl PayloadTransactions; - - /// Removes invalid transactions from the tx pool - fn remove_invalid>( - &self, - pool: Pool, - hashes: Vec, - ); } impl OpPayloadTransactions for () { @@ -830,14 +668,6 @@ impl OpPayloadTransactions for () { ) -> impl PayloadTransactions { BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) } - - fn remove_invalid>( - &self, - pool: Pool, - hashes: Vec, - ) { - pool.remove_transactions(hashes); - } } /// Container type that holds all necessities to build a new payload. @@ -861,8 +691,6 @@ pub struct OpPayloadBuilderCtx { pub builder_signer: Option, /// The metrics for the builder pub metrics: OpRBuilderMetrics, - /// Whether we enabled revert protection - pub enable_revert_protection: bool, } impl OpPayloadBuilderCtx @@ -1124,7 +952,7 @@ where info: &mut ExecutionInfo, db: &mut State, mut best_txs: impl PayloadTransactions< - Transaction: PoolTransaction, + Transaction: FBPoolTransaction, >, block_gas_limit: u64, block_da_limit: Option, @@ -1142,6 +970,8 @@ where let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); while let Some(tx) = best_txs.next(()) { + let exclude_reverting_txs = tx.exclude_reverting_txs(); + let tx = tx.into_consensus(); num_txs_considered += 1; // ensure we still have capacity for this transaction @@ -1195,10 +1025,9 @@ where num_txs_simulated_success += 1; } else { num_txs_simulated_fail += 1; - if self.enable_revert_protection { + if exclude_reverting_txs { info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); - info.invalid_tx_hashes.insert(tx.tx_hash()); continue; } } diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs new file mode 100644 index 00000000..89c53071 --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -0,0 +1,26 @@ +use alloy_primitives::Bytes; +use alloy_rpc_types_eth::erc4337::TransactionConditional; +use serde::{Deserialize, Serialize}; + +pub const MAX_BLOCK_RANGE_BLOCKS: u64 = 10; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Bundle { + #[serde(rename = "txs")] + pub transactions: Vec, + + #[serde(rename = "maxBlockNumber")] + pub block_number_max: Option, +} + +impl Bundle { + pub fn conditional(&self) -> TransactionConditional { + TransactionConditional { + block_number_min: None, + block_number_max: self.block_number_max, + known_accounts: Default::default(), + timestamp_max: None, + timestamp_min: None, + } + } +} diff --git a/crates/builder/op-rbuilder/src/primitives/mod.rs b/crates/builder/op-rbuilder/src/primitives/mod.rs index 02615de6..2af3ab8b 100644 --- a/crates/builder/op-rbuilder/src/primitives/mod.rs +++ b/crates/builder/op-rbuilder/src/primitives/mod.rs @@ -1 +1,3 @@ pub mod reth; + +pub mod bundle; diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 6ffdd2d1..3f5bf177 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -1,9 +1,8 @@ //! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570) use alloy_consensus::Transaction; -use alloy_primitives::{private::alloy_rlp::Encodable, Address, TxHash, U256}; +use alloy_primitives::{private::alloy_rlp::Encodable, Address, U256}; use reth_node_api::NodePrimitives; use reth_optimism_primitives::OpReceipt; -use std::collections::HashSet; #[derive(Default, Debug)] pub struct ExecutionInfo { @@ -19,8 +18,6 @@ pub struct ExecutionInfo { pub cumulative_da_bytes_used: u64, /// Tracks fees from executed mempool transactions pub total_fees: U256, - /// Tracks the reverted transaction hashes to remove from the transaction pool - pub invalid_tx_hashes: HashSet, #[cfg(feature = "flashblocks")] /// Index of the last consumed flashblock pub last_flashblock_index: usize, @@ -36,7 +33,6 @@ impl ExecutionInfo { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO, - invalid_tx_hashes: HashSet::new(), #[cfg(feature = "flashblocks")] last_flashblock_index: 0, } diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs new file mode 100644 index 00000000..7f6f42e5 --- /dev/null +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -0,0 +1,97 @@ +use crate::{ + primitives::bundle::{Bundle, MAX_BLOCK_RANGE_BLOCKS}, + tx::{FBPooledTransaction, MaybeRevertingTransaction}, +}; +use alloy_primitives::B256; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, +}; +use reth_optimism_txpool::{conditional::MaybeConditionalTransaction, OpPooledTransaction}; +use reth_provider::StateProviderFactory; +use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; +use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool}; + +// Namespace overrides for revert protection support +#[cfg_attr(not(test), rpc(server, namespace = "eth"))] +#[cfg_attr(test, rpc(server, client, namespace = "eth"))] +pub trait EthApiOverride { + #[method(name = "sendBundle")] + async fn send_bundle(&self, tx: Bundle) -> RpcResult; +} + +pub struct RevertProtectionExt { + pool: Pool, + provider: Provider, +} + +impl RevertProtectionExt { + pub fn new(pool: Pool, provider: Provider) -> Self { + Self { pool, provider } + } +} + +#[async_trait] +impl EthApiOverrideServer for RevertProtectionExt +where + Pool: TransactionPool + Clone + 'static, + Provider: StateProviderFactory + Send + Sync + Clone + 'static, +{ + async fn send_bundle(&self, mut bundle: Bundle) -> RpcResult { + let last_block_number = self + .provider + .best_block_number() + .map_err(|_e| EthApiError::InternalEthError)?; + + // Only one transaction in the bundle is expected + let bundle_transaction = match bundle.transactions.len() { + 0 => { + return Err(EthApiError::InvalidParams( + "bundle must contain at least one transaction".into(), + ) + .into()); + } + 1 => bundle.transactions[0].clone(), + _ => { + return Err(EthApiError::InvalidParams( + "bundle must contain exactly one transaction".into(), + ) + .into()); + } + }; + + if let Some(block_number_max) = bundle.block_number_max { + // The max block cannot be a past block + if block_number_max <= last_block_number { + return Err( + EthApiError::InvalidParams("block_number_max is a past block".into()).into(), + ); + } + + // Validate that it is not greater than the max_block_range + if block_number_max > last_block_number + MAX_BLOCK_RANGE_BLOCKS { + return Err( + EthApiError::InvalidParams("block_number_max is too high".into()).into(), + ); + } + } else { + // If no upper bound is set, use the maximum block range + bundle.block_number_max = Some(last_block_number + MAX_BLOCK_RANGE_BLOCKS); + } + + let recovered = recover_raw_transaction(&bundle_transaction)?; + let mut pool_transaction: FBPooledTransaction = + OpPooledTransaction::from_pooled(recovered).into(); + + pool_transaction.set_exclude_reverting_txs(true); + pool_transaction.set_conditional(bundle.conditional()); + + let hash = self + .pool + .add_transaction(TransactionOrigin::Local, pool_transaction) + .await + .map_err(EthApiError::from)?; + + Ok(hash) + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/harness.rs b/crates/builder/op-rbuilder/src/tests/framework/harness.rs index e5a0195d..64c38d9c 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/harness.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/harness.rs @@ -7,9 +7,9 @@ use super::{ }; use alloy_eips::BlockNumberOrTag; use alloy_network::Network; -use alloy_primitives::hex; +use alloy_primitives::{hex, B256}; use alloy_provider::{ - Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, + ext::TxPoolApi, Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, }; use op_alloy_network::Optimism; use parking_lot::Mutex; @@ -190,8 +190,72 @@ impl TestHarness { BUILDER_PRIVATE_KEY } - pub const fn builder_log_path(&self) -> &PathBuf { - &self.builder_log_path + pub async fn check_tx_in_pool(&self, tx_hash: B256) -> eyre::Result { + let pool_inspect = self + .provider() + .expect("provider not available") + .txpool_content() + .await?; + + let is_pending = pool_inspect.pending.iter().any(|pending_account_map| { + pending_account_map + .1 + .iter() + .any(|(_, tx)| tx.as_recovered().hash() == *tx_hash) + }); + if is_pending { + return Ok(TransactionStatus::Pending); + } + + let is_queued = pool_inspect.queued.iter().any(|queued_account_map| { + queued_account_map + .1 + .iter() + .any(|(_, tx)| tx.as_recovered().hash() == *tx_hash) + }); + if is_queued { + return Ok(TransactionStatus::Queued); + } + + // check that the builder emitted logs for the reverted transactions with the monitoring logic + // this will tell us whether the builder dropped the transaction + // TODO: this is not ideal, lets find a different way to detect this + // Each time a transaction is dropped, it emits a log like this + // Note that this does not tell us the reason why the transaction was dropped. Ideally + // we should know it at this point. + // 'Transaction event received target="monitoring" tx_hash="" kind="discarded"' + let builder_logs = std::fs::read_to_string(&self.builder_log_path)?; + let txn_log = format!( + "Transaction event received target=\"monitoring\" tx_hash=\"{}\" kind=\"discarded\"", + tx_hash, + ); + if builder_logs.contains(txn_log.as_str()) { + return Ok(TransactionStatus::Dropped); + } + + Ok(TransactionStatus::NotFound) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TransactionStatus { + NotFound, + Pending, + Queued, + Dropped, +} + +impl TransactionStatus { + pub fn is_pending(&self) -> bool { + matches!(self, TransactionStatus::Pending) + } + + pub fn is_queued(&self) -> bool { + matches!(self, TransactionStatus::Queued) + } + + pub fn is_dropped(&self) -> bool { + matches!(self, TransactionStatus::Dropped) } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs index 7ebab77f..d8ffff3c 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/mod.rs @@ -15,6 +15,9 @@ pub use txs::*; const BUILDER_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; +const FUNDED_PRIVATE_KEYS: &[&str] = + &["0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"]; + pub const DEFAULT_JWT_TOKEN: &str = "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; diff --git a/crates/builder/op-rbuilder/src/tests/framework/op.rs b/crates/builder/op-rbuilder/src/tests/framework/op.rs index 00a0f7f7..ea66f306 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/op.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/op.rs @@ -137,7 +137,9 @@ impl Service for OpRbuilderConfig { if let Some(http_port) = self.http_port { cmd.arg("--http") .arg("--http.port") - .arg(http_port.to_string()); + .arg(http_port.to_string()) + .arg("--http.api") + .arg("eth,web3,txpool"); } if let Some(flashblocks_ws_url) = &self.flashblocks_ws_url { diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index 5c55e060..b4049673 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -1,7 +1,7 @@ -use crate::tx_signer::Signer; +use crate::{primitives::bundle::Bundle, tx_signer::Signer}; use alloy_consensus::TxEip1559; use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; -use alloy_primitives::Bytes; +use alloy_primitives::{hex, Bytes}; use alloy_provider::{PendingTransactionBuilder, Provider, RootProvider}; use core::cmp::max; use op_alloy_consensus::{OpTxEnvelope, OpTypedTransaction}; @@ -10,7 +10,12 @@ use reth_primitives::Recovered; use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; -use super::BUILDER_PRIVATE_KEY; +use super::FUNDED_PRIVATE_KEYS; + +#[derive(Clone, Copy, Default)] +pub struct BundleOpts { + pub block_number_max: Option, +} #[derive(Clone)] pub struct TransactionBuilder { @@ -19,6 +24,8 @@ pub struct TransactionBuilder { nonce: Option, base_fee: Option, tx: TxEip1559, + bundle_opts: Option, + key: Option, } impl TransactionBuilder { @@ -33,9 +40,16 @@ impl TransactionBuilder { gas_limit: 210000, ..Default::default() }, + bundle_opts: None, + key: None, } } + pub fn with_key(mut self, key: u64) -> Self { + self.key = Some(key); + self + } + pub fn with_signer(mut self, signer: Signer) -> Self { self.signer = Some(signer); self @@ -71,11 +85,21 @@ impl TransactionBuilder { self } + pub fn with_bundle(mut self, bundle_opts: BundleOpts) -> Self { + self.bundle_opts = Some(bundle_opts); + self + } + + pub fn with_revert(mut self) -> Self { + self.tx.input = hex!("60006000fd").into(); + self + } + pub async fn build(mut self) -> Recovered { let signer = match self.signer { Some(signer) => signer, None => Signer::try_from_secret( - BUILDER_PRIVATE_KEY + FUNDED_PRIVATE_KEYS[self.key.unwrap_or(0) as usize] .parse() .expect("invalid hardcoded builder private key"), ) @@ -118,10 +142,31 @@ impl TransactionBuilder { } pub async fn send(self) -> eyre::Result> { + let bundle_opts = self.bundle_opts.clone(); let provider = self.provider.clone(); let transaction = self.build().await; + let transaction_encoded = transaction.encoded_2718(); + + if let Some(bundle_opts) = bundle_opts { + // Send the transaction as a bundle with the bundle options + let bundle = Bundle { + transactions: vec![transaction_encoded.into()], + block_number_max: bundle_opts.block_number_max, + }; + + let tx_hash = provider + .client() + .request("eth_sendBundle", (bundle,)) + .await?; + + return Ok(PendingTransactionBuilder::new( + provider.root().clone(), + tx_hash, + )); + } + Ok(provider - .send_raw_transaction(transaction.encoded_2718().as_slice()) + .send_raw_transaction(transaction_encoded.as_slice()) .await?) } } diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs index 67d4177e..f4710612 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -1,51 +1,61 @@ -use crate::tests::TestHarnessBuilder; +use alloy_provider::PendingTransactionBuilder; +use op_alloy_network::Optimism; -/// This test ensures that the transactions that get reverted an not included in the block -/// are emitted as a log on the builder. +use crate::{ + primitives::bundle::MAX_BLOCK_RANGE_BLOCKS, + tests::{BundleOpts, TestHarness, TestHarnessBuilder}, +}; + +/// This test ensures that the transactions that get reverted and not included in the block, +/// are eventually dropped from the pool once their block range is reached. +/// This test creates N transactions with different block ranges. #[tokio::test] -async fn monitor_transaction_drops() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("monitor_transaction_drops") +async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("revert_protection_monitor_transaction_gc") .with_revert_protection() .build() .await?; let mut generator = harness.block_generator().await?; - // send 10 reverting transactions + // send 10 bundles with different block ranges let mut pending_txn = Vec::new(); - for _ in 0..10 { - let txn = harness.send_revert_transaction().await?; + for i in 1..=10 { + let txn = harness + .create_transaction() + .with_revert() + .with_bundle(BundleOpts { + block_number_max: Some(i), + }) + .send() + .await?; pending_txn.push(txn); } // generate 10 blocks - for _ in 0..10 { - generator.generate_block().await?; - let latest_block = harness.latest_block().await; + for i in 0..10 { + let generated_block = generator.generate_block().await?; // blocks should only include two transactions (deposit + builder) - assert_eq!(latest_block.transactions.len(), 2); - } - - // check that the builder emitted logs for the reverted transactions - // with the monitoring logic - // TODO: this is not ideal, lets find a different way to detect this - // Each time a transaction is dropped, it emits a log like this - // 'Transaction event received target="monitoring" tx_hash="" kind="discarded"' - let builder_logs = std::fs::read_to_string(harness.builder_log_path())?; - - for txn in pending_txn { - let txn_log = format!( - "Transaction event received target=\"monitoring\" tx_hash=\"{}\" kind=\"discarded\"", - txn.tx_hash() - ); - - assert!(builder_logs.contains(txn_log.as_str())); + assert_eq!(generated_block.block.transactions.len(), 2); + + // since we created the 10 transactions with increasing block ranges, as we generate blocks + // one transaction will be gc on each block. + // transactions from [0, i] should be dropped, transactions from [i+1, 10] should be queued + for j in 0..=i { + let status = harness.check_tx_in_pool(*pending_txn[j].tx_hash()).await?; + assert!(status.is_dropped()); + } + for j in i + 1..10 { + let status = harness.check_tx_in_pool(*pending_txn[j].tx_hash()).await?; + assert!(status.is_queued()); + } } Ok(()) } +/// If revert protection is disabled, the transactions that revert are included in the block. #[tokio::test] async fn revert_protection_disabled() -> eyre::Result<()> { let harness = TestHarnessBuilder::new("revert_protection_disabled") @@ -66,22 +76,154 @@ async fn revert_protection_disabled() -> eyre::Result<()> { Ok(()) } +/// If revert protection is disabled, it should not be possible to send a revert bundle +/// since the revert RPC endpoint is not available. +#[tokio::test] +async fn revert_protection_disabled_bundle_endpoint_error() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("revert_protection_disabled_bundle_endpoint_error") + .build() + .await?; + + let res = harness + .create_transaction() + .with_bundle(BundleOpts::default()) + .send() + .await; + + assert!( + res.is_err(), + "Expected error because method is not available" + ); + Ok(()) +} + +/// Test the behaviour of the revert protection bundle, if the bundle **does not** revert +/// the transaction is included in the block. If the bundle reverts, the transaction +/// is not included in the block and tried again for the next bundle range blocks +/// when it will be dropped from the pool. +#[tokio::test] +async fn revert_protection_bundle() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("revert_protection_bundle") + .with_revert_protection() + .build() + .await?; + + let mut generator = harness.block_generator().await?; // Block 1 + + // Test 1: Bundle does not revert + let valid_bundle = harness + .create_transaction() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + let block_generated = generator.generate_block().await?; // Block 2 + assert!(block_generated.includes(*valid_bundle.tx_hash())); + + let bundle_opts = BundleOpts { + block_number_max: Some(4), + }; + + let reverted_bundle = harness + .create_transaction() + .with_revert() + .with_bundle(bundle_opts) + .send() + .await?; + + // Test 2: Bundle reverts. It is not included in the block + let block_generated = generator.generate_block().await?; // Block 3 + assert!(block_generated.not_includes(*reverted_bundle.tx_hash())); + + // After the block the transaction is still pending in the pool + assert!(harness + .check_tx_in_pool(*reverted_bundle.tx_hash()) + .await? + .is_pending()); + + // Test 3: Chain progresses beyond the bundle range. The transaction is dropped from the pool + generator.generate_block().await?; // Block 4 + assert!(harness + .check_tx_in_pool(*reverted_bundle.tx_hash()) + .await? + .is_pending()); + + generator.generate_block().await?; // Block 5 + assert!(harness + .check_tx_in_pool(*reverted_bundle.tx_hash()) + .await? + .is_dropped()); + + Ok(()) +} + +/// Test the range limits for the revert protection bundle. #[tokio::test] -async fn revert_protection() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection") +async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("revert_protection_bundle_range_limits") .with_revert_protection() .build() .await?; let mut generator = harness.block_generator().await?; + // Advance two blocks and try to send a bundle with max block = 1 + generator.generate_block().await?; // Block 1 + generator.generate_block().await?; // Block 2 + + async fn send_bundle( + harness: &TestHarness, + block_number_max: u64, + ) -> eyre::Result> { + harness + .create_transaction() + .with_bundle(BundleOpts { + block_number_max: Some(block_number_max), + }) + .send() + .await + } + + // Max block cannot be a past block + assert!(send_bundle(&harness, 1).await.is_err()); + + // Bundles are valid if their max block in in between the current block and the max block range + let next_valid_block = 3; + + for i in next_valid_block..next_valid_block + MAX_BLOCK_RANGE_BLOCKS { + assert!(send_bundle(&harness, i).await.is_ok()); + } + + // A bundle with a block out of range is invalid + assert!( + send_bundle(&harness, next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1) + .await + .is_err() + ); + + Ok(()) +} + +/// If a transaction reverts and was sent as a normal transaction through the eth_sendRawTransaction +/// bundle, the transaction should be included in the block. +/// This behaviour is the same as the 'revert_protection_disabled' test. +#[tokio::test] +async fn revert_protection_allow_reverted_transactions_without_bundle() -> eyre::Result<()> { + let harness = + TestHarnessBuilder::new("revert_protection_allow_reverted_transactions_without_bundle") + .with_revert_protection() + .build() + .await?; + + let mut generator = harness.block_generator().await?; + for _ in 0..10 { let valid_tx = harness.send_valid_transaction().await?; let reverting_tx = harness.send_revert_transaction().await?; let block_generated = generator.generate_block().await?; assert!(block_generated.includes(*valid_tx.tx_hash())); - assert!(block_generated.not_includes(*reverting_tx.tx_hash())); + assert!(block_generated.includes(*reverting_tx.tx_hash())); } Ok(()) diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs new file mode 100644 index 00000000..023c5123 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -0,0 +1,259 @@ +use std::sync::Arc; + +use alloy_consensus::{ + conditional::BlockConditionalAttributes, BlobTransactionSidecar, BlobTransactionValidationError, +}; +use alloy_eips::{eip7702::SignedAuthorization, Typed2718}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_rpc_types_eth::{erc4337::TransactionConditional, AccessList}; +use reth_optimism_primitives::OpTransactionSigned; +use reth_optimism_txpool::{ + conditional::MaybeConditionalTransaction, estimated_da_size::DataAvailabilitySized, + interop::MaybeInteropTransaction, OpPooledTransaction, +}; +use reth_primitives::{kzg::KzgSettings, Recovered}; +use reth_primitives_traits::InMemorySize; +use reth_transaction_pool::{EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction}; + +pub trait FBPoolTransaction: + EthPoolTransaction + + MaybeInteropTransaction + + MaybeConditionalTransaction + + DataAvailabilitySized + + MaybeRevertingTransaction +{ +} + +#[derive(Clone, Debug)] +pub struct FBPooledTransaction { + pub inner: OpPooledTransaction, + pub exclude_reverting_txs: bool, +} + +impl FBPoolTransaction for FBPooledTransaction {} + +pub trait MaybeRevertingTransaction { + fn set_exclude_reverting_txs(&mut self, exclude: bool); + fn exclude_reverting_txs(&self) -> bool; +} + +impl MaybeRevertingTransaction for FBPooledTransaction { + fn set_exclude_reverting_txs(&mut self, exclude: bool) { + self.exclude_reverting_txs = exclude; + } + + fn exclude_reverting_txs(&self) -> bool { + self.exclude_reverting_txs + } +} + +impl InMemorySize for FBPooledTransaction { + fn size(&self) -> usize { + self.inner.size() + core::mem::size_of::() + } +} + +impl PoolTransaction for FBPooledTransaction { + type TryFromConsensusError = + >::Error; + type Consensus = OpTransactionSigned; + type Pooled = op_alloy_consensus::OpPooledTransaction; + + fn clone_into_consensus(&self) -> Recovered { + self.inner.clone_into_consensus() + } + + fn into_consensus(self) -> Recovered { + self.inner.into_consensus() + } + + fn from_pooled(tx: Recovered) -> Self { + let inner = OpPooledTransaction::from_pooled(tx); + Self { + inner, + exclude_reverting_txs: false, + } + } + + fn hash(&self) -> &TxHash { + self.inner.hash() + } + + fn sender(&self) -> Address { + self.inner.sender() + } + + fn sender_ref(&self) -> &Address { + self.inner.sender_ref() + } + + fn cost(&self) -> &U256 { + self.inner.cost() + } + + fn encoded_length(&self) -> usize { + self.inner.encoded_length() + } +} + +impl Typed2718 for FBPooledTransaction { + fn ty(&self) -> u8 { + self.inner.ty() + } +} + +impl alloy_consensus::Transaction for FBPooledTransaction { + fn chain_id(&self) -> Option { + self.inner.chain_id() + } + + fn nonce(&self) -> u64 { + self.inner.nonce() + } + + fn gas_limit(&self) -> u64 { + self.inner.gas_limit() + } + + fn gas_price(&self) -> Option { + self.inner.gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.inner.max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.inner.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.inner.max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.inner.priority_fee_or_price() + } + + fn effective_gas_price(&self, base_fee: Option) -> u128 { + self.inner.effective_gas_price(base_fee) + } + + fn is_dynamic_fee(&self) -> bool { + self.inner.is_dynamic_fee() + } + + fn kind(&self) -> TxKind { + self.inner.kind() + } + + fn is_create(&self) -> bool { + self.inner.is_create() + } + + fn value(&self) -> U256 { + self.inner.value() + } + + fn input(&self) -> &Bytes { + self.inner.input() + } + + fn access_list(&self) -> Option<&AccessList> { + self.inner.access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.inner.blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.inner.authorization_list() + } +} + +impl EthPoolTransaction for FBPooledTransaction { + fn take_blob(&mut self) -> EthBlobTransactionSidecar { + EthBlobTransactionSidecar::None + } + + fn try_into_pooled_eip4844( + self, + sidecar: Arc, + ) -> Option> { + self.inner.try_into_pooled_eip4844(sidecar) + } + + fn try_from_eip4844( + _tx: Recovered, + _sidecar: BlobTransactionSidecar, + ) -> Option { + None + } + + fn validate_blob( + &self, + _sidecar: &BlobTransactionSidecar, + _settings: &KzgSettings, + ) -> Result<(), BlobTransactionValidationError> { + Err(BlobTransactionValidationError::NotBlobTransaction( + self.ty(), + )) + } +} + +impl MaybeInteropTransaction for FBPooledTransaction { + fn interop_deadline(&self) -> Option { + self.inner.interop_deadline() + } + + fn set_interop_deadline(&self, deadline: u64) { + self.inner.set_interop_deadline(deadline); + } + + fn with_interop_deadline(self, interop: u64) -> Self + where + Self: Sized, + { + self.inner.with_interop_deadline(interop).into() + } +} + +impl DataAvailabilitySized for FBPooledTransaction { + fn estimated_da_size(&self) -> u64 { + self.inner.estimated_da_size() + } +} + +impl From for FBPooledTransaction { + fn from(tx: OpPooledTransaction) -> Self { + Self { + inner: tx, + exclude_reverting_txs: false, + } + } +} + +impl MaybeConditionalTransaction for FBPooledTransaction { + fn set_conditional(&mut self, conditional: TransactionConditional) { + self.inner.set_conditional(conditional); + } + + fn conditional(&self) -> Option<&TransactionConditional> { + self.inner.conditional() + } + + fn has_exceeded_block_attributes(&self, block_attr: &BlockConditionalAttributes) -> bool { + self.inner.has_exceeded_block_attributes(block_attr) + } + + fn with_conditional(self, conditional: TransactionConditional) -> Self + where + Self: Sized, + { + FBPooledTransaction { + inner: self.inner.with_conditional(conditional), + exclude_reverting_txs: self.exclude_reverting_txs, + } + } +} From e155e20e5a05e292ac574c0fc9fce6b1270f94c1 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 26 May 2025 12:14:13 +0200 Subject: [PATCH 098/262] Fix bundle result (#66) --- crates/builder/op-rbuilder/src/primitives/bundle.rs | 8 +++++++- crates/builder/op-rbuilder/src/revert_protection.rs | 10 +++++----- crates/builder/op-rbuilder/src/tests/framework/txs.rs | 9 ++++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index 89c53071..c6ed2416 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -1,4 +1,4 @@ -use alloy_primitives::Bytes; +use alloy_primitives::{Bytes, B256}; use alloy_rpc_types_eth::erc4337::TransactionConditional; use serde::{Deserialize, Serialize}; @@ -24,3 +24,9 @@ impl Bundle { } } } + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct BundleResult { + #[serde(rename = "bundleHash")] + pub bundle_hash: B256, +} diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 7f6f42e5..17ab7368 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -1,8 +1,7 @@ use crate::{ - primitives::bundle::{Bundle, MAX_BLOCK_RANGE_BLOCKS}, + primitives::bundle::{Bundle, BundleResult, MAX_BLOCK_RANGE_BLOCKS}, tx::{FBPooledTransaction, MaybeRevertingTransaction}, }; -use alloy_primitives::B256; use jsonrpsee::{ core::{async_trait, RpcResult}, proc_macros::rpc, @@ -17,7 +16,7 @@ use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool} #[cfg_attr(test, rpc(server, client, namespace = "eth"))] pub trait EthApiOverride { #[method(name = "sendBundle")] - async fn send_bundle(&self, tx: Bundle) -> RpcResult; + async fn send_bundle(&self, tx: Bundle) -> RpcResult; } pub struct RevertProtectionExt { @@ -37,7 +36,7 @@ where Pool: TransactionPool + Clone + 'static, Provider: StateProviderFactory + Send + Sync + Clone + 'static, { - async fn send_bundle(&self, mut bundle: Bundle) -> RpcResult { + async fn send_bundle(&self, mut bundle: Bundle) -> RpcResult { let last_block_number = self .provider .best_block_number() @@ -92,6 +91,7 @@ where .await .map_err(EthApiError::from)?; - Ok(hash) + let result = BundleResult { bundle_hash: hash }; + Ok(result) } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index b4049673..ce06ef85 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -1,4 +1,7 @@ -use crate::{primitives::bundle::Bundle, tx_signer::Signer}; +use crate::{ + primitives::bundle::{Bundle, BundleResult}, + tx_signer::Signer, +}; use alloy_consensus::TxEip1559; use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; use alloy_primitives::{hex, Bytes}; @@ -154,14 +157,14 @@ impl TransactionBuilder { block_number_max: bundle_opts.block_number_max, }; - let tx_hash = provider + let result: BundleResult = provider .client() .request("eth_sendBundle", (bundle,)) .await?; return Ok(PendingTransactionBuilder::new( provider.root().clone(), - tx_hash, + result.bundle_hash, )); } From a8c7ee86c5ef7185279d366c978252b6bc229c1f Mon Sep 17 00:00:00 2001 From: Karim Agha Date: Mon, 26 May 2025 15:44:57 +0200 Subject: [PATCH 099/262] Remove flashblocks conditional compilation (#67) --- crates/builder/op-rbuilder/Cargo.toml | 1 - crates/builder/op-rbuilder/src/args/mod.rs | 80 +- crates/builder/op-rbuilder/src/args/op.rs | 13 + .../op-rbuilder/src/args/playground.rs | 50 +- .../op-rbuilder/src/builders/context.rs | 558 ++++++++ .../src/builders/flashblocks/config.rs | 55 + .../src/builders/flashblocks/mod.rs | 34 + .../src/builders/flashblocks/payload.rs | 650 +++++++++ .../src/builders/flashblocks/service.rs | 56 + .../src/builders/flashblocks/wspub.rs | 239 ++++ .../src/{ => builders}/generator.rs | 5 - .../builder/op-rbuilder/src/builders/mod.rs | 163 +++ .../op-rbuilder/src/builders/standard/mod.rs | 34 + .../src/builders/standard/payload.rs | 588 ++++++++ crates/builder/op-rbuilder/src/main.rs | 184 +-- crates/builder/op-rbuilder/src/metrics.rs | 5 - .../op-rbuilder/src/payload_builder.rs | 1269 ----------------- .../src/payload_builder_vanilla.rs | 1221 ---------------- .../src/primitives/reth/execution.rs | 20 +- .../op-rbuilder/src/tests/framework/op.rs | 2 + .../op-rbuilder/src/tests/framework/txs.rs | 2 +- crates/builder/op-rbuilder/src/tests/mod.rs | 5 +- crates/builder/op-rbuilder/src/traits.rs | 70 + 23 files changed, 2649 insertions(+), 2655 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/builders/context.rs create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/config.rs create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/service.rs create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs rename crates/builder/op-rbuilder/src/{ => builders}/generator.rs (99%) create mode 100644 crates/builder/op-rbuilder/src/builders/mod.rs create mode 100644 crates/builder/op-rbuilder/src/builders/standard/mod.rs create mode 100644 crates/builder/op-rbuilder/src/builders/standard/payload.rs delete mode 100644 crates/builder/op-rbuilder/src/payload_builder.rs delete mode 100644 crates/builder/op-rbuilder/src/payload_builder_vanilla.rs create mode 100644 crates/builder/op-rbuilder/src/traits.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 595beaf4..40c9d6cd 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -132,7 +132,6 @@ min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] testing = [] -flashblocks = [] [[bin]] name = "op-rbuilder" diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index 25475206..5c87428f 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -1,5 +1,81 @@ +use crate::builders::BuilderMode; +use clap::Parser; +pub use op::OpRbuilderArgs; +use playground::PlaygroundOptions; +use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; + mod op; mod playground; -pub use op::OpRbuilderArgs; -pub use playground::CliExt; +/// 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. +/// +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; +} + +pub type Cli = reth_optimism_cli::Cli; + +impl CliExt for Cli { + /// Checks if the node is started with the `--builder.playground` flag, + /// and if so, populates the default values for the CLI arguments from the + /// playground configuration. + /// + /// The `--builder.playground` flag is used to populate the CLI arguments with + /// default values for running the builder against the playground environment. + /// + /// The values are populated from the default directory of the playground + /// configuration, which is `$HOME/.playground/devnet/` by default. + /// + /// Any manually specified CLI arguments by the user will override the defaults. + fn populate_defaults(self) -> Self { + let Commands::Node(ref node_command) = self.command else { + // playground defaults are only relevant if running the node commands. + return self; + }; + + let Some(ref playground_dir) = node_command.ext.playground else { + // not running in playground mode. + return self; + }; + + let options = match PlaygroundOptions::new(playground_dir) { + Ok(options) => options, + Err(e) => exit(e), + }; + + options.apply(self) + } + + fn parsed() -> Self { + Cli::parse().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 { + if node_command.ext.enable_flashblocks { + return BuilderMode::Flashblocks; + } + } + BuilderMode::Standard + } +} + +/// Following clap's convention, a failure to parse the command line arguments +/// will result in terminating the program with a non-zero exit code. +fn exit(error: eyre::Report) -> ! { + eprintln!("{error}"); + std::process::exit(-1); +} diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 5818a5c1..44dcae78 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -19,6 +19,19 @@ pub struct OpRbuilderArgs { /// Builder secret key for signing last transaction in block #[arg(long = "rollup.builder-secret-key", env = "BUILDER_SECRET_KEY")] pub builder_signer: Option, + + /// 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 = "rollup.enable-flashblocks", + default_value = "false", + env = "ENABLE_FLASHBLOCKS" + )] + pub enable_flashblocks: bool, + /// Websocket port for flashblock payload builder #[arg( long = "rollup.flashblocks-ws-url", diff --git a/crates/builder/op-rbuilder/src/args/playground.rs b/crates/builder/op-rbuilder/src/args/playground.rs index 6898eb9e..b02a0f4c 100644 --- a/crates/builder/op-rbuilder/src/args/playground.rs +++ b/crates/builder/op-rbuilder/src/args/playground.rs @@ -27,7 +27,6 @@ //! directory to use. This is useful for testing against different playground //! configurations. -use super::OpRbuilderArgs; use alloy_primitives::hex; use clap::{parser::ValueSource, CommandFactory}; use core::{ @@ -49,47 +48,9 @@ use std::{ }; use url::{Host, Url}; -/// This trait is used to extend Reth's CLI with additional functionality that -/// populates the default values for the command line arguments when the user -/// specifies that they want to use the playground. -/// -/// The `--builder.playground` flag is used to populate the CLI arguments with -/// default values for running the builder against the playground environment. -/// -/// The values are populated from the default directory of the playground -/// configuration, which is `$HOME/.playground/devnet/` by default. -/// -/// Any manually specified CLI arguments by the user will override the defaults. -pub trait CliExt { - /// Populates the default values for the CLI arguments when the user specifies - /// the `--builder.playground` flag. - fn populate_defaults(self) -> Self; -} - -type Cli = reth_optimism_cli::Cli; - -impl CliExt for Cli { - fn populate_defaults(self) -> Self { - let Commands::Node(ref node_command) = self.command else { - // playground defaults are only relevant if running the node commands. - return self; - }; - - let Some(ref playground_dir) = node_command.ext.playground else { - // not running in playground mode. - return self; - }; +use super::Cli; - let options = match PlaygroundOptions::new(playground_dir) { - Ok(options) => options, - Err(e) => exit(e), - }; - - options.apply(self) - } -} - -struct PlaygroundOptions { +pub struct PlaygroundOptions { /// Sets node.chain in NodeCommand pub chain: Arc, @@ -210,13 +171,6 @@ impl PlaygroundOptions { } } -/// Following clap's convention, a failure to parse the command line arguments -/// will result in terminating the program with a non-zero exit code. -fn exit(error: eyre::Report) -> ! { - eprintln!("{error}"); - std::process::exit(-1); -} - fn existing_path(base: &Path, relative: &str) -> Result { let path = base.join(relative); if path.exists() { diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs new file mode 100644 index 00000000..04e63158 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -0,0 +1,558 @@ +use alloy_consensus::{Eip658Value, Transaction, TxEip1559}; +use alloy_eips::Typed2718; +use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; +use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxKind, U256}; +use alloy_rpc_types_eth::Withdrawals; +use core::fmt::Debug; +use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; +use op_revm::OpSpecId; +use reth::payload::PayloadBuilderAttributes; +use reth_basic_payload_builder::PayloadConfig; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_evm::{ + eth::receipt_builder::ReceiptBuilderCtx, ConfigureEvm, Evm, EvmEnv, EvmError, InvalidTxError, +}; +use reth_node_api::PayloadBuilderError; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_node::OpPayloadBuilderAttributes; +use reth_optimism_payload_builder::{config::OpDAConfig, error::OpPayloadBuilderError}; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +use reth_payload_builder::PayloadId; +use reth_primitives::{Recovered, SealedHeader}; +use reth_primitives_traits::{InMemorySize, SignedTransaction}; +use reth_provider::ProviderError; +use reth_revm::State; +use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; +use revm::{ + context::{result::ResultAndState, Block}, + Database, DatabaseCommit, +}; +use std::{sync::Arc, time::Instant}; +use tokio_util::sync::CancellationToken; +use tracing::{info, trace, warn}; + +use crate::{ + metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::PayloadTxsBounds, + tx::MaybeRevertingTransaction, tx_signer::Signer, +}; + +/// Container type that holds all necessities to build a new payload. +#[derive(Debug)] +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 + pub da_config: OpDAConfig, + /// The chainspec + pub chain_spec: Arc, + /// How to build the payload. + pub config: PayloadConfig>, + /// Evm Settings + pub evm_env: EvmEnv, + /// Block env attributes for the current block. + pub block_env_attributes: OpNextBlockEnvAttributes, + /// Marker to check whether the job has been cancelled. + pub cancel: CancellationToken, + /// The builder signer + pub builder_signer: Option, + /// The metrics for the builder + pub metrics: Arc, +} + +impl OpPayloadBuilderCtx { + /// Returns the parent block the payload will be build on. + pub fn parent(&self) -> &SealedHeader { + &self.config.parent_header + } + + /// Returns the builder attributes. + pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { + &self.config.attributes + } + + /// Returns the withdrawals if shanghai is active. + pub fn withdrawals(&self) -> Option<&Withdrawals> { + self.chain_spec + .is_shanghai_active_at_timestamp(self.attributes().timestamp()) + .then(|| &self.attributes().payload_attributes.withdrawals) + } + + /// Returns the block gas limit to target. + pub fn block_gas_limit(&self) -> u64 { + self.attributes() + .gas_limit + .unwrap_or(self.evm_env.block_env.gas_limit) + } + + /// Returns the block number for the block. + pub fn block_number(&self) -> u64 { + self.evm_env.block_env.number + } + + /// Returns the current base fee + pub fn base_fee(&self) -> u64 { + self.evm_env.block_env.basefee + } + + /// Returns the current blob gas price. + pub fn get_blob_gasprice(&self) -> Option { + self.evm_env + .block_env + .blob_gasprice() + .map(|gasprice| gasprice as u64) + } + + /// Returns the blob fields for the header. + /// + /// This will always return `Some(0)` after ecotone. + pub fn blob_fields(&self) -> (Option, Option) { + // 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. + if self.is_ecotone_active() { + (Some(0), Some(0)) + } else { + (None, None) + } + } + + /// Returns the extra data for the block. + /// + /// After holocene this extracts the extradata from the paylpad + pub fn extra_data(&self) -> Result { + if self.is_holocene_active() { + self.attributes() + .get_holocene_extra_data( + self.chain_spec.base_fee_params_at_timestamp( + self.attributes().payload_attributes.timestamp, + ), + ) + .map_err(PayloadBuilderError::other) + } else { + Ok(Default::default()) + } + } + + /// Returns the current fee settings for transactions from the mempool + pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { + BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) + } + + /// Returns the unique id for this payload job. + pub fn payload_id(&self) -> PayloadId { + self.attributes().payload_id() + } + + /// Returns true if regolith is active for the payload. + pub fn is_regolith_active(&self) -> bool { + self.chain_spec + .is_regolith_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if ecotone is active for the payload. + pub fn is_ecotone_active(&self) -> bool { + self.chain_spec + .is_ecotone_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if canyon is active for the payload. + pub fn is_canyon_active(&self) -> bool { + self.chain_spec + .is_canyon_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if holocene is active for the payload. + pub fn is_holocene_active(&self) -> bool { + self.chain_spec + .is_holocene_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns true if isthmus is active for the payload. + pub fn is_isthmus_active(&self) -> bool { + self.chain_spec + .is_isthmus_active_at_timestamp(self.attributes().timestamp()) + } + + /// Returns the chain id + pub fn chain_id(&self) -> u64 { + self.chain_spec.chain_id() + } + + /// Returns the builder signer + pub fn builder_signer(&self) -> Option { + self.builder_signer + } +} + +impl OpPayloadBuilderCtx { + /// Constructs a receipt for the given transaction. + fn build_receipt( + &self, + ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, + deposit_nonce: Option, + ) -> OpReceipt { + let receipt_builder = self.evm_config.block_executor_factory().receipt_builder(); + match receipt_builder.build_receipt(ctx) { + Ok(receipt) => receipt, + Err(ctx) => { + let receipt = alloy_consensus::Receipt { + // Success flag was added in `EIP-658: Embedding transaction status code + // in receipts`. + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + }; + + receipt_builder.build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_nonce, + // The deposit receipt version was introduced in Canyon to indicate an + // update to how receipt hashes should be computed + // when set. The state transition process ensures + // this is only set for post-Canyon deposit + // transactions. + deposit_receipt_version: self.is_canyon_active().then_some(1), + }) + } + } + } + + /// Executes all sequencer transactions that are included in the payload attributes. + pub fn execute_sequencer_transactions( + &self, + db: &mut State, + ) -> Result, PayloadBuilderError> + where + DB: Database, + { + 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()); + + for sequencer_tx in &self.attributes().transactions { + // A sequencer's block should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + OpPayloadBuilderError::BlobTransactionRejected, + )); + } + + // Convert the transaction to a [Recovered]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` function. + // Deposit transactions do not have signatures, so if the tx is a deposit, this + // will just pull in its `from` address. + let sequencer_tx = sequencer_tx + .value() + .try_clone_into_recovered() + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + // Cache the depositor account prior to the state transition for the deposit nonce. + // + // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces + // were not introduced in Bedrock. In addition, regular transactions don't have deposit + // nonces, so we don't need to touch the DB for those. + let depositor_nonce = (self.is_regolith_active() && sequencer_tx.is_deposit()) + .then(|| { + evm.db_mut() + .load_cache_account(sequencer_tx.signer()) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + }) + .transpose() + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( + sequencer_tx.signer(), + )) + })?; + + let ResultAndState { result, state } = match evm.transact(&sequencer_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue; + } + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); + } + }; + + // add gas used by the transaction to cumulative gas used, before creating the receipt + let gas_used = result.gas_used(); + info.cumulative_gas_used += gas_used; + + let ctx = ReceiptBuilderCtx { + tx: sequencer_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, depositor_nonce)); + + // commit changes + evm.db_mut().commit(state); + + // append sender and transaction to the respective lists + info.executed_senders.push(sequencer_tx.signer()); + info.executed_transactions.push(sequencer_tx.into_inner()); + } + + Ok(info) + } + + /// Executes the given best transactions and updates the execution info. + /// + /// Returns `Ok(Some(())` if the job was cancelled. + pub fn execute_best_transactions( + &self, + info: &mut ExecutionInfo, + db: &mut State, + mut best_txs: impl PayloadTxsBounds, + block_gas_limit: u64, + block_da_limit: Option, + ) -> Result, PayloadBuilderError> + where + DB: Database, + { + let execute_txs_start_time = Instant::now(); + let mut num_txs_considered = 0; + let mut num_txs_simulated = 0; + let mut num_txs_simulated_success = 0; + let mut num_txs_simulated_fail = 0; + let base_fee = self.base_fee(); + let tx_da_limit = self.da_config.max_da_tx_size(); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + + while let Some(tx) = best_txs.next(()) { + let exclude_reverting_txs = tx.exclude_reverting_txs(); + + let tx = tx.into_consensus(); + num_txs_considered += 1; + // ensure we still have capacity for this transaction + if info.is_tx_over_limits(tx.inner(), block_gas_limit, tx_da_limit, block_da_limit) { + // we can't fit this transaction into the block, so we need to mark it as + // invalid which also removes all dependent transaction from + // the iterator before we can continue + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // A sequencer's block should never contain blob or deposit transactions from the pool. + if tx.is_eip4844() || tx.is_deposit() { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + + // check if the job was cancelled, if so we can exit early + if self.cancel.is_cancelled() { + return Ok(Some(())); + } + + let tx_simulation_start_time = Instant::now(); + let ResultAndState { result, state } = match evm.transact(&tx) { + Ok(res) => res, + Err(err) => { + if let Some(err) = err.as_invalid_tx_err() { + if err.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + } + + continue; + } + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); + } + }; + + self.metrics + .tx_simulation_duration + .record(tx_simulation_start_time.elapsed()); + self.metrics.tx_byte_size.record(tx.inner().size() as f64); + num_txs_simulated += 1; + if result.is_success() { + num_txs_simulated_success += 1; + } else { + num_txs_simulated_fail += 1; + if exclude_reverting_txs { + info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + } + + // add gas used by the transaction to cumulative gas used, before creating the + // receipt + let gas_used = result.gas_used(); + info.cumulative_gas_used += gas_used; + + // Push transaction changeset and calculate header bloom filter for receipt. + let ctx = ReceiptBuilderCtx { + tx: tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, None)); + + // commit changes + evm.db_mut().commit(state); + + // update add to total fees + let miner_fee = tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + + // append sender and transaction to the respective lists + info.executed_senders.push(tx.signer()); + info.executed_transactions.push(tx.into_inner()); + } + + self.metrics + .payload_tx_simulation_duration + .record(execute_txs_start_time.elapsed()); + self.metrics + .payload_num_tx_considered + .record(num_txs_considered as f64); + self.metrics + .payload_num_tx_simulated + .record(num_txs_simulated as f64); + self.metrics + .payload_num_tx_simulated_success + .record(num_txs_simulated_success as f64); + self.metrics + .payload_num_tx_simulated_fail + .record(num_txs_simulated_fail as f64); + + Ok(None) + } + + pub fn add_builder_tx( + &self, + info: &mut ExecutionInfo, + db: &mut State, + builder_tx_gas: u64, + message: Vec, + ) -> Option<()> + where + DB: Database, + { + self.builder_signer() + .map(|signer| { + let base_fee = self.base_fee(); + let chain_id = self.chain_id(); + // Create and sign the transaction + let builder_tx = + signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; + + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + + let ResultAndState { result, state } = evm + .transact(&builder_tx) + .map_err(|err| PayloadBuilderError::EvmExecutionError(Box::new(err)))?; + + // Add gas used by the transaction to cumulative gas used, before creating the receipt + let gas_used = result.gas_used(); + info.cumulative_gas_used += gas_used; + + let ctx = ReceiptBuilderCtx { + tx: builder_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, None)); + + // Release the db reference by dropping evm + drop(evm); + // Commit changes + db.commit(state); + + // Append sender and transaction to the respective lists + info.executed_senders.push(builder_tx.signer()); + info.executed_transactions.push(builder_tx.into_inner()); + Ok(()) + }) + .transpose() + .unwrap_or_else(|err: PayloadBuilderError| { + warn!(target: "payload_builder", %err, "Failed to add builder transaction"); + None + }) + } + + /// Calculates EIP 2718 builder transaction size + pub fn estimate_builder_tx_da_size( + &self, + db: &mut State, + builder_tx_gas: u64, + message: Vec, + ) -> Option + where + DB: Database, + { + self.builder_signer() + .map(|signer| { + let base_fee = self.base_fee(); + let chain_id = self.chain_id(); + // Create and sign the transaction + let builder_tx = + signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; + Ok(builder_tx.length()) + }) + .transpose() + .unwrap_or_else(|err: PayloadBuilderError| { + warn!(target: "payload_builder", %err, "Failed to add builder transaction"); + None + }) + } +} + +/// Creates signed builder tx to Address::ZERO and specified message as input +pub fn signed_builder_tx( + db: &mut State, + builder_tx_gas: u64, + message: Vec, + signer: Signer, + base_fee: u64, + chain_id: u64, +) -> Result, PayloadBuilderError> +where + DB: Database, +{ + // Create message with block number for the builder to sign + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed(signer.address)) + })?; + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id, + nonce, + gas_limit: builder_tx_gas, + max_fee_per_gas: 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(PayloadBuilderError::other)?; + + Ok(builder_tx) +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs new file mode 100644 index 00000000..18c9ea6d --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -0,0 +1,55 @@ +use crate::{args::OpRbuilderArgs, builders::BuilderConfig}; +use core::{ + net::{Ipv4Addr, SocketAddr}, + time::Duration, +}; + +/// Configuration values that are specific to the flashblocks builder. +#[derive(Debug, Clone)] +pub struct FlashblocksConfig { + /// The address of the websockets endpoint that listens for subscriptions to + /// new flashblocks updates. + pub ws_addr: SocketAddr, + + /// How often a flashblock is produced. This is independent of the block time of the chain. + /// Each block will contain one or more flashblocks. On average, the number of flashblocks + /// per block is equal to the block time divided by the flashblock interval. + pub interval: Duration, +} + +impl Default for FlashblocksConfig { + fn default() -> Self { + Self { + ws_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 1111), + interval: Duration::from_millis(250), + } + } +} + +impl TryFrom for FlashblocksConfig { + type Error = eyre::Report; + + fn try_from(args: OpRbuilderArgs) -> Result { + let ws_addr = args + .flashblocks_ws_url + .parse() + .map_err(|_| eyre::eyre!("Invalid flashblocks websocket address"))?; + + let interval = Duration::from_millis(args.flashblock_block_time); + + Ok(Self { ws_addr, interval }) + } +} + +pub trait FlashBlocksConfigExt { + fn flashblocks_per_block(&self) -> u64; +} + +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 + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs new file mode 100644 index 00000000..d85259ab --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs @@ -0,0 +1,34 @@ +use super::BuilderConfig; +use crate::traits::{NodeBounds, PoolBounds}; +use config::FlashblocksConfig; +use service::FlashblocksServiceBuilder; + +mod config; +//mod context; +mod payload; +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. +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/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs new file mode 100644 index 00000000..fe1e638f --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -0,0 +1,650 @@ +use core::time::Duration; +use std::{sync::Arc, time::Instant}; + +use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; +use crate::{ + builders::{ + context::OpPayloadBuilderCtx, + flashblocks::config::FlashBlocksConfigExt, + generator::{BlockCell, BuildArguments}, + BuilderConfig, + }, + metrics::OpRBuilderMetrics, + primitives::reth::ExecutionInfo, + traits::{ClientBounds, PoolBounds}, +}; +use alloy_consensus::{ + constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, EMPTY_OMMER_ROOT_HASH, +}; +use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE, Encodable2718}; +use alloy_primitives::{map::foldhash::HashMap, Address, B256, U256}; +use reth::payload::PayloadBuilderAttributes; +use reth_basic_payload_builder::BuildOutcome; +use reth_evm::{execute::BlockBuilder, ConfigureEvm}; +use reth_node_api::{Block, NodePrimitives, PayloadBuilderError}; +use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; +use reth_payload_util::BestPayloadTransactions; +use reth_provider::{ + ExecutionOutcome, HashedPostStateProvider, ProviderError, StateRootProvider, + StorageRootProvider, +}; +use reth_revm::{ + database::StateProviderDatabase, + db::{states::bundle_state::BundleRetention, BundleState}, + State, +}; +use revm::Database; +use rollup_boost::primitives::{ + ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, +}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; +use tracing::{debug, error, warn}; + +#[derive(Debug, Default)] +struct ExtraExecutionInfo { + /// Index of the last consumed flashblock + pub last_flashblock_index: usize, +} + +/// Optimism's payload builder +#[derive(Debug, Clone)] +pub struct OpPayloadBuilder { + /// The type responsible for creating the evm. + pub evm_config: OpEvmConfig, + /// The transaction pool + pub pool: Pool, + /// Node client + pub client: Client, + /// WebSocket publisher for broadcasting flashblocks + /// to all connected subscribers. + pub ws_pub: Arc, + /// System configuration for the builder + pub config: BuilderConfig, + /// The metrics for the builder + pub metrics: Arc, +} + +impl OpPayloadBuilder { + /// `OpPayloadBuilder` constructor. + pub fn new( + evm_config: OpEvmConfig, + pool: Pool, + client: Client, + config: BuilderConfig, + ) -> eyre::Result { + let metrics = Arc::new(OpRBuilderMetrics::default()); + let ws_pub = WebSocketPublisher::new(config.specific.ws_addr, Arc::clone(&metrics))?.into(); + + Ok(Self { + evm_config, + pool, + client, + ws_pub, + config, + metrics, + }) + } +} + +impl reth_basic_payload_builder::PayloadBuilder for OpPayloadBuilder +where + Pool: Clone + Send + Sync, + Client: Clone + Send + Sync, +{ + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + _args: reth_basic_payload_builder::BuildArguments, + ) -> Result, PayloadBuilderError> { + unimplemented!() + } + + fn build_empty_payload( + &self, + _config: reth_basic_payload_builder::PayloadConfig< + Self::Attributes, + reth_basic_payload_builder::HeaderForPayload, + >, + ) -> Result { + unimplemented!() + } +} + +impl OpPayloadBuilder +where + Pool: PoolBounds, + Client: ClientBounds, +{ + /// 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( + &self, + args: BuildArguments, OpBuiltPayload>, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + let block_build_start_time = Instant::now(); + let BuildArguments { config, cancel, .. } = args; + + let chain_spec = self.client.chain_spec(); + let timestamp = config.attributes.timestamp(); + 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: 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 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(), + chain_spec: self.client.chain_spec(), + config, + evm_env, + block_env_attributes, + cancel, + da_config: self.config.da_config.clone(), + builder_signer: self.config.builder_signer, + metrics: Default::default(), + }; + + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let state = StateProviderDatabase::new(&state_provider); + + // 1. execute the pre steps and seal an early block with that + let sequencer_tx_start_time = Instant::now(); + let mut db = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + + let mut info = execute_pre_steps(&mut db, &ctx)?; + ctx.metrics + .sequencer_tx_duration + .record(sequencer_tx_start_time.elapsed()); + + let (payload, fb_payload, mut bundle_state) = build_block(db, &ctx, &mut info)?; + + best_payload.set(payload.clone()); + self.ws_pub + .publish(&fb_payload) + .map_err(PayloadBuilderError::other)?; + + tracing::info!(target: "payload_builder", "Fallback block built"); + ctx.metrics + .payload_num_tx + .record(info.executed_transactions.len() as f64); + + if ctx.attributes().no_tx_pool { + tracing::info!( + target: "payload_builder", + "No transaction pool, skipping transaction pool processing", + ); + + self.metrics + .total_block_built_duration + .record(block_build_start_time.elapsed()); + + // return early since we don't need to build a block with transactions from the pool + return Ok(()); + } + + let gas_per_batch = ctx.block_gas_limit() / self.config.flashblocks_per_block(); + let mut total_gas_per_batch = gas_per_batch; + let total_da_bytes_per_batch = ctx + .da_config + .max_da_block_size() + .map(|limit| limit / self.config.flashblocks_per_block()); + + let mut flashblock_count = 0; + // Create a channel to coordinate flashblock building + let (build_tx, mut build_rx) = mpsc::channel(1); + + // Spawn the timer task that signals when to build a new flashblock + let cancel_clone = ctx.cancel.clone(); + let interval = self.config.specific.interval; + tokio::spawn(async move { + let mut interval = tokio::time::interval(interval); + loop { + tokio::select! { + // Add a cancellation check that only runs every 10ms to avoid tight polling + _ = tokio::time::sleep(Duration::from_millis(10)) => { + if cancel_clone.is_cancelled() { + tracing::info!(target: "payload_builder", "Job cancelled during sleep, stopping payload building"); + drop(build_tx); + break; + } + } + _ = interval.tick() => { + if let Err(err) = build_tx.send(()).await { + error!(target: "payload_builder", "Error sending build signal: {}", err); + break; + } + } + } + } + }); + + // Process flashblocks in a blocking loop + loop { + // Block on receiving a message, break on cancellation or closed channel + let received = tokio::task::block_in_place(|| { + // Get runtime handle + let rt = tokio::runtime::Handle::current(); + + // Run the async operation to completion, blocking the current thread + rt.block_on(async { + // Check for cancellation first + if ctx.cancel.is_cancelled() { + tracing::info!( + target: "payload_builder", + "Job cancelled, stopping payload building", + ); + return None; + } + + // Wait for next message + build_rx.recv().await + }) + }); + + // Exit loop if channel closed or cancelled + match received { + Some(()) => { + if flashblock_count >= self.config.flashblocks_per_block() { + tracing::info!( + target: "payload_builder", + "Skipping flashblock reached target={} idx={}", + self.config.flashblocks_per_block(), + flashblock_count + ); + continue; + } + + // Continue with flashblock building + tracing::info!( + target: "payload_builder", + "Building flashblock {} {}", + flashblock_count, + total_gas_per_batch, + ); + + let flashblock_build_start_time = Instant::now(); + let state = StateProviderDatabase::new(&state_provider); + + let mut db = State::builder() + .with_database(state) + .with_bundle_update() + .with_bundle_prestate(bundle_state) + .build(); + + let best_txs_start_time = Instant::now(); + let best_txs = BestPayloadTransactions::new( + self.pool + .best_transactions_with_attributes(ctx.best_transaction_attributes()), + ); + ctx.metrics + .transaction_pool_fetch_duration + .record(best_txs_start_time.elapsed()); + + let tx_execution_start_time = Instant::now(); + ctx.execute_best_transactions( + &mut info, + &mut db, + best_txs, + total_gas_per_batch.min(ctx.block_gas_limit()), + total_da_bytes_per_batch, + )?; + ctx.metrics + .payload_tx_simulation_duration + .record(tx_execution_start_time.elapsed()); + + if ctx.cancel.is_cancelled() { + tracing::info!( + target: "payload_builder", + "Job cancelled, stopping payload building", + ); + // if the job was cancelled, stop + return Ok(()); + } + + let total_block_built_duration = Instant::now(); + let build_result = build_block(db, &ctx, &mut info); + ctx.metrics + .total_block_built_duration + .record(total_block_built_duration.elapsed()); + + // Handle build errors with match pattern + match build_result { + Err(err) => { + // Track invalid/bad block + self.metrics.invalid_blocks_count.increment(1); + error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), flashblock_count, err); + // Return the error + return Err(err); + } + Ok((new_payload, mut fb_payload, new_bundle_state)) => { + fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 + fb_payload.base = None; + + self.ws_pub + .publish(&fb_payload) + .map_err(PayloadBuilderError::other)?; + + // Record flashblock build duration + self.metrics + .flashblock_build_duration + .record(flashblock_build_start_time.elapsed()); + ctx.metrics + .payload_byte_size + .record(new_payload.block().size() as f64); + ctx.metrics + .payload_num_tx + .record(info.executed_transactions.len() as f64); + + best_payload.set(new_payload.clone()); + // Update bundle_state for next iteration + bundle_state = new_bundle_state; + total_gas_per_batch += gas_per_batch; + flashblock_count += 1; + tracing::info!(target: "payload_builder", "Flashblock {} built", flashblock_count); + } + } + } + None => { + // Exit loop if channel closed or cancelled + self.metrics.block_built_success.increment(1); + self.metrics + .flashblock_count + .record(flashblock_count as f64); + return Ok(()); + } + } + } + } +} + +impl crate::builders::generator::PayloadBuilder for OpPayloadBuilder +where + Pool: PoolBounds, + Client: ClientBounds, +{ + type Attributes = OpPayloadBuilderAttributes; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + self.build_payload(args, best_payload) + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct FlashblocksMetadata { + receipts: HashMap::Receipt>, + new_account_balances: HashMap, + block_number: u64, +} + +fn execute_pre_steps( + state: &mut State, + ctx: &OpPayloadBuilderCtx, +) -> Result, PayloadBuilderError> +where + DB: Database, +{ + // 1. apply pre-execution changes + ctx.evm_config + .builder_for_next_block(state, ctx.parent(), ctx.block_env_attributes.clone()) + .map_err(PayloadBuilderError::other)? + .apply_pre_execution_changes()?; + + // 3. execute sequencer transactions + let info = ctx.execute_sequencer_transactions(state)?; + + Ok(info) +} + +fn build_block( + mut state: State, + ctx: &OpPayloadBuilderCtx, + info: &mut ExecutionInfo, +) -> Result<(OpBuiltPayload, FlashblocksPayloadV1, BundleState), PayloadBuilderError> +where + DB: Database + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, +{ + // TODO: We must run this only once per block, but we are running it on every flashblock + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + let state_merge_start_time = Instant::now(); + state.merge_transitions(BundleRetention::Reverts); + ctx.metrics + .state_transition_merge_duration + .record(state_merge_start_time.elapsed()); + + let new_bundle = state.take_bundle(); + + let block_number = ctx.block_number(); + assert_eq!(block_number, ctx.parent().number + 1); + + let execution_outcome = ExecutionOutcome::new( + new_bundle.clone(), + vec![info.receipts.clone()], + block_number, + vec![], + ); + + 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 state_provider = state.database.as_ref(); + let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); + let (state_root, _trie_output) = { + state + .database + .as_ref() + .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" + ); + })? + }; + ctx.metrics + .state_root_calculation_duration + .record(state_root_start_time.elapsed()); + + let mut requests_hash = None; + let withdrawals_root = if ctx + .chain_spec + .is_isthmus_active_at_timestamp(ctx.attributes().timestamp()) + { + // always empty requests hash post isthmus + requests_hash = Some(EMPTY_REQUESTS_HASH); + + // 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.database.as_ref()) + .map_err(PayloadBuilderError::other)?, + ) + } else if ctx + .chain_spec + .is_canyon_active_at_timestamp(ctx.attributes().timestamp()) + { + Some(EMPTY_WITHDRAWALS) + } else { + None + }; + + // create the block header + let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); + + // 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(); + 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.clone(), + ommers: vec![], + withdrawals: ctx.withdrawals().cloned(), + }, + ); + + let sealed_block = Arc::new(block.seal_slow()); + debug!(target: "payload_builder", ?sealed_block, "sealed built block"); + + let block_hash = sealed_block.hash(); + + // pick the new transactions from the info field and update the last flashblock index + let new_transactions = info.executed_transactions[info.extra.last_flashblock_index..].to_vec(); + + let new_transactions_encoded = new_transactions + .clone() + .into_iter() + .map(|tx| tx.encoded_2718().into()) + .collect::>(); + + let new_receipts = info.receipts[info.extra.last_flashblock_index..].to_vec(); + info.extra.last_flashblock_index = info.executed_transactions.len(); + let receipts_with_hash = new_transactions + .iter() + .zip(new_receipts.iter()) + .map(|(tx, receipt)| (tx.tx_hash(), receipt.clone())) + .collect::>(); + let new_account_balances = new_bundle + .state + .iter() + .filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance))) + .collect::>(); + + let metadata: FlashblocksMetadata = FlashblocksMetadata { + receipts: receipts_with_hash, + new_account_balances, + block_number: ctx.parent().number + 1, + }; + + // Prepare the flashblocks message + let fb_payload = FlashblocksPayloadV1 { + payload_id: ctx.payload_id(), + index: 0, + base: Some(ExecutionPayloadBaseV1 { + parent_beacon_block_root: ctx + .attributes() + .payload_attributes + .parent_beacon_block_root + .unwrap(), + parent_hash: ctx.parent().hash(), + fee_recipient: ctx.attributes().suggested_fee_recipient(), + prev_randao: ctx.attributes().payload_attributes.prev_randao, + block_number: ctx.parent().number + 1, + gas_limit: ctx.block_gas_limit(), + timestamp: ctx.attributes().payload_attributes.timestamp, + extra_data: ctx.extra_data()?, + base_fee_per_gas: ctx.base_fee().try_into().unwrap(), + }), + diff: ExecutionPayloadFlashblockDeltaV1 { + state_root, + receipts_root, + logs_bloom, + gas_used: info.cumulative_gas_used, + block_hash, + transactions: new_transactions_encoded, + withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), + withdrawals_root, + }, + metadata: serde_json::to_value(&metadata).unwrap_or_default(), + }; + + Ok(( + OpBuiltPayload::new( + ctx.payload_id(), + sealed_block, + info.total_fees, + // This must be set to NONE for now because we are doing merge transitions on every flashblock + // when it should only happen once per block, thus, it returns a confusing state back to op-reth. + // We can live without this for now because Op syncs up the executed block using new_payload + // calls, but eventually we would want to return the executed block here. + None, + ), + fb_payload, + new_bundle, + )) +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs new file mode 100644 index 00000000..210b7bb6 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -0,0 +1,56 @@ +use super::{payload::OpPayloadBuilder, FlashblocksConfig}; +use crate::{ + builders::{generator::BlockPayloadJobGenerator, BuilderConfig}, + traits::{NodeBounds, PoolBounds}, +}; +use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; +use reth_node_api::NodeTypes; +use reth_node_builder::{components::PayloadServiceBuilder, BuilderContext}; +use reth_optimism_evm::OpEvmConfig; +use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; +use reth_provider::CanonStateSubscriptions; + +pub struct FlashblocksServiceBuilder(pub BuilderConfig); + +impl PayloadServiceBuilder for FlashblocksServiceBuilder +where + Node: NodeBounds, + Pool: PoolBounds, +{ + async fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + pool: Pool, + _: OpEvmConfig, + ) -> eyre::Result::Payload>> { + tracing::debug!("Spawning flashblocks payload builder service"); + + let payload_builder = OpPayloadBuilder::new( + OpEvmConfig::optimism(ctx.chain_spec()), + pool, + ctx.provider().clone(), + self.0.clone(), + )?; + + let payload_job_config = BasicPayloadJobGeneratorConfig::default(); + + let payload_generator = BlockPayloadJobGenerator::with_builder( + ctx.provider().clone(), + ctx.task_executor().clone(), + payload_job_config, + payload_builder, + true, + self.0.block_time_leeway, + ); + + let (payload_service, payload_builder) = + PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + + ctx.task_executor() + .spawn_critical("custom payload builder service", Box::pin(payload_service)); + + tracing::info!("Flashblocks payload builder service started"); + + Ok(payload_builder) + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs new file mode 100644 index 00000000..a31ad3be --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -0,0 +1,239 @@ +use core::{ + fmt::{Debug, Formatter}, + net::SocketAddr, + pin::Pin, + sync::atomic::{AtomicUsize, Ordering}, + task::{Context, Poll}, +}; +use futures::{Sink, SinkExt}; +use rollup_boost::primitives::FlashblocksPayloadV1; +use std::{io, net::TcpListener, sync::Arc}; +use tokio::{ + net::TcpStream, + sync::{ + broadcast::{self, error::RecvError, Receiver}, + watch, + }, +}; +use tokio_tungstenite::{ + accept_async, + tungstenite::{Message, Utf8Bytes}, + WebSocketStream, +}; +use tracing::warn; + +use crate::metrics::OpRBuilderMetrics; + +/// A WebSockets publisher that accepts connections from client websockets and broadcasts to them +/// updates about new flashblocks. It maintains a count of sent messages and active subscriptions. +/// +/// This is modelled as a `futures::Sink` that can be used to send `FlashblocksPayloadV1` messages. +pub struct WebSocketPublisher { + sent: Arc, + subs: Arc, + term: watch::Sender, + pipe: broadcast::Sender, +} + +impl WebSocketPublisher { + pub fn new(addr: SocketAddr, metrics: Arc) -> io::Result { + let (pipe, _) = broadcast::channel(100); + let (term, _) = watch::channel(false); + + let sent = Arc::new(AtomicUsize::new(0)); + let subs = Arc::new(AtomicUsize::new(0)); + let listener = TcpListener::bind(addr)?; + + tokio::spawn(listener_loop( + listener, + metrics, + pipe.subscribe(), + term.subscribe(), + Arc::clone(&sent), + Arc::clone(&subs), + )); + + Ok(Self { + sent, + subs, + term, + pipe, + }) + } + + pub fn publish(&self, payload: &FlashblocksPayloadV1) -> io::Result<()> { + // Serialize the payload to a UTF-8 string + // serialize only once, then just copy around only a pointer + // to the serialized data for each subscription. + let serialized = serde_json::to_string(payload)?; + let utf8_bytes = Utf8Bytes::from(serialized); + + // Send the serialized payload to all subscribers + self.pipe + .send(utf8_bytes) + .map_err(|e| io::Error::new(io::ErrorKind::ConnectionAborted, e))?; + Ok(()) + } +} + +impl Drop for WebSocketPublisher { + fn drop(&mut self) { + // Notify the listener loop to terminate + let _ = self.term.send(true); + tracing::info!("WebSocketPublisher dropped, terminating listener loop"); + } +} + +async fn listener_loop( + listener: TcpListener, + metrics: Arc, + receiver: Receiver, + term: watch::Receiver, + sent: Arc, + subs: Arc, +) { + listener + .set_nonblocking(true) + .expect("Failed to set TcpListener socket to non-blocking"); + + let listener = tokio::net::TcpListener::from_std(listener) + .expect("Failed to convert TcpListener to tokio TcpListener"); + + let listen_addr = listener + .local_addr() + .expect("Failed to get local address of listener"); + tracing::info!("Flashblocks WebSocketPublisher listening on {listen_addr}"); + + let mut term = term; + + loop { + let subs = Arc::clone(&subs); + let metrics = Arc::clone(&metrics); + + tokio::select! { + // drop this connection if the `WebSocketPublisher` is dropped + _ = term.changed() => { + if *term.borrow() { + return; + } + } + + // Accept new connections on the websocket listener + // when a new connection is established, spawn a dedicated task to handle + // the connection and broadcast with that connection. + Ok((connection, peer_addr)) = listener.accept() => { + let sent = Arc::clone(&sent); + let term = term.clone(); + let receiver_clone = receiver.resubscribe(); + + match accept_async(connection).await { + Ok(stream) => { + tokio::spawn(async move { + subs.fetch_add(1, Ordering::Relaxed); + tracing::debug!("WebSocket connection established with {}", peer_addr); + + // Handle the WebSocket connection in a dedicated task + broadcast_loop(stream, metrics, term, receiver_clone, sent).await; + + subs.fetch_sub(1, Ordering::Relaxed); + tracing::debug!("WebSocket connection closed for {}", peer_addr); + }); + } + Err(e) => { + warn!("Failed to accept WebSocket connection from {peer_addr}: {e}"); + } + } + } + } + } +} + +/// An instance of this loop is spawned for each connected WebSocket client. +/// It listens for broadcast updates about new flashblocks and sends them to the client. +/// It also handles termination signals to gracefully close the connection. +/// Any connectivity errors will terminate the loop, which will in turn +/// decrement the subscription count in the `WebSocketPublisher`. +async fn broadcast_loop( + stream: WebSocketStream, + metrics: Arc, + term: watch::Receiver, + blocks: broadcast::Receiver, + sent: Arc, +) { + let mut term = term; + let mut blocks = blocks; + let mut stream = stream; + let Ok(peer_addr) = stream.get_ref().peer_addr() else { + return; + }; + + loop { + let metrics = Arc::clone(&metrics); + + tokio::select! { + // Check if the publisher is terminated + _ = term.changed() => { + if *term.borrow() { + tracing::info!("WebSocketPublisher is terminating, closing broadcast loop"); + return; + } + } + + // Receive payloads from the broadcast channel + payload = blocks.recv() => match payload { + Ok(payload) => { + // Here you would typically send the payload to the WebSocket clients. + // For this example, we just increment the sent counter. + sent.fetch_add(1, Ordering::Relaxed); + metrics.messages_sent_count.increment(1); + + tracing::info!("Broadcasted payload: {:?}", payload); + if let Err(e) = stream.send(Message::Text(payload)).await { + tracing::debug!("Closing flashblocks subscription for {peer_addr}: {e}"); + break; // Exit the loop if sending fails + } + } + Err(RecvError::Closed) => { + tracing::debug!("Broadcast channel closed, exiting broadcast loop"); + return; + } + Err(RecvError::Lagged(_)) => { + tracing::warn!("Broadcast channel lagged, some messages were dropped"); + } + }, + } + } +} + +impl Debug for WebSocketPublisher { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let subs = self.subs.load(Ordering::Relaxed); + let sent = self.sent.load(Ordering::Relaxed); + + f.debug_struct("WebSocketPublisher") + .field("subs", &subs) + .field("payloads_sent", &sent) + .finish() + } +} + +impl Sink<&FlashblocksPayloadV1> for WebSocketPublisher { + type Error = eyre::Report; + + fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn start_send(self: Pin<&mut Self>, item: &FlashblocksPayloadV1) -> Result<(), Self::Error> { + self.publish(item)?; + Ok(()) + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/crates/builder/op-rbuilder/src/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs similarity index 99% rename from crates/builder/op-rbuilder/src/generator.rs rename to crates/builder/op-rbuilder/src/builders/generator.rs index eefe6200..2132d105 100644 --- a/crates/builder/op-rbuilder/src/generator.rs +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -355,11 +355,6 @@ impl BlockCell { } } - pub fn is_some(&self) -> bool { - let inner = self.inner.lock().unwrap(); - inner.is_some() - } - pub fn set(&self, value: T) { let mut inner = self.inner.lock().unwrap(); *inner = Some(value); diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs new file mode 100644 index 00000000..9cbc4a0f --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -0,0 +1,163 @@ +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; + +use crate::{ + args::OpRbuilderArgs, + traits::{NodeBounds, PoolBounds}, + tx_signer::Signer, +}; + +mod context; +mod flashblocks; +mod generator; +mod standard; + +pub use flashblocks::FlashblocksBuilder; +pub use standard::StandardBuilder; + +/// 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, + + /// When set to true, transactions are simulated by the builder and excluded from the block + /// if they revert. They may still be included in the block if individual transactions + /// opt-out of revert protection. + pub revert_protection: bool, + + /// The interval at which blocks are added to the chain. + /// This is also the frequency at which the builder will be receiving FCU rquests 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, + + // 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, + + /// Configuration values that are specific to the block builder implementation used. + pub specific: Specific, +} + +impl core::fmt::Debug for BuilderConfig { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Config") + .field( + "builder_signer", + &match self.builder_signer.as_ref() { + Some(signer) => signer.address.to_string(), + None => "None".into(), + }, + ) + .field("revert_protection", &self.revert_protection) + .field("block_time", &self.block_time) + .field("block_time_leeway", &self.block_time_leeway) + .field("da_config", &self.da_config) + .field("specific", &self.specific) + .finish() + } +} + +impl Default for BuilderConfig { + fn default() -> Self { + Self { + builder_signer: None, + revert_protection: false, + block_time: Duration::from_secs(2), + block_time_leeway: Duration::from_millis(500), + da_config: OpDAConfig::default(), + specific: S::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, + revert_protection: args.enable_revert_protection, + block_time: Duration::from_millis(args.chain_block_time), + block_time_leeway: Duration::from_millis(500), + da_config: Default::default(), + specific: S::try_from(args)?, + }) + } +} + +impl TryFrom for () { + type Error = Infallible; + + fn try_from(_: OpRbuilderArgs) -> Result { + Ok(()) + } +} diff --git a/crates/builder/op-rbuilder/src/builders/standard/mod.rs b/crates/builder/op-rbuilder/src/builders/standard/mod.rs new file mode 100644 index 00000000..98e9b44f --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/standard/mod.rs @@ -0,0 +1,34 @@ +use payload::StandardPayloadBuilderBuilder; +use reth_node_builder::components::BasicPayloadServiceBuilder; + +use crate::traits::{NodeBounds, PoolBounds}; + +use super::BuilderConfig; + +mod payload; + +/// Block building strategy that builds blocks using the standard approach by +/// producing blocks every chain block time. +pub struct StandardBuilder; + +impl super::PayloadBuilder for StandardBuilder { + type Config = (); + + type ServiceBuilder + = BasicPayloadServiceBuilder + where + Node: NodeBounds, + Pool: PoolBounds; + + fn new_service( + config: BuilderConfig, + ) -> eyre::Result> + where + Node: NodeBounds, + Pool: PoolBounds, + { + Ok(BasicPayloadServiceBuilder::new( + StandardPayloadBuilderBuilder(config), + )) + } +} diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs new file mode 100644 index 00000000..4c70e448 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -0,0 +1,588 @@ +use alloy_consensus::{ + constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, EMPTY_OMMER_ROOT_HASH, +}; +use alloy_eips::{ + eip7623::TOTAL_COST_FLOOR_PER_TOKEN, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE, +}; +use alloy_primitives::U256; +use reth::payload::PayloadBuilderAttributes; +use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; +use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; +use reth_evm::{execute::BlockBuilder, ConfigureEvm}; +use reth_node_api::{Block, PayloadBuilderError}; +use reth_node_builder::{components::PayloadBuilderBuilder, BuilderContext}; +use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; +use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; +use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; +use reth_primitives::RecoveredBlock; +use reth_provider::{ + ExecutionOutcome, HashedPostStateProvider, ProviderError, StateRootProvider, + StorageRootProvider, +}; +use reth_revm::{ + database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State, +}; +use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; +use revm::Database; +use std::{sync::Arc, time::Instant}; +use tokio_util::sync::CancellationToken; +use tracing::{info, warn}; + +use crate::{ + builders::{generator::BuildArguments, BuilderConfig}, + metrics::OpRBuilderMetrics, + primitives::reth::ExecutionInfo, + traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, +}; + +use super::super::context::OpPayloadBuilderCtx; + +pub struct StandardPayloadBuilderBuilder(pub BuilderConfig<()>); + +impl PayloadBuilderBuilder for StandardPayloadBuilderBuilder +where + Node: NodeBounds, + Pool: PoolBounds, +{ + type PayloadBuilder = StandardOpPayloadBuilder; + + async fn build_payload_builder( + self, + ctx: &BuilderContext, + pool: Pool, + _evm_config: OpEvmConfig, + ) -> eyre::Result { + Ok(StandardOpPayloadBuilder::new( + OpEvmConfig::optimism(ctx.chain_spec()), + pool, + ctx.provider().clone(), + self.0.clone(), + )) + } +} + +/// Optimism's payload builder +#[derive(Debug, Clone)] +pub 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, +} + +impl StandardOpPayloadBuilder { + /// `OpPayloadBuilder` constructor. + pub fn new( + evm_config: OpEvmConfig, + pool: Pool, + client: Client, + config: BuilderConfig<()>, + ) -> Self { + Self { + pool, + client, + config, + evm_config, + best_transactions: (), + metrics: Default::default(), + } + } +} + +/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. +pub 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 { + BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) + } +} + +impl reth_basic_payload_builder::PayloadBuilder + for StandardOpPayloadBuilder +where + Pool: PoolBounds, + Client: ClientBounds, + 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, +{ + /// 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 BuildArguments { + mut cached_reads, + config, + cancel, + } = args; + + let chain_spec = self.client.chain_spec(); + let timestamp = config.attributes.timestamp(); + 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: 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 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(), + chain_spec, + config, + evm_env, + block_env_attributes, + cancel, + builder_signer: self.config.builder_signer, + metrics: self.metrics.clone(), + }; + + let builder = OpBuilder::new(best); + + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let state = StateProviderDatabase::new(state_provider); + + if ctx.attributes().no_tx_pool { + let db = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + builder.build(db, ctx) + } else { + // sequencer mode we can reuse cachedreads from previous runs + let db = State::builder() + .with_database(cached_reads.as_db_mut(state)) + .with_bundle_update() + .build(); + builder.build(db, ctx) + } + .map(|out| 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 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 struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, +} + +impl OpBuilder<'_, Txs> { + /// Executes the payload and returns the outcome. + pub fn execute( + self, + state: &mut State, + ctx: &OpPayloadBuilderCtx, + ) -> Result, PayloadBuilderError> + where + DB: Database + AsRef

, + P: StorageRootProvider, + { + 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(state, 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(state)?; + + ctx.metrics + .sequencer_tx_duration + .record(sequencer_tx_start_time.elapsed()); + + // 4. if mem pool transactions are requested we execute them + + // gas reserved for builder tx + let message = format!("Block Number: {}", ctx.block_number()) + .as_bytes() + .to_vec(); + let builder_tx_gas = ctx + .builder_signer() + .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); + let block_gas_limit = ctx.block_gas_limit() - builder_tx_gas; + // Save some space in the block_da_limit for builder tx + let builder_tx_da_size = ctx + .estimate_builder_tx_da_size(state, builder_tx_gas, message.clone()) + .unwrap_or(0); + let block_da_limit = ctx + .da_config + .max_da_block_size() + .map(|da_size| da_size - builder_tx_da_size as u64); + // Check that it's possible to create builder tx, considering max_da_tx_size, otherwise panic + if let Some(tx_da_limit) = ctx.da_config.max_da_tx_size() { + // Panic indicate max_da_tx_size misconfiguration + assert!( + tx_da_limit >= builder_tx_da_size as u64, + "The configured da_config.max_da_tx_size is too small to accommodate builder tx." + ); + } + + if !ctx.attributes().no_tx_pool { + let best_txs_start_time = Instant::now(); + let best_txs = best(ctx.best_transaction_attributes()); + ctx.metrics + .transaction_pool_fetch_duration + .record(best_txs_start_time.elapsed()); + if ctx + .execute_best_transactions( + &mut info, + state, + best_txs, + block_gas_limit, + block_da_limit, + )? + .is_some() + { + return Ok(BuildOutcomeKind::Cancelled); + } + } + + // Add builder tx to the block + ctx.add_builder_tx(&mut info, state, builder_tx_gas, message); + + let state_merge_start_time = Instant::now(); + + // merge all transitions into bundle state, this would apply the withdrawal balance changes + // and 4788 contract call + state.merge_transitions(BundleRetention::Reverts); + + ctx.metrics + .state_transition_merge_duration + .record(state_merge_start_time.elapsed()); + ctx.metrics + .payload_num_tx + .record(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 fn build( + self, + mut state: State, + ctx: OpPayloadBuilderCtx, + ) -> Result, PayloadBuilderError> + where + DB: Database + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + { + let ExecutedPayload { info } = match self.execute(&mut state, &ctx)? { + 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(); + let execution_outcome = ExecutionOutcome::new( + state.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 state_provider = state.database.as_ref(); + let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); + let (state_root, trie_output) = { + state + .database + .as_ref() + .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" + ); + })? + }; + + ctx.metrics + .state_root_calculation_duration + .record(state_root_start_time.elapsed()); + + 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.database.as_ref()) + .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); + + // 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(); + 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: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { + block: ExecutedBlock { + recovered_block: Arc::new(RecoveredBlock::< + alloy_consensus::Block, + >::new_sealed( + sealed_block.as_ref().clone(), info.executed_senders + )), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + }, + trie: 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(payload.block().size() 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 }) + } + } +} + +fn estimate_gas_for_builder_tx(input: Vec) -> 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; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) +} diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 5477f1d2..21f63a7b 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,6 +1,6 @@ -use args::CliExt; -use clap::Parser; -use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; +use args::*; +use builders::{BuilderConfig, BuilderMode, FlashblocksBuilder, StandardBuilder}; +use core::fmt::Debug; use reth_optimism_node::{ node::{OpAddOnsBuilder, OpPoolBuilder}, OpNode, @@ -9,25 +9,15 @@ use reth_transaction_pool::TransactionPool; /// CLI argument parsing. pub mod args; -pub mod generator; +mod builders; mod metrics; mod monitor_tx_pool; -#[cfg(feature = "flashblocks")] -pub mod payload_builder; mod primitives; mod revert_protection; +mod traits; mod tx; mod tx_signer; -#[cfg(not(feature = "flashblocks"))] -mod payload_builder_vanilla; - -#[cfg(not(feature = "flashblocks"))] -use payload_builder_vanilla::CustomOpPayloadBuilder; - -#[cfg(feature = "flashblocks")] -use payload_builder::CustomOpPayloadBuilder; - use metrics::{ VersionInfo, BUILD_PROFILE_NAME, CARGO_PKG_VERSION, VERGEN_BUILD_TIMESTAMP, VERGEN_CARGO_FEATURES, VERGEN_CARGO_TARGET_TRIPLE, VERGEN_GIT_SHA, @@ -41,86 +31,104 @@ use tx::FBPooledTransaction; #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; +const VERSION: VersionInfo = VersionInfo { + version: CARGO_PKG_VERSION, + build_timestamp: VERGEN_BUILD_TIMESTAMP, + cargo_features: VERGEN_CARGO_FEATURES, + git_sha: VERGEN_GIT_SHA, + target_triple: VERGEN_CARGO_TARGET_TRIPLE, + build_profile: BUILD_PROFILE_NAME, +}; + fn main() { - let version = VersionInfo { - version: CARGO_PKG_VERSION, - build_timestamp: VERGEN_BUILD_TIMESTAMP, - cargo_features: VERGEN_CARGO_FEATURES, - git_sha: VERGEN_GIT_SHA, - target_triple: VERGEN_CARGO_TARGET_TRIPLE, - build_profile: BUILD_PROFILE_NAME, + let cli = Cli::parsed(); + cli.logs + .init_tracing() + .expect("Failed to initialize tracing"); + + match cli.builder_mode() { + BuilderMode::Standard => { + tracing::info!("Starting OP builder in standard mode"); + start_builder_node::(cli); + } + BuilderMode::Flashblocks => { + tracing::info!("Starting OP builder in flashblocks mode"); + start_builder_node::(cli); + } }; +} - Cli::::parse() - .populate_defaults() - .run(|builder, builder_args| async move { - let rollup_args = builder_args.rollup_args; +/// Starts the OP builder node with a given payload builder implementation. +fn start_builder_node(cli: Cli) +where + BuilderConfig<::Config>: TryFrom, + ::Config> as TryFrom>::Error: + Debug, +{ + cli.run(|builder, builder_args| async move { + let builder_config = BuilderConfig::::try_from(builder_args.clone()) + .expect("Failed to convert rollup args to builder config"); - let op_node = OpNode::new(rollup_args.clone()); - let handle = builder - .with_types::() - .with_components( - op_node - .components() - .pool( - OpPoolBuilder::::default() - .with_enable_tx_conditional( - // Revert protection uses the same internal pool logic as conditional transactions - // to garbage collect transactions out of the bundle range. - rollup_args.enable_tx_conditional - || builder_args.enable_revert_protection, - ) - .with_supervisor( - rollup_args.supervisor_http.clone(), - rollup_args.supervisor_safety_level, - ), - ) - .payload(CustomOpPayloadBuilder::new( - builder_args.builder_signer, - std::time::Duration::from_secs(builder_args.extra_block_deadline_secs), - builder_args.flashblocks_ws_url, - builder_args.chain_block_time, - builder_args.flashblock_block_time, - )), - ) - .with_add_ons( - OpAddOnsBuilder::default() - .with_sequencer(rollup_args.sequencer.clone()) - .with_enable_tx_conditional(rollup_args.enable_tx_conditional) - .build(), - ) - .extend_rpc_modules(move |ctx| { - if builder_args.enable_revert_protection { - tracing::info!("Revert protection enabled"); + let rollup_args = builder_args.rollup_args; + let op_node = OpNode::new(rollup_args.clone()); + let handle = builder + .with_types::() + .with_components( + op_node + .components() + .pool( + OpPoolBuilder::::default() + .with_enable_tx_conditional( + // Revert protection uses the same internal pool logic as conditional transactions + // to garbage collect transactions out of the bundle range. + rollup_args.enable_tx_conditional + || builder_args.enable_revert_protection, + ) + .with_supervisor( + rollup_args.supervisor_http.clone(), + rollup_args.supervisor_safety_level, + ), + ) + .payload(B::new_service(builder_config)?), + ) + .with_add_ons( + OpAddOnsBuilder::default() + .with_sequencer(rollup_args.sequencer.clone()) + .with_enable_tx_conditional(rollup_args.enable_tx_conditional) + .build(), + ) + .extend_rpc_modules(move |ctx| { + if builder_args.enable_revert_protection { + tracing::info!("Revert protection enabled"); - let pool = ctx.pool().clone(); - let provider = ctx.provider().clone(); - let revert_protection_ext = RevertProtectionExt::new(pool, provider); + let pool = ctx.pool().clone(); + let provider = ctx.provider().clone(); + let revert_protection_ext = RevertProtectionExt::new(pool, provider); - ctx.modules - .merge_configured(revert_protection_ext.into_rpc())?; - } + ctx.modules + .merge_configured(revert_protection_ext.into_rpc())?; + } - Ok(()) - }) - .on_node_started(move |ctx| { - version.register_version_metrics(); - if builder_args.log_pool_transactions { - tracing::info!("Logging pool transactions"); - ctx.task_executor.spawn_critical( - "txlogging", - Box::pin(async move { - monitor_tx_pool(ctx.pool.all_transactions_event_listener()).await; - }), - ); - } + Ok(()) + }) + .on_node_started(move |ctx| { + VERSION.register_version_metrics(); + if builder_args.log_pool_transactions { + tracing::info!("Logging pool transactions"); + ctx.task_executor.spawn_critical( + "txlogging", + Box::pin(async move { + monitor_tx_pool(ctx.pool.all_transactions_event_listener()).await; + }), + ); + } - Ok(()) - }) - .launch() - .await?; + Ok(()) + }) + .launch() + .await?; - handle.node_exit_future.await - }) - .unwrap(); + handle.node_exit_future.await + }) + .unwrap(); } diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index e3efa5e0..4b44410b 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -28,19 +28,14 @@ pub struct OpRBuilderMetrics { /// Block built success pub block_built_success: Counter, /// Number of flashblocks added to block (Total per block) - #[cfg(feature = "flashblocks")] pub flashblock_count: Histogram, /// Number of messages sent - #[cfg(feature = "flashblocks")] pub messages_sent_count: Counter, /// Total duration of building a block - #[cfg(feature = "flashblocks")] pub total_block_built_duration: Histogram, /// Flashblock build duration - #[cfg(feature = "flashblocks")] pub flashblock_build_duration: Histogram, /// Number of invalid blocks - #[cfg(feature = "flashblocks")] pub invalid_blocks_count: Counter, /// Duration of fetching transactions from the pool pub transaction_pool_fetch_duration: Histogram, diff --git a/crates/builder/op-rbuilder/src/payload_builder.rs b/crates/builder/op-rbuilder/src/payload_builder.rs deleted file mode 100644 index 053165c3..00000000 --- a/crates/builder/op-rbuilder/src/payload_builder.rs +++ /dev/null @@ -1,1269 +0,0 @@ -use crate::{ - generator::{BlockCell, BlockPayloadJobGenerator, BuildArguments, PayloadBuilder}, - metrics::OpRBuilderMetrics, - primitives::reth::ExecutionInfo, - tx_signer::Signer, -}; -use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, Eip658Value, Header, Transaction, Typed2718, - EMPTY_OMMER_ROOT_HASH, -}; -use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE, Encodable2718}; -use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256}; -use alloy_rpc_types_engine::PayloadId; -use alloy_rpc_types_eth::Withdrawals; -use futures_util::{FutureExt, SinkExt}; -use op_alloy_consensus::OpDepositReceipt; -use op_revm::OpSpecId; -use reth::{ - builder::{ - components::{PayloadBuilderBuilder, PayloadServiceBuilder}, - node::FullNodeTypes, - BuilderContext, - }, - payload::PayloadBuilderHandle, -}; -use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, BuildOutcome, PayloadConfig}; -use reth_chainspec::{ChainSpecProvider, EthChainSpec}; -use reth_evm::{ - env::EvmEnv, eth::receipt_builder::ReceiptBuilderCtx, execute::BlockBuilder, ConfigureEvm, - Database, Evm, EvmError, InvalidTxError, -}; -use reth_execution_types::ExecutionOutcome; -use reth_node_api::{NodePrimitives, NodeTypes, PrimitivesTy, TxTy}; -use reth_optimism_chainspec::OpChainSpec; -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::OpEngineTypes; -use reth_optimism_payload_builder::{ - error::OpPayloadBuilderError, - payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, -}; -use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; -use reth_optimism_txpool::OpPooledTx; -use reth_payload_builder::PayloadBuilderService; -use reth_payload_builder_primitives::PayloadBuilderError; -use reth_payload_primitives::PayloadBuilderAttributes; -use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; -use reth_primitives::{BlockBody, SealedHeader}; -use reth_primitives_traits::{proofs, Block as _, InMemorySize, SignedTransaction}; -use reth_provider::{ - CanonStateSubscriptions, HashedPostStateProvider, ProviderError, StateProviderFactory, - StateRootProvider, StorageRootProvider, -}; -use reth_revm::database::StateProviderDatabase; -use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; -use revm::{ - context::{result::ResultAndState, Block as _}, - database::{states::bundle_state::BundleRetention, BundleState, State}, - DatabaseCommit, -}; -use rollup_boost::primitives::{ - ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, -}; -use serde::{Deserialize, Serialize}; -use std::{ - sync::{Arc, Mutex}, - time::{Duration, Instant}, -}; -use tokio::{ - net::{TcpListener, TcpStream}, - sync::mpsc, -}; -use tokio_tungstenite::{accept_async, WebSocketStream}; -use tokio_util::sync::CancellationToken; -use tracing::{debug, error, trace, warn}; - -/// Flashblocks specific payload building errors. -#[derive(Debug, thiserror::Error)] -pub enum FlashblockPayloadBuilderError { - /// Thrown when the job was cancelled. - #[error("error sending build signal")] - SendBuildSignalError, -} - -#[derive(Debug, Serialize, Deserialize)] -struct FlashblocksMetadata { - receipts: HashMap, - new_account_balances: HashMap, - block_number: u64, -} - -#[derive(Debug, Clone, Default)] -#[non_exhaustive] -pub struct CustomOpPayloadBuilder { - #[expect(dead_code)] - builder_signer: Option, - flashblocks_ws_url: String, - chain_block_time: u64, - flashblock_block_time: u64, - extra_block_deadline: std::time::Duration, -} - -impl CustomOpPayloadBuilder { - pub fn new( - builder_signer: Option, - extra_block_deadline: std::time::Duration, - flashblocks_ws_url: String, - chain_block_time: u64, - flashblock_block_time: u64, - ) -> Self { - Self { - builder_signer, - flashblocks_ws_url, - chain_block_time, - flashblock_block_time, - extra_block_deadline, - } - } -} - -impl PayloadBuilderBuilder for CustomOpPayloadBuilder -where - Node: FullNodeTypes< - Types: NodeTypes< - Payload = OpEngineTypes, - ChainSpec = OpChainSpec, - Primitives = OpPrimitives, - >, - >, - Pool: TransactionPool>> - + Unpin - + 'static, - Evm: ConfigureEvm< - Primitives = PrimitivesTy, - NextBlockEnvCtx = OpNextBlockEnvAttributes, - > + 'static, -{ - type PayloadBuilder = OpPayloadBuilder; - - async fn build_payload_builder( - self, - ctx: &BuilderContext, - pool: Pool, - _evm_config: Evm, - ) -> eyre::Result { - Ok(OpPayloadBuilder::new( - OpEvmConfig::optimism(ctx.chain_spec()), - pool, - ctx.provider().clone(), - self.flashblocks_ws_url.clone(), - self.chain_block_time, - self.flashblock_block_time, - )) - } -} - -impl PayloadServiceBuilder for CustomOpPayloadBuilder -where - Node: FullNodeTypes< - Types: NodeTypes< - Payload = OpEngineTypes, - ChainSpec = OpChainSpec, - Primitives = OpPrimitives, - >, - >, - Pool: TransactionPool>> - + Unpin - + 'static, - ::Transaction: OpPooledTx, - Evm: ConfigureEvm< - Primitives = PrimitivesTy, - NextBlockEnvCtx = OpNextBlockEnvAttributes, - > + 'static, -{ - async fn spawn_payload_builder_service( - self, - ctx: &BuilderContext, - pool: Pool, - evm_config: Evm, - ) -> eyre::Result::Payload>> { - tracing::info!("Spawning a custom payload builder"); - let extra_block_deadline = self.extra_block_deadline; - let payload_builder = self.build_payload_builder(ctx, pool, evm_config).await?; - let payload_job_config = BasicPayloadJobGeneratorConfig::default(); - - let payload_generator = BlockPayloadJobGenerator::with_builder( - ctx.provider().clone(), - ctx.task_executor().clone(), - payload_job_config, - payload_builder, - true, - extra_block_deadline, - ); - - let (payload_service, payload_builder) = - PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - - ctx.task_executor() - .spawn_critical("custom payload builder service", Box::pin(payload_service)); - - tracing::info!("Custom payload service started"); - - Ok(payload_builder) - } -} - -impl reth_basic_payload_builder::PayloadBuilder for OpPayloadBuilder -where - Pool: Clone + Send + Sync, - Client: Clone + Send + Sync, -{ - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; - - fn try_build( - &self, - _args: reth_basic_payload_builder::BuildArguments, - ) -> Result, PayloadBuilderError> { - unimplemented!() - } - - fn build_empty_payload( - &self, - _config: reth_basic_payload_builder::PayloadConfig< - Self::Attributes, - reth_basic_payload_builder::HeaderForPayload, - >, - ) -> Result { - unimplemented!() - } -} - -/// Optimism's payload builder -#[derive(Debug, Clone)] -pub struct OpPayloadBuilder { - /// The type responsible for creating the evm. - pub evm_config: OpEvmConfig, - /// The transaction pool - pub pool: Pool, - /// Node client - pub client: Client, - /// Channel sender for publishing messages - pub tx: mpsc::UnboundedSender, - /// chain block time - pub chain_block_time: u64, - /// Flashblock block time - pub flashblock_block_time: u64, - /// Number of flashblocks per block - pub flashblocks_per_block: u64, - /// The metrics for the builder - pub metrics: OpRBuilderMetrics, -} - -impl OpPayloadBuilder { - /// `OpPayloadBuilder` constructor. - pub fn new( - evm_config: OpEvmConfig, - pool: Pool, - client: Client, - flashblocks_ws_url: String, - chain_block_time: u64, - flashblock_block_time: u64, - ) -> Self { - let (tx, rx) = mpsc::unbounded_channel(); - let subscribers = Arc::new(Mutex::new(Vec::new())); - - Self::publish_task(rx, subscribers.clone()); - - tokio::spawn(async move { - Self::start_ws(subscribers, &flashblocks_ws_url).await; - }); - - Self { - evm_config, - pool, - client, - tx, - chain_block_time, - flashblock_block_time, - flashblocks_per_block: chain_block_time / flashblock_block_time, - metrics: Default::default(), - } - } - - /// Start the WebSocket server - pub async fn start_ws(subscribers: Arc>>>, addr: &str) { - let listener = TcpListener::bind(addr).await.unwrap(); - let subscribers = subscribers.clone(); - - tracing::info!("Starting WebSocket server on {}", addr); - - while let Ok((stream, _)) = listener.accept().await { - tracing::info!("Accepted websocket connection"); - let subscribers = subscribers.clone(); - - tokio::spawn(async move { - match accept_async(stream).await { - Ok(ws_stream) => { - let mut subs = subscribers.lock().unwrap(); - subs.push(ws_stream); - } - Err(e) => eprintln!("Error accepting websocket connection: {}", e), - } - }); - } - } - - /// Background task that handles publishing messages to WebSocket subscribers - fn publish_task( - mut rx: mpsc::UnboundedReceiver, - subscribers: Arc>>>, - ) { - tokio::spawn(async move { - while let Some(message) = rx.recv().await { - let mut subscribers = subscribers.lock().unwrap(); - - // Remove disconnected subscribers and send message to connected ones - subscribers.retain_mut(|ws_stream| { - let message = message.clone(); - async move { - ws_stream - .send(tokio_tungstenite::tungstenite::Message::Text( - message.into(), - )) - .await - .is_ok() - } - .now_or_never() - .unwrap_or(false) - }); - } - }); - } -} - -impl OpPayloadBuilder -where - Pool: TransactionPool>, - Client: StateProviderFactory + ChainSpecProvider, -{ - /// Send a message to be published - pub fn send_message(&self, message: String) -> Result<(), Box> { - self.tx.send(message)?; - self.metrics.messages_sent_count.increment(1); - Ok(()) - } - - /// 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( - &self, - args: BuildArguments, OpBuiltPayload>, - best_payload: BlockCell, - ) -> Result<(), PayloadBuilderError> { - let block_build_start_time = Instant::now(); - let BuildArguments { config, cancel, .. } = args; - - let chain_spec = self.client.chain_spec(); - let timestamp = config.attributes.timestamp(); - 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: 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 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(), - chain_spec: self.client.chain_spec(), - config, - evm_env, - block_env_attributes, - cancel, - metrics: Default::default(), - }; - - let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(&state_provider); - - // 1. execute the pre steps and seal an early block with that - let sequencer_tx_start_time = Instant::now(); - let mut db = State::builder() - .with_database(state) - .with_bundle_update() - .build(); - - let mut info = execute_pre_steps(&mut db, &ctx)?; - ctx.metrics - .sequencer_tx_duration - .record(sequencer_tx_start_time.elapsed()); - - let (payload, fb_payload, mut bundle_state) = build_block(db, &ctx, &mut info)?; - - best_payload.set(payload.clone()); - let _ = self.send_message(serde_json::to_string(&fb_payload).unwrap_or_default()); - - tracing::info!(target: "payload_builder", "Fallback block built"); - ctx.metrics - .payload_num_tx - .record(info.executed_transactions.len() as f64); - - if ctx.attributes().no_tx_pool { - tracing::info!( - target: "payload_builder", - "No transaction pool, skipping transaction pool processing", - ); - - self.metrics - .total_block_built_duration - .record(block_build_start_time.elapsed()); - - // return early since we don't need to build a block with transactions from the pool - return Ok(()); - } - - let gas_per_batch = ctx.block_gas_limit() / self.flashblocks_per_block; - - let mut total_gas_per_batch = gas_per_batch; - - let mut flashblock_count = 0; - // Create a channel to coordinate flashblock building - let (build_tx, mut build_rx) = mpsc::channel(1); - - // Spawn the timer task that signals when to build a new flashblock - let cancel_clone = ctx.cancel.clone(); - let flashblock_block_time = self.flashblock_block_time; - tokio::spawn(async move { - let mut interval = tokio::time::interval(Duration::from_millis(flashblock_block_time)); - loop { - tokio::select! { - // Add a cancellation check that only runs every 10ms to avoid tight polling - _ = tokio::time::sleep(Duration::from_millis(10)) => { - if cancel_clone.is_cancelled() { - tracing::info!(target: "payload_builder", "Job cancelled during sleep, stopping payload building"); - drop(build_tx); - break; - } - } - _ = interval.tick() => { - if let Err(err) = build_tx.send(()).await { - error!(target: "payload_builder", "Error sending build signal: {}", err); - break; - } - } - } - } - }); - - // Process flashblocks in a blocking loop - loop { - // Block on receiving a message, break on cancellation or closed channel - let received = tokio::task::block_in_place(|| { - // Get runtime handle - let rt = tokio::runtime::Handle::current(); - - // Run the async operation to completion, blocking the current thread - rt.block_on(async { - // Check for cancellation first - if ctx.cancel.is_cancelled() { - tracing::info!( - target: "payload_builder", - "Job cancelled, stopping payload building", - ); - return None; - } - - // Wait for next message - build_rx.recv().await - }) - }); - - // Exit loop if channel closed or cancelled - match received { - Some(()) => { - if flashblock_count >= self.flashblocks_per_block { - tracing::info!( - target: "payload_builder", - "Skipping flashblock reached target={} idx={}", - self.flashblocks_per_block, - flashblock_count - ); - continue; - } - - // Continue with flashblock building - tracing::info!( - target: "payload_builder", - "Building flashblock {} {}", - flashblock_count, - total_gas_per_batch, - ); - - let flashblock_build_start_time = Instant::now(); - let state = StateProviderDatabase::new(&state_provider); - - let mut db = State::builder() - .with_database(state) - .with_bundle_update() - .with_bundle_prestate(bundle_state) - .build(); - - let best_txs_start_time = Instant::now(); - let best_txs = BestPayloadTransactions::new( - self.pool - .best_transactions_with_attributes(ctx.best_transaction_attributes()), - ); - ctx.metrics - .transaction_pool_fetch_duration - .record(best_txs_start_time.elapsed()); - - let tx_execution_start_time = Instant::now(); - ctx.execute_best_transactions( - &mut info, - &mut db, - best_txs, - total_gas_per_batch.min(ctx.block_gas_limit()), - )?; - ctx.metrics - .payload_tx_simulation_duration - .record(tx_execution_start_time.elapsed()); - - if ctx.cancel.is_cancelled() { - tracing::info!( - target: "payload_builder", - "Job cancelled, stopping payload building", - ); - // if the job was cancelled, stop - return Ok(()); - } - - let total_block_built_duration = Instant::now(); - let build_result = build_block(db, &ctx, &mut info); - ctx.metrics - .total_block_built_duration - .record(total_block_built_duration.elapsed()); - - // Handle build errors with match pattern - match build_result { - Err(err) => { - // Track invalid/bad block - self.metrics.invalid_blocks_count.increment(1); - error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), flashblock_count, err); - // Return the error - return Err(err); - } - Ok((new_payload, mut fb_payload, new_bundle_state)) => { - fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 - fb_payload.base = None; - - if let Err(err) = self.send_message( - serde_json::to_string(&fb_payload).unwrap_or_default(), - ) { - error!(target: "payload_builder", "Failed to send flashblock message: {}", err); - } - - // Record flashblock build duration - self.metrics - .flashblock_build_duration - .record(flashblock_build_start_time.elapsed()); - ctx.metrics - .payload_byte_size - .record(new_payload.block().size() as f64); - ctx.metrics - .payload_num_tx - .record(info.executed_transactions.len() as f64); - - best_payload.set(new_payload.clone()); - // Update bundle_state for next iteration - bundle_state = new_bundle_state; - total_gas_per_batch += gas_per_batch; - flashblock_count += 1; - tracing::info!(target: "payload_builder", "Flashblock {} built", flashblock_count); - } - } - } - None => { - // Exit loop if channel closed or cancelled - self.metrics.block_built_success.increment(1); - self.metrics - .flashblock_count - .record(flashblock_count as f64); - return Ok(()); - } - } - } - } -} - -impl PayloadBuilder for OpPayloadBuilder -where - Client: StateProviderFactory + ChainSpecProvider + Clone, - Pool: TransactionPool>, -{ - type Attributes = OpPayloadBuilderAttributes; - type BuiltPayload = OpBuiltPayload; - - fn try_build( - &self, - args: BuildArguments, - best_payload: BlockCell, - ) -> Result<(), PayloadBuilderError> { - self.build_payload(args, best_payload) - } -} - -pub fn build_block( - mut state: State, - ctx: &OpPayloadBuilderCtx, - info: &mut ExecutionInfo, -) -> Result<(OpBuiltPayload, FlashblocksPayloadV1, BundleState), PayloadBuilderError> -where - ChainSpec: EthChainSpec + OpHardforks, - DB: Database + AsRef

, - P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, -{ - // TODO: We must run this only once per block, but we are running it on every flashblock - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - let state_merge_start_time = Instant::now(); - state.merge_transitions(BundleRetention::Reverts); - ctx.metrics - .state_transition_merge_duration - .record(state_merge_start_time.elapsed()); - - let new_bundle = state.take_bundle(); - - let block_number = ctx.block_number(); - assert_eq!(block_number, ctx.parent().number + 1); - - let execution_outcome = ExecutionOutcome::new( - new_bundle.clone(), - vec![info.receipts.clone()], - block_number, - vec![], - ); - - 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 state_provider = state.database.as_ref(); - let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); - let (state_root, _trie_output) = { - state - .database - .as_ref() - .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" - ); - })? - }; - ctx.metrics - .state_root_calculation_duration - .record(state_root_start_time.elapsed()); - - let mut requests_hash = None; - let withdrawals_root = if ctx - .chain_spec - .is_isthmus_active_at_timestamp(ctx.attributes().timestamp()) - { - // always empty requests hash post isthmus - requests_hash = Some(EMPTY_REQUESTS_HASH); - - // 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.database.as_ref()) - .map_err(PayloadBuilderError::other)?, - ) - } else if ctx - .chain_spec - .is_canyon_active_at_timestamp(ctx.attributes().timestamp()) - { - Some(EMPTY_WITHDRAWALS) - } else { - None - }; - - // create the block header - let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); - - // 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(); - 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.clone(), - ommers: vec![], - withdrawals: ctx.withdrawals().cloned(), - }, - ); - - let sealed_block = Arc::new(block.seal_slow()); - debug!(target: "payload_builder", ?sealed_block, "sealed built block"); - - let block_hash = sealed_block.hash(); - - // pick the new transactions from the info field and update the last flashblock index - let new_transactions = info.executed_transactions[info.last_flashblock_index..].to_vec(); - - let new_transactions_encoded = new_transactions - .clone() - .into_iter() - .map(|tx| tx.encoded_2718().into()) - .collect::>(); - - let new_receipts = info.receipts[info.last_flashblock_index..].to_vec(); - info.last_flashblock_index = info.executed_transactions.len(); - let receipts_with_hash = new_transactions - .iter() - .zip(new_receipts.iter()) - .map(|(tx, receipt)| (tx.tx_hash(), receipt.clone())) - .collect::>(); - let new_account_balances = new_bundle - .state - .iter() - .filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance))) - .collect::>(); - - let metadata: FlashblocksMetadata = FlashblocksMetadata { - receipts: receipts_with_hash, - new_account_balances, - block_number: ctx.parent().number + 1, - }; - - // Prepare the flashblocks message - let fb_payload = FlashblocksPayloadV1 { - payload_id: ctx.payload_id(), - index: 0, - base: Some(ExecutionPayloadBaseV1 { - parent_beacon_block_root: ctx - .attributes() - .payload_attributes - .parent_beacon_block_root - .unwrap(), - parent_hash: ctx.parent().hash(), - fee_recipient: ctx.attributes().suggested_fee_recipient(), - prev_randao: ctx.attributes().payload_attributes.prev_randao, - block_number: ctx.parent().number + 1, - gas_limit: ctx.block_gas_limit(), - timestamp: ctx.attributes().payload_attributes.timestamp, - extra_data: ctx.extra_data()?, - base_fee_per_gas: ctx.base_fee().try_into().unwrap(), - }), - diff: ExecutionPayloadFlashblockDeltaV1 { - state_root, - receipts_root, - logs_bloom, - gas_used: info.cumulative_gas_used, - block_hash, - transactions: new_transactions_encoded, - withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), - withdrawals_root, - }, - metadata: serde_json::to_value(&metadata).unwrap_or_default(), - }; - - Ok(( - OpBuiltPayload::new( - ctx.payload_id(), - sealed_block, - info.total_fees, - // This must be set to NONE for now because we are doing merge transitions on every flashblock - // when it should only happen once per block, thus, it returns a confusing state back to op-reth. - // We can live without this for now because Op syncs up the executed block using new_payload - // calls, but eventually we would want to return the executed block here. - None, - ), - fb_payload, - new_bundle, - )) -} - -fn execute_pre_steps( - state: &mut State, - ctx: &OpPayloadBuilderCtx, -) -> Result, PayloadBuilderError> -where - ChainSpec: EthChainSpec + OpHardforks, - DB: Database, -{ - // 1. apply pre-execution changes - ctx.evm_config - .builder_for_next_block(state, ctx.parent(), ctx.block_env_attributes.clone()) - .map_err(PayloadBuilderError::other)? - .apply_pre_execution_changes()?; - - // 3. execute sequencer transactions - let info = ctx.execute_sequencer_transactions(state)?; - - Ok(info) -} - -/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. -pub 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 { - BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) - } -} - -/// Container type that holds all necessities to build a new payload. -#[derive(Debug)] -pub struct OpPayloadBuilderCtx { - /// The type that knows how to perform system calls and configure the evm. - pub evm_config: OpEvmConfig, - /// The chainspec - pub chain_spec: Arc, - /// How to build the payload. - pub config: PayloadConfig>, - /// Evm Settings - pub evm_env: EvmEnv, - /// Block env attributes for the current block. - pub block_env_attributes: OpNextBlockEnvAttributes, - /// Marker to check whether the job has been cancelled. - pub cancel: CancellationToken, - /// The metrics for the builder - pub metrics: OpRBuilderMetrics, -} - -impl OpPayloadBuilderCtx -where - ChainSpec: EthChainSpec + OpHardforks, -{ - /// Returns the parent block the payload will be build on. - pub fn parent(&self) -> &SealedHeader { - &self.config.parent_header - } - - /// Returns the builder attributes. - pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { - &self.config.attributes - } - - /// Returns the withdrawals if shanghai is active. - pub fn withdrawals(&self) -> Option<&Withdrawals> { - self.chain_spec - .is_shanghai_active_at_timestamp(self.attributes().timestamp()) - .then(|| &self.attributes().payload_attributes.withdrawals) - } - - /// Returns the block gas limit to target. - pub fn block_gas_limit(&self) -> u64 { - self.attributes() - .gas_limit - .unwrap_or(self.evm_env.block_env.gas_limit) - } - - /// Returns the block number for the block. - pub fn block_number(&self) -> u64 { - self.evm_env.block_env.number - } - - /// Returns the current base fee - pub fn base_fee(&self) -> u64 { - self.evm_env.block_env.basefee - } - - /// Returns the current blob gas price. - pub fn get_blob_gasprice(&self) -> Option { - self.evm_env - .block_env - .blob_gasprice() - .map(|gasprice| gasprice as u64) - } - - /// Returns the blob fields for the header. - /// - /// This will always return `Some(0)` after ecotone. - pub fn blob_fields(&self) -> (Option, Option) { - // 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. - if self.is_ecotone_active() { - (Some(0), Some(0)) - } else { - (None, None) - } - } - - /// Returns the extra data for the block. - /// - /// After holocene this extracts the extradata from the paylpad - pub fn extra_data(&self) -> Result { - if self.is_holocene_active() { - self.attributes() - .get_holocene_extra_data( - self.chain_spec.base_fee_params_at_timestamp( - self.attributes().payload_attributes.timestamp, - ), - ) - .map_err(PayloadBuilderError::other) - } else { - Ok(Default::default()) - } - } - - /// Returns the current fee settings for transactions from the mempool - pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { - BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) - } - - /// Returns the unique id for this payload job. - pub fn payload_id(&self) -> PayloadId { - self.attributes().payload_id() - } - - /// Returns true if regolith is active for the payload. - pub fn is_regolith_active(&self) -> bool { - self.chain_spec - .is_regolith_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if ecotone is active for the payload. - pub fn is_ecotone_active(&self) -> bool { - self.chain_spec - .is_ecotone_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if canyon is active for the payload. - pub fn is_canyon_active(&self) -> bool { - self.chain_spec - .is_canyon_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if holocene is active for the payload. - pub fn is_holocene_active(&self) -> bool { - self.chain_spec - .is_holocene_active_at_timestamp(self.attributes().timestamp()) - } -} - -impl OpPayloadBuilderCtx -where - ChainSpec: EthChainSpec + OpHardforks, -{ - /// Constructs a receipt for the given transaction. - fn build_receipt( - &self, - ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, - deposit_nonce: Option, - ) -> OpReceipt { - let receipt_builder = self.evm_config.block_executor_factory().receipt_builder(); - match receipt_builder.build_receipt(ctx) { - Ok(receipt) => receipt, - Err(ctx) => { - let receipt = alloy_consensus::Receipt { - // Success flag was added in `EIP-658: Embedding transaction status code - // in receipts`. - status: Eip658Value::Eip658(ctx.result.is_success()), - cumulative_gas_used: ctx.cumulative_gas_used, - logs: ctx.result.into_logs(), - }; - - receipt_builder.build_deposit_receipt(OpDepositReceipt { - inner: receipt, - deposit_nonce, - // The deposit receipt version was introduced in Canyon to indicate an - // update to how receipt hashes should be computed - // when set. The state transition process ensures - // this is only set for post-Canyon deposit - // transactions. - deposit_receipt_version: self.is_canyon_active().then_some(1), - }) - } - } - } - - /// Executes all sequencer transactions that are included in the payload attributes. - pub fn execute_sequencer_transactions( - &self, - db: &mut State, - ) -> Result, PayloadBuilderError> - where - DB: Database, - { - 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()); - - for sequencer_tx in &self.attributes().transactions { - // A sequencer's block should never contain blob transactions. - if sequencer_tx.value().is_eip4844() { - return Err(PayloadBuilderError::other( - OpPayloadBuilderError::BlobTransactionRejected, - )); - } - - // Convert the transaction to a [Recovered]. This is - // purely for the purposes of utilizing the `evm_config.tx_env`` function. - // Deposit transactions do not have signatures, so if the tx is a deposit, this - // will just pull in its `from` address. - let sequencer_tx = sequencer_tx - .value() - .clone() - .try_clone_into_recovered() - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) - })?; - - // Cache the depositor account prior to the state transition for the deposit nonce. - // - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor_nonce = (self.is_regolith_active() && sequencer_tx.is_deposit()) - .then(|| { - evm.db_mut() - .load_cache_account(sequencer_tx.signer()) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - }) - .transpose() - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( - sequencer_tx.signer(), - )) - })?; - - let ResultAndState { result, state } = match evm.transact(&sequencer_tx) { - Ok(res) => res, - Err(err) => { - if err.is_invalid_tx_err() { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue; - } - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); - } - }; - - // add gas used by the transaction to cumulative gas used, before creating the receipt - let gas_used = result.gas_used(); - info.cumulative_gas_used += gas_used; - - let ctx = ReceiptBuilderCtx { - tx: sequencer_tx.inner(), - evm: &evm, - result, - state: &state, - cumulative_gas_used: info.cumulative_gas_used, - }; - info.receipts.push(self.build_receipt(ctx, depositor_nonce)); - - // commit changes - evm.db_mut().commit(state); - - // append sender and transaction to the respective lists - info.executed_senders.push(sequencer_tx.signer()); - info.executed_transactions.push(sequencer_tx.into_inner()); - } - - Ok(info) - } - - /// Executes the given best transactions and updates the execution info. - /// - /// Returns `Ok(Some(())` if the job was cancelled. - pub fn execute_best_transactions( - &self, - info: &mut ExecutionInfo, - db: &mut State, - mut best_txs: impl PayloadTransactions< - Transaction: PoolTransaction, - >, - batch_gas_limit: u64, - ) -> Result, PayloadBuilderError> - where - DB: Database, - { - let base_fee = self.base_fee(); - let mut num_txs_considered = 0; - let mut num_txs_simulated = 0; - let mut num_txs_simulated_success = 0; - let mut num_txs_simulated_fail = 0; - - let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); - - while let Some(tx) = best_txs.next(()) { - let tx = tx.into_consensus(); - num_txs_considered += 1; - - // check in info if the txn has been executed already - if info.executed_transactions.contains(&tx) { - continue; - } - - // ensure we still have capacity for this transaction - if info.is_tx_over_limits(tx.inner(), batch_gas_limit, None, None) { - // we can't fit this transaction into the block, so we need to mark it as - // invalid which also removes all dependent transaction from - // the iterator before we can continue - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } - - // A sequencer's block should never contain blob or deposit transactions from the pool. - if tx.is_eip4844() || tx.is_deposit() { - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } - - // check if the job was cancelled, if so we can exit early - if self.cancel.is_cancelled() { - return Ok(Some(())); - } - - let tx_simulation_start_time = Instant::now(); - let ResultAndState { result, state } = match evm.transact(&tx) { - Ok(res) => res, - Err(err) => { - if let Some(err) = err.as_invalid_tx_err() { - if err.is_nonce_too_low() { - // if the nonce is too low, we can skip this transaction - trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - } - - continue; - } - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); - } - }; - - self.metrics - .tx_simulation_duration - .record(tx_simulation_start_time.elapsed()); - self.metrics.tx_byte_size.record(tx.inner().size() as f64); - num_txs_simulated += 1; - - if result.is_success() { - num_txs_simulated_success += 1; - } else { - num_txs_simulated_fail += 1; - trace!(target: "payload_builder", ?tx, "reverted transaction"); - } - - // add gas used by the transaction to cumulative gas used, before creating the receipt - let gas_used = result.gas_used(); - info.cumulative_gas_used += gas_used; - - let ctx = ReceiptBuilderCtx { - tx: tx.inner(), - evm: &evm, - result, - state: &state, - cumulative_gas_used: info.cumulative_gas_used, - }; - info.receipts.push(self.build_receipt(ctx, None)); - - // commit changes - evm.db_mut().commit(state); - - // update add to total fees - let miner_fee = tx - .effective_tip_per_gas(base_fee) - .expect("fee is always valid; execution succeeded"); - info.total_fees += U256::from(miner_fee) * U256::from(gas_used); - - // append sender and transaction to the respective lists - info.executed_senders.push(tx.signer()); - info.executed_transactions.push(tx.into_inner()); - } - - self.metrics - .payload_num_tx_considered - .record(num_txs_considered as f64); - self.metrics - .payload_num_tx_simulated - .record(num_txs_simulated as f64); - self.metrics - .payload_num_tx_simulated_success - .record(num_txs_simulated_success as f64); - self.metrics - .payload_num_tx_simulated_fail - .record(num_txs_simulated_fail as f64); - - Ok(None) - } -} diff --git a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs b/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs deleted file mode 100644 index 377aafce..00000000 --- a/crates/builder/op-rbuilder/src/payload_builder_vanilla.rs +++ /dev/null @@ -1,1221 +0,0 @@ -use crate::{ - generator::BuildArguments, - metrics::OpRBuilderMetrics, - primitives::reth::ExecutionInfo, - tx::{FBPoolTransaction, MaybeRevertingTransaction}, - tx_signer::Signer, -}; -use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, transaction::Recovered, Eip658Value, Header, Transaction, - TxEip1559, Typed2718, EMPTY_OMMER_ROOT_HASH, -}; -use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; -use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxKind, U256}; -use alloy_rpc_types_engine::PayloadId; -use alloy_rpc_types_eth::Withdrawals; -use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; -use op_revm::OpSpecId; -use reth::{ - builder::{components::PayloadBuilderBuilder, node::FullNodeTypes, BuilderContext}, - core::primitives::InMemorySize, -}; -use reth_basic_payload_builder::{ - BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour, PayloadConfig, -}; -use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; -use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; -use reth_evm::{ - env::EvmEnv, eth::receipt_builder::ReceiptBuilderCtx, execute::BlockBuilder, ConfigureEvm, - Database, Evm, EvmError, InvalidTxError, -}; -use reth_execution_types::ExecutionOutcome; -use reth_node_api::{NodePrimitives, NodeTypes, PrimitivesTy}; -use reth_node_builder::components::BasicPayloadServiceBuilder; -use reth_optimism_chainspec::OpChainSpec; -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::OpEngineTypes; -use reth_optimism_payload_builder::{ - config::{OpBuilderConfig, OpDAConfig}, - error::OpPayloadBuilderError, - payload::{OpBuiltPayload, OpPayloadBuilderAttributes}, - OpPayloadPrimitives, -}; -use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; -use reth_payload_builder_primitives::PayloadBuilderError; -use reth_payload_primitives::PayloadBuilderAttributes; -use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; -use reth_primitives::{BlockBody, SealedHeader}; -use reth_primitives_traits::{proofs, Block as _, RecoveredBlock, SignedTransaction}; -use reth_provider::{ - HashedPostStateProvider, ProviderError, StateProviderFactory, StateRootProvider, - StorageRootProvider, -}; -use reth_revm::database::StateProviderDatabase; -use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; -use revm::{ - context::{result::ResultAndState, Block as _}, - database::{states::bundle_state::BundleRetention, State}, - DatabaseCommit, -}; -use std::{sync::Arc, time::Instant}; -use tokio_util::sync::CancellationToken; -use tracing::*; - -// From https://eips.ethereum.org/EIPS/eip-7623 -const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10; - -/// Holds the state after execution -#[derive(Debug)] -pub struct ExecutedPayload { - /// Tracked execution info - pub info: ExecutionInfo, -} - -#[derive(Debug, Clone, Default)] -#[non_exhaustive] -pub struct CustomOpPayloadBuilder { - builder_signer: Option, - #[allow(dead_code)] - extra_block_deadline: std::time::Duration, - #[cfg(feature = "flashblocks")] - flashblocks_ws_url: String, - #[cfg(feature = "flashblocks")] - chain_block_time: u64, - #[cfg(feature = "flashblocks")] - flashblock_block_time: u64, -} - -impl CustomOpPayloadBuilder { - #[cfg(feature = "flashblocks")] - pub fn new( - builder_signer: Option, - flashblocks_ws_url: String, - chain_block_time: u64, - flashblock_block_time: u64, - ) -> Self { - Self { - builder_signer, - flashblocks_ws_url, - chain_block_time, - flashblock_block_time, - } - } - - #[cfg(not(feature = "flashblocks"))] - pub fn new( - builder_signer: Option, - extra_block_deadline: std::time::Duration, - _flashblocks_ws_url: String, - _chain_block_time: u64, - _flashblock_block_time: u64, - ) -> BasicPayloadServiceBuilder { - BasicPayloadServiceBuilder::new(CustomOpPayloadBuilder { - builder_signer, - extra_block_deadline, - }) - } -} - -impl PayloadBuilderBuilder for CustomOpPayloadBuilder -where - Node: FullNodeTypes< - Types: NodeTypes< - Payload = OpEngineTypes, - ChainSpec = OpChainSpec, - Primitives = OpPrimitives, - >, - >, - Pool: TransactionPool> - + Unpin - + 'static, - ::Transaction: FBPoolTransaction, - Evm: ConfigureEvm< - Primitives = PrimitivesTy, - NextBlockEnvCtx = OpNextBlockEnvAttributes, - > + 'static, -{ - type PayloadBuilder = OpPayloadBuilderVanilla; - - async fn build_payload_builder( - self, - ctx: &BuilderContext, - pool: Pool, - _evm_config: Evm, - ) -> eyre::Result { - Ok(OpPayloadBuilderVanilla::new( - OpEvmConfig::optimism(ctx.chain_spec()), - self.builder_signer, - pool, - ctx.provider().clone(), - )) - } -} - -impl reth_basic_payload_builder::PayloadBuilder - for OpPayloadBuilderVanilla -where - Pool: TransactionPool>, - Client: StateProviderFactory + ChainSpecProvider + Clone, - 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) - } -} - -/// Optimism's payload builder -#[derive(Debug, Clone)] -pub struct OpPayloadBuilderVanilla { - /// The type responsible for creating the evm. - pub evm_config: OpEvmConfig, - /// The builder's signer key to use for an end of block tx - pub builder_signer: Option, - /// The transaction pool - pub pool: Pool, - /// Node client - pub client: Client, - /// Settings for the builder, e.g. DA settings. - pub config: OpBuilderConfig, - /// 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: OpRBuilderMetrics, -} - -impl OpPayloadBuilderVanilla { - /// `OpPayloadBuilder` constructor. - pub fn new( - evm_config: OpEvmConfig, - builder_signer: Option, - pool: Pool, - client: Client, - ) -> Self { - Self::with_builder_config(evm_config, builder_signer, pool, client, Default::default()) - } - - pub fn with_builder_config( - evm_config: OpEvmConfig, - builder_signer: Option, - pool: Pool, - client: Client, - config: OpBuilderConfig, - ) -> Self { - Self { - pool, - client, - config, - evm_config, - best_transactions: (), - metrics: Default::default(), - builder_signer, - } - } -} - -impl OpPayloadBuilderVanilla -where - Pool: TransactionPool>, - Client: StateProviderFactory + ChainSpecProvider, -{ - /// 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>( - &self, - args: BuildArguments, OpBuiltPayload>, - best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, - ) -> Result, PayloadBuilderError> - where - Txs: PayloadTransactions>, - { - let BuildArguments { - mut cached_reads, - config, - cancel, - } = args; - - let chain_spec = self.client.chain_spec(); - let timestamp = config.attributes.timestamp(); - 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: 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 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(), - chain_spec, - config, - evm_env, - block_env_attributes, - cancel, - builder_signer: self.builder_signer, - metrics: self.metrics.clone(), - }; - - let builder = OpBuilder::new(best); - - let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(state_provider); - - if ctx.attributes().no_tx_pool { - let db = State::builder() - .with_database(state) - .with_bundle_update() - .build(); - builder.build(db, ctx) - } else { - // sequencer mode we can reuse cachedreads from previous runs - let db = State::builder() - .with_database(cached_reads.as_db_mut(state)) - .with_bundle_update() - .build(); - builder.build(db, ctx) - } - .map(|out| 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 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), - } - } -} - -impl OpBuilder<'_, Txs> { - /// Executes the payload and returns the outcome. - pub fn execute( - self, - state: &mut State, - ctx: &OpPayloadBuilderCtx, - ) -> Result>, PayloadBuilderError> - where - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, - Txs: PayloadTransactions>, - ChainSpec: EthChainSpec + OpHardforks, - DB: Database + AsRef

, - P: StorageRootProvider, - { - 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(state, 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(state)?; - - ctx.metrics - .sequencer_tx_duration - .record(sequencer_tx_start_time.elapsed()); - - // 4. if mem pool transactions are requested we execute them - - // gas reserved for builder tx - let message = format!("Block Number: {}", ctx.block_number()) - .as_bytes() - .to_vec(); - let builder_tx_gas = ctx - .builder_signer() - .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); - let block_gas_limit = ctx.block_gas_limit() - builder_tx_gas; - // Save some space in the block_da_limit for builder tx - let builder_tx_da_size = ctx - .estimate_builder_tx_da_size(state, builder_tx_gas, message.clone()) - .unwrap_or(0); - let block_da_limit = ctx - .da_config - .max_da_block_size() - .map(|da_size| da_size - builder_tx_da_size as u64); - // Check that it's possible to create builder tx, considering max_da_tx_size, otherwise panic - if let Some(tx_da_limit) = ctx.da_config.max_da_tx_size() { - // Panic indicate max_da_tx_size misconfiguration - assert!( - tx_da_limit >= builder_tx_da_size as u64, - "The configured da_config.max_da_tx_size is too small to accommodate builder tx." - ); - } - - if !ctx.attributes().no_tx_pool { - let best_txs_start_time = Instant::now(); - let best_txs = best(ctx.best_transaction_attributes()); - ctx.metrics - .transaction_pool_fetch_duration - .record(best_txs_start_time.elapsed()); - if ctx - .execute_best_transactions( - &mut info, - state, - best_txs, - block_gas_limit, - block_da_limit, - )? - .is_some() - { - return Ok(BuildOutcomeKind::Cancelled); - } - } - - // Add builder tx to the block - ctx.add_builder_tx(&mut info, state, builder_tx_gas, message); - - let state_merge_start_time = Instant::now(); - - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call - state.merge_transitions(BundleRetention::Reverts); - - ctx.metrics - .state_transition_merge_duration - .record(state_merge_start_time.elapsed()); - ctx.metrics - .payload_num_tx - .record(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 fn build( - self, - mut state: State, - ctx: OpPayloadBuilderCtx, - ) -> Result, PayloadBuilderError> - where - ChainSpec: EthChainSpec + OpHardforks, - Txs: PayloadTransactions>, - DB: Database + AsRef

, - P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, - { - let ExecutedPayload { info } = match self.execute(&mut state, &ctx)? { - 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(); - let execution_outcome = ExecutionOutcome::new( - state.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 state_provider = state.database.as_ref(); - let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); - let (state_root, trie_output) = { - state - .database - .as_ref() - .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" - ); - })? - }; - - ctx.metrics - .state_root_calculation_duration - .record(state_root_start_time.elapsed()); - - 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.database.as_ref()) - .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); - - // 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(); - 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: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { - block: ExecutedBlock { - recovered_block: Arc::new(RecoveredBlock::< - alloy_consensus::Block, - >::new_sealed( - sealed_block.as_ref().clone(), info.executed_senders - )), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - }, - trie: 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(payload.block().size() 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 }) - } - } -} - -/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. -pub 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 { - BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) - } -} - -/// Container type that holds all necessities to build a new payload. -#[derive(Debug)] -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 - pub da_config: OpDAConfig, - /// The chainspec - pub chain_spec: Arc, - /// How to build the payload. - pub config: PayloadConfig>, - /// Evm Settings - pub evm_env: EvmEnv, - /// Block env attributes for the current block. - pub block_env_attributes: OpNextBlockEnvAttributes, - /// Marker to check whether the job has been cancelled. - pub cancel: CancellationToken, - /// The builder signer - pub builder_signer: Option, - /// The metrics for the builder - pub metrics: OpRBuilderMetrics, -} - -impl OpPayloadBuilderCtx -where - ChainSpec: EthChainSpec + OpHardforks, - N: NodePrimitives, -{ - /// Returns the parent block the payload will be build on. - pub fn parent(&self) -> &SealedHeader { - &self.config.parent_header - } - - /// Returns the builder attributes. - pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { - &self.config.attributes - } - - /// Returns the withdrawals if shanghai is active. - pub fn withdrawals(&self) -> Option<&Withdrawals> { - self.chain_spec - .is_shanghai_active_at_timestamp(self.attributes().timestamp()) - .then(|| &self.attributes().payload_attributes.withdrawals) - } - - /// Returns the block gas limit to target. - pub fn block_gas_limit(&self) -> u64 { - self.attributes() - .gas_limit - .unwrap_or(self.evm_env.block_env.gas_limit) - } - - /// Returns the block number for the block. - pub fn block_number(&self) -> u64 { - self.evm_env.block_env.number - } - - /// Returns the current base fee - pub fn base_fee(&self) -> u64 { - self.evm_env.block_env.basefee - } - - /// Returns the current blob gas price. - pub fn get_blob_gasprice(&self) -> Option { - self.evm_env - .block_env - .blob_gasprice() - .map(|gasprice| gasprice as u64) - } - - /// Returns the blob fields for the header. - /// - /// This will always return `Some(0)` after ecotone. - pub fn blob_fields(&self) -> (Option, Option) { - // 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. - if self.is_ecotone_active() { - (Some(0), Some(0)) - } else { - (None, None) - } - } - - /// Returns the extra data for the block. - /// - /// After holocene this extracts the extradata from the paylpad - pub fn extra_data(&self) -> Result { - if self.is_holocene_active() { - self.attributes() - .get_holocene_extra_data( - self.chain_spec.base_fee_params_at_timestamp( - self.attributes().payload_attributes.timestamp, - ), - ) - .map_err(PayloadBuilderError::other) - } else { - Ok(Default::default()) - } - } - - /// Returns the current fee settings for transactions from the mempool - pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { - BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) - } - - /// Returns the unique id for this payload job. - pub fn payload_id(&self) -> PayloadId { - self.attributes().payload_id() - } - - /// Returns true if regolith is active for the payload. - pub fn is_regolith_active(&self) -> bool { - self.chain_spec - .is_regolith_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if ecotone is active for the payload. - pub fn is_ecotone_active(&self) -> bool { - self.chain_spec - .is_ecotone_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if canyon is active for the payload. - pub fn is_canyon_active(&self) -> bool { - self.chain_spec - .is_canyon_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if holocene is active for the payload. - pub fn is_holocene_active(&self) -> bool { - self.chain_spec - .is_holocene_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns true if isthmus is active for the payload. - pub fn is_isthmus_active(&self) -> bool { - self.chain_spec - .is_isthmus_active_at_timestamp(self.attributes().timestamp()) - } - - /// Returns the chain id - pub fn chain_id(&self) -> u64 { - self.chain_spec.chain_id() - } - - /// Returns the builder signer - pub fn builder_signer(&self) -> Option { - self.builder_signer - } -} - -impl OpPayloadBuilderCtx -where - ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives<_TX = OpTransactionSigned>, -{ - /// Constructs a receipt for the given transaction. - fn build_receipt( - &self, - ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, - deposit_nonce: Option, - ) -> OpReceipt { - let receipt_builder = self.evm_config.block_executor_factory().receipt_builder(); - match receipt_builder.build_receipt(ctx) { - Ok(receipt) => receipt, - Err(ctx) => { - let receipt = alloy_consensus::Receipt { - // Success flag was added in `EIP-658: Embedding transaction status code - // in receipts`. - status: Eip658Value::Eip658(ctx.result.is_success()), - cumulative_gas_used: ctx.cumulative_gas_used, - logs: ctx.result.into_logs(), - }; - - receipt_builder.build_deposit_receipt(OpDepositReceipt { - inner: receipt, - deposit_nonce, - // The deposit receipt version was introduced in Canyon to indicate an - // update to how receipt hashes should be computed - // when set. The state transition process ensures - // this is only set for post-Canyon deposit - // transactions. - deposit_receipt_version: self.is_canyon_active().then_some(1), - }) - } - } - } - - /// Executes all sequencer transactions that are included in the payload attributes. - pub fn execute_sequencer_transactions( - &self, - db: &mut State, - ) -> Result, PayloadBuilderError> - where - DB: Database, - { - 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()); - - for sequencer_tx in &self.attributes().transactions { - // A sequencer's block should never contain blob transactions. - if sequencer_tx.value().is_eip4844() { - return Err(PayloadBuilderError::other( - OpPayloadBuilderError::BlobTransactionRejected, - )); - } - - // Convert the transaction to a [Recovered]. This is - // purely for the purposes of utilizing the `evm_config.tx_env`` function. - // Deposit transactions do not have signatures, so if the tx is a deposit, this - // will just pull in its `from` address. - let sequencer_tx = sequencer_tx - .value() - .try_clone_into_recovered() - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) - })?; - - // Cache the depositor account prior to the state transition for the deposit nonce. - // - // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces - // were not introduced in Bedrock. In addition, regular transactions don't have deposit - // nonces, so we don't need to touch the DB for those. - let depositor_nonce = (self.is_regolith_active() && sequencer_tx.is_deposit()) - .then(|| { - evm.db_mut() - .load_cache_account(sequencer_tx.signer()) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - }) - .transpose() - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed( - sequencer_tx.signer(), - )) - })?; - - let ResultAndState { result, state } = match evm.transact(&sequencer_tx) { - Ok(res) => res, - Err(err) => { - if err.is_invalid_tx_err() { - trace!(target: "payload_builder", %err, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue; - } - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); - } - }; - - // add gas used by the transaction to cumulative gas used, before creating the receipt - let gas_used = result.gas_used(); - info.cumulative_gas_used += gas_used; - - let ctx = ReceiptBuilderCtx { - tx: sequencer_tx.inner(), - evm: &evm, - result, - state: &state, - cumulative_gas_used: info.cumulative_gas_used, - }; - info.receipts.push(self.build_receipt(ctx, depositor_nonce)); - - // commit changes - evm.db_mut().commit(state); - - // append sender and transaction to the respective lists - info.executed_senders.push(sequencer_tx.signer()); - info.executed_transactions.push(sequencer_tx.into_inner()); - } - - Ok(info) - } - - /// Executes the given best transactions and updates the execution info. - /// - /// Returns `Ok(Some(())` if the job was cancelled. - pub fn execute_best_transactions( - &self, - info: &mut ExecutionInfo, - db: &mut State, - mut best_txs: impl PayloadTransactions< - Transaction: FBPoolTransaction, - >, - block_gas_limit: u64, - block_da_limit: Option, - ) -> Result, PayloadBuilderError> - where - DB: Database, - { - let execute_txs_start_time = Instant::now(); - let mut num_txs_considered = 0; - let mut num_txs_simulated = 0; - let mut num_txs_simulated_success = 0; - let mut num_txs_simulated_fail = 0; - let base_fee = self.base_fee(); - let tx_da_limit = self.da_config.max_da_tx_size(); - let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); - - while let Some(tx) = best_txs.next(()) { - let exclude_reverting_txs = tx.exclude_reverting_txs(); - - let tx = tx.into_consensus(); - num_txs_considered += 1; - // ensure we still have capacity for this transaction - if info.is_tx_over_limits(tx.inner(), block_gas_limit, tx_da_limit, block_da_limit) { - // we can't fit this transaction into the block, so we need to mark it as - // invalid which also removes all dependent transaction from - // the iterator before we can continue - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } - - // A sequencer's block should never contain blob or deposit transactions from the pool. - if tx.is_eip4844() || tx.is_deposit() { - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } - - // check if the job was cancelled, if so we can exit early - if self.cancel.is_cancelled() { - return Ok(Some(())); - } - - let tx_simulation_start_time = Instant::now(); - let ResultAndState { result, state } = match evm.transact(&tx) { - Ok(res) => res, - Err(err) => { - if let Some(err) = err.as_invalid_tx_err() { - if err.is_nonce_too_low() { - // if the nonce is too low, we can skip this transaction - trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); - } else { - // if the transaction is invalid, we can skip it and all of its - // descendants - trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - } - - continue; - } - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); - } - }; - - self.metrics - .tx_simulation_duration - .record(tx_simulation_start_time.elapsed()); - self.metrics.tx_byte_size.record(tx.inner().size() as f64); - num_txs_simulated += 1; - if result.is_success() { - num_txs_simulated_success += 1; - } else { - num_txs_simulated_fail += 1; - if exclude_reverting_txs { - info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } - } - - // add gas used by the transaction to cumulative gas used, before creating the - // receipt - let gas_used = result.gas_used(); - info.cumulative_gas_used += gas_used; - - // Push transaction changeset and calculate header bloom filter for receipt. - let ctx = ReceiptBuilderCtx { - tx: tx.inner(), - evm: &evm, - result, - state: &state, - cumulative_gas_used: info.cumulative_gas_used, - }; - info.receipts.push(self.build_receipt(ctx, None)); - - // commit changes - evm.db_mut().commit(state); - - // update add to total fees - let miner_fee = tx - .effective_tip_per_gas(base_fee) - .expect("fee is always valid; execution succeeded"); - info.total_fees += U256::from(miner_fee) * U256::from(gas_used); - - // append sender and transaction to the respective lists - info.executed_senders.push(tx.signer()); - info.executed_transactions.push(tx.into_inner()); - } - - self.metrics - .payload_tx_simulation_duration - .record(execute_txs_start_time.elapsed()); - self.metrics - .payload_num_tx_considered - .record(num_txs_considered as f64); - self.metrics - .payload_num_tx_simulated - .record(num_txs_simulated as f64); - self.metrics - .payload_num_tx_simulated_success - .record(num_txs_simulated_success as f64); - self.metrics - .payload_num_tx_simulated_fail - .record(num_txs_simulated_fail as f64); - - Ok(None) - } - - pub fn add_builder_tx( - &self, - info: &mut ExecutionInfo, - db: &mut State, - builder_tx_gas: u64, - message: Vec, - ) -> Option<()> - where - DB: Database, - { - self.builder_signer() - .map(|signer| { - let base_fee = self.base_fee(); - let chain_id = self.chain_id(); - // Create and sign the transaction - let builder_tx = - signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; - - let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); - - let ResultAndState { result, state } = evm - .transact(&builder_tx) - .map_err(|err| PayloadBuilderError::EvmExecutionError(Box::new(err)))?; - - // Add gas used by the transaction to cumulative gas used, before creating the receipt - let gas_used = result.gas_used(); - info.cumulative_gas_used += gas_used; - - let ctx = ReceiptBuilderCtx { - tx: builder_tx.inner(), - evm: &evm, - result, - state: &state, - cumulative_gas_used: info.cumulative_gas_used, - }; - info.receipts.push(self.build_receipt(ctx, None)); - - // Release the db reference by dropping evm - drop(evm); - // Commit changes - db.commit(state); - - // Append sender and transaction to the respective lists - info.executed_senders.push(builder_tx.signer()); - info.executed_transactions.push(builder_tx.into_inner()); - Ok(()) - }) - .transpose() - .unwrap_or_else(|err: PayloadBuilderError| { - warn!(target: "payload_builder", %err, "Failed to add builder transaction"); - None - }) - } - - /// Calculates EIP 2718 builder transaction size - pub fn estimate_builder_tx_da_size( - &self, - db: &mut State, - builder_tx_gas: u64, - message: Vec, - ) -> Option - where - DB: Database, - { - self.builder_signer() - .map(|signer| { - let base_fee = self.base_fee(); - let chain_id = self.chain_id(); - // Create and sign the transaction - let builder_tx = - signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; - Ok(builder_tx.length()) - }) - .transpose() - .unwrap_or_else(|err: PayloadBuilderError| { - warn!(target: "payload_builder", %err, "Failed to add builder transaction"); - None - }) - } -} - -/// Creates signed builder tx to Address::ZERO and specified message as input -pub fn signed_builder_tx( - db: &mut State, - builder_tx_gas: u64, - message: Vec, - signer: Signer, - base_fee: u64, - chain_id: u64, -) -> Result, PayloadBuilderError> -where - DB: Database, -{ - // Create message with block number for the builder to sign - let nonce = db - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed(signer.address)) - })?; - - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit: builder_tx_gas, - max_fee_per_gas: 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(PayloadBuilderError::other)?; - - Ok(builder_tx) -} - -fn estimate_gas_for_builder_tx(input: Vec) -> 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; - - std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) -} diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 3f5bf177..1caf2a60 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -1,13 +1,13 @@ //! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570) use alloy_consensus::Transaction; use alloy_primitives::{private::alloy_rlp::Encodable, Address, U256}; -use reth_node_api::NodePrimitives; -use reth_optimism_primitives::OpReceipt; +use core::fmt::Debug; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; #[derive(Default, Debug)] -pub struct ExecutionInfo { +pub struct ExecutionInfo { /// All executed transactions (unrecovered). - pub executed_transactions: Vec, + pub executed_transactions: Vec, /// The recovered senders for the executed transactions. pub executed_senders: Vec

, /// The transaction receipts @@ -18,12 +18,11 @@ pub struct ExecutionInfo { pub cumulative_da_bytes_used: u64, /// Tracks fees from executed mempool transactions pub total_fees: U256, - #[cfg(feature = "flashblocks")] - /// Index of the last consumed flashblock - pub last_flashblock_index: usize, + /// Extra execution information that can be attached by individual builders. + pub extra: Extra, } -impl ExecutionInfo { +impl ExecutionInfo { /// Create a new instance with allocated slots. pub fn with_capacity(capacity: usize) -> Self { Self { @@ -33,8 +32,7 @@ impl ExecutionInfo { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO, - #[cfg(feature = "flashblocks")] - last_flashblock_index: 0, + extra: Default::default(), } } @@ -46,7 +44,7 @@ impl ExecutionInfo { /// maximum allowed DA limit per block. pub fn is_tx_over_limits( &self, - tx: &N::SignedTx, + tx: &OpTransactionSigned, block_gas_limit: u64, tx_data_limit: Option, block_data_limit: Option, diff --git a/crates/builder/op-rbuilder/src/tests/framework/op.rs b/crates/builder/op-rbuilder/src/tests/framework/op.rs index ea66f306..45c2daf7 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/op.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/op.rs @@ -143,6 +143,8 @@ impl Service for OpRbuilderConfig { } if let Some(flashblocks_ws_url) = &self.flashblocks_ws_url { + cmd.arg("--rollup.enable-flashblocks").arg("true"); + cmd.arg("--rollup.flashblocks-ws-url") .arg(flashblocks_ws_url); } diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index ce06ef85..70c8d3bd 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -145,7 +145,7 @@ impl TransactionBuilder { } pub async fn send(self) -> eyre::Result> { - let bundle_opts = self.bundle_opts.clone(); + let bundle_opts = self.bundle_opts; let provider = self.provider.clone(); let transaction = self.build().await; let transaction_encoded = transaction.encoded_2718(); diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs index 05ed01b6..e3f96187 100644 --- a/crates/builder/op-rbuilder/src/tests/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -2,8 +2,5 @@ mod framework; pub use framework::*; -#[cfg(not(feature = "flashblocks"))] -mod vanilla; - -#[cfg(feature = "flashblocks")] mod flashblocks; +mod vanilla; diff --git a/crates/builder/op-rbuilder/src/traits.rs b/crates/builder/op-rbuilder/src/traits.rs new file mode 100644 index 00000000..3c924353 --- /dev/null +++ b/crates/builder/op-rbuilder/src/traits.rs @@ -0,0 +1,70 @@ +use alloy_consensus::Header; +use reth_node_api::{FullNodeTypes, NodeTypes}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_node::OpEngineTypes; +use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; +use reth_payload_util::PayloadTransactions; +use reth_provider::{BlockReaderIdExt, ChainSpecProvider, StateProviderFactory}; +use reth_transaction_pool::TransactionPool; + +use crate::tx::FBPoolTransaction; + +pub trait NodeBounds: + FullNodeTypes< + Types: NodeTypes, +> +{ +} + +impl NodeBounds for T where + T: FullNodeTypes< + Types: NodeTypes< + Payload = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + > +{ +} + +pub trait PoolBounds: + TransactionPool> + Unpin + 'static +where + ::Transaction: FBPoolTransaction, +{ +} + +impl PoolBounds for T +where + T: TransactionPool> + + Unpin + + 'static, + ::Transaction: FBPoolTransaction, +{ +} + +pub trait ClientBounds: + StateProviderFactory + + ChainSpecProvider + + BlockReaderIdExt
+ + Clone +{ +} + +impl ClientBounds for T where + T: StateProviderFactory + + ChainSpecProvider + + BlockReaderIdExt
+ + Clone +{ +} + +pub trait PayloadTxsBounds: + PayloadTransactions> +{ +} + +impl PayloadTxsBounds for T where + T: PayloadTransactions> +{ +} From a23787e22077efebca163410994d7a9a7c9cd546 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 27 May 2025 12:47:25 +0700 Subject: [PATCH 100/262] Use correct DA transaction compression (#61) * Added drop impl to clean up after tests Changed revert tests a bit fmt Add txpool test Fix DA config, set it via miner Extend tests Add op-alloy-flz dependency and update related configurations - Added `op-alloy-flz` as a dependency in `Cargo.toml` and `Cargo.lock`. - Configured `op-alloy-flz` to be part of the workspace in `op-rbuilder`'s `Cargo.toml`. - Updated the `payload_builder_vanilla.rs` to utilize `op-alloy-flz` for transaction size estimation. - Enhanced test framework to include new data availability tests ensuring block size limits are respected. Add max data availability transaction and block size configuration - Introduced `max_da_tx_size` and `max_da_block_size` fields in `TestHarnessBuilder` and `OpRbuilderConfig`. - Added builder methods `with_max_da_tx_size` and `with_max_da_block_size` for setting these values. - Implemented a new test to ensure transaction size limits are respected in data availability scenarios. - Updated test module to include the new data availability test. Add cumulative_da_bytes_used accur Add da config Use correct DA transaction compression * conflict changes --- crates/builder/op-rbuilder/Cargo.toml | 4 + crates/builder/op-rbuilder/src/args/op.rs | 3 +- .../op-rbuilder/src/builders/context.rs | 22 +++- .../src/builders/flashblocks/payload.rs | 18 ++- .../src/builders/standard/payload.rs | 10 +- crates/builder/op-rbuilder/src/main.rs | 3 +- .../src/primitives/reth/execution.rs | 12 +- .../op-rbuilder/src/tests/framework/blocks.rs | 8 ++ .../src/tests/framework/harness.rs | 35 +++++- .../op-rbuilder/src/tests/framework/op.rs | 24 +++- .../src/tests/vanilla/data_availability.rs | 114 ++++++++++++++++++ .../op-rbuilder/src/tests/vanilla/mod.rs | 2 + .../op-rbuilder/src/tests/vanilla/revert.rs | 2 + .../op-rbuilder/src/tests/vanilla/txpool.rs | 69 +++++++++++ 14 files changed, 290 insertions(+), 36 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs create mode 100644 crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 40c9d6cd..994207c4 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -64,6 +64,7 @@ op-alloy-consensus.workspace = true op-alloy-rpc-types-engine.workspace = true op-alloy-rpc-types.workspace = true op-alloy-network.workspace = true +op-alloy-flz.workspace = true revm.workspace = true op-revm.workspace = true @@ -110,6 +111,9 @@ tikv-jemallocator = { version = "0.6", optional = true } vergen = { workspace = true, features = ["build", "cargo", "emit_and_set"] } vergen-git2.workspace = true +[dev-dependencies] +alloy-provider = {workspace = true, default-features = true, features = ["txpool-api"]} + [features] default = ["jemalloc"] diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 44dcae78..caf25cf4 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -5,9 +5,8 @@ //! clap [Args](clap::Args) for optimism rollup configuration use std::path::PathBuf; -use reth_optimism_node::args::RollupArgs; - use crate::tx_signer::Signer; +use reth_optimism_node::args::RollupArgs; /// Parameters for rollup configuration #[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 04e63158..7af42b28 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -1,7 +1,7 @@ use alloy_consensus::{Eip658Value, Transaction, TxEip1559}; -use alloy_eips::Typed2718; +use alloy_eips::{Encodable2718, Typed2718}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{private::alloy_rlp::Encodable, Address, Bytes, TxKind, U256}; +use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types_eth::Withdrawals; use core::fmt::Debug; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; @@ -19,6 +19,7 @@ use reth_optimism_forks::OpHardforks; use reth_optimism_node::OpPayloadBuilderAttributes; use reth_optimism_payload_builder::{config::OpDAConfig, error::OpPayloadBuilderError}; use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +use reth_optimism_txpool::estimated_da_size::DataAvailabilitySized; use reth_payload_builder::PayloadId; use reth_primitives::{Recovered, SealedHeader}; use reth_primitives_traits::{InMemorySize, SignedTransaction}; @@ -329,11 +330,18 @@ impl OpPayloadBuilderCtx { while let Some(tx) = best_txs.next(()) { let exclude_reverting_txs = tx.exclude_reverting_txs(); + let tx_da_size = tx.estimated_da_size(); let tx = tx.into_consensus(); num_txs_considered += 1; // ensure we still have capacity for this transaction - if info.is_tx_over_limits(tx.inner(), block_gas_limit, tx_da_limit, block_da_limit) { + if info.is_tx_over_limits( + tx_da_size, + block_gas_limit, + tx_da_limit, + block_da_limit, + tx.gas_limit(), + ) { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from // the iterator before we can continue @@ -394,6 +402,8 @@ impl OpPayloadBuilderCtx { // receipt let gas_used = result.gas_used(); info.cumulative_gas_used += gas_used; + // record tx da size + info.cumulative_da_bytes_used += tx_da_size; // Push transaction changeset and calculate header bloom filter for receipt. let ctx = ReceiptBuilderCtx { @@ -498,7 +508,7 @@ impl OpPayloadBuilderCtx { db: &mut State, builder_tx_gas: u64, message: Vec, - ) -> Option + ) -> Option where DB: Database, { @@ -509,7 +519,9 @@ impl OpPayloadBuilderCtx { // Create and sign the transaction let builder_tx = signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; - Ok(builder_tx.length()) + Ok(op_alloy_flz::tx_estimated_size_fjord( + builder_tx.encoded_2718().as_slice(), + )) }) .transpose() .unwrap_or_else(|err: PayloadBuilderError| { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index fe1e638f..128c9a68 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -222,10 +222,11 @@ where let gas_per_batch = ctx.block_gas_limit() / self.config.flashblocks_per_block(); let mut total_gas_per_batch = gas_per_batch; - let total_da_bytes_per_batch = ctx + let da_per_batch = ctx .da_config .max_da_block_size() - .map(|limit| limit / self.config.flashblocks_per_block()); + .map(|da_limit| da_limit / self.config.flashblocks_per_block()); + let mut total_da_per_batch = da_per_batch; let mut flashblock_count = 0; // Create a channel to coordinate flashblock building @@ -295,11 +296,11 @@ where // Continue with flashblock building tracing::info!( target: "payload_builder", - "Building flashblock {} {}", + "Building flashblock idx={} target_gas={} taget_da={}", flashblock_count, total_gas_per_batch, + total_da_per_batch.unwrap_or(0), ); - let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); @@ -324,7 +325,7 @@ where &mut db, best_txs, total_gas_per_batch.min(ctx.block_gas_limit()), - total_da_bytes_per_batch, + total_da_per_batch, )?; ctx.metrics .payload_tx_simulation_duration @@ -377,6 +378,13 @@ where // Update bundle_state for next iteration bundle_state = new_bundle_state; total_gas_per_batch += gas_per_batch; + if let Some(da_limit) = da_per_batch { + if let Some(da) = total_da_per_batch.as_mut() { + *da += da_limit; + } else { + error!("Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set"); + } + } flashblock_count += 1; tracing::info!(target: "payload_builder", "Flashblock {} built", flashblock_count); } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 4c70e448..fc6e93d0 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -355,15 +355,7 @@ impl OpBuilder<'_, Txs> { let block_da_limit = ctx .da_config .max_da_block_size() - .map(|da_size| da_size - builder_tx_da_size as u64); - // Check that it's possible to create builder tx, considering max_da_tx_size, otherwise panic - if let Some(tx_da_limit) = ctx.da_config.max_da_tx_size() { - // Panic indicate max_da_tx_size misconfiguration - assert!( - tx_da_limit >= builder_tx_da_size as u64, - "The configured da_config.max_da_tx_size is too small to accommodate builder tx." - ); - } + .map(|da_size| da_size.saturating_sub(builder_tx_da_size)); if !ctx.attributes().no_tx_pool { let best_txs_start_time = Instant::now(); diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 21f63a7b..f378f961 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -68,7 +68,7 @@ where cli.run(|builder, builder_args| async move { let builder_config = BuilderConfig::::try_from(builder_args.clone()) .expect("Failed to convert rollup args to builder config"); - + let da_config = builder_config.da_config.clone(); let rollup_args = builder_args.rollup_args; let op_node = OpNode::new(rollup_args.clone()); let handle = builder @@ -95,6 +95,7 @@ where OpAddOnsBuilder::default() .with_sequencer(rollup_args.sequencer.clone()) .with_enable_tx_conditional(rollup_args.enable_tx_conditional) + .with_da_config(da_config) .build(), ) .extend_rpc_modules(move |ctx| { diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 1caf2a60..32bf6a55 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -1,6 +1,5 @@ //! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570) -use alloy_consensus::Transaction; -use alloy_primitives::{private::alloy_rlp::Encodable, Address, U256}; +use alloy_primitives::{Address, U256}; use core::fmt::Debug; use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; @@ -44,21 +43,22 @@ impl ExecutionInfo { /// maximum allowed DA limit per block. pub fn is_tx_over_limits( &self, - tx: &OpTransactionSigned, + tx_da_size: u64, block_gas_limit: u64, tx_data_limit: Option, block_data_limit: Option, + tx_gas_limit: u64, ) -> bool { - if tx_data_limit.is_some_and(|da_limit| tx.length() as u64 > da_limit) { + if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) { return true; } if block_data_limit - .is_some_and(|da_limit| self.cumulative_da_bytes_used + (tx.length() as u64) > da_limit) + .is_some_and(|da_limit| self.cumulative_da_bytes_used + tx_da_size > da_limit) { return true; } - self.cumulative_gas_used + tx.gas_limit() > block_gas_limit + self.cumulative_gas_used + tx_gas_limit > block_gas_limit } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs index fb4012f0..a9fd7ba1 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs @@ -372,4 +372,12 @@ impl BlockGenerated { pub fn includes(&self, tx_hash: B256) -> bool { self.block.transactions.hashes().any(|hash| hash == tx_hash) } + + pub fn includes_vec(&self, tx_hashes: Vec) -> bool { + tx_hashes.iter().all(|hash| self.includes(*hash)) + } + + pub fn not_includes_vec(&self, tx_hashes: Vec) -> bool { + tx_hashes.iter().all(|hash| self.not_includes(*hash)) + } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/harness.rs b/crates/builder/op-rbuilder/src/tests/framework/harness.rs index 64c38d9c..f55da367 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/harness.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/harness.rs @@ -25,6 +25,8 @@ pub struct TestHarnessBuilder { flashblocks_ws_url: Option, chain_block_time: Option, flashbots_block_time: Option, + namespaces: Option, + extra_params: Option, } impl TestHarnessBuilder { @@ -35,6 +37,8 @@ impl TestHarnessBuilder { flashblocks_ws_url: None, chain_block_time: None, flashbots_block_time: None, + namespaces: None, + extra_params: None, } } @@ -58,6 +62,16 @@ impl TestHarnessBuilder { self } + pub fn with_namespaces(mut self, namespaces: &str) -> Self { + self.namespaces = Some(namespaces.to_string()); + self + } + + pub fn with_extra_params(mut self, extra_params: &str) -> Self { + self.extra_params = Some(extra_params.to_string()); + self + } + pub async fn build(self) -> eyre::Result { let mut framework = IntegrationFramework::new(&self.name).unwrap(); @@ -69,7 +83,7 @@ impl TestHarnessBuilder { std::fs::write(&genesis_path, genesis)?; // create the builder - let builder_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); + let builder_data_dir: PathBuf = std::env::temp_dir().join(Uuid::new_v4().to_string()); let builder_auth_rpc_port = get_available_port(); let builder_http_port = get_available_port(); let mut op_rbuilder_config = OpRbuilderConfig::new() @@ -79,8 +93,9 @@ impl TestHarnessBuilder { .network_port(get_available_port()) .http_port(builder_http_port) .with_builder_private_key(BUILDER_PRIVATE_KEY) - .with_revert_protection(self.use_revert_protection); - + .with_revert_protection(self.use_revert_protection) + .with_namespaces(self.namespaces) + .with_extra_params(self.extra_params); if let Some(flashblocks_ws_url) = self.flashblocks_ws_url { op_rbuilder_config = op_rbuilder_config.with_flashblocks_ws_url(&flashblocks_ws_url); } @@ -113,7 +128,7 @@ impl TestHarnessBuilder { let builder_log_path = builder.log_path.clone(); Ok(TestHarness { - _framework: framework, + framework: framework, builder_auth_rpc_port, builder_http_port, validator_auth_rpc_port, @@ -123,7 +138,7 @@ impl TestHarnessBuilder { } pub struct TestHarness { - _framework: IntegrationFramework, + framework: IntegrationFramework, builder_auth_rpc_port: u16, builder_http_port: u16, validator_auth_rpc_port: u16, @@ -237,6 +252,16 @@ impl TestHarness { } } +impl Drop for TestHarness { + fn drop(&mut self) { + for service in &mut self.framework.services { + let res = service.stop(); + if let Err(e) = res { + println!("Failed to stop service: {}", e); + } + } + } +} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TransactionStatus { NotFound, diff --git a/crates/builder/op-rbuilder/src/tests/framework/op.rs b/crates/builder/op-rbuilder/src/tests/framework/op.rs index 45c2daf7..3dea0513 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/op.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/op.rs @@ -27,6 +27,8 @@ pub struct OpRbuilderConfig { chain_block_time: Option, flashbots_block_time: Option, with_revert_protection: Option, + namespaces: Option, + extra_params: Option, } impl OpRbuilderConfig { @@ -83,6 +85,16 @@ impl OpRbuilderConfig { self.flashbots_block_time = Some(time); self } + + pub fn with_namespaces(mut self, namespaces: Option) -> Self { + self.namespaces = namespaces; + self + } + + pub fn with_extra_params(mut self, extra_params: Option) -> Self { + self.extra_params = extra_params; + self + } } impl Service for OpRbuilderConfig { @@ -137,9 +149,7 @@ impl Service for OpRbuilderConfig { if let Some(http_port) = self.http_port { cmd.arg("--http") .arg("--http.port") - .arg(http_port.to_string()) - .arg("--http.api") - .arg("eth,web3,txpool"); + .arg(http_port.to_string()); } if let Some(flashblocks_ws_url) = &self.flashblocks_ws_url { @@ -159,6 +169,14 @@ impl Service for OpRbuilderConfig { .arg(flashbots_block_time.to_string()); } + if let Some(namespaces) = &self.namespaces { + cmd.arg("--http.api").arg(namespaces); + } + + if let Some(extra_params) = &self.extra_params { + cmd.args(extra_params.split_ascii_whitespace()); + } + cmd } diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs b/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs new file mode 100644 index 00000000..f18385c7 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs @@ -0,0 +1,114 @@ +use crate::tests::framework::TestHarnessBuilder; +use alloy_provider::Provider; + +/// This test ensures that the transaction size limit is respected. +/// We will set limit to 1 byte and see that the builder will not include any transactions. +#[tokio::test] +async fn data_availability_tx_size_limit() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("data_availability_tx_size_limit") + .with_namespaces("admin,eth,miner") + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + // Set (max_tx_da_size, max_block_da_size), with this case block won't fit any transaction + let call = harness + .provider()? + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (1, 0)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + let unfit_tx = harness + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + let block = generator.generate_block().await?; + // tx should not be included because we set the tx_size_limit to 1 + assert!( + block.not_includes(*unfit_tx.tx_hash()), + "transaction should not be included in the block" + ); + + Ok(()) +} + +/// This test ensures that the block size limit is respected. +/// We will set limit to 1 byte and see that the builder will not include any transactions. +#[tokio::test] +async fn data_availability_block_size_limit() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("data_availability_block_size_limit") + .with_namespaces("admin,eth,miner") + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + // Set block da size to be small, so it won't include tx + let call = harness + .provider()? + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 1)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + let unfit_tx = harness.send_valid_transaction().await?; + let block = generator.generate_block().await?; + // tx should not be included because we set the tx_size_limit to 1 + assert!( + block.not_includes(*unfit_tx.tx_hash()), + "transaction should not be included in the block" + ); + + Ok(()) +} + +/// This test ensures that block will fill up to the limit. +/// Size of each transaction is 100000000 +/// We will set limit to 3 txs and see that the builder will include 3 transactions. +/// We should not forget about builder transaction so we will spawn only 2 regular txs. +#[tokio::test] +async fn data_availability_block_fill() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("data_availability_block_fill") + .with_namespaces("admin,eth,miner") + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + // Set block big enough so it could fit 3 transactions without tx size limit + let call = harness + .provider()? + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100000000 * 3)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + // We already have 2 so we will spawn one more to check that it won't be included (it won't fit + // because of builder tx) + let fit_tx_1 = harness + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + let fit_tx_2 = harness + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + let unfit_tx_3 = harness.create_transaction().send().await?; + + let block = generator.generate_block().await?; + // Now the first 2 txs will fit into the block + assert!(block.includes(*fit_tx_1.tx_hash()), "tx should be in block"); + assert!(block.includes(*fit_tx_2.tx_hash()), "tx should be in block"); + assert!( + block.not_includes(*unfit_tx_3.tx_hash()), + "unfit tx should not be in block" + ); + assert!( + harness.latest_block().await.transactions.len() == 4, + "builder + deposit + 2 valid txs should be in the block" + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs b/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs index f45fa23f..976a3dc3 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs @@ -1,7 +1,9 @@ #![cfg(test)] +mod data_availability; mod ordering; mod revert; mod smoke; +mod txpool; use super::*; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs index f4710612..037ab032 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -13,6 +13,7 @@ use crate::{ async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { let harness = TestHarnessBuilder::new("revert_protection_monitor_transaction_gc") .with_revert_protection() + .with_namespaces("eth,web3,txpool") .build() .await?; @@ -105,6 +106,7 @@ async fn revert_protection_disabled_bundle_endpoint_error() -> eyre::Result<()> async fn revert_protection_bundle() -> eyre::Result<()> { let harness = TestHarnessBuilder::new("revert_protection_bundle") .with_revert_protection() + .with_namespaces("eth,web3,txpool") .build() .await?; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs b/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs new file mode 100644 index 00000000..cbf15512 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs @@ -0,0 +1,69 @@ +use crate::tests::{framework::TestHarnessBuilder, ONE_ETH}; +use alloy_provider::ext::TxPoolApi; + +/// This test ensures that pending pool custom limit is respected and priority tx would be included even when pool if full. +#[tokio::test] +async fn pending_pool_limit() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("pending_pool_limit") + .with_namespaces("txpool,eth,debug,admin") + .with_extra_params("--txpool.pending-max-count 50") + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + // Send 50 txs from different addrs + let accounts = generator.create_funded_accounts(2, ONE_ETH).await?; + let acc_no_priority = accounts.first().unwrap(); + let acc_with_priority = accounts.last().unwrap(); + + for _ in 0..50 { + let _ = harness + .create_transaction() + .with_signer(*acc_no_priority) + .send() + .await?; + } + + let pool = harness + .provider() + .expect("provider not available") + .txpool_status() + .await?; + assert_eq!( + pool.pending, 50, + "Pending pool must contain at max 50 txs {:?}", + pool + ); + + // Send 10 txs that should be included in the block + let mut txs = Vec::new(); + for _ in 0..10 { + let tx = harness + .create_transaction() + .with_signer(*acc_with_priority) + .with_max_priority_fee_per_gas(10) + .send() + .await?; + txs.push(*tx.tx_hash()); + } + + let pool = harness + .provider() + .expect("provider not available") + .txpool_status() + .await?; + assert_eq!( + pool.pending, 50, + "Pending pool must contain at max 50 txs {:?}", + pool + ); + + // After we try building block our reverting tx would be removed and other tx will move to queue pool + let block = generator.generate_block().await?; + + // Ensure that 10 extra txs got included + assert!(block.includes_vec(txs)); + + Ok(()) +} From 7e196e655bf7b6a9c5de5e4fbaba0147bb3dba3c Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 27 May 2025 13:30:16 +0200 Subject: [PATCH 101/262] Modity flashblocks ws bind/port flags (#71) * Modity flashblocks ws bind/port flags * Update * Fix --- crates/builder/op-rbuilder/src/args/mod.rs | 2 +- crates/builder/op-rbuilder/src/args/op.rs | 73 ++++++++++++------- .../src/builders/flashblocks/config.rs | 11 ++- .../src/tests/flashblocks/smoke.rs | 2 +- .../src/tests/framework/harness.rs | 12 +-- .../op-rbuilder/src/tests/framework/op.rs | 16 ++-- 6 files changed, 68 insertions(+), 48 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index 5c87428f..806a49da 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -65,7 +65,7 @@ impl CliExt for Cli { /// Currently supports `Standard` and `Flashblocks` modes. fn builder_mode(&self) -> BuilderMode { if let Commands::Node(ref node_command) = self.command { - if node_command.ext.enable_flashblocks { + if node_command.ext.flashblocks.enabled { return BuilderMode::Flashblocks; } } diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index caf25cf4..136e58ad 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -19,25 +19,6 @@ pub struct OpRbuilderArgs { #[arg(long = "rollup.builder-secret-key", env = "BUILDER_SECRET_KEY")] pub builder_signer: Option, - /// 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 = "rollup.enable-flashblocks", - default_value = "false", - env = "ENABLE_FLASHBLOCKS" - )] - pub enable_flashblocks: bool, - - /// Websocket port for flashblock payload builder - #[arg( - long = "rollup.flashblocks-ws-url", - env = "FLASHBLOCKS_WS_URL", - default_value = "127.0.0.1:1111" - )] - pub flashblocks_ws_url: String, /// chain block time in milliseconds #[arg( long = "rollup.chain-block-time", @@ -45,13 +26,7 @@ pub struct OpRbuilderArgs { env = "CHAIN_BLOCK_TIME" )] pub chain_block_time: u64, - /// flashblock block time in milliseconds - #[arg( - long = "rollup.flashblock-block-time", - default_value = "250", - env = "FLASHBLOCK_BLOCK_TIME" - )] - pub flashblock_block_time: u64, + /// Signals whether to log pool transaction events #[arg(long = "builder.log-pool-transactions", default_value = "false")] pub log_pool_transactions: bool, @@ -71,6 +46,9 @@ pub struct OpRbuilderArgs { env = "PLAYGROUND_DIR", )] pub playground: Option, + + #[command(flatten)] + pub flashblocks: FlashblocksArgs, } fn expand_path(s: &str) -> Result { @@ -80,3 +58,46 @@ fn expand_path(s: &str) -> Result { .parse() .map_err(|e| format!("invalid path after expansion: {e}")) } + +/// 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. +#[derive(Debug, Clone, Default, 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, + + /// 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, + + /// The address that we bind to for the websocket server that provides flashblocks + #[arg( + long = "flashblocks.addr", + env = "FLASHBLOCKS_WS_ADDR", + default_value = "127.0.0.1" + )] + pub flashblocks_addr: String, + + /// flashblock block time in milliseconds + #[arg( + long = "flashblock.block-time", + default_value = "250", + env = "FLASHBLOCK_BLOCK_TIME" + )] + pub flashblocks_block_time: u64, +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index 18c9ea6d..1c2af41b 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -30,13 +30,12 @@ impl TryFrom for FlashblocksConfig { type Error = eyre::Report; fn try_from(args: OpRbuilderArgs) -> Result { - let ws_addr = args - .flashblocks_ws_url - .parse() - .map_err(|_| eyre::eyre!("Invalid flashblocks websocket address"))?; - - let interval = Duration::from_millis(args.flashblock_block_time); + let interval = Duration::from_millis(args.flashblocks.flashblocks_block_time); + let ws_addr = SocketAddr::new( + args.flashblocks.flashblocks_addr.parse()?, + args.flashblocks.flashblocks_port, + ); Ok(Self { ws_addr, interval }) } } diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs index 63e71cb3..6b972eb5 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs @@ -12,7 +12,7 @@ use crate::tests::TestHarnessBuilder; #[ignore = "Flashblocks tests need more work"] async fn chain_produces_blocks() -> eyre::Result<()> { let harness = TestHarnessBuilder::new("flashbots_chain_produces_blocks") - .with_flashblocks_ws_url("ws://localhost:1239") + .with_flashblocks_port(1239) .with_chain_block_time(2000) .with_flashbots_block_time(200) .build() diff --git a/crates/builder/op-rbuilder/src/tests/framework/harness.rs b/crates/builder/op-rbuilder/src/tests/framework/harness.rs index f55da367..30fb084e 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/harness.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/harness.rs @@ -22,7 +22,7 @@ use uuid::Uuid; pub struct TestHarnessBuilder { name: String, use_revert_protection: bool, - flashblocks_ws_url: Option, + flashblocks_port: Option, chain_block_time: Option, flashbots_block_time: Option, namespaces: Option, @@ -34,7 +34,7 @@ impl TestHarnessBuilder { Self { name: name.to_string(), use_revert_protection: false, - flashblocks_ws_url: None, + flashblocks_port: None, chain_block_time: None, flashbots_block_time: None, namespaces: None, @@ -47,8 +47,8 @@ impl TestHarnessBuilder { self } - pub fn with_flashblocks_ws_url(mut self, url: &str) -> Self { - self.flashblocks_ws_url = Some(url.to_string()); + pub fn with_flashblocks_port(mut self, port: u16) -> Self { + self.flashblocks_port = Some(port); self } @@ -96,8 +96,8 @@ impl TestHarnessBuilder { .with_revert_protection(self.use_revert_protection) .with_namespaces(self.namespaces) .with_extra_params(self.extra_params); - if let Some(flashblocks_ws_url) = self.flashblocks_ws_url { - op_rbuilder_config = op_rbuilder_config.with_flashblocks_ws_url(&flashblocks_ws_url); + if let Some(flashblocks_port) = self.flashblocks_port { + op_rbuilder_config = op_rbuilder_config.with_flashblocks_port(flashblocks_port); } if let Some(chain_block_time) = self.chain_block_time { diff --git a/crates/builder/op-rbuilder/src/tests/framework/op.rs b/crates/builder/op-rbuilder/src/tests/framework/op.rs index 3dea0513..30aae7a8 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/op.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/op.rs @@ -23,7 +23,7 @@ pub struct OpRbuilderConfig { http_port: Option, network_port: Option, builder_private_key: Option, - flashblocks_ws_url: Option, + flashblocks_port: Option, chain_block_time: Option, flashbots_block_time: Option, with_revert_protection: Option, @@ -71,8 +71,8 @@ impl OpRbuilderConfig { self } - pub fn with_flashblocks_ws_url(mut self, url: &str) -> Self { - self.flashblocks_ws_url = Some(url.to_string()); + pub fn with_flashblocks_port(mut self, port: u16) -> Self { + self.flashblocks_port = Some(port); self } @@ -152,11 +152,11 @@ impl Service for OpRbuilderConfig { .arg(http_port.to_string()); } - if let Some(flashblocks_ws_url) = &self.flashblocks_ws_url { - cmd.arg("--rollup.enable-flashblocks").arg("true"); - - cmd.arg("--rollup.flashblocks-ws-url") - .arg(flashblocks_ws_url); + if let Some(flashblocks_port) = &self.flashblocks_port { + cmd.arg("--flashblocks.enabled").arg("true"); + cmd.arg("--flashblocks.addr").arg("127.0.0.1"); + cmd.arg("--flashblocks.port") + .arg(flashblocks_port.to_string()); } if let Some(chain_block_time) = self.chain_block_time { From bf5d9e1f080032226ed49e3b48f3847c1713614a Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 27 May 2025 16:09:24 +0200 Subject: [PATCH 102/262] Migrate e2e tests to Isthmus (#45) * Use isthmus * Update to payload v3 --- crates/builder/op-rbuilder/Cargo.toml | 5 +- .../op-rbuilder/src/tests/framework/apis.rs | 22 +++-- .../framework/artifacts/genesis.json.tmpl | 2 + .../op-rbuilder/src/tests/framework/blocks.rs | 92 ++++++++++++------- 4 files changed, 76 insertions(+), 45 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 994207c4..dbefc829 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -44,6 +44,7 @@ reth-testing-utils.workspace = true reth-optimism-forks.workspace = true reth-node-builder.workspace = true reth-rpc-eth-types.workspace = true +reth-optimism-rpc.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true @@ -112,7 +113,9 @@ vergen = { workspace = true, features = ["build", "cargo", "emit_and_set"] } vergen-git2.workspace = true [dev-dependencies] -alloy-provider = {workspace = true, default-features = true, features = ["txpool-api"]} +alloy-provider = { workspace = true, default-features = true, features = [ + "txpool-api", +] } [features] default = ["jemalloc"] diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs index cdcba088..edc9eef3 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -1,14 +1,16 @@ use super::DEFAULT_JWT_TOKEN; -use alloy_eips::BlockNumberOrTag; +use alloy_eips::{eip7685::Requests, BlockNumberOrTag}; use alloy_primitives::B256; -use alloy_rpc_types_engine::{ExecutionPayloadV3, ForkchoiceUpdated, PayloadStatus}; +use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadStatus}; use jsonrpsee::{ core::{client::SubscriptionClientT, RpcResult}, proc_macros::rpc, }; -use reth::rpc::{api::EngineApiClient, types::engine::ForkchoiceState}; +use op_alloy_rpc_types_engine::OpExecutionPayloadV4; +use reth::rpc::types::engine::ForkchoiceState; use reth_node_api::{EngineTypes, PayloadTypes}; use reth_optimism_node::OpEngineTypes; +use reth_optimism_rpc::engine::OpEngineApiClient; use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, JwtSecret}; use serde_json::Value; @@ -78,10 +80,10 @@ impl EngineApi { .expect("Failed to create http client") } - pub async fn get_payload_v3( + pub async fn get_payload( &self, payload_id: PayloadId, - ) -> eyre::Result<::ExecutionPayloadEnvelopeV3> { + ) -> eyre::Result<::ExecutionPayloadEnvelopeV4> { println!( "Fetching payload with id: {} at {}", payload_id, @@ -89,24 +91,26 @@ impl EngineApi { ); Ok( - EngineApiClient::::get_payload_v3(&self.http_client(), payload_id) + OpEngineApiClient::::get_payload_v4(&self.http_client(), payload_id) .await?, ) } pub async fn new_payload( &self, - payload: ExecutionPayloadV3, + payload: OpExecutionPayloadV4, versioned_hashes: Vec, parent_beacon_block_root: B256, + execution_requests: Requests, ) -> eyre::Result { println!("Submitting new payload at {}...", chrono::Utc::now()); - Ok(EngineApiClient::::new_payload_v3( + Ok(OpEngineApiClient::::new_payload_v4( &self.http_client(), payload, versioned_hashes, parent_beacon_block_root, + execution_requests, ) .await?) } @@ -119,7 +123,7 @@ impl EngineApi { ) -> eyre::Result { println!("Updating forkchoice at {}...", chrono::Utc::now()); - Ok(EngineApiClient::::fork_choice_updated_v3( + Ok(OpEngineApiClient::::fork_choice_updated_v3( &self.http_client(), ForkchoiceState { head_block_hash: new_head, diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl b/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl index a1ab0146..dddb61d7 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl @@ -23,6 +23,8 @@ "ecotoneTime": 0, "fjordTime": 0, "graniteTime": 0, + "isthmusTime": 0, + "pragueTime": 0, "terminalTotalDifficulty": 0, "terminalTotalDifficultyPassed": true, "depositContractAddress": "0x0000000000000000000000000000000000000000", diff --git a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs index a9fd7ba1..bc4f9004 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs @@ -1,13 +1,13 @@ use crate::tx_signer::Signer; -use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; +use alloy_eips::{eip2718::Encodable2718, eip7685::Requests, BlockNumberOrTag}; use alloy_primitives::{address, hex, Address, Bytes, TxKind, B256, U256}; use alloy_rpc_types_engine::{ - ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, - PayloadAttributes, PayloadStatusEnum, + ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadAttributes, + PayloadStatusEnum, }; use alloy_rpc_types_eth::Block; use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; -use op_alloy_rpc_types_engine::OpPayloadAttributes; +use op_alloy_rpc_types_engine::{OpExecutionPayloadV4, OpPayloadAttributes}; use rollup_boost::{Flashblocks, FlashblocksService, OpExecutionPayloadEnvelope, Version}; use super::apis::EngineApi; @@ -104,32 +104,37 @@ impl BlockGenerator { return Err(eyre::eyre!("unexpected parent hash during sync")); } - let payload_request = ExecutionPayloadV3 { - payload_inner: ExecutionPayloadV2 { - payload_inner: ExecutionPayloadV1 { - parent_hash: block.header.parent_hash, - fee_recipient: block.header.beneficiary, - state_root: block.header.state_root, - receipts_root: block.header.receipts_root, - logs_bloom: block.header.logs_bloom, - prev_randao: B256::ZERO, - block_number: block.header.number, - gas_limit: block.header.gas_limit, - gas_used: block.header.gas_used, - timestamp: block.header.timestamp, - extra_data: block.header.extra_data.clone(), - base_fee_per_gas: U256::from(block.header.base_fee_per_gas.unwrap()), - block_hash: block.header.hash, - transactions: vec![], // there are no txns yet + let payload_request = OpExecutionPayloadV4 { + payload_inner: ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: block.header.parent_hash, + fee_recipient: block.header.beneficiary, + state_root: block.header.state_root, + receipts_root: block.header.receipts_root, + logs_bloom: block.header.logs_bloom, + prev_randao: B256::ZERO, + block_number: block.header.number, + gas_limit: block.header.gas_limit, + gas_used: block.header.gas_used, + timestamp: block.header.timestamp, + extra_data: block.header.extra_data.clone(), + base_fee_per_gas: U256::from( + block.header.base_fee_per_gas.unwrap(), + ), + block_hash: block.header.hash, + transactions: vec![], // there are no txns yet + }, + withdrawals: block.withdrawals.unwrap().to_vec(), }, - withdrawals: block.withdrawals.unwrap().to_vec(), + blob_gas_used: block.header.inner.blob_gas_used.unwrap(), + excess_blob_gas: block.header.inner.excess_blob_gas.unwrap(), }, - blob_gas_used: block.header.inner.blob_gas_used.unwrap(), - excess_blob_gas: block.header.inner.excess_blob_gas.unwrap(), + withdrawals_root: Default::default(), }; let validation_status = validation_api - .new_payload(payload_request, vec![], B256::ZERO) + .new_payload(payload_request, vec![], B256::ZERO, Requests::default()) .await?; if validation_status.status != PayloadStatusEnum::Valid { @@ -237,23 +242,31 @@ impl BlockGenerator { let payload = if let Some(flashblocks_service) = &self.flashblocks_service { flashblocks_service - .get_best_payload(Version::V3) + .get_best_payload(Version::V4) .await? .unwrap() } else { - OpExecutionPayloadEnvelope::V3(self.engine_api.get_payload_v3(payload_id).await?) + OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?) }; - let execution_payload = if let ExecutionPayload::V3(execution_payload) = payload.into() { - execution_payload - } else { - return Err(eyre::eyre!("execution_payload should be V3")); + let execution_payload = match payload { + OpExecutionPayloadEnvelope::V4(execution_payload) => { + execution_payload.execution_payload + } + _ => { + return Err(eyre::eyre!("execution_payload should be V4")); + } }; // Validate with builder node let validation_status = self .engine_api - .new_payload(execution_payload.clone(), vec![], B256::ZERO) + .new_payload( + execution_payload.clone(), + vec![], + B256::ZERO, + Requests::default(), + ) .await?; if validation_status.status != PayloadStatusEnum::Valid { @@ -263,7 +276,12 @@ impl BlockGenerator { // Validate with validation node if present if let Some(validation_api) = &self.validation_api { let validation_status = validation_api - .new_payload(execution_payload.clone(), vec![], B256::ZERO) + .new_payload( + execution_payload.clone(), + vec![], + B256::ZERO, + Requests::default(), + ) .await?; if validation_status.status != PayloadStatusEnum::Valid { @@ -271,7 +289,11 @@ impl BlockGenerator { } } - let new_block_hash = execution_payload.payload_inner.payload_inner.block_hash; + let new_block_hash = execution_payload + .payload_inner + .payload_inner + .payload_inner + .block_hash; // Update forkchoice on builder self.engine_api @@ -287,7 +309,7 @@ impl BlockGenerator { // Update internal state self.latest_hash = new_block_hash; - self.timestamp = execution_payload.timestamp(); + self.timestamp = execution_payload.payload_inner.timestamp(); let block = self .engine_api From d43eaf78c12639f4bd29d8a12c4a2992df6b6f43 Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 28 May 2025 17:06:26 +1000 Subject: [PATCH 103/262] Add total_block_built_duration metric back to vanilla builder (#77) --- .../op-rbuilder/src/builders/standard/payload.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index fc6e93d0..96c280a3 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -203,6 +203,8 @@ where 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, @@ -254,6 +256,7 @@ where let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; let state = StateProviderDatabase::new(state_provider); + let metrics = ctx.metrics.clone(); if ctx.attributes().no_tx_pool { let db = State::builder() @@ -269,7 +272,13 @@ where .build(); builder.build(db, ctx) } - .map(|out| out.with_cached_reads(cached_reads)) + .map(|out| { + metrics + .total_block_built_duration + .record(block_build_start_time.elapsed()); + + out.with_cached_reads(cached_reads) + }) } } From 628b8f4f32f1901e0ab19a8da092e030160354e4 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 29 May 2025 08:04:13 +0200 Subject: [PATCH 104/262] Feat/revert protection status endpoint (#76) * Add revert protection status endpoint * Add more changes * Change type * Fix compile error * More * Add test * Clean * Change comment * Fix lint * Remove print statement * Change to moka * Fix --------- Co-authored-by: Solar Mithril --- crates/builder/op-rbuilder/Cargo.toml | 3 +- crates/builder/op-rbuilder/src/main.rs | 24 ++++- .../op-rbuilder/src/monitor_tx_pool.rs | 18 +++- .../op-rbuilder/src/revert_protection.rs | 89 +++++++++++++++++-- .../op-rbuilder/src/tests/vanilla/revert.rs | 41 ++++++++- 5 files changed, 160 insertions(+), 15 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index dbefc829..0e66e6d2 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -58,6 +58,7 @@ alloy-transport.workspace = true alloy-network.workspace = true alloy-provider.workspace = true alloy-serde.workspace = true +alloy-json-rpc.workspace = true # op alloy-op-evm.workspace = true @@ -100,7 +101,7 @@ rand = "0.9.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } shellexpand = "3.1" serde_yaml = { version = "0.9" } - +moka = "0.12" # `msozin/flashblocks-v1.4.1` branch based on `flashblocks-rebase` rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "8506dfb7d84c65746f7c88d250983658438f59e8" } diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index f378f961..f150bd88 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -22,8 +22,9 @@ use metrics::{ VersionInfo, BUILD_PROFILE_NAME, CARGO_PKG_VERSION, VERGEN_BUILD_TIMESTAMP, VERGEN_CARGO_FEATURES, VERGEN_CARGO_TARGET_TRIPLE, VERGEN_GIT_SHA, }; +use moka::future::Cache; use monitor_tx_pool::monitor_tx_pool; -use revert_protection::{EthApiOverrideServer, RevertProtectionExt}; +use revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}; use tx::FBPooledTransaction; // Prefer jemalloc for performance reasons. @@ -71,6 +72,9 @@ where let da_config = builder_config.da_config.clone(); let rollup_args = builder_args.rollup_args; let op_node = OpNode::new(rollup_args.clone()); + let reverted_cache = Cache::builder().max_capacity(100).build(); + let reverted_cache_copy = reverted_cache.clone(); + let handle = builder .with_types::() .with_components( @@ -104,10 +108,18 @@ where let pool = ctx.pool().clone(); let provider = ctx.provider().clone(); - let revert_protection_ext = RevertProtectionExt::new(pool, provider); + let revert_protection_ext: RevertProtectionExt< + _, + _, + _, + op_alloy_network::Optimism, + > = RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); ctx.modules - .merge_configured(revert_protection_ext.into_rpc())?; + .merge_configured(revert_protection_ext.bundle_api().into_rpc())?; + ctx.modules.replace_configured( + revert_protection_ext.eth_api(reverted_cache).into_rpc(), + )?; } Ok(()) @@ -119,7 +131,11 @@ where ctx.task_executor.spawn_critical( "txlogging", Box::pin(async move { - monitor_tx_pool(ctx.pool.all_transactions_event_listener()).await; + monitor_tx_pool( + ctx.pool.all_transactions_event_listener(), + reverted_cache_copy, + ) + .await; }), ); } diff --git a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs index 5bb3a4ea..4554c5e6 100644 --- a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs +++ b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs @@ -1,15 +1,23 @@ use crate::tx::FBPooledTransaction; +use alloy_primitives::B256; use futures_util::StreamExt; +use moka::future::Cache; use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent}; use tracing::info; -pub async fn monitor_tx_pool(mut new_transactions: AllTransactionsEvents) { +pub async fn monitor_tx_pool( + mut new_transactions: AllTransactionsEvents, + reverted_cache: Cache, +) { while let Some(event) = new_transactions.next().await { - transaction_event_log(event); + transaction_event_log(event, &reverted_cache).await; } } -fn transaction_event_log(event: FullTransactionEvent) { +async fn transaction_event_log( + event: FullTransactionEvent, + reverted_cache: &Cache, +) { match event { FullTransactionEvent::Pending(hash) => { info!( @@ -48,6 +56,10 @@ fn transaction_event_log(event: FullTransactionEvent) { "Transaction event received" ), FullTransactionEvent::Discarded(hash) => { + // add the transaction hash to the reverted cache to notify the + // eth get transaction receipt method + reverted_cache.insert(hash, ()).await; + info!( target = "monitoring", tx_hash = hash.to_string(), diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 17ab7368..7b145b45 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -2,36 +2,82 @@ use crate::{ primitives::bundle::{Bundle, BundleResult, MAX_BLOCK_RANGE_BLOCKS}, tx::{FBPooledTransaction, MaybeRevertingTransaction}, }; +use alloy_json_rpc::RpcObject; +use alloy_primitives::B256; use jsonrpsee::{ core::{async_trait, RpcResult}, proc_macros::rpc, }; +use moka::future::Cache; +use reth::rpc::api::eth::{helpers::FullEthApi, RpcReceipt}; use reth_optimism_txpool::{conditional::MaybeConditionalTransaction, OpPooledTransaction}; use reth_provider::StateProviderFactory; use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool}; +// We have to split the RPC modules in two sets because we have methods that both +// replace an existing method and add a new one. +// Tracking change in Reth here to have a single method for both: +// https://github.com/paradigmxyz/reth/issues/16502 + // Namespace overrides for revert protection support #[cfg_attr(not(test), rpc(server, namespace = "eth"))] #[cfg_attr(test, rpc(server, client, namespace = "eth"))] -pub trait EthApiOverride { +pub trait EthApiExt { #[method(name = "sendBundle")] async fn send_bundle(&self, tx: Bundle) -> RpcResult; } -pub struct RevertProtectionExt { +#[rpc(server, client, namespace = "eth")] +pub trait EthApiOverride { + #[method(name = "getTransactionReceipt")] + async fn transaction_receipt(&self, hash: B256) -> RpcResult>; +} + +pub struct RevertProtectionExt { pool: Pool, provider: Provider, + eth_api: Eth, + _network: std::marker::PhantomData, } -impl RevertProtectionExt { - pub fn new(pool: Pool, provider: Provider) -> Self { - Self { pool, provider } +impl RevertProtectionExt +where + Pool: Clone, + Provider: Clone, + Eth: Clone, +{ + pub fn new(pool: Pool, provider: Provider, eth_api: Eth) -> Self { + Self { + pool, + provider, + eth_api, + _network: std::marker::PhantomData, + } } + + pub fn bundle_api(&self) -> RevertProtectionBundleAPI { + RevertProtectionBundleAPI { + pool: self.pool.clone(), + provider: self.provider.clone(), + } + } + + pub fn eth_api(&self, reverted_cache: Cache) -> RevertProtectionEthAPI { + RevertProtectionEthAPI { + eth_api: self.eth_api.clone(), + reverted_cache, + } + } +} + +pub struct RevertProtectionBundleAPI { + pool: Pool, + provider: Provider, } #[async_trait] -impl EthApiOverrideServer for RevertProtectionExt +impl EthApiExtServer for RevertProtectionBundleAPI where Pool: TransactionPool + Clone + 'static, Provider: StateProviderFactory + Send + Sync + Clone + 'static, @@ -95,3 +141,34 @@ where Ok(result) } } + +pub struct RevertProtectionEthAPI { + eth_api: Eth, + reverted_cache: Cache, +} + +#[async_trait] +impl EthApiOverrideServer> for RevertProtectionEthAPI +where + Eth: FullEthApi + Send + Sync + Clone + 'static, +{ + async fn transaction_receipt( + &self, + hash: B256, + ) -> RpcResult>> { + match self.eth_api.transaction_receipt(hash).await.unwrap() { + Some(receipt) => Ok(Some(receipt)), + None => { + // Try to find the transaction in the reverted cache + if self.reverted_cache.get(&hash).await.is_some() { + return Err(EthApiError::InvalidParams( + "the transaction was dropped from the pool".into(), + ) + .into()); + } else { + return Ok(None); + } + } + } + } +} diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs index 037ab032..e3dcef12 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -1,4 +1,4 @@ -use alloy_provider::PendingTransactionBuilder; +use alloy_provider::{PendingTransactionBuilder, Provider}; use op_alloy_network::Optimism; use crate::{ @@ -230,3 +230,42 @@ async fn revert_protection_allow_reverted_transactions_without_bundle() -> eyre: Ok(()) } + +/// If a transaction reverts and gets dropped it, the eth_getTransactionReceipt should return +/// an error message that it was dropped. +#[tokio::test] +async fn revert_protection_check_transaction_receipt_status_message() -> eyre::Result<()> { + let harness = + TestHarnessBuilder::new("revert_protection_check_transaction_receipt_status_message") + .with_revert_protection() + .build() + .await?; + + let provider = harness.provider()?; + let mut generator = harness.block_generator().await?; + + let reverting_tx = harness + .create_transaction() + .with_revert() + .with_bundle(BundleOpts { + block_number_max: Some(3), + }) + .send() + .await?; + let tx_hash = reverting_tx.tx_hash(); + + let _ = generator.generate_block().await?; + let receipt = provider.get_transaction_receipt(*tx_hash).await?; + assert!(receipt.is_none()); + + let _ = generator.generate_block().await?; + let receipt = provider.get_transaction_receipt(*tx_hash).await?; + assert!(receipt.is_none()); + + // Dropped + let _ = generator.generate_block().await?; + let receipt = provider.get_transaction_receipt(*tx_hash).await; + assert!(receipt.is_err()); + + Ok(()) +} From f378658237bd2c59e074b1b3b67f75b69228cef9 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Fri, 30 May 2025 15:15:21 +0700 Subject: [PATCH 105/262] Fix da scaling (#81) --- crates/builder/op-rbuilder/src/builders/context.rs | 2 +- .../builder/op-rbuilder/src/tests/vanilla/data_availability.rs | 2 +- crates/builder/op-rbuilder/src/tx.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 7af42b28..dd500de3 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -519,7 +519,7 @@ impl OpPayloadBuilderCtx { // Create and sign the transaction let builder_tx = signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; - Ok(op_alloy_flz::tx_estimated_size_fjord( + Ok(op_alloy_flz::data_gas_fjord( builder_tx.encoded_2718().as_slice(), )) }) diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs b/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs index f18385c7..8d4c656f 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs @@ -79,7 +79,7 @@ async fn data_availability_block_fill() -> eyre::Result<()> { // Set block big enough so it could fit 3 transactions without tx size limit let call = harness .provider()? - .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100000000 * 3)) + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 1600 * 3)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index 023c5123..d954c5c6 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -221,7 +221,7 @@ impl MaybeInteropTransaction for FBPooledTransaction { impl DataAvailabilitySized for FBPooledTransaction { fn estimated_da_size(&self) -> u64 { - self.inner.estimated_da_size() + op_alloy_flz::data_gas_fjord(self.inner.encoded_2718()) } } From 0805d422b120355d9b3c6aee9f8b7065af713443 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 30 May 2025 11:22:22 +0200 Subject: [PATCH 106/262] Add logs for da limits (#86) * Add logs for da limits * Add comments * Fix --- crates/builder/op-rbuilder/src/builders/context.rs | 11 +++++++++++ crates/builder/op-rbuilder/src/metrics.rs | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index dd500de3..add58ced 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -328,6 +328,17 @@ impl OpPayloadBuilderCtx { let tx_da_limit = self.da_config.max_da_tx_size(); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + info!(target: "payload_builder", block_da_limit = ?block_da_limit, tx_da_size = ?tx_da_limit, block_gas_limit = ?block_gas_limit, "DA limits"); + + // Remove once we merge Reth 1.4.4 + // Fixed in https://github.com/paradigmxyz/reth/pull/16514 + self.metrics + .da_block_size_limit + .record(block_da_limit.map_or(-1.0, |v| v as f64)); + self.metrics + .da_tx_size_limit + .record(tx_da_limit.map_or(-1.0, |v| v as f64)); + while let Some(tx) = best_txs.next(()) { let exclude_reverting_txs = tx.exclude_reverting_txs(); let tx_da_size = tx.estimated_da_size(); diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 4b44410b..06d1297f 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -63,6 +63,10 @@ pub struct OpRBuilderMetrics { pub tx_simulation_duration: Histogram, /// Byte size of transactions pub tx_byte_size: Histogram, + /// Da block size limit + pub da_block_size_limit: Histogram, + /// Da tx size limit + pub da_tx_size_limit: Histogram, } /// Contains version information for the application. From bcc5483da986cb970b14d65b8d8df6bc9676f943 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 30 May 2025 12:51:35 +0200 Subject: [PATCH 107/262] Add log for the block building execution (#87) * Add log * Fix lint --- .../op-rbuilder/src/builders/context.rs | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index add58ced..75da34ed 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -5,7 +5,7 @@ use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types_eth::Withdrawals; use core::fmt::Debug; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; -use op_revm::OpSpecId; +use op_revm::{OpSpecId, OpTransactionError}; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::PayloadConfig; use reth_chainspec::{EthChainSpec, EthereumHardforks}; @@ -187,6 +187,33 @@ impl OpPayloadBuilderCtx { } } +#[derive(Debug)] +enum TxnExecutionResult { + InvalidDASize, + SequencerTransaction, + NonceTooLow, + InternalError(OpTransactionError), + EvmError, + Success, + Reverted, + RevertedAndExcluded, +} + +impl std::fmt::Display for TxnExecutionResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TxnExecutionResult::InvalidDASize => write!(f, "InvalidDASize"), + TxnExecutionResult::SequencerTransaction => write!(f, "SequencerTransaction"), + TxnExecutionResult::NonceTooLow => write!(f, "NonceTooLow"), + TxnExecutionResult::InternalError(err) => write!(f, "InternalError({err})"), + TxnExecutionResult::EvmError => write!(f, "EvmError"), + TxnExecutionResult::Success => write!(f, "Success"), + TxnExecutionResult::Reverted => write!(f, "Reverted"), + TxnExecutionResult::RevertedAndExcluded => write!(f, "RevertedAndExcluded"), + } + } +} + impl OpPayloadBuilderCtx { /// Constructs a receipt for the given transaction. fn build_receipt( @@ -342,8 +369,13 @@ impl OpPayloadBuilderCtx { while let Some(tx) = best_txs.next(()) { let exclude_reverting_txs = tx.exclude_reverting_txs(); let tx_da_size = tx.estimated_da_size(); - let tx = tx.into_consensus(); + let tx_hash = tx.tx_hash(); + + let log_txn = |result: TxnExecutionResult| { + info!(target: "payload_builder", tx_hash = ?tx_hash, tx_da_size = ?tx_da_size, exclude_reverting_txs = ?exclude_reverting_txs, result = %result, "Considering transaction"); + }; + num_txs_considered += 1; // ensure we still have capacity for this transaction if info.is_tx_over_limits( @@ -356,12 +388,14 @@ impl OpPayloadBuilderCtx { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from // the iterator before we can continue + log_txn(TxnExecutionResult::InvalidDASize); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } // A sequencer's block should never contain blob or deposit transactions from the pool. if tx.is_eip4844() || tx.is_deposit() { + log_txn(TxnExecutionResult::SequencerTransaction); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } @@ -378,10 +412,12 @@ impl OpPayloadBuilderCtx { if let Some(err) = err.as_invalid_tx_err() { if err.is_nonce_too_low() { // if the nonce is too low, we can skip this transaction + log_txn(TxnExecutionResult::NonceTooLow); trace!(target: "payload_builder", %err, ?tx, "skipping nonce too low transaction"); } else { // if the transaction is invalid, we can skip it and all of its // descendants + log_txn(TxnExecutionResult::InternalError(err.clone())); trace!(target: "payload_builder", %err, ?tx, "skipping invalid transaction and its descendants"); best_txs.mark_invalid(tx.signer(), tx.nonce()); } @@ -389,6 +425,7 @@ impl OpPayloadBuilderCtx { continue; } // this is an error that we should treat as fatal for this attempt + log_txn(TxnExecutionResult::EvmError); return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); } }; @@ -399,13 +436,17 @@ impl OpPayloadBuilderCtx { self.metrics.tx_byte_size.record(tx.inner().size() as f64); num_txs_simulated += 1; if result.is_success() { + log_txn(TxnExecutionResult::Success); num_txs_simulated_success += 1; } else { num_txs_simulated_fail += 1; if exclude_reverting_txs { + log_txn(TxnExecutionResult::RevertedAndExcluded); info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; + } else { + log_txn(TxnExecutionResult::Reverted); } } From 9529c688e102da1f58b9ddbc06dd572675d33c36 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 30 May 2025 13:06:25 +0200 Subject: [PATCH 108/262] Add builder txn to Flashblocks (#89) * Add changes * Remove print --- crates/builder/op-rbuilder/src/args/op.rs | 2 +- .../op-rbuilder/src/builders/context.rs | 27 ++++++++++++++++--- .../src/builders/flashblocks/payload.rs | 15 ++++++++++- .../builder/op-rbuilder/src/builders/mod.rs | 2 +- .../src/builders/standard/payload.rs | 27 ++----------------- .../src/tests/flashblocks/smoke.rs | 5 ++-- .../op-rbuilder/src/tests/framework/blocks.rs | 4 +++ .../src/tests/framework/harness.rs | 10 ++++++- .../op-rbuilder/src/tests/framework/op.rs | 4 +-- 9 files changed, 60 insertions(+), 36 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 136e58ad..d4b92e58 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -95,7 +95,7 @@ pub struct FlashblocksArgs { /// flashblock block time in milliseconds #[arg( - long = "flashblock.block-time", + long = "flashblocks.block-time", default_value = "250", env = "FLASHBLOCK_BLOCK_TIME" )] diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 75da34ed..24c8026a 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -1,5 +1,5 @@ use alloy_consensus::{Eip658Value, Transaction, TxEip1559}; -use alloy_eips::{Encodable2718, Typed2718}; +use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718, Typed2718}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types_eth::Withdrawals; @@ -500,9 +500,9 @@ impl OpPayloadBuilderCtx { Ok(None) } - pub fn add_builder_tx( + pub fn add_builder_tx( &self, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, db: &mut State, builder_tx_gas: u64, message: Vec, @@ -583,6 +583,27 @@ impl OpPayloadBuilderCtx { } } +pub fn estimate_gas_for_builder_tx(input: Vec) -> 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; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) +} + /// Creates signed builder tx to Address::ZERO and specified message as input pub fn signed_builder_tx( db: &mut State, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 128c9a68..8c37bbb3 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -4,7 +4,7 @@ use std::{sync::Arc, time::Instant}; use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; use crate::{ builders::{ - context::OpPayloadBuilderCtx, + context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}, flashblocks::config::FlashBlocksConfigExt, generator::{BlockCell, BuildArguments}, BuilderConfig, @@ -257,6 +257,13 @@ where } }); + let message = format!("Block Number: {}", ctx.block_number()) + .as_bytes() + .to_vec(); + let builder_tx_gas = ctx + .builder_signer() + .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); + // Process flashblocks in a blocking loop loop { // Block on receiving a message, break on cancellation or closed channel @@ -340,6 +347,12 @@ where return Ok(()); } + // If it is the last flashblocks, add the builder txn to the block if enabled + if flashblock_count == self.config.flashblocks_per_block() - 1 { + // TODO: Account for DA size limits + ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); + } + let total_block_built_duration = Instant::now(); let build_result = build_block(db, &ctx, &mut info); ctx.metrics diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 9cbc4a0f..66ad7436 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -147,7 +147,7 @@ where builder_signer: args.builder_signer, revert_protection: args.enable_revert_protection, block_time: Duration::from_millis(args.chain_block_time), - block_time_leeway: Duration::from_millis(500), + block_time_leeway: Duration::from_secs(args.extra_block_deadline_secs), da_config: Default::default(), specific: S::try_from(args)?, }) diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 96c280a3..35e16ff8 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -1,9 +1,7 @@ use alloy_consensus::{ constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, EMPTY_OMMER_ROOT_HASH, }; -use alloy_eips::{ - eip7623::TOTAL_COST_FLOOR_PER_TOKEN, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE, -}; +use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_primitives::U256; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; @@ -38,7 +36,7 @@ use crate::{ traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, }; -use super::super::context::OpPayloadBuilderCtx; +use super::super::context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}; pub struct StandardPayloadBuilderBuilder(pub BuilderConfig<()>); @@ -566,24 +564,3 @@ impl OpBuilder<'_, Txs> { } } } - -fn estimate_gas_for_builder_tx(input: Vec) -> 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; - - std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) -} diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs index 6b972eb5..c500c40c 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs @@ -9,7 +9,6 @@ use tokio_util::sync::CancellationToken; use crate::tests::TestHarnessBuilder; #[tokio::test] -#[ignore = "Flashblocks tests need more work"] async fn chain_produces_blocks() -> eyre::Result<()> { let harness = TestHarnessBuilder::new("flashbots_chain_produces_blocks") .with_flashblocks_port(1239) @@ -49,7 +48,9 @@ async fn chain_produces_blocks() -> eyre::Result<()> { let _ = harness.send_valid_transaction().await?; } - generator.generate_block().await?; + let generated_block = generator.generate_block().await?; + assert_eq!(generated_block.num_transactions(), 7); // 5 normal txn + deposit + builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; } diff --git a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs index bc4f9004..2c50f788 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs @@ -402,4 +402,8 @@ impl BlockGenerated { pub fn not_includes_vec(&self, tx_hashes: Vec) -> bool { tx_hashes.iter().all(|hash| self.not_includes(*hash)) } + + pub fn num_transactions(&self) -> usize { + self.block.transactions.len() + } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/harness.rs b/crates/builder/op-rbuilder/src/tests/framework/harness.rs index 30fb084e..cd5ee0ee 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/harness.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/harness.rs @@ -133,6 +133,7 @@ impl TestHarnessBuilder { builder_http_port, validator_auth_rpc_port, builder_log_path, + chain_block_time: self.chain_block_time, }) } } @@ -143,6 +144,7 @@ pub struct TestHarness { builder_http_port: u16, validator_auth_rpc_port: u16, builder_log_path: PathBuf, + chain_block_time: Option, } impl TestHarness { @@ -173,7 +175,13 @@ impl TestHarness { let engine_api = EngineApi::new_with_port(self.builder_auth_rpc_port).unwrap(); let validation_api = Some(EngineApi::new_with_port(self.validator_auth_rpc_port).unwrap()); - let mut generator = BlockGenerator::new(engine_api, validation_api, false, 1, None); + let mut generator = BlockGenerator::new( + engine_api, + validation_api, + false, + self.chain_block_time.map_or(1, |time| time / 1000), // in seconds + None, + ); generator.init().await?; Ok(generator) diff --git a/crates/builder/op-rbuilder/src/tests/framework/op.rs b/crates/builder/op-rbuilder/src/tests/framework/op.rs index 30aae7a8..f007b33d 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/op.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/op.rs @@ -153,7 +153,7 @@ impl Service for OpRbuilderConfig { } if let Some(flashblocks_port) = &self.flashblocks_port { - cmd.arg("--flashblocks.enabled").arg("true"); + cmd.arg("--flashblocks.enabled"); cmd.arg("--flashblocks.addr").arg("127.0.0.1"); cmd.arg("--flashblocks.port") .arg(flashblocks_port.to_string()); @@ -165,7 +165,7 @@ impl Service for OpRbuilderConfig { } if let Some(flashbots_block_time) = self.flashbots_block_time { - cmd.arg("--rollup.flashblock-block-time") + cmd.arg("--flashblocks.block-time") .arg(flashbots_block_time.to_string()); } From ab3cc8a2913f95cefbcf74648c86e71ecba21387 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 2 Jun 2025 19:13:00 +0700 Subject: [PATCH 109/262] Use original function with manual scaling (#96) * Use original function with manual scaling * Added comment --- crates/builder/op-rbuilder/src/builders/context.rs | 9 ++++++--- .../op-rbuilder/src/tests/vanilla/data_availability.rs | 2 +- crates/builder/op-rbuilder/src/tx.rs | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 24c8026a..97aaaead 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -571,9 +571,12 @@ impl OpPayloadBuilderCtx { // Create and sign the transaction let builder_tx = signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; - Ok(op_alloy_flz::data_gas_fjord( - builder_tx.encoded_2718().as_slice(), - )) + Ok( + op_alloy_flz::tx_estimated_size_fjord(builder_tx.encoded_2718().as_slice()) + // Downscaled by 1e6 to be compliant with op-geth estimate size function + // https://github.com/ethereum-optimism/op-geth/blob/optimism/core/types/rollup_cost.go#L563 + .wrapping_div(1_000_000), + ) }) .transpose() .unwrap_or_else(|err: PayloadBuilderError| { diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs b/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs index 8d4c656f..cf640bab 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs @@ -79,7 +79,7 @@ async fn data_availability_block_fill() -> eyre::Result<()> { // Set block big enough so it could fit 3 transactions without tx size limit let call = harness .provider()? - .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 1600 * 3)) + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 3)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index d954c5c6..3de69bc5 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -221,7 +221,9 @@ impl MaybeInteropTransaction for FBPooledTransaction { impl DataAvailabilitySized for FBPooledTransaction { fn estimated_da_size(&self) -> u64 { - op_alloy_flz::data_gas_fjord(self.inner.encoded_2718()) + // Downscaled by 1e6 to be compliant with op-geth estimate size function + // https://github.com/ethereum-optimism/op-geth/blob/optimism/core/types/rollup_cost.go#L563 + op_alloy_flz::tx_estimated_size_fjord(self.inner.encoded_2718()).wrapping_div(1_000_000) } } From 7a24735eac7a763cb5d5640b244dde264d88d6d8 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 2 Jun 2025 20:45:32 +0700 Subject: [PATCH 110/262] Add error log in case builder tx da size sets max_da_block_size to 0 (#97) --- .../src/builders/standard/payload.rs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 35e16ff8..bcefd658 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -1,3 +1,9 @@ +use crate::{ + builders::{generator::BuildArguments, BuilderConfig}, + metrics::OpRBuilderMetrics, + primitives::reth::ExecutionInfo, + traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, +}; use alloy_consensus::{ constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, EMPTY_OMMER_ROOT_HASH, }; @@ -27,14 +33,7 @@ use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, Transac use revm::Database; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; -use tracing::{info, warn}; - -use crate::{ - builders::{generator::BuildArguments, BuilderConfig}, - metrics::OpRBuilderMetrics, - primitives::reth::ExecutionInfo, - traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, -}; +use tracing::{error, info, warn}; use super::super::context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}; @@ -362,7 +361,13 @@ impl OpBuilder<'_, Txs> { let block_da_limit = ctx .da_config .max_da_block_size() - .map(|da_size| da_size.saturating_sub(builder_tx_da_size)); + .map(|da_size| { + let da_size = da_size.saturating_sub(builder_tx_da_size); + if da_size == 0 { + error!("Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included."); + } + da_size + }); if !ctx.attributes().no_tx_pool { let best_txs_start_time = Instant::now(); From 5f2eca5bdde7e9ee790d4413c3dde14a6803f6cc Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 2 Jun 2025 23:02:15 +0700 Subject: [PATCH 111/262] Add replacement for default reth version (#98) * Add replacement for default reth version Move VERSION to different crate * fmt --- crates/builder/op-rbuilder/src/args/mod.rs | 24 +++++++++++++++++++--- crates/builder/op-rbuilder/src/main.rs | 14 +------------ crates/builder/op-rbuilder/src/metrics.rs | 9 ++++++++ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index 806a49da..20f716f9 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -1,5 +1,8 @@ -use crate::builders::BuilderMode; -use clap::Parser; +use crate::{ + builders::BuilderMode, + metrics::{CARGO_PKG_VERSION, VERGEN_GIT_SHA}, +}; +use clap_builder::{CommandFactory, FromArgMatches}; pub use op::OpRbuilderArgs; use playground::PlaygroundOptions; use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; @@ -22,6 +25,10 @@ pub trait CliExt { /// Returns the Cli instance with the parsed command line arguments /// and defaults populated if applicable. fn parsed() -> Self; + + /// Returns the Cli instance with the parsed command line arguments + /// and replaces version, name, author, and about + fn set_version() -> Self; } pub type Cli = reth_optimism_cli::Cli; @@ -58,7 +65,7 @@ impl CliExt for Cli { } fn parsed() -> Self { - Cli::parse().populate_defaults() + Cli::set_version().populate_defaults() } /// Returns the type of builder implementation that the node is started with. @@ -71,6 +78,17 @@ impl CliExt for Cli { } BuilderMode::Standard } + + /// Parses commands and overrides versions + fn set_version() -> Self { + let matches = Cli::command() + .version(format!("{CARGO_PKG_VERSION} ({VERGEN_GIT_SHA})")) + .about("Block builder designed for the Optimism stack") + .author("Flashbots") + .name("op-rbuilder") + .get_matches(); + Cli::from_arg_matches(&matches).expect("Parsing args") + } } /// Following clap's convention, a failure to parse the command line arguments diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index f150bd88..969c0871 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -18,10 +18,7 @@ mod traits; mod tx; mod tx_signer; -use metrics::{ - VersionInfo, BUILD_PROFILE_NAME, CARGO_PKG_VERSION, VERGEN_BUILD_TIMESTAMP, - VERGEN_CARGO_FEATURES, VERGEN_CARGO_TARGET_TRIPLE, VERGEN_GIT_SHA, -}; +use metrics::VERSION; use moka::future::Cache; use monitor_tx_pool::monitor_tx_pool; use revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}; @@ -32,15 +29,6 @@ use tx::FBPooledTransaction; #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -const VERSION: VersionInfo = VersionInfo { - version: CARGO_PKG_VERSION, - build_timestamp: VERGEN_BUILD_TIMESTAMP, - cargo_features: VERGEN_CARGO_FEATURES, - git_sha: VERGEN_GIT_SHA, - target_triple: VERGEN_CARGO_TARGET_TRIPLE, - build_profile: BUILD_PROFILE_NAME, -}; - fn main() { let cli = Cli::parsed(); cli.logs diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 06d1297f..a0eb06f6 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -21,6 +21,15 @@ pub const VERGEN_CARGO_FEATURES: &str = env!("VERGEN_CARGO_FEATURES"); /// The build profile name. pub const BUILD_PROFILE_NAME: &str = env!("OP_RBUILDER_BUILD_PROFILE"); +pub const VERSION: VersionInfo = VersionInfo { + version: CARGO_PKG_VERSION, + build_timestamp: VERGEN_BUILD_TIMESTAMP, + cargo_features: VERGEN_CARGO_FEATURES, + git_sha: VERGEN_GIT_SHA, + target_triple: VERGEN_CARGO_TARGET_TRIPLE, + build_profile: BUILD_PROFILE_NAME, +}; + /// op-rbuilder metrics #[derive(Metrics, Clone)] #[metrics(scope = "op_rbuilder")] From 29fbc589cd59973e0e7dabcd38a4184d8b9c7e61 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 3 Jun 2025 13:01:35 +0700 Subject: [PATCH 112/262] Added feature-gated interop (#93) Use derive_more for enum --- crates/builder/op-rbuilder/Cargo.toml | 2 + .../op-rbuilder/src/builders/context.rs | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 0e66e6d2..f7be4858 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -141,6 +141,8 @@ min-trace-logs = ["tracing/release_max_level_trace"] testing = [] +interop = [] + [[bin]] name = "op-rbuilder" path = "src/main.rs" diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 97aaaead..62daea70 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -4,6 +4,7 @@ use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types_eth::Withdrawals; use core::fmt::Debug; +use derive_more::Display; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; use op_revm::{OpSpecId, OpTransactionError}; use reth::payload::PayloadBuilderAttributes; @@ -19,7 +20,10 @@ use reth_optimism_forks::OpHardforks; use reth_optimism_node::OpPayloadBuilderAttributes; use reth_optimism_payload_builder::{config::OpDAConfig, error::OpPayloadBuilderError}; use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; -use reth_optimism_txpool::estimated_da_size::DataAvailabilitySized; +use reth_optimism_txpool::{ + estimated_da_size::DataAvailabilitySized, + interop::{is_valid_interop, MaybeInteropTransaction}, +}; use reth_payload_builder::PayloadId; use reth_primitives::{Recovered, SealedHeader}; use reth_primitives_traits::{InMemorySize, SignedTransaction}; @@ -187,11 +191,13 @@ impl OpPayloadBuilderCtx { } } -#[derive(Debug)] +#[derive(Debug, Display)] enum TxnExecutionResult { InvalidDASize, SequencerTransaction, NonceTooLow, + InteropFailed, + #[display("InternalError({_0})")] InternalError(OpTransactionError), EvmError, Success, @@ -199,21 +205,6 @@ enum TxnExecutionResult { RevertedAndExcluded, } -impl std::fmt::Display for TxnExecutionResult { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TxnExecutionResult::InvalidDASize => write!(f, "InvalidDASize"), - TxnExecutionResult::SequencerTransaction => write!(f, "SequencerTransaction"), - TxnExecutionResult::NonceTooLow => write!(f, "NonceTooLow"), - TxnExecutionResult::InternalError(err) => write!(f, "InternalError({err})"), - TxnExecutionResult::EvmError => write!(f, "EvmError"), - TxnExecutionResult::Success => write!(f, "Success"), - TxnExecutionResult::Reverted => write!(f, "Reverted"), - TxnExecutionResult::RevertedAndExcluded => write!(f, "RevertedAndExcluded"), - } - } -} - impl OpPayloadBuilderCtx { /// Constructs a receipt for the given transaction. fn build_receipt( @@ -367,6 +358,7 @@ impl OpPayloadBuilderCtx { .record(tx_da_limit.map_or(-1.0, |v| v as f64)); while let Some(tx) = best_txs.next(()) { + let interop = tx.interop_deadline(); let exclude_reverting_txs = tx.exclude_reverting_txs(); let tx_da_size = tx.estimated_da_size(); let tx = tx.into_consensus(); @@ -376,6 +368,19 @@ impl OpPayloadBuilderCtx { info!(target: "payload_builder", tx_hash = ?tx_hash, tx_da_size = ?tx_da_size, exclude_reverting_txs = ?exclude_reverting_txs, result = %result, "Considering transaction"); }; + // TODO: remove this condition and feature once we are comfortable enabling interop for everything + if cfg!(feature = "interop") { + // We skip invalid cross chain txs, they would be removed on the next block update in + // the maintenance job + if let Some(interop) = interop { + if !is_valid_interop(interop, self.config.attributes.timestamp()) { + log_txn(TxnExecutionResult::InteropFailed); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + } + } + num_txs_considered += 1; // ensure we still have capacity for this transaction if info.is_tx_over_limits( From 1354add23a953b9ec3dcd5908f7fa2f84bd2b4ab Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 3 Jun 2025 18:12:04 +0700 Subject: [PATCH 113/262] Use Gauge for da size limits (#105) --- crates/builder/op-rbuilder/src/builders/context.rs | 4 ++-- crates/builder/op-rbuilder/src/metrics.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 62daea70..944daea6 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -352,10 +352,10 @@ impl OpPayloadBuilderCtx { // Fixed in https://github.com/paradigmxyz/reth/pull/16514 self.metrics .da_block_size_limit - .record(block_da_limit.map_or(-1.0, |v| v as f64)); + .set(block_da_limit.map_or(-1.0, |v| v as f64)); self.metrics .da_tx_size_limit - .record(tx_da_limit.map_or(-1.0, |v| v as f64)); + .set(tx_da_limit.map_or(-1.0, |v| v as f64)); while let Some(tx) = best_txs.next(()) { let interop = tx.interop_deadline(); diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index a0eb06f6..f39787cd 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -1,5 +1,5 @@ use reth_metrics::{ - metrics::{gauge, Counter, Histogram}, + metrics::{gauge, Counter, Gauge, Histogram}, Metrics, }; @@ -73,9 +73,9 @@ pub struct OpRBuilderMetrics { /// Byte size of transactions pub tx_byte_size: Histogram, /// Da block size limit - pub da_block_size_limit: Histogram, + pub da_block_size_limit: Gauge, /// Da tx size limit - pub da_tx_size_limit: Histogram, + pub da_tx_size_limit: Gauge, } /// Contains version information for the application. From b461fa9563025afda09aad39f601d18095187e23 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 3 Jun 2025 22:02:58 +0700 Subject: [PATCH 114/262] Account for DA and gas limit in flashblocks (#104) * Account for DA and gas limit in flashblocks * Use fancy staff * thx karim * Use into_bytes --- .../op-rbuilder/src/builders/context.rs | 2 + .../src/builders/flashblocks/payload.rs | 49 ++++++++++++++----- .../src/builders/standard/payload.rs | 8 +-- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 944daea6..06be9d58 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -560,6 +560,8 @@ impl OpPayloadBuilderCtx { } /// Calculates EIP 2718 builder transaction size + // TODO: this function could be improved, ideally we shouldn't take mut ref to db and maybe + // it's possible to do this without db at all pub fn estimate_builder_tx_da_size( &self, db: &mut State, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 8c37bbb3..29ecd1cd 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -189,6 +189,16 @@ where .with_bundle_update() .build(); + // We subtract gas limit and da limit for builder transaction from the whole limit + // TODO: we could optimise this and subtract this only for the last flashblocks + let message = format!("Block Number: {}", ctx.block_number()).into_bytes(); + let builder_tx_gas = ctx + .builder_signer() + .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); + let builder_tx_da_size = ctx + .estimate_builder_tx_da_size(&mut db, builder_tx_gas, message.clone()) + .unwrap_or(0); + let mut info = execute_pre_steps(&mut db, &ctx)?; ctx.metrics .sequencer_tx_duration @@ -219,15 +229,22 @@ where // return early since we don't need to build a block with transactions from the pool return Ok(()); } - let gas_per_batch = ctx.block_gas_limit() / self.config.flashblocks_per_block(); let mut total_gas_per_batch = gas_per_batch; let da_per_batch = ctx .da_config .max_da_block_size() .map(|da_limit| da_limit / self.config.flashblocks_per_block()); + // Check that builder tx won't affect fb limit too much + if let Some(da_limit) = da_per_batch { + // We error if we can't insert any tx aside from builder tx in flashblock + if da_limit / 2 < builder_tx_da_size { + error!("Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included."); + } + } let mut total_da_per_batch = da_per_batch; + let last_flashblock = self.config.flashblocks_per_block().saturating_sub(1); let mut flashblock_count = 0; // Create a channel to coordinate flashblock building let (build_tx, mut build_rx) = mpsc::channel(1); @@ -257,13 +274,6 @@ where } }); - let message = format!("Block Number: {}", ctx.block_number()) - .as_bytes() - .to_vec(); - let builder_tx_gas = ctx - .builder_signer() - .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); - // Process flashblocks in a blocking loop loop { // Block on receiving a message, break on cancellation or closed channel @@ -310,7 +320,13 @@ where ); let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); - + invoke_on_last_flashblock(flashblock_count, last_flashblock, || { + total_gas_per_batch -= builder_tx_gas; + // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size + if let Some(da_limit) = total_da_per_batch.as_mut() { + *da_limit = da_limit.saturating_sub(builder_tx_da_size); + } + }); let mut db = State::builder() .with_database(state) .with_bundle_update() @@ -348,10 +364,9 @@ where } // If it is the last flashblocks, add the builder txn to the block if enabled - if flashblock_count == self.config.flashblocks_per_block() - 1 { - // TODO: Account for DA size limits + invoke_on_last_flashblock(flashblock_count, last_flashblock, || { ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); - } + }); let total_block_built_duration = Instant::now(); let build_result = build_block(db, &ctx, &mut info); @@ -669,3 +684,13 @@ where new_bundle, )) } + +pub fn invoke_on_last_flashblock( + current_flashblock: u64, + flashblock_limit: u64, + fun: F, +) { + if current_flashblock == flashblock_limit { + fun() + } +} diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index bcefd658..ecf32137 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -361,12 +361,12 @@ impl OpBuilder<'_, Txs> { let block_da_limit = ctx .da_config .max_da_block_size() - .map(|da_size| { - let da_size = da_size.saturating_sub(builder_tx_da_size); - if da_size == 0 { + .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_size + da_limit }); if !ctx.attributes().no_tx_pool { From eecce038c99537eb39ad19c9e105b728579dc7e2 Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 4 Jun 2025 16:53:34 +1000 Subject: [PATCH 115/262] Upgrade to reth 1.4.7 and main rollup-boost branch (#112) --- crates/builder/op-rbuilder/Cargo.toml | 4 +-- .../op-rbuilder/src/builders/context.rs | 7 ++--- .../src/builders/flashblocks/payload.rs | 4 +-- .../src/builders/flashblocks/wspub.rs | 2 +- .../op-rbuilder/src/tests/framework/apis.rs | 7 +++-- .../op-rbuilder/src/tests/framework/blocks.rs | 28 +++++++++++++---- crates/builder/op-rbuilder/src/tx.rs | 31 +++++++++---------- 7 files changed, 47 insertions(+), 36 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index f7be4858..1326c6be 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -102,9 +102,9 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } shellexpand = "3.1" serde_yaml = { version = "0.9" } moka = "0.12" +http = "1.0" -# `msozin/flashblocks-v1.4.1` branch based on `flashblocks-rebase` -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "8506dfb7d84c65746f7c88d250983658438f59e8" } +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 06be9d58..fc5d9b44 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -28,12 +28,9 @@ use reth_payload_builder::PayloadId; use reth_primitives::{Recovered, SealedHeader}; use reth_primitives_traits::{InMemorySize, SignedTransaction}; use reth_provider::ProviderError; -use reth_revm::State; +use reth_revm::{context::Block, State}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; -use revm::{ - context::{result::ResultAndState, Block}, - Database, DatabaseCommit, -}; +use revm::{context::result::ResultAndState, Database, DatabaseCommit}; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{info, trace, warn}; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 29ecd1cd..f8a33a7e 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -38,7 +38,7 @@ use reth_revm::{ State, }; use revm::Database; -use rollup_boost::primitives::{ +use rollup_boost::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, }; use serde::{Deserialize, Serialize}; @@ -664,7 +664,7 @@ where block_hash, transactions: new_transactions_encoded, withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), - withdrawals_root, + withdrawals_root: withdrawals_root.unwrap_or_default(), }, metadata: serde_json::to_value(&metadata).unwrap_or_default(), }; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index a31ad3be..71cc053a 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -6,7 +6,7 @@ use core::{ task::{Context, Poll}, }; use futures::{Sink, SinkExt}; -use rollup_boost::primitives::FlashblocksPayloadV1; +use rollup_boost::FlashblocksPayloadV1; use std::{io, net::TcpListener, sync::Arc}; use tokio::{ net::TcpStream, diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs index edc9eef3..19b63e87 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -2,6 +2,7 @@ use super::DEFAULT_JWT_TOKEN; use alloy_eips::{eip7685::Requests, BlockNumberOrTag}; use alloy_primitives::B256; use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadStatus}; +use http::Uri; use jsonrpsee::{ core::{client::SubscriptionClientT, RpcResult}, proc_macros::rpc, @@ -18,8 +19,8 @@ use std::str::FromStr; /// Helper for engine api operations pub struct EngineApi { - url: url::Url, - jwt_secret: JwtSecret, + pub url: Uri, + pub jwt_secret: JwtSecret, } /// Builder for EngineApi configuration @@ -76,7 +77,7 @@ impl EngineApi { let middleware = tower::ServiceBuilder::default().layer(secret_layer); jsonrpsee::http_client::HttpClientBuilder::default() .set_http_middleware(middleware) - .build(&self.url) + .build(&self.url.to_string()) .expect("Failed to create http client") } diff --git a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs index 2c50f788..f6094679 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs @@ -1,3 +1,8 @@ +use std::{ + net::{IpAddr, SocketAddr}, + str::FromStr, +}; + use crate::tx_signer::Signer; use alloy_eips::{eip2718::Encodable2718, eip7685::Requests, BlockNumberOrTag}; use alloy_primitives::{address, hex, Address, Bytes, TxKind, B256, U256}; @@ -8,7 +13,11 @@ use alloy_rpc_types_engine::{ use alloy_rpc_types_eth::Block; use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; use op_alloy_rpc_types_engine::{OpExecutionPayloadV4, OpPayloadAttributes}; -use rollup_boost::{Flashblocks, FlashblocksService, OpExecutionPayloadEnvelope, Version}; +use rollup_boost::{ + Flashblocks, FlashblocksService, OpExecutionPayloadEnvelope, PayloadSource, PayloadVersion, + RpcClient, +}; +use url::Url; use super::apis::EngineApi; @@ -64,10 +73,17 @@ impl BlockGenerator { // Initialize flashblocks service if let Some(flashblocks_endpoint) = &self.flashblocks_endpoint { println!("Initializing flashblocks service at {flashblocks_endpoint}"); + let builder_client = RpcClient::new( + self.engine_api.url.clone(), + self.engine_api.jwt_secret.clone(), + 10, + PayloadSource::Builder, + )?; self.flashblocks_service = Some(Flashblocks::run( - flashblocks_endpoint.to_string(), - "127.0.0.1:1112".to_string(), // output address for the preconfirmations from rb + builder_client, + Url::from_str(flashblocks_endpoint)?, + SocketAddr::new(IpAddr::from_str("127.0.0.1")?, 1112), // output address for the preconfirmations from rb )?); } @@ -181,7 +197,7 @@ impl BlockGenerator { source_hash: B256::default(), from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"), to: TxKind::Call(address!("4200000000000000000000000000000000000015")), - mint: None, + mint: 0, value: U256::default(), gas_limit: 210000, is_system_transaction: false, @@ -242,7 +258,7 @@ impl BlockGenerator { let payload = if let Some(flashblocks_service) = &self.flashblocks_service { flashblocks_service - .get_best_payload(Version::V4) + .get_best_payload(PayloadVersion::V4) .await? .unwrap() } else { @@ -339,7 +355,7 @@ impl BlockGenerator { source_hash: B256::default(), from: address, // Set the sender to the address of the account to seed to: TxKind::Create, - mint: Some(value), // Amount to deposit + mint: value, // Amount to deposit value: U256::default(), gas_limit: 210000, is_system_transaction: false, diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index 3de69bc5..5e6c4d7d 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -1,28 +1,19 @@ -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; -use alloy_consensus::{ - conditional::BlockConditionalAttributes, BlobTransactionSidecar, BlobTransactionValidationError, -}; -use alloy_eips::{eip7702::SignedAuthorization, Typed2718}; +use alloy_consensus::{conditional::BlockConditionalAttributes, BlobTransactionValidationError}; +use alloy_eips::{eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization, Typed2718}; use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; use alloy_rpc_types_eth::{erc4337::TransactionConditional, AccessList}; use reth_optimism_primitives::OpTransactionSigned; use reth_optimism_txpool::{ conditional::MaybeConditionalTransaction, estimated_da_size::DataAvailabilitySized, - interop::MaybeInteropTransaction, OpPooledTransaction, + interop::MaybeInteropTransaction, OpPooledTransaction, OpPooledTx, }; use reth_primitives::{kzg::KzgSettings, Recovered}; use reth_primitives_traits::InMemorySize; use reth_transaction_pool::{EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction}; -pub trait FBPoolTransaction: - EthPoolTransaction - + MaybeInteropTransaction - + MaybeConditionalTransaction - + DataAvailabilitySized - + MaybeRevertingTransaction -{ -} +pub trait FBPoolTransaction: MaybeRevertingTransaction + OpPooledTx {} #[derive(Clone, Debug)] pub struct FBPooledTransaction { @@ -32,6 +23,12 @@ pub struct FBPooledTransaction { impl FBPoolTransaction for FBPooledTransaction {} +impl OpPooledTx for FBPooledTransaction { + fn encoded_2718(&self) -> Cow<'_, Bytes> { + Cow::Borrowed(self.inner.encoded_2718()) + } +} + pub trait MaybeRevertingTransaction { fn set_exclude_reverting_txs(&mut self, exclude: bool); fn exclude_reverting_txs(&self) -> bool; @@ -179,21 +176,21 @@ impl EthPoolTransaction for FBPooledTransaction { fn try_into_pooled_eip4844( self, - sidecar: Arc, + sidecar: Arc, ) -> Option> { self.inner.try_into_pooled_eip4844(sidecar) } fn try_from_eip4844( _tx: Recovered, - _sidecar: BlobTransactionSidecar, + _sidecar: BlobTransactionSidecarVariant, ) -> Option { None } fn validate_blob( &self, - _sidecar: &BlobTransactionSidecar, + _sidecar: &BlobTransactionSidecarVariant, _settings: &KzgSettings, ) -> Result<(), BlobTransactionValidationError> { Err(BlobTransactionValidationError::NotBlobTransaction( From 0f69fd7682301cd946f3fa2d62a33a2ee9672951 Mon Sep 17 00:00:00 2001 From: cody-wang-cb Date: Wed, 4 Jun 2025 03:34:37 -0400 Subject: [PATCH 116/262] Add block number and DA used to logging (#107) * modify logging * more * structure logs --- .../src/builders/flashblocks/payload.rs | 18 +++++++++++------- .../src/builders/flashblocks/wspub.rs | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index f8a33a7e..c30eaabf 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -303,9 +303,10 @@ where if flashblock_count >= self.config.flashblocks_per_block() { tracing::info!( target: "payload_builder", - "Skipping flashblock reached target={} idx={}", - self.config.flashblocks_per_block(), - flashblock_count + target = self.config.flashblocks_per_block(), + flashblock_count = flashblock_count, + block_number = ctx.block_number(), + "Skipping flashblock reached target", ); continue; } @@ -313,10 +314,13 @@ where // Continue with flashblock building tracing::info!( target: "payload_builder", - "Building flashblock idx={} target_gas={} taget_da={}", - flashblock_count, - total_gas_per_batch, - total_da_per_batch.unwrap_or(0), + block_number = ctx.block_number(), + flashblock_count = flashblock_count, + target_gas = total_gas_per_batch, + gas_used = info.cumulative_gas_used, + target_da = total_da_per_batch.unwrap_or(0), + da_used = info.cumulative_da_bytes_used, + "Building flashblock", ); let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index 71cc053a..4455e785 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -187,7 +187,7 @@ async fn broadcast_loop( sent.fetch_add(1, Ordering::Relaxed); metrics.messages_sent_count.increment(1); - tracing::info!("Broadcasted payload: {:?}", payload); + tracing::debug!("Broadcasted payload: {:?}", payload); if let Err(e) = stream.send(Message::Text(payload)).await { tracing::debug!("Closing flashblocks subscription for {peer_addr}: {e}"); break; // Exit the loop if sending fails From 5fc7b37aa2cb37884094d547f99bf9ecb157d1de Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 4 Jun 2025 17:35:00 +1000 Subject: [PATCH 117/262] Add gas limit and DA transaction results for tracing (#110) * Add gas limit and DA transaction results for tracing * use result --- .../op-rbuilder/src/builders/context.rs | 28 +++++------------- .../src/primitives/reth/execution.rs | 29 ++++++++++++++++--- .../op-rbuilder/src/primitives/reth/mod.rs | 2 +- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index fc5d9b44..6cf26cab 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -4,9 +4,8 @@ use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types_eth::Withdrawals; use core::fmt::Debug; -use derive_more::Display; use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; -use op_revm::{OpSpecId, OpTransactionError}; +use op_revm::OpSpecId; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::PayloadConfig; use reth_chainspec::{EthChainSpec, EthereumHardforks}; @@ -36,8 +35,11 @@ use tokio_util::sync::CancellationToken; use tracing::{info, trace, warn}; use crate::{ - metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::PayloadTxsBounds, - tx::MaybeRevertingTransaction, tx_signer::Signer, + metrics::OpRBuilderMetrics, + primitives::reth::{ExecutionInfo, TxnExecutionResult}, + traits::PayloadTxsBounds, + tx::MaybeRevertingTransaction, + tx_signer::Signer, }; /// Container type that holds all necessities to build a new payload. @@ -188,20 +190,6 @@ impl OpPayloadBuilderCtx { } } -#[derive(Debug, Display)] -enum TxnExecutionResult { - InvalidDASize, - SequencerTransaction, - NonceTooLow, - InteropFailed, - #[display("InternalError({_0})")] - InternalError(OpTransactionError), - EvmError, - Success, - Reverted, - RevertedAndExcluded, -} - impl OpPayloadBuilderCtx { /// Constructs a receipt for the given transaction. fn build_receipt( @@ -380,7 +368,7 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; // ensure we still have capacity for this transaction - if info.is_tx_over_limits( + if let Err(result) = info.is_tx_over_limits( tx_da_size, block_gas_limit, tx_da_limit, @@ -390,7 +378,7 @@ impl OpPayloadBuilderCtx { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from // the iterator before we can continue - log_txn(TxnExecutionResult::InvalidDASize); + log_txn(result); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 32bf6a55..43f1eb73 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -1,8 +1,26 @@ //! Heavily influenced by [reth](https://github.com/paradigmxyz/reth/blob/1e965caf5fa176f244a31c0d2662ba1b590938db/crates/optimism/payload/src/builder.rs#L570) use alloy_primitives::{Address, U256}; use core::fmt::Debug; +use derive_more::Display; +use op_revm::OpTransactionError; use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +#[derive(Debug, Display)] +pub enum TxnExecutionResult { + TransactionDALimitExceeded, + BlockDALimitExceeded, + TransactionGasLimitExceeded, + SequencerTransaction, + NonceTooLow, + InteropFailed, + #[display("InternalError({_0})")] + InternalError(OpTransactionError), + EvmError, + Success, + Reverted, + RevertedAndExcluded, +} + #[derive(Default, Debug)] pub struct ExecutionInfo { /// All executed transactions (unrecovered). @@ -48,17 +66,20 @@ impl ExecutionInfo { tx_data_limit: Option, block_data_limit: Option, tx_gas_limit: u64, - ) -> bool { + ) -> Result<(), TxnExecutionResult> { if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) { - return true; + return Err(TxnExecutionResult::TransactionDALimitExceeded); } if block_data_limit .is_some_and(|da_limit| self.cumulative_da_bytes_used + tx_da_size > da_limit) { - return true; + return Err(TxnExecutionResult::BlockDALimitExceeded); } - self.cumulative_gas_used + tx_gas_limit > block_gas_limit + if self.cumulative_gas_used + tx_gas_limit > block_gas_limit { + return Err(TxnExecutionResult::TransactionGasLimitExceeded); + } + Ok(()) } } diff --git a/crates/builder/op-rbuilder/src/primitives/reth/mod.rs b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs index 916207bd..3aa4812a 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/mod.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs @@ -1,2 +1,2 @@ mod execution; -pub use execution::ExecutionInfo; +pub use execution::{ExecutionInfo, TxnExecutionResult}; From 13d441fcdd270e99688b464bced5b89e3e700a81 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Wed, 4 Jun 2025 10:41:58 +0100 Subject: [PATCH 118/262] Use block number as hex (#116) --- crates/builder/op-rbuilder/src/primitives/bundle.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index c6ed2416..61547bdd 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -9,7 +9,12 @@ pub struct Bundle { #[serde(rename = "txs")] pub transactions: Vec, - #[serde(rename = "maxBlockNumber")] + #[serde( + default, + rename = "maxBlockNumber", + with = "alloy_serde::quantity::opt", + skip_serializing_if = "Option::is_none" + )] pub block_number_max: Option, } From 43b6768ebe8d6c0cda6dccba02c19b9c7968f5c3 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 4 Jun 2025 23:23:05 +0700 Subject: [PATCH 119/262] Add another builder tx after the first flashblock (#121) * Add another builder tx after the first flashblock * Fix fb test --- .../src/builders/flashblocks/payload.rs | 18 ++++++++++++++++++ .../op-rbuilder/src/tests/flashblocks/smoke.rs | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index c30eaabf..0257f8b7 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -324,6 +324,13 @@ where ); let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); + invoke_on_first_flashblock(flashblock_count, || { + total_gas_per_batch -= builder_tx_gas; + // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size + if let Some(da_limit) = total_da_per_batch.as_mut() { + *da_limit = da_limit.saturating_sub(builder_tx_da_size); + } + }); invoke_on_last_flashblock(flashblock_count, last_flashblock, || { total_gas_per_batch -= builder_tx_gas; // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size @@ -367,6 +374,11 @@ where return Ok(()); } + // TODO: temporary we add builder tx to the first flashblock too + invoke_on_first_flashblock(flashblock_count, || { + ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); + }); + // If it is the last flashblocks, add the builder txn to the block if enabled invoke_on_last_flashblock(flashblock_count, last_flashblock, || { ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); @@ -689,6 +701,12 @@ where )) } +pub fn invoke_on_first_flashblock(current_flashblock: u64, fun: F) { + if current_flashblock == 0 { + fun() + } +} + pub fn invoke_on_last_flashblock( current_flashblock: u64, flashblock_limit: u64, diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs index c500c40c..f48a0f73 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs @@ -49,7 +49,7 @@ async fn chain_produces_blocks() -> eyre::Result<()> { } let generated_block = generator.generate_block().await?; - assert_eq!(generated_block.num_transactions(), 7); // 5 normal txn + deposit + builder txn + assert_eq!(generated_block.num_transactions(), 8); // 5 normal txn + deposit + 2 builder txn tokio::time::sleep(std::time::Duration::from_secs(1)).await; } From f4b51eefb5b8a95ad8e863dba029d0ccdb90da00 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Thu, 5 Jun 2025 14:40:32 +0100 Subject: [PATCH 120/262] Add reverting hashes + min block number to Bundle (#115) * Implement bundle reverting hashes * Merge main * Fix * Add min_block_number * fix litn * Fix * Fix comment * Mark invalid * Rename reverting_hashes * Remove Option * Revert "Remove Option" This reverts commit 4087d35ac53c7bc97f05728bba6c2402a132262c. --- .../op-rbuilder/src/builders/context.rs | 27 +++- .../op-rbuilder/src/primitives/bundle.rs | 13 +- .../op-rbuilder/src/revert_protection.rs | 11 +- .../op-rbuilder/src/tests/framework/txs.rs | 16 +++ .../op-rbuilder/src/tests/vanilla/revert.rs | 117 +++++++++++++++--- crates/builder/op-rbuilder/src/tx.rs | 24 ++-- 6 files changed, 180 insertions(+), 28 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 6cf26cab..65bd8463 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -1,4 +1,6 @@ -use alloy_consensus::{Eip658Value, Transaction, TxEip1559}; +use alloy_consensus::{ + conditional::BlockConditionalAttributes, Eip658Value, Transaction, TxEip1559, +}; use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718, Typed2718}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{Address, Bytes, TxKind, U256}; @@ -20,6 +22,7 @@ use reth_optimism_node::OpPayloadBuilderAttributes; use reth_optimism_payload_builder::{config::OpDAConfig, error::OpPayloadBuilderError}; use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; use reth_optimism_txpool::{ + conditional::MaybeConditionalTransaction, estimated_da_size::DataAvailabilitySized, interop::{is_valid_interop, MaybeInteropTransaction}, }; @@ -342,17 +345,37 @@ impl OpPayloadBuilderCtx { .da_tx_size_limit .set(tx_da_limit.map_or(-1.0, |v| v as f64)); + let block_attr = BlockConditionalAttributes { + number: self.block_number(), + timestamp: self.attributes().timestamp(), + }; + while let Some(tx) = best_txs.next(()) { let interop = tx.interop_deadline(); - let exclude_reverting_txs = tx.exclude_reverting_txs(); + let reverted_hashes = tx.reverted_hashes().clone(); + let conditional = tx.conditional().cloned(); + let tx_da_size = tx.estimated_da_size(); let tx = tx.into_consensus(); let tx_hash = tx.tx_hash(); + // exclude reverting transaction if: + // - the transaction comes from a bundle and the hash **is not** in reverted hashes + let exclude_reverting_txs = + reverted_hashes.is_some() && !reverted_hashes.unwrap().contains(&tx_hash); + let log_txn = |result: TxnExecutionResult| { info!(target: "payload_builder", tx_hash = ?tx_hash, tx_da_size = ?tx_da_size, exclude_reverting_txs = ?exclude_reverting_txs, result = %result, "Considering transaction"); }; + if let Some(conditional) = conditional { + // TODO: ideally we should get this from the txpool stream + if !conditional.matches_block_attributes(&block_attr) { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + } + // TODO: remove this condition and feature once we are comfortable enabling interop for everything if cfg!(feature = "interop") { // We skip invalid cross chain txs, they would be removed on the next block update in diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index 61547bdd..bda773db 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -9,6 +9,9 @@ pub struct Bundle { #[serde(rename = "txs")] pub transactions: Vec, + #[serde(rename = "reverting_tx_hashes")] + pub reverting_hashes: Vec, + #[serde( default, rename = "maxBlockNumber", @@ -16,12 +19,20 @@ pub struct Bundle { skip_serializing_if = "Option::is_none" )] pub block_number_max: Option, + + #[serde( + default, + rename = "minBlockNumber", + with = "alloy_serde::quantity::opt", + skip_serializing_if = "Option::is_none" + )] + pub block_number_min: Option, } impl Bundle { pub fn conditional(&self) -> TransactionConditional { TransactionConditional { - block_number_min: None, + block_number_min: self.block_number_min, block_number_max: self.block_number_max, known_accounts: Default::default(), timestamp_max: None, diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 7b145b45..020f5a82 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -106,6 +106,15 @@ where }; if let Some(block_number_max) = bundle.block_number_max { + if let Some(block_number_min) = bundle.block_number_min { + if block_number_min > block_number_max { + return Err(EthApiError::InvalidParams( + "block_number_min is greater than block_number_max".into(), + ) + .into()); + } + } + // The max block cannot be a past block if block_number_max <= last_block_number { return Err( @@ -128,7 +137,7 @@ where let mut pool_transaction: FBPooledTransaction = OpPooledTransaction::from_pooled(recovered).into(); - pool_transaction.set_exclude_reverting_txs(true); + pool_transaction.set_reverted_hashes(bundle.reverting_hashes.clone()); pool_transaction.set_conditional(bundle.conditional()); let hash = self diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index 70c8d3bd..c53db562 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -18,6 +18,7 @@ use super::FUNDED_PRIVATE_KEYS; #[derive(Clone, Copy, Default)] pub struct BundleOpts { pub block_number_max: Option, + pub block_number_min: Option, } #[derive(Clone)] @@ -28,6 +29,7 @@ pub struct TransactionBuilder { base_fee: Option, tx: TxEip1559, bundle_opts: Option, + with_reverted_hash: bool, key: Option, } @@ -44,6 +46,7 @@ impl TransactionBuilder { ..Default::default() }, bundle_opts: None, + with_reverted_hash: false, key: None, } } @@ -93,6 +96,11 @@ impl TransactionBuilder { self } + pub fn with_reverted_hash(mut self) -> Self { + self.with_reverted_hash = true; + self + } + pub fn with_revert(mut self) -> Self { self.tx.input = hex!("60006000fd").into(); self @@ -145,16 +153,24 @@ impl TransactionBuilder { } pub async fn send(self) -> eyre::Result> { + let with_reverted_hash = self.with_reverted_hash; let bundle_opts = self.bundle_opts; let provider = self.provider.clone(); let transaction = self.build().await; + let txn_hash = transaction.tx_hash(); let transaction_encoded = transaction.encoded_2718(); if let Some(bundle_opts) = bundle_opts { // Send the transaction as a bundle with the bundle options let bundle = Bundle { transactions: vec![transaction_encoded.into()], + reverting_hashes: if with_reverted_hash { + vec![txn_hash] + } else { + vec![] + }, block_number_max: bundle_opts.block_number_max, + block_number_min: bundle_opts.block_number_min, }; let result: BundleResult = provider diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs index e3dcef12..5f44204d 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -27,6 +27,7 @@ async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { .with_revert() .with_bundle(BundleOpts { block_number_max: Some(i), + block_number_min: None, }) .send() .await?; @@ -123,42 +124,106 @@ async fn revert_protection_bundle() -> eyre::Result<()> { assert!(block_generated.includes(*valid_bundle.tx_hash())); let bundle_opts = BundleOpts { - block_number_max: Some(4), + block_number_max: Some(5), + block_number_min: None, }; - let reverted_bundle = harness + let reverted_bundle_0 = harness .create_transaction() .with_revert() + .with_reverted_hash() .with_bundle(bundle_opts) .send() .await?; - // Test 2: Bundle reverts. It is not included in the block + // Test 2: Bundle reverts. It is included in the block since the transaction + // includes the reverted_hashes field let block_generated = generator.generate_block().await?; // Block 3 - assert!(block_generated.not_includes(*reverted_bundle.tx_hash())); + assert!(block_generated.includes(*reverted_bundle_0.tx_hash())); + + let reverted_bundle_1 = harness + .create_transaction() + .with_revert() + .with_bundle(bundle_opts) + .send() + .await?; + + // Test 3: Bundle reverts. It is not included in the block since it reverts + // and the hash is not in the reverted_hashes field. + let block_generated = generator.generate_block().await?; // Block 4 + assert!(block_generated.not_includes(*reverted_bundle_1.tx_hash())); // After the block the transaction is still pending in the pool assert!(harness - .check_tx_in_pool(*reverted_bundle.tx_hash()) + .check_tx_in_pool(*reverted_bundle_1.tx_hash()) .await? .is_pending()); // Test 3: Chain progresses beyond the bundle range. The transaction is dropped from the pool - generator.generate_block().await?; // Block 4 + generator.generate_block().await?; // Block 5 assert!(harness - .check_tx_in_pool(*reverted_bundle.tx_hash()) + .check_tx_in_pool(*reverted_bundle_1.tx_hash()) .await? .is_pending()); - generator.generate_block().await?; // Block 5 + generator.generate_block().await?; // Block 6 assert!(harness - .check_tx_in_pool(*reverted_bundle.tx_hash()) + .check_tx_in_pool(*reverted_bundle_1.tx_hash()) .await? .is_dropped()); Ok(()) } +/// Test the behaviour of the revert protection bundle with a min block number. +#[tokio::test] +async fn revert_protection_bundle_min_block_number() -> eyre::Result<()> { + let harness = TestHarnessBuilder::new("revert_protection_bundle_min_block_number") + .with_revert_protection() + .build() + .await?; + + let mut generator = harness.block_generator().await?; + + // The bundle is valid when the min block number is equal to the current block + let bundle_with_min_block = harness + .create_transaction() + .with_revert() // the transaction reverts but it is included in the block + .with_reverted_hash() + .with_bundle(BundleOpts { + block_number_max: None, + block_number_min: Some(2), + }) + .send() + .await?; + + let block = generator.generate_block().await?; // Block 1, bundle still not valid + assert!(block.not_includes(*bundle_with_min_block.tx_hash())); + + let block = generator.generate_block().await?; // Block 2, bundle is valid + assert!(block.includes(*bundle_with_min_block.tx_hash())); + + // Send a bundle with a match of min and max block number + let bundle_with_min_and_max_block = harness + .create_transaction() + .with_revert() + .with_reverted_hash() + .with_bundle(BundleOpts { + block_number_max: Some(4), + block_number_min: Some(4), + }) + .send() + .await?; + + let block = generator.generate_block().await?; // Block 3, bundle still not valid + assert!(block.not_includes(*bundle_with_min_and_max_block.tx_hash())); + + let block = generator.generate_block().await?; // Block 4, bundle is valid + assert!(block.includes(*bundle_with_min_and_max_block.tx_hash())); + + Ok(()) +} + /// Test the range limits for the revert protection bundle. #[tokio::test] async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { @@ -176,31 +241,54 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { async fn send_bundle( harness: &TestHarness, block_number_max: u64, + block_number_min: Option, ) -> eyre::Result> { harness .create_transaction() .with_bundle(BundleOpts { block_number_max: Some(block_number_max), + block_number_min: block_number_min, }) .send() .await } // Max block cannot be a past block - assert!(send_bundle(&harness, 1).await.is_err()); + assert!(send_bundle(&harness, 1, None).await.is_err()); // Bundles are valid if their max block in in between the current block and the max block range - let next_valid_block = 3; + let current_block = 2; + let next_valid_block = current_block + 1; for i in next_valid_block..next_valid_block + MAX_BLOCK_RANGE_BLOCKS { - assert!(send_bundle(&harness, i).await.is_ok()); + assert!(send_bundle(&harness, i, None).await.is_ok()); } // A bundle with a block out of range is invalid + assert!(send_bundle( + &harness, + next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1, + None + ) + .await + .is_err()); + + // A bundle with a min block number higher than the max block is invalid + assert!(send_bundle(&harness, 1, Some(2)).await.is_err()); + + // A bundle with a min block number lower or equal to the current block is valid + assert!(send_bundle(&harness, next_valid_block, Some(current_block)) + .await + .is_ok()); + assert!(send_bundle(&harness, next_valid_block, Some(0)) + .await + .is_ok()); + + // A bundle with a min block equal to max block is valid assert!( - send_bundle(&harness, next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1) + send_bundle(&harness, next_valid_block, Some(next_valid_block)) .await - .is_err() + .is_ok() ); Ok(()) @@ -249,6 +337,7 @@ async fn revert_protection_check_transaction_receipt_status_message() -> eyre::R .with_revert() .with_bundle(BundleOpts { block_number_max: Some(3), + block_number_min: None, }) .send() .await?; diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index 5e6c4d7d..a827767f 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -18,7 +18,11 @@ pub trait FBPoolTransaction: MaybeRevertingTransaction + OpPooledTx {} #[derive(Clone, Debug)] pub struct FBPooledTransaction { pub inner: OpPooledTransaction, - pub exclude_reverting_txs: bool, + + /// reverted hashes for the transaction. If the transaction is a bundle, + /// this is the list of hashes of the transactions that reverted. If the + /// transaction is not a bundle, this is `None`. + pub reverted_hashes: Option>, } impl FBPoolTransaction for FBPooledTransaction {} @@ -30,17 +34,17 @@ impl OpPooledTx for FBPooledTransaction { } pub trait MaybeRevertingTransaction { - fn set_exclude_reverting_txs(&mut self, exclude: bool); - fn exclude_reverting_txs(&self) -> bool; + fn set_reverted_hashes(&mut self, reverted_hashes: Vec); + fn reverted_hashes(&self) -> Option>; } impl MaybeRevertingTransaction for FBPooledTransaction { - fn set_exclude_reverting_txs(&mut self, exclude: bool) { - self.exclude_reverting_txs = exclude; + fn set_reverted_hashes(&mut self, reverted_hashes: Vec) { + self.reverted_hashes = Some(reverted_hashes); } - fn exclude_reverting_txs(&self) -> bool { - self.exclude_reverting_txs + fn reverted_hashes(&self) -> Option> { + self.reverted_hashes.clone() } } @@ -68,7 +72,7 @@ impl PoolTransaction for FBPooledTransaction { let inner = OpPooledTransaction::from_pooled(tx); Self { inner, - exclude_reverting_txs: false, + reverted_hashes: None, } } @@ -228,7 +232,7 @@ impl From for FBPooledTransaction { fn from(tx: OpPooledTransaction) -> Self { Self { inner: tx, - exclude_reverting_txs: false, + reverted_hashes: None, } } } @@ -252,7 +256,7 @@ impl MaybeConditionalTransaction for FBPooledTransaction { { FBPooledTransaction { inner: self.inner.with_conditional(conditional), - exclude_reverting_txs: self.exclude_reverting_txs, + reverted_hashes: self.reverted_hashes, } } } From 200db775aa4ffb986523d6bad2d0b866b7bf2b56 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Thu, 5 Jun 2025 17:56:04 +0200 Subject: [PATCH 121/262] feat: add a feature to activate otlp telemetry (#31) --- crates/builder/op-rbuilder/Cargo.toml | 5 ++ crates/builder/op-rbuilder/src/main.rs | 92 +++++++++++++++++++------- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 1326c6be..8a1a8182 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -18,7 +18,9 @@ reth-optimism-consensus.workspace = true reth-optimism-primitives.workspace = true reth-optimism-txpool.workspace = true reth-cli.workspace = true +reth-cli-commands.workspace = true reth-cli-util.workspace = true +reth-db.workspace = true reth-payload-primitives.workspace = true reth-evm.workspace = true reth-exex.workspace = true @@ -45,6 +47,7 @@ reth-optimism-forks.workspace = true reth-node-builder.workspace = true reth-rpc-eth-types.workspace = true reth-optimism-rpc.workspace = true +reth-tracing-otlp = { workspace = true, optional = true } alloy-primitives.workspace = true alloy-consensus.workspace = true @@ -143,6 +146,8 @@ testing = [] interop = [] +telemetry = ["reth-tracing-otlp"] + [[bin]] name = "op-rbuilder" path = "src/main.rs" diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index 969c0871..d13b669c 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -1,11 +1,6 @@ use args::*; -use builders::{BuilderConfig, BuilderMode, FlashblocksBuilder, StandardBuilder}; -use core::fmt::Debug; -use reth_optimism_node::{ - node::{OpAddOnsBuilder, OpPoolBuilder}, - OpNode, -}; -use reth_transaction_pool::TransactionPool; +use builders::{BuilderMode, FlashblocksBuilder, StandardBuilder}; +use eyre::Result; /// CLI argument parsing. pub mod args; @@ -18,10 +13,23 @@ mod traits; mod tx; mod tx_signer; +use builders::{BuilderConfig, PayloadBuilder}; +use core::fmt::Debug; use metrics::VERSION; use moka::future::Cache; use monitor_tx_pool::monitor_tx_pool; +use reth::builder::{NodeBuilder, WithLaunchContext}; +use reth_cli_commands::launcher::Launcher; +use reth_db::mdbx::DatabaseEnv; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_cli::chainspec::OpChainSpecParser; +use reth_optimism_node::{ + node::{OpAddOnsBuilder, OpPoolBuilder}, + OpNode, +}; +use reth_transaction_pool::TransactionPool; use revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}; +use std::{marker::PhantomData, sync::Arc}; use tx::FBPooledTransaction; // Prefer jemalloc for performance reasons. @@ -29,32 +37,68 @@ use tx::FBPooledTransaction; #[global_allocator] static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -fn main() { +fn main() -> Result<()> { let cli = Cli::parsed(); - cli.logs - .init_tracing() - .expect("Failed to initialize tracing"); + let mode = cli.builder_mode(); + let mut cli_app = cli.configure(); - match cli.builder_mode() { + #[cfg(feature = "telemetry")] + { + let otlp = reth_tracing_otlp::layer("op-reth"); + cli_app.access_tracing_layers()?.add_layer(otlp); + } + + cli_app.init_tracing()?; + match mode { BuilderMode::Standard => { tracing::info!("Starting OP builder in standard mode"); - start_builder_node::(cli); + let launcher = BuilderLauncher::::new(); + cli_app.run(launcher)?; } BuilderMode::Flashblocks => { tracing::info!("Starting OP builder in flashblocks mode"); - start_builder_node::(cli); + let launcher = BuilderLauncher::::new(); + cli_app.run(launcher)?; + } + } + Ok(()) +} + +pub struct BuilderLauncher { + _builder: PhantomData, +} + +impl BuilderLauncher +where + B: PayloadBuilder, +{ + pub fn new() -> Self { + Self { + _builder: PhantomData, } - }; + } +} + +impl Default for BuilderLauncher +where + B: PayloadBuilder, +{ + fn default() -> Self { + Self::new() + } } -/// Starts the OP builder node with a given payload builder implementation. -fn start_builder_node(cli: Cli) +impl Launcher for BuilderLauncher where - BuilderConfig<::Config>: TryFrom, - ::Config> as TryFrom>::Error: - Debug, + B: PayloadBuilder, + BuilderConfig: TryFrom, + as TryFrom>::Error: Debug, { - cli.run(|builder, builder_args| async move { + async fn entrypoint( + self, + builder: WithLaunchContext, OpChainSpec>>, + builder_args: OpRbuilderArgs, + ) -> Result<()> { let builder_config = BuilderConfig::::try_from(builder_args.clone()) .expect("Failed to convert rollup args to builder config"); let da_config = builder_config.da_config.clone(); @@ -133,7 +177,7 @@ where .launch() .await?; - handle.node_exit_future.await - }) - .unwrap(); + handle.node_exit_future.await?; + Ok(()) + } } From b8cc2602862246f4d3add7461c3bb4073cd525ab Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Fri, 6 Jun 2025 03:48:50 -0700 Subject: [PATCH 122/262] Bundles: Ensure that the min block number is inside the MAX_BLOCK_RANGE_BLOCKS (#128) If a user only specifies the min block number, the max block number is set to the current block number + MAX_BLOCK_RANGE_BLOCKS; however, there is no check that the min block is less than the newly set max block number. --- crates/builder/op-rbuilder/src/revert_protection.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 020f5a82..3bde51c1 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -131,6 +131,14 @@ where } else { // If no upper bound is set, use the maximum block range bundle.block_number_max = Some(last_block_number + MAX_BLOCK_RANGE_BLOCKS); + // Ensure that the new max is not smaller than the min + if let Some(block_number_min) = bundle.block_number_min { + if block_number_min > bundle.block_number_max.unwrap() { + return Err( + EthApiError::InvalidParams("block_number_min is too high".into()).into(), + ); + } + } } let recovered = recover_raw_transaction(&bundle_transaction)?; From 307f3c06a75126979f09790cc9ce634c15f048fc Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Fri, 6 Jun 2025 12:03:55 +0100 Subject: [PATCH 123/262] Fix bundle type reverting hashes optional param (#126) * Fix bundle type reverting hashes optional param * Fix --- crates/builder/op-rbuilder/src/builders/context.rs | 4 +++- crates/builder/op-rbuilder/src/primitives/bundle.rs | 4 ++-- crates/builder/op-rbuilder/src/revert_protection.rs | 2 +- crates/builder/op-rbuilder/src/tests/framework/txs.rs | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 65bd8463..8e0856ef 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -360,7 +360,9 @@ impl OpPayloadBuilderCtx { let tx_hash = tx.tx_hash(); // exclude reverting transaction if: - // - the transaction comes from a bundle and the hash **is not** in reverted hashes + // - the transaction comes from a bundle (is_some) and the hash **is not** in reverted hashes + // Note that we need to use the Option to signal whether the transaction comes from a bundle, + // otherwise, we would exclude all transactions that are not in the reverted hashes. let exclude_reverting_txs = reverted_hashes.is_some() && !reverted_hashes.unwrap().contains(&tx_hash); diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index bda773db..c686530c 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -9,8 +9,8 @@ pub struct Bundle { #[serde(rename = "txs")] pub transactions: Vec, - #[serde(rename = "reverting_tx_hashes")] - pub reverting_hashes: Vec, + #[serde(rename = "revertingTxHashes")] + pub reverting_hashes: Option>, #[serde( default, diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 3bde51c1..5989a62f 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -145,7 +145,7 @@ where let mut pool_transaction: FBPooledTransaction = OpPooledTransaction::from_pooled(recovered).into(); - pool_transaction.set_reverted_hashes(bundle.reverting_hashes.clone()); + pool_transaction.set_reverted_hashes(bundle.reverting_hashes.clone().unwrap_or_default()); pool_transaction.set_conditional(bundle.conditional()); let hash = self diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index c53db562..aea27663 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -165,9 +165,9 @@ impl TransactionBuilder { let bundle = Bundle { transactions: vec![transaction_encoded.into()], reverting_hashes: if with_reverted_hash { - vec![txn_hash] + Some(vec![txn_hash.into()]) } else { - vec![] + None }, block_number_max: bundle_opts.block_number_max, block_number_min: bundle_opts.block_number_min, From a9676b311a6f5503d6cc66c57f83a53691110e58 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Fri, 6 Jun 2025 20:49:26 +0700 Subject: [PATCH 124/262] Remove toml and add feature gate (#117) Rebase Remove custom logic Custom Engine API support + engine call propagation --- crates/builder/op-rbuilder/Cargo.toml | 12 +- crates/builder/op-rbuilder/src/args/op.rs | 14 +- .../op-rbuilder/src/builders/context.rs | 9 +- .../src/builders/standard/payload.rs | 4 +- crates/builder/op-rbuilder/src/lib.rs | 3 + crates/builder/op-rbuilder/src/main.rs | 27 +- .../src/primitives/reth/engine_api_builder.rs | 425 ++++++++++++++++++ .../op-rbuilder/src/primitives/reth/mod.rs | 1 + crates/builder/op-rbuilder/src/traits.rs | 20 +- crates/builder/op-rbuilder/src/tx.rs | 4 +- 10 files changed, 491 insertions(+), 28 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 8a1a8182..ac239f4b 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -28,6 +28,8 @@ reth-chainspec.workspace = true reth-primitives.workspace = true reth-primitives-traits.workspace = true reth-node-api.workspace = true +reth-rpc-engine-api.workspace = true +reth-node-core.workspace = true reth-basic-payload-builder.workspace = true reth-payload-builder.workspace = true reth-node-ethereum.workspace = true @@ -45,8 +47,11 @@ reth-network-peers.workspace = true reth-testing-utils.workspace = true reth-optimism-forks.workspace = true reth-node-builder.workspace = true +reth-storage-api.workspace = true +reth-rpc-api.workspace = true reth-rpc-eth-types.workspace = true reth-optimism-rpc.workspace = true +reth-tasks.workspace = true reth-tracing-otlp = { workspace = true, optional = true } alloy-primitives.workspace = true @@ -81,7 +86,8 @@ serde.workspace = true secp256k1.workspace = true tokio.workspace = true jsonrpsee = { workspace = true } -jsonrpsee-types.workspace = true +jsonrpsee-core = { workspace = true } +jsonrpsee-types = { workspace = true } async-trait = { workspace = true } clap_builder = { workspace = true } clap.workspace = true @@ -92,6 +98,7 @@ tokio-util.workspace = true thiserror.workspace = true parking_lot.workspace = true url.workspace = true +anyhow = "1" tower = "0.5" futures = "0.3" @@ -120,6 +127,7 @@ vergen-git2.workspace = true alloy-provider = { workspace = true, default-features = true, features = [ "txpool-api", ] } +tempfile = "3.8" [features] default = ["jemalloc"] @@ -148,6 +156,8 @@ interop = [] telemetry = ["reth-tracing-otlp"] +custom-engine-api = [] + [[bin]] name = "op-rbuilder" path = "src/main.rs" diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index d4b92e58..777baa91 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -3,13 +3,13 @@ //! Copied from OptimismNode to allow easy extension. //! clap [Args](clap::Args) for optimism rollup configuration -use std::path::PathBuf; - use crate::tx_signer::Signer; +use anyhow::{anyhow, Result}; use reth_optimism_node::args::RollupArgs; +use std::path::PathBuf; /// Parameters for rollup configuration -#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +#[derive(Debug, Clone, Default, clap::Args)] #[command(next_help_heading = "Rollup")] pub struct OpRbuilderArgs { /// Rollup configuration @@ -38,6 +38,7 @@ pub struct OpRbuilderArgs { #[arg(long = "builder.enable-revert-protection", default_value = "false")] pub enable_revert_protection: bool, + /// Path to builder playgorund to automatically start up the node connected to it #[arg( long = "builder.playground", num_args = 0..=1, @@ -46,17 +47,16 @@ pub struct OpRbuilderArgs { env = "PLAYGROUND_DIR", )] pub playground: Option, - #[command(flatten)] pub flashblocks: FlashblocksArgs, } -fn expand_path(s: &str) -> Result { +fn expand_path(s: &str) -> Result { shellexpand::full(s) - .map_err(|e| format!("expansion error for `{s}`: {e}"))? + .map_err(|e| anyhow!("expansion error for `{s}`: {e}"))? .into_owned() .parse() - .map_err(|e| format!("invalid path after expansion: {e}")) + .map_err(|e| anyhow!("invalid path after expansion: {e}")) } /// Parameters for Flashblocks configuration diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 8e0856ef..2b7484f9 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -588,12 +588,9 @@ impl OpPayloadBuilderCtx { // Create and sign the transaction let builder_tx = signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; - Ok( - op_alloy_flz::tx_estimated_size_fjord(builder_tx.encoded_2718().as_slice()) - // Downscaled by 1e6 to be compliant with op-geth estimate size function - // https://github.com/ethereum-optimism/op-geth/blob/optimism/core/types/rollup_cost.go#L563 - .wrapping_div(1_000_000), - ) + Ok(op_alloy_flz::tx_estimated_size_fjord_bytes( + builder_tx.encoded_2718().as_slice(), + )) }) .transpose() .unwrap_or_else(|err: PayloadBuilderError| { diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index ecf32137..45f4ffca 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -11,7 +11,7 @@ use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_primitives::U256; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; -use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates}; +use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; use reth_evm::{execute::BlockBuilder, ConfigureEvm}; use reth_node_api::{Block, PayloadBuilderError}; use reth_node_builder::{components::PayloadBuilderBuilder, BuilderContext}; @@ -543,7 +543,7 @@ impl OpBuilder<'_, Txs> { execution_output: Arc::new(execution_outcome), hashed_state: Arc::new(hashed_state), }, - trie: Arc::new(trie_output), + trie: ExecutedTrieUpdates::Present(Arc::new(trie_output)), }; let no_tx_pool = ctx.attributes().no_tx_pool; diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index f377643f..2abc3a32 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -3,3 +3,6 @@ pub mod tx_signer; #[cfg(any(test, feature = "testing"))] pub mod tests; + +pub mod traits; +pub mod tx; diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/main.rs index d13b669c..19495823 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/main.rs @@ -18,13 +18,14 @@ use core::fmt::Debug; use metrics::VERSION; use moka::future::Cache; use monitor_tx_pool::monitor_tx_pool; +use primitives::reth::engine_api_builder::OpEngineApiBuilder; use reth::builder::{NodeBuilder, WithLaunchContext}; use reth_cli_commands::launcher::Launcher; use reth_db::mdbx::DatabaseEnv; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_cli::chainspec::OpChainSpecParser; use reth_optimism_node::{ - node::{OpAddOnsBuilder, OpPoolBuilder}, + node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, OpNode, }; use reth_transaction_pool::TransactionPool; @@ -101,12 +102,28 @@ where ) -> Result<()> { let builder_config = BuilderConfig::::try_from(builder_args.clone()) .expect("Failed to convert rollup args to builder config"); + let da_config = builder_config.da_config.clone(); let rollup_args = builder_args.rollup_args; let op_node = OpNode::new(rollup_args.clone()); let reverted_cache = Cache::builder().max_capacity(100).build(); let reverted_cache_copy = reverted_cache.clone(); + let mut addons: OpAddOns< + _, + _, + OpEngineValidatorBuilder, + OpEngineApiBuilder, + > = OpAddOnsBuilder::default() + .with_sequencer(rollup_args.sequencer.clone()) + .with_enable_tx_conditional(rollup_args.enable_tx_conditional) + .with_da_config(da_config) + .build(); + if cfg!(feature = "custom-engine-api") { + let engine_builder: OpEngineApiBuilder = + OpEngineApiBuilder::default(); + addons = addons.with_engine_api(engine_builder); + } let handle = builder .with_types::() .with_components( @@ -127,13 +144,7 @@ where ) .payload(B::new_service(builder_config)?), ) - .with_add_ons( - OpAddOnsBuilder::default() - .with_sequencer(rollup_args.sequencer.clone()) - .with_enable_tx_conditional(rollup_args.enable_tx_conditional) - .with_da_config(da_config) - .build(), - ) + .with_add_ons(addons) .extend_rpc_modules(move |ctx| { if builder_args.enable_revert_protection { tracing::info!("Revert protection enabled"); diff --git a/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs b/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs new file mode 100644 index 00000000..486602e1 --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs @@ -0,0 +1,425 @@ +//! RPC component builder + +use reth_node_api::AddOnsContext; +use reth_node_builder::rpc::{EngineApiBuilder, EngineValidatorBuilder}; +use reth_node_core::version::{CARGO_PKG_VERSION, CLIENT_CODE, VERGEN_GIT_SHA}; +use reth_optimism_node::{OpEngineTypes, OP_NAME_CLIENT}; +use reth_optimism_rpc::engine::OP_ENGINE_CAPABILITIES; +pub use reth_optimism_rpc::OpEngineApi; +use reth_payload_builder::PayloadStore; +use reth_rpc_engine_api::EngineCapabilities; + +use crate::traits::NodeComponents; +use alloy_eips::eip7685::Requests; +use alloy_primitives::{BlockHash, B256, U64}; +use alloy_rpc_types_engine::{ + ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV3, + ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, +}; +use jsonrpsee::proc_macros::rpc; +use jsonrpsee_core::{server::RpcModule, RpcResult}; +use op_alloy_rpc_types_engine::{ + OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4, + OpPayloadAttributes, ProtocolVersion, SuperchainSignal, +}; +use reth_node_api::{EngineTypes, EngineValidator}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_rpc::OpEngineApiServer; +use reth_rpc_api::IntoEngineApiRpcModule; +use reth_storage_api::{BlockReader, HeaderProvider, StateProviderFactory}; +use reth_transaction_pool::TransactionPool; + +/// Builder for basic [`OpEngineApi`] implementation. +#[derive(Debug, Clone)] +pub struct OpEngineApiBuilder { + engine_validator_builder: EV, +} + +impl Default for OpEngineApiBuilder +where + EV: Default, +{ + fn default() -> Self { + Self { + engine_validator_builder: EV::default(), + } + } +} + +impl EngineApiBuilder for OpEngineApiBuilder +where + N: NodeComponents, + EV: EngineValidatorBuilder, +{ + type EngineApi = OpEngineApiExt; + + async fn build_engine_api(self, ctx: &AddOnsContext<'_, N>) -> eyre::Result { + let Self { + engine_validator_builder, + } = self; + + let engine_validator = engine_validator_builder.build(ctx).await?; + let client = ClientVersionV1 { + code: CLIENT_CODE, + name: OP_NAME_CLIENT.to_string(), + version: CARGO_PKG_VERSION.to_string(), + commit: VERGEN_GIT_SHA.to_string(), + }; + let inner = reth_rpc_engine_api::EngineApi::new( + ctx.node.provider().clone(), + ctx.config.chain.clone(), + ctx.beacon_engine_handle.clone(), + PayloadStore::new(ctx.node.payload_builder_handle().clone()), + ctx.node.pool().clone(), + Box::new(ctx.node.task_executor().clone()), + client, + EngineCapabilities::new(OP_ENGINE_CAPABILITIES.iter().copied()), + engine_validator, + ctx.config.engine.accept_execution_requests_hash, + ); + + Ok(OpEngineApiExt::new(OpEngineApi::new(inner))) + } +} + +pub struct OpEngineApiExt { + inner: OpEngineApi, +} + +impl OpEngineApiExt +where + Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static, + Pool: TransactionPool + 'static, + Validator: EngineValidator, +{ + pub fn new(engine: OpEngineApi) -> Self { + Self { inner: engine } + } +} + +#[async_trait::async_trait] +impl OpRbuilderEngineApiServer + for OpEngineApiExt +where + Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static, + Pool: TransactionPool + 'static, + Validator: EngineValidator, +{ + async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult { + self.inner.new_payload_v2(payload).await + } + + async fn new_payload_v3( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> RpcResult { + self.inner + .new_payload_v3(payload, versioned_hashes, parent_beacon_block_root) + .await + } + + async fn new_payload_v4( + &self, + payload: OpExecutionPayloadV4, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + execution_requests: Requests, + ) -> RpcResult { + self.inner + .new_payload_v4( + payload, + versioned_hashes, + parent_beacon_block_root, + execution_requests, + ) + .await + } + + async fn fork_choice_updated_v1( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult { + self.inner + .fork_choice_updated_v1(fork_choice_state, payload_attributes) + .await + } + + async fn fork_choice_updated_v2( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult { + self.inner + .fork_choice_updated_v2(fork_choice_state, payload_attributes) + .await + } + + async fn fork_choice_updated_v3( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult { + self.inner + .fork_choice_updated_v3(fork_choice_state, payload_attributes) + .await + } + + async fn get_payload_v2( + &self, + payload_id: PayloadId, + ) -> RpcResult<::ExecutionPayloadEnvelopeV2> { + self.inner.get_payload_v2(payload_id).await + } + + async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> RpcResult { + self.inner.get_payload_v3(payload_id).await + } + + async fn get_payload_v4( + &self, + payload_id: PayloadId, + ) -> RpcResult { + self.inner.get_payload_v4(payload_id).await + } + + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec, + ) -> RpcResult { + self.inner.get_payload_bodies_by_hash_v1(block_hashes).await + } + + async fn get_payload_bodies_by_range_v1( + &self, + start: U64, + count: U64, + ) -> RpcResult { + self.inner + .get_payload_bodies_by_range_v1(start, count) + .await + } + + async fn signal_superchain_v1(&self, signal: SuperchainSignal) -> RpcResult { + self.inner.signal_superchain_v1(signal).await + } + + async fn get_client_version_v1( + &self, + client: ClientVersionV1, + ) -> RpcResult> { + self.inner.get_client_version_v1(client).await + } + + async fn exchange_capabilities(&self, capabilities: Vec) -> RpcResult> { + self.inner.exchange_capabilities(capabilities).await + } +} + +impl IntoEngineApiRpcModule for OpEngineApiExt +where + Self: OpRbuilderEngineApiServer, +{ + fn into_rpc_module(self) -> RpcModule<()> { + self.into_rpc().remove_context() + } +} + +/// Extension trait that gives access to Optimism engine API RPC methods. +/// +/// Note: +/// > The provider should use a JWT authentication layer. +/// +/// This follows the Optimism specs that can be found at: +/// +#[rpc(server, namespace = "engine", server_bounds(Engine::PayloadAttributes: jsonrpsee::core::DeserializeOwned))] +pub trait OpRbuilderEngineApi { + /// Sends the given payload to the execution layer client, as specified for the Shanghai fork. + /// + /// See also + /// + /// No modifications needed for OP compatibility. + #[method(name = "newPayloadV2")] + async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult; + + /// Sends the given payload to the execution layer client, as specified for the Cancun fork. + /// + /// See also + /// + /// OP modifications: + /// - expected versioned hashes MUST be an empty array: therefore the `versioned_hashes` + /// parameter is removed. + /// - parent beacon block root MUST be the parent beacon block root from the L1 origin block of + /// the L2 block. + /// - blob versioned hashes MUST be empty list. + #[method(name = "newPayloadV3")] + async fn new_payload_v3( + &self, + payload: ExecutionPayloadV3, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + ) -> RpcResult; + + /// Sends the given payload to the execution layer client, as specified for the Prague fork. + /// + /// See also + /// + /// - blob versioned hashes MUST be empty list. + /// - execution layer requests MUST be empty list. + #[method(name = "newPayloadV4")] + async fn new_payload_v4( + &self, + payload: OpExecutionPayloadV4, + versioned_hashes: Vec, + parent_beacon_block_root: B256, + execution_requests: Requests, + ) -> RpcResult; + + /// See also + /// + /// This exists because it is used by op-node: + /// + /// Caution: This should not accept the `withdrawals` field in the payload attributes. + #[method(name = "forkchoiceUpdatedV1")] + async fn fork_choice_updated_v1( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult; + + /// Updates the execution layer client with the given fork choice, as specified for the Shanghai + /// fork. + /// + /// Caution: This should not accept the `parentBeaconBlockRoot` field in the payload attributes. + /// + /// See also + /// + /// OP modifications: + /// - The `payload_attributes` parameter is extended with the [`EngineTypes::PayloadAttributes`](EngineTypes) type as described in + #[method(name = "forkchoiceUpdatedV2")] + async fn fork_choice_updated_v2( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult; + + /// Updates the execution layer client with the given fork choice, as specified for the Cancun + /// fork. + /// + /// See also + /// + /// OP modifications: + /// - Must be called with an Ecotone payload + /// - Attributes must contain the parent beacon block root field + /// - The `payload_attributes` parameter is extended with the [`EngineTypes::PayloadAttributes`](EngineTypes) type as described in + #[method(name = "forkchoiceUpdatedV3")] + async fn fork_choice_updated_v3( + &self, + fork_choice_state: ForkchoiceState, + payload_attributes: Option, + ) -> RpcResult; + + /// Retrieves an execution payload from a previously started build process, as specified for the + /// Shanghai fork. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// No modifications needed for OP compatibility. + #[method(name = "getPayloadV2")] + async fn get_payload_v2( + &self, + payload_id: PayloadId, + ) -> RpcResult; + + /// Retrieves an execution payload from a previously started build process, as specified for the + /// Cancun fork. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// OP modifications: + /// - the response type is extended to [`EngineTypes::ExecutionPayloadEnvelopeV3`]. + #[method(name = "getPayloadV3")] + async fn get_payload_v3( + &self, + payload_id: PayloadId, + ) -> RpcResult; + + /// Returns the most recent version of the payload that is available in the corresponding + /// payload build process at the time of receiving this call. + /// + /// See also + /// + /// Note: + /// > Provider software MAY stop the corresponding build process after serving this call. + /// + /// OP modifications: + /// - the response type is extended to [`EngineTypes::ExecutionPayloadEnvelopeV4`]. + #[method(name = "getPayloadV4")] + async fn get_payload_v4( + &self, + payload_id: PayloadId, + ) -> RpcResult; + + /// Returns the execution payload bodies by the given hash. + /// + /// See also + #[method(name = "getPayloadBodiesByHashV1")] + async fn get_payload_bodies_by_hash_v1( + &self, + block_hashes: Vec, + ) -> RpcResult; + + /// Returns the execution payload bodies by the range starting at `start`, containing `count` + /// blocks. + /// + /// WARNING: This method is associated with the BeaconBlocksByRange message in the consensus + /// layer p2p specification, meaning the input should be treated as untrusted or potentially + /// adversarial. + /// + /// Implementers should take care when acting on the input to this method, specifically + /// ensuring that the range is limited properly, and that the range boundaries are computed + /// correctly and without panics. + /// + /// See also + #[method(name = "getPayloadBodiesByRangeV1")] + async fn get_payload_bodies_by_range_v1( + &self, + start: U64, + count: U64, + ) -> RpcResult; + + /// Signals superchain information to the Engine. + /// Returns the latest supported OP-Stack protocol version of the execution engine. + /// See also + #[method(name = "engine_signalSuperchainV1")] + async fn signal_superchain_v1(&self, _signal: SuperchainSignal) -> RpcResult; + + /// Returns the execution client version information. + /// + /// Note: + /// > The `client_version` parameter identifies the consensus client. + /// + /// See also + #[method(name = "getClientVersionV1")] + async fn get_client_version_v1( + &self, + client_version: ClientVersionV1, + ) -> RpcResult>; + + /// Returns the list of Engine API methods supported by the execution layer client software. + /// + /// See also + #[method(name = "exchangeCapabilities")] + async fn exchange_capabilities(&self, capabilities: Vec) -> RpcResult>; +} diff --git a/crates/builder/op-rbuilder/src/primitives/reth/mod.rs b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs index 3aa4812a..ed2b38b9 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/mod.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/mod.rs @@ -1,2 +1,3 @@ +pub mod engine_api_builder; mod execution; pub use execution::{ExecutionInfo, TxnExecutionResult}; diff --git a/crates/builder/op-rbuilder/src/traits.rs b/crates/builder/op-rbuilder/src/traits.rs index 3c924353..ef9dde45 100644 --- a/crates/builder/op-rbuilder/src/traits.rs +++ b/crates/builder/op-rbuilder/src/traits.rs @@ -1,5 +1,5 @@ use alloy_consensus::Header; -use reth_node_api::{FullNodeTypes, NodeTypes}; +use reth_node_api::{FullNodeComponents, FullNodeTypes, NodeTypes}; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::OpEngineTypes; use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; @@ -27,6 +27,24 @@ impl NodeBounds for T where { } +pub trait NodeComponents: + FullNodeComponents< + Types: NodeTypes, +> +{ +} + +impl NodeComponents for T where + T: FullNodeComponents< + Types: NodeTypes< + Payload = OpEngineTypes, + ChainSpec = OpChainSpec, + Primitives = OpPrimitives, + >, + > +{ +} + pub trait PoolBounds: TransactionPool> + Unpin + 'static where diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index a827767f..b54b470e 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -222,9 +222,7 @@ impl MaybeInteropTransaction for FBPooledTransaction { impl DataAvailabilitySized for FBPooledTransaction { fn estimated_da_size(&self) -> u64 { - // Downscaled by 1e6 to be compliant with op-geth estimate size function - // https://github.com/ethereum-optimism/op-geth/blob/optimism/core/types/rollup_cost.go#L563 - op_alloy_flz::tx_estimated_size_fjord(self.inner.encoded_2718()).wrapping_div(1_000_000) + self.inner.estimated_da_size() } } From 9198745a593a777a84880ec257dcfde201c9e999 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Sat, 7 Jun 2025 12:41:01 +0100 Subject: [PATCH 125/262] Move bundle validation to primitives folder (#129) * Move bundle validation to primitives folder * Add another check * Fix --- .../op-rbuilder/src/primitives/bundle.rs | 258 +++++++++++++++++- .../op-rbuilder/src/revert_protection.rs | 44 +-- .../op-rbuilder/src/tests/vanilla/revert.rs | 42 ++- 3 files changed, 289 insertions(+), 55 deletions(-) diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index c686530c..251e28b9 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -1,5 +1,6 @@ use alloy_primitives::{Bytes, B256}; use alloy_rpc_types_eth::erc4337::TransactionConditional; +use reth_rpc_eth_types::EthApiError; use serde::{Deserialize, Serialize}; pub const MAX_BLOCK_RANGE_BLOCKS: u64 = 10; @@ -29,15 +30,89 @@ pub struct Bundle { pub block_number_min: Option, } +impl From for EthApiError { + fn from(err: BundleConditionalError) -> Self { + EthApiError::InvalidParams(err.to_string()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum BundleConditionalError { + #[error("block_number_min ({min}) is greater than block_number_max ({max})")] + MinGreaterThanMax { min: u64, max: u64 }, + #[error("block_number_max ({max}) is a past block (current: {current})")] + MaxBlockInPast { max: u64, current: u64 }, + #[error( + "block_number_max ({max}) is too high (current: {current}, max allowed: {max_allowed})" + )] + MaxBlockTooHigh { + max: u64, + current: u64, + max_allowed: u64, + }, + #[error( + "block_number_min ({min}) is too high with default max range (max allowed: {max_allowed})" + )] + MinTooHighForDefaultRange { min: u64, max_allowed: u64 }, +} + impl Bundle { - pub fn conditional(&self) -> TransactionConditional { - TransactionConditional { - block_number_min: self.block_number_min, - block_number_max: self.block_number_max, + pub fn conditional( + &self, + last_block_number: u64, + ) -> Result { + let mut block_number_max = self.block_number_max; + let block_number_min = self.block_number_min; + + // Validate block number ranges + if let Some(max) = block_number_max { + // Check if min > max + if let Some(min) = block_number_min { + if min > max { + return Err(BundleConditionalError::MinGreaterThanMax { min, max }); + } + } + + // The max block cannot be a past block + if max <= last_block_number { + return Err(BundleConditionalError::MaxBlockInPast { + max, + current: last_block_number, + }); + } + + // Validate that it is not greater than the max_block_range + let max_allowed = last_block_number + MAX_BLOCK_RANGE_BLOCKS; + if max > max_allowed { + return Err(BundleConditionalError::MaxBlockTooHigh { + max, + current: last_block_number, + max_allowed, + }); + } + } else { + // If no upper bound is set, use the maximum block range + let default_max = last_block_number + MAX_BLOCK_RANGE_BLOCKS; + block_number_max = Some(default_max); + + // Ensure that the new max is not smaller than the min + if let Some(min) = block_number_min { + if min > default_max { + return Err(BundleConditionalError::MinTooHighForDefaultRange { + min, + max_allowed: default_max, + }); + } + } + } + + Ok(TransactionConditional { + block_number_min, + block_number_max, known_accounts: Default::default(), timestamp_max: None, timestamp_min: None, - } + }) } } @@ -46,3 +121,176 @@ pub struct BundleResult { #[serde(rename = "bundleHash")] pub bundle_hash: B256, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bundle_conditional_no_bounds() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: None, + block_number_min: None, + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.block_number_min, None); + assert_eq!( + result.block_number_max, + Some(last_block + MAX_BLOCK_RANGE_BLOCKS) + ); + } + + #[test] + fn test_bundle_conditional_with_valid_bounds() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: Some(1005), + block_number_min: Some(1002), + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.block_number_min, Some(1002)); + assert_eq!(result.block_number_max, Some(1005)); + } + + #[test] + fn test_bundle_conditional_min_greater_than_max() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: Some(1005), + block_number_min: Some(1010), + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::MinGreaterThanMax { + min: 1010, + max: 1005 + }) + )); + } + + #[test] + fn test_bundle_conditional_max_in_past() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: Some(999), + block_number_min: None, + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::MaxBlockInPast { + max: 999, + current: 1000 + }) + )); + } + + #[test] + fn test_bundle_conditional_max_too_high() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: Some(1020), + block_number_min: None, + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::MaxBlockTooHigh { + max: 1020, + current: 1000, + max_allowed: 1010 + }) + )); + } + + #[test] + fn test_bundle_conditional_min_too_high_for_default_range() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: None, + block_number_min: Some(1015), + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::MinTooHighForDefaultRange { + min: 1015, + max_allowed: 1010 + }) + )); + } + + #[test] + fn test_bundle_conditional_with_only_min() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: None, + block_number_min: Some(1005), + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.block_number_min, Some(1005)); + assert_eq!(result.block_number_max, Some(1010)); // last_block + MAX_BLOCK_RANGE_BLOCKS + } + + #[test] + fn test_bundle_conditional_with_only_max() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: Some(1008), + block_number_min: None, + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.block_number_min, None); + assert_eq!(result.block_number_max, Some(1008)); + } + + #[test] + fn test_bundle_conditional_min_lower_than_last_block() { + let bundle = Bundle { + transactions: vec![], + reverting_hashes: None, + block_number_max: None, + block_number_min: Some(999), + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.block_number_min, Some(999)); + assert_eq!(result.block_number_max, Some(1010)); + } +} diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 5989a62f..d9f7a9ed 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -1,5 +1,5 @@ use crate::{ - primitives::bundle::{Bundle, BundleResult, MAX_BLOCK_RANGE_BLOCKS}, + primitives::bundle::{Bundle, BundleResult}, tx::{FBPooledTransaction, MaybeRevertingTransaction}, }; use alloy_json_rpc::RpcObject; @@ -82,7 +82,7 @@ where Pool: TransactionPool + Clone + 'static, Provider: StateProviderFactory + Send + Sync + Clone + 'static, { - async fn send_bundle(&self, mut bundle: Bundle) -> RpcResult { + async fn send_bundle(&self, bundle: Bundle) -> RpcResult { let last_block_number = self .provider .best_block_number() @@ -105,48 +105,16 @@ where } }; - if let Some(block_number_max) = bundle.block_number_max { - if let Some(block_number_min) = bundle.block_number_min { - if block_number_min > block_number_max { - return Err(EthApiError::InvalidParams( - "block_number_min is greater than block_number_max".into(), - ) - .into()); - } - } - - // The max block cannot be a past block - if block_number_max <= last_block_number { - return Err( - EthApiError::InvalidParams("block_number_max is a past block".into()).into(), - ); - } - - // Validate that it is not greater than the max_block_range - if block_number_max > last_block_number + MAX_BLOCK_RANGE_BLOCKS { - return Err( - EthApiError::InvalidParams("block_number_max is too high".into()).into(), - ); - } - } else { - // If no upper bound is set, use the maximum block range - bundle.block_number_max = Some(last_block_number + MAX_BLOCK_RANGE_BLOCKS); - // Ensure that the new max is not smaller than the min - if let Some(block_number_min) = bundle.block_number_min { - if block_number_min > bundle.block_number_max.unwrap() { - return Err( - EthApiError::InvalidParams("block_number_min is too high".into()).into(), - ); - } - } - } + let conditional = bundle + .conditional(last_block_number) + .map_err(EthApiError::from)?; let recovered = recover_raw_transaction(&bundle_transaction)?; let mut pool_transaction: FBPooledTransaction = OpPooledTransaction::from_pooled(recovered).into(); pool_transaction.set_reverted_hashes(bundle.reverting_hashes.clone().unwrap_or_default()); - pool_transaction.set_conditional(bundle.conditional()); + pool_transaction.set_conditional(conditional); let hash = self .pool diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs index 5f44204d..be0d139e 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -240,57 +240,75 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { async fn send_bundle( harness: &TestHarness, - block_number_max: u64, + block_number_max: Option, block_number_min: Option, ) -> eyre::Result> { harness .create_transaction() .with_bundle(BundleOpts { - block_number_max: Some(block_number_max), - block_number_min: block_number_min, + block_number_max, + block_number_min, }) .send() .await } // Max block cannot be a past block - assert!(send_bundle(&harness, 1, None).await.is_err()); + assert!(send_bundle(&harness, Some(1), None).await.is_err()); // Bundles are valid if their max block in in between the current block and the max block range let current_block = 2; let next_valid_block = current_block + 1; for i in next_valid_block..next_valid_block + MAX_BLOCK_RANGE_BLOCKS { - assert!(send_bundle(&harness, i, None).await.is_ok()); + assert!(send_bundle(&harness, Some(i), None).await.is_ok()); } // A bundle with a block out of range is invalid assert!(send_bundle( &harness, - next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1, + Some(next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1), None ) .await .is_err()); // A bundle with a min block number higher than the max block is invalid - assert!(send_bundle(&harness, 1, Some(2)).await.is_err()); + assert!(send_bundle(&harness, Some(1), Some(2)).await.is_err()); // A bundle with a min block number lower or equal to the current block is valid - assert!(send_bundle(&harness, next_valid_block, Some(current_block)) - .await - .is_ok()); - assert!(send_bundle(&harness, next_valid_block, Some(0)) + assert!( + send_bundle(&harness, Some(next_valid_block), Some(current_block)) + .await + .is_ok() + ); + assert!(send_bundle(&harness, Some(next_valid_block), Some(0)) .await .is_ok()); // A bundle with a min block equal to max block is valid assert!( - send_bundle(&harness, next_valid_block, Some(next_valid_block)) + send_bundle(&harness, Some(next_valid_block), Some(next_valid_block)) .await .is_ok() ); + // Test min-only cases (no max specified) + // A bundle with only min block that's within the default range is valid + let default_max = current_block + MAX_BLOCK_RANGE_BLOCKS; + assert!(send_bundle(&harness, None, Some(current_block)) + .await + .is_ok()); + assert!(send_bundle(&harness, None, Some(default_max - 1)) + .await + .is_ok()); + assert!(send_bundle(&harness, None, Some(default_max)).await.is_ok()); + + // A bundle with only min block that exceeds the default max range is invalid + assert!(send_bundle(&harness, None, Some(default_max + 1)) + .await + .is_err()); + Ok(()) } From 01b627a94bbac6077c06c248cea321a6b9104a73 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Mon, 9 Jun 2025 14:13:11 +0100 Subject: [PATCH 126/262] Split op-rbuilder in lib and main (#138) * Make op-rbuilder a lib * Fix lint * Two more fixes * Move alloc --------- Co-authored-by: Solar Mithril --- crates/builder/op-rbuilder/Cargo.toml | 2 +- .../op-rbuilder/src/bin/op-rbuilder/main.rs | 10 ++++++ .../op-rbuilder/src/{main.rs => launcher.rs} | 35 ++++++------------- crates/builder/op-rbuilder/src/lib.rs | 8 +++++ 4 files changed, 29 insertions(+), 26 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/bin/op-rbuilder/main.rs rename crates/builder/op-rbuilder/src/{main.rs => launcher.rs} (88%) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index ac239f4b..37d5855f 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -160,7 +160,7 @@ custom-engine-api = [] [[bin]] name = "op-rbuilder" -path = "src/main.rs" +path = "src/bin/op-rbuilder/main.rs" [[bin]] name = "tester" diff --git a/crates/builder/op-rbuilder/src/bin/op-rbuilder/main.rs b/crates/builder/op-rbuilder/src/bin/op-rbuilder/main.rs new file mode 100644 index 00000000..985b93c4 --- /dev/null +++ b/crates/builder/op-rbuilder/src/bin/op-rbuilder/main.rs @@ -0,0 +1,10 @@ +use op_rbuilder::launcher::launch; + +// Prefer jemalloc for performance reasons. +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +fn main() -> eyre::Result<()> { + launch() +} diff --git a/crates/builder/op-rbuilder/src/main.rs b/crates/builder/op-rbuilder/src/launcher.rs similarity index 88% rename from crates/builder/op-rbuilder/src/main.rs rename to crates/builder/op-rbuilder/src/launcher.rs index 19495823..a8904ed8 100644 --- a/crates/builder/op-rbuilder/src/main.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -1,24 +1,16 @@ -use args::*; -use builders::{BuilderMode, FlashblocksBuilder, StandardBuilder}; use eyre::Result; -/// CLI argument parsing. -pub mod args; -mod builders; -mod metrics; -mod monitor_tx_pool; -mod primitives; -mod revert_protection; -mod traits; -mod tx; -mod tx_signer; - -use builders::{BuilderConfig, PayloadBuilder}; +use crate::{ + args::*, + builders::{BuilderConfig, BuilderMode, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, + metrics::VERSION, + monitor_tx_pool::monitor_tx_pool, + primitives::reth::engine_api_builder::OpEngineApiBuilder, + revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}, + tx::FBPooledTransaction, +}; use core::fmt::Debug; -use metrics::VERSION; use moka::future::Cache; -use monitor_tx_pool::monitor_tx_pool; -use primitives::reth::engine_api_builder::OpEngineApiBuilder; use reth::builder::{NodeBuilder, WithLaunchContext}; use reth_cli_commands::launcher::Launcher; use reth_db::mdbx::DatabaseEnv; @@ -29,16 +21,9 @@ use reth_optimism_node::{ OpNode, }; use reth_transaction_pool::TransactionPool; -use revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}; use std::{marker::PhantomData, sync::Arc}; -use tx::FBPooledTransaction; - -// Prefer jemalloc for performance reasons. -#[cfg(all(feature = "jemalloc", unix))] -#[global_allocator] -static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -fn main() -> Result<()> { +pub fn launch() -> Result<()> { let cli = Cli::parsed(); let mode = cli.builder_mode(); let mut cli_app = cli.configure(); diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index 2abc3a32..421220da 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -6,3 +6,11 @@ pub mod tests; pub mod traits; pub mod tx; + +/// CLI argument parsing. +pub mod args; +mod builders; +pub mod launcher; +mod metrics; +mod monitor_tx_pool; +mod revert_protection; From 3e9395a0a45122681f9bf29f989227f3bcca47a0 Mon Sep 17 00:00:00 2001 From: Karim Agha Date: Wed, 11 Jun 2025 10:18:31 +0200 Subject: [PATCH 127/262] In-process tests, optional dockerized validation node (#132) * Validation node works and tests are passing using the new test infra * revert_protection_monitor_transaction_gc passes * revert_protection_disabled passes * ordering tests are passing * all tests are passing * ready * lint * Github Action login to GHCR.io * testing Github Actions ghcr access * testing Github Actions ghcr access * lint * Closes issue 133 * review feedback * review feedback * Review feedback - ctrl-c * Review feedback * review feedback * Review feedback * review feedback * lint * macos compatibility * lint * lint --- crates/builder/op-rbuilder/Cargo.toml | 25 +- crates/builder/op-rbuilder/src/args/mod.rs | 2 +- crates/builder/op-rbuilder/src/args/op.rs | 27 +- .../op-rbuilder/src/bin/tester/main.rs | 73 +-- .../op-rbuilder/src/builders/context.rs | 4 +- crates/builder/op-rbuilder/src/lib.rs | 19 +- .../src/tests/flashblocks/smoke.rs | 42 +- .../op-rbuilder/src/tests/framework/apis.rs | 181 +++--- .../op-rbuilder/src/tests/framework/blocks.rs | 425 -------------- .../op-rbuilder/src/tests/framework/driver.rs | 289 ++++++++++ .../src/tests/framework/external.rs | 528 ++++++++++++++++++ .../src/tests/framework/harness.rs | 357 ------------ .../src/tests/framework/instance.rs | 355 ++++++++++++ .../op-rbuilder/src/tests/framework/mod.rs | 47 +- .../op-rbuilder/src/tests/framework/op.rs | 325 ----------- .../src/tests/framework/service.rs | 111 ---- .../op-rbuilder/src/tests/framework/txs.rs | 165 +++++- .../op-rbuilder/src/tests/framework/utils.rs | 193 +++++++ .../src/tests/vanilla/data_availability.rs | 73 +-- .../op-rbuilder/src/tests/vanilla/mod.rs | 2 - .../op-rbuilder/src/tests/vanilla/ordering.rs | 31 +- .../op-rbuilder/src/tests/vanilla/revert.rs | 297 +++++----- .../op-rbuilder/src/tests/vanilla/smoke.rs | 183 +++--- .../op-rbuilder/src/tests/vanilla/txpool.rs | 57 +- 24 files changed, 2122 insertions(+), 1689 deletions(-) delete mode 100644 crates/builder/op-rbuilder/src/tests/framework/blocks.rs create mode 100644 crates/builder/op-rbuilder/src/tests/framework/driver.rs create mode 100644 crates/builder/op-rbuilder/src/tests/framework/external.rs delete mode 100644 crates/builder/op-rbuilder/src/tests/framework/harness.rs create mode 100644 crates/builder/op-rbuilder/src/tests/framework/instance.rs delete mode 100644 crates/builder/op-rbuilder/src/tests/framework/op.rs delete mode 100644 crates/builder/op-rbuilder/src/tests/framework/service.rs create mode 100644 crates/builder/op-rbuilder/src/tests/framework/utils.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 37d5855f..5e828706 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -116,6 +116,13 @@ http = "1.0" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } +dashmap = { version = "6.1", optional = true } +nanoid = { version = "0.4", optional = true } +reth-ipc = { workspace = true, optional = true } +bollard = { version = "0.19", optional = true } +tar = { version = "0.4", optional = true } +ctor = { version = "0.4.2", optional = true } + [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -129,6 +136,13 @@ alloy-provider = { workspace = true, default-features = true, features = [ ] } tempfile = "3.8" +dashmap = { version = "6.1" } +nanoid = { version = "0.4" } +reth-ipc = { workspace = true } +reth-node-builder = { workspace = true, features = ["test-utils"] } +bollard = "0.19" +ctor = "0.4.2" + [features] default = ["jemalloc"] @@ -150,7 +164,16 @@ min-info-logs = ["tracing/release_max_level_info"] min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] -testing = [] + +testing = [ + "dashmap", + "nanoid", + "reth-ipc", + "reth-node-builder/test-utils", + "bollard", + "ctor", +] + interop = [] diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index 20f716f9..9048fde7 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -3,7 +3,7 @@ use crate::{ metrics::{CARGO_PKG_VERSION, VERGEN_GIT_SHA}, }; use clap_builder::{CommandFactory, FromArgMatches}; -pub use op::OpRbuilderArgs; +pub use op::{FlashblocksArgs, OpRbuilderArgs}; use playground::PlaygroundOptions; use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 777baa91..4f54adaa 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -3,13 +3,16 @@ //! Copied from OptimismNode to allow easy extension. //! clap [Args](clap::Args) for optimism rollup configuration + use crate::tx_signer::Signer; use anyhow::{anyhow, Result}; +use clap::Parser; +use reth_optimism_cli::commands::Commands; use reth_optimism_node::args::RollupArgs; use std::path::PathBuf; /// Parameters for rollup configuration -#[derive(Debug, Clone, Default, clap::Args)] +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] #[command(next_help_heading = "Rollup")] pub struct OpRbuilderArgs { /// Rollup configuration @@ -51,6 +54,16 @@ pub struct OpRbuilderArgs { pub flashblocks: FlashblocksArgs, } +impl Default for OpRbuilderArgs { + fn default() -> Self { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext + } +} + fn expand_path(s: &str) -> Result { shellexpand::full(s) .map_err(|e| anyhow!("expansion error for `{s}`: {e}"))? @@ -63,7 +76,7 @@ fn expand_path(s: &str) -> Result { /// 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. -#[derive(Debug, Clone, Default, PartialEq, Eq, clap::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. @@ -101,3 +114,13 @@ pub struct FlashblocksArgs { )] pub flashblocks_block_time: u64, } + +impl Default for FlashblocksArgs { + fn default() -> Self { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext.flashblocks + } +} diff --git a/crates/builder/op-rbuilder/src/bin/tester/main.rs b/crates/builder/op-rbuilder/src/bin/tester/main.rs index 10768caa..b011beb6 100644 --- a/crates/builder/op-rbuilder/src/bin/tester/main.rs +++ b/crates/builder/op-rbuilder/src/bin/tester/main.rs @@ -1,5 +1,7 @@ use alloy_primitives::Address; +use alloy_provider::{Identity, ProviderBuilder}; use clap::Parser; +use op_alloy_network::Optimism; use op_rbuilder::tests::*; /// CLI Commands @@ -49,69 +51,38 @@ async fn main() -> eyre::Result<()> { match cli.command { Commands::Genesis { output } => generate_genesis(output).await, - Commands::Run { - validation, - no_tx_pool, - block_time_secs, - flashblocks_endpoint, - no_sleep, - } => { - run_system( - validation, - no_tx_pool, - block_time_secs, - flashblocks_endpoint, - no_sleep, - ) - .await - } + Commands::Run { validation, .. } => run_system(validation).await, Commands::Deposit { address, amount } => { - let engine_api = EngineApi::builder().build().unwrap(); - let mut generator = BlockGenerator::new(engine_api, None, false, 1, None); - - generator.init().await?; - - let block_generated = generator.deposit(address, amount).await?; - println!( - "Deposit transaction included in block: {:?}", - block_generated.block_hash() - ); + let engine_api = EngineApi::with_http("http://localhost:4444"); + let provider = ProviderBuilder::::default() + .connect_http("http://localhost:2222".try_into()?); + let driver = ChainDriver::::remote(provider, engine_api); + let block_hash = driver.fund(address, amount).await?; + println!("Deposit transaction included in block: {block_hash}"); Ok(()) } } } #[allow(dead_code)] -pub async fn run_system( - validation: bool, - no_tx_pool: bool, - block_time_secs: u64, - flashblocks_endpoint: Option, - no_sleep: bool, -) -> eyre::Result<()> { - println!("Validation: {validation}"); +pub async fn run_system(validation: bool) -> eyre::Result<()> { + println!("Validation node enabled: {validation}"); - let engine_api = EngineApi::new("http://localhost:4444").unwrap(); - let validation_api = if validation { - Some(EngineApi::new("http://localhost:5555").unwrap()) - } else { - None - }; + let engine_api = EngineApi::with_http("http://localhost:4444"); + let provider = ProviderBuilder::::default() + .connect_http("http://localhost:2222".try_into()?); + let mut driver = ChainDriver::::remote(provider, engine_api); - let mut generator = BlockGenerator::new( - engine_api, - validation_api, - no_tx_pool, - block_time_secs, - flashblocks_endpoint, - ); - - generator.init().await?; + if validation { + driver = driver + .with_validation_node(ExternalNode::reth().await?) + .await?; + } // Infinite loop generating blocks loop { println!("Generating new block..."); - let block_generated = generator.submit_payload(None, 0, no_sleep).await?; - println!("Generated block: {:?}", block_generated.block_hash()); + let block = driver.build_new_block().await?; + println!("Generated block: {:?}", block.header.hash); } } diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 2b7484f9..218596ec 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -35,7 +35,7 @@ use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; use revm::{context::result::ResultAndState, Database, DatabaseCommit}; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; -use tracing::{info, trace, warn}; +use tracing::{debug, info, trace, warn}; use crate::{ metrics::OpRBuilderMetrics, @@ -367,7 +367,7 @@ impl OpPayloadBuilderCtx { reverted_hashes.is_some() && !reverted_hashes.unwrap().contains(&tx_hash); let log_txn = |result: TxnExecutionResult| { - info!(target: "payload_builder", tx_hash = ?tx_hash, tx_da_size = ?tx_da_size, exclude_reverting_txs = ?exclude_reverting_txs, result = %result, "Considering transaction"); + debug!(target: "payload_builder", tx_hash = ?tx_hash, tx_da_size = ?tx_da_size, exclude_reverting_txs = ?exclude_reverting_txs, result = %result, "Considering transaction"); }; if let Some(conditional) = conditional { diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index 421220da..0305ed08 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,16 +1,13 @@ +pub mod args; +pub mod builders; +pub mod launcher; +pub mod metrics; +mod monitor_tx_pool; pub mod primitives; +pub mod revert_protection; +pub mod traits; +pub mod tx; pub mod tx_signer; #[cfg(any(test, feature = "testing"))] pub mod tests; - -pub mod traits; -pub mod tx; - -/// CLI argument parsing. -pub mod args; -mod builders; -pub mod launcher; -mod metrics; -mod monitor_tx_pool; -mod revert_protection; diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs index f48a0f73..5a98b5d0 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs @@ -6,26 +6,39 @@ use tokio::task::JoinHandle; use tokio_tungstenite::{connect_async, tungstenite::Message}; use tokio_util::sync::CancellationToken; -use crate::tests::TestHarnessBuilder; +use crate::{ + args::{FlashblocksArgs, OpRbuilderArgs}, + builders::FlashblocksBuilder, + tests::{ChainDriverExt, LocalInstance, TransactionBuilderExt}, +}; #[tokio::test] async fn chain_produces_blocks() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("flashbots_chain_produces_blocks") - .with_flashblocks_port(1239) - .with_chain_block_time(2000) - .with_flashbots_block_time(200) - .build() - .await?; + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + chain_block_time: 2000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + }, + ..Default::default() + }) + .await?; + + let driver = rbuilder.driver().await?; + driver.fund_default_accounts().await?; // Create a struct to hold received messages let received_messages = Arc::new(Mutex::new(Vec::new())); let messages_clone = received_messages.clone(); let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); // Spawn WebSocket listener task let cancellation_token_clone = cancellation_token.clone(); let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async("ws://localhost:1239").await?; + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; let (_, mut read) = ws_stream.split(); loop { @@ -40,22 +53,25 @@ async fn chain_produces_blocks() -> eyre::Result<()> { } }); - let mut generator = harness.block_generator().await?; - for _ in 0..10 { for _ in 0..5 { // send a valid transaction - let _ = harness.send_valid_transaction().await?; + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; } - let generated_block = generator.generate_block().await?; - assert_eq!(generated_block.num_transactions(), 8); // 5 normal txn + deposit + 2 builder txn + let block = driver.build_new_block().await?; + assert_eq!(block.transactions.len(), 8); // 5 normal txn + deposit + 2 builder txn tokio::time::sleep(std::time::Duration::from_secs(1)).await; } cancellation_token.cancel(); assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + assert!( !received_messages .lock() diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs index 19b63e87..d9357207 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -1,8 +1,9 @@ use super::DEFAULT_JWT_TOKEN; use alloy_eips::{eip7685::Requests, BlockNumberOrTag}; use alloy_primitives::B256; + use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadStatus}; -use http::Uri; +use core::{future::Future, marker::PhantomData}; use jsonrpsee::{ core::{client::SubscriptionClientT, RpcResult}, proc_macros::rpc, @@ -15,84 +16,143 @@ use reth_optimism_rpc::engine::OpEngineApiClient; use reth_payload_builder::PayloadId; use reth_rpc_layer::{AuthClientLayer, JwtSecret}; use serde_json::Value; -use std::str::FromStr; +use tracing::debug; -/// Helper for engine api operations -pub struct EngineApi { - pub url: Uri, - pub jwt_secret: JwtSecret, +#[derive(Clone, Debug)] +pub enum Address { + Ipc(String), + Http(url::Url), +} + +pub trait Protocol { + fn client( + jwt: JwtSecret, + address: Address, + ) -> impl Future; +} + +pub struct Http; +impl Protocol for Http { + async fn client( + jwt: JwtSecret, + address: Address, + ) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + let Address::Http(url) = address else { + unreachable!(); + }; + + let secret_layer = AuthClientLayer::new(jwt); + let middleware = tower::ServiceBuilder::default().layer(secret_layer); + jsonrpsee::http_client::HttpClientBuilder::default() + .set_http_middleware(middleware) + .build(url) + .expect("Failed to create http client") + } +} + +pub struct Ipc; +impl Protocol for Ipc { + async fn client( + _: JwtSecret, // ipc does not use JWT + address: Address, + ) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + let Address::Ipc(path) = address else { + unreachable!(); + }; + reth_ipc::client::IpcClientBuilder::default() + .build(&path) + .await + .expect("Failed to create ipc client") + } } -/// Builder for EngineApi configuration -pub struct EngineApiBuilder { - url: String, - jwt_secret: String, +/// Helper for engine api operations +pub struct EngineApi { + address: Address, + jwt_secret: JwtSecret, + _tag: PhantomData

, } -impl Default for EngineApiBuilder { - fn default() -> Self { - Self::new() +impl EngineApi

{ + async fn client(&self) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + P::client(self.jwt_secret, self.address.clone()).await } } -impl EngineApiBuilder { - pub fn new() -> Self { - Self { - url: String::from("http://localhost:8551"), - jwt_secret: String::from(DEFAULT_JWT_TOKEN), +// http specific +impl EngineApi { + pub fn with_http(url: &str) -> EngineApi { + EngineApi:: { + address: Address::Http(url.parse().expect("Invalid URL")), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, } } - pub fn with_url(mut self, url: &str) -> Self { - self.url = url.to_string(); - self + pub fn with_localhost_port(port: u16) -> EngineApi { + EngineApi:: { + address: Address::Http( + format!("http://localhost:{}", port) + .parse() + .expect("Invalid URL"), + ), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, + } } - pub fn build(self) -> Result> { - Ok(EngineApi { - url: self.url.parse()?, - jwt_secret: JwtSecret::from_str(&self.jwt_secret)?, - }) + pub fn with_port(mut self, port: u16) -> Self { + let Address::Http(url) = &mut self.address else { + unreachable!(); + }; + + url.set_port(Some(port)).expect("Invalid port"); + self } -} -impl EngineApi { - pub fn builder() -> EngineApiBuilder { - EngineApiBuilder::new() + pub fn with_jwt_secret(mut self, jwt_secret: &str) -> Self { + self.jwt_secret = jwt_secret.parse().expect("Invalid JWT"); + self } - pub fn new(url: &str) -> Result> { - Self::builder().with_url(url).build() + pub fn url(&self) -> &url::Url { + let Address::Http(url) = &self.address else { + unreachable!(); + }; + url } +} - pub fn new_with_port(port: u16) -> Result> { - Self::builder() - .with_url(&format!("http://localhost:{port}")) - .build() +// ipc specific +impl EngineApi { + pub fn with_ipc(path: &str) -> EngineApi { + EngineApi:: { + address: Address::Ipc(path.into()), + jwt_secret: DEFAULT_JWT_TOKEN.parse().expect("Invalid JWT"), + _tag: PhantomData, + } } - pub fn http_client(&self) -> impl SubscriptionClientT + Clone + Send + Sync + Unpin + 'static { - // Create a middleware that adds a new JWT token to every request. - let secret_layer = AuthClientLayer::new(self.jwt_secret); - let middleware = tower::ServiceBuilder::default().layer(secret_layer); - jsonrpsee::http_client::HttpClientBuilder::default() - .set_http_middleware(middleware) - .build(&self.url.to_string()) - .expect("Failed to create http client") + pub fn path(&self) -> &str { + let Address::Ipc(path) = &self.address else { + unreachable!(); + }; + path } +} +impl EngineApi

{ pub async fn get_payload( &self, payload_id: PayloadId, ) -> eyre::Result<::ExecutionPayloadEnvelopeV4> { - println!( + debug!( "Fetching payload with id: {} at {}", payload_id, chrono::Utc::now() ); - Ok( - OpEngineApiClient::::get_payload_v4(&self.http_client(), payload_id) + OpEngineApiClient::::get_payload_v4(&self.client().await, payload_id) .await?, ) } @@ -104,10 +164,9 @@ impl EngineApi { parent_beacon_block_root: B256, execution_requests: Requests, ) -> eyre::Result { - println!("Submitting new payload at {}...", chrono::Utc::now()); - + debug!("Submitting new payload at {}...", chrono::Utc::now()); Ok(OpEngineApiClient::::new_payload_v4( - &self.http_client(), + &self.client().await, payload, versioned_hashes, parent_beacon_block_root, @@ -122,10 +181,9 @@ impl EngineApi { new_head: B256, payload_attributes: Option<::PayloadAttributes>, ) -> eyre::Result { - println!("Updating forkchoice at {}...", chrono::Utc::now()); - + debug!("Updating forkchoice at {}...", chrono::Utc::now()); Ok(OpEngineApiClient::::fork_choice_updated_v3( - &self.http_client(), + &self.client().await, ForkchoiceState { head_block_hash: new_head, safe_block_hash: current_head, @@ -135,19 +193,6 @@ impl EngineApi { ) .await?) } - - pub async fn latest(&self) -> eyre::Result> { - self.get_block_by_number(BlockNumberOrTag::Latest, false) - .await - } - - pub async fn get_block_by_number( - &self, - number: BlockNumberOrTag, - include_txs: bool, - ) -> eyre::Result> { - Ok(BlockApiClient::get_block_by_number(&self.http_client(), number, include_txs).await?) - } } #[rpc(server, client, namespace = "eth")] @@ -177,9 +222,9 @@ pub async fn generate_genesis(output: Option) -> eyre::Result<()> { // Write the result to the output file if let Some(output) = output { std::fs::write(&output, serde_json::to_string_pretty(&genesis)?)?; - println!("Generated genesis file at: {output}"); + debug!("Generated genesis file at: {output}"); } else { - println!("{}", serde_json::to_string_pretty(&genesis)?); + debug!("{}", serde_json::to_string_pretty(&genesis)?); } Ok(()) diff --git a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs b/crates/builder/op-rbuilder/src/tests/framework/blocks.rs deleted file mode 100644 index f6094679..00000000 --- a/crates/builder/op-rbuilder/src/tests/framework/blocks.rs +++ /dev/null @@ -1,425 +0,0 @@ -use std::{ - net::{IpAddr, SocketAddr}, - str::FromStr, -}; - -use crate::tx_signer::Signer; -use alloy_eips::{eip2718::Encodable2718, eip7685::Requests, BlockNumberOrTag}; -use alloy_primitives::{address, hex, Address, Bytes, TxKind, B256, U256}; -use alloy_rpc_types_engine::{ - ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadAttributes, - PayloadStatusEnum, -}; -use alloy_rpc_types_eth::Block; -use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; -use op_alloy_rpc_types_engine::{OpExecutionPayloadV4, OpPayloadAttributes}; -use rollup_boost::{ - Flashblocks, FlashblocksService, OpExecutionPayloadEnvelope, PayloadSource, PayloadVersion, - RpcClient, -}; -use url::Url; - -use super::apis::EngineApi; - -// L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) -// -// https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1 -const FJORD_DATA: &[u8] = &hex!("440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"); - -/// A system that continuously generates blocks using the engine API -pub struct BlockGenerator { - engine_api: EngineApi, - validation_api: Option, - latest_hash: B256, - no_tx_pool: bool, - block_time_secs: u64, - timestamp: u64, - // flashblocks service - flashblocks_endpoint: Option, - flashblocks_service: Option, -} - -impl BlockGenerator { - pub fn new( - engine_api: EngineApi, - validation_api: Option, - no_tx_pool: bool, - block_time_secs: u64, - flashblocks_endpoint: Option, - ) -> Self { - Self { - engine_api, - validation_api, - latest_hash: B256::ZERO, // temporary value - no_tx_pool, - timestamp: 0, - block_time_secs, - flashblocks_endpoint, - flashblocks_service: None, - } - } - - /// Initialize the block generator by fetching the latest block - pub async fn init(&mut self) -> eyre::Result { - let latest_block = self.engine_api.latest().await?.expect("block not found"); - self.latest_hash = latest_block.header.hash; - self.timestamp = latest_block.header.timestamp; - - // Sync validation node if it exists - if let Some(validation_api) = &self.validation_api { - self.sync_validation_node(validation_api).await?; - } - - // Initialize flashblocks service - if let Some(flashblocks_endpoint) = &self.flashblocks_endpoint { - println!("Initializing flashblocks service at {flashblocks_endpoint}"); - let builder_client = RpcClient::new( - self.engine_api.url.clone(), - self.engine_api.jwt_secret.clone(), - 10, - PayloadSource::Builder, - )?; - - self.flashblocks_service = Some(Flashblocks::run( - builder_client, - Url::from_str(flashblocks_endpoint)?, - SocketAddr::new(IpAddr::from_str("127.0.0.1")?, 1112), // output address for the preconfirmations from rb - )?); - } - - Ok(latest_block) - } - - /// Sync the validation node to the current state - async fn sync_validation_node(&self, validation_api: &EngineApi) -> eyre::Result<()> { - let latest_validation_block = validation_api.latest().await?.expect("block not found"); - let latest_block = self.engine_api.latest().await?.expect("block not found"); - - if latest_validation_block.header.number > latest_block.header.number { - return Err(eyre::eyre!("validation node is ahead of the builder")); - } - - if latest_validation_block.header.number < latest_block.header.number { - println!( - "validation node {} is behind the builder {}, syncing up", - latest_validation_block.header.number, latest_block.header.number - ); - - let mut latest_hash = latest_validation_block.header.hash; - - for i in (latest_validation_block.header.number + 1)..=latest_block.header.number { - println!("syncing block {i}"); - - let block = self - .engine_api - .get_block_by_number(BlockNumberOrTag::Number(i), true) - .await? - .expect("block not found"); - - if block.header.parent_hash != latest_hash { - return Err(eyre::eyre!("unexpected parent hash during sync")); - } - - let payload_request = OpExecutionPayloadV4 { - payload_inner: ExecutionPayloadV3 { - payload_inner: ExecutionPayloadV2 { - payload_inner: ExecutionPayloadV1 { - parent_hash: block.header.parent_hash, - fee_recipient: block.header.beneficiary, - state_root: block.header.state_root, - receipts_root: block.header.receipts_root, - logs_bloom: block.header.logs_bloom, - prev_randao: B256::ZERO, - block_number: block.header.number, - gas_limit: block.header.gas_limit, - gas_used: block.header.gas_used, - timestamp: block.header.timestamp, - extra_data: block.header.extra_data.clone(), - base_fee_per_gas: U256::from( - block.header.base_fee_per_gas.unwrap(), - ), - block_hash: block.header.hash, - transactions: vec![], // there are no txns yet - }, - withdrawals: block.withdrawals.unwrap().to_vec(), - }, - blob_gas_used: block.header.inner.blob_gas_used.unwrap(), - excess_blob_gas: block.header.inner.excess_blob_gas.unwrap(), - }, - withdrawals_root: Default::default(), - }; - - let validation_status = validation_api - .new_payload(payload_request, vec![], B256::ZERO, Requests::default()) - .await?; - - if validation_status.status != PayloadStatusEnum::Valid { - return Err(eyre::eyre!("invalid payload status during sync")); - } - - let new_chain_hash = validation_status - .latest_valid_hash - .ok_or_else(|| eyre::eyre!("missing latest valid hash"))?; - - if new_chain_hash != block.header.hash { - return Err(eyre::eyre!("hash mismatch during sync")); - } - - validation_api - .update_forkchoice(latest_hash, new_chain_hash, None) - .await?; - - latest_hash = new_chain_hash; - } - } - - Ok(()) - } - - /// Helper function to submit a payload and update chain state - pub async fn submit_payload( - &mut self, - transactions: Option>, - block_building_delay_secs: u64, - no_sleep: bool, // TODO: Change this, too many parameters we can tweak here to put as a function arguments - ) -> eyre::Result { - let timestamp = self.timestamp + self.block_time_secs; - - // Add L1 block info as the first transaction in every L2 block - // This deposit transaction contains L1 block metadata required by the L2 chain - // Currently using hardcoded data from L1 block 124665056 - // If this info is not provided, Reth cannot decode the receipt for any transaction - // in the block since it also includes this info as part of the result. - // It does not matter if the to address (4200000000000000000000000000000000000015) is - // not deployed on the L2 chain since Reth queries the block to get the info and not the contract. - let block_info_tx: Bytes = { - let deposit_tx = TxDeposit { - source_hash: B256::default(), - from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"), - to: TxKind::Call(address!("4200000000000000000000000000000000000015")), - mint: 0, - value: U256::default(), - gas_limit: 210000, - is_system_transaction: false, - input: FJORD_DATA.into(), - }; - - // Create a temporary signer for the deposit - let signer = Signer::random(); - let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; - signed_tx.encoded_2718().into() - }; - - let transactions = if let Some(transactions) = transactions { - // prepend the block info transaction - let mut all_transactions = vec![block_info_tx]; - all_transactions.extend(transactions.into_iter()); - all_transactions - } else { - vec![block_info_tx] - }; - - let result = self - .engine_api - .update_forkchoice( - self.latest_hash, - self.latest_hash, - Some(OpPayloadAttributes { - payload_attributes: PayloadAttributes { - withdrawals: Some(vec![]), - parent_beacon_block_root: Some(B256::ZERO), - timestamp, - prev_randao: B256::ZERO, - suggested_fee_recipient: Default::default(), - }, - transactions: Some(transactions), - no_tx_pool: Some(self.no_tx_pool), - gas_limit: Some(10000000), - eip_1559_params: None, - }), - ) - .await?; - - if result.payload_status.status != PayloadStatusEnum::Valid { - return Err(eyre::eyre!("Invalid payload status")); - } - - let payload_id = result.payload_id.unwrap(); - - // update the payload id in the flashblocks service if present - if let Some(flashblocks_service) = &self.flashblocks_service { - flashblocks_service.set_current_payload_id(payload_id).await; - } - - if !self.no_tx_pool && !no_sleep { - let sleep_time = self.block_time_secs + block_building_delay_secs; - tokio::time::sleep(tokio::time::Duration::from_secs(sleep_time)).await; - } - - let payload = if let Some(flashblocks_service) = &self.flashblocks_service { - flashblocks_service - .get_best_payload(PayloadVersion::V4) - .await? - .unwrap() - } else { - OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?) - }; - - let execution_payload = match payload { - OpExecutionPayloadEnvelope::V4(execution_payload) => { - execution_payload.execution_payload - } - _ => { - return Err(eyre::eyre!("execution_payload should be V4")); - } - }; - - // Validate with builder node - let validation_status = self - .engine_api - .new_payload( - execution_payload.clone(), - vec![], - B256::ZERO, - Requests::default(), - ) - .await?; - - if validation_status.status != PayloadStatusEnum::Valid { - return Err(eyre::eyre!("Invalid validation status from builder")); - } - - // Validate with validation node if present - if let Some(validation_api) = &self.validation_api { - let validation_status = validation_api - .new_payload( - execution_payload.clone(), - vec![], - B256::ZERO, - Requests::default(), - ) - .await?; - - if validation_status.status != PayloadStatusEnum::Valid { - return Err(eyre::eyre!("Invalid validation status from validator")); - } - } - - let new_block_hash = execution_payload - .payload_inner - .payload_inner - .payload_inner - .block_hash; - - // Update forkchoice on builder - self.engine_api - .update_forkchoice(self.latest_hash, new_block_hash, None) - .await?; - - // Update forkchoice on validator if present - if let Some(validation_api) = &self.validation_api { - validation_api - .update_forkchoice(self.latest_hash, new_block_hash, None) - .await?; - } - - // Update internal state - self.latest_hash = new_block_hash; - self.timestamp = execution_payload.payload_inner.timestamp(); - - let block = self - .engine_api - .get_block_by_number(BlockNumberOrTag::Latest, false) - .await? - .expect("block not found"); - - assert_eq!(block.header.hash, new_block_hash); - - let generated_block = BlockGenerated { block }; - Ok(generated_block) - } - - /// Generate a single new block and return its hash - pub async fn generate_block(&mut self) -> eyre::Result { - self.submit_payload(None, 0, false).await - } - - pub async fn generate_block_with_delay(&mut self, delay: u64) -> eyre::Result { - self.submit_payload(None, delay, false).await - } - - /// Submit a deposit transaction to seed an account with ETH - pub async fn deposit(&mut self, address: Address, value: u128) -> eyre::Result { - // Create deposit transaction - let deposit_tx = TxDeposit { - source_hash: B256::default(), - from: address, // Set the sender to the address of the account to seed - to: TxKind::Create, - mint: value, // Amount to deposit - value: U256::default(), - gas_limit: 210000, - is_system_transaction: false, - input: Bytes::default(), - }; - - // Create a temporary signer for the deposit - let signer = Signer::random(); - let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; - let signed_tx_rlp = signed_tx.encoded_2718(); - - self.submit_payload(Some(vec![signed_tx_rlp.into()]), 0, false) - .await - } - - pub async fn create_funded_accounts( - &mut self, - count: usize, - amount: u128, - ) -> eyre::Result> { - let mut signers = Vec::with_capacity(count); - - for _ in 0..count { - // Create a new signer - let signer = Signer::random(); - let address = signer.address; - - // Deposit funds to the new account - self.deposit(address, amount).await?; - - signers.push(signer); - } - - Ok(signers) - } -} - -#[derive(Debug)] -pub struct BlockGenerated { - pub block: Block, -} - -impl BlockGenerated { - pub fn block_hash(&self) -> B256 { - self.block.header.hash - } - - pub fn not_includes(&self, tx_hash: B256) -> bool { - !self.includes(tx_hash) - } - - pub fn includes(&self, tx_hash: B256) -> bool { - self.block.transactions.hashes().any(|hash| hash == tx_hash) - } - - pub fn includes_vec(&self, tx_hashes: Vec) -> bool { - tx_hashes.iter().all(|hash| self.includes(*hash)) - } - - pub fn not_includes_vec(&self, tx_hashes: Vec) -> bool { - tx_hashes.iter().all(|hash| self.not_includes(*hash)) - } - - pub fn num_transactions(&self) -> usize { - self.block.transactions.len() - } -} diff --git a/crates/builder/op-rbuilder/src/tests/framework/driver.rs b/crates/builder/op-rbuilder/src/tests/framework/driver.rs new file mode 100644 index 00000000..49321366 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/driver.rs @@ -0,0 +1,289 @@ +use core::time::Duration; +use std::time::SystemTime; + +use alloy_eips::{eip7685::Requests, BlockNumberOrTag, Encodable2718}; +use alloy_primitives::{address, hex, Bytes, TxKind, B256, U256}; +use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadAttributes, PayloadStatusEnum}; +use alloy_rpc_types_eth::Block; +use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; +use op_alloy_network::Optimism; +use op_alloy_rpc_types::Transaction; +use reth_optimism_node::OpPayloadAttributes; +use rollup_boost::OpExecutionPayloadEnvelope; + +use super::{EngineApi, Ipc, LocalInstance, TransactionBuilder}; +use crate::{ + args::OpRbuilderArgs, + tests::{ExternalNode, Protocol}, + tx_signer::Signer, +}; + +const DEFAULT_GAS_LIMIT: u64 = 10_000_000; + +/// The ChainDriver is a type that allows driving the op builder node to build new blocks manually +/// by calling the `build_new_block` method. It uses the Engine API to interact with the node +/// and the provider to fetch blocks and transactions. +pub struct ChainDriver { + engine_api: EngineApi, + provider: RootProvider, + signer: Option, + gas_limit: Option, + args: OpRbuilderArgs, + validation_nodes: Vec, +} + +// instantiation and configuration +impl ChainDriver { + const MIN_BLOCK_TIME: Duration = Duration::from_secs(1); + + /// Creates a new ChainDriver instance for a local instance of RBuilder running in-process + /// communicating over IPC. + pub async fn local(instance: &LocalInstance) -> eyre::Result> { + Ok(ChainDriver:: { + engine_api: instance.engine_api(), + provider: instance.provider().await?, + signer: Default::default(), + gas_limit: None, + args: instance.args().clone(), + validation_nodes: vec![], + }) + } + + /// Creates a new ChainDriver for some EL node instance. + pub fn remote( + provider: RootProvider, + engine_api: EngineApi, + ) -> ChainDriver { + ChainDriver { + engine_api, + provider, + signer: Default::default(), + gas_limit: None, + args: OpRbuilderArgs::default(), + validation_nodes: vec![], + } + } + + /// Specifies the block builder signing key used to sign builder transactions. + /// If not specified, a random signer will be used. + pub fn with_signer(mut self, signer: Signer) -> Self { + self.signer = Some(signer); + self + } + + /// Specifies a custom gas limit for blocks being built, otherwise the limit is + /// set to a default value of 10_000_000. + pub fn with_gas_limit(mut self, gas_limit: u64) -> Self { + self.gas_limit = Some(gas_limit); + self + } + + /// Adds an external Optimism execution client node that will receive all newly built + /// blocks by this driver and ensure that they are valid. This validation process is + /// transparent and happens in the background when building new blocks. + /// + /// If there are validation nodes specified any newly built block will be submitted to + /// the validation EL and the driver will fail if the block is rejected by the + /// validation node. + pub async fn with_validation_node(mut self, node: ExternalNode) -> eyre::Result { + node.catch_up_with(self.provider()).await?; + self.validation_nodes.push(node); + Ok(self) + } +} + +// public test api +impl ChainDriver { + /// Builds a new block using the current state of the chain and the transactions in the pool. + pub async fn build_new_block(&self) -> eyre::Result> { + self.build_new_block_with_txs(vec![]).await + } + + /// Builds a new block using the current state of the chain and the transactions in the pool with a list + /// of mandatory builder transactions. Those are usually deposit transactions. + pub async fn build_new_block_with_txs( + &self, + txs: Vec, + ) -> eyre::Result> { + let latest = self.latest().await?; + let latest_timestamp = Duration::from_secs(latest.header.timestamp); + let actual_timestamp = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_err(|_| eyre::eyre!("Failed to get current system time"))?; + + // block timestamp will be the max of the actual timestamp and the latest block + // timestamp plus the minimum block time. This ensures that blocks don't break any + // assumptions, but also gives the test author the ability to control the block time + // in the test. + let block_timestamp = actual_timestamp.max(latest_timestamp + Self::MIN_BLOCK_TIME); + + // Add L1 block info as the first transaction in every L2 block + // This deposit transaction contains L1 block metadata required by the L2 chain + // Currently using hardcoded data from L1 block 124665056 + // If this info is not provided, Reth cannot decode the receipt for any transaction + // in the block since it also includes this info as part of the result. + // It does not matter if the to address (4200000000000000000000000000000000000015) is + // not deployed on the L2 chain since Reth queries the block to get the info and not the contract. + let block_info_tx: Bytes = { + let deposit_tx = TxDeposit { + source_hash: B256::default(), + from: address!("DeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001"), + to: TxKind::Call(address!("4200000000000000000000000000000000000015")), + mint: 0, + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: FJORD_DATA.into(), + }; + + // Create a temporary signer for the deposit + let signer = self.signer.unwrap_or_else(Signer::random); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit_tx))?; + signed_tx.encoded_2718().into() + }; + + let fcu_result = self + .fcu(OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: block_timestamp.as_secs(), + parent_beacon_block_root: Some(B256::ZERO), + withdrawals: Some(vec![]), + ..Default::default() + }, + transactions: Some(vec![block_info_tx].into_iter().chain(txs).collect()), + gas_limit: Some(self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)), + ..Default::default() + }) + .await?; + + if fcu_result.payload_status.is_invalid() { + return Err(eyre::eyre!("Forkchoice update failed: {fcu_result:?}")); + } + + let payload_id = fcu_result + .payload_id + .ok_or_else(|| eyre::eyre!("Forkchoice update did not return a payload ID"))?; + + // wait for the block to be built for the specified chain block time + tokio::time::sleep( + Duration::from_millis(self.args.chain_block_time).max(Self::MIN_BLOCK_TIME), + ) + .await; + + let payload = + OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?); + let OpExecutionPayloadEnvelope::V4(payload) = payload else { + return Err(eyre::eyre!("Expected V4 payload, got something else")); + }; + let payload = payload.execution_payload; + + if self + .engine_api + .new_payload(payload.clone(), vec![], B256::ZERO, Requests::default()) + .await? + .status + != PayloadStatusEnum::Valid + { + return Err(eyre::eyre!("Invalid validation status from builder")); + } + + let new_block_hash = payload.payload_inner.payload_inner.payload_inner.block_hash; + self.engine_api + .update_forkchoice(latest.header.hash, new_block_hash, None) + .await?; + + let block = self + .provider + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .full() + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest block after building new block"))?; + + assert_eq!( + block.header.hash, new_block_hash, + "New block hash does not match expected hash" + ); + + for validation_node in &self.validation_nodes { + // optionally for each external validation node, ensure + // that they consider the block valid before returning it + // to the test author. + validation_node.post_block(&payload).await?; + } + + Ok(block) + } + + /// Retreives the latest built block and returns only a list of transaction + /// hashes from its body. + pub async fn latest(&self) -> eyre::Result> { + self.provider + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest block")) + } + + /// Retreives the latest built block and returns a list of full transaction + /// contents in its body. + pub async fn latest_full(&self) -> eyre::Result> { + self.provider + .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .full() + .await? + .ok_or_else(|| eyre::eyre!("Failed to get latest full block")) + } + + /// retreives a specific block by its number or tag and returns a list of transaction + /// hashes from its body. + pub async fn get_block( + &self, + number: BlockNumberOrTag, + ) -> eyre::Result>> { + Ok(self.provider.get_block_by_number(number).await?) + } + + /// retreives a specific block by its number or tag and returns a list of full transaction + /// contents in its body. + pub async fn get_block_full( + &self, + number: BlockNumberOrTag, + ) -> eyre::Result>> { + Ok(self.provider.get_block_by_number(number).full().await?) + } + + /// Returns a transaction builder that can be used to create and send transactions. + pub fn create_transaction(&self) -> TransactionBuilder { + TransactionBuilder::new(self.provider.clone()) + } + + /// Returns a reference to the underlying alloy provider that is used to + /// interact with the chain. + pub const fn provider(&self) -> &RootProvider { + &self.provider + } +} + +// internal methods +impl ChainDriver { + async fn fcu(&self, attribs: OpPayloadAttributes) -> eyre::Result { + let latest = self.latest().await?.header.hash; + let response = self + .engine_api + .update_forkchoice(latest, latest, Some(attribs)) + .await?; + + Ok(response) + } +} + +// L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) +// +// https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1 +const FJORD_DATA: &[u8] = &hex!( + "440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a + 3000000000000000000000000000000000000000000000000000000003ef12787000000 + 00000000000000000000000000000000000000000000000000000000012fdf87b89884a + 61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a5900000000000000000000 + 00006887246668a3b87f54deb3b94ba47a6f63f32985" +); diff --git a/crates/builder/op-rbuilder/src/tests/framework/external.rs b/crates/builder/op-rbuilder/src/tests/framework/external.rs new file mode 100644 index 00000000..e67fcfcc --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/external.rs @@ -0,0 +1,528 @@ +use alloy_consensus::constants::EMPTY_WITHDRAWALS; +use alloy_eips::{eip7685::Requests, BlockNumberOrTag, Encodable2718}; +use alloy_primitives::{keccak256, private::alloy_rlp::Encodable, B256, U256}; +use alloy_provider::{Identity, Provider, ProviderBuilder, RootProvider}; +use alloy_rpc_types_engine::{ + ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadStatusEnum, +}; +use bollard::{ + exec::{CreateExecOptions, StartExecResults}, + query_parameters::{ + AttachContainerOptions, CreateContainerOptions, CreateImageOptions, RemoveContainerOptions, + StartContainerOptions, StopContainerOptions, + }, + secret::{ContainerCreateBody, ContainerCreateResponse, HostConfig}, + Docker, +}; +use futures::{StreamExt, TryStreamExt}; +use op_alloy_network::Optimism; +use op_alloy_rpc_types_engine::OpExecutionPayloadV4; +use std::path::{Path, PathBuf}; +use tokio::signal; +use tracing::{debug, warn}; + +use crate::tests::{EngineApi, Ipc}; + +const AUTH_CONTAINER_IPC_PATH: &str = "/home/op-reth-shared/auth.ipc"; +const RPC_CONTAINER_IPC_PATH: &str = "/home/op-reth-shared/rpc.ipc"; + +/// This type represents an Optimism execution client node that is running inside a +/// docker container. This node is used to validate the correctness of the blocks built +/// by op-rbuilder. +/// +/// When this node is attached to a `ChainDriver`, it will automatically catch up with the +/// provided chain and will transparently ingest all newly built blocks by the driver. +/// +/// If the built payload fails to validate, then the driver block production function will +/// return an error during `ChainDriver::build_new_block`. +pub struct ExternalNode { + engine_api: EngineApi, + provider: RootProvider, + docker: Docker, + tempdir: PathBuf, + container_id: String, +} + +impl ExternalNode { + /// Creates a new instance of `ExternalNode` that runs the `op-reth` client in a Docker container + /// using the specified version tag. + pub async fn reth_version(version_tag: &str) -> eyre::Result { + let docker = Docker::connect_with_local_defaults()?; + + let tempdir = std::env::var("TESTS_TEMP_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::temp_dir()); + + let tempdir = tempdir.join(format!("reth-shared-{}", nanoid::nanoid!())); + let auth_ipc = tempdir.join("auth.ipc").to_string_lossy().to_string(); + let rpc_ipc = tempdir.join("rpc.ipc").to_string_lossy().to_string(); + + std::fs::create_dir_all(&tempdir) + .map_err(|_| eyre::eyre!("Failed to create temporary directory"))?; + + std::fs::write( + tempdir.join("genesis.json"), + include_str!("./artifacts/genesis.json.tmpl"), + ) + .map_err(|_| eyre::eyre!("Failed to write genesis file"))?; + + // Create Docker container with reth EL client + let container = create_container(&tempdir, &docker, version_tag).await?; + + docker + .start_container(&container.id, None::) + .await?; + + // Wait for the container to be ready and IPCs to be created + await_ipc_readiness(&docker, &container.id).await?; + + // IPC files created by the container have restrictive permissions, + // so we need to relax them to allow the host to access them. + relax_permissions(&docker, &container.id, AUTH_CONTAINER_IPC_PATH).await?; + relax_permissions(&docker, &container.id, RPC_CONTAINER_IPC_PATH).await?; + + // Connect to the IPCs + let engine_api = EngineApi::with_ipc(&auth_ipc); + let provider = ProviderBuilder::::default() + .connect_ipc(rpc_ipc.into()) + .await?; + + // spin up a task that will clean up the container on ctrl-c + tokio::spawn({ + let docker = docker.clone(); + let container_id = container.id.clone(); + let tempdir = tempdir.clone(); + + async move { + if signal::ctrl_c().await.is_ok() { + cleanup(tempdir.clone(), docker.clone(), container_id.clone()).await; + } + } + }); + + Ok(Self { + engine_api, + provider, + docker, + tempdir, + container_id: container.id, + }) + } + + /// Creates a new instance of `ExternalNode` that runs the `op-reth` client in a Docker container + /// using the latest version. + pub async fn reth() -> eyre::Result { + Self::reth_version("latest").await + } +} + +impl ExternalNode { + /// Access to the RPC API of the validation node. + pub fn provider(&self) -> &RootProvider { + &self.provider + } + + /// Access to the Engine API of the validation node. + pub fn engine_api(&self) -> &EngineApi { + &self.engine_api + } +} + +impl ExternalNode { + /// Catches up this node with another node. + /// + /// This method will fail if this node is ahead of the provided chain or they do not + /// share the same genesis block. + pub async fn catch_up_with(&self, chain: &RootProvider) -> eyre::Result<()> { + // check if we need to catch up + let (latest_hash, latest_number) = chain.latest_block_hash_and_number().await?; + let (our_latest_hash, our_latest_number) = + self.provider.latest_block_hash_and_number().await?; + + // check if we can sync in the first place + match (our_latest_number, latest_number) { + (we, them) if we == them && our_latest_hash == latest_hash => { + // we are already caught up and in sync with the provided chain + return Ok(()); + } + (we, them) if we == them && our_latest_hash != latest_hash => { + // divergent histories, can't sync + return Err(eyre::eyre!( + "External node is at the same height but has a different latest block hash: \ + {we} == {them}, {our_latest_hash} != {latest_hash}", + )); + } + (we, them) if we > them => { + return Err(eyre::eyre!( + "External node is ahead of the provided chain: {we} > {them}", + )); + } + (we, them) if we < them => { + debug!("external node is behind the local chain: {we} < {them}, catching up..."); + + // make sure that we share common history with the provided chain + let hash_at_height = chain.hash_at_height(we).await?; + if hash_at_height != our_latest_hash { + return Err(eyre::eyre!( + "External node does not share the same genesis block or history with \ + the provided chain: {} != {} at height {}", + hash_at_height, + our_latest_hash, + we + )); + } + } + _ => {} + }; + + // we are behind, let's catch up + let mut our_current_height = our_latest_number + 1; + + while our_current_height <= latest_number { + let payload = chain + .execution_payload_for_block(our_current_height) + .await?; + + let (latest_hash, _) = self.provider().latest_block_hash_and_number().await?; + + let status = self + .engine_api() + .new_payload(payload, vec![], B256::ZERO, Requests::default()) + .await?; + + if status.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!( + "Failed to import block at height {our_current_height} into external validation node: {:?}", + status.status + )); + } + + let new_chain_hash = status.latest_valid_hash.unwrap_or_default(); + self.engine_api() + .update_forkchoice(latest_hash, new_chain_hash, None) + .await?; + + our_current_height += 1; + } + + // sync complete, double check that we are in sync + let (final_hash, final_number) = self.provider().latest_block_hash_and_number().await?; + + if final_hash != latest_hash || final_number != latest_number { + return Err(eyre::eyre!( + "Failed to sync external validation node: {:?} != {:?}, {:?} != {:?}", + final_hash, + latest_hash, + final_number, + latest_number + )); + } + + Ok(()) + } + + /// Posts a block to the external validation node for validation and sets it as the latest block + /// in the fork choice. + pub async fn post_block(&self, payload: &OpExecutionPayloadV4) -> eyre::Result<()> { + let result = self + .engine_api + .new_payload(payload.clone(), vec![], B256::ZERO, Requests::default()) + .await?; + + let new_block_hash = payload.payload_inner.payload_inner.payload_inner.block_hash; + debug!( + "external validation node payload status for block {new_block_hash}: {:?}", + result.status + ); + + if result.status != PayloadStatusEnum::Valid { + return Err(eyre::eyre!( + "Failed to validate block {new_block_hash} with external validation node." + )); + } + + let (latest_hash, _) = self.provider.latest_block_hash_and_number().await?; + + self.engine_api + .update_forkchoice(latest_hash, new_block_hash, None) + .await?; + + Ok(()) + } +} + +impl Drop for ExternalNode { + fn drop(&mut self) { + // Block on cleaning up the container + let docker = self.docker.clone(); + let container_id = self.container_id.clone(); + let tempdir = self.tempdir.clone(); + tokio::spawn(async move { + cleanup(tempdir, docker, container_id).await; + }); + } +} + +async fn create_container( + tempdir: &Path, + docker: &Docker, + version_tag: &str, +) -> eyre::Result { + let host_config = HostConfig { + binds: Some(vec![format!( + "{}:/home/op-reth-shared:rw", + tempdir.display() + )]), + ..Default::default() + }; + + // first pull the image locally + let mut pull_stream = docker.create_image( + Some(CreateImageOptions { + from_image: Some("ghcr.io/paradigmxyz/op-reth".to_string()), + tag: Some(version_tag.into()), + ..Default::default() + }), + None, + None, + ); + + while let Some(pull_result) = pull_stream.try_next().await? { + debug!( + "Pulling 'ghcr.io/paradigmxyz/op-reth:{version_tag}' locally: {:?}", + pull_result + ); + } + + // Don't expose any ports, as we will only use IPC for communication. + let container_config = ContainerCreateBody { + image: Some(format!("ghcr.io/paradigmxyz/op-reth:{version_tag}")), + entrypoint: Some(vec!["op-reth".to_string()]), + cmd: Some( + vec![ + "node", + "--chain=/home/op-reth-shared/genesis.json", + "--auth-ipc", + &format!("--auth-ipc.path={AUTH_CONTAINER_IPC_PATH}"), + &format!("--ipcpath={RPC_CONTAINER_IPC_PATH}"), + "--disable-discovery", + "--no-persist-peers", + "--max-outbound-peers=0", + "--max-inbound-peers=0", + "--trusted-only", + ] + .into_iter() + .map(String::from) + .collect(), + ), + host_config: Some(host_config), + ..Default::default() + }; + + Ok(docker + .create_container(Some(CreateContainerOptions::default()), container_config) + .await?) +} + +async fn relax_permissions(docker: &Docker, container: &str, path: &str) -> eyre::Result<()> { + let exec = docker + .create_exec( + container, + CreateExecOptions { + cmd: Some(vec!["chmod", "777", path]), + attach_stdout: Some(true), + attach_stderr: Some(true), + ..Default::default() + }, + ) + .await?; + + let StartExecResults::Attached { mut output, .. } = docker.start_exec(&exec.id, None).await? + else { + return Err(eyre::eyre!("Failed to start exec for relaxing permissions")); + }; + + while let Some(Ok(output)) = output.next().await { + use bollard::container::LogOutput::*; + match output { + StdErr { message } => { + return Err(eyre::eyre!( + "Failed to relax permissions for {path}: {}", + String::from_utf8_lossy(&message) + )) + } + _ => continue, + }; + } + + Ok(()) +} + +async fn await_ipc_readiness(docker: &Docker, container: &str) -> eyre::Result<()> { + let mut attach_stream = docker + .attach_container( + container, + Some(AttachContainerOptions { + stdout: true, + stderr: true, + stream: true, + logs: true, + ..Default::default() + }), + ) + .await?; + + let mut rpc_ipc_started = false; + let mut auth_ipc_started = false; + + // wait for the node to start and signal that IPCs are ready + while let Some(Ok(output)) = attach_stream.output.next().await { + use bollard::container::LogOutput; + match output { + LogOutput::StdOut { message } | LogOutput::StdErr { message } => { + let message = String::from_utf8_lossy(&message); + if message.contains(AUTH_CONTAINER_IPC_PATH) { + auth_ipc_started = true; + } + + if message.contains(RPC_CONTAINER_IPC_PATH) { + rpc_ipc_started = true; + } + + if message.to_lowercase().contains("error") { + return Err(eyre::eyre!("Failed to start op-reth container: {message}.")); + } + } + LogOutput::StdIn { .. } | LogOutput::Console { .. } => {} + } + + if auth_ipc_started && rpc_ipc_started { + break; + } + } + + if !auth_ipc_started || !rpc_ipc_started { + return Err(eyre::eyre!( + "Failed to start op-reth container: IPCs not ready" + )); + } + + Ok(()) +} + +async fn cleanup(tempdir: PathBuf, docker: Docker, container_id: String) { + // This is a no-op function that will be spawned to clean up the container on ctrl-c + // or Drop. + debug!( + "Cleaning up external node resources at {} [{container_id}]...", + tempdir.display() + ); + + if !tempdir.exists() { + return; // If the tempdir does not exist, there's nothing to clean up. + } + + // Block on cleaning up the container + if let Err(e) = docker + .stop_container(&container_id, None::) + .await + { + warn!("Failed to stop container {}: {}", container_id, e); + } + + if let Err(e) = docker + .remove_container( + &container_id, + Some(RemoveContainerOptions { + force: true, + ..Default::default() + }), + ) + .await + { + warn!("Failed to remove container {}: {}", container_id, e); + } + + // Clean up the temporary directory + std::fs::remove_dir_all(&tempdir).expect("Failed to remove temporary directory"); +} + +trait OptimismProviderExt { + async fn hash_at_height(&self, height: u64) -> eyre::Result; + async fn latest_block_hash_and_number(&self) -> eyre::Result<(B256, u64)>; + async fn execution_payload_for_block(&self, number: u64) -> eyre::Result; +} + +impl OptimismProviderExt for RootProvider { + async fn hash_at_height(&self, height: u64) -> eyre::Result { + let block = self + .get_block_by_number(BlockNumberOrTag::Number(height)) + .await? + .ok_or_else(|| eyre::eyre!("No block found at height {}", height))?; + Ok(block.header.hash) + } + + async fn latest_block_hash_and_number(&self) -> eyre::Result<(B256, u64)> { + let block = self + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .ok_or_else(|| eyre::eyre!("No latest block found"))?; + Ok((block.header.hash, block.header.number)) + } + + async fn execution_payload_for_block(&self, number: u64) -> eyre::Result { + let block = self + .get_block_by_number(BlockNumberOrTag::Number(number)) + .full() + .await? + .ok_or_else(|| eyre::eyre!("No block found at height {}", number))?; + + let withdrawals = block.withdrawals.clone().unwrap_or_default(); + + // Calculate the withdrawals root properly + let withdrawals_root = if withdrawals.is_empty() { + EMPTY_WITHDRAWALS + } else { + // Calculate the Merkle Patricia Trie root of the withdrawals + let mut buf = Vec::new(); + withdrawals.encode(&mut buf); + keccak256(&buf) + }; + + let payload = OpExecutionPayloadV4 { + payload_inner: ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: block.header.parent_hash, + fee_recipient: block.header.beneficiary, + state_root: block.header.state_root, + receipts_root: block.header.receipts_root, + logs_bloom: block.header.logs_bloom, + prev_randao: block.header.mix_hash, + block_number: block.header.number, + gas_limit: block.header.gas_limit, + gas_used: block.header.gas_used, + timestamp: block.header.timestamp, + extra_data: block.header.extra_data.clone(), + base_fee_per_gas: U256::from( + block.header.base_fee_per_gas.unwrap_or_default(), + ), + block_hash: block.header.hash, + transactions: block + .transactions + .into_transactions_vec() + .into_iter() + .map(|tx| tx.as_ref().encoded_2718().into()) + .collect(), + }, + withdrawals: block.withdrawals.unwrap_or_default().to_vec(), + }, + blob_gas_used: block.header.inner.blob_gas_used.unwrap_or_default(), + excess_blob_gas: block.header.inner.excess_blob_gas.unwrap_or_default(), + }, + withdrawals_root, + }; + + Ok(payload) + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/harness.rs b/crates/builder/op-rbuilder/src/tests/framework/harness.rs deleted file mode 100644 index cd5ee0ee..00000000 --- a/crates/builder/op-rbuilder/src/tests/framework/harness.rs +++ /dev/null @@ -1,357 +0,0 @@ -use super::{ - apis::EngineApi, - blocks::BlockGenerator, - op::{OpRbuilderConfig, OpRethConfig}, - service::{self, Service, ServiceInstance}, - TransactionBuilder, BUILDER_PRIVATE_KEY, -}; -use alloy_eips::BlockNumberOrTag; -use alloy_network::Network; -use alloy_primitives::{hex, B256}; -use alloy_provider::{ - ext::TxPoolApi, Identity, PendingTransactionBuilder, Provider, ProviderBuilder, RootProvider, -}; -use op_alloy_network::Optimism; -use parking_lot::Mutex; -use std::{ - collections::HashSet, net::TcpListener, path::PathBuf, sync::LazyLock, time::SystemTime, -}; -use time::{format_description, OffsetDateTime}; -use uuid::Uuid; - -pub struct TestHarnessBuilder { - name: String, - use_revert_protection: bool, - flashblocks_port: Option, - chain_block_time: Option, - flashbots_block_time: Option, - namespaces: Option, - extra_params: Option, -} - -impl TestHarnessBuilder { - pub fn new(name: &str) -> Self { - Self { - name: name.to_string(), - use_revert_protection: false, - flashblocks_port: None, - chain_block_time: None, - flashbots_block_time: None, - namespaces: None, - extra_params: None, - } - } - - pub fn with_revert_protection(mut self) -> Self { - self.use_revert_protection = true; - self - } - - pub fn with_flashblocks_port(mut self, port: u16) -> Self { - self.flashblocks_port = Some(port); - self - } - - pub fn with_chain_block_time(mut self, block_time: u64) -> Self { - self.chain_block_time = Some(block_time); - self - } - - pub fn with_flashbots_block_time(mut self, block_time: u64) -> Self { - self.flashbots_block_time = Some(block_time); - self - } - - pub fn with_namespaces(mut self, namespaces: &str) -> Self { - self.namespaces = Some(namespaces.to_string()); - self - } - - pub fn with_extra_params(mut self, extra_params: &str) -> Self { - self.extra_params = Some(extra_params.to_string()); - self - } - - pub async fn build(self) -> eyre::Result { - let mut framework = IntegrationFramework::new(&self.name).unwrap(); - - // we are going to use the fixture genesis and copy it to each test folder - let genesis = include_str!("artifacts/genesis.json.tmpl"); - - let mut genesis_path = framework.test_dir.clone(); - genesis_path.push("genesis.json"); - std::fs::write(&genesis_path, genesis)?; - - // create the builder - let builder_data_dir: PathBuf = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let builder_auth_rpc_port = get_available_port(); - let builder_http_port = get_available_port(); - let mut op_rbuilder_config = OpRbuilderConfig::new() - .chain_config_path(genesis_path.clone()) - .data_dir(builder_data_dir) - .auth_rpc_port(builder_auth_rpc_port) - .network_port(get_available_port()) - .http_port(builder_http_port) - .with_builder_private_key(BUILDER_PRIVATE_KEY) - .with_revert_protection(self.use_revert_protection) - .with_namespaces(self.namespaces) - .with_extra_params(self.extra_params); - if let Some(flashblocks_port) = self.flashblocks_port { - op_rbuilder_config = op_rbuilder_config.with_flashblocks_port(flashblocks_port); - } - - if let Some(chain_block_time) = self.chain_block_time { - op_rbuilder_config = op_rbuilder_config.with_chain_block_time(chain_block_time); - } - - if let Some(flashbots_block_time) = self.flashbots_block_time { - op_rbuilder_config = op_rbuilder_config.with_flashbots_block_time(flashbots_block_time); - } - - // create the validation reth node - - let reth_data_dir = std::env::temp_dir().join(Uuid::new_v4().to_string()); - let validator_auth_rpc_port = get_available_port(); - let reth = OpRethConfig::new() - .chain_config_path(genesis_path) - .data_dir(reth_data_dir) - .auth_rpc_port(validator_auth_rpc_port) - .network_port(get_available_port()); - - framework.start("op-reth", &reth).await.unwrap(); - - let builder = framework - .start("op-rbuilder", &op_rbuilder_config) - .await - .unwrap(); - - let builder_log_path = builder.log_path.clone(); - - Ok(TestHarness { - framework: framework, - builder_auth_rpc_port, - builder_http_port, - validator_auth_rpc_port, - builder_log_path, - chain_block_time: self.chain_block_time, - }) - } -} - -pub struct TestHarness { - framework: IntegrationFramework, - builder_auth_rpc_port: u16, - builder_http_port: u16, - validator_auth_rpc_port: u16, - builder_log_path: PathBuf, - chain_block_time: Option, -} - -impl TestHarness { - pub async fn send_valid_transaction( - &self, - ) -> eyre::Result> { - self.create_transaction().send().await - } - - pub async fn send_revert_transaction( - &self, - ) -> eyre::Result> { - self.create_transaction() - .with_input(hex!("60006000fd").into()) // PUSH1 0x00 PUSH1 0x00 REVERT - .send() - .await - } - - pub fn provider(&self) -> eyre::Result> { - let url = format!("http://localhost:{}", self.builder_http_port); - let provider = - ProviderBuilder::::default().connect_http(url.parse()?); - - Ok(provider) - } - - pub async fn block_generator(&self) -> eyre::Result { - let engine_api = EngineApi::new_with_port(self.builder_auth_rpc_port).unwrap(); - let validation_api = Some(EngineApi::new_with_port(self.validator_auth_rpc_port).unwrap()); - - let mut generator = BlockGenerator::new( - engine_api, - validation_api, - false, - self.chain_block_time.map_or(1, |time| time / 1000), // in seconds - None, - ); - generator.init().await?; - - Ok(generator) - } - - pub fn create_transaction(&self) -> TransactionBuilder { - TransactionBuilder::new(self.provider().expect("provider not available")) - } - - pub async fn latest_block(&self) -> ::BlockResponse { - self.provider() - .expect("provider not available") - .get_block_by_number(BlockNumberOrTag::Latest) - .full() - .await - .expect("failed to get latest block by hash") - .expect("latest block should exist") - } - - pub async fn latest_base_fee(&self) -> u128 { - self.latest_block() - .await - .header - .base_fee_per_gas - .expect("Base fee per gas not found in the latest block header") as u128 - } - - pub const fn builder_private_key() -> &'static str { - BUILDER_PRIVATE_KEY - } - - pub async fn check_tx_in_pool(&self, tx_hash: B256) -> eyre::Result { - let pool_inspect = self - .provider() - .expect("provider not available") - .txpool_content() - .await?; - - let is_pending = pool_inspect.pending.iter().any(|pending_account_map| { - pending_account_map - .1 - .iter() - .any(|(_, tx)| tx.as_recovered().hash() == *tx_hash) - }); - if is_pending { - return Ok(TransactionStatus::Pending); - } - - let is_queued = pool_inspect.queued.iter().any(|queued_account_map| { - queued_account_map - .1 - .iter() - .any(|(_, tx)| tx.as_recovered().hash() == *tx_hash) - }); - if is_queued { - return Ok(TransactionStatus::Queued); - } - - // check that the builder emitted logs for the reverted transactions with the monitoring logic - // this will tell us whether the builder dropped the transaction - // TODO: this is not ideal, lets find a different way to detect this - // Each time a transaction is dropped, it emits a log like this - // Note that this does not tell us the reason why the transaction was dropped. Ideally - // we should know it at this point. - // 'Transaction event received target="monitoring" tx_hash="" kind="discarded"' - let builder_logs = std::fs::read_to_string(&self.builder_log_path)?; - let txn_log = format!( - "Transaction event received target=\"monitoring\" tx_hash=\"{}\" kind=\"discarded\"", - tx_hash, - ); - if builder_logs.contains(txn_log.as_str()) { - return Ok(TransactionStatus::Dropped); - } - - Ok(TransactionStatus::NotFound) - } -} - -impl Drop for TestHarness { - fn drop(&mut self) { - for service in &mut self.framework.services { - let res = service.stop(); - if let Err(e) = res { - println!("Failed to stop service: {}", e); - } - } - } -} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TransactionStatus { - NotFound, - Pending, - Queued, - Dropped, -} - -impl TransactionStatus { - pub fn is_pending(&self) -> bool { - matches!(self, TransactionStatus::Pending) - } - - pub fn is_queued(&self) -> bool { - matches!(self, TransactionStatus::Queued) - } - - pub fn is_dropped(&self) -> bool { - matches!(self, TransactionStatus::Dropped) - } -} - -pub fn get_available_port() -> u16 { - static CLAIMED_PORTS: LazyLock>> = - LazyLock::new(|| Mutex::new(HashSet::new())); - loop { - let port: u16 = rand::random_range(1000..20000); - if TcpListener::bind(("127.0.0.1", port)).is_ok() && CLAIMED_PORTS.lock().insert(port) { - return port; - } - } -} - -#[derive(Debug)] -pub enum IntegrationError { - SpawnError, - BinaryNotFound, - SetupError, - LogError, - ServiceAlreadyRunning, -} - -struct IntegrationFramework { - test_dir: PathBuf, - services: Vec, -} - -impl IntegrationFramework { - pub fn new(test_name: &str) -> Result { - let dt: OffsetDateTime = SystemTime::now().into(); - let format = format_description::parse("[year]_[month]_[day]_[hour]_[minute]_[second]") - .map_err(|_| IntegrationError::SetupError)?; - - let date_format = dt - .format(&format) - .map_err(|_| IntegrationError::SetupError)?; - - let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - test_dir.push("../../integration_logs"); - test_dir.push(format!("{date_format}_{test_name}")); - - std::fs::create_dir_all(&test_dir).map_err(|_| IntegrationError::SetupError)?; - - Ok(Self { - test_dir, - services: Vec::new(), - }) - } - - pub async fn start( - &mut self, - name: &str, - config: &T, - ) -> Result<&mut ServiceInstance, service::Error> { - let service = self.create_service(name)?; - service.start_with_config(config).await?; - Ok(service) - } - - pub fn create_service(&mut self, name: &str) -> Result<&mut ServiceInstance, service::Error> { - let service = ServiceInstance::new(name.to_string(), self.test_dir.clone()); - self.services.push(service); - Ok(self.services.last_mut().unwrap()) - } -} diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs new file mode 100644 index 00000000..7c482c93 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -0,0 +1,355 @@ +use crate::{ + args::OpRbuilderArgs, + builders::{BuilderConfig, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, + primitives::reth::engine_api_builder::OpEngineApiBuilder, + revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}, + tests::{ + framework::{driver::ChainDriver, BUILDER_PRIVATE_KEY}, + ChainDriverExt, EngineApi, Ipc, TransactionPoolObserver, + }, + tx::FBPooledTransaction, + tx_signer::Signer, +}; +use alloy_provider::{Identity, ProviderBuilder, RootProvider}; +use clap::Parser; +use core::{ + any::Any, + future::Future, + net::Ipv4Addr, + pin::Pin, + task::{Context, Poll}, + time::Duration, +}; +use futures::FutureExt; +use moka::future::Cache; +use nanoid::nanoid; +use op_alloy_network::Optimism; +use reth::{ + args::{DatadirArgs, NetworkArgs, RpcServerArgs}, + core::exit::NodeExitFuture, + tasks::TaskManager, +}; +use reth_node_builder::{NodeBuilder, NodeConfig}; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_cli::commands::Commands; +use reth_optimism_node::{ + node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, + OpNode, +}; +use reth_transaction_pool::{AllTransactionsEvents, TransactionPool}; +use std::sync::{Arc, LazyLock}; +use tokio::sync::oneshot; + +/// Represents a type that emulates a local in-process instance of the OP builder node. +/// This node uses IPC as the communication channel for the RPC server Engine API. +pub struct LocalInstance { + signer: Signer, + config: NodeConfig, + args: OpRbuilderArgs, + task_manager: Option, + exit_future: NodeExitFuture, + _node_handle: Box, + pool_observer: TransactionPoolObserver, +} + +impl LocalInstance { + /// Creates a new local instance of the OP builder node with the given arguments, + /// with the default Reth node configuration. + /// + /// 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 { + Self::new_with_config::

(args, default_node_config()).await + } + + /// Creates a new local instance of the OP builder node with the given arguments, + /// with a given Reth node configuration. + /// + /// 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( + args: OpRbuilderArgs, + config: NodeConfig, + ) -> eyre::Result { + let mut args = args; + let task_manager = task_manager(); + let op_node = OpNode::new(args.rollup_args.clone()); + let reverted_cache = Cache::builder().max_capacity(100).build(); + let reverted_cache_clone = reverted_cache.clone(); + + let (rpc_ready_tx, rpc_ready_rx) = oneshot::channel::<()>(); + let (txpool_ready_tx, txpool_ready_rx) = + oneshot::channel::>(); + + let signer = args.builder_signer.unwrap_or_else(|| { + Signer::try_from_secret( + BUILDER_PRIVATE_KEY + .parse() + .expect("Invalid builder private key"), + ) + .expect("Failed to create signer from private key") + }); + args.builder_signer = Some(signer); + args.rollup_args.enable_tx_conditional = true; + + 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 addons: OpAddOns< + _, + _, + OpEngineValidatorBuilder, + OpEngineApiBuilder, + > = OpAddOnsBuilder::default() + .with_sequencer(args.rollup_args.sequencer.clone()) + .with_enable_tx_conditional(args.rollup_args.enable_tx_conditional) + .with_da_config(da_config) + .build(); + + let node_builder = NodeBuilder::<_, OpChainSpec>::new(config.clone()) + .testing_node(task_manager.executor()) + .with_types::() + .with_components( + op_node + .components() + .pool(pool_component(&args)) + .payload(P::new_service(builder_config)?), + ) + .with_add_ons(addons) + .extend_rpc_modules(move |ctx| { + if args.enable_revert_protection { + tracing::info!("Revert protection enabled"); + + let pool = ctx.pool().clone(); + let provider = ctx.provider().clone(); + let revert_protection_ext: RevertProtectionExt< + _, + _, + _, + op_alloy_network::Optimism, + > = RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); + + ctx.modules + .merge_configured(revert_protection_ext.bundle_api().into_rpc())?; + + ctx.modules.replace_configured( + revert_protection_ext.eth_api(reverted_cache).into_rpc(), + )?; + } + + Ok(()) + }) + .on_rpc_started(move |_, _| { + let _ = rpc_ready_tx.send(()); + Ok(()) + }) + .on_node_started(move |ctx| { + txpool_ready_tx + .send(ctx.pool.all_transactions_event_listener()) + .expect("Failed to send txpool ready signal"); + + Ok(()) + }); + + let node_handle = node_builder.launch().await?; + let exit_future = node_handle.node_exit_future; + let boxed_handle = Box::new(node_handle.node); + let node_handle: Box = boxed_handle; + + // Wait for all required components to be ready + rpc_ready_rx.await.expect("Failed to receive ready signal"); + let pool_monitor = txpool_ready_rx + .await + .expect("Failed to receive txpool ready signal"); + + Ok(Self { + args, + signer, + config, + exit_future, + _node_handle: node_handle, + task_manager: Some(task_manager), + pool_observer: TransactionPoolObserver::new(pool_monitor, reverted_cache_clone), + }) + } + + /// 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 { + let args = crate::args::Cli::parse_from(["dummy", "node"]); + let Commands::Node(ref node_command) = args.command else { + unreachable!() + }; + let instance = Self::new::(node_command.ext.clone()).await?; + let driver = ChainDriver::::local(&instance).await?; + driver.fund_default_accounts().await?; + Ok(instance) + } + + /// 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 { + 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 + let instance = Self::new::(node_command.ext.clone()).await?; + let driver = ChainDriver::::local(&instance).await?; + driver.fund_default_accounts().await?; + Ok(instance) + } + + pub const fn config(&self) -> &NodeConfig { + &self.config + } + + pub const fn args(&self) -> &OpRbuilderArgs { + &self.args + } + + pub const fn signer(&self) -> &Signer { + &self.signer + } + + pub fn flashblocks_ws_url(&self) -> String { + let ipaddr: Ipv4Addr = self + .args + .flashblocks + .flashblocks_addr + .parse() + .expect("Failed to parse flashblocks IP address"); + + let ipaddr = if ipaddr.is_unspecified() { + Ipv4Addr::LOCALHOST + } else { + ipaddr + }; + + let port = self.args.flashblocks.flashblocks_port; + + format!("ws://{ipaddr}:{port}/") + } + + pub fn rpc_ipc(&self) -> &str { + &self.config.rpc.ipcpath + } + + pub fn auth_ipc(&self) -> &str { + &self.config.rpc.auth_ipc_path + } + + pub fn engine_api(&self) -> EngineApi { + EngineApi::::with_ipc(self.auth_ipc()) + } + + pub const fn pool(&self) -> &TransactionPoolObserver { + &self.pool_observer + } + + pub async fn driver(&self) -> eyre::Result> { + ChainDriver::::local(self).await + } + + pub async fn provider(&self) -> eyre::Result> { + ProviderBuilder::::default() + .connect_ipc(self.rpc_ipc().to_string().into()) + .await + .map_err(|e| eyre::eyre!("Failed to connect to provider: {e}")) + } +} + +impl Drop for LocalInstance { + fn drop(&mut self) { + if let Some(task_manager) = self.task_manager.take() { + task_manager.graceful_shutdown_with_timeout(Duration::from_secs(3)); + std::fs::remove_dir_all(self.config().datadir().to_string()).unwrap_or_else(|e| { + panic!( + "Failed to remove temporary data directory {}: {e}", + self.config().datadir() + ) + }); + } + } +} + +impl Future for LocalInstance { + type Output = eyre::Result<()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.get_mut().exit_future.poll_unpin(cx) + } +} + +pub fn default_node_config() -> NodeConfig { + let tempdir = std::env::temp_dir(); + let random_id = nanoid!(); + + let data_path = tempdir + .join(format!("rbuilder.{random_id}.datadir")) + .to_path_buf(); + + std::fs::create_dir_all(&data_path).expect("Failed to create temporary data directory"); + + let rpc_ipc_path = tempdir + .join(format!("rbuilder.{random_id}.rpc-ipc")) + .to_path_buf(); + + let auth_ipc_path = tempdir + .join(format!("rbuilder.{random_id}.auth-ipc")) + .to_path_buf(); + + let mut rpc = RpcServerArgs::default().with_auth_ipc(); + rpc.ws = false; + rpc.http = false; + rpc.auth_port = 0; + rpc.ipcpath = rpc_ipc_path.to_string_lossy().into(); + rpc.auth_ipc_path = auth_ipc_path.to_string_lossy().into(); + + let mut network = NetworkArgs::default().with_unused_ports(); + network.discovery.disable_discovery = true; + + let datadir = DatadirArgs { + datadir: data_path + .to_string_lossy() + .parse() + .expect("Failed to parse data dir path"), + static_files_path: None, + }; + + NodeConfig::::new(chain_spec()) + .with_datadir_args(datadir) + .with_rpc(rpc) + .with_network(network) +} + +fn chain_spec() -> Arc { + static CHAIN_SPEC: LazyLock> = LazyLock::new(|| { + let genesis = include_str!("./artifacts/genesis.json.tmpl"); + let genesis = serde_json::from_str(genesis).expect("invalid genesis JSON"); + let chain_spec = OpChainSpec::from_genesis(genesis); + Arc::new(chain_spec) + }); + + CHAIN_SPEC.clone() +} + +fn task_manager() -> TaskManager { + TaskManager::new(tokio::runtime::Handle::current()) +} + +fn pool_component(args: &OpRbuilderArgs) -> OpPoolBuilder { + let rollup_args = &args.rollup_args; + OpPoolBuilder::::default() + .with_enable_tx_conditional( + // Revert protection uses the same internal pool logic as conditional transactions + // to garbage collect transactions out of the bundle range. + rollup_args.enable_tx_conditional || args.enable_revert_protection, + ) + .with_supervisor( + rollup_args.supervisor_http.clone(), + rollup_args.supervisor_safety_level, + ) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs index d8ffff3c..e8488d99 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/mod.rs @@ -1,16 +1,16 @@ mod apis; -mod blocks; -mod harness; -mod op; -mod service; +mod driver; +mod external; +mod instance; mod txs; +mod utils; pub use apis::*; -pub use blocks::*; -pub use harness::*; -pub use op::*; -pub use service::*; +pub use driver::*; +pub use external::*; +pub use instance::*; pub use txs::*; +pub use utils::*; const BUILDER_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; @@ -22,3 +22,34 @@ pub const DEFAULT_JWT_TOKEN: &str = "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; pub const ONE_ETH: u128 = 1_000_000_000_000_000_000; + +/// This gets invoked before any tests, when the cargo test framework loads the test library. +/// It injects itself into +#[ctor::ctor] +fn init_tests_logging() { + use tracing_subscriber::{filter::filter_fn, prelude::*}; + if let Ok(v) = std::env::var("TEST_TRACE") { + let level = match v.as_str() { + "false" | "off" => return, + "true" | "debug" | "on" => tracing::Level::DEBUG, + "trace" => tracing::Level::TRACE, + "info" => tracing::Level::INFO, + "warn" => tracing::Level::WARN, + "error" => tracing::Level::ERROR, + _ => return, + }; + + // let prefix_blacklist = &["alloy_transport_ipc", "storage::db::mdbx"]; + let prefix_blacklist = &["storage::db::mdbx"]; + + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with(filter_fn(move |metadata| { + metadata.level() <= &level + && !prefix_blacklist + .iter() + .any(|prefix| metadata.target().starts_with(prefix)) + })) + .init(); + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/op.rs b/crates/builder/op-rbuilder/src/tests/framework/op.rs deleted file mode 100644 index f007b33d..00000000 --- a/crates/builder/op-rbuilder/src/tests/framework/op.rs +++ /dev/null @@ -1,325 +0,0 @@ -use std::{ - fs::File, - future::Future, - io::{ErrorKind, Read}, - path::{Path, PathBuf}, - process::Command, -}; - -use std::time::Duration; -use tokio::time::sleep; - -use super::{ - service::{self, Service}, - DEFAULT_JWT_TOKEN, -}; - -#[derive(Default, Debug)] -pub struct OpRbuilderConfig { - auth_rpc_port: Option, - jwt_secret_path: Option, - chain_config_path: Option, - data_dir: Option, - http_port: Option, - network_port: Option, - builder_private_key: Option, - flashblocks_port: Option, - chain_block_time: Option, - flashbots_block_time: Option, - with_revert_protection: Option, - namespaces: Option, - extra_params: Option, -} - -impl OpRbuilderConfig { - pub fn new() -> Self { - Self::default() - } - - pub fn auth_rpc_port(mut self, port: u16) -> Self { - self.auth_rpc_port = Some(port); - self - } - - pub fn chain_config_path>(mut self, path: P) -> Self { - self.chain_config_path = Some(path.into()); - self - } - - pub fn data_dir>(mut self, path: P) -> Self { - self.data_dir = Some(path.into()); - self - } - - pub fn network_port(mut self, port: u16) -> Self { - self.network_port = Some(port); - self - } - - pub fn http_port(mut self, port: u16) -> Self { - self.http_port = Some(port); - self - } - - pub fn with_builder_private_key(mut self, private_key: &str) -> Self { - self.builder_private_key = Some(private_key.to_string()); - self - } - - pub fn with_revert_protection(mut self, revert_protection: bool) -> Self { - self.with_revert_protection = Some(revert_protection); - self - } - - pub fn with_flashblocks_port(mut self, port: u16) -> Self { - self.flashblocks_port = Some(port); - self - } - - pub fn with_chain_block_time(mut self, time: u64) -> Self { - self.chain_block_time = Some(time); - self - } - - pub fn with_flashbots_block_time(mut self, time: u64) -> Self { - self.flashbots_block_time = Some(time); - self - } - - pub fn with_namespaces(mut self, namespaces: Option) -> Self { - self.namespaces = namespaces; - self - } - - pub fn with_extra_params(mut self, extra_params: Option) -> Self { - self.extra_params = extra_params; - self - } -} - -impl Service for OpRbuilderConfig { - fn command(&self) -> Command { - let mut bin_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - bin_path.push("../../target/debug/op-rbuilder"); - - let mut cmd = Command::new(bin_path); - let jwt_path = get_or_create_jwt_path(self.jwt_secret_path.as_ref()); - - cmd.arg("node") - .arg("--authrpc.port") - .arg( - self.auth_rpc_port - .expect("auth_rpc_port not set") - .to_string(), - ) - .arg("--authrpc.jwtsecret") - .arg( - jwt_path - .to_str() - .expect("Failed to convert jwt_path to string"), - ) - .arg("--chain") - .arg( - self.chain_config_path - .as_ref() - .expect("chain_config_path not set"), - ) - .arg("--datadir") - .arg(self.data_dir.as_ref().expect("data_dir not set")) - .arg("--disable-discovery") - .arg("--color") - .arg("never") - .arg("--builder.log-pool-transactions") - .arg("--port") - .arg(self.network_port.expect("network_port not set").to_string()) - .arg("--ipcdisable") - .arg("-vvvv"); - - if let Some(revert_protection) = self.with_revert_protection { - if revert_protection { - cmd.arg("--builder.enable-revert-protection"); - } - } - - if let Some(builder_private_key) = &self.builder_private_key { - cmd.arg("--rollup.builder-secret-key") - .arg(builder_private_key); - } - - if let Some(http_port) = self.http_port { - cmd.arg("--http") - .arg("--http.port") - .arg(http_port.to_string()); - } - - if let Some(flashblocks_port) = &self.flashblocks_port { - cmd.arg("--flashblocks.enabled"); - cmd.arg("--flashblocks.addr").arg("127.0.0.1"); - cmd.arg("--flashblocks.port") - .arg(flashblocks_port.to_string()); - } - - if let Some(chain_block_time) = self.chain_block_time { - cmd.arg("--rollup.chain-block-time") - .arg(chain_block_time.to_string()); - } - - if let Some(flashbots_block_time) = self.flashbots_block_time { - cmd.arg("--flashblocks.block-time") - .arg(flashbots_block_time.to_string()); - } - - if let Some(namespaces) = &self.namespaces { - cmd.arg("--http.api").arg(namespaces); - } - - if let Some(extra_params) = &self.extra_params { - cmd.args(extra_params.split_ascii_whitespace()); - } - - cmd - } - - #[allow(clippy::manual_async_fn)] - fn ready(&self, log_path: &Path) -> impl Future> + Send { - async move { - poll_logs( - log_path, - "Starting consensus engine", - Duration::from_millis(100), - Duration::from_secs(60), - ) - .await - } - } -} - -#[derive(Default, Debug)] -pub struct OpRethConfig { - auth_rpc_port: Option, - jwt_secret_path: Option, - chain_config_path: Option, - data_dir: Option, - http_port: Option, - network_port: Option, -} - -impl OpRethConfig { - pub fn new() -> Self { - Self::default() - } - - pub fn auth_rpc_port(mut self, port: u16) -> Self { - self.auth_rpc_port = Some(port); - self - } - - pub fn chain_config_path>(mut self, path: P) -> Self { - self.chain_config_path = Some(path.into()); - self - } - - pub fn data_dir>(mut self, path: P) -> Self { - self.data_dir = Some(path.into()); - self - } - - pub fn network_port(mut self, port: u16) -> Self { - self.network_port = Some(port); - self - } -} - -impl Service for OpRethConfig { - fn command(&self) -> Command { - let bin_path = PathBuf::from("op-reth"); - - let mut cmd = Command::new(bin_path); - let jwt_path = get_or_create_jwt_path(self.jwt_secret_path.as_ref()); - - cmd.arg("node") - .arg("--authrpc.port") - .arg( - self.auth_rpc_port - .expect("auth_rpc_port not set") - .to_string(), - ) - .arg("--authrpc.jwtsecret") - .arg( - jwt_path - .to_str() - .expect("Failed to convert jwt_path to string"), - ) - .arg("--chain") - .arg( - self.chain_config_path - .as_ref() - .expect("chain_config_path not set"), - ) - .arg("--datadir") - .arg(self.data_dir.as_ref().expect("data_dir not set")) - .arg("--disable-discovery") - .arg("--color") - .arg("never") - .arg("--port") - .arg(self.network_port.expect("network_port not set").to_string()) - .arg("--ipcdisable"); - - if let Some(http_port) = self.http_port { - cmd.arg("--http") - .arg("--http.port") - .arg(http_port.to_string()); - } - - cmd - } - - #[allow(clippy::manual_async_fn)] - fn ready(&self, log_path: &Path) -> impl Future> + Send { - async move { - poll_logs( - log_path, - "Starting consensus engine", - Duration::from_millis(100), - Duration::from_secs(60), - ) - .await - } - } -} - -fn get_or_create_jwt_path(jwt_path: Option<&PathBuf>) -> PathBuf { - jwt_path.cloned().unwrap_or_else(|| { - let tmp_dir = std::env::temp_dir(); - let jwt_path = tmp_dir.join("jwt.hex"); - std::fs::write(&jwt_path, DEFAULT_JWT_TOKEN).expect("Failed to write JWT secret file"); - jwt_path - }) -} - -/// Helper function to poll logs periodically -pub async fn poll_logs( - log_path: &Path, - pattern: &str, - interval: Duration, - timeout: Duration, -) -> Result<(), service::Error> { - let start = std::time::Instant::now(); - - loop { - if start.elapsed() > timeout { - return Err(service::Error::Spawn(ErrorKind::TimedOut)); - } - - let mut file = File::open(log_path).map_err(|_| service::Error::Logs)?; - let mut contents = String::new(); - file.read_to_string(&mut contents) - .map_err(|_| service::Error::Logs)?; - - if contents.contains(pattern) { - return Ok(()); - } - - sleep(interval).await; - } -} diff --git a/crates/builder/op-rbuilder/src/tests/framework/service.rs b/crates/builder/op-rbuilder/src/tests/framework/service.rs deleted file mode 100644 index 6bc587c4..00000000 --- a/crates/builder/op-rbuilder/src/tests/framework/service.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::{ - fs::{File, OpenOptions}, - future::Future, - io::{ErrorKind, Read}, - path::{Path, PathBuf}, - process::{Child, Command}, -}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum Error { - #[error("Binary not found")] - BinaryNotFound, - - #[error("Failed to spawn process")] - Spawn(ErrorKind), - - #[error("Failed initialize log streams")] - Logs, - - #[error("Service is already running")] - ServiceAlreadyRunning, -} - -pub struct ServiceInstance { - process: Option, - pub log_path: PathBuf, -} - -impl ServiceInstance { - pub fn new(name: String, test_dir: PathBuf) -> Self { - let log_path = test_dir.join(format!("{name}.log")); - Self { - process: None, - log_path, - } - } - - pub fn start(&mut self, command: Command) -> Result<(), Error> { - if self.process.is_some() { - return Err(Error::ServiceAlreadyRunning); - } - - let log = open_log_file(&self.log_path)?; - let stdout = log.try_clone().map_err(|_| Error::Logs)?; - let stderr = log.try_clone().map_err(|_| Error::Logs)?; - - let mut cmd = command; - cmd.stdout(stdout).stderr(stderr); - - let child = match cmd.spawn() { - Ok(child) => Ok(child), - Err(e) => match e.kind() { - ErrorKind::NotFound => Err(Error::BinaryNotFound), - e => Err(Error::Spawn(e)), - }, - }?; - - self.process = Some(child); - Ok(()) - } - - pub fn stop(&mut self) -> Result<(), Error> { - if let Some(mut process) = self.process.take() { - return process.kill().map_err(|e| Error::Spawn(e.kind())); - } - Ok(()) - } - - /// Start a service using its configuration and wait for it to be ready - pub async fn start_with_config(&mut self, config: &T) -> Result<(), Error> { - self.start(config.command())?; - config.ready(&self.log_path).await?; - Ok(()) - } - - pub async fn find_log_line(&self, pattern: &str) -> eyre::Result<()> { - let mut file = - File::open(&self.log_path).map_err(|_| eyre::eyre!("Failed to open log file"))?; - let mut contents = String::new(); - file.read_to_string(&mut contents) - .map_err(|_| eyre::eyre!("Failed to read log file"))?; - - if contents.contains(pattern) { - Ok(()) - } else { - Err(eyre::eyre!("Pattern not found in log file: {}", pattern)) - } - } -} - -pub struct IntegrationFramework; - -pub trait Service { - /// Configure and return the command to run the service - fn command(&self) -> Command; - - /// Return a future that resolves when the service is ready - fn ready(&self, log_path: &Path) -> impl Future> + Send; -} - -fn open_log_file(path: &PathBuf) -> Result { - let prefix = path.parent().unwrap(); - std::fs::create_dir_all(prefix).map_err(|_| Error::Logs)?; - - OpenOptions::new() - .append(true) - .create(true) - .open(path) - .map_err(|_| Error::Logs) -} diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index aea27663..95a869fa 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -1,15 +1,23 @@ use crate::{ primitives::bundle::{Bundle, BundleResult}, + tx::FBPooledTransaction, tx_signer::Signer, }; use alloy_consensus::TxEip1559; use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; -use alloy_primitives::{hex, Bytes}; +use alloy_primitives::{hex, Address, Bytes, TxHash, TxKind, B256, U256}; use alloy_provider::{PendingTransactionBuilder, Provider, RootProvider}; use core::cmp::max; +use dashmap::DashMap; +use futures::StreamExt; +use moka::future::Cache; use op_alloy_consensus::{OpTxEnvelope, OpTypedTransaction}; use op_alloy_network::Optimism; use reth_primitives::Recovered; +use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent, TransactionEvent}; +use std::{collections::VecDeque, sync::Arc}; +use tokio::sync::watch; +use tracing::debug; use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; @@ -51,11 +59,26 @@ impl TransactionBuilder { } } + pub fn with_to(mut self, to: Address) -> Self { + self.tx.to = TxKind::Call(to); + self + } + + pub fn with_create(mut self) -> Self { + self.tx.to = TxKind::Create; + self + } + pub fn with_key(mut self, key: u64) -> Self { self.key = Some(key); self } + pub fn with_value(mut self, value: u128) -> Self { + self.tx.value = U256::from(value); + self + } + pub fn with_signer(mut self, signer: Signer) -> Self { self.signer = Some(signer); self @@ -165,7 +188,7 @@ impl TransactionBuilder { let bundle = Bundle { transactions: vec![transaction_encoded.into()], reverting_hashes: if with_reverted_hash { - Some(vec![txn_hash.into()]) + Some(vec![txn_hash]) } else { None }, @@ -189,3 +212,141 @@ impl TransactionBuilder { .await?) } } + +type ObservationsMap = DashMap>; + +pub struct TransactionPoolObserver { + /// Stores a mapping of all observed transactions to their history of events. + observations: Arc, + + /// Fired when this type is dropped, giving a signal to the listener loop + /// to stop listening for events. + term: Option>, +} + +impl Drop for TransactionPoolObserver { + fn drop(&mut self) { + // Signal the listener loop to stop listening for events + if let Some(term) = self.term.take() { + let _ = term.send(true); + } + } +} + +impl TransactionPoolObserver { + pub fn new( + stream: AllTransactionsEvents, + reverts: Cache, + ) -> Self { + let mut stream = stream; + let observations = Arc::new(ObservationsMap::new()); + let observations_clone = Arc::clone(&observations); + let (term, mut term_rx) = watch::channel(false); + + tokio::spawn(async move { + let observations = observations_clone; + + loop { + tokio::select! { + _ = term_rx.changed() => { + if *term_rx.borrow() { + debug!("Transaction pool observer terminated."); + return; + } + } + tx_event = stream.next() => { + match tx_event { + Some(FullTransactionEvent::Pending(hash)) => { + tracing::debug!("Transaction pending: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Pending); + }, + Some(FullTransactionEvent::Queued(hash)) => { + tracing::debug!("Transaction queued: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Queued); + }, + Some(FullTransactionEvent::Mined { tx_hash, block_hash }) => { + tracing::debug!("Transaction mined: {tx_hash} in block {block_hash}"); + observations.entry(tx_hash).or_default().push_back(TransactionEvent::Mined(block_hash)); + }, + Some(FullTransactionEvent::Replaced { transaction, replaced_by }) => { + tracing::debug!("Transaction replaced: {transaction:?} by {replaced_by}"); + observations.entry(*transaction.hash()).or_default().push_back(TransactionEvent::Replaced(replaced_by)); + }, + Some(FullTransactionEvent::Discarded(hash)) => { + tracing::debug!("Transaction discarded: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Discarded); + reverts.insert(hash, ()).await; + }, + Some(FullTransactionEvent::Invalid(hash)) => { + tracing::debug!("Transaction invalid: {hash}"); + observations.entry(hash).or_default().push_back(TransactionEvent::Invalid); + }, + Some(FullTransactionEvent::Propagated(_)) => {}, + None => {}, + } + } + } + } + }); + + Self { + observations, + term: Some(term), + } + } + + pub fn tx_status(&self, txhash: TxHash) -> Option { + self.observations + .get(&txhash) + .and_then(|history| history.back().cloned()) + } + + pub fn is_pending(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Pending)) + } + + pub fn is_queued(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Queued)) + } + + pub fn is_dropped(&self, txhash: TxHash) -> bool { + matches!(self.tx_status(txhash), Some(TransactionEvent::Discarded)) + } + + pub fn count(&self, status: TransactionEvent) -> usize { + self.observations + .iter() + .filter(|tx| tx.value().back() == Some(&status)) + .count() + } + + pub fn pending_count(&self) -> usize { + self.count(TransactionEvent::Pending) + } + + pub fn queued_count(&self) -> usize { + self.count(TransactionEvent::Queued) + } + + pub fn dropped_count(&self) -> usize { + self.count(TransactionEvent::Discarded) + } + + /// Returns the history of pool events for a transaction. + pub fn history(&self, txhash: TxHash) -> Option> { + self.observations + .get(&txhash) + .map(|history| history.iter().cloned().collect()) + } + + pub fn print_all(&self) { + tracing::debug!("TxPool {:#?}", self.observations); + } + + pub fn exists(&self, txhash: TxHash) -> bool { + matches!( + self.tx_status(txhash), + Some(TransactionEvent::Pending) | Some(TransactionEvent::Queued) + ) + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs new file mode 100644 index 00000000..ceba8434 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -0,0 +1,193 @@ +use alloy_eips::Encodable2718; +use alloy_primitives::{hex, Address, BlockHash, TxHash, TxKind, B256, U256}; +use alloy_rpc_types_eth::{Block, BlockTransactionHashes}; +use core::future::Future; +use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; +use op_alloy_rpc_types::Transaction; + +use crate::{ + tests::{framework::driver::ChainDriver, Protocol, ONE_ETH}, + tx_signer::Signer, +}; + +use super::{TransactionBuilder, FUNDED_PRIVATE_KEYS}; + +pub trait TransactionBuilderExt { + fn random_valid_transfer(self) -> Self; + fn random_reverting_transaction(self) -> Self; +} + +impl TransactionBuilderExt for TransactionBuilder { + fn random_valid_transfer(self) -> Self { + self.with_to(rand::random::

()).with_value(1) + } + + fn random_reverting_transaction(self) -> Self { + self.with_create().with_input(hex!("60006000fd").into()) // PUSH1 0x00 PUSH1 0x00 REVERT + } +} + +pub trait ChainDriverExt { + fn fund_default_accounts(&self) -> impl Future>; + fn fund_many( + &self, + addresses: Vec
, + amount: u128, + ) -> impl Future>; + fn fund(&self, address: Address, amount: u128) + -> impl Future>; + fn first_funded_address(&self) -> Address { + FUNDED_PRIVATE_KEYS[0] + .parse() + .expect("Invalid funded private key") + } + + fn fund_accounts( + &self, + count: usize, + amount: u128, + ) -> impl Future>> { + async move { + let accounts = (0..count).map(|_| Signer::random()).collect::>(); + self.fund_many(accounts.iter().map(|a| a.address).collect(), amount) + .await?; + Ok(accounts) + } + } + + fn build_new_block_with_valid_transaction( + &self, + ) -> impl Future)>>; + + fn build_new_block_with_reverrting_transaction( + &self, + ) -> impl Future)>>; +} + +impl ChainDriverExt for ChainDriver

{ + async fn fund_default_accounts(&self) -> eyre::Result<()> { + for key in FUNDED_PRIVATE_KEYS { + let signer: Signer = key.parse()?; + self.fund(signer.address, ONE_ETH).await?; + } + Ok(()) + } + + async fn fund_many(&self, addresses: Vec

, amount: u128) -> eyre::Result { + let mut txs = Vec::with_capacity(addresses.len()); + + for address in addresses { + let deposit = TxDeposit { + source_hash: B256::default(), + from: address, // Set the sender to the address of the account to seed + to: TxKind::Create, + mint: amount, // Amount to deposit + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: Default::default(), // No input data for the deposit + }; + + let signer = Signer::random(); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit))?; + let signed_tx_rlp = signed_tx.encoded_2718(); + txs.push(signed_tx_rlp.into()); + } + + Ok(self.build_new_block_with_txs(txs).await?.header.hash) + } + + async fn fund(&self, address: Address, amount: u128) -> eyre::Result { + let deposit = TxDeposit { + source_hash: B256::default(), + from: address, // Set the sender to the address of the account to seed + to: TxKind::Create, + mint: amount, // Amount to deposit + value: U256::default(), + gas_limit: 210000, + is_system_transaction: false, + input: Default::default(), // No input data for the deposit + }; + + let signer = Signer::random(); + let signed_tx = signer.sign_tx(OpTypedTransaction::Deposit(deposit))?; + let signed_tx_rlp = signed_tx.encoded_2718(); + Ok(self + .build_new_block_with_txs(vec![signed_tx_rlp.into()]) + .await? + .header + .hash) + } + + async fn build_new_block_with_valid_transaction( + &self, + ) -> eyre::Result<(TxHash, Block)> { + let tx = self + .create_transaction() + .random_valid_transfer() + .send() + .await?; + Ok((*tx.tx_hash(), self.build_new_block().await?)) + } + + async fn build_new_block_with_reverrting_transaction( + &self, + ) -> eyre::Result<(TxHash, Block)> { + let tx = self + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + + Ok((*tx.tx_hash(), self.build_new_block().await?)) + } +} + +pub trait BlockTransactionsExt { + fn includes(&self, txs: &impl AsTxs) -> bool; +} + +impl BlockTransactionsExt for Block { + fn includes(&self, txs: &impl AsTxs) -> bool { + txs.as_txs() + .into_iter() + .all(|tx| self.transactions.hashes().any(|included| included == tx)) + } +} + +impl BlockTransactionsExt for BlockTransactionHashes<'_, Transaction> { + fn includes(&self, txs: &impl AsTxs) -> bool { + let mut included_tx_iter = self.clone(); + txs.as_txs() + .iter() + .all(|tx| included_tx_iter.any(|included| included == *tx)) + } +} + +pub trait OpRbuilderArgsTestExt { + fn test_default() -> Self; +} + +impl OpRbuilderArgsTestExt for crate::args::OpRbuilderArgs { + fn test_default() -> Self { + let mut default = Self::default(); + default.flashblocks.flashblocks_port = 0; // randomize port + default + } +} + +pub trait AsTxs { + fn as_txs(&self) -> Vec; +} + +impl AsTxs for TxHash { + fn as_txs(&self) -> Vec { + vec![*self] + } +} + +impl AsTxs for Vec { + fn as_txs(&self) -> Vec { + self.clone() + } +} diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs b/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs index cf640bab..745f10b6 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs @@ -1,33 +1,30 @@ -use crate::tests::framework::TestHarnessBuilder; +use crate::tests::{BlockTransactionsExt, ChainDriverExt, LocalInstance}; use alloy_provider::Provider; /// This test ensures that the transaction size limit is respected. /// We will set limit to 1 byte and see that the builder will not include any transactions. #[tokio::test] -async fn data_availability_tx_size_limit() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("data_availability_tx_size_limit") - .with_namespaces("admin,eth,miner") - .build() - .await?; - - let mut generator = harness.block_generator().await?; +async fn tx_size_limit() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; // Set (max_tx_da_size, max_block_da_size), with this case block won't fit any transaction - let call = harness - .provider()? + let call = driver + .provider() .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (1, 0)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); - let unfit_tx = harness + let unfit_tx = driver .create_transaction() .with_max_priority_fee_per_gas(50) .send() .await?; - let block = generator.generate_block().await?; + let block = driver.build_new_block().await?; + // tx should not be included because we set the tx_size_limit to 1 assert!( - block.not_includes(*unfit_tx.tx_hash()), + !block.includes(unfit_tx.tx_hash()), "transaction should not be included in the block" ); @@ -37,26 +34,22 @@ async fn data_availability_tx_size_limit() -> eyre::Result<()> { /// This test ensures that the block size limit is respected. /// We will set limit to 1 byte and see that the builder will not include any transactions. #[tokio::test] -async fn data_availability_block_size_limit() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("data_availability_block_size_limit") - .with_namespaces("admin,eth,miner") - .build() - .await?; - - let mut generator = harness.block_generator().await?; +async fn block_size_limit() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; // Set block da size to be small, so it won't include tx - let call = harness - .provider()? + let call = driver + .provider() .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 1)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); - let unfit_tx = harness.send_valid_transaction().await?; - let block = generator.generate_block().await?; + let (unfit_tx, block) = driver.build_new_block_with_valid_transaction().await?; + // tx should not be included because we set the tx_size_limit to 1 assert!( - block.not_includes(*unfit_tx.tx_hash()), + !block.includes(&unfit_tx), "transaction should not be included in the block" ); @@ -68,45 +61,41 @@ async fn data_availability_block_size_limit() -> eyre::Result<()> { /// We will set limit to 3 txs and see that the builder will include 3 transactions. /// We should not forget about builder transaction so we will spawn only 2 regular txs. #[tokio::test] -async fn data_availability_block_fill() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("data_availability_block_fill") - .with_namespaces("admin,eth,miner") - .build() - .await?; - - let mut generator = harness.block_generator().await?; +async fn block_fill() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; // Set block big enough so it could fit 3 transactions without tx size limit - let call = harness - .provider()? + let call = driver + .provider() .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 3)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); // We already have 2 so we will spawn one more to check that it won't be included (it won't fit // because of builder tx) - let fit_tx_1 = harness + let fit_tx_1 = driver .create_transaction() .with_max_priority_fee_per_gas(50) .send() .await?; - let fit_tx_2 = harness + let fit_tx_2 = driver .create_transaction() .with_max_priority_fee_per_gas(50) .send() .await?; - let unfit_tx_3 = harness.create_transaction().send().await?; + let unfit_tx_3 = driver.create_transaction().send().await?; - let block = generator.generate_block().await?; + let block = driver.build_new_block().await?; // Now the first 2 txs will fit into the block - assert!(block.includes(*fit_tx_1.tx_hash()), "tx should be in block"); - assert!(block.includes(*fit_tx_2.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_2.tx_hash()), "tx should be in block"); assert!( - block.not_includes(*unfit_tx_3.tx_hash()), + !block.includes(unfit_tx_3.tx_hash()), "unfit tx should not be in block" ); assert!( - harness.latest_block().await.transactions.len() == 4, + driver.latest_full().await?.transactions.len() == 4, "builder + deposit + 2 valid txs should be in the block" ); diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs b/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs index 976a3dc3..5eb1364e 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs @@ -5,5 +5,3 @@ mod ordering; mod revert; mod smoke; mod txpool; - -use super::*; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs b/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs index f524e57d..74fe8967 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs @@ -1,21 +1,23 @@ -use crate::tests::framework::{TestHarnessBuilder, ONE_ETH}; +use crate::tests::{framework::ONE_ETH, ChainDriverExt, LocalInstance}; use alloy_consensus::Transaction; use futures::{future::join_all, stream, StreamExt}; /// This test ensures that the transactions are ordered by fee priority in the block. #[tokio::test] async fn fee_priority_ordering() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("integration_test_fee_priority_ordering") - .build() - .await?; + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(10, ONE_ETH).await?; - let mut generator = harness.block_generator().await?; - let accounts = generator.create_funded_accounts(10, ONE_ETH).await?; - let base_fee = harness.latest_base_fee().await; + let latest_block = driver.latest().await?; + let base_fee = latest_block + .header + .base_fee_per_gas + .expect("Base fee should be present in the latest block"); // generate transactions with randomized tips let txs = join_all(accounts.iter().map(|signer| { - harness + driver .create_transaction() .with_signer(*signer) .with_max_priority_fee_per_gas(rand::random_range(1..50)) @@ -28,15 +30,16 @@ async fn fee_priority_ordering() -> eyre::Result<()> { .map(|tx| *tx.tx_hash()) .collect::>(); - generator.generate_block().await?; + driver.build_new_block().await?; // verify all transactions are included in the block assert!( stream::iter(txs.iter()) .all(|tx_hash| async { - harness - .latest_block() + driver + .latest_full() .await + .expect("Failed to fetch latest block") .transactions .hashes() .any(|hash| hash == *tx_hash) @@ -46,9 +49,9 @@ async fn fee_priority_ordering() -> eyre::Result<()> { ); // verify all transactions are ordered by fee priority - let txs_tips = harness - .latest_block() - .await + let txs_tips = driver + .latest_full() + .await? .into_transactions_vec() .into_iter() .skip(1) // skip the deposit transaction diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs index be0d139e..d137c1cc 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -2,32 +2,41 @@ use alloy_provider::{PendingTransactionBuilder, Provider}; use op_alloy_network::Optimism; use crate::{ + args::OpRbuilderArgs, + builders::StandardBuilder, primitives::bundle::MAX_BLOCK_RANGE_BLOCKS, - tests::{BundleOpts, TestHarness, TestHarnessBuilder}, + tests::{ + BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, LocalInstance, + TransactionBuilderExt, ONE_ETH, + }, }; /// This test ensures that the transactions that get reverted and not included in the block, /// are eventually dropped from the pool once their block range is reached. /// This test creates N transactions with different block ranges. #[tokio::test] -async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_monitor_transaction_gc") - .with_revert_protection() - .with_namespaces("eth,web3,txpool") - .build() - .await?; +async fn monitor_transaction_gc() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(10, ONE_ETH).await?; + let latest_block_number = driver.latest().await?.header.number; // send 10 bundles with different block ranges let mut pending_txn = Vec::new(); - for i in 1..=10 { - let txn = harness + + for i in 0..accounts.len() { + let txn = driver .create_transaction() - .with_revert() + .random_reverting_transaction() + .with_signer(accounts[i].clone()) .with_bundle(BundleOpts { - block_number_max: Some(i), - block_number_min: None, + block_number_max: Some(latest_block_number + i as u64 + 1), + ..Default::default() }) .send() .await?; @@ -36,21 +45,19 @@ async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { // generate 10 blocks for i in 0..10 { - let generated_block = generator.generate_block().await?; + let generated_block = driver.build_new_block().await?; // blocks should only include two transactions (deposit + builder) - assert_eq!(generated_block.block.transactions.len(), 2); + assert_eq!(generated_block.transactions.len(), 2); // since we created the 10 transactions with increasing block ranges, as we generate blocks // one transaction will be gc on each block. // transactions from [0, i] should be dropped, transactions from [i+1, 10] should be queued for j in 0..=i { - let status = harness.check_tx_in_pool(*pending_txn[j].tx_hash()).await?; - assert!(status.is_dropped()); + assert!(rbuilder.pool().is_dropped(*pending_txn[j].tx_hash())); } for j in i + 1..10 { - let status = harness.check_tx_in_pool(*pending_txn[j].tx_hash()).await?; - assert!(status.is_queued()); + assert!(rbuilder.pool().is_pending(*pending_txn[j].tx_hash())); } } @@ -59,20 +66,26 @@ async fn revert_protection_monitor_transaction_gc() -> eyre::Result<()> { /// If revert protection is disabled, the transactions that revert are included in the block. #[tokio::test] -async fn revert_protection_disabled() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_disabled") - .build() - .await?; - - let mut generator = harness.block_generator().await?; +async fn disabled() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; for _ in 0..10 { - let valid_tx = harness.send_valid_transaction().await?; - let reverting_tx = harness.send_revert_transaction().await?; - let block_generated = generator.generate_block().await?; + let valid_tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + + let reverting_tx = driver + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + let block = driver.build_new_block().await?; - assert!(block_generated.includes(*valid_tx.tx_hash())); - assert!(block_generated.includes(*reverting_tx.tx_hash())); + assert!(block.includes(valid_tx.tx_hash())); + assert!(block.includes(reverting_tx.tx_hash())); } Ok(()) @@ -81,12 +94,11 @@ async fn revert_protection_disabled() -> eyre::Result<()> { /// If revert protection is disabled, it should not be possible to send a revert bundle /// since the revert RPC endpoint is not available. #[tokio::test] -async fn revert_protection_disabled_bundle_endpoint_error() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_disabled_bundle_endpoint_error") - .build() - .await?; +async fn disabled_bundle_endpoint_error() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; - let res = harness + let res = driver .create_transaction() .with_bundle(BundleOpts::default()) .send() @@ -104,89 +116,72 @@ async fn revert_protection_disabled_bundle_endpoint_error() -> eyre::Result<()> /// is not included in the block and tried again for the next bundle range blocks /// when it will be dropped from the pool. #[tokio::test] -async fn revert_protection_bundle() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_bundle") - .with_revert_protection() - .with_namespaces("eth,web3,txpool") - .build() - .await?; +async fn bundle() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; // Block 1 + let driver = rbuilder.driver().await?; + let _ = driver.build_new_block().await?; // Block 1 // Test 1: Bundle does not revert - let valid_bundle = harness + let valid_bundle = driver .create_transaction() + .random_valid_transfer() .with_bundle(BundleOpts::default()) .send() .await?; - let block_generated = generator.generate_block().await?; // Block 2 - assert!(block_generated.includes(*valid_bundle.tx_hash())); + let block2 = driver.build_new_block().await?; // Block 2 + assert!(block2 + .transactions + .hashes() + .includes(valid_bundle.tx_hash())); let bundle_opts = BundleOpts { - block_number_max: Some(5), - block_number_min: None, + block_number_max: Some(4), + ..Default::default() }; - let reverted_bundle_0 = harness - .create_transaction() - .with_revert() - .with_reverted_hash() - .with_bundle(bundle_opts) - .send() - .await?; - - // Test 2: Bundle reverts. It is included in the block since the transaction - // includes the reverted_hashes field - let block_generated = generator.generate_block().await?; // Block 3 - assert!(block_generated.includes(*reverted_bundle_0.tx_hash())); - - let reverted_bundle_1 = harness + let reverted_bundle = driver .create_transaction() - .with_revert() + .random_reverting_transaction() .with_bundle(bundle_opts) .send() .await?; - // Test 3: Bundle reverts. It is not included in the block since it reverts - // and the hash is not in the reverted_hashes field. - let block_generated = generator.generate_block().await?; // Block 4 - assert!(block_generated.not_includes(*reverted_bundle_1.tx_hash())); + // Test 2: Bundle reverts. It is not included in the block + let block3 = driver.build_new_block().await?; // Block 3 + assert!(!block3.includes(reverted_bundle.tx_hash())); // After the block the transaction is still pending in the pool - assert!(harness - .check_tx_in_pool(*reverted_bundle_1.tx_hash()) - .await? - .is_pending()); + assert!(rbuilder.pool().is_pending(*reverted_bundle.tx_hash())); // Test 3: Chain progresses beyond the bundle range. The transaction is dropped from the pool - generator.generate_block().await?; // Block 5 - assert!(harness - .check_tx_in_pool(*reverted_bundle_1.tx_hash()) - .await? - .is_pending()); - - generator.generate_block().await?; // Block 6 - assert!(harness - .check_tx_in_pool(*reverted_bundle_1.tx_hash()) - .await? - .is_dropped()); + driver.build_new_block().await?; // Block 4 + assert!(rbuilder.pool().is_dropped(*reverted_bundle.tx_hash())); + + driver.build_new_block().await?; // Block 5 + assert!(rbuilder.pool().is_dropped(*reverted_bundle.tx_hash())); Ok(()) } /// Test the behaviour of the revert protection bundle with a min block number. #[tokio::test] -async fn revert_protection_bundle_min_block_number() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_bundle_min_block_number") - .with_revert_protection() - .build() - .await?; +async fn bundle_min_block_number() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; // The bundle is valid when the min block number is equal to the current block - let bundle_with_min_block = harness + let bundle_with_min_block = driver .create_transaction() .with_revert() // the transaction reverts but it is included in the block .with_reverted_hash() @@ -197,14 +192,14 @@ async fn revert_protection_bundle_min_block_number() -> eyre::Result<()> { .send() .await?; - let block = generator.generate_block().await?; // Block 1, bundle still not valid - assert!(block.not_includes(*bundle_with_min_block.tx_hash())); + let block = driver.build_new_block().await?; // Block 1, bundle still not valid + assert!(!block.includes(bundle_with_min_block.tx_hash())); - let block = generator.generate_block().await?; // Block 2, bundle is valid - assert!(block.includes(*bundle_with_min_block.tx_hash())); + let block = driver.build_new_block().await?; // Block 2, bundle is valid + assert!(block.includes(bundle_with_min_block.tx_hash())); // Send a bundle with a match of min and max block number - let bundle_with_min_and_max_block = harness + let bundle_with_min_and_max_block = driver .create_transaction() .with_revert() .with_reverted_hash() @@ -215,35 +210,34 @@ async fn revert_protection_bundle_min_block_number() -> eyre::Result<()> { .send() .await?; - let block = generator.generate_block().await?; // Block 3, bundle still not valid - assert!(block.not_includes(*bundle_with_min_and_max_block.tx_hash())); + let block = driver.build_new_block().await?; // Block 3, bundle still not valid + assert!(!block.includes(bundle_with_min_and_max_block.tx_hash())); - let block = generator.generate_block().await?; // Block 4, bundle is valid - assert!(block.includes(*bundle_with_min_and_max_block.tx_hash())); + let block = driver.build_new_block().await?; // Block 4, bundle is valid + assert!(block.includes(bundle_with_min_and_max_block.tx_hash())); Ok(()) } /// Test the range limits for the revert protection bundle. #[tokio::test] -async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("revert_protection_bundle_range_limits") - .with_revert_protection() - .build() - .await?; +async fn bundle_range_limits() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; - - // Advance two blocks and try to send a bundle with max block = 1 - generator.generate_block().await?; // Block 1 - generator.generate_block().await?; // Block 2 + let driver = rbuilder.driver().await?; + let _ = driver.build_new_block().await?; // Block 1 + let _ = driver.build_new_block().await?; // Block 2 async fn send_bundle( - harness: &TestHarness, + driver: &ChainDriver, block_number_max: Option, block_number_min: Option, ) -> eyre::Result> { - harness + driver .create_transaction() .with_bundle(BundleOpts { block_number_max, @@ -254,19 +248,19 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { } // Max block cannot be a past block - assert!(send_bundle(&harness, Some(1), None).await.is_err()); + assert!(send_bundle(&driver, Some(1), None).await.is_err()); // Bundles are valid if their max block in in between the current block and the max block range let current_block = 2; let next_valid_block = current_block + 1; for i in next_valid_block..next_valid_block + MAX_BLOCK_RANGE_BLOCKS { - assert!(send_bundle(&harness, Some(i), None).await.is_ok()); + assert!(send_bundle(&driver, Some(i), None).await.is_ok()); } // A bundle with a block out of range is invalid assert!(send_bundle( - &harness, + &driver, Some(next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1), None ) @@ -274,21 +268,21 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { .is_err()); // A bundle with a min block number higher than the max block is invalid - assert!(send_bundle(&harness, Some(1), Some(2)).await.is_err()); + assert!(send_bundle(&driver, Some(1), Some(2)).await.is_err()); // A bundle with a min block number lower or equal to the current block is valid assert!( - send_bundle(&harness, Some(next_valid_block), Some(current_block)) + send_bundle(&driver, Some(next_valid_block), Some(current_block)) .await .is_ok() ); - assert!(send_bundle(&harness, Some(next_valid_block), Some(0)) + assert!(send_bundle(&driver, Some(next_valid_block), Some(0)) .await .is_ok()); // A bundle with a min block equal to max block is valid assert!( - send_bundle(&harness, Some(next_valid_block), Some(next_valid_block)) + send_bundle(&driver, Some(next_valid_block), Some(next_valid_block)) .await .is_ok() ); @@ -296,16 +290,16 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { // Test min-only cases (no max specified) // A bundle with only min block that's within the default range is valid let default_max = current_block + MAX_BLOCK_RANGE_BLOCKS; - assert!(send_bundle(&harness, None, Some(current_block)) + assert!(send_bundle(&driver, None, Some(current_block)) .await .is_ok()); - assert!(send_bundle(&harness, None, Some(default_max - 1)) + assert!(send_bundle(&driver, None, Some(default_max - 1)) .await .is_ok()); - assert!(send_bundle(&harness, None, Some(default_max)).await.is_ok()); + assert!(send_bundle(&driver, None, Some(default_max)).await.is_ok()); // A bundle with only min block that exceeds the default max range is invalid - assert!(send_bundle(&harness, None, Some(default_max + 1)) + assert!(send_bundle(&driver, None, Some(default_max + 1)) .await .is_err()); @@ -314,24 +308,32 @@ async fn revert_protection_bundle_range_limits() -> eyre::Result<()> { /// If a transaction reverts and was sent as a normal transaction through the eth_sendRawTransaction /// bundle, the transaction should be included in the block. -/// This behaviour is the same as the 'revert_protection_disabled' test. +/// This behaviour is the same as the 'disabled' test. #[tokio::test] -async fn revert_protection_allow_reverted_transactions_without_bundle() -> eyre::Result<()> { - let harness = - TestHarnessBuilder::new("revert_protection_allow_reverted_transactions_without_bundle") - .with_revert_protection() - .build() - .await?; +async fn allow_reverted_transactions_without_bundle() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; for _ in 0..10 { - let valid_tx = harness.send_valid_transaction().await?; - let reverting_tx = harness.send_revert_transaction().await?; - let block_generated = generator.generate_block().await?; + let valid_tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + let reverting_tx = driver + .create_transaction() + .random_reverting_transaction() + .send() + .await?; + let block = driver.build_new_block().await?; - assert!(block_generated.includes(*valid_tx.tx_hash())); - assert!(block_generated.includes(*reverting_tx.tx_hash())); + assert!(block.includes(valid_tx.tx_hash())); + assert!(block.includes(reverting_tx.tx_hash())); } Ok(()) @@ -340,38 +342,39 @@ async fn revert_protection_allow_reverted_transactions_without_bundle() -> eyre: /// If a transaction reverts and gets dropped it, the eth_getTransactionReceipt should return /// an error message that it was dropped. #[tokio::test] -async fn revert_protection_check_transaction_receipt_status_message() -> eyre::Result<()> { - let harness = - TestHarnessBuilder::new("revert_protection_check_transaction_receipt_status_message") - .with_revert_protection() - .build() - .await?; +async fn check_transaction_receipt_status_message() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; - let provider = harness.provider()?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; - let reverting_tx = harness + let reverting_tx = driver .create_transaction() - .with_revert() + .random_reverting_transaction() .with_bundle(BundleOpts { block_number_max: Some(3), - block_number_min: None, + ..Default::default() }) .send() .await?; let tx_hash = reverting_tx.tx_hash(); - let _ = generator.generate_block().await?; + let _ = driver.build_new_block().await?; let receipt = provider.get_transaction_receipt(*tx_hash).await?; assert!(receipt.is_none()); - let _ = generator.generate_block().await?; + let _ = driver.build_new_block().await?; let receipt = provider.get_transaction_receipt(*tx_hash).await?; assert!(receipt.is_none()); // Dropped - let _ = generator.generate_block().await?; + let _ = driver.build_new_block().await?; let receipt = provider.get_transaction_receipt(*tx_hash).await; + assert!(receipt.is_err()); Ok(()) diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs b/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs index 33109c4a..8fd537e2 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs @@ -1,16 +1,27 @@ -use super::framework::TestHarnessBuilder; -use alloy_provider::Provider; +use crate::tests::{LocalInstance, TransactionBuilderExt}; +use alloy_primitives::TxHash; +use core::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; use std::collections::HashSet; +use tokio::{join, task::yield_now}; +use tracing::info; /// This is a smoke test that ensures that transactions are included in blocks /// and that the block generator is functioning correctly. +/// +/// Generated blocks are also validated against an external op-reth node to +/// ensure their correctness. #[tokio::test] async fn chain_produces_blocks() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("chain_produces_blocks") - .build() - .await?; + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?; - let mut generator = harness.block_generator().await?; + #[cfg(target_os = "linux")] + let driver = driver + .with_validation_node(crate::tests::ExternalNode::reth().await?) + .await?; const SAMPLE_SIZE: usize = 10; @@ -18,14 +29,13 @@ async fn chain_produces_blocks() -> eyre::Result<()> { // no user transactions are sent. // the deposit transaction and the block generator's transaction for _ in 0..SAMPLE_SIZE { - generator - .generate_block() - .await - .expect("Failed to generate block"); - let transactions = harness.latest_block().await.transactions; - assert!( - transactions.len() == 2, - "Block should have exactly two transactions" + let block = driver.build_new_block().await?; + let transactions = block.transactions; + + assert_eq!( + transactions.len(), + 2, + "Empty blocks should have exactly two transactions" ); } @@ -33,98 +43,111 @@ async fn chain_produces_blocks() -> eyre::Result<()> { // sent to it during its block time + the two mandatory transactions for _ in 0..SAMPLE_SIZE { let count = rand::random_range(1..8); - let mut tx_hashes = HashSet::new(); + let mut tx_hashes = HashSet::::default(); + for _ in 0..count { - let tx = harness - .send_valid_transaction() + let tx = driver + .create_transaction() + .random_valid_transfer() + .send() .await .expect("Failed to send transaction"); - let tx_hash = *tx.tx_hash(); - tx_hashes.insert(tx_hash); + tx_hashes.insert(*tx.tx_hash()); } - generator - .generate_block() - .await - .expect("Failed to generate block"); - let transactions = harness.latest_block().await.transactions; - - assert!( - transactions.len() == 2 + count, + + let block = driver.build_new_block().await?; + + let txs = block.transactions; + + assert_eq!( + txs.len(), + 2 + count, "Block should have {} transactions", 2 + count ); for tx_hash in tx_hashes { assert!( - transactions.hashes().any(|hash| hash == *tx_hash), + txs.hashes().any(|hash| hash == tx_hash), "Transaction {} should be included in the block", tx_hash ); } } - Ok(()) } /// Ensures that payloads are generated correctly even when the builder is busy /// with other requests, such as fcu or getPayload. -#[tokio::test] -async fn get_payload_close_to_fcu() -> eyre::Result<()> { - let test_harness = TestHarnessBuilder::new("get_payload_close_to_fcu") - .build() - .await?; - let mut block_generator = test_harness.block_generator().await?; - - // add some transactions to the pool so that the builder - // is busy when we send the fcu/getPayload requests - for _ in 0..10 { - // Note, for this test it is okay if they are not valid - let _ = test_harness.send_valid_transaction().await?; - } - - let result = tokio::time::timeout( - std::time::Duration::from_secs(1), - block_generator.submit_payload(None, 0, true), - ) - .await; - - // ensure we didn't timeout - let result = result.expect("Submit payload timed out"); - - // ensure we got a payload - assert!(result.is_ok(), "Failed to get payload: {:?}", result); +#[tokio::test(flavor = "multi_thread")] +async fn produces_blocks_under_load_within_deadline() -> eyre::Result<()> { + let rbuilder = LocalInstance::standard().await?; + let driver = rbuilder.driver().await?.with_gas_limit(10_00_000); + + let done = AtomicBool::new(false); + + let (populate, produce) = join!( + async { + // Keep the builder busy with new transactions. + loop { + match driver + .create_transaction() + .random_valid_transfer() + .send() + .await + { + Ok(_) => {} + Err(e) if e.to_string().contains("txpool is full") => { + // If the txpool is full, give it a short break + tokio::time::sleep(Duration::from_millis(100)).await; + } + Err(e) => return Err(e), + }; + + if done.load(Ordering::Relaxed) { + break; + } + + yield_now().await; + } + Ok::<(), eyre::Error>(()) + }, + async { + // Wait for a short time to allow the transaction population to start + // and fill up the txpool. + tokio::time::sleep(Duration::from_secs(1)).await; + + // Now, start producing blocks under load. + for _ in 0..10 { + // Ensure that the builder can still produce blocks under + // heavy load of incoming transactions. + let block = tokio::time::timeout( + Duration::from_secs(rbuilder.args().chain_block_time) + + Duration::from_millis(500), + driver.build_new_block(), + ) + .await + .expect("Timeout while waiting for block production") + .expect("Failed to produce block under load"); - Ok(()) -} + info!("Produced a block under load: {block:#?}"); -/// This test validates that if we flood the builder with many transactions -/// and we request short block times, the builder can still eventually resolve all the transactions -#[tokio::test] -async fn transaction_flood_no_sleep() -> eyre::Result<()> { - let test_harness = TestHarnessBuilder::new("transaction_flood_no_sleep") - .build() - .await?; + yield_now().await; + } - let mut block_generator = test_harness.block_generator().await?; - let provider = test_harness.provider()?; + // we're happy with one block produced under load + // set the done flag to true to stop the transaction population + done.store(true, Ordering::Relaxed); + info!("All blocks produced under load"); - // Send 200 valid transactions to the builder - // More than this and there is an issue with the RPC endpoint not being able to handle the load - let mut transactions = vec![]; - for _ in 0..200 { - let tx = test_harness.send_valid_transaction().await?; - let tx_hash = *tx.tx_hash(); - transactions.push(tx_hash); - } + Ok::<(), eyre::Error>(()) + } + ); - // After a 10 blocks all the transactions should be included in a block - for _ in 0..10 { - block_generator.submit_payload(None, 0, true).await.unwrap(); - } + populate.unwrap(); - for tx in transactions { - provider.get_transaction_receipt(tx).await?; - } + //assert!(populate.is_ok(), "Failed to populate transactions"); + assert!(produce.is_ok(), "Failed to produce block under load"); Ok(()) } diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs b/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs index cbf15512..be8aae12 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs @@ -1,45 +1,52 @@ -use crate::tests::{framework::TestHarnessBuilder, ONE_ETH}; -use alloy_provider::ext::TxPoolApi; +use crate::{ + builders::StandardBuilder, + tests::{default_node_config, BlockTransactionsExt, ChainDriverExt, LocalInstance, ONE_ETH}, +}; +use reth::args::TxPoolArgs; +use reth_node_builder::NodeConfig; +use reth_optimism_chainspec::OpChainSpec; /// This test ensures that pending pool custom limit is respected and priority tx would be included even when pool if full. #[tokio::test] async fn pending_pool_limit() -> eyre::Result<()> { - let harness = TestHarnessBuilder::new("pending_pool_limit") - .with_namespaces("txpool,eth,debug,admin") - .with_extra_params("--txpool.pending-max-count 50") - .build() - .await?; + let rbuilder = LocalInstance::new_with_config::( + Default::default(), + NodeConfig:: { + txpool: TxPoolArgs { + pending_max_count: 50, + ..Default::default() + }, + ..default_node_config() + }, + ) + .await?; - let mut generator = harness.block_generator().await?; + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(50, ONE_ETH).await?; // Send 50 txs from different addrs - let accounts = generator.create_funded_accounts(2, ONE_ETH).await?; let acc_no_priority = accounts.first().unwrap(); let acc_with_priority = accounts.last().unwrap(); for _ in 0..50 { - let _ = harness + let _ = driver .create_transaction() .with_signer(*acc_no_priority) .send() .await?; } - let pool = harness - .provider() - .expect("provider not available") - .txpool_status() - .await?; assert_eq!( - pool.pending, 50, + rbuilder.pool().pending_count(), + 50, "Pending pool must contain at max 50 txs {:?}", - pool + rbuilder.pool().pending_count() ); // Send 10 txs that should be included in the block let mut txs = Vec::new(); for _ in 0..10 { - let tx = harness + let tx = driver .create_transaction() .with_signer(*acc_with_priority) .with_max_priority_fee_per_gas(10) @@ -48,22 +55,18 @@ async fn pending_pool_limit() -> eyre::Result<()> { txs.push(*tx.tx_hash()); } - let pool = harness - .provider() - .expect("provider not available") - .txpool_status() - .await?; assert_eq!( - pool.pending, 50, + rbuilder.pool().pending_count(), + 50, "Pending pool must contain at max 50 txs {:?}", - pool + rbuilder.pool().pending_count() ); // After we try building block our reverting tx would be removed and other tx will move to queue pool - let block = generator.generate_block().await?; + let block = driver.build_new_block().await?; // Ensure that 10 extra txs got included - assert!(block.includes_vec(txs)); + assert!(block.includes(&txs)); Ok(()) } From 1f70a527566f7a44b253ac3ddf6d12529bcd25b6 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 11 Jun 2025 19:22:36 +0700 Subject: [PATCH 128/262] fmt (#142) Logic Add extended flashblocks tracing --- crates/builder/op-rbuilder/Cargo.toml | 6 ++- crates/builder/op-rbuilder/src/args/mod.rs | 2 +- crates/builder/op-rbuilder/src/args/op.rs | 14 ++++++ .../op-rbuilder/src/builders/context.rs | 27 ++++++++++-- .../src/builders/flashblocks/payload.rs | 44 +++++++++++++++++-- .../src/builders/flashblocks/wspub.rs | 10 ++++- crates/builder/op-rbuilder/src/launcher.rs | 14 +++++- .../builder/op-rbuilder/src/primitives/mod.rs | 5 ++- .../op-rbuilder/src/primitives/telemetry.rs | 30 +++++++++++++ .../op-rbuilder/src/tests/framework/apis.rs | 2 +- 10 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/primitives/telemetry.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 5e828706..5e417352 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -99,6 +99,7 @@ thiserror.workspace = true parking_lot.workspace = true url.workspace = true anyhow = "1" +opentelemetry = { workspace = true, optional = true } tower = "0.5" futures = "0.3" @@ -177,7 +178,10 @@ testing = [ interop = [] -telemetry = ["reth-tracing-otlp"] +telemetry = [ + "reth-tracing-otlp", + "opentelemetry", +] custom-engine-api = [] diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index 9048fde7..0c0b1a4e 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -3,7 +3,7 @@ use crate::{ metrics::{CARGO_PKG_VERSION, VERGEN_GIT_SHA}, }; use clap_builder::{CommandFactory, FromArgMatches}; -pub use op::{FlashblocksArgs, OpRbuilderArgs}; +pub use op::{FlashblocksArgs, OpRbuilderArgs, TelemetryArgs}; use playground::PlaygroundOptions; use reth_optimism_cli::{chainspec::OpChainSpecParser, commands::Commands}; diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 4f54adaa..4cd9095a 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -52,6 +52,8 @@ pub struct OpRbuilderArgs { pub playground: Option, #[command(flatten)] pub flashblocks: FlashblocksArgs, + #[command(flatten)] + pub telemetry: TelemetryArgs, } impl Default for OpRbuilderArgs { @@ -124,3 +126,15 @@ impl Default for FlashblocksArgs { node_command.ext.flashblocks } } + +/// Parameters for telemetry configuration +#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +pub struct TelemetryArgs { + /// OpenTelemetry endpoint for traces + #[arg(long = "telemetry.otlp-endpoint", env = "OTEL_EXPORTER_OTLP_ENDPOINT")] + pub otlp_endpoint: Option, + + /// OpenTelemetry headers for authentication + #[arg(long = "telemetry.otlp-headers", env = "OTEL_EXPORTER_OTLP_HEADERS")] + pub otlp_headers: Option, +} diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 218596ec..99e85b35 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -334,7 +334,13 @@ impl OpPayloadBuilderCtx { let tx_da_limit = self.da_config.max_da_tx_size(); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); - info!(target: "payload_builder", block_da_limit = ?block_da_limit, tx_da_size = ?tx_da_limit, block_gas_limit = ?block_gas_limit, "DA limits"); + info!( + target: "payload_builder", + message = "Executing best transactions", + block_da_limit = ?block_da_limit, + tx_da_limit = ?tx_da_limit, + block_gas_limit = ?block_gas_limit, + ); // Remove once we merge Reth 1.4.4 // Fixed in https://github.com/paradigmxyz/reth/pull/16514 @@ -367,9 +373,18 @@ impl OpPayloadBuilderCtx { reverted_hashes.is_some() && !reverted_hashes.unwrap().contains(&tx_hash); let log_txn = |result: TxnExecutionResult| { - debug!(target: "payload_builder", tx_hash = ?tx_hash, tx_da_size = ?tx_da_size, exclude_reverting_txs = ?exclude_reverting_txs, result = %result, "Considering transaction"); + debug!( + target: "payload_builder", + message = "Considering transaction", + tx_hash = ?tx_hash, + tx_da_size = ?tx_da_size, + exclude_reverting_txs = ?exclude_reverting_txs, + result = %result, + ); }; + num_txs_considered += 1; + if let Some(conditional) = conditional { // TODO: ideally we should get this from the txpool stream if !conditional.matches_block_attributes(&block_attr) { @@ -391,7 +406,6 @@ impl OpPayloadBuilderCtx { } } - num_txs_considered += 1; // ensure we still have capacity for this transaction if let Err(result) = info.is_tx_over_limits( tx_da_size, @@ -512,6 +526,13 @@ impl OpPayloadBuilderCtx { .payload_num_tx_simulated_fail .record(num_txs_simulated_fail as f64); + debug!( + target: "payload_builder", + message = "Completed executing best transactions", + txs_executed = num_txs_considered, + txs_applied = num_txs_simulated_success, + txs_rejected = num_txs_simulated_fail, + ); Ok(None) } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 0257f8b7..a7b0f6c2 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -43,7 +43,7 @@ use rollup_boost::{ }; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc; -use tracing::{debug, error, warn}; +use tracing::{debug, error, metadata::Level, span, warn}; #[derive(Debug, Default)] struct ExtraExecutionInfo { @@ -138,6 +138,18 @@ where let block_build_start_time = Instant::now(); let BuildArguments { config, cancel, .. } = args; + // We log only every 100th block to reduce usage + let span = if cfg!(feature = "telemetry") && config.parent_header.number % 100 == 0 { + span!(Level::INFO, "build_payload") + } else { + tracing::Span::none() + }; + let _enter = span.enter(); + span.record( + "payload_id", + config.attributes.payload_attributes.id.to_string(), + ); + let chain_spec = self.client.chain_spec(); let timestamp = config.attributes.timestamp(); let block_env_attributes = OpNextBlockEnvAttributes { @@ -211,7 +223,12 @@ where .publish(&fb_payload) .map_err(PayloadBuilderError::other)?; - tracing::info!(target: "payload_builder", "Fallback block built"); + tracing::info!( + target: "payload_builder", + message = "Fallback block built", + payload_id = fb_payload.payload_id.to_string(), + ); + ctx.metrics .payload_num_tx .record(info.executed_transactions.len() as f64); @@ -276,6 +293,16 @@ where // Process flashblocks in a blocking loop loop { + let fb_span = if span.is_none() { + tracing::Span::none() + } else { + span!( + parent: &span, + Level::INFO, + "build_flashblock", + ) + }; + let _entered = fb_span.enter(); // Block on receiving a message, break on cancellation or closed channel let received = tokio::task::block_in_place(|| { // Get runtime handle @@ -430,7 +457,13 @@ where } } flashblock_count += 1; - tracing::info!(target: "payload_builder", "Flashblock {} built", flashblock_count); + tracing::info!( + target: "payload_builder", + message = "Flashblock built", + ?flashblock_count, + current_gas = info.cumulative_gas_used, + current_da = info.cumulative_da_bytes_used, + ); } } } @@ -440,6 +473,11 @@ where self.metrics .flashblock_count .record(flashblock_count as f64); + debug!( + target: "payload_builder", + message = "Payload building complete, channel closed or job cancelled" + ); + span.record("flashblock_count", flashblock_count); return Ok(()); } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index 4455e785..94802681 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -20,7 +20,7 @@ use tokio_tungstenite::{ tungstenite::{Message, Utf8Bytes}, WebSocketStream, }; -use tracing::warn; +use tracing::{debug, warn}; use crate::metrics::OpRBuilderMetrics; @@ -65,6 +65,14 @@ impl WebSocketPublisher { // Serialize the payload to a UTF-8 string // serialize only once, then just copy around only a pointer // to the serialized data for each subscription. + debug!( + target: "payload_builder", + message = "Sending flashblock to rollup-boost", + payload_id = payload.payload_id.to_string(), + index = payload.index, + base = payload.base.is_some(), + ); + let serialized = serde_json::to_string(payload)?; let utf8_bytes = Utf8Bytes::from(serialized); diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index a8904ed8..d725a49e 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -26,12 +26,22 @@ use std::{marker::PhantomData, sync::Arc}; pub fn launch() -> Result<()> { let cli = Cli::parsed(); let mode = cli.builder_mode(); + + #[cfg(feature = "telemetry")] + let telemetry_args = match &cli.command { + reth_optimism_cli::commands::Commands::Node(node_command) => { + node_command.ext.telemetry.clone() + } + _ => Default::default(), + }; + let mut cli_app = cli.configure(); #[cfg(feature = "telemetry")] { - let otlp = reth_tracing_otlp::layer("op-reth"); - cli_app.access_tracing_layers()?.add_layer(otlp); + use crate::primitives::telemetry::setup_telemetry_layer; + let telemetry_layer = setup_telemetry_layer(&telemetry_args)?; + cli_app.access_tracing_layers()?.add_layer(telemetry_layer); } cli_app.init_tracing()?; diff --git a/crates/builder/op-rbuilder/src/primitives/mod.rs b/crates/builder/op-rbuilder/src/primitives/mod.rs index 2af3ab8b..da146d30 100644 --- a/crates/builder/op-rbuilder/src/primitives/mod.rs +++ b/crates/builder/op-rbuilder/src/primitives/mod.rs @@ -1,3 +1,4 @@ -pub mod reth; - pub mod bundle; +pub mod reth; +#[cfg(feature = "telemetry")] +pub mod telemetry; diff --git a/crates/builder/op-rbuilder/src/primitives/telemetry.rs b/crates/builder/op-rbuilder/src/primitives/telemetry.rs new file mode 100644 index 00000000..f663156b --- /dev/null +++ b/crates/builder/op-rbuilder/src/primitives/telemetry.rs @@ -0,0 +1,30 @@ +use crate::args::TelemetryArgs; +use tracing_subscriber::{filter::Targets, Layer}; + +/// Setup telemetry layer with sampling and custom endpoint configuration +pub fn setup_telemetry_layer( + args: &TelemetryArgs, +) -> eyre::Result> { + use tracing::level_filters::LevelFilter; + + // Otlp uses evn vars inside + if let Some(endpoint) = &args.otlp_endpoint { + std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint); + } + if let Some(headers) = &args.otlp_headers { + std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", headers); + } + + // Create OTLP layer with custom configuration + let otlp_layer = reth_tracing_otlp::layer("op-rbuilder"); + + // Create a trace filter that sends more data to OTLP but less to stdout + let trace_filter = Targets::new() + .with_default(LevelFilter::WARN) + .with_target("op_rbuilder", LevelFilter::INFO) + .with_target("payload_builder", LevelFilter::DEBUG); + + let filtered_layer = otlp_layer.with_filter(trace_filter); + + Ok(filtered_layer) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs index d9357207..519261ee 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -92,7 +92,7 @@ impl EngineApi { pub fn with_localhost_port(port: u16) -> EngineApi { EngineApi:: { address: Address::Http( - format!("http://localhost:{}", port) + format!("http://localhost:{port}") .parse() .expect("Invalid URL"), ), From cd3b6cce0258e6267cab2650296d1aee840012bb Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:55:48 -0400 Subject: [PATCH 129/262] Add minTimestamp and maxTimestamp as optional fields to bundle (#141) * Add minTimestamp and maxTimestamp as optional fields to bundle * Test that bundles with min timestamp wait aren't included until the block time exceeds it --- .../op-rbuilder/src/primitives/bundle.rs | 38 +++++++++++++++++- .../op-rbuilder/src/tests/framework/driver.rs | 11 +---- .../op-rbuilder/src/tests/framework/txs.rs | 4 ++ .../op-rbuilder/src/tests/vanilla/revert.rs | 40 +++++++++++++++++++ 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index 251e28b9..f6f00506 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -28,6 +28,22 @@ pub struct Bundle { skip_serializing_if = "Option::is_none" )] pub block_number_min: Option, + + // Not recommended because this is subject to the builder node clock + #[serde( + default, + rename = "minTimestamp", + skip_serializing_if = "Option::is_none" + )] + pub min_timestamp: Option, + + // Not recommended because this is subject to the builder node clock + #[serde( + default, + rename = "maxTimestamp", + skip_serializing_if = "Option::is_none" + )] + pub max_timestamp: Option, } impl From for EthApiError { @@ -110,8 +126,8 @@ impl Bundle { block_number_min, block_number_max, known_accounts: Default::default(), - timestamp_max: None, - timestamp_min: None, + timestamp_max: self.max_timestamp, + timestamp_min: self.min_timestamp, }) } } @@ -133,6 +149,8 @@ mod tests { reverting_hashes: None, block_number_max: None, block_number_min: None, + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; @@ -152,6 +170,8 @@ mod tests { reverting_hashes: None, block_number_max: Some(1005), block_number_min: Some(1002), + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; @@ -168,6 +188,8 @@ mod tests { reverting_hashes: None, block_number_max: Some(1005), block_number_min: Some(1010), + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; @@ -189,6 +211,8 @@ mod tests { reverting_hashes: None, block_number_max: Some(999), block_number_min: None, + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; @@ -210,6 +234,8 @@ mod tests { reverting_hashes: None, block_number_max: Some(1020), block_number_min: None, + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; @@ -232,6 +258,8 @@ mod tests { reverting_hashes: None, block_number_max: None, block_number_min: Some(1015), + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; @@ -253,6 +281,8 @@ mod tests { reverting_hashes: None, block_number_max: None, block_number_min: Some(1005), + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; @@ -269,6 +299,8 @@ mod tests { reverting_hashes: None, block_number_max: Some(1008), block_number_min: None, + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; @@ -285,6 +317,8 @@ mod tests { reverting_hashes: None, block_number_max: None, block_number_min: Some(999), + min_timestamp: None, + max_timestamp: None, }; let last_block = 1000; diff --git a/crates/builder/op-rbuilder/src/tests/framework/driver.rs b/crates/builder/op-rbuilder/src/tests/framework/driver.rs index 49321366..5ee806da 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/driver.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/driver.rs @@ -1,5 +1,4 @@ use core::time::Duration; -use std::time::SystemTime; use alloy_eips::{eip7685::Requests, BlockNumberOrTag, Encodable2718}; use alloy_primitives::{address, hex, Bytes, TxKind, B256, U256}; @@ -108,15 +107,7 @@ impl ChainDriver { ) -> eyre::Result> { let latest = self.latest().await?; let latest_timestamp = Duration::from_secs(latest.header.timestamp); - let actual_timestamp = SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map_err(|_| eyre::eyre!("Failed to get current system time"))?; - - // block timestamp will be the max of the actual timestamp and the latest block - // timestamp plus the minimum block time. This ensures that blocks don't break any - // assumptions, but also gives the test author the ability to control the block time - // in the test. - let block_timestamp = actual_timestamp.max(latest_timestamp + Self::MIN_BLOCK_TIME); + let block_timestamp = latest_timestamp + Self::MIN_BLOCK_TIME; // Add L1 block info as the first transaction in every L2 block // This deposit transaction contains L1 block metadata required by the L2 chain diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index 95a869fa..84135c8a 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -27,6 +27,8 @@ use super::FUNDED_PRIVATE_KEYS; pub struct BundleOpts { pub block_number_max: Option, pub block_number_min: Option, + pub min_timestamp: Option, + pub max_timestamp: Option, } #[derive(Clone)] @@ -194,6 +196,8 @@ impl TransactionBuilder { }, block_number_max: bundle_opts.block_number_max, block_number_min: bundle_opts.block_number_min, + min_timestamp: bundle_opts.min_timestamp, + max_timestamp: bundle_opts.max_timestamp, }; let result: BundleResult = provider diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs index d137c1cc..7108a795 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs @@ -188,6 +188,8 @@ async fn bundle_min_block_number() -> eyre::Result<()> { .with_bundle(BundleOpts { block_number_max: None, block_number_min: Some(2), + min_timestamp: None, + max_timestamp: None, }) .send() .await?; @@ -206,6 +208,8 @@ async fn bundle_min_block_number() -> eyre::Result<()> { .with_bundle(BundleOpts { block_number_max: Some(4), block_number_min: Some(4), + min_timestamp: None, + max_timestamp: None, }) .send() .await?; @@ -219,6 +223,40 @@ async fn bundle_min_block_number() -> eyre::Result<()> { Ok(()) } +/// Test the behaviour of the revert protection bundle with a min timestamp. +#[tokio::test] +async fn revert_protection_bundle_min_timestamp() -> eyre::Result<()> { + let rbuilder = LocalInstance::new::(OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() + }) + .await?; + + let driver = rbuilder.driver().await?; + let initial_timestamp = driver.latest().await?.header.timestamp; + + // The bundle is valid when the min timestamp is equal to the current block's timestamp + let bundle_with_min_timestamp = driver + .create_transaction() + .with_revert() // the transaction reverts but it is included in the block + .with_reverted_hash() + .with_bundle(BundleOpts { + min_timestamp: Some(initial_timestamp + 2), + ..Default::default() + }) + .send() + .await?; + + // Each block advances the timestamp by block_time_secs which is 1 when chain_block_time isn't set + let block = driver.build_new_block().await?; // Block 1, initial_timestamp + 1 + assert!(!block.includes(bundle_with_min_timestamp.tx_hash())); + + let block = driver.build_new_block().await?; // Block 2, initial_timestamp + 2, so bundle is valid + assert!(block.includes(bundle_with_min_timestamp.tx_hash())); + + Ok(()) +} + /// Test the range limits for the revert protection bundle. #[tokio::test] async fn bundle_range_limits() -> eyre::Result<()> { @@ -242,6 +280,8 @@ async fn bundle_range_limits() -> eyre::Result<()> { .with_bundle(BundleOpts { block_number_max, block_number_min, + min_timestamp: None, + max_timestamp: None, }) .send() .await From 83bbd730168451a37770c614fa5959f4c8464403 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:02:29 -0400 Subject: [PATCH 130/262] Add metric for bundles received (#149) --- crates/builder/op-rbuilder/src/metrics.rs | 2 ++ crates/builder/op-rbuilder/src/revert_protection.rs | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index f39787cd..ac4d957b 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -76,6 +76,8 @@ pub struct OpRBuilderMetrics { pub da_block_size_limit: Gauge, /// Da tx size limit pub da_tx_size_limit: Gauge, + /// Number of valid bundles received at the eth_sendBundle endpoint + pub bundles_received: Counter, } /// Contains version information for the application. diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index d9f7a9ed..c709cb56 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -1,4 +1,7 @@ +use std::sync::Arc; + use crate::{ + metrics::OpRBuilderMetrics, primitives::bundle::{Bundle, BundleResult}, tx::{FBPooledTransaction, MaybeRevertingTransaction}, }; @@ -38,6 +41,7 @@ pub struct RevertProtectionExt, _network: std::marker::PhantomData, } @@ -52,6 +56,7 @@ where pool, provider, eth_api, + metrics: Arc::new(OpRBuilderMetrics::default()), _network: std::marker::PhantomData, } } @@ -60,6 +65,7 @@ where RevertProtectionBundleAPI { pool: self.pool.clone(), provider: self.provider.clone(), + metrics: self.metrics.clone(), } } @@ -74,6 +80,7 @@ where pub struct RevertProtectionBundleAPI { pool: Pool, provider: Provider, + metrics: Arc, } #[async_trait] @@ -122,6 +129,8 @@ where .await .map_err(EthApiError::from)?; + self.metrics.bundles_received.increment(1); + let result = BundleResult { bundle_hash: hash }; Ok(result) } From 048a495049cefa8e1eb3d59569d4192df76ce1ba Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Mon, 16 Jun 2025 07:13:47 -0400 Subject: [PATCH 131/262] Add metric to count reverted bundles (#151) --- crates/builder/op-rbuilder/src/builders/context.rs | 11 ++++++++++- crates/builder/op-rbuilder/src/metrics.rs | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 99e85b35..f9db06de 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -330,6 +330,7 @@ impl OpPayloadBuilderCtx { let mut num_txs_simulated = 0; let mut num_txs_simulated_success = 0; let mut num_txs_simulated_fail = 0; + let mut num_bundles_reverted = 0; let base_fee = self.base_fee(); let tx_da_limit = self.da_config.max_da_tx_size(); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); @@ -369,8 +370,9 @@ impl OpPayloadBuilderCtx { // - the transaction comes from a bundle (is_some) and the hash **is not** in reverted hashes // Note that we need to use the Option to signal whether the transaction comes from a bundle, // otherwise, we would exclude all transactions that are not in the reverted hashes. + let is_bundle_tx = reverted_hashes.is_some(); let exclude_reverting_txs = - reverted_hashes.is_some() && !reverted_hashes.unwrap().contains(&tx_hash); + is_bundle_tx && !reverted_hashes.unwrap().contains(&tx_hash); let log_txn = |result: TxnExecutionResult| { debug!( @@ -469,6 +471,9 @@ impl OpPayloadBuilderCtx { num_txs_simulated_success += 1; } else { num_txs_simulated_fail += 1; + if is_bundle_tx { + num_bundles_reverted += 1; + } if exclude_reverting_txs { log_txn(TxnExecutionResult::RevertedAndExcluded); info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); @@ -525,6 +530,9 @@ impl OpPayloadBuilderCtx { self.metrics .payload_num_tx_simulated_fail .record(num_txs_simulated_fail as f64); + self.metrics + .bundles_reverted + .record(num_bundles_reverted as f64); debug!( target: "payload_builder", @@ -532,6 +540,7 @@ impl OpPayloadBuilderCtx { txs_executed = num_txs_considered, txs_applied = num_txs_simulated_success, txs_rejected = num_txs_simulated_fail, + bundles_reverted = num_bundles_reverted, ); Ok(None) } diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index ac4d957b..32dbd3b5 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -78,6 +78,8 @@ pub struct OpRBuilderMetrics { pub da_tx_size_limit: Gauge, /// Number of valid bundles received at the eth_sendBundle endpoint pub bundles_received: Counter, + /// Number of reverted bundles + pub bundles_reverted: Histogram, } /// Contains version information for the application. From 6b383982bfbf8f8258886e640568cda77f4e1dd1 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 16 Jun 2025 19:52:13 +0700 Subject: [PATCH 132/262] Remove extra generic param (#152) * Remove extra generic * Fix fmt --- crates/builder/op-rbuilder/src/launcher.rs | 8 ++------ crates/builder/op-rbuilder/src/revert_protection.rs | 6 ++---- .../builder/op-rbuilder/src/tests/framework/instance.rs | 8 ++------ 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index d725a49e..c6ce22c5 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -146,12 +146,8 @@ where let pool = ctx.pool().clone(); let provider = ctx.provider().clone(); - let revert_protection_ext: RevertProtectionExt< - _, - _, - _, - op_alloy_network::Optimism, - > = RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); + let revert_protection_ext = + RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); ctx.modules .merge_configured(revert_protection_ext.bundle_api().into_rpc())?; diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index c709cb56..bb311a45 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -37,15 +37,14 @@ pub trait EthApiOverride { async fn transaction_receipt(&self, hash: B256) -> RpcResult>; } -pub struct RevertProtectionExt { +pub struct RevertProtectionExt { pool: Pool, provider: Provider, eth_api: Eth, metrics: Arc, - _network: std::marker::PhantomData, } -impl RevertProtectionExt +impl RevertProtectionExt where Pool: Clone, Provider: Clone, @@ -57,7 +56,6 @@ where provider, eth_api, metrics: Arc::new(OpRBuilderMetrics::default()), - _network: std::marker::PhantomData, } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 7c482c93..08c04c91 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -123,12 +123,8 @@ impl LocalInstance { let pool = ctx.pool().clone(); let provider = ctx.provider().clone(); - let revert_protection_ext: RevertProtectionExt< - _, - _, - _, - op_alloy_network::Optimism, - > = RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); + let revert_protection_ext = + RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); ctx.modules .merge_configured(revert_protection_ext.bundle_api().into_rpc())?; From be4bcce688c2691200c3b4f4efe596aeda556c4d Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 16 Jun 2025 19:53:03 +0700 Subject: [PATCH 133/262] Add pingpong and closing frame handle (#154) --- .../src/builders/flashblocks/wspub.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index 94802681..09a396c5 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -6,6 +6,7 @@ use core::{ task::{Context, Poll}, }; use futures::{Sink, SinkExt}; +use futures_util::StreamExt; use rollup_boost::FlashblocksPayloadV1; use std::{io, net::TcpListener, sync::Arc}; use tokio::{ @@ -209,6 +210,30 @@ async fn broadcast_loop( tracing::warn!("Broadcast channel lagged, some messages were dropped"); } }, + + // This handles channel closing and ping-pong logic + message = stream.next() => if let Some(message) = message { match message { + Ok(message) => { + match message { + Message::Ping(data) => { + if let Err(e) = stream.send(Message::Pong(data)).await { + tracing::warn!("Closing flashblocks subscription for {peer_addr}: {e}"); + break; // Exit the loop if sending fails + } + } + // We don't get any data from RB, so we won't handle closing frame + Message::Close(_) => { + tracing::info!("Closing frame received, stopping connection for {peer_addr}"); + break; + } + _ => (), + } + } + Err(e) => { + tracing::warn!("Received error. Closing flashblocks subscription for {peer_addr}: {e}"); + break; + } + } } } } } From af637b1599143cf74971c2a2794b9e05bb2da9e2 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 16 Jun 2025 20:06:23 +0700 Subject: [PATCH 134/262] Supress infallible clippy error (#155) --- crates/builder/op-rbuilder/src/builders/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 66ad7436..ce0974e3 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -154,6 +154,7 @@ where } } +#[expect(clippy::infallible_try_from)] impl TryFrom for () { type Error = Infallible; From bf61aa1820c858b888bfdc9c929a0cbab11fb1a9 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 16 Jun 2025 20:50:16 +0700 Subject: [PATCH 135/262] Use tungstenite provided ping handling (#156) --- .../src/builders/flashblocks/wspub.rs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index 09a396c5..3329478b 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -211,28 +211,18 @@ async fn broadcast_loop( } }, - // This handles channel closing and ping-pong logic + // Ping-pong handled by tokio_tungstenite when you perform read on the socket message = stream.next() => if let Some(message) = message { match message { - Ok(message) => { - match message { - Message::Ping(data) => { - if let Err(e) = stream.send(Message::Pong(data)).await { - tracing::warn!("Closing flashblocks subscription for {peer_addr}: {e}"); - break; // Exit the loop if sending fails - } - } - // We don't get any data from RB, so we won't handle closing frame - Message::Close(_) => { - tracing::info!("Closing frame received, stopping connection for {peer_addr}"); - break; - } - _ => (), - } + // We handle only close frame to highlight conn closing + Ok(Message::Close(_)) => { + tracing::info!("Closing frame received, stopping connection for {peer_addr}"); + break; } Err(e) => { tracing::warn!("Received error. Closing flashblocks subscription for {peer_addr}: {e}"); break; } + _ => (), } } } } From 4f12d5e186141d2653eaa3efc7861989853dd33b Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Mon, 16 Jun 2025 16:09:01 +0200 Subject: [PATCH 136/262] Avoid boxing for the txlogging task (#153) --- crates/builder/op-rbuilder/src/launcher.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index c6ce22c5..1c496517 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -162,16 +162,9 @@ where VERSION.register_version_metrics(); if builder_args.log_pool_transactions { tracing::info!("Logging pool transactions"); - ctx.task_executor.spawn_critical( - "txlogging", - Box::pin(async move { - monitor_tx_pool( - ctx.pool.all_transactions_event_listener(), - reverted_cache_copy, - ) - .await; - }), - ); + let listener = ctx.pool.all_transactions_event_listener(); + let task = monitor_tx_pool(listener, reverted_cache_copy); + ctx.task_executor.spawn_critical("txlogging", task); } Ok(()) From 2ec74c588c3fc1ed9c24a64d148714f02961a1fa Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 17 Jun 2025 10:35:36 +0100 Subject: [PATCH 137/262] Fix regression tester command (#160) --- crates/builder/op-rbuilder/src/bin/tester/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/bin/tester/main.rs b/crates/builder/op-rbuilder/src/bin/tester/main.rs index b011beb6..9345a8c0 100644 --- a/crates/builder/op-rbuilder/src/bin/tester/main.rs +++ b/crates/builder/op-rbuilder/src/bin/tester/main.rs @@ -70,7 +70,7 @@ pub async fn run_system(validation: bool) -> eyre::Result<()> { let engine_api = EngineApi::with_http("http://localhost:4444"); let provider = ProviderBuilder::::default() - .connect_http("http://localhost:2222".try_into()?); + .connect_http("http://localhost:4444".try_into()?); let mut driver = ChainDriver::::remote(provider, engine_api); if validation { From 8656471c322f9634f1cd520d296328f95d8e73b6 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 17 Jun 2025 12:07:47 +0100 Subject: [PATCH 138/262] Genesis command outputs genesis file (#159) --- crates/builder/op-rbuilder/src/tests/framework/apis.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs index 519261ee..645fd8b7 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -222,9 +222,9 @@ pub async fn generate_genesis(output: Option) -> eyre::Result<()> { // Write the result to the output file if let Some(output) = output { std::fs::write(&output, serde_json::to_string_pretty(&genesis)?)?; - debug!("Generated genesis file at: {output}"); + println!("Generated genesis file at: {output}"); } else { - debug!("{}", serde_json::to_string_pretty(&genesis)?); + println!("{}", serde_json::to_string_pretty(&genesis)?); } Ok(()) From 95e8ead8f2bbe50d942273d51a3037998823924f Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 17 Jun 2025 19:09:14 +0500 Subject: [PATCH 139/262] Account for flashblocks time drift (#123) * quick bugfix Account for dynamic lag in the beginning of building process * More improvements * More improvements * More improvements * More improvements * Wrap everything into the config * Review * Review --- crates/builder/op-rbuilder/src/args/op.rs | 28 +++ .../src/builders/flashblocks/config.rs | 21 +- .../src/builders/flashblocks/payload.rs | 206 +++++++++++++----- .../builder/op-rbuilder/src/builders/mod.rs | 7 +- crates/builder/op-rbuilder/src/metrics.rs | 6 + .../src/tests/flashblocks/smoke.rs | 2 + 6 files changed, 217 insertions(+), 53 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 4cd9095a..fa791189 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -115,6 +115,26 @@ pub struct FlashblocksArgs { env = "FLASHBLOCK_BLOCK_TIME" )] pub flashblocks_block_time: u64, + + /// Enabled dynamic flashblocks adjustment. This will allow account for late FCUs and produce + /// less flashblocks, while each flashblock would be bigger. + #[arg( + long = "flashblocks.dynamic", + default_value = "false", + env = "FLASHBLOCK_DYNAMIC" + )] + pub flashblocks_dynamic: bool, + + /// Time by which blocks would be completed earlier in milliseconds. + /// + /// This time used to account for latencies, this time would be deducted from total block + /// building time before calculating number of fbs. + #[arg( + long = "flashblocks.leeway-time", + default_value = "50", + env = "FLASHBLOCK_LEEWAY_TIME" + )] + pub flashblocks_leeway_time: u64, } impl Default for FlashblocksArgs { @@ -137,4 +157,12 @@ pub struct TelemetryArgs { /// OpenTelemetry headers for authentication #[arg(long = "telemetry.otlp-headers", env = "OTEL_EXPORTER_OTLP_HEADERS")] pub otlp_headers: Option, + + /// Inverted sampling frequency in blocks. 1 - each block, 100 - every 100th block. + #[arg( + long = "telemetry.sampling-ratio", + env = "SAMPLING_RATIO", + default_value = "100" + )] + pub sampling_ratio: u64, } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index 1c2af41b..0229a2b3 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -15,6 +15,13 @@ pub struct FlashblocksConfig { /// Each block will contain one or more flashblocks. On average, the number of flashblocks /// per block is equal to the block time divided by the flashblock interval. pub interval: Duration, + + /// How much time would be deducted from block build time to account for latencies in + /// milliseconds + pub leeway_time: Duration, + + /// Enables dynamic flashblocks number based on FCU arrival time + pub dynamic_adjustment: bool, } impl Default for FlashblocksConfig { @@ -22,6 +29,8 @@ impl Default for FlashblocksConfig { Self { ws_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 1111), interval: Duration::from_millis(250), + leeway_time: Duration::from_millis(50), + dynamic_adjustment: false, } } } @@ -36,7 +45,17 @@ impl TryFrom for FlashblocksConfig { args.flashblocks.flashblocks_addr.parse()?, args.flashblocks.flashblocks_port, ); - Ok(Self { ws_addr, interval }) + + let leeway_time = Duration::from_millis(args.flashblocks.flashblocks_leeway_time); + + let dynamic_adjustment = args.flashblocks.flashblocks_dynamic; + + Ok(Self { + ws_addr, + interval, + leeway_time, + dynamic_adjustment, + }) } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index a7b0f6c2..256d2756 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -1,6 +1,3 @@ -use core::time::Duration; -use std::{sync::Arc, time::Instant}; - use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; use crate::{ builders::{ @@ -18,6 +15,7 @@ use alloy_consensus::{ }; use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE, Encodable2718}; use alloy_primitives::{map::foldhash::HashMap, Address, B256, U256}; +use core::time::Duration; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::BuildOutcome; use reth_evm::{execute::BlockBuilder, ConfigureEvm}; @@ -42,8 +40,13 @@ use rollup_boost::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, }; use serde::{Deserialize, Serialize}; +use std::{ + ops::{Div, Rem}, + sync::Arc, + time::Instant, +}; use tokio::sync::mpsc; -use tracing::{debug, error, metadata::Level, span, warn}; +use tracing::{debug, error, info, metadata::Level, span, warn}; #[derive(Debug, Default)] struct ExtraExecutionInfo { @@ -139,12 +142,14 @@ where let BuildArguments { config, cancel, .. } = args; // We log only every 100th block to reduce usage - let span = if cfg!(feature = "telemetry") && config.parent_header.number % 100 == 0 { + let span = if cfg!(feature = "telemetry") + && config.parent_header.number % self.config.sampling_ratio == 0 + { span!(Level::INFO, "build_payload") } else { tracing::Span::none() }; - let _enter = span.enter(); + let _entered = span.enter(); span.record( "payload_id", config.attributes.payload_attributes.id.to_string(), @@ -152,6 +157,34 @@ where let chain_spec = self.client.chain_spec(); let timestamp = config.attributes.timestamp(); + // We use this system time to determine remining time to build a block + // Things to consider: + // FCU(a) - FCU with attributes + // FCU(a) could arrive with `block_time - fb_time < delay`. In this case we could only produce 1 flashblock + // 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 time = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp) + - self.config.specific.leeway_time; + let time_drift = time.duration_since(std::time::SystemTime::now()).ok(); + match time_drift { + None => error!( + target: "payload_builder", + message = "FCU arrived too late or system clock are unsynced", + ?time, + ), + Some(time_drift) => { + self.metrics + .flashblock_time_drift + .record(time_drift.as_millis() as f64); + debug!( + target: "payload_builder", + message = "Time drift for building round", + ?time, + ?time_drift, + ?timestamp + ); + } + } let block_env_attributes = OpNextBlockEnvAttributes { timestamp, suggested_fee_recipient: config.attributes.suggested_fee_recipient(), @@ -202,7 +235,6 @@ where .build(); // We subtract gas limit and da limit for builder transaction from the whole limit - // TODO: we could optimise this and subtract this only for the last flashblocks let message = format!("Block Number: {}", ctx.block_number()).into_bytes(); let builder_tx_gas = ctx .builder_signer() @@ -223,7 +255,7 @@ where .publish(&fb_payload) .map_err(PayloadBuilderError::other)?; - tracing::info!( + info!( target: "payload_builder", message = "Fallback block built", payload_id = fb_payload.payload_id.to_string(), @@ -234,24 +266,40 @@ where .record(info.executed_transactions.len() as f64); if ctx.attributes().no_tx_pool { - tracing::info!( + info!( target: "payload_builder", "No transaction pool, skipping transaction pool processing", ); - self.metrics + ctx.metrics .total_block_built_duration .record(block_build_start_time.elapsed()); // return early since we don't need to build a block with transactions from the pool return Ok(()); } - let gas_per_batch = ctx.block_gas_limit() / self.config.flashblocks_per_block(); + // We adjust our flashblocks timings based on time_drift if dynamic adjustment enable + let (flashblocks_per_block, first_flashblock_offset) = + self.calculate_flashblocks(time_drift); + info!( + target: "payload_builder", + message = "Performed flashblocks timing derivation", + flashblocks_per_block, + first_flashblock_offset = first_flashblock_offset.as_millis(), + flashblocks_interval = self.config.specific.interval.as_millis(), + ); + ctx.metrics + .target_flashblock + .record(flashblocks_per_block as f64); + ctx.metrics + .first_flashblock_time_offset + .record(first_flashblock_offset.as_millis() as f64); + let gas_per_batch = ctx.block_gas_limit() / flashblocks_per_block; let mut total_gas_per_batch = gas_per_batch; let da_per_batch = ctx .da_config .max_da_block_size() - .map(|da_limit| da_limit / self.config.flashblocks_per_block()); + .map(|da_limit| da_limit / flashblocks_per_block); // Check that builder tx won't affect fb limit too much if let Some(da_limit) = da_per_batch { // We error if we can't insert any tx aside from builder tx in flashblock @@ -261,7 +309,9 @@ where } let mut total_da_per_batch = da_per_batch; - let last_flashblock = self.config.flashblocks_per_block().saturating_sub(1); + // TODO: we should account for a case when we will issue only 1 flashblock + let last_flashblock = flashblocks_per_block.saturating_sub(1); + let mut flashblock_count = 0; // Create a channel to coordinate flashblock building let (build_tx, mut build_rx) = mpsc::channel(1); @@ -270,24 +320,46 @@ where let cancel_clone = ctx.cancel.clone(); let interval = self.config.specific.interval; tokio::spawn(async move { - let mut interval = tokio::time::interval(interval); - loop { - tokio::select! { - // Add a cancellation check that only runs every 10ms to avoid tight polling - _ = tokio::time::sleep(Duration::from_millis(10)) => { - if cancel_clone.is_cancelled() { - tracing::info!(target: "payload_builder", "Job cancelled during sleep, stopping payload building"); - drop(build_tx); - break; - } - } - _ = interval.tick() => { + // We handle first flashblock separately, because it could be shrunk to fit everything + let mut first_interval = tokio::time::interval(first_flashblock_offset); + // If first_flashblock_offset == 0 that means all flashblock are proper, and we just + // skip this cusom logic + if first_flashblock_offset.as_millis() != 0 { + let cancelled = cancel_clone + .run_until_cancelled(async { + // We send first signal straight away and then wait first_interval duration + // to send second signal and start building normal blocks + for _ in 0..2 { + first_interval.tick().await; + // TODO: maybe return None if cancelled if let Err(err) = build_tx.send(()).await { error!(target: "payload_builder", "Error sending build signal: {}", err); - break; } } + }) + .await; + if cancelled.is_none() { + info!(target: "payload_builder", "Building job cancelled, stopping payload building"); + drop(build_tx); + return; + } + } + // Handle rest of fbs in steady rate + let mut interval = tokio::time::interval(interval); + let cancelled = cancel_clone.run_until_cancelled(async { + // First tick completes immediately + interval.tick().await; + loop { + interval.tick().await; + if let Err(err) = build_tx.send(()).await { + error!(target: "payload_builder", "Error sending build signal: {}", err); + break; + } } + }).await; + if cancelled.is_none() { + info!(target: "payload_builder", "Building job cancelled, stopping payload building"); + drop(build_tx); } }); @@ -312,7 +384,7 @@ where rt.block_on(async { // Check for cancellation first if ctx.cancel.is_cancelled() { - tracing::info!( + info!( target: "payload_builder", "Job cancelled, stopping payload building", ); @@ -327,10 +399,10 @@ where // Exit loop if channel closed or cancelled match received { Some(()) => { - if flashblock_count >= self.config.flashblocks_per_block() { - tracing::info!( + if flashblock_count >= flashblocks_per_block { + info!( target: "payload_builder", - target = self.config.flashblocks_per_block(), + target = flashblocks_per_block, flashblock_count = flashblock_count, block_number = ctx.block_number(), "Skipping flashblock reached target", @@ -339,7 +411,7 @@ where } // Continue with flashblock building - tracing::info!( + info!( target: "payload_builder", block_number = ctx.block_number(), flashblock_count = flashblock_count, @@ -381,25 +453,30 @@ where .record(best_txs_start_time.elapsed()); let tx_execution_start_time = Instant::now(); - ctx.execute_best_transactions( - &mut info, - &mut db, - best_txs, - total_gas_per_batch.min(ctx.block_gas_limit()), - total_da_per_batch, - )?; - ctx.metrics - .payload_tx_simulation_duration - .record(tx_execution_start_time.elapsed()); - - if ctx.cancel.is_cancelled() { - tracing::info!( + if ctx + .execute_best_transactions( + &mut info, + &mut db, + best_txs, + total_gas_per_batch.min(ctx.block_gas_limit()), + total_da_per_batch, + )? + .is_some() + { + // Handles job cancellation + info!( target: "payload_builder", "Job cancelled, stopping payload building", ); + ctx.metrics + .payload_tx_simulation_duration + .record(tx_execution_start_time.elapsed()); // if the job was cancelled, stop return Ok(()); } + ctx.metrics + .payload_tx_simulation_duration + .record(tx_execution_start_time.elapsed()); // TODO: temporary we add builder tx to the first flashblock too invoke_on_first_flashblock(flashblock_count, || { @@ -421,7 +498,7 @@ where match build_result { Err(err) => { // Track invalid/bad block - self.metrics.invalid_blocks_count.increment(1); + ctx.metrics.invalid_blocks_count.increment(1); error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), flashblock_count, err); // Return the error return Err(err); @@ -435,7 +512,7 @@ where .map_err(PayloadBuilderError::other)?; // Record flashblock build duration - self.metrics + ctx.metrics .flashblock_build_duration .record(flashblock_build_start_time.elapsed()); ctx.metrics @@ -457,22 +534,21 @@ where } } flashblock_count += 1; - tracing::info!( + info!( target: "payload_builder", message = "Flashblock built", ?flashblock_count, current_gas = info.cumulative_gas_used, current_da = info.cumulative_da_bytes_used, + target_flashblocks = flashblocks_per_block, ); } } } None => { // Exit loop if channel closed or cancelled - self.metrics.block_built_success.increment(1); - self.metrics - .flashblock_count - .record(flashblock_count as f64); + ctx.metrics.block_built_success.increment(1); + ctx.metrics.flashblock_count.record(flashblock_count as f64); debug!( target: "payload_builder", message = "Payload building complete, channel closed or job cancelled" @@ -483,6 +559,34 @@ where } } } + + /// Calculate number of flashblocks. + /// If dynamic is enabled this function will take time drift into the account. + pub fn calculate_flashblocks(&self, time_drift: Option) -> (u64, Duration) { + if !self.config.specific.dynamic_adjustment || time_drift.is_none() { + return ( + self.config.flashblocks_per_block(), + self.config.specific.interval, + ); + } + let time_drift = time_drift.unwrap(); + let interval = self.config.specific.interval.as_millis(); + let time_drift = time_drift.as_millis(); + let first_flashblock_offset = time_drift.rem(interval); + let flashblocks_per_block = if first_flashblock_offset == 0 { + // In this case all flashblock are full and they all in division + time_drift.div(interval) + } else { + // If we have any reminder that mean the first flashblock won't be in division + // so we add it manually. + time_drift.div(interval) + 1 + }; + // We won't have any problems because of casting + ( + flashblocks_per_block as u64, + Duration::from_millis(first_flashblock_offset as u64), + ) + } } impl crate::builders::generator::PayloadBuilder for OpPayloadBuilder diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index ce0974e3..926fd842 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -74,7 +74,7 @@ pub struct BuilderConfig { pub revert_protection: bool, /// The interval at which blocks are added to the chain. - /// This is also the frequency at which the builder will be receiving FCU rquests from the + /// This is also the frequency at which the builder will be receiving FCU requests from the /// sequencer. pub block_time: Duration, @@ -100,6 +100,9 @@ pub struct BuilderConfig { // (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, } @@ -132,6 +135,7 @@ impl Default for BuilderConfig { block_time_leeway: Duration::from_millis(500), da_config: OpDAConfig::default(), specific: S::default(), + sampling_ratio: 100, } } } @@ -149,6 +153,7 @@ where block_time: Duration::from_millis(args.chain_block_time), block_time_leeway: Duration::from_secs(args.extra_block_deadline_secs), da_config: Default::default(), + sampling_ratio: args.telemetry.sampling_ratio, specific: S::try_from(args)?, }) } diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 32dbd3b5..d3ed291e 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -76,6 +76,12 @@ pub struct OpRBuilderMetrics { pub da_block_size_limit: Gauge, /// Da tx size limit pub da_tx_size_limit: Gauge, + /// Desired number of flashblocks + pub target_flashblock: Histogram, + /// Time drift that we account for in the beginning of block building + pub flashblock_time_drift: Histogram, + /// Time offset we used for first flashblock + pub first_flashblock_time_offset: Histogram, /// Number of valid bundles received at the eth_sendBundle endpoint pub bundles_received: Counter, /// Number of reverted bundles diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs index 5a98b5d0..339d332b 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs @@ -21,6 +21,8 @@ async fn chain_produces_blocks() -> eyre::Result<()> { flashblocks_port: 1239, flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, + flashblocks_leeway_time: 0, + flashblocks_dynamic: false, }, ..Default::default() }) From ebbc038b0d4818e2e372947ca542fd3eaf9f88e7 Mon Sep 17 00:00:00 2001 From: Karim Agha Date: Tue, 17 Jun 2025 16:52:39 +0200 Subject: [PATCH 140/262] Run the vanilla tests using both the flashblocks builder and the vanilla builder (#145) * Applied 68 on top of latest main * Update crates/op-rbuilder/src/tests/smoke.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * lint * port new revert unit test * lint --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/builder/op-rbuilder/Cargo.toml | 12 +- .../tests/{vanilla => }/data_availability.rs | 34 ++- .../{flashblocks/smoke.rs => flashblocks.rs} | 31 +- .../op-rbuilder/src/tests/flashblocks/mod.rs | 3 - .../src/tests/framework/macros/Cargo.toml | 14 + .../src/tests/framework/macros/src/lib.rs | 287 ++++++++++++++++++ .../op-rbuilder/src/tests/framework/mod.rs | 5 +- crates/builder/op-rbuilder/src/tests/mod.rs | 17 +- .../src/tests/{vanilla => }/ordering.rs | 9 +- .../src/tests/{vanilla => }/revert.rs | 116 +++---- .../src/tests/{vanilla => }/smoke.rs | 59 ++-- .../src/tests/{vanilla => }/txpool.rs | 29 +- .../op-rbuilder/src/tests/vanilla/mod.rs | 7 - 13 files changed, 475 insertions(+), 148 deletions(-) rename crates/builder/op-rbuilder/src/tests/{vanilla => }/data_availability.rs (77%) rename crates/builder/op-rbuilder/src/tests/{flashblocks/smoke.rs => flashblocks.rs} (79%) delete mode 100644 crates/builder/op-rbuilder/src/tests/flashblocks/mod.rs create mode 100644 crates/builder/op-rbuilder/src/tests/framework/macros/Cargo.toml create mode 100644 crates/builder/op-rbuilder/src/tests/framework/macros/src/lib.rs rename crates/builder/op-rbuilder/src/tests/{vanilla => }/ordering.rs (84%) rename crates/builder/op-rbuilder/src/tests/{vanilla => }/revert.rs (84%) rename crates/builder/op-rbuilder/src/tests/{vanilla => }/smoke.rs (75%) rename crates/builder/op-rbuilder/src/tests/{vanilla => }/txpool.rs (75%) delete mode 100644 crates/builder/op-rbuilder/src/tests/vanilla/mod.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 5e417352..869a7240 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -123,6 +123,8 @@ reth-ipc = { workspace = true, optional = true } bollard = { version = "0.19", optional = true } tar = { version = "0.4", optional = true } ctor = { version = "0.4.2", optional = true } +rlimit = { version = "0.10", optional = true } +macros = { path = "src/tests/framework/macros", optional = true } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -136,13 +138,14 @@ alloy-provider = { workspace = true, default-features = true, features = [ "txpool-api", ] } tempfile = "3.8" - +macros = { path = "src/tests/framework/macros" } dashmap = { version = "6.1" } nanoid = { version = "0.4" } reth-ipc = { workspace = true } reth-node-builder = { workspace = true, features = ["test-utils"] } bollard = "0.19" ctor = "0.4.2" +rlimit = { version = "0.10" } [features] default = ["jemalloc"] @@ -173,15 +176,14 @@ testing = [ "reth-node-builder/test-utils", "bollard", "ctor", + "macros", + "rlimit", ] interop = [] -telemetry = [ - "reth-tracing-otlp", - "opentelemetry", -] +telemetry = ["reth-tracing-otlp", "opentelemetry"] custom-engine-api = [] diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs b/crates/builder/op-rbuilder/src/tests/data_availability.rs similarity index 77% rename from crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs rename to crates/builder/op-rbuilder/src/tests/data_availability.rs index 745f10b6..ec37ed10 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/data_availability.rs +++ b/crates/builder/op-rbuilder/src/tests/data_availability.rs @@ -1,11 +1,11 @@ use crate::tests::{BlockTransactionsExt, ChainDriverExt, LocalInstance}; use alloy_provider::Provider; +use macros::{if_flashblocks, if_standard, rb_test}; /// This test ensures that the transaction size limit is respected. /// We will set limit to 1 byte and see that the builder will not include any transactions. -#[tokio::test] -async fn tx_size_limit() -> eyre::Result<()> { - let rbuilder = LocalInstance::standard().await?; +#[rb_test] +async fn tx_size_limit(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; // Set (max_tx_da_size, max_block_da_size), with this case block won't fit any transaction @@ -33,9 +33,8 @@ async fn tx_size_limit() -> eyre::Result<()> { /// This test ensures that the block size limit is respected. /// We will set limit to 1 byte and see that the builder will not include any transactions. -#[tokio::test] -async fn block_size_limit() -> eyre::Result<()> { - let rbuilder = LocalInstance::standard().await?; +#[rb_test] +async fn block_size_limit(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; // Set block da size to be small, so it won't include tx @@ -60,9 +59,8 @@ async fn block_size_limit() -> eyre::Result<()> { /// Size of each transaction is 100000000 /// We will set limit to 3 txs and see that the builder will include 3 transactions. /// We should not forget about builder transaction so we will spawn only 2 regular txs. -#[tokio::test] -async fn block_fill() -> eyre::Result<()> { - let rbuilder = LocalInstance::standard().await?; +#[rb_test] +async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; // Set block big enough so it could fit 3 transactions without tx size limit @@ -87,9 +85,21 @@ async fn block_fill() -> eyre::Result<()> { let unfit_tx_3 = driver.create_transaction().send().await?; let block = driver.build_new_block().await?; - // Now the first 2 txs will fit into the block - assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); - assert!(block.includes(fit_tx_2.tx_hash()), "tx should be in block"); + + if_standard! { + // Now the first 2 txs will fit into the block + assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_2.tx_hash()), "tx should be in block"); + } + + if_flashblocks! { + // in flashblocks the DA quota is divided by the number of flashblocks + // so we will include only one tx in the block because not all of them + // will fit within DA quote / flashblocks count. + assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); + assert!(!block.includes(fit_tx_2.tx_hash()), "tx should not be in block"); + } + assert!( !block.includes(unfit_tx_3.tx_hash()), "unfit tx should not be in block" diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs similarity index 79% rename from crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs rename to crates/builder/op-rbuilder/src/tests/flashblocks.rs index 339d332b..44d8df21 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use futures::StreamExt; +use macros::rb_test; use parking_lot::Mutex; use tokio::task::JoinHandle; use tokio_tungstenite::{connect_async, tungstenite::Message}; @@ -8,26 +9,22 @@ use tokio_util::sync::CancellationToken; use crate::{ args::{FlashblocksArgs, OpRbuilderArgs}, - builders::FlashblocksBuilder, tests::{ChainDriverExt, LocalInstance, TransactionBuilderExt}, }; -#[tokio::test] -async fn chain_produces_blocks() -> eyre::Result<()> { - let rbuilder = LocalInstance::new::(OpRbuilderArgs { - chain_block_time: 2000, - flashblocks: FlashblocksArgs { - enabled: true, - flashblocks_port: 1239, - flashblocks_addr: "127.0.0.1".into(), - flashblocks_block_time: 200, - flashblocks_leeway_time: 0, - flashblocks_dynamic: false, - }, - ..Default::default() - }) - .await?; - +#[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, + flashblocks_leeway_time: 0, + flashblocks_dynamic: false, + }, + ..Default::default() +})] +async fn smoke(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; driver.fund_default_accounts().await?; diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/tests/flashblocks/mod.rs deleted file mode 100644 index 6ca67633..00000000 --- a/crates/builder/op-rbuilder/src/tests/flashblocks/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![cfg(test)] - -mod smoke; diff --git a/crates/builder/op-rbuilder/src/tests/framework/macros/Cargo.toml b/crates/builder/op-rbuilder/src/tests/framework/macros/Cargo.toml new file mode 100644 index 00000000..c131b63b --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/macros/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "macros" +version = "0.1.0" +edition = "2024" +description = "Macros supporting the tests infrastructure for op-rbuilder" + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0" +quote = "1.0" +proc-macro2 = "1.0" +paste = "1.0" 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 new file mode 100644 index 00000000..64cb940a --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/macros/src/lib.rs @@ -0,0 +1,287 @@ +use proc_macro::TokenStream; +use quote::{ToTokens, quote}; +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 = 0; + args + } + } + }, + default_args_factory: || { + quote! { + { + let mut args = crate::args::OpRbuilderArgs::default(); + args.flashblocks.enabled = true; + args.flashblocks.flashblocks_port = 0; + args + } + } + }, + }, +]; + +fn get_variant_info(variant: &str) -> Option<&'static VariantInfo> { + BUILDER_VARIANTS.iter().find(|v| v.name == variant) +} + +fn get_variant_names() -> Vec<&'static str> { + BUILDER_VARIANTS.iter().map(|v| v.name).collect() +} + +struct TestConfig { + variants: std::collections::HashMap>, // variant name -> custom expression (None = default) + args: Option, // Expression to pass to LocalInstance::new() + config: Option, // NodeConfig for new_with_config + multi_threaded: bool, // Whether to use multi_thread flavor +} + +impl syn::parse::Parse for TestConfig { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut config = TestConfig { + variants: std::collections::HashMap::new(), + args: None, + config: None, + multi_threaded: false, + }; + + if input.is_empty() { + // No arguments provided, generate all variants with defaults + for variant in BUILDER_VARIANTS { + config.variants.insert(variant.name.to_string(), None); + } + return Ok(config); + } + + let args: Punctuated = input.parse_terminated(Meta::parse, Token![,])?; + let variant_names = get_variant_names(); + + for arg in args { + match arg { + Meta::Path(path) => { + if let Some(ident) = path.get_ident() { + let name = ident.to_string(); + if variant_names.contains(&name.as_str()) { + config.variants.insert(name, None); + } else if name == "multi_threaded" { + config.multi_threaded = true; + } else { + return Err(syn::Error::new_spanned( + path, + format!( + "Unknown variant '{}'. Use one of: {}, 'multi_threaded', 'args', or 'config'", + name, + variant_names.join(", ") + ), + )); + } + } + } + Meta::NameValue(nv) => { + if let Some(ident) = nv.path.get_ident() { + let name = ident.to_string(); + if variant_names.contains(&name.as_str()) { + config.variants.insert(name, Some(nv.value)); + } else if name == "args" { + config.args = Some(nv.value); + } else if name == "config" { + config.config = Some(nv.value); + } else { + return Err(syn::Error::new_spanned( + nv.path, + format!( + "Unknown attribute '{}'. Use one of: {}, 'multi_threaded', 'args', or 'config'", + name, + variant_names.join(", ") + ), + )); + } + } + } + _ => { + return Err(syn::Error::new_spanned( + arg, + format!( + "Invalid attribute format. Use one of: {}, 'multi_threaded', 'args', or 'config'", + variant_names.join(", ") + ), + )); + } + } + } + + // Validate that custom expressions and args/config are not used together + for (variant, custom_expr) in &config.variants { + if custom_expr.is_some() && (config.args.is_some() || config.config.is_some()) { + return Err(syn::Error::new_spanned( + config.args.as_ref().or(config.config.as_ref()).unwrap(), + format!( + "Cannot use 'args' or 'config' with custom '{variant}' expression. Use either '{variant} = expression' or 'args/config' parameters, not both." + ), + )); + } + } + + // If only args/config/multi_threaded is specified, generate all variants + if config.variants.is_empty() + && (config.args.is_some() || config.config.is_some() || config.multi_threaded) + { + for variant in BUILDER_VARIANTS { + config.variants.insert(variant.name.to_string(), None); + } + } + + Ok(config) + } +} + +fn generate_instance_init( + variant: &str, + custom_expr: Option<&Expr>, + args: &Option, + config: &Option, +) -> proc_macro2::TokenStream { + if let Some(expr) = custom_expr { + return quote! { #expr }; + } + + 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(); + + match (args, config) { + (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? } + } + (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? + } + } + (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? + } + } + } +} + +#[proc_macro_attribute] +pub fn rb_test(args: TokenStream, input: TokenStream) -> TokenStream { + let input_fn = parse_macro_input!(input as ItemFn); + let config = parse_macro_input!(args as TestConfig); + + validate_signature(&input_fn); + + // Create the original function without test attributes (helper function) + let mut helper_fn = input_fn.clone(); + helper_fn + .attrs + .retain(|attr| !attr.path().is_ident("test") && !attr.path().is_ident("tokio")); + + let original_name = &input_fn.sig.ident; + let mut generated_functions = vec![quote! { #helper_fn }]; + + // Generate test for each requested variant + for (variant, custom_expr) in &config.variants { + let test_name = + syn::Ident::new(&format!("{original_name}_{variant}"), original_name.span()); + + let instance_init = + generate_instance_init(variant, custom_expr.as_ref(), &config.args, &config.config); + + let test_attribute = if config.multi_threaded { + quote! { #[tokio::test(flavor = "multi_thread")] } + } else { + quote! { #[tokio::test] } + }; + + generated_functions.push(quote! { + #test_attribute + async fn #test_name() -> eyre::Result<()> { + let instance = #instance_init; + #original_name(instance).await + } + }); + } + + TokenStream::from(quote! { + #(#generated_functions)* + }) +} + +fn validate_signature(item_fn: &ItemFn) { + if item_fn.sig.asyncness.is_none() { + panic!("Function must be async."); + } + if item_fn.sig.inputs.len() != 1 { + panic!("Function must have exactly one parameter of type LocalInstance."); + } + + let output_types = item_fn + .sig + .output + .to_token_stream() + .to_string() + .replace(" ", ""); + + if output_types != "->eyre::Result<()>" { + panic!("Function must return Result<(), eyre::Error>. Actual: {output_types}",); + } +} + +// Generate conditional execution macros for each variant +macro_rules! generate_if_variant_macros { + ($($variant_name:ident),*) => { + $( + paste::paste! { + #[proc_macro] + pub fn [](input: TokenStream) -> TokenStream { + let input = proc_macro2::TokenStream::from(input); + let suffix = concat!("_", stringify!($variant_name)); + + TokenStream::from(quote! { + if std::thread::current().name().unwrap_or("").ends_with(#suffix) { + #input + } + }) + } + } + )* + }; +} + +// Generate macros for all variants +generate_if_variant_macros!(standard, flashblocks); diff --git a/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs index e8488d99..d9c7bf1f 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/mod.rs @@ -26,7 +26,7 @@ pub const ONE_ETH: u128 = 1_000_000_000_000_000_000; /// This gets invoked before any tests, when the cargo test framework loads the test library. /// It injects itself into #[ctor::ctor] -fn init_tests_logging() { +fn init_tests() { use tracing_subscriber::{filter::filter_fn, prelude::*}; if let Ok(v) = std::env::var("TEST_TRACE") { let level = match v.as_str() { @@ -52,4 +52,7 @@ fn init_tests_logging() { })) .init(); } + + #[cfg(not(windows))] + let _ = rlimit::setrlimit(rlimit::Resource::NOFILE, 500_000, 500_000); } diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs index e3f96187..ecdda8bb 100644 --- a/crates/builder/op-rbuilder/src/tests/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -2,5 +2,20 @@ mod framework; pub use framework::*; +#[cfg(test)] mod flashblocks; -mod vanilla; + +#[cfg(test)] +mod data_availability; + +#[cfg(test)] +mod ordering; + +#[cfg(test)] +mod revert; + +#[cfg(test)] +mod smoke; + +#[cfg(test)] +mod txpool; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs b/crates/builder/op-rbuilder/src/tests/ordering.rs similarity index 84% rename from crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs rename to crates/builder/op-rbuilder/src/tests/ordering.rs index 74fe8967..11cc496c 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/ordering.rs +++ b/crates/builder/op-rbuilder/src/tests/ordering.rs @@ -1,11 +1,14 @@ use crate::tests::{framework::ONE_ETH, ChainDriverExt, LocalInstance}; use alloy_consensus::Transaction; use futures::{future::join_all, stream, StreamExt}; +use macros::rb_test; /// This test ensures that the transactions are ordered by fee priority in the block. -#[tokio::test] -async fn fee_priority_ordering() -> eyre::Result<()> { - let rbuilder = LocalInstance::standard().await?; +/// 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)] +async fn fee_priority_ordering(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let accounts = driver.fund_accounts(10, ONE_ETH).await?; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs b/crates/builder/op-rbuilder/src/tests/revert.rs similarity index 84% rename from crates/builder/op-rbuilder/src/tests/vanilla/revert.rs rename to crates/builder/op-rbuilder/src/tests/revert.rs index 7108a795..404c1790 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/revert.rs @@ -1,27 +1,24 @@ use alloy_provider::{PendingTransactionBuilder, Provider}; +use macros::{if_flashblocks, if_standard, rb_test}; use op_alloy_network::Optimism; use crate::{ args::OpRbuilderArgs, - builders::StandardBuilder, primitives::bundle::MAX_BLOCK_RANGE_BLOCKS, tests::{ BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, LocalInstance, - TransactionBuilderExt, ONE_ETH, + OpRbuilderArgsTestExt, TransactionBuilderExt, ONE_ETH, }, }; /// This test ensures that the transactions that get reverted and not included in the block, /// are eventually dropped from the pool once their block range is reached. /// This test creates N transactions with different block ranges. -#[tokio::test] -async fn monitor_transaction_gc() -> eyre::Result<()> { - let rbuilder = LocalInstance::new::(OpRbuilderArgs { - enable_revert_protection: true, - ..Default::default() - }) - .await?; - +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn monitor_transaction_gc(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let accounts = driver.fund_accounts(10, ONE_ETH).await?; let latest_block_number = driver.latest().await?.header.number; @@ -47,8 +44,15 @@ async fn monitor_transaction_gc() -> eyre::Result<()> { for i in 0..10 { let generated_block = driver.build_new_block().await?; - // blocks should only include two transactions (deposit + builder) - assert_eq!(generated_block.transactions.len(), 2); + if_standard! { + // standard builder blocks should only include two transactions (deposit + builder) + assert_eq!(generated_block.transactions.len(), 2); + } + + if_flashblocks! { + // flashblocks should include three transactions (deposit + builder + first flashblock) + assert_eq!(generated_block.transactions.len(), 3); + } // since we created the 10 transactions with increasing block ranges, as we generate blocks // one transaction will be gc on each block. @@ -65,9 +69,8 @@ async fn monitor_transaction_gc() -> eyre::Result<()> { } /// If revert protection is disabled, the transactions that revert are included in the block. -#[tokio::test] -async fn disabled() -> eyre::Result<()> { - let rbuilder = LocalInstance::standard().await?; +#[rb_test] +async fn disabled(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; for _ in 0..10 { @@ -93,9 +96,8 @@ async fn disabled() -> eyre::Result<()> { /// If revert protection is disabled, it should not be possible to send a revert bundle /// since the revert RPC endpoint is not available. -#[tokio::test] -async fn disabled_bundle_endpoint_error() -> eyre::Result<()> { - let rbuilder = LocalInstance::standard().await?; +#[rb_test] +async fn disabled_bundle_endpoint_error(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let res = driver @@ -115,14 +117,11 @@ async fn disabled_bundle_endpoint_error() -> eyre::Result<()> { /// the transaction is included in the block. If the bundle reverts, the transaction /// is not included in the block and tried again for the next bundle range blocks /// when it will be dropped from the pool. -#[tokio::test] -async fn bundle() -> eyre::Result<()> { - let rbuilder = LocalInstance::new::(OpRbuilderArgs { - enable_revert_protection: true, - ..Default::default() - }) - .await?; - +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn bundle(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let _ = driver.build_new_block().await?; // Block 1 @@ -170,14 +169,11 @@ async fn bundle() -> eyre::Result<()> { } /// Test the behaviour of the revert protection bundle with a min block number. -#[tokio::test] -async fn bundle_min_block_number() -> eyre::Result<()> { - let rbuilder = LocalInstance::new::(OpRbuilderArgs { - enable_revert_protection: true, - ..Default::default() - }) - .await?; - +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn bundle_min_block_number(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; // The bundle is valid when the min block number is equal to the current block @@ -224,14 +220,11 @@ async fn bundle_min_block_number() -> eyre::Result<()> { } /// Test the behaviour of the revert protection bundle with a min timestamp. -#[tokio::test] -async fn revert_protection_bundle_min_timestamp() -> eyre::Result<()> { - let rbuilder = LocalInstance::new::(OpRbuilderArgs { - enable_revert_protection: true, - ..Default::default() - }) - .await?; - +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn bundle_min_timestamp(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let initial_timestamp = driver.latest().await?.header.timestamp; @@ -258,14 +251,11 @@ async fn revert_protection_bundle_min_timestamp() -> eyre::Result<()> { } /// Test the range limits for the revert protection bundle. -#[tokio::test] -async fn bundle_range_limits() -> eyre::Result<()> { - let rbuilder = LocalInstance::new::(OpRbuilderArgs { - enable_revert_protection: true, - ..Default::default() - }) - .await?; - +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn bundle_range_limits(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let _ = driver.build_new_block().await?; // Block 1 let _ = driver.build_new_block().await?; // Block 2 @@ -349,14 +339,11 @@ async fn bundle_range_limits() -> eyre::Result<()> { /// If a transaction reverts and was sent as a normal transaction through the eth_sendRawTransaction /// bundle, the transaction should be included in the block. /// This behaviour is the same as the 'disabled' test. -#[tokio::test] -async fn allow_reverted_transactions_without_bundle() -> eyre::Result<()> { - let rbuilder = LocalInstance::new::(OpRbuilderArgs { - enable_revert_protection: true, - ..Default::default() - }) - .await?; - +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..Default::default() +})] +async fn allow_reverted_transactions_without_bundle(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; for _ in 0..10 { @@ -381,14 +368,11 @@ async fn allow_reverted_transactions_without_bundle() -> eyre::Result<()> { /// If a transaction reverts and gets dropped it, the eth_getTransactionReceipt should return /// an error message that it was dropped. -#[tokio::test] -async fn check_transaction_receipt_status_message() -> eyre::Result<()> { - let rbuilder = LocalInstance::new::(OpRbuilderArgs { - enable_revert_protection: true, - ..Default::default() - }) - .await?; - +#[rb_test(args = OpRbuilderArgs { + enable_revert_protection: true, + ..OpRbuilderArgs::test_default() +})] +async fn check_transaction_receipt_status_message(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let provider = rbuilder.provider().await?; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs b/crates/builder/op-rbuilder/src/tests/smoke.rs similarity index 75% rename from crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs rename to crates/builder/op-rbuilder/src/tests/smoke.rs index 8fd537e2..d92a7c65 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/smoke.rs @@ -4,6 +4,7 @@ use core::{ sync::atomic::{AtomicBool, Ordering}, time::Duration, }; +use macros::{if_flashblocks, if_standard, rb_test}; use std::collections::HashSet; use tokio::{join, task::yield_now}; use tracing::info; @@ -13,9 +14,8 @@ use tracing::info; /// /// Generated blocks are also validated against an external op-reth node to /// ensure their correctness. -#[tokio::test] -async fn chain_produces_blocks() -> eyre::Result<()> { - let rbuilder = LocalInstance::standard().await?; +#[rb_test] +async fn chain_produces_blocks(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; #[cfg(target_os = "linux")] @@ -32,11 +32,23 @@ async fn chain_produces_blocks() -> eyre::Result<()> { let block = driver.build_new_block().await?; let transactions = block.transactions; - assert_eq!( - transactions.len(), - 2, - "Empty blocks should have exactly two transactions" - ); + if_standard! { + assert_eq!( + transactions.len(), + 2, + "Empty blocks should have exactly two transactions" + ); + } + + if_flashblocks! { + // in flashblocks we add an additional transaction on the first + // flashblocks and then one on the last flashblock + assert_eq!( + transactions.len(), + 3, + "Empty blocks should have exactly three transactions" + ); + } } // ensure that transactions are included in blocks and each block has all the transactions @@ -59,12 +71,26 @@ async fn chain_produces_blocks() -> eyre::Result<()> { let txs = block.transactions; - assert_eq!( - txs.len(), - 2 + count, - "Block should have {} transactions", - 2 + count - ); + if_standard! { + assert_eq!( + txs.len(), + 2 + count, + "Block should have {} transactions", + 2 + count + ); + } + + if_flashblocks! { + // in flashblocks we add an additional transaction on the first + // flashblocks and then one on the last flashblock, so it will have + // one more transaction than the standard builder + assert_eq!( + txs.len(), + 3 + count, + "Block should have {} transactions", + 3 + count + ); + } for tx_hash in tx_hashes { assert!( @@ -79,9 +105,8 @@ async fn chain_produces_blocks() -> eyre::Result<()> { /// Ensures that payloads are generated correctly even when the builder is busy /// with other requests, such as fcu or getPayload. -#[tokio::test(flavor = "multi_thread")] -async fn produces_blocks_under_load_within_deadline() -> eyre::Result<()> { - let rbuilder = LocalInstance::standard().await?; +#[rb_test(multi_threaded)] +async fn produces_blocks_under_load_within_deadline(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?.with_gas_limit(10_00_000); let done = AtomicBool::new(false); diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs b/crates/builder/op-rbuilder/src/tests/txpool.rs similarity index 75% rename from crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs rename to crates/builder/op-rbuilder/src/tests/txpool.rs index be8aae12..41f9b736 100644 --- a/crates/builder/op-rbuilder/src/tests/vanilla/txpool.rs +++ b/crates/builder/op-rbuilder/src/tests/txpool.rs @@ -1,26 +1,23 @@ -use crate::{ - builders::StandardBuilder, - tests::{default_node_config, BlockTransactionsExt, ChainDriverExt, LocalInstance, ONE_ETH}, +use crate::tests::{ + default_node_config, BlockTransactionsExt, ChainDriverExt, LocalInstance, ONE_ETH, }; +use macros::rb_test; use reth::args::TxPoolArgs; use reth_node_builder::NodeConfig; use reth_optimism_chainspec::OpChainSpec; /// This test ensures that pending pool custom limit is respected and priority tx would be included even when pool if full. -#[tokio::test] -async fn pending_pool_limit() -> eyre::Result<()> { - let rbuilder = LocalInstance::new_with_config::( - Default::default(), - NodeConfig:: { - txpool: TxPoolArgs { - pending_max_count: 50, - ..Default::default() - }, - ..default_node_config() +#[rb_test( + config = NodeConfig:: { + txpool: TxPoolArgs { + pending_max_count: 50, + ..Default::default() }, - ) - .await?; - + ..default_node_config() + }, + standard +)] +async fn pending_pool_limit(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let accounts = driver.fund_accounts(50, ONE_ETH).await?; diff --git a/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs b/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs deleted file mode 100644 index 5eb1364e..00000000 --- a/crates/builder/op-rbuilder/src/tests/vanilla/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![cfg(test)] - -mod data_availability; -mod ordering; -mod revert; -mod smoke; -mod txpool; From c3e66d845bf6435e7f70363616beffb8ed21cc33 Mon Sep 17 00:00:00 2001 From: Denis Kolodin Date: Tue, 17 Jun 2025 17:08:13 +0200 Subject: [PATCH 141/262] fix: add default-run to the op-rbuilder's manifest (#162) --- crates/builder/op-rbuilder/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 869a7240..23a50b5c 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -6,6 +6,7 @@ rust-version.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true +default-run = "op-rbuilder" [dependencies] reth.workspace = true From e6ecf7bca18e87d9b5f653eadd00f63ad0598a20 Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 17 Jun 2025 10:10:02 -0700 Subject: [PATCH 142/262] Flashtestions (#137) * wip flashtestations * add builder tx interface * add bootstrap on launch * transaction management * working bootstrap * fix github action * address comments * fix cli flags * make flashtestations service non-blocking --------- Co-authored-by: mophr --- crates/builder/op-rbuilder/Cargo.toml | 6 + crates/builder/op-rbuilder/src/args/op.rs | 4 +- .../op-rbuilder/src/args/playground.rs | 2 +- .../op-rbuilder/src/builders/builder_tx.rs | 32 +++ .../src/builders/flashblocks/payload.rs | 23 +- .../src/builders/flashblocks/service.rs | 67 ++++- .../builder/op-rbuilder/src/builders/mod.rs | 10 + .../src/builders/standard/payload.rs | 29 ++ .../op-rbuilder/src/flashtestations/args.rs | 76 +++++ .../src/flashtestations/attestation.rs | 90 ++++++ .../op-rbuilder/src/flashtestations/mod.rs | 4 + .../src/flashtestations/service.rs | 180 ++++++++++++ .../src/flashtestations/tx_manager.rs | 269 ++++++++++++++++++ crates/builder/op-rbuilder/src/launcher.rs | 1 - crates/builder/op-rbuilder/src/lib.rs | 1 + crates/builder/op-rbuilder/src/tx_signer.rs | 85 +++++- 16 files changed, 854 insertions(+), 25 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/builders/builder_tx.rs create mode 100644 crates/builder/op-rbuilder/src/flashtestations/args.rs create mode 100644 crates/builder/op-rbuilder/src/flashtestations/attestation.rs create mode 100644 crates/builder/op-rbuilder/src/flashtestations/mod.rs create mode 100644 crates/builder/op-rbuilder/src/flashtestations/service.rs create mode 100644 crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 23a50b5c..8464c0e9 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -55,6 +55,7 @@ reth-optimism-rpc.workspace = true reth-tasks.workspace = true reth-tracing-otlp = { workspace = true, optional = true } +alloy.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true alloy-eips.workspace = true @@ -115,8 +116,13 @@ shellexpand = "3.1" serde_yaml = { version = "0.9" } moka = "0.12" http = "1.0" +sha3 = "0.10" +hex = "0.4" +ureq = "2.10" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } +tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git"} +dcap-rs = { git = "https://github.com/automata-network/dcap-rs.git" } dashmap = { version = "6.1", optional = true } nanoid = { version = "0.4", optional = true } diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index fa791189..784aeb7c 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -4,7 +4,7 @@ //! clap [Args](clap::Args) for optimism rollup configuration -use crate::tx_signer::Signer; +use crate::{flashtestations::args::FlashtestationsArgs, tx_signer::Signer}; use anyhow::{anyhow, Result}; use clap::Parser; use reth_optimism_cli::commands::Commands; @@ -54,6 +54,8 @@ pub struct OpRbuilderArgs { pub flashblocks: FlashblocksArgs, #[command(flatten)] pub telemetry: TelemetryArgs, + #[command(flatten)] + pub flashtestations: FlashtestationsArgs, } impl Default for OpRbuilderArgs { diff --git a/crates/builder/op-rbuilder/src/args/playground.rs b/crates/builder/op-rbuilder/src/args/playground.rs index b02a0f4c..b836f45b 100644 --- a/crates/builder/op-rbuilder/src/args/playground.rs +++ b/crates/builder/op-rbuilder/src/args/playground.rs @@ -1,4 +1,4 @@ -//! Autmoatic builder playground configuration. +//! Automatic builder playground configuration. //! //! This module is used mostly for testing purposes. It allows op-rbuilder to //! automatically configure itself to run against a running op-builder playground. diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs new file mode 100644 index 00000000..3dcb8984 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -0,0 +1,32 @@ +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::Recovered; + +use crate::tx_signer::Signer; + +pub trait BuilderTx { + fn estimated_builder_tx_gas(&self) -> u64; + fn estimated_builder_tx_da_size(&self) -> Option; + fn signed_builder_tx(&self) -> Result, secp256k1::Error>; +} + +// Scaffolding for how to construct the end of block builder transaction +// This will be the regular end of block transaction without the TEE key +#[derive(Clone)] +pub struct StandardBuilderTx { + #[allow(dead_code)] + pub signer: Option, +} + +impl BuilderTx for StandardBuilderTx { + fn estimated_builder_tx_gas(&self) -> u64 { + todo!() + } + + fn estimated_builder_tx_da_size(&self) -> Option { + todo!() + } + + fn signed_builder_tx(&self) -> Result, secp256k1::Error> { + todo!() + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 256d2756..56c1cb18 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -4,7 +4,7 @@ use crate::{ context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}, flashblocks::config::FlashBlocksConfigExt, generator::{BlockCell, BuildArguments}, - BuilderConfig, + BuilderConfig, BuilderTx, }, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, @@ -56,7 +56,7 @@ struct ExtraExecutionInfo { /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct OpPayloadBuilder { +pub struct OpPayloadBuilder { /// The type responsible for creating the evm. pub evm_config: OpEvmConfig, /// The transaction pool @@ -70,19 +70,22 @@ pub struct OpPayloadBuilder { pub config: BuilderConfig, /// The metrics for the builder pub metrics: Arc, + /// The end of builder transaction type + #[allow(dead_code)] + pub builder_tx: BT, } -impl OpPayloadBuilder { +impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. pub fn new( evm_config: OpEvmConfig, pool: Pool, client: Client, config: BuilderConfig, + builder_tx: BT, ) -> eyre::Result { let metrics = Arc::new(OpRBuilderMetrics::default()); let ws_pub = WebSocketPublisher::new(config.specific.ws_addr, Arc::clone(&metrics))?.into(); - Ok(Self { evm_config, pool, @@ -90,14 +93,17 @@ impl OpPayloadBuilder { ws_pub, config, metrics, + builder_tx, }) } } -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, + BT: Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; @@ -120,10 +126,11 @@ where } } -impl OpPayloadBuilder +impl OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, + BT: BuilderTx, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -589,10 +596,12 @@ where } } -impl crate::builders::generator::PayloadBuilder for OpPayloadBuilder +impl crate::builders::generator::PayloadBuilder + for OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, + BT: BuilderTx + Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index 210b7bb6..0fb8d368 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -1,6 +1,10 @@ use super::{payload::OpPayloadBuilder, FlashblocksConfig}; use crate::{ - builders::{generator::BlockPayloadJobGenerator, BuilderConfig}, + builders::{ + builder_tx::StandardBuilderTx, generator::BlockPayloadJobGenerator, BuilderConfig, + BuilderTx, + }, + flashtestations::service::spawn_flashtestations_service, traits::{NodeBounds, PoolBounds}, }; use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; @@ -12,24 +16,24 @@ use reth_provider::CanonStateSubscriptions; pub struct FlashblocksServiceBuilder(pub BuilderConfig); -impl PayloadServiceBuilder for FlashblocksServiceBuilder -where - Node: NodeBounds, - Pool: PoolBounds, -{ - async fn spawn_payload_builder_service( +impl FlashblocksServiceBuilder { + fn spawn_payload_builder_service( self, ctx: &BuilderContext, pool: Pool, - _: OpEvmConfig, - ) -> eyre::Result::Payload>> { - tracing::debug!("Spawning flashblocks payload builder service"); - + builder_tx: BT, + ) -> eyre::Result::Payload>> + where + Node: NodeBounds, + Pool: PoolBounds, + BT: BuilderTx + Unpin + Clone + Send + Sync + 'static, + { let payload_builder = OpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), pool, ctx.provider().clone(), self.0.clone(), + builder_tx, )?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); @@ -54,3 +58,44 @@ where Ok(payload_builder) } } + +impl PayloadServiceBuilder for FlashblocksServiceBuilder +where + Node: NodeBounds, + Pool: PoolBounds, +{ + async fn spawn_payload_builder_service( + self, + ctx: &BuilderContext, + pool: Pool, + _: OpEvmConfig, + ) -> eyre::Result::Payload>> { + tracing::debug!("Spawning flashblocks payload builder service"); + let signer = self.0.builder_signer; + if self.0.flashtestations_config.flashtestations_enabled { + let funding_signer = signer.expect("Key to fund TEE generated address not set"); + let flashtestations_service = match spawn_flashtestations_service( + self.0.flashtestations_config.clone(), + funding_signer, + ctx, + ) + .await + { + Ok(service) => service, + Err(e) => { + tracing::warn!(error = %e, "Failed to spawn flashtestations service, falling back to standard builder tx"); + return self.spawn_payload_builder_service( + ctx, + pool, + StandardBuilderTx { signer }, + ); + } + }; + + if self.0.flashtestations_config.enable_block_proofs { + return self.spawn_payload_builder_service(ctx, pool, flashtestations_service); + } + } + self.spawn_payload_builder_service(ctx, pool, StandardBuilderTx { signer }) + } +} diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 926fd842..1c41f370 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -9,15 +9,18 @@ use reth_optimism_payload_builder::config::OpDAConfig; use crate::{ args::OpRbuilderArgs, + flashtestations::args::FlashtestationsArgs, traits::{NodeBounds, PoolBounds}, tx_signer::Signer, }; +mod builder_tx; mod context; mod flashblocks; mod generator; mod standard; +pub use builder_tx::BuilderTx; pub use flashblocks::FlashblocksBuilder; pub use standard::StandardBuilder; @@ -73,6 +76,10 @@ pub struct BuilderConfig { /// opt-out of revert protection. pub revert_protection: bool, + /// When enabled, this will invoke the flashtestions workflow. This involves a + /// bootstrapping step that generates a new pubkey for the TEE service + pub flashtestations_config: FlashtestationsArgs, + /// 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. @@ -118,6 +125,7 @@ impl core::fmt::Debug for BuilderConfig { }, ) .field("revert_protection", &self.revert_protection) + .field("flashtestations", &self.flashtestations_config) .field("block_time", &self.block_time) .field("block_time_leeway", &self.block_time_leeway) .field("da_config", &self.da_config) @@ -131,6 +139,7 @@ impl Default for BuilderConfig { Self { builder_signer: None, revert_protection: false, + flashtestations_config: FlashtestationsArgs::default(), block_time: Duration::from_secs(2), block_time_leeway: Duration::from_millis(500), da_config: OpDAConfig::default(), @@ -150,6 +159,7 @@ where Ok(Self { builder_signer: args.builder_signer, revert_protection: args.enable_revert_protection, + flashtestations_config: args.flashtestations.clone(), block_time: Duration::from_millis(args.chain_block_time), block_time_leeway: Duration::from_secs(args.extra_block_deadline_secs), da_config: Default::default(), diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 45f4ffca..eaf98c2c 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -1,5 +1,6 @@ use crate::{ builders::{generator::BuildArguments, BuilderConfig}, + flashtestations::service::spawn_flashtestations_service, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, @@ -52,6 +53,34 @@ where pool: Pool, _evm_config: OpEvmConfig, ) -> eyre::Result { + let signer = self.0.builder_signer; + + if self.0.flashtestations_config.flashtestations_enabled { + let funding_signer = signer.expect("Key to fund TEE generated address not set"); + match spawn_flashtestations_service( + self.0.flashtestations_config.clone(), + funding_signer, + ctx, + ) + .await + { + Ok(service) => service, + Err(e) => { + tracing::warn!(error = %e, "Failed to spawn flashtestations service, falling back to standard builder tx"); + return Ok(StandardOpPayloadBuilder::new( + OpEvmConfig::optimism(ctx.chain_spec()), + pool, + ctx.provider().clone(), + self.0.clone(), + )); + } + }; + + if self.0.flashtestations_config.enable_block_proofs { + // TODO: flashtestations end of block transaction + } + } + Ok(StandardOpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), pool, diff --git a/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs new file mode 100644 index 00000000..f734392b --- /dev/null +++ b/crates/builder/op-rbuilder/src/flashtestations/args.rs @@ -0,0 +1,76 @@ +use alloy_primitives::{utils::parse_ether, Address, U256}; + +/// Parameters for Flashtestations configuration +/// The names in the struct are prefixed with `flashtestations` +#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +pub struct FlashtestationsArgs { + /// When set to true, the builder will initiate the flashtestations + /// workflow within the bootstrapping and block building process. + #[arg( + long = "flashtestations.enabled", + default_value = "false", + env = "ENABLE_FLASHTESTATIONS" + )] + pub flashtestations_enabled: bool, + + /// Whether to use the debug HTTP service for quotes + #[arg( + long = "flashtestations.debug", + default_value = "false", + env = "FLASHTESTATIONS_DEBUG" + )] + pub debug: bool, + + // Debug url for attestations + #[arg(long = "flashtestations.debug-url", env = "FLASHTESTATIONS_DEBUG_URL")] + pub debug_url: Option, + + /// The rpc url to post the onchain attestation requests to + #[arg( + long = "flashtestations.rpc-url", + env = "FLASHTESTATIONS_RPC_URL", + default_value = "http://localhost:8545" + )] + pub rpc_url: String, + + /// Funding amount for the generated signer + #[arg( + long = "flashtestations.funding-amount", + env = "FLASHTESTATIONS_FUNDING_AMOUNT", + default_value = "1", + value_parser = parse_ether + )] + pub funding_amount: U256, + + /// Enable end of block TEE proof + #[arg( + long = "flashtestations.enable-block-proofs", + env = "FLASHTESTATIONS_ENABLE_BLOCK_PROOFS", + default_value = "false" + )] + pub enable_block_proofs: bool, + + /// The address of the flashtestations registry contract + #[arg( + long = "flashtestations.registry-address", + env = "FLASHTESTATIONS_REGISTRY_ADDRESS", + required_if_eq("flashtestations_enabled", "true") + )] + pub registry_address: Option
, + + /// The address of the builder policy contract + #[arg( + long = "flashtestations.builder-policy-address", + env = "FLASHTESTATIONS_BUILDER_POLICY_ADDRESS", + required_if_eq("flashtestations_enabled", "true") + )] + pub builder_policy_address: Option
, + + /// The version of the block builder verification proof + #[arg( + long = "flashtestations.builder-proof-version", + env = "FLASHTESTATIONS_BUILDER_PROOF_VERSION", + default_value = "1" + )] + pub builder_proof_version: u8, +} diff --git a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs new file mode 100644 index 00000000..4119f444 --- /dev/null +++ b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs @@ -0,0 +1,90 @@ +use std::io::Read; +use tdx::{device::DeviceOptions, Tdx}; +use tracing::info; +use ureq; + +const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/attest"; + +/// Configuration for attestation +#[derive(Default)] +pub struct AttestationConfig { + /// If true, uses the debug HTTP service instead of real TDX hardware + pub debug: bool, + /// The URL of the debug HTTP service + pub debug_url: Option, +} + +/// Trait for attestation providers +pub trait AttestationProvider { + fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result>; +} + +/// Real TDX hardware attestation provider +pub struct TdxAttestationProvider { + tdx: Tdx, +} + +impl Default for TdxAttestationProvider { + fn default() -> Self { + Self::new() + } +} + +impl TdxAttestationProvider { + pub fn new() -> Self { + Self { tdx: Tdx::new() } + } +} + +impl AttestationProvider for TdxAttestationProvider { + fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { + self.tdx + .get_attestation_report_raw_with_options(DeviceOptions { + report_data: Some(report_data), + }) + .map_err(|e| e.into()) + } +} + +/// Debug HTTP service attestation provider +pub struct DebugAttestationProvider { + service_url: String, +} + +impl DebugAttestationProvider { + pub fn new(service_url: String) -> Self { + Self { service_url } + } +} + +impl AttestationProvider for DebugAttestationProvider { + fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { + let report_data_hex = hex::encode(report_data); + let url = format!("{}/{}", self.service_url, report_data_hex); + + info!(target: "flashtestations", url = url, "fetching quote in debug mode"); + + let response = ureq::get(&url) + .timeout(std::time::Duration::from_secs(10)) + .call()?; + + let mut body = Vec::new(); + response.into_reader().read_to_end(&mut body)?; + + Ok(body) + } +} + +pub fn get_attestation_provider( + config: AttestationConfig, +) -> Box { + if config.debug { + Box::new(DebugAttestationProvider::new( + config + .debug_url + .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), + )) + } else { + Box::new(TdxAttestationProvider::new()) + } +} diff --git a/crates/builder/op-rbuilder/src/flashtestations/mod.rs b/crates/builder/op-rbuilder/src/flashtestations/mod.rs new file mode 100644 index 00000000..a5b16e73 --- /dev/null +++ b/crates/builder/op-rbuilder/src/flashtestations/mod.rs @@ -0,0 +1,4 @@ +pub mod args; +pub mod attestation; +pub mod service; +pub mod tx_manager; diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs new file mode 100644 index 00000000..df82de6b --- /dev/null +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -0,0 +1,180 @@ +use std::sync::Arc; + +use alloy_primitives::U256; +use reth_node_builder::BuilderContext; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::Recovered; +use tracing::{info, warn}; + +use crate::{ + builders::BuilderTx, + traits::NodeBounds, + tx_signer::{generate_ethereum_keypair, Signer}, +}; + +use super::{ + args::FlashtestationsArgs, + attestation::{get_attestation_provider, AttestationConfig, AttestationProvider}, + tx_manager::TxManager, +}; + +#[derive(Clone)] +pub struct FlashtestationsService { + // Attestation provider generating attestations + attestation_provider: Arc>, + // Handles the onchain attestation and TEE block building proofs + tx_manager: TxManager, + // TEE service generated key + tee_service_signer: Signer, + // Funding amount for the TEE signer + funding_amount: U256, +} + +// TODO: FlashtestationsService error types +impl FlashtestationsService { + pub fn new(args: FlashtestationsArgs, funding_signer: Signer) -> Self { + let (private_key, public_key, address) = generate_ethereum_keypair(); + let tee_service_signer = Signer { + address, + pubkey: public_key, + secret: private_key, + }; + + let attestation_provider = Arc::new(get_attestation_provider(AttestationConfig { + debug: args.debug, + debug_url: args.debug_url, + })); + + let tx_manager = TxManager::new( + tee_service_signer, + funding_signer, + args.rpc_url, + args.registry_address + .expect("registry address required when flashtestations enabled"), + args.builder_policy_address + .expect("builder policy address required when flashtestations enabled"), + args.builder_proof_version, + ); + + Self { + attestation_provider, + tx_manager, + tee_service_signer, + funding_amount: args.funding_amount, + } + } + + pub async fn bootstrap(&self) -> eyre::Result<()> { + // Prepare report data with public key (64 bytes, no 0x04 prefix) + let mut report_data = [0u8; 64]; + let pubkey_uncompressed = self.tee_service_signer.pubkey.serialize_uncompressed(); + report_data.copy_from_slice(&pubkey_uncompressed[1..65]); // Skip 0x04 prefix + + // Request TDX attestation + info!(target: "flashtestations", "requesting TDX attestation"); + let attestation = self.attestation_provider.get_attestation(report_data)?; + + // Submit report onchain by registering the key of the tee service + self.tx_manager + .fund_and_register_tee_service(attestation, self.funding_amount) + .await + } + + pub async fn clean_up(&self) -> eyre::Result<()> { + self.tx_manager.clean_up().await + } +} + +impl BuilderTx for FlashtestationsService { + fn estimated_builder_tx_gas(&self) -> u64 { + todo!() + } + + fn estimated_builder_tx_da_size(&self) -> Option { + todo!() + } + + fn signed_builder_tx(&self) -> Result, secp256k1::Error> { + todo!() + } +} + +pub async fn spawn_flashtestations_service( + args: FlashtestationsArgs, + funding_signer: Signer, + ctx: &BuilderContext, +) -> eyre::Result +where + Node: NodeBounds, +{ + info!("Flashtestations enabled"); + + let flashtestations_service = FlashtestationsService::new(args.clone(), funding_signer); + // Generates new key and registers the attestation onchain + flashtestations_service.bootstrap().await?; + + let flashtestations_clone = flashtestations_service.clone(); + ctx.task_executor() + .spawn_critical_with_graceful_shutdown_signal( + "flashtestations clean up task", + |shutdown| { + Box::pin(async move { + let graceful_guard = shutdown.await; + if let Err(e) = flashtestations_clone.clean_up().await { + warn!( + error = %e, + "Failed to complete clean up for flashtestations service", + ) + }; + drop(graceful_guard) + }) + }, + ); + + Ok(flashtestations_service) +} + +#[cfg(test)] +mod tests { + use alloy_primitives::Address; + use secp256k1::{PublicKey, Secp256k1, SecretKey}; + use sha3::{Digest, Keccak256}; + + use crate::tx_signer::public_key_to_address; + + /// Derives Ethereum address from report data using the same logic as the Solidity contract + fn derive_ethereum_address_from_report_data(pubkey_64_bytes: &[u8]) -> Address { + // This exactly matches the Solidity implementation: + // address(uint160(uint256(keccak256(reportData)))) + + // Step 1: keccak256(reportData) + let hash = Keccak256::digest(pubkey_64_bytes); + + // Step 2: Take last 20 bytes (same as uint256 -> uint160 conversion) + let mut address_bytes = [0u8; 20]; + address_bytes.copy_from_slice(&hash[12..32]); + + Address::from(address_bytes) + } + + #[test] + fn test_address_derivation_matches() { + // Test that our manual derivation is correct + let secp = Secp256k1::new(); + let private_key = SecretKey::from_slice(&[0x01; 32]).unwrap(); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + + // Get address using our implementation + let our_address = public_key_to_address(&public_key); + + // Get address using our manual derivation (matching Solidity) + let pubkey_bytes = public_key.serialize_uncompressed(); + let report_data = &pubkey_bytes[1..65]; // Skip 0x04 prefix + let manual_address = derive_ethereum_address_from_report_data(report_data); + + assert_eq!( + our_address, manual_address, + "Address derivation should match" + ); + } +} diff --git a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs new file mode 100644 index 00000000..5f76d4a7 --- /dev/null +++ b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs @@ -0,0 +1,269 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::Encodable2718; +use alloy_network::ReceiptResponse; +use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_transport::TransportResult; +use op_alloy_consensus::OpTypedTransaction; +use reth_optimism_node::OpBuiltPayload; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::Recovered; +use std::time::Duration; + +use alloy::{ + signers::local::PrivateKeySigner, + sol, + sol_types::{SolCall, SolValue}, +}; +use alloy_provider::{PendingTransactionBuilder, Provider, ProviderBuilder}; +use op_alloy_network::Optimism; +use tracing::{debug, error, info}; + +use crate::tx_signer::Signer; + +sol!( + #[sol(rpc, abi)] + interface IFlashtestationRegistry { + function registerTEEService(bytes calldata rawQuote) external; + } + + #[sol(rpc, abi)] + interface IBlockBuilderPolicy { + function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + } + + struct BlockData { + bytes32 parentHash; + uint256 blockNumber; + uint256 timestamp; + bytes32[] transactionHashes; + } +); + +#[derive(Debug, Clone)] +pub struct TxManager { + tee_service_signer: Signer, + funding_signer: Signer, + rpc_url: String, + registry_address: Address, + builder_policy_address: Address, + builder_proof_version: u8, +} + +impl TxManager { + pub fn new( + tee_service_signer: Signer, + funding_signer: Signer, + rpc_url: String, + registry_address: Address, + builder_policy_address: Address, + builder_proof_version: u8, + ) -> Self { + Self { + tee_service_signer, + funding_signer, + rpc_url, + registry_address, + builder_policy_address, + builder_proof_version, + } + } + + pub async fn fund_address(&self, from: Signer, to: Address, amount: U256) -> eyre::Result<()> { + let funding_wallet = PrivateKeySigner::from_bytes(&from.secret.secret_bytes().into())?; + let provider = ProviderBuilder::new() + .disable_recommended_fillers() + .fetch_chain_id() + .with_gas_estimation() + .with_cached_nonce_management() + .wallet(funding_wallet) + .network::() + .connect(self.rpc_url.as_str()) + .await?; + + // Create funding transaction + let funding_tx = alloy::rpc::types::TransactionRequest { + from: Some(from.address), + to: Some(TxKind::Call(to)), + value: Some(amount), + gas: Some(21_000), // Standard gas for ETH transfer + ..Default::default() + }; + + // Send funding transaction + match Self::process_pending_tx(provider.send_transaction(funding_tx.into()).await).await { + Ok(tx_hash) => { + info!(target: "flashtestations", tx_hash = %tx_hash, "funding transaction confirmed successfully"); + } + Err(e) => { + error!(target: "flashtestations", error = %e, "funding transaction failed"); + return Err(e); + } + } + + Ok(()) + } + + pub async fn fund_and_register_tee_service( + &self, + attestation: Vec, + funding_amount: U256, + ) -> eyre::Result<()> { + info!(target: "flashtestations", "funding TEE address at {}", self.tee_service_signer.address); + self.fund_address( + self.funding_signer, + self.tee_service_signer.address, + funding_amount, + ) + .await?; + + let quote_bytes = Bytes::from(attestation); + let wallet = + PrivateKeySigner::from_bytes(&self.tee_service_signer.secret.secret_bytes().into())?; + let provider = ProviderBuilder::new() + .disable_recommended_fillers() + .fetch_chain_id() + .with_gas_estimation() + .wallet(wallet) + .network::() + .connect(self.rpc_url.as_str()) + .await?; + + info!(target: "flashtestations", "submitting quote to registry at {}", self.registry_address); + + // TODO: add retries + let calldata = IFlashtestationRegistry::registerTEEServiceCall { + rawQuote: quote_bytes, + } + .abi_encode(); + let tx = alloy::rpc::types::TransactionRequest { + from: Some(self.tee_service_signer.address), + to: Some(TxKind::Call(self.registry_address)), + // gas: Some(10_000_000), // Set gas limit manually as the contract is gas heavy + nonce: Some(0), + input: calldata.into(), + ..Default::default() + }; + match Self::process_pending_tx(provider.send_transaction(tx.into()).await).await { + Ok(tx_hash) => { + info!(target: "flashtestations", tx_hash = %tx_hash, "attestation transaction confirmed successfully"); + Ok(()) + } + Err(e) => { + error!(target: "flashtestations", error = %e, "attestation transaction failed to be sent"); + Err(e) + } + } + } + + pub fn signed_block_builder_proof( + &self, + payload: OpBuiltPayload, + gas_limit: u64, + base_fee: u64, + chain_id: u64, + nonce: u64, + ) -> Result, secp256k1::Error> { + let block_content_hash = Self::compute_block_content_hash(payload); + + info!(target: "flashtestations", block_content_hash = ?block_content_hash, "submitting block builder proof transaction"); + let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { + version: self.builder_proof_version, + blockContentHash: block_content_hash, + } + .abi_encode(); + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas: base_fee.into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(self.builder_policy_address), + input: calldata.into(), + ..Default::default() + }); + self.tee_service_signer.sign_tx(tx) + } + + pub async fn clean_up(&self) -> eyre::Result<()> { + info!(target: "flashtestations", "sending funds back from TEE generated key to funding address"); + let provider = ProviderBuilder::new() + .disable_recommended_fillers() + .network::() + .connect(self.rpc_url.as_str()) + .await?; + let balance = provider + .get_balance(self.tee_service_signer.address) + .await?; + let gas_estimate = 21_000u128; + let gas_price = provider.get_gas_price().await?; + let gas_cost = U256::from(gas_estimate * gas_price); + if balance.gt(&gas_cost) { + self.fund_address( + self.tee_service_signer, + self.funding_signer.address, + balance.saturating_sub(gas_cost), + ) + .await? + } + Ok(()) + } + + /// Processes a pending transaction and logs whether the transaction succeeded or not + async fn process_pending_tx( + pending_tx_result: TransportResult>, + ) -> eyre::Result { + match pending_tx_result { + Ok(pending_tx) => { + let tx_hash = *pending_tx.tx_hash(); + debug!(target: "flashtestations", tx_hash = %tx_hash, "transaction submitted"); + + // Wait for funding transaction confirmation + match pending_tx + .with_timeout(Some(Duration::from_secs(30))) + .get_receipt() + .await + { + Ok(receipt) => { + if receipt.status() { + Ok(receipt.transaction_hash()) + } else { + Err(eyre::eyre!("Transaction reverted: {}", tx_hash)) + } + } + Err(e) => Err(e.into()), + } + } + Err(e) => Err(e.into()), + } + } + + /// Computes the block content hash according to the formula: + /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) + fn compute_block_content_hash(payload: OpBuiltPayload) -> B256 { + let block = payload.block(); + let body = block.clone().into_body(); + let transactions = body.transactions(); + + // Create ordered list of transaction hashes + let transaction_hashes: Vec = transactions + .map(|tx| { + // RLP encode the transaction and hash it + let mut encoded = Vec::new(); + tx.encode_2718(&mut encoded); + keccak256(&encoded) + }) + .collect(); + + // Create struct and ABI encode + let block_data = BlockData { + parentHash: block.parent_hash, + blockNumber: U256::from(block.number), + timestamp: U256::from(block.timestamp), + transactionHashes: transaction_hashes, + }; + + let encoded = block_data.abi_encode(); + keccak256(&encoded) + } +} diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index 1c496517..29f5bd7c 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -166,7 +166,6 @@ where let task = monitor_tx_pool(listener, reverted_cache_copy); ctx.task_executor.spawn_critical("txlogging", task); } - Ok(()) }) .launch() diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index 0305ed08..a11d81c6 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,5 +1,6 @@ pub mod args; pub mod builders; +pub mod flashtestations; pub mod launcher; pub mod metrics; mod monitor_tx_pool; diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs index bbfb77ff..f10e5d41 100644 --- a/crates/builder/op-rbuilder/src/tx_signer.rs +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -4,14 +4,16 @@ use alloy_consensus::SignableTransaction; use alloy_primitives::{Address, Signature, B256, U256}; use op_alloy_consensus::OpTypedTransaction; use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::{public_key_to_address, Recovered}; -use secp256k1::{Message, SecretKey, SECP256K1}; +use reth_primitives::Recovered; +use secp256k1::{rand::rngs::OsRng, Message, PublicKey, Secp256k1, SecretKey, SECP256K1}; +use sha3::{Digest, Keccak256}; /// Simple struct to sign txs/messages. /// Mainly used to sign payout txs from the builder and to create test data. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Signer { pub address: Address, + pub pubkey: PublicKey, pub secret: SecretKey, } @@ -19,9 +21,13 @@ impl Signer { pub fn try_from_secret(secret: B256) -> Result { let secret = SecretKey::from_slice(secret.as_ref())?; let pubkey = secret.public_key(SECP256K1); - let address = public_key_to_address(pubkey); + let address = public_key_to_address(&pubkey); - Ok(Self { address, secret }) + Ok(Self { + address, + pubkey, + secret, + }) } pub fn sign_message(&self, message: B256) -> Result { @@ -67,6 +73,33 @@ impl FromStr for Signer { } } +pub fn generate_ethereum_keypair() -> (SecretKey, PublicKey, Address) { + let secp = Secp256k1::new(); + + // Generate cryptographically secure random private key + let private_key = SecretKey::new(&mut OsRng); + + // Derive public key + let public_key = PublicKey::from_secret_key(&secp, &private_key); + + // Derive Ethereum address + let address = public_key_to_address(&public_key); + + (private_key, public_key, address) +} + +/// Converts a public key to an Ethereum address +pub fn public_key_to_address(public_key: &PublicKey) -> Address { + // Get uncompressed public key (65 bytes: 0x04 + 64 bytes) + let pubkey_bytes = public_key.serialize_uncompressed(); + + // Skip the 0x04 prefix and hash the remaining 64 bytes + let hash = Keccak256::digest(&pubkey_bytes[1..65]); + + // Take last 20 bytes as address + Address::from_slice(&hash[12..32]) +} + #[cfg(test)] mod test { use super::*; @@ -97,4 +130,48 @@ mod test { let signed = signed_tx.into_inner(); assert_eq!(signed.recover_signer().ok(), Some(address)); } + + #[test] + fn test_public_key_format() { + let secp = Secp256k1::new(); + let private_key = SecretKey::new(&mut OsRng); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + + let pubkey_bytes = public_key.serialize_uncompressed(); + + // Verify the public key format + assert_eq!( + pubkey_bytes.len(), + 65, + "Uncompressed public key should be 65 bytes" + ); + assert_eq!( + pubkey_bytes[0], 0x04, + "Uncompressed public key should start with 0x04" + ); + + // Verify report data would be 64 bytes + let report_data = &pubkey_bytes[1..65]; + assert_eq!( + report_data.len(), + 64, + "Report data should be exactly 64 bytes" + ); + } + + #[test] + fn test_deterministic_address_derivation() { + // Test with a known private key to ensure deterministic results + let secp = Secp256k1::new(); + let private_key = SecretKey::from_slice(&[0x42; 32]).unwrap(); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + + let address1 = public_key_to_address(&public_key); + let address2 = public_key_to_address(&public_key); + + assert_eq!( + address1, address2, + "Address derivation should be deterministic" + ); + } } From b0a512daa25afa935c1f0080d9267c3793ea0c08 Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 17 Jun 2025 11:32:28 -0700 Subject: [PATCH 143/262] Flashtestations flag (#165) * Add feature flag for flashtestations * fix fmt command --- crates/builder/op-rbuilder/Cargo.toml | 22 +++++++++++++++---- .../src/flashtestations/attestation.rs | 17 +++++++++++++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 8464c0e9..1076478e 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -121,8 +121,8 @@ hex = "0.4" ureq = "2.10" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } -tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git"} -dcap-rs = { git = "https://github.com/automata-network/dcap-rs.git" } +tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", optional = true } +dcap-rs = { git = "https://github.com/automata-network/dcap-rs.git", optional = true } dashmap = { version = "6.1", optional = true } nanoid = { version = "0.4", optional = true } @@ -175,7 +175,6 @@ min-info-logs = ["tracing/release_max_level_info"] min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] - testing = [ "dashmap", "nanoid", @@ -187,13 +186,28 @@ testing = [ "rlimit", ] - interop = [] +flashtestations = ["dcap-rs", "tdx"] + telemetry = ["reth-tracing-otlp", "opentelemetry"] custom-engine-api = [] +ci-features = [ + "default", + "jemalloc-prof", + "min-error-logs", + "min-warn-logs", + "min-info-logs", + "min-debug-logs", + "min-trace-logs", + "testing", + "interop", + "telemetry", + "custom-engine-api", +] + [[bin]] name = "op-rbuilder" path = "src/bin/op-rbuilder/main.rs" diff --git a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs index 4119f444..9e3a83cf 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs @@ -1,4 +1,5 @@ use std::io::Read; +#[cfg(feature = "flashtestations")] use tdx::{device::DeviceOptions, Tdx}; use tracing::info; use ureq; @@ -20,22 +21,26 @@ pub trait AttestationProvider { } /// Real TDX hardware attestation provider +#[cfg(feature = "flashtestations")] pub struct TdxAttestationProvider { tdx: Tdx, } +#[cfg(feature = "flashtestations")] impl Default for TdxAttestationProvider { fn default() -> Self { Self::new() } } +#[cfg(feature = "flashtestations")] impl TdxAttestationProvider { pub fn new() -> Self { Self { tdx: Tdx::new() } } } +#[cfg(feature = "flashtestations")] impl AttestationProvider for TdxAttestationProvider { fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { self.tdx @@ -85,6 +90,16 @@ pub fn get_attestation_provider( .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), )) } else { - Box::new(TdxAttestationProvider::new()) + #[cfg(feature = "flashtestations")] + { + Box::new(TdxAttestationProvider::new()) + } + #[cfg(not(feature = "flashtestations"))] + { + info!("Using debug attestation provider as flashtestations feature is disabled"); + Box::new(DebugAttestationProvider::new( + DEBUG_QUOTE_SERVICE_URL.to_string(), + )) + } } } From 42fb72517b41f56dee75b28c5b4e070a63a310a1 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:50:58 -0400 Subject: [PATCH 144/262] Add some telemetry for `eth_sendBundle` (#176) * measure eth_sendBundle latency * rename bundles_received metric to valid_bundles * log error messages * refactor to get rid of unnecessary mut * count all requests sent to eth_sendBundle * add failed_bundles counter --- crates/builder/op-rbuilder/src/metrics.rs | 8 +++- .../op-rbuilder/src/revert_protection.rs | 42 +++++++++++++++---- crates/builder/op-rbuilder/src/tx.rs | 5 ++- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index d3ed291e..25ca098d 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -82,10 +82,16 @@ pub struct OpRBuilderMetrics { pub flashblock_time_drift: Histogram, /// Time offset we used for first flashblock pub first_flashblock_time_offset: Histogram, + /// Number of requests sent to the eth_sendBundle endpoint + pub bundle_requests: Counter, /// Number of valid bundles received at the eth_sendBundle endpoint - pub bundles_received: Counter, + pub valid_bundles: Counter, + /// Number of bundles that failed to execute + pub failed_bundles: Counter, /// Number of reverted bundles pub bundles_reverted: Histogram, + /// Time taken to respond to a request to the eth_sendBundle endpoint + pub bundle_receive_duration: Histogram, } /// Contains version information for the application. diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index bb311a45..4e44c6af 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{sync::Arc, time::Instant}; use crate::{ metrics::OpRBuilderMetrics, @@ -17,6 +17,7 @@ use reth_optimism_txpool::{conditional::MaybeConditionalTransaction, OpPooledTra use reth_provider::StateProviderFactory; use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool}; +use tracing::error; // We have to split the RPC modules in two sets because we have methods that both // replace an existing method and add a new one. @@ -88,6 +89,34 @@ where Provider: StateProviderFactory + Send + Sync + Clone + 'static, { async fn send_bundle(&self, bundle: Bundle) -> RpcResult { + let request_start_time = Instant::now(); + self.metrics.bundle_requests.increment(1); + + let bundle_result = self + .send_bundle_inner(bundle) + .await + .inspect_err(|err| error!("eth_sendBundle request failed: {err:?}")); + + if bundle_result.is_ok() { + self.metrics.valid_bundles.increment(1); + } else { + self.metrics.failed_bundles.increment(1); + } + + self.metrics + .bundle_receive_duration + .record(request_start_time.elapsed()); + + bundle_result + } +} + +impl RevertProtectionBundleAPI +where + Pool: TransactionPool + Clone + 'static, + Provider: StateProviderFactory + Send + Sync + Clone + 'static, +{ + async fn send_bundle_inner(&self, bundle: Bundle) -> RpcResult { let last_block_number = self .provider .best_block_number() @@ -115,11 +144,10 @@ where .map_err(EthApiError::from)?; let recovered = recover_raw_transaction(&bundle_transaction)?; - let mut pool_transaction: FBPooledTransaction = - OpPooledTransaction::from_pooled(recovered).into(); - - pool_transaction.set_reverted_hashes(bundle.reverting_hashes.clone().unwrap_or_default()); - pool_transaction.set_conditional(conditional); + let pool_transaction = + FBPooledTransaction::from(OpPooledTransaction::from_pooled(recovered)) + .with_reverted_hashes(bundle.reverting_hashes.clone().unwrap_or_default()) + .with_conditional(conditional); let hash = self .pool @@ -127,8 +155,6 @@ where .await .map_err(EthApiError::from)?; - self.metrics.bundles_received.increment(1); - let result = BundleResult { bundle_hash: hash }; Ok(result) } diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index b54b470e..25be881d 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -34,13 +34,14 @@ impl OpPooledTx for FBPooledTransaction { } pub trait MaybeRevertingTransaction { - fn set_reverted_hashes(&mut self, reverted_hashes: Vec); + fn with_reverted_hashes(self, reverted_hashes: Vec) -> Self; fn reverted_hashes(&self) -> Option>; } impl MaybeRevertingTransaction for FBPooledTransaction { - fn set_reverted_hashes(&mut self, reverted_hashes: Vec) { + fn with_reverted_hashes(mut self, reverted_hashes: Vec) -> Self { self.reverted_hashes = Some(reverted_hashes); + self } fn reverted_hashes(&self) -> Option> { From 6aaa5fd265ddc9a4bbd788873380f4483cee46fc Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 25 Jun 2025 22:21:10 +0500 Subject: [PATCH 145/262] Review (#170) Co-authored-by: Solar Mithril --- crates/builder/op-rbuilder/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 1076478e..cc3e395d 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -121,7 +121,8 @@ hex = "0.4" ureq = "2.10" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } -tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", optional = true } +# TODO: this deps routes to https://github.com/SozinM/tdx-attestation-sdk/tree/msozin/backport - backport for tdx error fix, replace it once https://github.com/automata-network/tdx-attestation-sdk/pull/17 is merged +tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", rev = "ce02e0009c20bb4fc37c4c95701925fc3f139b41", optional = true } dcap-rs = { git = "https://github.com/automata-network/dcap-rs.git", optional = true } dashmap = { version = "6.1", optional = true } From 117c86421a33c6410ff586c157d88ef2fe7df977 Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 25 Jun 2025 14:08:21 -0700 Subject: [PATCH 146/262] Add cli flag for funding key (#168) * Add cli flag for funding key * add docs --- .../op-rbuilder/src/builders/flashblocks/service.rs | 2 -- .../op-rbuilder/src/builders/standard/payload.rs | 11 +---------- .../builder/op-rbuilder/src/flashtestations/args.rs | 10 ++++++++++ .../op-rbuilder/src/flashtestations/service.rs | 8 ++++---- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index 0fb8d368..c045a0c2 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -73,10 +73,8 @@ where tracing::debug!("Spawning flashblocks payload builder service"); let signer = self.0.builder_signer; if self.0.flashtestations_config.flashtestations_enabled { - let funding_signer = signer.expect("Key to fund TEE generated address not set"); let flashtestations_service = match spawn_flashtestations_service( self.0.flashtestations_config.clone(), - funding_signer, ctx, ) .await diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index eaf98c2c..18be6779 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -53,17 +53,8 @@ where pool: Pool, _evm_config: OpEvmConfig, ) -> eyre::Result { - let signer = self.0.builder_signer; - if self.0.flashtestations_config.flashtestations_enabled { - let funding_signer = signer.expect("Key to fund TEE generated address not set"); - match spawn_flashtestations_service( - self.0.flashtestations_config.clone(), - funding_signer, - ctx, - ) - .await - { + match spawn_flashtestations_service(self.0.flashtestations_config.clone(), ctx).await { Ok(service) => service, Err(e) => { tracing::warn!(error = %e, "Failed to spawn flashtestations service, falling back to standard builder tx"); diff --git a/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs index f734392b..4af83427 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/args.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/args.rs @@ -1,5 +1,7 @@ use alloy_primitives::{utils::parse_ether, Address, U256}; +use crate::tx_signer::Signer; + /// Parameters for Flashtestations configuration /// The names in the struct are prefixed with `flashtestations` #[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] @@ -33,6 +35,14 @@ pub struct FlashtestationsArgs { )] pub rpc_url: String, + /// Funding key for the TEE key + #[arg( + long = "flashtestations.funding-key", + env = "FLASHTESTATIONS_FUNDING_KEY", + required_if_eq("flashtestations_enabled", "true") + )] + pub funding_key: Option, + /// Funding amount for the generated signer #[arg( long = "flashtestations.funding-amount", diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index df82de6b..650bce03 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -32,7 +32,7 @@ pub struct FlashtestationsService { // TODO: FlashtestationsService error types impl FlashtestationsService { - pub fn new(args: FlashtestationsArgs, funding_signer: Signer) -> Self { + pub fn new(args: FlashtestationsArgs) -> Self { let (private_key, public_key, address) = generate_ethereum_keypair(); let tee_service_signer = Signer { address, @@ -47,7 +47,8 @@ impl FlashtestationsService { let tx_manager = TxManager::new( tee_service_signer, - funding_signer, + args.funding_key + .expect("funding key required when flashtestations enabled"), args.rpc_url, args.registry_address .expect("registry address required when flashtestations enabled"), @@ -101,7 +102,6 @@ impl BuilderTx for FlashtestationsService { pub async fn spawn_flashtestations_service( args: FlashtestationsArgs, - funding_signer: Signer, ctx: &BuilderContext, ) -> eyre::Result where @@ -109,7 +109,7 @@ where { info!("Flashtestations enabled"); - let flashtestations_service = FlashtestationsService::new(args.clone(), funding_signer); + let flashtestations_service = FlashtestationsService::new(args.clone()); // Generates new key and registers the attestation onchain flashtestations_service.bootstrap().await?; From cb22d054814cea4426eb4b9e60a6bacdc63eab16 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 26 Jun 2025 11:51:54 +0500 Subject: [PATCH 147/262] Move builder tx right after deposits and put it into base flashblock (#178) --- .../src/builders/flashblocks/payload.rs | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 56c1cb18..157b8986 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -255,6 +255,11 @@ where .sequencer_tx_duration .record(sequencer_tx_start_time.elapsed()); + // If we have payload with txpool we add first builder tx right after deposits + if !ctx.attributes().no_tx_pool { + ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); + } + let (payload, fb_payload, mut bundle_state) = build_block(db, &ctx, &mut info)?; best_payload.set(payload.clone()); @@ -316,6 +321,12 @@ where } let mut total_da_per_batch = da_per_batch; + // Account for already included builder tx + total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); + if let Some(da_limit) = total_da_per_batch.as_mut() { + *da_limit = da_limit.saturating_sub(builder_tx_da_size); + } + // TODO: we should account for a case when we will issue only 1 flashblock let last_flashblock = flashblocks_per_block.saturating_sub(1); @@ -430,15 +441,8 @@ where ); let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); - invoke_on_first_flashblock(flashblock_count, || { - total_gas_per_batch -= builder_tx_gas; - // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size - if let Some(da_limit) = total_da_per_batch.as_mut() { - *da_limit = da_limit.saturating_sub(builder_tx_da_size); - } - }); invoke_on_last_flashblock(flashblock_count, last_flashblock, || { - total_gas_per_batch -= builder_tx_gas; + total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size if let Some(da_limit) = total_da_per_batch.as_mut() { *da_limit = da_limit.saturating_sub(builder_tx_da_size); @@ -485,11 +489,6 @@ where .payload_tx_simulation_duration .record(tx_execution_start_time.elapsed()); - // TODO: temporary we add builder tx to the first flashblock too - invoke_on_first_flashblock(flashblock_count, || { - ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); - }); - // If it is the last flashblocks, add the builder txn to the block if enabled invoke_on_last_flashblock(flashblock_count, last_flashblock, || { ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); @@ -852,12 +851,6 @@ where )) } -pub fn invoke_on_first_flashblock(current_flashblock: u64, fun: F) { - if current_flashblock == 0 { - fun() - } -} - pub fn invoke_on_last_flashblock( current_flashblock: u64, flashblock_limit: u64, From 66eb367f1e4cd47c979dd791db8ad89cac07afab Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Fri, 27 Jun 2025 17:02:02 +0500 Subject: [PATCH 148/262] Implement correct flashblocks time cutoff (#172) * Implement dynamic flashblock time adjustment Add control flow with child cancel tokens, that control flashblock duration * Trying to make test non-flaky * Fix test * Rename config field to make it better * Remove extra db config * Remove extra db config --- crates/builder/op-rbuilder/src/args/op.rs | 11 +- .../src/builders/flashblocks/config.rs | 18 +- .../src/builders/flashblocks/payload.rs | 290 +++++++------- .../src/tests/data_availability.rs | 2 +- .../op-rbuilder/src/tests/flashblocks.rs | 355 +++++++++++++++++- .../op-rbuilder/src/tests/framework/driver.rs | 77 +++- .../src/tests/framework/instance.rs | 4 +- .../op-rbuilder/src/tests/framework/utils.rs | 37 +- .../builder/op-rbuilder/src/tests/revert.rs | 4 +- crates/builder/op-rbuilder/src/tests/smoke.rs | 6 +- 10 files changed, 620 insertions(+), 184 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 784aeb7c..6fa740a4 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -118,14 +118,15 @@ pub struct FlashblocksArgs { )] pub flashblocks_block_time: u64, - /// Enabled dynamic flashblocks adjustment. This will allow account for late FCUs and produce - /// less flashblocks, while each flashblock would be bigger. + /// Builder would always thry to produce fixed number of flashblocks without regard to time of + /// FCU arrival. + /// In cases of late FCU it could lead to partially filled blocks. #[arg( - long = "flashblocks.dynamic", + long = "flashblocks.fixed", default_value = "false", - env = "FLASHBLOCK_DYNAMIC" + env = "FLASHBLOCK_FIXED" )] - pub flashblocks_dynamic: bool, + pub flashblocks_fixed: bool, /// Time by which blocks would be completed earlier in milliseconds. /// diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index 0229a2b3..702566be 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -17,11 +17,17 @@ pub struct FlashblocksConfig { pub interval: Duration, /// How much time would be deducted from block build time to account for latencies in - /// milliseconds + /// milliseconds. + /// + /// If dynamic_adjustment is false this value would be deducted from first flashblock and + /// it shouldn't be more than interval + /// + /// If dynamic_adjustment is true this value would be deducted from first flashblock and + /// it shouldn't be more than interval pub leeway_time: Duration, - /// Enables dynamic flashblocks number based on FCU arrival time - pub dynamic_adjustment: bool, + /// Disables dynamic flashblocks number adjustment based on FCU arrival time + pub fixed: bool, } impl Default for FlashblocksConfig { @@ -30,7 +36,7 @@ impl Default for FlashblocksConfig { ws_addr: SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 1111), interval: Duration::from_millis(250), leeway_time: Duration::from_millis(50), - dynamic_adjustment: false, + fixed: false, } } } @@ -48,13 +54,13 @@ impl TryFrom for FlashblocksConfig { let leeway_time = Duration::from_millis(args.flashblocks.flashblocks_leeway_time); - let dynamic_adjustment = args.flashblocks.flashblocks_dynamic; + let fixed = args.flashblocks.flashblocks_fixed; Ok(Self { ws_addr, interval, leeway_time, - dynamic_adjustment, + fixed, }) } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 157b8986..46056ae6 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -45,7 +45,11 @@ use std::{ sync::Arc, time::Instant, }; -use tokio::sync::mpsc; +use tokio::sync::{ + mpsc, + mpsc::{error::SendError, Sender}, +}; +use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, metadata::Level, span, warn}; #[derive(Debug, Default)] @@ -146,7 +150,11 @@ where best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { let block_build_start_time = Instant::now(); - let BuildArguments { config, cancel, .. } = args; + let BuildArguments { + config, + cancel: block_cancel, + .. + } = args; // We log only every 100th block to reduce usage let span = if cfg!(feature = "telemetry") @@ -164,34 +172,6 @@ where let chain_spec = self.client.chain_spec(); let timestamp = config.attributes.timestamp(); - // We use this system time to determine remining time to build a block - // Things to consider: - // FCU(a) - FCU with attributes - // FCU(a) could arrive with `block_time - fb_time < delay`. In this case we could only produce 1 flashblock - // 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 time = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp) - - self.config.specific.leeway_time; - let time_drift = time.duration_since(std::time::SystemTime::now()).ok(); - match time_drift { - None => error!( - target: "payload_builder", - message = "FCU arrived too late or system clock are unsynced", - ?time, - ), - Some(time_drift) => { - self.metrics - .flashblock_time_drift - .record(time_drift.as_millis() as f64); - debug!( - target: "payload_builder", - message = "Time drift for building round", - ?time, - ?time_drift, - ?timestamp - ); - } - } let block_env_attributes = OpNextBlockEnvAttributes { timestamp, suggested_fee_recipient: config.attributes.suggested_fee_recipient(), @@ -219,13 +199,14 @@ where .next_evm_env(&config.parent_header, &block_env_attributes) .map_err(PayloadBuilderError::other)?; - let ctx = OpPayloadBuilderCtx { + let mut ctx = OpPayloadBuilderCtx { evm_config: self.evm_config.clone(), chain_spec: self.client.chain_spec(), config, evm_env, block_env_attributes, - cancel, + // Here we use parent token because child token handing is only for proper flashblocks + cancel: block_cancel.clone(), da_config: self.config.da_config.clone(), builder_signer: self.config.builder_signer, metrics: Default::default(), @@ -292,7 +273,7 @@ where } // We adjust our flashblocks timings based on time_drift if dynamic adjustment enable let (flashblocks_per_block, first_flashblock_offset) = - self.calculate_flashblocks(time_drift); + self.calculate_flashblocks(timestamp); info!( target: "payload_builder", message = "Performed flashblocks timing derivation", @@ -331,56 +312,14 @@ where let last_flashblock = flashblocks_per_block.saturating_sub(1); let mut flashblock_count = 0; - // Create a channel to coordinate flashblock building - let (build_tx, mut build_rx) = mpsc::channel(1); - - // Spawn the timer task that signals when to build a new flashblock - let cancel_clone = ctx.cancel.clone(); - let interval = self.config.specific.interval; - tokio::spawn(async move { - // We handle first flashblock separately, because it could be shrunk to fit everything - let mut first_interval = tokio::time::interval(first_flashblock_offset); - // If first_flashblock_offset == 0 that means all flashblock are proper, and we just - // skip this cusom logic - if first_flashblock_offset.as_millis() != 0 { - let cancelled = cancel_clone - .run_until_cancelled(async { - // We send first signal straight away and then wait first_interval duration - // to send second signal and start building normal blocks - for _ in 0..2 { - first_interval.tick().await; - // TODO: maybe return None if cancelled - if let Err(err) = build_tx.send(()).await { - error!(target: "payload_builder", "Error sending build signal: {}", err); - } - } - }) - .await; - if cancelled.is_none() { - info!(target: "payload_builder", "Building job cancelled, stopping payload building"); - drop(build_tx); - return; - } - } - // Handle rest of fbs in steady rate - let mut interval = tokio::time::interval(interval); - let cancelled = cancel_clone.run_until_cancelled(async { - // First tick completes immediately - interval.tick().await; - loop { - interval.tick().await; - if let Err(err) = build_tx.send(()).await { - error!(target: "payload_builder", "Error sending build signal: {}", err); - break; - } - } - }).await; - if cancelled.is_none() { - info!(target: "payload_builder", "Building job cancelled, stopping payload building"); - drop(build_tx); - } - }); - + // This channel coordinates flashblock building + let (fb_cancel_token_rx, mut fb_cancel_token_tx) = + mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); + self.spawn_timer_task( + block_cancel.clone(), + fb_cancel_token_rx, + first_flashblock_offset, + ); // Process flashblocks in a blocking loop loop { let fb_span = if span.is_none() { @@ -393,30 +332,19 @@ where ) }; let _entered = fb_span.enter(); - // Block on receiving a message, break on cancellation or closed channel - let received = tokio::task::block_in_place(|| { - // Get runtime handle - let rt = tokio::runtime::Handle::current(); - - // Run the async operation to completion, blocking the current thread - rt.block_on(async { - // Check for cancellation first - if ctx.cancel.is_cancelled() { - info!( - target: "payload_builder", - "Job cancelled, stopping payload building", - ); - return None; - } - - // Wait for next message - build_rx.recv().await - }) - }); - // Exit loop if channel closed or cancelled - match received { - Some(()) => { + // We get token from time loop. Token from this channel means that we need to start build flashblock + // Cancellation of this token means that we need to stop building flashblock. + // If channel return None it means that we built all flashblock or parent_token got cancelled + let fb_cancel_token = + tokio::task::block_in_place(|| fb_cancel_token_tx.blocking_recv()).flatten(); + + match fb_cancel_token { + Some(cancel_token) => { + // We use fb_cancel_token inside context so we could exit from + // execute_best_transaction without cancelling parent token + ctx.cancel = cancel_token; + // TODO: remove this if flashblock_count >= flashblocks_per_block { info!( target: "payload_builder", @@ -427,7 +355,6 @@ where ); continue; } - // Continue with flashblock building info!( target: "payload_builder", @@ -464,27 +391,17 @@ where .record(best_txs_start_time.elapsed()); let tx_execution_start_time = Instant::now(); - if ctx - .execute_best_transactions( - &mut info, - &mut db, - best_txs, - total_gas_per_batch.min(ctx.block_gas_limit()), - total_da_per_batch, - )? - .is_some() - { - // Handles job cancellation - info!( - target: "payload_builder", - "Job cancelled, stopping payload building", - ); - ctx.metrics - .payload_tx_simulation_duration - .record(tx_execution_start_time.elapsed()); - // if the job was cancelled, stop - return Ok(()); - } + ctx.execute_best_transactions( + &mut info, + &mut db, + best_txs, + total_gas_per_batch.min(ctx.block_gas_limit()), + total_da_per_batch, + )?; + ctx.metrics + .payload_tx_simulation_duration + .record(tx_execution_start_time.elapsed()); + ctx.metrics .payload_tx_simulation_duration .record(tx_execution_start_time.elapsed()); @@ -513,6 +430,12 @@ where fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 fb_payload.base = None; + // We check that child_job got cancelled before sending flashblock. + // This will ensure consistent timing between flashblocks. + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current() + .block_on(async { ctx.cancel.cancelled().await }); + }); self.ws_pub .publish(&fb_payload) .map_err(PayloadBuilderError::other)?; @@ -566,32 +489,107 @@ where } } + /// Spawn task that will send new flashblock level cancel token in steady intervals (first interval + /// may vary if --flashblocks.dynamic enabled) + pub fn spawn_timer_task( + &self, + block_cancel: CancellationToken, + flashblock_cancel_token_rx: Sender>, + first_flashblock_offset: Duration, + ) { + let interval = self.config.specific.interval; + tokio::spawn(async move { + let cancelled: Option>>> = block_cancel + .run_until_cancelled(async { + // Create first fb interval already started + let mut timer = tokio::time::interval(first_flashblock_offset); + timer.tick().await; + let child_token = block_cancel.child_token(); + flashblock_cancel_token_rx + .send(Some(child_token.clone())) + .await?; + timer.tick().await; + // Time to build flashblock has ended so we cancel the token + child_token.cancel(); + // We would start using regular intervals from here on + let mut timer = tokio::time::interval(interval); + timer.tick().await; + loop { + // Initiate fb job + let child_token = block_cancel.child_token(); + flashblock_cancel_token_rx + .send(Some(child_token.clone())) + .await?; + timer.tick().await; + // Cancel job once time is up + child_token.cancel(); + } + }) + .await; + if let Some(Err(err)) = cancelled { + error!(target: "payload_builder", "Timer task encountered error: {err}"); + } else { + info!(target: "payload_builder", "Building job cancelled, stopping payload building"); + } + }); + } + /// Calculate number of flashblocks. /// If dynamic is enabled this function will take time drift into the account. - pub fn calculate_flashblocks(&self, time_drift: Option) -> (u64, Duration) { - if !self.config.specific.dynamic_adjustment || time_drift.is_none() { + pub fn calculate_flashblocks(&self, timestamp: u64) -> (u64, Duration) { + if self.config.specific.fixed { return ( self.config.flashblocks_per_block(), - self.config.specific.interval, + // 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, ); } - let time_drift = time_drift.unwrap(); - let interval = self.config.specific.interval.as_millis(); - let time_drift = time_drift.as_millis(); + // We use this system time to determine remining time to build a block + // Things to consider: + // FCU(a) - FCU with attributes + // FCU(a) could arrive with `block_time - fb_time < delay`. In this case we could only produce 1 flashblock + // 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 time = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp) + - self.config.specific.leeway_time; + let now = std::time::SystemTime::now(); + let Ok(time_drift) = time.duration_since(now) else { + error!( + target: "payload_builder", + message = "FCU arrived too late or system clock are unsynced", + ?time, + ?now, + ); + return ( + self.config.flashblocks_per_block(), + self.config.specific.interval, + ); + }; + self.metrics + .flashblock_time_drift + .record(time_drift.as_millis() as f64); + debug!( + target: "payload_builder", + message = "Time drift for building round", + ?time, + ?time_drift, + ?timestamp + ); + // 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 time_drift = time_drift.as_millis() as u64; let first_flashblock_offset = time_drift.rem(interval); - let flashblocks_per_block = if first_flashblock_offset == 0 { - // In this case all flashblock are full and they all in division - time_drift.div(interval) + if first_flashblock_offset == 0 { + // We have perfect division, so we use interval as first fb offset + (time_drift.div(interval), Duration::from_millis(interval)) } else { - // If we have any reminder that mean the first flashblock won't be in division - // so we add it manually. - time_drift.div(interval) + 1 - }; - // We won't have any problems because of casting - ( - flashblocks_per_block as u64, - Duration::from_millis(first_flashblock_offset as u64), - ) + // Non-perfect division, so we account for it. + ( + time_drift.div(interval) + 1, + Duration::from_millis(first_flashblock_offset), + ) + } } } diff --git a/crates/builder/op-rbuilder/src/tests/data_availability.rs b/crates/builder/op-rbuilder/src/tests/data_availability.rs index ec37ed10..ffee176f 100644 --- a/crates/builder/op-rbuilder/src/tests/data_availability.rs +++ b/crates/builder/op-rbuilder/src/tests/data_availability.rs @@ -84,7 +84,7 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { .await?; let unfit_tx_3 = driver.create_transaction().send().await?; - let block = driver.build_new_block().await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; if_standard! { // Now the first 2 txs will fit into the block diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index 44d8df21..f285c709 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -1,8 +1,7 @@ -use std::sync::Arc; - use futures::StreamExt; use macros::rb_test; use parking_lot::Mutex; +use std::{sync::Arc, time::Duration}; use tokio::task::JoinHandle; use tokio_tungstenite::{connect_async, tungstenite::Message}; use tokio_util::sync::CancellationToken; @@ -19,12 +18,81 @@ use crate::{ flashblocks_port: 1239, flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, - flashblocks_leeway_time: 0, - flashblocks_dynamic: false, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + }, + ..Default::default() +})] +async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + driver.fund_default_accounts().await?; + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); + + // Spawn WebSocket listener task + let cancellation_token_clone = cancellation_token.clone(); + let ws_handle: JoinHandle> = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + messages_clone.lock().push(text); + } + } + } + }); + + // We align out block timestamps with current unix timestamp + for _ in 0..10 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver.build_new_block_with_current_timestamp(None).await?; + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + cancellation_token.cancel(); + assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + + assert!( + !received_messages + .lock() + .iter() + .any(|msg| msg.contains("Building flashblock")), + "No messages received from WebSocket" + ); + + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, }, ..Default::default() })] -async fn smoke(rbuilder: LocalInstance) -> eyre::Result<()> { +async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; driver.fund_default_accounts().await?; @@ -52,6 +120,7 @@ async fn smoke(rbuilder: LocalInstance) -> eyre::Result<()> { } }); + // We align out block timestamps with current unix timestamp for _ in 0..10 { for _ in 0..5 { // send a valid transaction @@ -61,10 +130,217 @@ async fn smoke(rbuilder: LocalInstance) -> eyre::Result<()> { .send() .await?; } + let block = driver.build_new_block_with_current_timestamp(None).await?; + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + cancellation_token.cancel(); + assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + + assert!( + !received_messages + .lock() + .iter() + .any(|msg| msg.contains("Building flashblock")), + "No messages received from WebSocket" + ); + + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 50, + flashblocks_fixed: true, + }, + ..Default::default() +})] +async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + driver.fund_default_accounts().await?; + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); + + // Spawn WebSocket listener task + let cancellation_token_clone = cancellation_token.clone(); + let ws_handle: JoinHandle> = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + messages_clone.lock().push(text); + } + } + } + }); + + // We align out block timestamps with current unix timestamp + for _ in 0..10 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } let block = driver.build_new_block().await?; - assert_eq!(block.transactions.len(), 8); // 5 normal txn + deposit + 2 builder txn + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + cancellation_token.cancel(); + assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + + assert!( + !received_messages + .lock() + .iter() + .any(|msg| msg.contains("Building flashblock")), + "No messages received from WebSocket" + ); + + Ok(()) +} + +#[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, + flashblocks_leeway_time: 50, + flashblocks_fixed: true, + }, + ..Default::default() +})] +async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + driver.fund_default_accounts().await?; + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); + + // Spawn WebSocket listener task + let cancellation_token_clone = cancellation_token.clone(); + let ws_handle: JoinHandle> = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + messages_clone.lock().push(text); + } + } + } + }); + + // We align out block timestamps with current unix timestamp + for _ in 0..10 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver.build_new_block().await?; + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + cancellation_token.cancel(); + assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + + assert!( + !received_messages + .lock() + .iter() + .any(|msg| msg.contains("Building flashblock")), + "No messages received from WebSocket" + ); + + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + }, + ..Default::default() +})] +async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + driver.fund_default_accounts().await?; + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); + + // Spawn WebSocket listener task + let cancellation_token_clone = cancellation_token.clone(); + let ws_handle: JoinHandle> = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + messages_clone.lock().push(text); + } + } + } + }); + + // We align out block timestamps with current unix timestamp + for i in 0..9 { + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver + .build_new_block_with_current_timestamp(Some(Duration::from_millis(i * 100))) + .await?; + assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn tokio::time::sleep(std::time::Duration::from_secs(1)).await; } @@ -81,3 +357,70 @@ async fn smoke(rbuilder: LocalInstance) -> eyre::Result<()> { Ok(()) } + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 0, + flashblocks_fixed: false, + }, + ..Default::default() +})] +async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + driver.fund_default_accounts().await?; + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); + + // Spawn WebSocket listener task + let cancellation_token_clone = cancellation_token.clone(); + let ws_handle: JoinHandle> = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + messages_clone.lock().push(text); + } + } + } + }); + + for _ in 0..5 { + // send a valid transaction + let _ = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + let block = driver + .build_new_block_with_current_timestamp(Some(Duration::from_millis(999))) + .await?; + // We could only produce block with deposits + builder tx because of short time frame + assert_eq!(block.transactions.len(), 2); + cancellation_token.cancel(); + assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + + assert!( + !received_messages + .lock() + .iter() + .any(|msg| msg.contains("Building flashblock")), + "No messages received from WebSocket" + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/driver.rs b/crates/builder/op-rbuilder/src/tests/framework/driver.rs index 5ee806da..4d3393fb 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/driver.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/driver.rs @@ -99,15 +99,24 @@ impl ChainDriver { self.build_new_block_with_txs(vec![]).await } - /// Builds a new block using the current state of the chain and the transactions in the pool with a list - /// of mandatory builder transactions. Those are usually deposit transactions. - pub async fn build_new_block_with_txs( + /// Builds a new block with block_timestamp calculated as block time right before sending FCU + pub async fn build_new_block_with_current_timestamp( + &self, + timestamp_jitter: Option, + ) -> eyre::Result> { + self.build_new_block_with_txs_timestamp(vec![], None, timestamp_jitter) + .await + } + + /// Builds a new block with provided txs and timestamp + pub async fn build_new_block_with_txs_timestamp( &self, txs: Vec, + block_timestamp: Option, + // Amount of time to lag before sending FCU. This tests late FCU scenarios + timestamp_jitter: Option, ) -> eyre::Result> { let latest = self.latest().await?; - let latest_timestamp = Duration::from_secs(latest.header.timestamp); - let block_timestamp = latest_timestamp + Self::MIN_BLOCK_TIME; // Add L1 block info as the first transaction in every L2 block // This deposit transaction contains L1 block metadata required by the L2 chain @@ -134,10 +143,35 @@ impl ChainDriver { signed_tx.encoded_2718().into() }; + let mut wait_until = None; + // If block_timestamp we need to produce new timestamp according to current clocks + let block_timestamp = if let Some(block_timestamp) = block_timestamp { + block_timestamp.as_secs() + } else { + // We take the following second, until which we will need to wait before issuing FCU + let latest_timestamp = (chrono::Utc::now().timestamp() + 1) as u64; + wait_until = Some(latest_timestamp); + latest_timestamp + + Duration::from_millis(self.args.chain_block_time) + .as_secs() + .max(Self::MIN_BLOCK_TIME.as_secs()) + }; + + // This step will alight time at which we send FCU. ideally we must send FCU and the beginning of the second. + if let Some(wait_until) = wait_until { + let sleep_time = Duration::from_secs(wait_until).saturating_sub(Duration::from_millis( + chrono::Utc::now().timestamp_millis() as u64, + )); + if let Some(timestamp_jitter) = timestamp_jitter { + tokio::time::sleep(sleep_time + timestamp_jitter).await; + } else { + tokio::time::sleep(sleep_time).await; + } + } let fcu_result = self .fcu(OpPayloadAttributes { payload_attributes: PayloadAttributes { - timestamp: block_timestamp.as_secs(), + timestamp: block_timestamp, parent_beacon_block_root: Some(B256::ZERO), withdrawals: Some(vec![]), ..Default::default() @@ -157,10 +191,19 @@ impl ChainDriver { .ok_or_else(|| eyre::eyre!("Forkchoice update did not return a payload ID"))?; // wait for the block to be built for the specified chain block time - tokio::time::sleep( - Duration::from_millis(self.args.chain_block_time).max(Self::MIN_BLOCK_TIME), - ) - .await; + if let Some(timestamp_jitter) = timestamp_jitter { + tokio::time::sleep( + Duration::from_millis(self.args.chain_block_time) + .max(Self::MIN_BLOCK_TIME) + .saturating_sub(timestamp_jitter), + ) + .await; + } else { + tokio::time::sleep( + Duration::from_millis(self.args.chain_block_time).max(Self::MIN_BLOCK_TIME), + ) + .await; + } let payload = OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?); @@ -206,6 +249,20 @@ impl ChainDriver { Ok(block) } + /// Builds a new block using the current state of the chain and the transactions in the pool with a list + /// of mandatory builder transactions. Those are usually deposit transactions. + pub async fn build_new_block_with_txs( + &self, + txs: Vec, + ) -> eyre::Result> { + let latest = self.latest().await?; + let latest_timestamp = Duration::from_secs(latest.header.timestamp); + let block_timestamp = latest_timestamp + Self::MIN_BLOCK_TIME; + + self.build_new_block_with_txs_timestamp(txs, Some(block_timestamp), None) + .await + } + /// Retreives the latest built block and returns only a list of transaction /// hashes from its body. pub async fn latest(&self) -> eyre::Result> { diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 08c04c91..0a9b6836 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -4,6 +4,7 @@ use crate::{ primitives::reth::engine_api_builder::OpEngineApiBuilder, revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}, tests::{ + create_test_db, framework::{driver::ChainDriver, BUILDER_PRIVATE_KEY}, ChainDriverExt, EngineApi, Ipc, TransactionPoolObserver, }, @@ -108,7 +109,8 @@ impl LocalInstance { .build(); let node_builder = NodeBuilder::<_, OpChainSpec>::new(config.clone()) - .testing_node(task_manager.executor()) + .with_database(create_test_db(config.clone())) + .with_launch_context(task_manager.executor()) .with_types::() .with_components( op_node diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs index ceba8434..e734f4d8 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -1,14 +1,22 @@ +use crate::{ + tests::{framework::driver::ChainDriver, Protocol, ONE_ETH}, + tx_signer::Signer, +}; use alloy_eips::Encodable2718; use alloy_primitives::{hex, Address, BlockHash, TxHash, TxKind, B256, U256}; use alloy_rpc_types_eth::{Block, BlockTransactionHashes}; use core::future::Future; use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; use op_alloy_rpc_types::Transaction; - -use crate::{ - tests::{framework::driver::ChainDriver, Protocol, ONE_ETH}, - tx_signer::Signer, +use reth_db::{ + init_db, + mdbx::{DatabaseArguments, MaxReadTransactionDuration, KILOBYTE, MEGABYTE}, + test_utils::{TempDatabase, ERROR_DB_CREATION}, + ClientVersion, DatabaseEnv, }; +use reth_node_core::{args::DatadirArgs, dirs::DataDirPath, node_config::NodeConfig}; +use reth_optimism_chainspec::OpChainSpec; +use std::sync::Arc; use super::{TransactionBuilder, FUNDED_PRIVATE_KEYS}; @@ -191,3 +199,24 @@ impl AsTxs for Vec { self.clone() } } + +pub fn create_test_db(config: NodeConfig) -> Arc> { + let path = reth_node_core::dirs::MaybePlatformPath::::from( + reth_db::test_utils::tempdir_path(), + ); + let db_config = config.with_datadir_args(DatadirArgs { + datadir: path.clone(), + ..Default::default() + }); + let data_dir = path.unwrap_or_chain_default(db_config.chain.chain(), db_config.datadir.clone()); + let path = data_dir.db(); + let db = init_db( + path.as_path(), + DatabaseArguments::new(ClientVersion::default()) + .with_max_read_transaction_duration(Some(MaxReadTransactionDuration::Unbounded)) + .with_geometry_max_size(Some(4 * MEGABYTE)) + .with_growth_step(Some(4 * KILOBYTE)), + ) + .expect(ERROR_DB_CREATION); + Arc::new(TempDatabase::new(db, path)) +} diff --git a/crates/builder/op-rbuilder/src/tests/revert.rs b/crates/builder/op-rbuilder/src/tests/revert.rs index 404c1790..cbc315f9 100644 --- a/crates/builder/op-rbuilder/src/tests/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/revert.rs @@ -42,7 +42,7 @@ async fn monitor_transaction_gc(rbuilder: LocalInstance) -> eyre::Result<()> { // generate 10 blocks for i in 0..10 { - let generated_block = driver.build_new_block().await?; + let generated_block = driver.build_new_block_with_current_timestamp(None).await?; if_standard! { // standard builder blocks should only include two transactions (deposit + builder) @@ -50,7 +50,7 @@ async fn monitor_transaction_gc(rbuilder: LocalInstance) -> eyre::Result<()> { } if_flashblocks! { - // flashblocks should include three transactions (deposit + builder + first flashblock) + // flashblocks should include three transactions (deposit + 2 builder txs) assert_eq!(generated_block.transactions.len(), 3); } diff --git a/crates/builder/op-rbuilder/src/tests/smoke.rs b/crates/builder/op-rbuilder/src/tests/smoke.rs index d92a7c65..6439bd88 100644 --- a/crates/builder/op-rbuilder/src/tests/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/smoke.rs @@ -29,7 +29,7 @@ async fn chain_produces_blocks(rbuilder: LocalInstance) -> eyre::Result<()> { // no user transactions are sent. // the deposit transaction and the block generator's transaction for _ in 0..SAMPLE_SIZE { - let block = driver.build_new_block().await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; let transactions = block.transactions; if_standard! { @@ -67,7 +67,7 @@ async fn chain_produces_blocks(rbuilder: LocalInstance) -> eyre::Result<()> { tx_hashes.insert(*tx.tx_hash()); } - let block = driver.build_new_block().await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; let txs = block.transactions; @@ -149,7 +149,7 @@ async fn produces_blocks_under_load_within_deadline(rbuilder: LocalInstance) -> let block = tokio::time::timeout( Duration::from_secs(rbuilder.args().chain_block_time) + Duration::from_millis(500), - driver.build_new_block(), + driver.build_new_block_with_current_timestamp(None), ) .await .expect("Timeout while waiting for block production") From 4fc3d81d28495c39e7e7eecd233250f6fa126fe2 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Tue, 1 Jul 2025 17:13:07 +0500 Subject: [PATCH 149/262] Change default leeway-time + add handing for block cancellation (#185) --- crates/builder/op-rbuilder/src/args/op.rs | 2 +- .../src/builders/flashblocks/payload.rs | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 6fa740a4..cd0d968d 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -134,7 +134,7 @@ pub struct FlashblocksArgs { /// building time before calculating number of fbs. #[arg( long = "flashblocks.leeway-time", - default_value = "50", + default_value = "75", env = "FLASHBLOCK_LEEWAY_TIME" )] pub flashblocks_leeway_time: u64, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 46056ae6..f8156fa8 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -398,6 +398,18 @@ where total_gas_per_batch.min(ctx.block_gas_limit()), total_da_per_batch, )?; + // We got block cancelled, we won't need anything from the block at this point + // Caution: this assume that block cancel token only cancelled when new FCU is received + if block_cancel.is_cancelled() { + ctx.metrics.block_built_success.increment(1); + ctx.metrics.flashblock_count.record(flashblock_count as f64); + debug!( + target: "payload_builder", + message = "Payload building complete, job cancelled during execution" + ); + span.record("flashblock_count", flashblock_count); + return Ok(()); + } ctx.metrics .payload_tx_simulation_duration .record(tx_execution_start_time.elapsed()); @@ -550,14 +562,14 @@ where // FCU(a) could arrive with `block_time - fb_time < delay`. In this case we could only produce 1 flashblock // 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 time = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp) + let target_time = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp) - self.config.specific.leeway_time; let now = std::time::SystemTime::now(); - let Ok(time_drift) = time.duration_since(now) else { + let Ok(time_drift) = target_time.duration_since(now) else { error!( target: "payload_builder", message = "FCU arrived too late or system clock are unsynced", - ?time, + ?target_time, ?now, ); return ( @@ -571,7 +583,7 @@ where debug!( target: "payload_builder", message = "Time drift for building round", - ?time, + ?target_time, ?time_drift, ?timestamp ); From dd58e9e5b5cbac2fdcaa28c7c7220c13f2179ff6 Mon Sep 17 00:00:00 2001 From: kristoffer Date: Thu, 3 Jul 2025 11:21:59 +0000 Subject: [PATCH 150/262] Bump reth 1.5.0 (#186) * Use reth 1.5.0 * Bump reth to 1.5.0 --------- Co-authored-by: Solar Mithril --- crates/builder/op-rbuilder/Cargo.toml | 42 ++++++++--------- .../op-rbuilder/src/builders/context.rs | 6 ++- .../src/tests/framework/external.rs | 46 ++++++++++--------- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index cc3e395d..dfca7317 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -128,11 +128,11 @@ dcap-rs = { git = "https://github.com/automata-network/dcap-rs.git", optional = dashmap = { version = "6.1", optional = true } nanoid = { version = "0.4", optional = true } reth-ipc = { workspace = true, optional = true } -bollard = { version = "0.19", optional = true } tar = { version = "0.4", optional = true } ctor = { version = "0.4.2", optional = true } rlimit = { version = "0.10", optional = true } macros = { path = "src/tests/framework/macros", optional = true } +testcontainers = "0.24.0" [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -143,7 +143,7 @@ vergen-git2.workspace = true [dev-dependencies] alloy-provider = { workspace = true, default-features = true, features = [ - "txpool-api", + "txpool-api", ] } tempfile = "3.8" macros = { path = "src/tests/framework/macros" } @@ -151,7 +151,6 @@ dashmap = { version = "6.1" } nanoid = { version = "0.4" } reth-ipc = { workspace = true } reth-node-builder = { workspace = true, features = ["test-utils"] } -bollard = "0.19" ctor = "0.4.2" rlimit = { version = "0.10" } @@ -159,15 +158,15 @@ rlimit = { version = "0.10" } default = ["jemalloc"] jemalloc = [ - "dep:tikv-jemallocator", - "reth-cli-util/jemalloc", - "reth-optimism-cli/jemalloc", + "dep:tikv-jemallocator", + "reth-cli-util/jemalloc", + "reth-optimism-cli/jemalloc", ] jemalloc-prof = [ - "jemalloc", - "tikv-jemallocator?/profiling", - "reth/jemalloc-prof", - "reth-cli-util/jemalloc-prof", + "jemalloc", + "tikv-jemallocator?/profiling", + "reth/jemalloc-prof", + "reth-cli-util/jemalloc-prof", ] min-error-logs = ["tracing/release_max_level_error"] @@ -177,14 +176,13 @@ min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] testing = [ - "dashmap", - "nanoid", - "reth-ipc", - "reth-node-builder/test-utils", - "bollard", - "ctor", - "macros", - "rlimit", + "dashmap", + "nanoid", + "reth-ipc", + "reth-node-builder/test-utils", + "ctor", + "macros", + "rlimit", ] interop = [] @@ -203,10 +201,10 @@ ci-features = [ "min-info-logs", "min-debug-logs", "min-trace-logs", - "testing", - "interop", - "telemetry", - "custom-engine-api", + "testing", + "interop", + "telemetry", + "custom-engine-api", ] [[bin]] diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index f9db06de..385ad0be 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -32,7 +32,9 @@ use reth_primitives_traits::{InMemorySize, SignedTransaction}; use reth_provider::ProviderError; use reth_revm::{context::Block, State}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; -use revm::{context::result::ResultAndState, Database, DatabaseCommit}; +use revm::{ + context::result::ResultAndState, interpreter::as_u64_saturated, Database, DatabaseCommit, +}; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{debug, info, trace, warn}; @@ -95,7 +97,7 @@ impl OpPayloadBuilderCtx { /// Returns the block number for the block. pub fn block_number(&self) -> u64 { - self.evm_env.block_env.number + as_u64_saturated!(self.evm_env.block_env.number) } /// Returns the current base fee diff --git a/crates/builder/op-rbuilder/src/tests/framework/external.rs b/crates/builder/op-rbuilder/src/tests/framework/external.rs index e67fcfcc..d8feb792 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/external.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/external.rs @@ -5,19 +5,20 @@ use alloy_provider::{Identity, Provider, ProviderBuilder, RootProvider}; use alloy_rpc_types_engine::{ ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadStatusEnum, }; -use bollard::{ - exec::{CreateExecOptions, StartExecResults}, - query_parameters::{ - AttachContainerOptions, CreateContainerOptions, CreateImageOptions, RemoveContainerOptions, - StartContainerOptions, StopContainerOptions, - }, - secret::{ContainerCreateBody, ContainerCreateResponse, HostConfig}, - Docker, -}; use futures::{StreamExt, TryStreamExt}; use op_alloy_network::Optimism; use op_alloy_rpc_types_engine::OpExecutionPayloadV4; use std::path::{Path, PathBuf}; +use testcontainers::bollard::{ + container::{ + AttachContainerOptions, Config, CreateContainerOptions, RemoveContainerOptions, + StartContainerOptions, StopContainerOptions, + }, + exec::{CreateExecOptions, StartExecResults}, + image::CreateImageOptions, + secret::{ContainerCreateResponse, HostConfig}, + Docker, +}; use tokio::signal; use tracing::{debug, warn}; @@ -70,7 +71,7 @@ impl ExternalNode { let container = create_container(&tempdir, &docker, version_tag).await?; docker - .start_container(&container.id, None::) + .start_container(&container.id, None::>) .await?; // Wait for the container to be ready and IPCs to be created @@ -279,8 +280,8 @@ async fn create_container( // first pull the image locally let mut pull_stream = docker.create_image( Some(CreateImageOptions { - from_image: Some("ghcr.io/paradigmxyz/op-reth".to_string()), - tag: Some(version_tag.into()), + from_image: "ghcr.io/paradigmxyz/op-reth".to_string(), + tag: version_tag.into(), ..Default::default() }), None, @@ -295,7 +296,7 @@ async fn create_container( } // Don't expose any ports, as we will only use IPC for communication. - let container_config = ContainerCreateBody { + let container_config = Config { image: Some(format!("ghcr.io/paradigmxyz/op-reth:{version_tag}")), entrypoint: Some(vec!["op-reth".to_string()]), cmd: Some( @@ -320,7 +321,10 @@ async fn create_container( }; Ok(docker - .create_container(Some(CreateContainerOptions::default()), container_config) + .create_container( + Some(CreateContainerOptions::::default()), + container_config, + ) .await?) } @@ -343,7 +347,7 @@ async fn relax_permissions(docker: &Docker, container: &str, path: &str) -> eyre }; while let Some(Ok(output)) = output.next().await { - use bollard::container::LogOutput::*; + use testcontainers::bollard::container::LogOutput::*; match output { StdErr { message } => { return Err(eyre::eyre!( @@ -362,11 +366,11 @@ async fn await_ipc_readiness(docker: &Docker, container: &str) -> eyre::Result<( let mut attach_stream = docker .attach_container( container, - Some(AttachContainerOptions { - stdout: true, - stderr: true, - stream: true, - logs: true, + Some(AttachContainerOptions:: { + stdout: Some(true), + stderr: Some(true), + stream: Some(true), + logs: Some(true), ..Default::default() }), ) @@ -377,7 +381,7 @@ async fn await_ipc_readiness(docker: &Docker, container: &str) -> eyre::Result<( // wait for the node to start and signal that IPCs are ready while let Some(Ok(output)) = attach_stream.output.next().await { - use bollard::container::LogOutput; + use testcontainers::bollard::container::LogOutput; match output { LogOutput::StdOut { message } | LogOutput::StdErr { message } => { let message = String::from_utf8_lossy(&message); From dcc678b14df02fa3771bd864c52837749b294b64 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 7 Jul 2025 21:18:53 +0500 Subject: [PATCH 151/262] Add simple logging to timer task (#191) --- crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index f8156fa8..4f674383 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -529,10 +529,12 @@ where loop { // Initiate fb job let child_token = block_cancel.child_token(); + debug!(target: "payload_builder", "Sending child cancel token to execution loop"); flashblock_cancel_token_rx .send(Some(child_token.clone())) .await?; timer.tick().await; + debug!(target: "payload_builder", "Cancelling child token to complete flashblock"); // Cancel job once time is up child_token.cancel(); } From a266b983a9c6a606dd1138fd6ac56d302e3f1619 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 9 Jul 2025 18:51:37 +0500 Subject: [PATCH 152/262] Improve metrics so we could better plot them (#193) --- .../src/builders/flashblocks/payload.rs | 26 +++++++++++++------ crates/builder/op-rbuilder/src/metrics.rs | 10 ++++--- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 4f674383..7886b491 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -281,9 +281,11 @@ where first_flashblock_offset = first_flashblock_offset.as_millis(), flashblocks_interval = self.config.specific.interval.as_millis(), ); - ctx.metrics - .target_flashblock - .record(flashblocks_per_block as f64); + ctx.metrics.reduced_flashblocks_number.record( + self.config + .flashblocks_per_block() + .saturating_sub(flashblocks_per_block) as f64, + ); ctx.metrics .first_flashblock_time_offset .record(first_flashblock_offset.as_millis() as f64); @@ -490,9 +492,14 @@ where // Exit loop if channel closed or cancelled ctx.metrics.block_built_success.increment(1); ctx.metrics.flashblock_count.record(flashblock_count as f64); + ctx.metrics + .missing_flashblocks_count + .record(flashblocks_per_block.saturating_sub(flashblock_count) as f64); debug!( target: "payload_builder", - message = "Payload building complete, channel closed or job cancelled" + message = "Payload building complete, channel closed or job cancelled", + missing_falshblocks = flashblocks_per_block.saturating_sub(flashblock_count), + reduced_flashblocks = self.config.flashblocks_per_block().saturating_sub(flashblocks_per_block), ); span.record("flashblock_count", flashblock_count); return Ok(()); @@ -579,14 +586,17 @@ where self.config.specific.interval, ); }; - self.metrics - .flashblock_time_drift - .record(time_drift.as_millis() as f64); + self.metrics.flashblocks_time_drift.record( + self.config + .block_time + .as_millis() + .saturating_sub(time_drift.as_millis()) as f64, + ); debug!( target: "payload_builder", message = "Time drift for building round", ?target_time, - ?time_drift, + time_drift = self.config.block_time.as_millis().saturating_sub(time_drift.as_millis()), ?timestamp ); // This is extra check to ensure that we would account at least for block time in case we have any timer discrepancies. diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 25ca098d..00abdddb 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -76,10 +76,12 @@ pub struct OpRBuilderMetrics { pub da_block_size_limit: Gauge, /// Da tx size limit pub da_tx_size_limit: Gauge, - /// Desired number of flashblocks - pub target_flashblock: Histogram, - /// Time drift that we account for in the beginning of block building - pub flashblock_time_drift: Histogram, + /// How much less flashblocks we issue to be on time with block construction + pub reduced_flashblocks_number: Histogram, + /// How much less flashblocks we issued in reality, comparing to calculated number for block + pub missing_flashblocks_count: Histogram, + /// How much time we have deducted from block building time + pub flashblocks_time_drift: Histogram, /// Time offset we used for first flashblock pub first_flashblock_time_offset: Histogram, /// Number of requests sent to the eth_sendBundle endpoint From 889e41eee7d34adc8eeac8c03df917ede4c2f99e Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 9 Jul 2025 18:57:18 +0500 Subject: [PATCH 153/262] Bump reth to 1.5.1 (#192) * Reth 151 * Add debug bounds --- crates/builder/op-rbuilder/src/builders/context.rs | 6 +++--- .../builder/op-rbuilder/src/builders/flashblocks/payload.rs | 2 +- crates/builder/op-rbuilder/src/builders/standard/payload.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 385ad0be..03f41fdd 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -234,7 +234,7 @@ impl OpPayloadBuilderCtx { db: &mut State, ) -> Result, PayloadBuilderError> where - DB: Database, + DB: Database + std::fmt::Debug, { let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); @@ -325,7 +325,7 @@ impl OpPayloadBuilderCtx { block_da_limit: Option, ) -> Result, PayloadBuilderError> where - DB: Database, + DB: Database + std::fmt::Debug, { let execute_txs_start_time = Instant::now(); let mut num_txs_considered = 0; @@ -555,7 +555,7 @@ impl OpPayloadBuilderCtx { message: Vec, ) -> Option<()> where - DB: Database, + DB: Database + std::fmt::Debug, { self.builder_signer() .map(|signer| { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 7886b491..94c67a4f 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -648,7 +648,7 @@ fn execute_pre_steps( ctx: &OpPayloadBuilderCtx, ) -> Result, PayloadBuilderError> where - DB: Database, + DB: Database + std::fmt::Debug, { // 1. apply pre-execution changes ctx.evm_config diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 18be6779..25e478a3 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -343,7 +343,7 @@ impl OpBuilder<'_, Txs> { ctx: &OpPayloadBuilderCtx, ) -> Result, PayloadBuilderError> where - DB: Database + AsRef

, + DB: Database + AsRef

+ std::fmt::Debug, P: StorageRootProvider, { let Self { best } = self; @@ -438,7 +438,7 @@ impl OpBuilder<'_, Txs> { ctx: OpPayloadBuilderCtx, ) -> Result, PayloadBuilderError> where - DB: Database + AsRef

, + DB: Database + AsRef

+ std::fmt::Debug, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, { let ExecutedPayload { info } = match self.execute(&mut state, &ctx)? { From f2feb76cbcb3a8eee7377206dee833706dc16308 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 9 Jul 2025 21:58:01 +0500 Subject: [PATCH 154/262] Fix ordering issue by not including arriving txs into the best transaction iterator (#195) --- .../op-rbuilder/src/builders/flashblocks/payload.rs | 6 +++++- .../op-rbuilder/src/builders/standard/payload.rs | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 94c67a4f..295afd7e 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -35,6 +35,7 @@ use reth_revm::{ db::{states::bundle_state::BundleRetention, BundleState}, State, }; +use reth_transaction_pool::BestTransactions; use revm::Database; use rollup_boost::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, @@ -385,8 +386,11 @@ where let best_txs_start_time = Instant::now(); let best_txs = BestPayloadTransactions::new( + // TODO: once this issue is fixed we could remove without_updates and rely on regular impl + // https://github.com/paradigmxyz/reth/issues/17325 self.pool - .best_transactions_with_attributes(ctx.best_transaction_attributes()), + .best_transactions_with_attributes(ctx.best_transaction_attributes()) + .without_updates(), ); ctx.metrics .transaction_pool_fetch_duration diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 25e478a3..81f4dfe1 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -30,7 +30,9 @@ use reth_provider::{ use reth_revm::{ database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State, }; -use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; +use reth_transaction_pool::{ + BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool, +}; use revm::Database; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; @@ -135,7 +137,12 @@ impl OpPayloadTransactions for () { pool: Pool, attr: BestTransactionsAttributes, ) -> impl PayloadTransactions { - BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) + // 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(), + ) } } From 4f45dc7ee4260564bad19c80b02d09dee08fb37a Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Fri, 11 Jul 2025 15:13:50 +0500 Subject: [PATCH 155/262] Remove without_updates for flashblocks (#198) --- .../op-rbuilder/src/builders/flashblocks/payload.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 295afd7e..9da41763 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -35,7 +35,6 @@ use reth_revm::{ db::{states::bundle_state::BundleRetention, BundleState}, State, }; -use reth_transaction_pool::BestTransactions; use revm::Database; use rollup_boost::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, @@ -386,11 +385,9 @@ where let best_txs_start_time = Instant::now(); let best_txs = BestPayloadTransactions::new( - // TODO: once this issue is fixed we could remove without_updates and rely on regular impl - // https://github.com/paradigmxyz/reth/issues/17325 + // We are not using without_updates in here, so arriving transaction could target the current block self.pool - .best_transactions_with_attributes(ctx.best_transaction_attributes()) - .without_updates(), + .best_transactions_with_attributes(ctx.best_transaction_attributes()), ); ctx.metrics .transaction_pool_fetch_duration From 75664869b30d44b28fb13a9bc0f2fed68727d798 Mon Sep 17 00:00:00 2001 From: Ferran Borreguero Date: Tue, 15 Jul 2025 09:43:00 +0100 Subject: [PATCH 156/262] Add a test to validate that no-tx-pool works (#199) * Add a test to validate that no-tx-pool works * Fix lint --- .../op-rbuilder/src/tests/framework/driver.rs | 11 +++++++++-- crates/builder/op-rbuilder/src/tests/smoke.rs | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/tests/framework/driver.rs b/crates/builder/op-rbuilder/src/tests/framework/driver.rs index 4d3393fb..54e6833b 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/driver.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/driver.rs @@ -94,6 +94,11 @@ impl ChainDriver { // public test api impl ChainDriver { + pub async fn build_new_block_with_no_tx_pool(&self) -> eyre::Result> { + self.build_new_block_with_txs_timestamp(vec![], Some(true), None, None) + .await + } + /// Builds a new block using the current state of the chain and the transactions in the pool. pub async fn build_new_block(&self) -> eyre::Result> { self.build_new_block_with_txs(vec![]).await @@ -104,7 +109,7 @@ impl ChainDriver { &self, timestamp_jitter: Option, ) -> eyre::Result> { - self.build_new_block_with_txs_timestamp(vec![], None, timestamp_jitter) + self.build_new_block_with_txs_timestamp(vec![], None, None, timestamp_jitter) .await } @@ -112,6 +117,7 @@ impl ChainDriver { pub async fn build_new_block_with_txs_timestamp( &self, txs: Vec, + no_tx_pool: Option, block_timestamp: Option, // Amount of time to lag before sending FCU. This tests late FCU scenarios timestamp_jitter: Option, @@ -178,6 +184,7 @@ impl ChainDriver { }, transactions: Some(vec![block_info_tx].into_iter().chain(txs).collect()), gas_limit: Some(self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)), + no_tx_pool, ..Default::default() }) .await?; @@ -259,7 +266,7 @@ impl ChainDriver { let latest_timestamp = Duration::from_secs(latest.header.timestamp); let block_timestamp = latest_timestamp + Self::MIN_BLOCK_TIME; - self.build_new_block_with_txs_timestamp(txs, Some(block_timestamp), None) + self.build_new_block_with_txs_timestamp(txs, None, Some(block_timestamp), None) .await } diff --git a/crates/builder/op-rbuilder/src/tests/smoke.rs b/crates/builder/op-rbuilder/src/tests/smoke.rs index 6439bd88..f4ee07c3 100644 --- a/crates/builder/op-rbuilder/src/tests/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/smoke.rs @@ -176,3 +176,16 @@ async fn produces_blocks_under_load_within_deadline(rbuilder: LocalInstance) -> Ok(()) } + +#[rb_test] +async fn test_no_tx_pool(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // make sure we can build a couple of blocks first + let _ = driver.build_new_block().await?; + + // now lets try to build a block with no transactions + let _ = driver.build_new_block_with_no_tx_pool().await?; + + Ok(()) +} From 6b69848f094460fcab1fa4156ad870d64295e9d8 Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 16 Jul 2025 08:50:04 -0700 Subject: [PATCH 157/262] Add TDX quote provider service (#200) --- crates/builder/op-rbuilder/Cargo.toml | 23 +-- .../src/flashtestations/attestation.rs | 48 +---- .../src/flashtestations/tx_manager.rs | 12 +- .../builder/tdx-quote-provider/.dockerignore | 6 + crates/builder/tdx-quote-provider/Cargo.toml | 41 ++++ crates/builder/tdx-quote-provider/Dockerfile | 23 +++ crates/builder/tdx-quote-provider/README.md | 102 ++++++++++ crates/builder/tdx-quote-provider/src/lib.rs | 7 + crates/builder/tdx-quote-provider/src/main.rs | 95 +++++++++ .../builder/tdx-quote-provider/src/metrics.rs | 9 + .../tdx-quote-provider/src/provider.rs | 93 +++++++++ .../builder/tdx-quote-provider/src/server.rs | 180 ++++++++++++++++++ .../builder/tdx-quote-provider/tests/mod.rs | 2 + .../tdx-quote-provider/tests/simple.rs | 114 +++++++++++ .../tests/test_data/quote.bin | Bin 0 -> 5006 bytes 15 files changed, 684 insertions(+), 71 deletions(-) create mode 100644 crates/builder/tdx-quote-provider/.dockerignore create mode 100644 crates/builder/tdx-quote-provider/Cargo.toml create mode 100644 crates/builder/tdx-quote-provider/Dockerfile create mode 100644 crates/builder/tdx-quote-provider/README.md create mode 100644 crates/builder/tdx-quote-provider/src/lib.rs create mode 100644 crates/builder/tdx-quote-provider/src/main.rs create mode 100644 crates/builder/tdx-quote-provider/src/metrics.rs create mode 100644 crates/builder/tdx-quote-provider/src/provider.rs create mode 100644 crates/builder/tdx-quote-provider/src/server.rs create mode 100644 crates/builder/tdx-quote-provider/tests/mod.rs create mode 100644 crates/builder/tdx-quote-provider/tests/simple.rs create mode 100644 crates/builder/tdx-quote-provider/tests/test_data/quote.bin diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index dfca7317..a2bf891e 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -55,9 +55,9 @@ reth-optimism-rpc.workspace = true reth-tasks.workspace = true reth-tracing-otlp = { workspace = true, optional = true } -alloy.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true +alloy-contract.workspace = true alloy-eips.workspace = true alloy-rpc-types-beacon.workspace = true alloy-rpc-types-engine.workspace = true @@ -69,6 +69,8 @@ alloy-network.workspace = true alloy-provider.workspace = true alloy-serde.workspace = true alloy-json-rpc.workspace = true +alloy-signer-local.workspace = true +alloy-sol-types.workspace = true # op alloy-op-evm.workspace = true @@ -121,9 +123,6 @@ hex = "0.4" ureq = "2.10" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } -# TODO: this deps routes to https://github.com/SozinM/tdx-attestation-sdk/tree/msozin/backport - backport for tdx error fix, replace it once https://github.com/automata-network/tdx-attestation-sdk/pull/17 is merged -tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", rev = "ce02e0009c20bb4fc37c4c95701925fc3f139b41", optional = true } -dcap-rs = { git = "https://github.com/automata-network/dcap-rs.git", optional = true } dashmap = { version = "6.1", optional = true } nanoid = { version = "0.4", optional = true } @@ -187,26 +186,10 @@ testing = [ interop = [] -flashtestations = ["dcap-rs", "tdx"] - telemetry = ["reth-tracing-otlp", "opentelemetry"] custom-engine-api = [] -ci-features = [ - "default", - "jemalloc-prof", - "min-error-logs", - "min-warn-logs", - "min-info-logs", - "min-debug-logs", - "min-trace-logs", - "testing", - "interop", - "telemetry", - "custom-engine-api", -] - [[bin]] name = "op-rbuilder" path = "src/bin/op-rbuilder/main.rs" diff --git a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs index 9e3a83cf..e52f588a 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs @@ -1,6 +1,4 @@ use std::io::Read; -#[cfg(feature = "flashtestations")] -use tdx::{device::DeviceOptions, Tdx}; use tracing::info; use ureq; @@ -20,37 +18,6 @@ pub trait AttestationProvider { fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result>; } -/// Real TDX hardware attestation provider -#[cfg(feature = "flashtestations")] -pub struct TdxAttestationProvider { - tdx: Tdx, -} - -#[cfg(feature = "flashtestations")] -impl Default for TdxAttestationProvider { - fn default() -> Self { - Self::new() - } -} - -#[cfg(feature = "flashtestations")] -impl TdxAttestationProvider { - pub fn new() -> Self { - Self { tdx: Tdx::new() } - } -} - -#[cfg(feature = "flashtestations")] -impl AttestationProvider for TdxAttestationProvider { - fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { - self.tdx - .get_attestation_report_raw_with_options(DeviceOptions { - report_data: Some(report_data), - }) - .map_err(|e| e.into()) - } -} - /// Debug HTTP service attestation provider pub struct DebugAttestationProvider { service_url: String, @@ -90,16 +57,9 @@ pub fn get_attestation_provider( .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), )) } else { - #[cfg(feature = "flashtestations")] - { - Box::new(TdxAttestationProvider::new()) - } - #[cfg(not(feature = "flashtestations"))] - { - info!("Using debug attestation provider as flashtestations feature is disabled"); - Box::new(DebugAttestationProvider::new( - DEBUG_QUOTE_SERVICE_URL.to_string(), - )) - } + // TODO: replace with real attestation provider + Box::new(DebugAttestationProvider::new( + DEBUG_QUOTE_SERVICE_URL.to_string(), + )) } } diff --git a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs index 5f76d4a7..d6c6345c 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs @@ -2,6 +2,7 @@ use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_network::ReceiptResponse; use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_rpc_types_eth::TransactionRequest; use alloy_transport::TransportResult; use op_alloy_consensus::OpTypedTransaction; use reth_optimism_node::OpBuiltPayload; @@ -9,12 +10,9 @@ use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use std::time::Duration; -use alloy::{ - signers::local::PrivateKeySigner, - sol, - sol_types::{SolCall, SolValue}, -}; use alloy_provider::{PendingTransactionBuilder, Provider, ProviderBuilder}; +use alloy_signer_local::PrivateKeySigner; +use alloy_sol_types::{sol, SolCall, SolValue}; use op_alloy_network::Optimism; use tracing::{debug, error, info}; @@ -81,7 +79,7 @@ impl TxManager { .await?; // Create funding transaction - let funding_tx = alloy::rpc::types::TransactionRequest { + let funding_tx = TransactionRequest { from: Some(from.address), to: Some(TxKind::Call(to)), value: Some(amount), @@ -135,7 +133,7 @@ impl TxManager { rawQuote: quote_bytes, } .abi_encode(); - let tx = alloy::rpc::types::TransactionRequest { + let tx = TransactionRequest { from: Some(self.tee_service_signer.address), to: Some(TxKind::Call(self.registry_address)), // gas: Some(10_000_000), // Set gas limit manually as the contract is gas heavy diff --git a/crates/builder/tdx-quote-provider/.dockerignore b/crates/builder/tdx-quote-provider/.dockerignore new file mode 100644 index 00000000..de51f7f6 --- /dev/null +++ b/crates/builder/tdx-quote-provider/.dockerignore @@ -0,0 +1,6 @@ +target/ +.git/ +.github/ +.gitignore +tests/ +README.md \ No newline at end of file diff --git a/crates/builder/tdx-quote-provider/Cargo.toml b/crates/builder/tdx-quote-provider/Cargo.toml new file mode 100644 index 00000000..5616cba8 --- /dev/null +++ b/crates/builder/tdx-quote-provider/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "tdx-quote-provider" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "TDX quote provider API service" +readme = "README.md" + +[dependencies] +axum = { version = "0.8.1" } +thiserror.workspace = true +clap.workspace = true +tracing.workspace = true +tokio = { workspace = true, features = ["full", "signal"] } +eyre.workspace = true +metrics.workspace = true +metrics-derive = "0.1" +serde.workspace = true +serde_json.workspace = true + +hex = "0.4.3" +dotenvy = "0.15.4" +metrics-exporter-prometheus = { version = "0.17.0", features = [ + "http-listener", +] } +tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } +tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", features = ["configfs"], branch = "main"} + +[dev-dependencies] +reqwest.workspace = true + +[[bin]] +name = "tdx-quote-provider" +path = "src/main.rs" + +[lib] +name = "tdx_quote_provider" +path = "src/lib.rs" \ No newline at end of file diff --git a/crates/builder/tdx-quote-provider/Dockerfile b/crates/builder/tdx-quote-provider/Dockerfile new file mode 100644 index 00000000..a9c2b715 --- /dev/null +++ b/crates/builder/tdx-quote-provider/Dockerfile @@ -0,0 +1,23 @@ +FROM rust:1.88 AS builder + +WORKDIR /app + +ARG BINARY="tdx-quote-provider" +ARG FEATURES + +COPY . . + +RUN apt-get update && apt-get install -y libssl3 libtss2-dev + +RUN cargo build --release --features="$FEATURES" --package=${BINARY} + +FROM debian:12-slim +WORKDIR /app + +# Install runtime dependencies +RUN apt-get update && apt-get install -y libssl3 libtss2-dev + +ARG BINARY="tdx-quote-provider" +COPY --from=builder /app/target/release/${BINARY} /usr/local/bin/ + +ENTRYPOINT ["/usr/local/bin/tdx-quote-provider"] diff --git a/crates/builder/tdx-quote-provider/README.md b/crates/builder/tdx-quote-provider/README.md new file mode 100644 index 00000000..205bdd67 --- /dev/null +++ b/crates/builder/tdx-quote-provider/README.md @@ -0,0 +1,102 @@ +# TDX Quote Provider + +This crate is a service intended to be run alongside the same TDX VM as op-rbuilder for flashtestations. This is a simple HTTP server that generates and returns an attestation quote by the requesting service. + +TDX attestations uses configfs, which requires root access. Having a separate service allow applications like the op-rbuilder to request attestations without root access. + +## Usage + +You can run the server using [Cargo](https://doc.rust-lang.org/cargo/). To run the quote provider server: + +```bash +cargo run -p tdx-quote-provider --bin tdx-quote-provider -- +``` + +This will run a server that will generate and provide TDX attestation quotes on http:localhost:8181 by default. + +To run the server with a mock attestation quote for testing: + +```bash +cargo run -p tdx-quote-provider --bin tdx-quote-provider \ + --mock \ + --mock-attestation-path /path/to/mock/quote.bin +``` + +### Command-line Options + +| Flag | Environment Variable | Default | Description | +|------|---------------------|---------|-------------| +| `--service-host` | `SERVICE_HOST` | `127.0.0.1` | Host to run the HTTP server on | +| `--service-port` | `SERVICE_PORT` | `8181` | Port to run the HTTP server on | +| `--metrics` | `METRICS` | `false` | Enable Prometheus metrics | +| `--metrics-host` | `METRICS_HOST` | `127.0.0.1` | Host to run the metrics server on | +| `--metrics-port` | `METRICS_PORT` | `9090` | Port to run the metrics server on | +| `--mock` | `MOCK` | `false` | Use mock attestation for testing | +| `--mock-attestation-path` | `MOCK_ATTESTATION_PATH` | `""` | Path to the mock attestation file | +| `--log-level` | `LOG_LEVEL` | `info` | Log level (trace, debug, info, warn, error) | +| `--log-format` | `LOG_FORMAT` | `text` | Log format (text, json) | + +## Endpoints + +### `GET /healthz` +Health check endpoint that returns `200 OK` if the server is running. + +**Response:** +``` +HTTP/1.1 200 OK +``` + +### `GET /attest/{appdata}` +Generates and returns a TDX attestation quote for the provided report data. In the case of op-rbuilder, this is the public key of the ethereum key pair generated during the bootstrapping step for flashtestations. + +**Parameters:** +- `appdata` (path parameter): Hex-encoded 64-byte report data + +**Response:** +- **Success (200 OK):** Binary attestation quote with `Content-Type: application/octet-stream` + +**Example:** +```bash +curl -X GET "http://localhost:8181/attest/bbbbf586ac29a7b62fef9118e9d614179962d463419ebd905eb5ece84f2946dfccff93a66129a140ea49c49f7590c36143ad2aec7f8ed74aaa0ff479494a6493" \ # debug key for flashtestations + -H "Accept: application/octet-stream" \ + --output attestation.bin +``` + +### Metrics Endpoint + +When enabled with `--metrics`, Prometheus metrics are available at the configured metrics address (default: `http://localhost:9090`). + +## Contributing + +### Building & Testing + +All tests and test data with a mock attestation quote are in the `/tests` folder. + +```bash +# Build the project +cargo build -p tdx-quote-provider + +# Run all the tests +cargo test -p tdx-quote-provider +``` + +### Deployment + +To build a docker image: + +```bash +# Build from the workspace root +docker build -f crates/tdx-quote-provider/Dockerfile -t tdx-quote-provider . +``` + +Builds of the websocket proxy are provided on [Dockerhub](https://hub.docker.com/r/flashbots/tdx-quote-provider/tags). + +You can see a full list of parameters by running: + +`docker run flashbots/tdx-quote-provider:latest --help` + +Example: + +```bash +docker run flashbots/tdx-quote-provider:latest +``` \ No newline at end of file diff --git a/crates/builder/tdx-quote-provider/src/lib.rs b/crates/builder/tdx-quote-provider/src/lib.rs new file mode 100644 index 00000000..3aad8c3a --- /dev/null +++ b/crates/builder/tdx-quote-provider/src/lib.rs @@ -0,0 +1,7 @@ +//! TDX Quote Provider +//! +//! This crate provides functionality for generating and managing TDX attestation quotes. + +pub mod metrics; +pub mod provider; +pub mod server; diff --git a/crates/builder/tdx-quote-provider/src/main.rs b/crates/builder/tdx-quote-provider/src/main.rs new file mode 100644 index 00000000..ecadead0 --- /dev/null +++ b/crates/builder/tdx-quote-provider/src/main.rs @@ -0,0 +1,95 @@ +use clap::Parser; +use dotenvy::dotenv; +use metrics_exporter_prometheus::PrometheusBuilder; +use tracing::{info, Level}; +use tracing_subscriber::filter::EnvFilter; + +use crate::server::{Server, ServerConfig}; + +mod metrics; +mod provider; +mod server; + +#[derive(Clone, Parser, Debug)] +#[command(about = "TDX Quote Provider CLI")] +struct Args { + /// Host to run the http server on + #[arg(long, env, default_value = "127.0.0.1")] + pub service_host: String, + + /// Port to run the http server on + #[arg(long, env, default_value = "8181")] + pub service_port: u16, + + // Enable Prometheus metrics + #[arg(long, env, default_value = "false")] + pub metrics: bool, + + /// Host to run the metrics server on + #[arg(long, env, default_value = "127.0.0.1")] + pub metrics_host: String, + + /// Port to run the metrics server on + #[arg(long, env, default_value = "9090")] + pub metrics_port: u16, + + /// Use mock attestation for testing + #[arg(long, env, default_value = "false")] + pub mock: bool, + + /// Path to the mock attestation file + #[arg(long, env, default_value = "")] + pub mock_attestation_path: String, + + /// Log level + #[arg(long, env, default_value = "info")] + pub log_level: Level, + + /// Log format + #[arg(long, env, default_value = "text")] + pub log_format: String, +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + dotenv().ok(); + + let args = Args::parse(); + + if args.log_format == "json" { + tracing_subscriber::fmt() + .json() + .with_env_filter(EnvFilter::new(args.log_level.to_string())) + .with_ansi(false) + .init(); + } else { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::new(args.log_level.to_string())) + .init(); + } + + info!("Starting TDX quote provider"); + + if args.metrics { + let metrics_addr = format!("{}:{}", args.metrics_host, args.metrics_port); + info!(message = "starting metrics server", address = metrics_addr); + let socket_addr: std::net::SocketAddr = + metrics_addr.parse().expect("invalid metrics address"); + let builder = PrometheusBuilder::new().with_http_listener(socket_addr); + + builder + .install() + .expect("failed to setup Prometheus endpoint") + } + + // Start the server + let server = Server::new(ServerConfig { + listen_addr: format!("{}:{}", args.service_host, args.service_port) + .parse() + .unwrap(), + use_mock: args.mock, + mock_attestation_path: args.mock_attestation_path, + }); + + server.listen().await +} diff --git a/crates/builder/tdx-quote-provider/src/metrics.rs b/crates/builder/tdx-quote-provider/src/metrics.rs new file mode 100644 index 00000000..695fad58 --- /dev/null +++ b/crates/builder/tdx-quote-provider/src/metrics.rs @@ -0,0 +1,9 @@ +use metrics::Histogram; +use metrics_derive::Metrics; + +#[derive(Metrics, Clone)] +#[metrics(scope = "tdx_quote_provider")] +pub struct Metrics { + /// Duration of attestation request + pub attest_duration: Histogram, +} diff --git a/crates/builder/tdx-quote-provider/src/provider.rs b/crates/builder/tdx-quote-provider/src/provider.rs new file mode 100644 index 00000000..fe6d21c9 --- /dev/null +++ b/crates/builder/tdx-quote-provider/src/provider.rs @@ -0,0 +1,93 @@ +use std::{fs::File, io::Read, sync::Arc}; +use tdx::{device::DeviceOptions, error::TdxError, Tdx}; +use thiserror::Error; +use tracing::info; + +#[derive(Error, Debug)] +pub enum AttestationError { + #[error("Failed to get attestation: {0}")] + GetAttestationFailed(TdxError), + #[error("Failed to read mock attestation file: {0}")] + ReadMockAttestationFailed(std::io::Error), +} + +/// Configuration for attestation +#[derive(Default)] +pub struct AttestationConfig { + /// If true, uses the mock attestation provider instead of real TDX hardware + pub mock: bool, + /// Path to the mock attestation file + pub mock_attestation_path: String, +} + +/// Trait for attestation providers +pub trait AttestationProvider { + fn get_attestation(&self, report_data: [u8; 64]) -> Result, AttestationError>; +} + +/// Real TDX hardware attestation provider +pub struct TdxAttestationProvider { + tdx: Tdx, +} + +impl Default for TdxAttestationProvider { + fn default() -> Self { + Self::new() + } +} + +impl TdxAttestationProvider { + pub fn new() -> Self { + Self { tdx: Tdx::new() } + } +} + +impl AttestationProvider for TdxAttestationProvider { + fn get_attestation(&self, report_data: [u8; 64]) -> Result, AttestationError> { + self.tdx + .get_attestation_report_raw_with_options(DeviceOptions { + report_data: Some(report_data), + }) + .map(|(quote, _var_data)| quote) + .map_err(AttestationError::GetAttestationFailed) + } +} + +/// Mock attestation provider +pub struct MockAttestationProvider { + mock_attestation_path: String, +} + +impl MockAttestationProvider { + pub fn new(mock_attestation_path: String) -> Self { + Self { + mock_attestation_path, + } + } +} + +impl AttestationProvider for MockAttestationProvider { + fn get_attestation(&self, _report_data: [u8; 64]) -> Result, AttestationError> { + info!( + target: "tdx_quote_provider", + mock_attestation_path = self.mock_attestation_path, + "using mock attestation provider", + ); + let mut file = File::open(self.mock_attestation_path.clone()) + .map_err(AttestationError::ReadMockAttestationFailed)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer) + .map_err(AttestationError::ReadMockAttestationFailed)?; + Ok(buffer) + } +} + +pub fn get_attestation_provider( + config: AttestationConfig, +) -> Arc { + if config.mock { + Arc::new(MockAttestationProvider::new(config.mock_attestation_path)) + } else { + Arc::new(TdxAttestationProvider::new()) + } +} diff --git a/crates/builder/tdx-quote-provider/src/server.rs b/crates/builder/tdx-quote-provider/src/server.rs new file mode 100644 index 00000000..d355727a --- /dev/null +++ b/crates/builder/tdx-quote-provider/src/server.rs @@ -0,0 +1,180 @@ +use std::{net::SocketAddr, sync::Arc, time::Instant}; + +use axum::{ + body::Body, + extract::{Path, State}, + http::{Response, StatusCode}, + response::IntoResponse, + routing::get, + Router, +}; +use serde_json::json; +use tokio::{net::TcpListener, signal}; +use tracing::info; + +use crate::{ + metrics::Metrics, + provider::{get_attestation_provider, AttestationConfig, AttestationProvider}, +}; + +/// Server configuration +#[derive(Debug, Clone)] +pub struct ServerConfig { + /// Address to listen on + pub listen_addr: SocketAddr, + /// Whether to use mock attestation + pub use_mock: bool, + /// Path to the mock attestation file + pub mock_attestation_path: String, +} + +impl Default for ServerConfig { + fn default() -> Self { + Self { + listen_addr: "127.0.0.1:8181".parse().unwrap(), + use_mock: false, + mock_attestation_path: "".to_string(), + } + } +} + +#[derive(Clone)] +struct ServerState { + quote_provider: Arc, + metrics: Metrics, +} + +#[derive(Clone)] +pub struct Server { + /// Quote provider + quote_provider: Arc, + /// Metrics for the server + metrics: Metrics, + /// Server configuration + config: ServerConfig, +} + +impl Server { + pub fn new(config: ServerConfig) -> Self { + let attestation_config = AttestationConfig { + mock: config.use_mock, + mock_attestation_path: config.mock_attestation_path.clone(), + }; + let quote_provider = get_attestation_provider(attestation_config); + Self { + quote_provider, + metrics: Metrics::default(), + config, + } + } + + pub async fn listen(self) -> eyre::Result<()> { + let router = Router::new() + .route("/healthz", get(healthz_handler)) + .route("/attest/{appdata}", get(attest_handler)) + .with_state(ServerState { + quote_provider: self.quote_provider, + metrics: self.metrics, + }); + + let listener = TcpListener::bind(self.config.listen_addr).await?; + info!( + message = "starting server", + address = listener.local_addr()?.to_string() + ); + + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(shutdown_signal()) + .await + .map_err(|e| eyre::eyre!("failed to start server: {}", e)) + } +} + +async fn healthz_handler() -> impl IntoResponse { + StatusCode::OK +} + +async fn attest_handler( + Path(appdata): Path, + State(server): State, +) -> impl IntoResponse { + let start = Instant::now(); + + // Decode hex + let appdata = match hex::decode(appdata) { + Ok(data) => data, + Err(e) => { + info!(target: "tdx_quote_provider", error = %e, "Invalid hex in report data for attestation"); + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from( + json!({"message": "Invalid hex in report data for attestation"}).to_string(), + )) + .unwrap() + .into_response(); + } + }; + + // Convert to report data + let report_data: [u8; 64] = match appdata.try_into() { + Ok(data) => data, + Err(e) => { + info!(target: "tdx_quote_provider", error = ?e, "Invalid report data length for attestation"); + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from( + json!({"message": "Invalid report data length for attestation"}).to_string(), + )) + .unwrap() + .into_response(); + } + }; + + // Get attestation + match server.quote_provider.get_attestation(report_data) { + Ok(attestation) => { + let duration = start.elapsed(); + server + .metrics + .attest_duration + .record(duration.as_secs_f64()); + + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/octet-stream") + .body(Body::from(attestation)) + .unwrap() + .into_response() + } + Err(e) => { + tracing::error!(target: "tdx_quote_provider", error = %e, "Failed to get TDX attestation"); + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from( + json!({"message": "Failed to get TDX attestation"}).to_string(), + )) + .unwrap() + .into_response() + } + } +} + +async fn shutdown_signal() { + let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate()).unwrap(); + let mut sigint = signal::unix::signal(signal::unix::SignalKind::interrupt()).unwrap(); + + tokio::select! { + _ = signal::ctrl_c() => { + info!("Received Ctrl+C, shutting down gracefully"); + } + _ = sigterm.recv() => { + info!("Received SIGTERM, shutting down gracefully"); + } + _ = sigint.recv() => { + info!("Received SIGINT, shutting down gracefully"); + } + } +} diff --git a/crates/builder/tdx-quote-provider/tests/mod.rs b/crates/builder/tdx-quote-provider/tests/mod.rs new file mode 100644 index 00000000..f4a70f9f --- /dev/null +++ b/crates/builder/tdx-quote-provider/tests/mod.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +mod simple; diff --git a/crates/builder/tdx-quote-provider/tests/simple.rs b/crates/builder/tdx-quote-provider/tests/simple.rs new file mode 100644 index 00000000..afa06028 --- /dev/null +++ b/crates/builder/tdx-quote-provider/tests/simple.rs @@ -0,0 +1,114 @@ +use axum::body::Bytes; +use std::{error::Error, net::SocketAddr, time::Duration}; +use tdx_quote_provider::server::{Server, ServerConfig}; +use tokio::net::TcpListener; + +struct TestHarness { + server: Server, + server_addr: SocketAddr, +} + +impl TestHarness { + async fn alloc_port() -> SocketAddr { + let address = SocketAddr::from(([127, 0, 0, 1], 0)); + let listener = TcpListener::bind(&address).await.unwrap(); + listener.local_addr().unwrap() + } + + fn new(addr: SocketAddr) -> TestHarness { + let path = format!("{}/tests/test_data/quote.bin", env!("CARGO_MANIFEST_DIR")); + Self { + server: Server::new(ServerConfig { + listen_addr: addr, + use_mock: true, + mock_attestation_path: path, + }), + server_addr: addr, + } + } + + async fn healthcheck(&self) -> Result<(), Box> { + let url = format!("http://{}/healthz", self.server_addr); + let response = reqwest::get(url).await?; + match response.error_for_status() { + Ok(_) => Ok(()), + Err(e) => Err(e.into()), + } + } + + async fn attest(&self, app_data: String) -> Result> { + let url = format!("http://{}/attest/{}", self.server_addr, app_data); + let response = reqwest::get(url).await?; + match response.error_for_status() { + Ok(response) => { + let body = response.bytes().await?; + Ok(body) + } + Err(e) => Err(e.into()), + } + } + + async fn start_server(&mut self) { + let mut healthy = true; + + let server = self.server.clone(); + let _server_handle = tokio::spawn(async move { + _ = server.listen().await; + }); + + for _ in 0..5 { + let resp = self.healthcheck().await; + match resp { + Ok(_) => { + healthy = true; + break; + } + Err(_) => { + tokio::time::sleep(Duration::from_millis(25)).await; + } + } + } + + assert!(healthy); + } +} + +#[tokio::test] +async fn test_healthcheck() { + let addr = TestHarness::alloc_port().await; + let mut harness = TestHarness::new(addr); + assert!(harness.healthcheck().await.is_err()); + harness.start_server().await; + assert!(harness.healthcheck().await.is_ok()); +} + +#[tokio::test] +async fn test_mock_attest() { + let addr = TestHarness::alloc_port().await; + + let mut harness = TestHarness::new(addr); + harness.start_server().await; + + let report_data = hex::encode(vec![0; 64]); + let response = harness.attest(report_data).await; + assert!(response.is_ok()); + let body = response.unwrap(); + assert_eq!( + body, + Bytes::from_static(include_bytes!("./test_data/quote.bin")) + ); +} + +#[tokio::test] +async fn test_attest_invalid_report_data() { + let addr = TestHarness::alloc_port().await; + + let mut harness = TestHarness::new(addr); + harness.start_server().await; + + let response = harness.attest("invalid".to_string()).await; + assert!(response.is_err()); + + let response = harness.attest("aede".to_string()).await; + assert!(response.is_err()); +} diff --git a/crates/builder/tdx-quote-provider/tests/test_data/quote.bin b/crates/builder/tdx-quote-provider/tests/test_data/quote.bin new file mode 100644 index 0000000000000000000000000000000000000000..ba753696270643bdcfab1501fe4c87d449ae09c7 GIT binary patch literal 5006 zcmcgv3y@RQ8D0T#y~;yn1%xUcbsX4gZeAN=tL2>g%Du@=a-YeKuO>IUH@SIlHn~Ye znOb#96~wM~q%zPQD`1gAWz_nr9Um2$wLYoO)~Xeh@iDeCI*v+Dl3fV6RFIiwCUcYT zKac-E|M#Eozf-y9rp+7AKY8`q?$M7HZOrYyvb(r9 z`ebqUzIQLnF<(qMX!6nhnb{n^d(za_EM{c%1pm$a=UQg-^ye=M?~HFgbKg^+jY`-b za~t-s`~G}!WcO>=th@2SHHkIeVpux+=(hSljDG92oz&%5Y^)D`c;DL77UrIRaNR~- zefgx+pL+g&SzonEtm5BV{-b$3^W};QR~gdH8+xu@dd2Vsw{N=pmnXg?Uj6ikKf7|J zGdA2a+gP@NeqH!ToqB=uoVN$w`On(NOra1e?Q;L*-ERDC+rzg{ede#f_`2r*e3|>*j8E$N)%3ZC}NeS7!Vne%Acwt}Bn+`o#@gZSSi)cg%cs z?h0YYvK>#_|G|Lv;?Gyvwm$omkR6`dMI$dJPo6t(%D%2Pt&ZRr zdd9}=9(%rf$A)`f!5_Qzhqp?D)1N-&nh7Y2lWe zW-~$8hA)W^C(l^gqkreIWnj(j=cqz>@C2j&{3nbW^|F89JM+uo+*X+3F7-?(=9 zi9i1R{nwN;kH2+Z@Qp=1^z2Qm?pY64zxdDRcfJy`J!~6YUzu_J**hM+aY0wtag!!b zIezL1-6vuvojh&&jG3pLdfKd>(`V0_d&a!^-#hcH#LSV2;v{x?=)^oO+uaZdnhAg3x4>{F<>f&*WFf`U*h_MY5 zr_gu{(@84L6MzAB1p!t}in#Gu#4;2-sj)O9r8*Q642{Qf-g-iUq##pq9vZFjgkg!; z_yob>t?P`pu0_RS!r`jXB#@vHR3XrL!X8W|@?wgmf@LkrrFfxi5rRglg1}-V;F_xj zc#vtW3R66xs=Vb(=|SEUYP?Cgd6`E~!5L{{4eB5aijCDvj=YekZTSq|-%O%PBP z?S`RH!2sGhtLMdHESEKrhgn`MSWJ7oA0r_>K7mX)ISYMLNhVuOn3Tdpw(XT=1r!He zNivinIBF?9N7aZ1;F=*~=-83>LD&%&Ls^`vG+>N}JOk?`M&eywU{DM}jQzGKAHZ1v zL+K!v%@@;=-aguw7Yin=L$j(3(m6^b!l^hVI0B2KiqkgW4$2v4tylKPO?yD|uu9%h zPDrc+EXKrG&?x5O33pvv;cpmYCBuFsObkisC6hnfD0VOqIcLu9sHPxp; zuAY=}j9=`6s#Wb*NjTUVEH0*EBuw)RVMI$%Pi2dRq))W;Mxh#T&{kY><9c3gBpEfM zJJdRsL?;`I@|Ad-Ko!m59SS&u`F_HSa1M`0VnxkCqo=nLaB6tK3C*}8SWl2%Q z)l65Cv?yq^geJ!siv);Njv!vm-$WNDPsNfPex}0Q*0! zb3i#hun3PbfQSTzR_+hr0)@dr5+p1%mjq#G6iTT~n(3u*bbC@X(aM7BkVzUvg*i_= zAI56|xr7Th)X5eO3j1#)2#^rq zI^ZdRz*sWx(IYi4Gte6~M0{*d4>o-t~S3AE;IfTHCoC5tX}wan5%Uxa{C z!r{e{Qov4i=oAO+yQo8_IACL{H>M7tpSJK?hy->day6b*EMA6jgf;la!5HrYHP!?M zHtsreuE2q^&D;pIMoqqVSlI?im$5YAsW=ai+NG7D5IsEJ`xPc8oIwV zB=qPyu4^5#OaLU=Xi;oj^&rq>kX2=#&RCSC`NrA`M7RAd2BMfw3I?Ep60~`S9QJHf z3SkbS(kRv}kE@@99tTw?86)Bzj1ogN$B-%qN_{DRrmxYfq{SGKFNvOFIaD=DbiY)~ z=Hx7w#rox3+7Zzj&Qb_g8U(C-L*RsTOlU+RMI28LgvcRIm8y1-2uV6KNJ6P~h(086 zQUh{4)o+M#NTxD$hrk`&E$Hdyj6&nsRs90IByaApOIY-U2l!_C@672Ul2nXwimNwqbNr7 z-L=estQM;Umr|&wC9O6{MgloSbP=pK=&uiigT2R4yC!N>fhmpGuEORN-kvQbgKWNG zcqCOB^a@NPlZPei(6}rtP9A7HQKKr3_nqxVm8&M4!eL3@%2@^K~kylbOB)kYF61s^NYGQdq^!<;ldKjCnYMH|QYIR4%AfXN_E0(`1w=X(Va!Axs@#B@&uFfSlQlD>rUh`m7INiE z<+^5OVLU6VT8$1n${h5&8(Dt{V&A1++s#|MT(R#`ubsUqhTd-4&FK-=>!B>gI%#^O z_1Z31szqz8;{}YAPz{R)n*yPT%`IfaUdcWih@V4mc(eh#Qrb6?RcL(mY@Fx D7Usjz literal 0 HcmV?d00001 From 53819b551b68ac61392d7343e6c86c3dabf55642 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Thu, 17 Jul 2025 09:38:39 -0400 Subject: [PATCH 158/262] Add gauge metrics for block building steps (#205) * introduce gauge metrics and fix naming for histogram metrics * fix a couple of flashblocks metrics * add fn to set payload builder metrics --- .../op-rbuilder/src/builders/context.rs | 27 ++---- .../src/builders/flashblocks/payload.rs | 52 ++++++++--- .../src/builders/standard/payload.rs | 38 ++++++-- crates/builder/op-rbuilder/src/metrics.rs | 87 ++++++++++++++++--- 4 files changed, 151 insertions(+), 53 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 03f41fdd..17db71c6 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -517,24 +517,15 @@ impl OpPayloadBuilderCtx { info.executed_transactions.push(tx.into_inner()); } - self.metrics - .payload_tx_simulation_duration - .record(execute_txs_start_time.elapsed()); - self.metrics - .payload_num_tx_considered - .record(num_txs_considered as f64); - self.metrics - .payload_num_tx_simulated - .record(num_txs_simulated as f64); - self.metrics - .payload_num_tx_simulated_success - .record(num_txs_simulated_success as f64); - self.metrics - .payload_num_tx_simulated_fail - .record(num_txs_simulated_fail as f64); - self.metrics - .bundles_reverted - .record(num_bundles_reverted as f64); + let payload_tx_simulation_time = execute_txs_start_time.elapsed(); + self.metrics.set_payload_builder_metrics( + payload_tx_simulation_time, + num_txs_considered, + num_txs_simulated, + num_txs_simulated_success, + num_txs_simulated_fail, + num_bundles_reverted, + ); debug!( target: "payload_builder", diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 9da41763..af44c1bc 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -232,9 +232,9 @@ where .unwrap_or(0); let mut info = execute_pre_steps(&mut db, &ctx)?; - ctx.metrics - .sequencer_tx_duration - .record(sequencer_tx_start_time.elapsed()); + 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); // If we have payload with txpool we add first builder tx right after deposits if !ctx.attributes().no_tx_pool { @@ -257,6 +257,9 @@ where 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); if ctx.attributes().no_tx_pool { info!( @@ -264,9 +267,13 @@ where "No transaction pool, skipping transaction pool processing", ); + let total_block_building_time = block_build_start_time.elapsed(); ctx.metrics .total_block_built_duration - .record(block_build_start_time.elapsed()); + .record(total_block_building_time); + ctx.metrics + .total_block_built_gauge + .set(total_block_building_time); // return early since we don't need to build a block with transactions from the pool return Ok(()); @@ -389,9 +396,13 @@ where self.pool .best_transactions_with_attributes(ctx.best_transaction_attributes()), ); + let transaction_pool_fetch_time = best_txs_start_time.elapsed(); ctx.metrics .transaction_pool_fetch_duration - .record(best_txs_start_time.elapsed()); + .record(transaction_pool_fetch_time); + ctx.metrics + .transaction_pool_fetch_gauge + .set(transaction_pool_fetch_time); let tx_execution_start_time = Instant::now(); ctx.execute_best_transactions( @@ -413,13 +424,14 @@ where span.record("flashblock_count", flashblock_count); return Ok(()); } - ctx.metrics - .payload_tx_simulation_duration - .record(tx_execution_start_time.elapsed()); + let payload_tx_simulation_time = tx_execution_start_time.elapsed(); ctx.metrics .payload_tx_simulation_duration - .record(tx_execution_start_time.elapsed()); + .record(payload_tx_simulation_time); + ctx.metrics + .payload_tx_simulation_gauge + .set(payload_tx_simulation_time); // If it is the last flashblocks, add the builder txn to the block if enabled invoke_on_last_flashblock(flashblock_count, last_flashblock, || { @@ -428,9 +440,13 @@ where let total_block_built_duration = Instant::now(); let build_result = build_block(db, &ctx, &mut info); + let total_block_built_duration = total_block_built_duration.elapsed(); ctx.metrics .total_block_built_duration - .record(total_block_built_duration.elapsed()); + .record(total_block_built_duration); + ctx.metrics + .total_block_built_gauge + .set(total_block_built_duration); // Handle build errors with match pattern match build_result { @@ -460,10 +476,10 @@ where .flashblock_build_duration .record(flashblock_build_start_time.elapsed()); ctx.metrics - .payload_byte_size + .flashblock_byte_size_histogram .record(new_payload.block().size() as f64); ctx.metrics - .payload_num_tx + .flashblock_num_tx_histogram .record(info.executed_transactions.len() as f64); best_payload.set(new_payload.clone()); @@ -677,9 +693,13 @@ where // and 4788 contract call let state_merge_start_time = Instant::now(); state.merge_transitions(BundleRetention::Reverts); + let state_transition_merge_time = state_merge_start_time.elapsed(); ctx.metrics .state_transition_merge_duration - .record(state_merge_start_time.elapsed()); + .record(state_transition_merge_time); + ctx.metrics + .state_transition_merge_gauge + .set(state_transition_merge_time); let new_bundle = state.take_bundle(); @@ -723,9 +743,13 @@ where ); })? }; + let state_root_calculation_time = state_root_start_time.elapsed(); ctx.metrics .state_root_calculation_duration - .record(state_root_start_time.elapsed()); + .record(state_root_calculation_time); + ctx.metrics + .state_root_calculation_gauge + .set(state_root_calculation_time); let mut requests_hash = None; let withdrawals_root = if ctx diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 81f4dfe1..10c234d2 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -297,9 +297,13 @@ where builder.build(db, ctx) } .map(|out| { + let total_block_building_time = block_build_start_time.elapsed(); metrics .total_block_built_duration - .record(block_build_start_time.elapsed()); + .record(total_block_building_time); + metrics + .total_block_built_gauge + .set(total_block_building_time); out.with_cached_reads(cached_reads) }) @@ -367,9 +371,9 @@ impl OpBuilder<'_, Txs> { // 3. execute sequencer transactions let mut info = ctx.execute_sequencer_transactions(state)?; - ctx.metrics - .sequencer_tx_duration - .record(sequencer_tx_start_time.elapsed()); + 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 @@ -399,9 +403,14 @@ impl OpBuilder<'_, Txs> { if !ctx.attributes().no_tx_pool { let best_txs_start_time = Instant::now(); let best_txs = best(ctx.best_transaction_attributes()); + let transaction_pool_fetch_time = best_txs_start_time.elapsed(); ctx.metrics .transaction_pool_fetch_duration - .record(best_txs_start_time.elapsed()); + .record(transaction_pool_fetch_time); + ctx.metrics + .transaction_pool_fetch_gauge + .set(transaction_pool_fetch_time); + if ctx .execute_best_transactions( &mut info, @@ -425,12 +434,20 @@ impl OpBuilder<'_, Txs> { // and 4788 contract call state.merge_transitions(BundleRetention::Reverts); + let state_transition_merge_time = state_merge_start_time.elapsed(); ctx.metrics .state_transition_merge_duration - .record(state_merge_start_time.elapsed()); + .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 }; @@ -493,9 +510,13 @@ impl OpBuilder<'_, Txs> { })? }; + let state_root_calculation_time = state_root_start_time.elapsed(); ctx.metrics .state_root_calculation_duration - .record(state_root_start_time.elapsed()); + .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 @@ -585,6 +606,9 @@ impl OpBuilder<'_, Txs> { ctx.metrics .payload_byte_size .record(payload.block().size() as f64); + ctx.metrics + .payload_byte_size_gauge + .set(payload.block().size() as f64); if no_tx_pool { // if `no_tx_pool` is set only transactions from the payload attributes will be included diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 00abdddb..b18ba6b9 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -1,3 +1,4 @@ +use metrics::IntoF64; use reth_metrics::{ metrics::{gauge, Counter, Gauge, Histogram}, Metrics, @@ -40,35 +41,63 @@ pub struct OpRBuilderMetrics { pub flashblock_count: Histogram, /// Number of messages sent pub messages_sent_count: Counter, - /// Total duration of building a block + /// Histogram of the time taken to build a block pub total_block_built_duration: Histogram, - /// Flashblock build duration + /// Latest time taken to build a block + pub total_block_built_gauge: Gauge, + /// Histogram of the time taken to build a Flashblock pub flashblock_build_duration: Histogram, + /// Flashblock byte size histogram + pub flashblock_byte_size_histogram: Histogram, + /// Histogram of transactions in a Flashblock + pub flashblock_num_tx_histogram: Histogram, /// Number of invalid blocks pub invalid_blocks_count: Counter, - /// Duration of fetching transactions from the pool + /// Histogram of fetching transactions from the pool duration pub transaction_pool_fetch_duration: Histogram, - /// Duration of state root calculation + /// Latest time taken to fetch tx from the pool + pub transaction_pool_fetch_gauge: Gauge, + /// Histogram of state root calculation duration pub state_root_calculation_duration: Histogram, - /// Duration of sequencer transaction execution + /// Latest state root calculation duration + pub state_root_calculation_gauge: Gauge, + /// Histogram of sequencer transaction execution duration pub sequencer_tx_duration: Histogram, - /// Duration of state merge transitions + /// Latest sequencer transaction execution duration + pub sequencer_tx_gauge: Gauge, + /// Histogram of state merge transitions duration pub state_transition_merge_duration: Histogram, - /// Duration of payload simulation of all transactions + /// Latest state merge transitions duration + pub state_transition_merge_gauge: Gauge, + /// Histogram of the duration of payload simulation of all transactions pub payload_tx_simulation_duration: Histogram, + /// Latest payload simulation of all transactions duration + pub payload_tx_simulation_gauge: Gauge, /// Number of transaction considered for inclusion in the block pub payload_num_tx_considered: Histogram, - /// Payload byte size + /// Latest number of transactions considered for inclusion in the block + pub payload_num_tx_considered_gauge: Gauge, + /// Payload byte size histogram pub payload_byte_size: Histogram, - /// Number of transactions in the payload + /// Latest Payload byte size + pub payload_byte_size_gauge: Gauge, + /// Histogram of transactions in the payload pub payload_num_tx: Histogram, - /// Number of transactions in the payload that were successfully simulated + /// Latest number of transactions in the payload + pub payload_num_tx_gauge: Gauge, + /// Histogram of transactions in the payload that were successfully simulated pub payload_num_tx_simulated: Histogram, - /// Number of transactions in the payload that were successfully simulated + /// Latest number of transactions in the payload that were successfully simulated + pub payload_num_tx_simulated_gauge: Gauge, + /// Histogram of transactions in the payload that were successfully simulated pub payload_num_tx_simulated_success: Histogram, - /// Number of transactions in the payload that failed simulation + /// Latest number of transactions in the payload that were successfully simulated + pub payload_num_tx_simulated_success_gauge: Gauge, + /// Histogram of transactions in the payload that failed simulation pub payload_num_tx_simulated_fail: Histogram, - /// Duration of tx simulation + /// Latest number of transactions in the payload that failed simulation + pub payload_num_tx_simulated_fail_gauge: Gauge, + /// Histogram of tx simulation duration pub tx_simulation_duration: Histogram, /// Byte size of transactions pub tx_byte_size: Histogram, @@ -92,10 +121,40 @@ pub struct OpRBuilderMetrics { pub failed_bundles: Counter, /// Number of reverted bundles pub bundles_reverted: Histogram, - /// Time taken to respond to a request to the eth_sendBundle endpoint + /// Histogram of eth_sendBundle request duration pub bundle_receive_duration: Histogram, } +impl OpRBuilderMetrics { + pub fn set_payload_builder_metrics( + &self, + payload_tx_simulation_time: impl IntoF64 + Copy, + num_txs_considered: impl IntoF64 + Copy, + num_txs_simulated: impl IntoF64 + Copy, + num_txs_simulated_success: impl IntoF64 + Copy, + num_txs_simulated_fail: impl IntoF64 + Copy, + num_bundles_reverted: impl IntoF64, + ) { + self.payload_tx_simulation_duration + .record(payload_tx_simulation_time); + self.payload_tx_simulation_gauge + .set(payload_tx_simulation_time); + self.payload_num_tx_considered.record(num_txs_considered); + self.payload_num_tx_considered_gauge.set(num_txs_considered); + self.payload_num_tx_simulated.record(num_txs_simulated); + self.payload_num_tx_simulated_gauge.set(num_txs_simulated); + self.payload_num_tx_simulated_success + .record(num_txs_simulated_success); + self.payload_num_tx_simulated_success_gauge + .set(num_txs_simulated_success); + self.payload_num_tx_simulated_fail + .record(num_txs_simulated_fail); + self.payload_num_tx_simulated_fail_gauge + .set(num_txs_simulated_fail); + self.bundles_reverted.record(num_bundles_reverted); + } +} + /// Contains version information for the application. #[derive(Debug, Clone)] pub struct VersionInfo { From d892e6d4c000ae29a7191d5014f70921d2862f3b Mon Sep 17 00:00:00 2001 From: kristoffer Date: Thu, 17 Jul 2025 19:33:09 +0000 Subject: [PATCH 159/262] Remove redundant account initialization (#208) --- crates/builder/op-rbuilder/src/tests/flashblocks.rs | 8 +------- .../op-rbuilder/src/tests/framework/instance.rs | 12 +++--------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index f285c709..d45f8392 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -8,7 +8,7 @@ use tokio_util::sync::CancellationToken; use crate::{ args::{FlashblocksArgs, OpRbuilderArgs}, - tests::{ChainDriverExt, LocalInstance, TransactionBuilderExt}, + tests::{LocalInstance, TransactionBuilderExt}, }; #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -25,7 +25,6 @@ use crate::{ })] async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - driver.fund_default_accounts().await?; // Create a struct to hold received messages let received_messages = Arc::new(Mutex::new(Vec::new())); @@ -94,7 +93,6 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { })] async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - driver.fund_default_accounts().await?; // Create a struct to hold received messages let received_messages = Arc::new(Mutex::new(Vec::new())); @@ -163,7 +161,6 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { })] async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - driver.fund_default_accounts().await?; // Create a struct to hold received messages let received_messages = Arc::new(Mutex::new(Vec::new())); @@ -232,7 +229,6 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { })] async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - driver.fund_default_accounts().await?; // Create a struct to hold received messages let received_messages = Arc::new(Mutex::new(Vec::new())); @@ -301,7 +297,6 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { })] async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - driver.fund_default_accounts().await?; // Create a struct to hold received messages let received_messages = Arc::new(Mutex::new(Vec::new())); @@ -372,7 +367,6 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> })] async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - driver.fund_default_accounts().await?; // Create a struct to hold received messages let received_messages = Arc::new(Mutex::new(Vec::new())); diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 0a9b6836..598a584c 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -6,7 +6,7 @@ use crate::{ tests::{ create_test_db, framework::{driver::ChainDriver, BUILDER_PRIVATE_KEY}, - ChainDriverExt, EngineApi, Ipc, TransactionPoolObserver, + EngineApi, Ipc, TransactionPoolObserver, }, tx::FBPooledTransaction, tx_signer::Signer, @@ -179,10 +179,7 @@ impl LocalInstance { let Commands::Node(ref node_command) = args.command else { unreachable!() }; - let instance = Self::new::(node_command.ext.clone()).await?; - let driver = ChainDriver::::local(&instance).await?; - driver.fund_default_accounts().await?; - Ok(instance) + Self::new::(node_command.ext.clone()).await } /// Creates new local instance of the OP builder node with the flashblocks builder configuration. @@ -194,10 +191,7 @@ impl LocalInstance { }; node_command.ext.flashblocks.enabled = true; node_command.ext.flashblocks.flashblocks_port = 0; // use random os assigned port - let instance = Self::new::(node_command.ext.clone()).await?; - let driver = ChainDriver::::local(&instance).await?; - driver.fund_default_accounts().await?; - Ok(instance) + Self::new::(node_command.ext.clone()).await } pub const fn config(&self) -> &NodeConfig { From 73ec080f5ecb944e3cbb4af4585d2dc820969922 Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 22 Jul 2025 00:51:26 -0700 Subject: [PATCH 160/262] Add flashblocks index to payload building context (#210) * Add flashblocks index to payload building context * refactor * fix index --- .../op-rbuilder/src/builders/context.rs | 8 +- .../src/builders/flashblocks/payload.rs | 117 ++++++++++++------ .../src/builders/standard/payload.rs | 1 + 3 files changed, 82 insertions(+), 44 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 17db71c6..e4408772 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -49,7 +49,7 @@ use crate::{ /// 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 @@ -68,9 +68,11 @@ pub struct OpPayloadBuilderCtx { pub builder_signer: Option, /// The metrics for the builder pub metrics: Arc, + /// Extra context for the payload builder + pub extra_ctx: ExtraCtx, } -impl OpPayloadBuilderCtx { +impl OpPayloadBuilderCtx { /// Returns the parent block the payload will be build on. pub fn parent(&self) -> &SealedHeader { &self.config.parent_header @@ -195,7 +197,7 @@ impl OpPayloadBuilderCtx { } } -impl OpPayloadBuilderCtx { +impl OpPayloadBuilderCtx { /// Constructs a receipt for the given transaction. fn build_receipt( &self, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index af44c1bc..1403b761 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -58,6 +58,43 @@ struct ExtraExecutionInfo { pub last_flashblock_index: usize, } +#[derive(Debug, Default)] +struct FlashblocksExtraCtx { + /// Current flashblock index + pub flashblock_index: u64, + /// Target flashblock count + pub target_flashblock_count: u64, +} + +impl OpPayloadBuilderCtx { + /// Returns the current flashblock index + pub fn flashblock_index(&self) -> u64 { + self.extra_ctx.flashblock_index + } + + /// Returns the target flashblock count + pub fn target_flashblock_count(&self) -> u64 { + self.extra_ctx.target_flashblock_count + } + + /// Increments the flashblock index + pub fn increment_flashblock_index(&mut self) -> u64 { + self.extra_ctx.flashblock_index += 1; + self.extra_ctx.flashblock_index + } + + /// Sets the target flashblock count + pub fn set_target_flashblock_count(&mut self, target_flashblock_count: u64) -> u64 { + self.extra_ctx.target_flashblock_count = target_flashblock_count; + self.extra_ctx.target_flashblock_count + } + + /// Returns if the flashblock is the last one + pub fn is_last_flashblock(&self) -> bool { + self.flashblock_index() == self.target_flashblock_count() - 1 + } +} + /// Optimism's payload builder #[derive(Debug, Clone)] pub struct OpPayloadBuilder { @@ -199,7 +236,7 @@ where .next_evm_env(&config.parent_header, &block_env_attributes) .map_err(PayloadBuilderError::other)?; - let mut ctx = OpPayloadBuilderCtx { + let mut ctx = OpPayloadBuilderCtx:: { evm_config: self.evm_config.clone(), chain_spec: self.client.chain_spec(), config, @@ -210,6 +247,10 @@ where da_config: self.config.da_config.clone(), builder_signer: self.config.builder_signer, metrics: Default::default(), + extra_ctx: FlashblocksExtraCtx { + flashblock_index: 0, + target_flashblock_count: self.config.flashblocks_per_block(), + }, }; let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; @@ -281,27 +322,28 @@ where // We adjust our flashblocks timings based on time_drift if dynamic adjustment enable let (flashblocks_per_block, first_flashblock_offset) = self.calculate_flashblocks(timestamp); + ctx.set_target_flashblock_count(flashblocks_per_block); info!( target: "payload_builder", message = "Performed flashblocks timing derivation", - flashblocks_per_block, + flashblocks_per_block = ctx.target_flashblock_count(), first_flashblock_offset = first_flashblock_offset.as_millis(), flashblocks_interval = self.config.specific.interval.as_millis(), ); ctx.metrics.reduced_flashblocks_number.record( self.config .flashblocks_per_block() - .saturating_sub(flashblocks_per_block) as f64, + .saturating_sub(ctx.target_flashblock_count()) as f64, ); ctx.metrics .first_flashblock_time_offset .record(first_flashblock_offset.as_millis() as f64); - let gas_per_batch = ctx.block_gas_limit() / flashblocks_per_block; + let gas_per_batch = ctx.block_gas_limit() / ctx.target_flashblock_count(); let mut total_gas_per_batch = gas_per_batch; let da_per_batch = ctx .da_config .max_da_block_size() - .map(|da_limit| da_limit / flashblocks_per_block); + .map(|da_limit| da_limit / ctx.target_flashblock_count()); // Check that builder tx won't affect fb limit too much if let Some(da_limit) = da_per_batch { // We error if we can't insert any tx aside from builder tx in flashblock @@ -317,10 +359,6 @@ where *da_limit = da_limit.saturating_sub(builder_tx_da_size); } - // TODO: we should account for a case when we will issue only 1 flashblock - let last_flashblock = flashblocks_per_block.saturating_sub(1); - - let mut flashblock_count = 0; // This channel coordinates flashblock building let (fb_cancel_token_rx, mut fb_cancel_token_tx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); @@ -354,11 +392,11 @@ where // execute_best_transaction without cancelling parent token ctx.cancel = cancel_token; // TODO: remove this - if flashblock_count >= flashblocks_per_block { + if ctx.flashblock_index() >= ctx.target_flashblock_count() { info!( target: "payload_builder", - target = flashblocks_per_block, - flashblock_count = flashblock_count, + target = ctx.target_flashblock_count(), + flashblock_count = ctx.flashblock_index(), block_number = ctx.block_number(), "Skipping flashblock reached target", ); @@ -368,7 +406,7 @@ where info!( target: "payload_builder", block_number = ctx.block_number(), - flashblock_count = flashblock_count, + flashblock_count = ctx.flashblock_index(), target_gas = total_gas_per_batch, gas_used = info.cumulative_gas_used, target_da = total_da_per_batch.unwrap_or(0), @@ -377,13 +415,14 @@ where ); let flashblock_build_start_time = Instant::now(); let state = StateProviderDatabase::new(&state_provider); - invoke_on_last_flashblock(flashblock_count, last_flashblock, || { + // If it is the last flashblock, we need to account for the builder tx + if ctx.is_last_flashblock() { total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size if let Some(da_limit) = total_da_per_batch.as_mut() { *da_limit = da_limit.saturating_sub(builder_tx_da_size); } - }); + } let mut db = State::builder() .with_database(state) .with_bundle_update() @@ -416,12 +455,14 @@ where // Caution: this assume that block cancel token only cancelled when new FCU is received if block_cancel.is_cancelled() { ctx.metrics.block_built_success.increment(1); - ctx.metrics.flashblock_count.record(flashblock_count as f64); + ctx.metrics + .flashblock_count + .record(ctx.flashblock_index() as f64); debug!( target: "payload_builder", message = "Payload building complete, job cancelled during execution" ); - span.record("flashblock_count", flashblock_count); + span.record("flashblock_count", ctx.flashblock_index()); return Ok(()); } @@ -434,9 +475,9 @@ where .set(payload_tx_simulation_time); // If it is the last flashblocks, add the builder txn to the block if enabled - invoke_on_last_flashblock(flashblock_count, last_flashblock, || { + if ctx.is_last_flashblock() { ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); - }); + }; let total_block_built_duration = Instant::now(); let build_result = build_block(db, &ctx, &mut info); @@ -453,12 +494,12 @@ where Err(err) => { // Track invalid/bad block ctx.metrics.invalid_blocks_count.increment(1); - error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), flashblock_count, err); + error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), ctx.flashblock_index(), err); // Return the error return Err(err); } Ok((new_payload, mut fb_payload, new_bundle_state)) => { - fb_payload.index = flashblock_count + 1; // we do this because the fallback block is index 0 + fb_payload.index = ctx.increment_flashblock_index(); // fallback block is index 0, so we need to increment here fb_payload.base = None; // We check that child_job got cancelled before sending flashblock. @@ -493,11 +534,11 @@ where error!("Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set"); } } - flashblock_count += 1; + info!( target: "payload_builder", message = "Flashblock built", - ?flashblock_count, + flashblock_count = ctx.flashblock_index(), current_gas = info.cumulative_gas_used, current_da = info.cumulative_da_bytes_used, target_flashblocks = flashblocks_per_block, @@ -508,17 +549,19 @@ where None => { // Exit loop if channel closed or cancelled ctx.metrics.block_built_success.increment(1); - ctx.metrics.flashblock_count.record(flashblock_count as f64); + ctx.metrics + .flashblock_count + .record(ctx.flashblock_index() as f64); ctx.metrics .missing_flashblocks_count - .record(flashblocks_per_block.saturating_sub(flashblock_count) as f64); + .record(flashblocks_per_block.saturating_sub(ctx.flashblock_index()) as f64); debug!( target: "payload_builder", message = "Payload building complete, channel closed or job cancelled", - missing_falshblocks = flashblocks_per_block.saturating_sub(flashblock_count), + missing_falshblocks = flashblocks_per_block.saturating_sub(ctx.flashblock_index()), reduced_flashblocks = self.config.flashblocks_per_block().saturating_sub(flashblocks_per_block), ); - span.record("flashblock_count", flashblock_count); + span.record("flashblock_count", ctx.flashblock_index()); return Ok(()); } } @@ -660,12 +703,13 @@ 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 @@ -679,14 +723,15 @@ where Ok(info) } -fn build_block( +fn build_block( mut state: State, - ctx: &OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, info: &mut ExecutionInfo, ) -> Result<(OpBuiltPayload, FlashblocksPayloadV1, BundleState), PayloadBuilderError> where DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + ExtraCtx: std::fmt::Debug + Default, { // TODO: We must run this only once per block, but we are running it on every flashblock // merge all transitions into bundle state, this would apply the withdrawal balance changes @@ -897,13 +942,3 @@ where new_bundle, )) } - -pub fn invoke_on_last_flashblock( - current_flashblock: u64, - flashblock_limit: u64, - fun: F, -) { - if current_flashblock == flashblock_limit { - fun() - } -} diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 10c234d2..2a1b8466 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -274,6 +274,7 @@ where cancel, builder_signer: self.config.builder_signer, metrics: self.metrics.clone(), + extra_ctx: Default::default(), }; let builder = OpBuilder::new(best); From 0a64d1ff4edeeb6514c6a65d1a58a09ce7516497 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Tue, 22 Jul 2025 11:46:26 -0400 Subject: [PATCH 161/262] gauge metrics to inspect flag settings (#207) --- crates/builder/op-rbuilder/src/launcher.rs | 4 +++- crates/builder/op-rbuilder/src/metrics.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index 29f5bd7c..e87982c1 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -3,7 +3,7 @@ use eyre::Result; use crate::{ args::*, builders::{BuilderConfig, BuilderMode, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, - metrics::VERSION, + metrics::{record_flag_gauge_metrics, VERSION}, monitor_tx_pool::monitor_tx_pool, primitives::reth::engine_api_builder::OpEngineApiBuilder, revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}, @@ -98,6 +98,8 @@ where 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 rollup_args = builder_args.rollup_args; let op_node = OpNode::new(rollup_args.clone()); diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index b18ba6b9..cb8aa94d 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -4,6 +4,8 @@ use reth_metrics::{ Metrics, }; +use crate::args::OpRbuilderArgs; + /// The latest version from Cargo.toml. pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -155,6 +157,16 @@ 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); + gauge!("op_rbuilder_flags_flashtestations_enabled") + .set(builder_args.flashtestations.flashtestations_enabled as i32); + gauge!("op_rbuilder_flags_enable_revert_protection") + .set(builder_args.enable_revert_protection as i32); +} + /// Contains version information for the application. #[derive(Debug, Clone)] pub struct VersionInfo { From 6ba67d562278bf605e786c86098569a260149dff Mon Sep 17 00:00:00 2001 From: kristoffer Date: Thu, 24 Jul 2025 12:13:36 +0000 Subject: [PATCH 162/262] Bump reth to 1.6 (#215) --- crates/builder/op-rbuilder/src/launcher.rs | 3 ++- crates/builder/op-rbuilder/src/tests/framework/instance.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index e87982c1..6ada7e29 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -1,4 +1,5 @@ use eyre::Result; +use reth_optimism_rpc::OpEthApiBuilder; use crate::{ args::*, @@ -108,7 +109,7 @@ where let mut addons: OpAddOns< _, - _, + OpEthApiBuilder, OpEngineValidatorBuilder, OpEngineApiBuilder, > = OpAddOnsBuilder::default() diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 598a584c..eb2c8cdb 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -37,6 +37,7 @@ use reth_optimism_node::{ node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, OpNode, }; +use reth_optimism_rpc::OpEthApiBuilder; use reth_transaction_pool::{AllTransactionsEvents, TransactionPool}; use std::sync::{Arc, LazyLock}; use tokio::sync::oneshot; @@ -99,7 +100,7 @@ impl LocalInstance { let addons: OpAddOns< _, - _, + OpEthApiBuilder, OpEngineValidatorBuilder, OpEngineApiBuilder, > = OpAddOnsBuilder::default() From 1cec430c006c5c894c5d2c0b8be92225ea00eed5 Mon Sep 17 00:00:00 2001 From: kristoffer Date: Thu, 31 Jul 2025 13:17:07 +0000 Subject: [PATCH 163/262] Less confusing naming, state is called db and db is called state (#219) * Less confusing naming, state is called db and db is called state * Use renaming destructuring --- .../op-rbuilder/src/builders/context.rs | 40 +++++++++++-------- .../src/builders/flashblocks/payload.rs | 26 ++++++------ .../src/builders/standard/payload.rs | 14 +++---- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index e4408772..3b8a0eb2 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -233,14 +233,16 @@ impl OpPayloadBuilderCtx { /// Executes all sequencer transactions that are included in the payload attributes. pub fn execute_sequencer_transactions( &self, - db: &mut State, + state: &mut State, ) -> Result, PayloadBuilderError> where DB: Database + std::fmt::Debug, { 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()); + let mut evm = self + .evm_config + .evm_with_env(&mut *state, self.evm_env.clone()); for sequencer_tx in &self.attributes().transactions { // A sequencer's block should never contain blob transactions. @@ -321,7 +323,7 @@ impl OpPayloadBuilderCtx { pub fn execute_best_transactions( &self, info: &mut ExecutionInfo, - db: &mut State, + state: &mut State, mut best_txs: impl PayloadTxsBounds, block_gas_limit: u64, block_da_limit: Option, @@ -337,7 +339,9 @@ impl OpPayloadBuilderCtx { let mut num_bundles_reverted = 0; let base_fee = self.base_fee(); let tx_da_limit = self.da_config.max_da_tx_size(); - let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + let mut evm = self + .evm_config + .evm_with_env(&mut *state, self.evm_env.clone()); info!( target: "payload_builder", @@ -543,7 +547,7 @@ impl OpPayloadBuilderCtx { pub fn add_builder_tx( &self, info: &mut ExecutionInfo, - db: &mut State, + state: &mut State, builder_tx_gas: u64, message: Vec, ) -> Option<()> @@ -556,23 +560,27 @@ impl OpPayloadBuilderCtx { let chain_id = self.chain_id(); // Create and sign the transaction let builder_tx = - signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; + signed_builder_tx(state, builder_tx_gas, message, signer, base_fee, chain_id)?; - let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); + let mut evm = self + .evm_config + .evm_with_env(&mut *state, self.evm_env.clone()); - let ResultAndState { result, state } = evm + let ResultAndState { + result, + state: new_state, + } = evm .transact(&builder_tx) .map_err(|err| PayloadBuilderError::EvmExecutionError(Box::new(err)))?; // Add gas used by the transaction to cumulative gas used, before creating the receipt - let gas_used = result.gas_used(); - info.cumulative_gas_used += gas_used; + info.cumulative_gas_used += result.gas_used(); let ctx = ReceiptBuilderCtx { tx: builder_tx.inner(), evm: &evm, result, - state: &state, + state: &new_state, cumulative_gas_used: info.cumulative_gas_used, }; info.receipts.push(self.build_receipt(ctx, None)); @@ -580,7 +588,7 @@ impl OpPayloadBuilderCtx { // Release the db reference by dropping evm drop(evm); // Commit changes - db.commit(state); + state.commit(new_state); // Append sender and transaction to the respective lists info.executed_senders.push(builder_tx.signer()); @@ -599,7 +607,7 @@ impl OpPayloadBuilderCtx { // it's possible to do this without db at all pub fn estimate_builder_tx_da_size( &self, - db: &mut State, + state: &mut State, builder_tx_gas: u64, message: Vec, ) -> Option @@ -612,7 +620,7 @@ impl OpPayloadBuilderCtx { let chain_id = self.chain_id(); // Create and sign the transaction let builder_tx = - signed_builder_tx(db, builder_tx_gas, message, signer, base_fee, chain_id)?; + signed_builder_tx(state, builder_tx_gas, message, signer, base_fee, chain_id)?; Ok(op_alloy_flz::tx_estimated_size_fjord_bytes( builder_tx.encoded_2718().as_slice(), )) @@ -648,7 +656,7 @@ pub fn estimate_gas_for_builder_tx(input: Vec) -> u64 { /// Creates signed builder tx to Address::ZERO and specified message as input pub fn signed_builder_tx( - db: &mut State, + state: &mut State, builder_tx_gas: u64, message: Vec, signer: Signer, @@ -659,7 +667,7 @@ where DB: Database, { // Create message with block number for the builder to sign - let nonce = db + let nonce = state .load_cache_account(signer.address) .map(|acc| acc.account_info().unwrap_or_default().nonce) .map_err(|_| { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 1403b761..c0c46900 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -254,12 +254,12 @@ where }; let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(&state_provider); + let db = StateProviderDatabase::new(&state_provider); // 1. execute the pre steps and seal an early block with that let sequencer_tx_start_time = Instant::now(); - let mut db = State::builder() - .with_database(state) + let mut state = State::builder() + .with_database(db) .with_bundle_update() .build(); @@ -269,20 +269,20 @@ where .builder_signer() .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); let builder_tx_da_size = ctx - .estimate_builder_tx_da_size(&mut db, builder_tx_gas, message.clone()) + .estimate_builder_tx_da_size(&mut state, builder_tx_gas, message.clone()) .unwrap_or(0); - let mut info = execute_pre_steps(&mut db, &ctx)?; + let mut info = execute_pre_steps(&mut state, &ctx)?; 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); // If we have payload with txpool we add first builder tx right after deposits if !ctx.attributes().no_tx_pool { - ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); + ctx.add_builder_tx(&mut info, &mut state, builder_tx_gas, message.clone()); } - let (payload, fb_payload, mut bundle_state) = build_block(db, &ctx, &mut info)?; + let (payload, fb_payload, mut bundle_state) = build_block(state, &ctx, &mut info)?; best_payload.set(payload.clone()); self.ws_pub @@ -414,7 +414,7 @@ where "Building flashblock", ); let flashblock_build_start_time = Instant::now(); - let state = StateProviderDatabase::new(&state_provider); + let db = StateProviderDatabase::new(&state_provider); // If it is the last flashblock, we need to account for the builder tx if ctx.is_last_flashblock() { total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); @@ -423,8 +423,8 @@ where *da_limit = da_limit.saturating_sub(builder_tx_da_size); } } - let mut db = State::builder() - .with_database(state) + let mut state = State::builder() + .with_database(db) .with_bundle_update() .with_bundle_prestate(bundle_state) .build(); @@ -446,7 +446,7 @@ where let tx_execution_start_time = Instant::now(); ctx.execute_best_transactions( &mut info, - &mut db, + &mut state, best_txs, total_gas_per_batch.min(ctx.block_gas_limit()), total_da_per_batch, @@ -476,11 +476,11 @@ where // If it is the last flashblocks, add the builder txn to the block if enabled if ctx.is_last_flashblock() { - ctx.add_builder_tx(&mut info, &mut db, builder_tx_gas, message.clone()); + ctx.add_builder_tx(&mut info, &mut state, builder_tx_gas, message.clone()); }; let total_block_built_duration = Instant::now(); - let build_result = build_block(db, &ctx, &mut info); + let build_result = build_block(state, &ctx, &mut info); let total_block_built_duration = total_block_built_duration.elapsed(); ctx.metrics .total_block_built_duration diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 2a1b8466..6fbf6415 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -280,22 +280,22 @@ where let builder = OpBuilder::new(best); let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(state_provider); + let db = StateProviderDatabase::new(state_provider); let metrics = ctx.metrics.clone(); if ctx.attributes().no_tx_pool { - let db = State::builder() - .with_database(state) + let state = State::builder() + .with_database(db) .with_bundle_update() .build(); - builder.build(db, ctx) + builder.build(state, ctx) } else { // sequencer mode we can reuse cachedreads from previous runs - let db = State::builder() - .with_database(cached_reads.as_db_mut(state)) + let state = State::builder() + .with_database(cached_reads.as_db_mut(db)) .with_bundle_update() .build(); - builder.build(db, ctx) + builder.build(state, ctx) } .map(|out| { let total_block_building_time = block_build_start_time.elapsed(); From d42edae52a1d7689937a85d154f659b03a5aee6c Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Thu, 31 Jul 2025 11:01:35 -0400 Subject: [PATCH 164/262] Add flashblock number filters to eth_sendBundle (#213) * add min and max flashblock number fields to bundle * add min and max flashblock number to FBPooledTransaction * filter based on flashblock number * wrap best txs iterator to consider flashblock number * fix map structure and wrap better * integration tests --- .../src/builders/flashblocks/best_txs.rs | 132 +++++++++++++ .../src/builders/flashblocks/mod.rs | 1 + .../src/builders/flashblocks/payload.rs | 30 +-- .../op-rbuilder/src/primitives/bundle.rs | 123 +++++++------ .../op-rbuilder/src/revert_protection.rs | 6 +- .../op-rbuilder/src/tests/flashblocks.rs | 174 +++++++++++++++++- .../op-rbuilder/src/tests/framework/txs.rs | 8 +- .../builder/op-rbuilder/src/tests/revert.rs | 10 +- crates/builder/op-rbuilder/src/tx.rs | 41 ++++- 9 files changed, 443 insertions(+), 82 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs new file mode 100644 index 00000000..1e7ad37f --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs @@ -0,0 +1,132 @@ +use std::{ + collections::{BTreeMap, VecDeque}, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; + +use alloy_primitives::Address; +use reth_payload_util::PayloadTransactions; +use reth_transaction_pool::PoolTransaction; +use tracing::debug; + +use crate::tx::MaybeFlashblockFilter; + +pub struct BestFlashblocksTxs +where + T: PoolTransaction, + I: PayloadTransactions, +{ + inner: I, + current_flashblock_number: Arc, + early_transactions: BTreeMap>, +} + +impl BestFlashblocksTxs +where + T: PoolTransaction, + I: PayloadTransactions, +{ + pub fn new(inner: I, current_flashblock_number: Arc) -> Self { + Self { + inner, + current_flashblock_number, + early_transactions: Default::default(), + } + } +} + +impl PayloadTransactions for BestFlashblocksTxs +where + T: PoolTransaction + MaybeFlashblockFilter, + I: PayloadTransactions, +{ + type Transaction = T; + + fn next(&mut self, ctx: ()) -> Option { + loop { + let flashblock_number = self.current_flashblock_number.load(Ordering::Relaxed); + + // Check for new transactions that can be executed with the higher flashblock number + while let Some((&min_flashblock, _)) = self.early_transactions.first_key_value() { + if min_flashblock > flashblock_number { + break; + } + + if let Some(mut txs) = self.early_transactions.remove(&min_flashblock) { + while let Some(tx) = txs.pop_front() { + // Re-check max flashblock number just in case + if let Some(max) = tx.flashblock_number_max() { + if flashblock_number > max { + debug!( + target: "payload_builder", + sender = ?tx.sender(), + nonce = tx.nonce(), + current_flashblock = flashblock_number, + max_flashblock = max, + "Bundle flashblock max exceeded" + ); + self.mark_invalid(tx.sender(), tx.nonce()); + continue; + } + } + + // The vecdeque isn't modified in place so we need to replace it + if !txs.is_empty() { + self.early_transactions.insert(min_flashblock, txs); + } + + return Some(tx); + } + } + } + + let tx = self.inner.next(ctx)?; + + let flashblock_number_min = tx.flashblock_number_min(); + let flashblock_number_max = tx.flashblock_number_max(); + + // Check min flashblock requirement + if let Some(min) = flashblock_number_min { + if flashblock_number < min { + self.early_transactions + .entry(min) + .or_default() + .push_back(tx); + continue; + } + } + + // Check max flashblock requirement + if let Some(max) = flashblock_number_max { + if flashblock_number > max { + debug!( + target: "payload_builder", + sender = ?tx.sender(), + nonce = tx.nonce(), + current_flashblock = flashblock_number, + max_flashblock = max, + "Bundle flashblock max exceeded" + ); + self.inner.mark_invalid(tx.sender(), tx.nonce()); + continue; + } + } + + return Some(tx); + } + } + + fn mark_invalid(&mut self, sender: Address, nonce: u64) { + self.inner.mark_invalid(sender, nonce); + + // Clear early_transactions from this sender with a greater nonce as + // these transactions now will not execute because there would be a + // nonce gap + self.early_transactions.retain(|_, txs| { + txs.retain(|tx| !(tx.sender() == sender && tx.nonce() > nonce)); + !txs.is_empty() + }); + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs index d85259ab..da20a061 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs @@ -5,6 +5,7 @@ use service::FlashblocksServiceBuilder; mod config; //mod context; +mod best_txs; mod payload; mod service; mod wspub; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index c0c46900..13bbe948 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -2,7 +2,7 @@ use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; use crate::{ builders::{ context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}, - flashblocks::config::FlashBlocksConfigExt, + flashblocks::{best_txs::BestFlashblocksTxs, config::FlashBlocksConfigExt}, generator::{BlockCell, BuildArguments}, BuilderConfig, BuilderTx, }, @@ -42,7 +42,10 @@ use rollup_boost::{ use serde::{Deserialize, Serialize}; use std::{ ops::{Div, Rem}, - sync::Arc, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, time::Instant, }; use tokio::sync::{ @@ -61,7 +64,7 @@ struct ExtraExecutionInfo { #[derive(Debug, Default)] struct FlashblocksExtraCtx { /// Current flashblock index - pub flashblock_index: u64, + pub flashblock_index: Arc, /// Target flashblock count pub target_flashblock_count: u64, } @@ -69,7 +72,7 @@ struct FlashblocksExtraCtx { impl OpPayloadBuilderCtx { /// Returns the current flashblock index pub fn flashblock_index(&self) -> u64 { - self.extra_ctx.flashblock_index + self.extra_ctx.flashblock_index.load(Ordering::Relaxed) } /// Returns the target flashblock count @@ -79,8 +82,10 @@ impl OpPayloadBuilderCtx { /// Increments the flashblock index pub fn increment_flashblock_index(&mut self) -> u64 { - self.extra_ctx.flashblock_index += 1; - self.extra_ctx.flashblock_index + self.extra_ctx + .flashblock_index + .fetch_add(1, Ordering::Relaxed); + self.flashblock_index() } /// Sets the target flashblock count @@ -248,7 +253,7 @@ where builder_signer: self.config.builder_signer, metrics: Default::default(), extra_ctx: FlashblocksExtraCtx { - flashblock_index: 0, + flashblock_index: Arc::new(AtomicU64::new(0)), target_flashblock_count: self.config.flashblocks_per_block(), }, }; @@ -430,10 +435,13 @@ where .build(); let best_txs_start_time = Instant::now(); - let best_txs = BestPayloadTransactions::new( - // We are not using without_updates in here, so arriving transaction could target the current block - self.pool - .best_transactions_with_attributes(ctx.best_transaction_attributes()), + let best_txs = BestFlashblocksTxs::new( + BestPayloadTransactions::new( + self.pool.best_transactions_with_attributes( + ctx.best_transaction_attributes(), + ), + ), + ctx.extra_ctx.flashblock_index.clone(), ); let transaction_pool_fetch_time = best_txs_start_time.elapsed(); ctx.metrics diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index f6f00506..073422a8 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; pub const MAX_BLOCK_RANGE_BLOCKS: u64 = 10; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct Bundle { #[serde(rename = "txs")] pub transactions: Vec, @@ -13,6 +13,14 @@ pub struct Bundle { #[serde(rename = "revertingTxHashes")] pub reverting_hashes: Option>, + #[serde( + default, + rename = "minBlockNumber", + with = "alloy_serde::quantity::opt", + skip_serializing_if = "Option::is_none" + )] + pub block_number_min: Option, + #[serde( default, rename = "maxBlockNumber", @@ -23,11 +31,19 @@ pub struct Bundle { #[serde( default, - rename = "minBlockNumber", + rename = "minFlashblockNumber", with = "alloy_serde::quantity::opt", skip_serializing_if = "Option::is_none" )] - pub block_number_min: Option, + pub flashblock_number_min: Option, + + #[serde( + default, + rename = "maxFlashblockNumber", + with = "alloy_serde::quantity::opt", + skip_serializing_if = "Option::is_none" + )] + pub flashblock_number_max: Option, // Not recommended because this is subject to the builder node clock #[serde( @@ -72,11 +88,17 @@ pub enum BundleConditionalError { MinTooHighForDefaultRange { min: u64, max_allowed: u64 }, } +pub struct BundleConditional { + pub transaction_conditional: TransactionConditional, + pub flashblock_number_min: Option, + pub flashblock_number_max: Option, +} + impl Bundle { pub fn conditional( &self, last_block_number: u64, - ) -> Result { + ) -> Result { let mut block_number_max = self.block_number_max; let block_number_min = self.block_number_min; @@ -122,12 +144,16 @@ impl Bundle { } } - Ok(TransactionConditional { - block_number_min, - block_number_max, - known_accounts: Default::default(), - timestamp_max: self.max_timestamp, - timestamp_min: self.min_timestamp, + Ok(BundleConditional { + transaction_conditional: TransactionConditional { + block_number_min, + block_number_max, + known_accounts: Default::default(), + timestamp_max: self.max_timestamp, + timestamp_min: self.min_timestamp, + }, + flashblock_number_min: self.flashblock_number_min, + flashblock_number_max: self.flashblock_number_max, }) } } @@ -146,15 +172,14 @@ mod tests { fn test_bundle_conditional_no_bounds() { let bundle = Bundle { transactions: vec![], - reverting_hashes: None, - block_number_max: None, - block_number_min: None, - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; - let result = bundle.conditional(last_block).unwrap(); + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; assert_eq!(result.block_number_min, None); assert_eq!( @@ -166,16 +191,16 @@ mod tests { #[test] fn test_bundle_conditional_with_valid_bounds() { let bundle = Bundle { - transactions: vec![], - reverting_hashes: None, block_number_max: Some(1005), block_number_min: Some(1002), - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; - let result = bundle.conditional(last_block).unwrap(); + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; assert_eq!(result.block_number_min, Some(1002)); assert_eq!(result.block_number_max, Some(1005)); @@ -184,12 +209,9 @@ mod tests { #[test] fn test_bundle_conditional_min_greater_than_max() { let bundle = Bundle { - transactions: vec![], - reverting_hashes: None, block_number_max: Some(1005), block_number_min: Some(1010), - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; @@ -207,12 +229,8 @@ mod tests { #[test] fn test_bundle_conditional_max_in_past() { let bundle = Bundle { - transactions: vec![], - reverting_hashes: None, block_number_max: Some(999), - block_number_min: None, - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; @@ -230,12 +248,8 @@ mod tests { #[test] fn test_bundle_conditional_max_too_high() { let bundle = Bundle { - transactions: vec![], - reverting_hashes: None, block_number_max: Some(1020), - block_number_min: None, - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; @@ -254,12 +268,8 @@ mod tests { #[test] fn test_bundle_conditional_min_too_high_for_default_range() { let bundle = Bundle { - transactions: vec![], - reverting_hashes: None, - block_number_max: None, block_number_min: Some(1015), - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; @@ -277,16 +287,15 @@ mod tests { #[test] fn test_bundle_conditional_with_only_min() { let bundle = Bundle { - transactions: vec![], - reverting_hashes: None, - block_number_max: None, block_number_min: Some(1005), - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; - let result = bundle.conditional(last_block).unwrap(); + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; assert_eq!(result.block_number_min, Some(1005)); assert_eq!(result.block_number_max, Some(1010)); // last_block + MAX_BLOCK_RANGE_BLOCKS @@ -295,16 +304,15 @@ mod tests { #[test] fn test_bundle_conditional_with_only_max() { let bundle = Bundle { - transactions: vec![], - reverting_hashes: None, block_number_max: Some(1008), - block_number_min: None, - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; - let result = bundle.conditional(last_block).unwrap(); + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; assert_eq!(result.block_number_min, None); assert_eq!(result.block_number_max, Some(1008)); @@ -313,16 +321,15 @@ mod tests { #[test] fn test_bundle_conditional_min_lower_than_last_block() { let bundle = Bundle { - transactions: vec![], - reverting_hashes: None, - block_number_max: None, block_number_min: Some(999), - min_timestamp: None, - max_timestamp: None, + ..Default::default() }; let last_block = 1000; - let result = bundle.conditional(last_block).unwrap(); + let result = bundle + .conditional(last_block) + .unwrap() + .transaction_conditional; assert_eq!(result.block_number_min, Some(999)); assert_eq!(result.block_number_max, Some(1010)); diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 4e44c6af..13b887e8 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -3,7 +3,7 @@ use std::{sync::Arc, time::Instant}; use crate::{ metrics::OpRBuilderMetrics, primitives::bundle::{Bundle, BundleResult}, - tx::{FBPooledTransaction, MaybeRevertingTransaction}, + tx::{FBPooledTransaction, MaybeFlashblockFilter, MaybeRevertingTransaction}, }; use alloy_json_rpc::RpcObject; use alloy_primitives::B256; @@ -147,7 +147,9 @@ where let pool_transaction = FBPooledTransaction::from(OpPooledTransaction::from_pooled(recovered)) .with_reverted_hashes(bundle.reverting_hashes.clone().unwrap_or_default()) - .with_conditional(conditional); + .with_flashblock_number_min(conditional.flashblock_number_min) + .with_flashblock_number_max(conditional.flashblock_number_max) + .with_conditional(conditional.transaction_conditional); let hash = self .pool diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index d45f8392..cd17f8ae 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -1,3 +1,4 @@ +use alloy_provider::Provider; use futures::StreamExt; use macros::rb_test; use parking_lot::Mutex; @@ -8,7 +9,7 @@ use tokio_util::sync::CancellationToken; use crate::{ args::{FlashblocksArgs, OpRbuilderArgs}, - tests::{LocalInstance, TransactionBuilderExt}, + tests::{BlockTransactionsExt, BundleOpts, LocalInstance, TransactionBuilderExt}, }; #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -418,3 +419,174 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<() Ok(()) } + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + }, + ..Default::default() +})] +async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); + + // Spawn WebSocket listener task + let cancellation_token_clone = cancellation_token.clone(); + let ws_handle: JoinHandle> = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + messages_clone.lock().push(text); + } + } + } + }); + + // Create two transactions and set their tips so that while ordinarily + // tx2 would come before tx1 because its tip is bigger, now tx1 comes + // first because it has a lower minimum flashblock requirement. + let tx1 = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts { + flashblock_number_min: Some(0), + ..Default::default() + }) + .with_max_priority_fee_per_gas(0) + .send() + .await?; + + let tx2 = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts { + flashblock_number_min: Some(3), + ..Default::default() + }) + .with_max_priority_fee_per_gas(10) + .send() + .await?; + + let block1 = driver.build_new_block_with_current_timestamp(None).await?; + + // Check that tx1 comes before tx2 + let tx1_hash = *tx1.tx_hash(); + let tx2_hash = *tx2.tx_hash(); + let mut tx1_pos = None; + let mut tx2_pos = None; + + for (i, item) in block1.transactions.hashes().into_iter().enumerate() { + if item == tx1_hash { + tx1_pos = Some(i); + } + if item == tx2_hash { + tx2_pos = Some(i); + } + } + + assert!(tx1_pos.is_some(), "tx {tx1_hash:?} not found"); + assert!(tx2_pos.is_some(), "tx {tx2_hash:?} not found"); + assert!( + tx1_pos.unwrap() < tx2_pos.unwrap(), + "tx {tx1_hash:?} does not come before {tx2_hash:?}" + ); + + cancellation_token.cancel(); + assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + }, + ..Default::default() +})] +async fn test_flashblock_max_filtering(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // Create a struct to hold received messages + let received_messages = Arc::new(Mutex::new(Vec::new())); + let messages_clone = received_messages.clone(); + let cancellation_token = CancellationToken::new(); + let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); + + // Spawn WebSocket listener task + let cancellation_token_clone = cancellation_token.clone(); + let ws_handle: JoinHandle> = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + messages_clone.lock().push(text); + } + } + } + }); + + // Since we cannot directly trigger flashblock creation in tests, we + // instead fill up the gas of flashblocks so that our tx with the + // flashblock_number_max parameter set is properly delayed, simulating + // the scenario where we'd sent the tx after the flashblock max number + // had passed. + let call = driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 3)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + let _fit_tx_1 = driver + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + + let tx1 = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts { + flashblock_number_max: Some(1), + ..Default::default() + }) + .send() + .await?; + + let block = driver.build_new_block_with_current_timestamp(None).await?; + assert!(!block.includes(tx1.tx_hash())); + + cancellation_token.cancel(); + assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index 84135c8a..7b49acde 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -25,8 +25,10 @@ use super::FUNDED_PRIVATE_KEYS; #[derive(Clone, Copy, Default)] pub struct BundleOpts { - pub block_number_max: Option, pub block_number_min: Option, + pub block_number_max: Option, + pub flashblock_number_min: Option, + pub flashblock_number_max: Option, pub min_timestamp: Option, pub max_timestamp: Option, } @@ -194,8 +196,10 @@ impl TransactionBuilder { } else { None }, - block_number_max: bundle_opts.block_number_max, block_number_min: bundle_opts.block_number_min, + block_number_max: bundle_opts.block_number_max, + flashblock_number_min: bundle_opts.block_number_min, + flashblock_number_max: bundle_opts.block_number_max, min_timestamp: bundle_opts.min_timestamp, max_timestamp: bundle_opts.max_timestamp, }; diff --git a/crates/builder/op-rbuilder/src/tests/revert.rs b/crates/builder/op-rbuilder/src/tests/revert.rs index cbc315f9..befce2c3 100644 --- a/crates/builder/op-rbuilder/src/tests/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/revert.rs @@ -182,10 +182,8 @@ async fn bundle_min_block_number(rbuilder: LocalInstance) -> eyre::Result<()> { .with_revert() // the transaction reverts but it is included in the block .with_reverted_hash() .with_bundle(BundleOpts { - block_number_max: None, block_number_min: Some(2), - min_timestamp: None, - max_timestamp: None, + ..Default::default() }) .send() .await?; @@ -204,8 +202,7 @@ async fn bundle_min_block_number(rbuilder: LocalInstance) -> eyre::Result<()> { .with_bundle(BundleOpts { block_number_max: Some(4), block_number_min: Some(4), - min_timestamp: None, - max_timestamp: None, + ..Default::default() }) .send() .await?; @@ -270,8 +267,7 @@ async fn bundle_range_limits(rbuilder: LocalInstance) -> eyre::Result<()> { .with_bundle(BundleOpts { block_number_max, block_number_min, - min_timestamp: None, - max_timestamp: None, + ..Default::default() }) .send() .await diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index 25be881d..10c914fc 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -13,7 +13,10 @@ use reth_primitives::{kzg::KzgSettings, Recovered}; use reth_primitives_traits::InMemorySize; use reth_transaction_pool::{EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction}; -pub trait FBPoolTransaction: MaybeRevertingTransaction + OpPooledTx {} +pub trait FBPoolTransaction: + MaybeRevertingTransaction + OpPooledTx + MaybeFlashblockFilter +{ +} #[derive(Clone, Debug)] pub struct FBPooledTransaction { @@ -23,6 +26,9 @@ pub struct FBPooledTransaction { /// this is the list of hashes of the transactions that reverted. If the /// transaction is not a bundle, this is `None`. pub reverted_hashes: Option>, + + pub flashblock_number_min: Option, + pub flashblock_number_max: Option, } impl FBPoolTransaction for FBPooledTransaction {} @@ -49,6 +55,33 @@ impl MaybeRevertingTransaction for FBPooledTransaction { } } +pub trait MaybeFlashblockFilter { + fn with_flashblock_number_min(self, flashblock_number_min: Option) -> Self; + fn with_flashblock_number_max(self, flashblock_number_max: Option) -> Self; + fn flashblock_number_min(&self) -> Option; + fn flashblock_number_max(&self) -> Option; +} + +impl MaybeFlashblockFilter for FBPooledTransaction { + fn with_flashblock_number_min(mut self, flashblock_number_min: Option) -> Self { + self.flashblock_number_min = flashblock_number_min; + self + } + + fn with_flashblock_number_max(mut self, flashblock_number_max: Option) -> Self { + self.flashblock_number_max = flashblock_number_max; + self + } + + fn flashblock_number_min(&self) -> Option { + self.flashblock_number_min + } + + fn flashblock_number_max(&self) -> Option { + self.flashblock_number_max + } +} + impl InMemorySize for FBPooledTransaction { fn size(&self) -> usize { self.inner.size() + core::mem::size_of::() @@ -74,6 +107,8 @@ impl PoolTransaction for FBPooledTransaction { Self { inner, reverted_hashes: None, + flashblock_number_min: None, + flashblock_number_max: None, } } @@ -232,6 +267,8 @@ impl From for FBPooledTransaction { Self { inner: tx, reverted_hashes: None, + flashblock_number_min: None, + flashblock_number_max: None, } } } @@ -256,6 +293,8 @@ impl MaybeConditionalTransaction for FBPooledTransaction { FBPooledTransaction { inner: self.inner.with_conditional(conditional), reverted_hashes: self.reverted_hashes, + flashblock_number_min: None, + flashblock_number_max: None, } } } From 587a73de48fdb488578fe4722cec1d3bf497eb06 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 4 Aug 2025 14:46:18 +0500 Subject: [PATCH 165/262] Fix bundle state and produce executed block (#223) * WIP right now it has consensus error 2025-08-01T16:10:44.956046Z ERROR engine::persistence: Persistence service failed err=ProviderError(Database(Write(DatabaseWriteError { info: DatabaseErrorInfo { message: "the given key value is mismatched to the current cursor position", code: -30418 }, operation: CursorAppendDup, table_name: "AccountChangeSets", key: [0, 0, 0, 0, 0, 0, 0, 9] }))) * Hacky solution to mergin state * fmt * fmt * remove config.toml * Update crates/op-rbuilder/src/builders/flashblocks/payload.rs --------- Co-authored-by: shana --- .../src/builders/flashblocks/payload.rs | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 13bbe948..24107313 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -18,6 +18,7 @@ use alloy_primitives::{map::foldhash::HashMap, Address, B256, U256}; use core::time::Duration; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::BuildOutcome; +use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; use reth_evm::{execute::BlockBuilder, ConfigureEvm}; use reth_node_api::{Block, NodePrimitives, PayloadBuilderError}; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; @@ -26,14 +27,13 @@ use reth_optimism_forks::OpHardforks; use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; use reth_payload_util::BestPayloadTransactions; +use reth_primitives_traits::RecoveredBlock; use reth_provider::{ ExecutionOutcome, HashedPostStateProvider, ProviderError, StateRootProvider, StorageRootProvider, }; use reth_revm::{ - database::StateProviderDatabase, - db::{states::bundle_state::BundleRetention, BundleState}, - State, + database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State, }; use revm::Database; use rollup_boost::{ @@ -287,7 +287,7 @@ where ctx.add_builder_tx(&mut info, &mut state, builder_tx_gas, message.clone()); } - let (payload, fb_payload, mut bundle_state) = build_block(state, &ctx, &mut info)?; + let (payload, fb_payload) = build_block(&mut state, &ctx, &mut info)?; best_payload.set(payload.clone()); self.ws_pub @@ -419,7 +419,6 @@ where "Building flashblock", ); let flashblock_build_start_time = Instant::now(); - let db = StateProviderDatabase::new(&state_provider); // If it is the last flashblock, we need to account for the builder tx if ctx.is_last_flashblock() { total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); @@ -428,11 +427,6 @@ where *da_limit = da_limit.saturating_sub(builder_tx_da_size); } } - let mut state = State::builder() - .with_database(db) - .with_bundle_update() - .with_bundle_prestate(bundle_state) - .build(); let best_txs_start_time = Instant::now(); let best_txs = BestFlashblocksTxs::new( @@ -488,7 +482,7 @@ where }; let total_block_built_duration = Instant::now(); - let build_result = build_block(state, &ctx, &mut info); + let build_result = build_block(&mut state, &ctx, &mut info); let total_block_built_duration = total_block_built_duration.elapsed(); ctx.metrics .total_block_built_duration @@ -506,7 +500,7 @@ where // Return the error return Err(err); } - Ok((new_payload, mut fb_payload, new_bundle_state)) => { + Ok((new_payload, mut fb_payload)) => { fb_payload.index = ctx.increment_flashblock_index(); // fallback block is index 0, so we need to increment here fb_payload.base = None; @@ -516,6 +510,21 @@ where tokio::runtime::Handle::current() .block_on(async { ctx.cancel.cancelled().await }); }); + + // If main token got canceled in here that means we received get_payload and we should drop everything and now update best_payload + // To ensure that we will return same blocks as rollup-boost (to leverage caches) + if block_cancel.is_cancelled() { + ctx.metrics.block_built_success.increment(1); + ctx.metrics + .flashblock_count + .record(ctx.flashblock_index() as f64); + debug!( + target: "payload_builder", + message = "Payload building complete, job cancelled during execution" + ); + span.record("flashblock_count", ctx.flashblock_index()); + return Ok(()); + } self.ws_pub .publish(&fb_payload) .map_err(PayloadBuilderError::other)?; @@ -533,7 +542,6 @@ where best_payload.set(new_payload.clone()); // Update bundle_state for next iteration - bundle_state = new_bundle_state; total_gas_per_batch += gas_per_batch; if let Some(da_limit) = da_per_batch { if let Some(da) = total_da_per_batch.as_mut() { @@ -732,18 +740,17 @@ where } fn build_block( - mut state: State, + state: &mut State, ctx: &OpPayloadBuilderCtx, info: &mut ExecutionInfo, -) -> Result<(OpBuiltPayload, FlashblocksPayloadV1, BundleState), PayloadBuilderError> +) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError> where DB: Database + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, ExtraCtx: std::fmt::Debug + Default, { - // TODO: We must run this only once per block, but we are running it on every flashblock - // merge all transitions into bundle state, this would apply the withdrawal balance changes - // and 4788 contract call + // 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(); let state_merge_start_time = Instant::now(); state.merge_transitions(BundleRetention::Reverts); let state_transition_merge_time = state_merge_start_time.elapsed(); @@ -754,13 +761,11 @@ where .state_transition_merge_gauge .set(state_transition_merge_time); - let new_bundle = state.take_bundle(); - let block_number = ctx.block_number(); assert_eq!(block_number, ctx.parent().number + 1); let execution_outcome = ExecutionOutcome::new( - new_bundle.clone(), + state.bundle_state.clone(), vec![info.receipts.clone()], block_number, vec![], @@ -779,11 +784,12 @@ where .block_logs_bloom(block_number) .expect("Number is in range"); + // TODO: maybe recreate state with bundle in here // // calculate the state root let state_root_start_time = Instant::now(); let state_provider = state.database.as_ref(); let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); - let (state_root, _trie_output) = { + let (state_root, trie_output) = { state .database .as_ref() @@ -870,6 +876,19 @@ where }, ); + let recovered_block = + RecoveredBlock::new_unhashed(block.clone(), info.executed_senders.clone()); + // create the executed block data + let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { + block: ExecutedBlock { + recovered_block: Arc::new(recovered_block), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + }, + trie: ExecutedTrieUpdates::Present(Arc::new(trie_output)), + }; + info!(target: "payload_builder", message = "Executed block created"); + let sealed_block = Arc::new(block.seal_slow()); debug!(target: "payload_builder", ?sealed_block, "sealed built block"); @@ -891,7 +910,8 @@ where .zip(new_receipts.iter()) .map(|(tx, receipt)| (tx.tx_hash(), receipt.clone())) .collect::>(); - let new_account_balances = new_bundle + let new_account_balances = state + .bundle_state .state .iter() .filter_map(|(address, account)| account.info.as_ref().map(|info| (*address, info.balance))) @@ -935,18 +955,17 @@ where metadata: serde_json::to_value(&metadata).unwrap_or_default(), }; + // We clean bundle and place initial state transaction back + state.take_bundle(); + state.transition_state = untouched_transition_state; + Ok(( OpBuiltPayload::new( ctx.payload_id(), sealed_block, info.total_fees, - // This must be set to NONE for now because we are doing merge transitions on every flashblock - // when it should only happen once per block, thus, it returns a confusing state back to op-reth. - // We can live without this for now because Op syncs up the executed block using new_payload - // calls, but eventually we would want to return the executed block here. - None, + Some(executed), ), fb_payload, - new_bundle, )) } From c1a38f8db00670e6bd335e78661af766f957c420 Mon Sep 17 00:00:00 2001 From: shana Date: Mon, 4 Aug 2025 03:01:09 -0700 Subject: [PATCH 166/262] Add caching to generator (#221) * WIP right now it has consensus error 2025-08-01T16:10:44.956046Z ERROR engine::persistence: Persistence service failed err=ProviderError(Database(Write(DatabaseWriteError { info: DatabaseErrorInfo { message: "the given key value is mismatched to the current cursor position", code: -30418 }, operation: CursorAppendDup, table_name: "AccountChangeSets", key: [0, 0, 0, 0, 0, 0, 0, 9] }))) * fmt * cache reth db reads in flashblocks payload generation * save tip to cache on new committed state --------- Co-authored-by: Solar Mithril Co-authored-by: Ash Kunda <18058966+akundaz@users.noreply.github.com> --- .../src/builders/flashblocks/payload.rs | 4 +- .../op-rbuilder/src/builders/generator.rs | 55 +++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 24107313..38a88178 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -193,9 +193,9 @@ where ) -> Result<(), PayloadBuilderError> { let block_build_start_time = Instant::now(); let BuildArguments { + mut cached_reads, config, cancel: block_cancel, - .. } = args; // We log only every 100th block to reduce usage @@ -264,7 +264,7 @@ where // 1. execute the pre steps and seal an early block with that let sequencer_tx_start_time = Instant::now(); let mut state = State::builder() - .with_database(db) + .with_database(cached_reads.as_db_mut(db)) .with_bundle_update() .build(); diff --git a/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs index 2132d105..8b62d2c5 100644 --- a/crates/builder/op-rbuilder/src/builders/generator.rs +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -1,15 +1,19 @@ +use alloy_primitives::B256; use futures_util::{Future, FutureExt}; use reth::{ providers::{BlockReaderIdExt, StateProviderFactory}, tasks::TaskSpawner, }; -use reth_basic_payload_builder::{BasicPayloadJobGeneratorConfig, HeaderForPayload, PayloadConfig}; -use reth_node_api::{PayloadBuilderAttributes, PayloadKind}; +use reth_basic_payload_builder::{ + BasicPayloadJobGeneratorConfig, HeaderForPayload, PayloadConfig, PrecachedState, +}; +use reth_node_api::{NodePrimitives, PayloadBuilderAttributes, PayloadKind}; use reth_payload_builder::{ KeepPayloadJobAlive, PayloadBuilderError, PayloadJob, PayloadJobGenerator, }; use reth_payload_primitives::BuiltPayload; use reth_primitives_traits::HeaderTy; +use reth_provider::CanonStateNotification; use reth_revm::cached::CachedReads; use std::{ sync::{Arc, Mutex}, @@ -74,6 +78,8 @@ pub struct BlockPayloadJobGenerator { last_payload: Arc>, /// The extra block deadline in seconds extra_block_deadline: std::time::Duration, + /// Stored `cached_reads` for new payload jobs. + pre_cached: Option, } // === impl EmptyBlockPayloadJobGenerator === @@ -97,8 +103,18 @@ impl BlockPayloadJobGenerator { ensure_only_one_payload, last_payload: Arc::new(Mutex::new(CancellationToken::new())), extra_block_deadline, + pre_cached: None, } } + + /// Returns the pre-cached reads for the given parent header if it matches the cached state's + /// block. + fn maybe_pre_cached(&self, parent: B256) -> Option { + self.pre_cached + .as_ref() + .filter(|pc| pc.block == parent) + .map(|pc| pc.cached.clone()) + } } impl PayloadJobGenerator @@ -182,12 +198,38 @@ where cancel: cancel_token, deadline, build_complete: None, + cached_reads: self.maybe_pre_cached(parent_header.hash()), }; job.spawn_build_job(); Ok(job) } + + fn on_new_state(&mut self, new_state: CanonStateNotification) { + let mut cached = CachedReads::default(); + + // extract the state from the notification and put it into the cache + let committed = new_state.committed(); + let new_execution_outcome = committed.execution_outcome(); + for (addr, acc) in new_execution_outcome.bundle_accounts_iter() { + if let Some(info) = acc.info.clone() { + // we want pre cache existing accounts and their storage + // this only includes changed accounts and storage but is better than nothing + let storage = acc + .storage + .iter() + .map(|(key, slot)| (*key, slot.present_value)) + .collect(); + cached.insert_account(addr, info, storage); + } + } + + self.pre_cached = Some(PrecachedState { + block: committed.tip().hash(), + cached, + }); + } } use std::{ @@ -214,6 +256,11 @@ where pub(crate) cancel: CancellationToken, pub(crate) deadline: Pin>, // Add deadline pub(crate) build_complete: Option>>, + /// Caches all disk reads for the state the new payloads builds on + /// + /// This is used to avoid reading the same state over and over again when new attempts are + /// triggered, because during the building process we'll repeatedly execute the transactions. + pub(crate) cached_reads: Option, } impl PayloadJob for BlockPayloadJob @@ -274,10 +321,10 @@ where let (tx, rx) = oneshot::channel(); self.build_complete = Some(rx); - + let cached_reads = self.cached_reads.take().unwrap_or_default(); self.executor.spawn_blocking(Box::pin(async move { let args = BuildArguments { - cached_reads: Default::default(), + cached_reads, config: payload_config, cancel, }; From 7eb701e0f427e3d85440a5caa42497dedb8f74fc Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Thu, 7 Aug 2025 02:34:39 -0400 Subject: [PATCH 167/262] fix: record missing flashblocks (#225) * record missing flashblocks in more places * code cleanup * edit log message --- .../src/builders/flashblocks/payload.rs | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 38a88178..4af16050 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -456,15 +456,12 @@ where // We got block cancelled, we won't need anything from the block at this point // Caution: this assume that block cancel token only cancelled when new FCU is received if block_cancel.is_cancelled() { - ctx.metrics.block_built_success.increment(1); - ctx.metrics - .flashblock_count - .record(ctx.flashblock_index() as f64); - debug!( - target: "payload_builder", - message = "Payload building complete, job cancelled during execution" + self.record_flashblocks_metrics( + &ctx, + flashblocks_per_block, + &span, + "Payload building complete, channel closed or job cancelled", ); - span.record("flashblock_count", ctx.flashblock_index()); return Ok(()); } @@ -514,15 +511,12 @@ where // If main token got canceled in here that means we received get_payload and we should drop everything and now update best_payload // To ensure that we will return same blocks as rollup-boost (to leverage caches) if block_cancel.is_cancelled() { - ctx.metrics.block_built_success.increment(1); - ctx.metrics - .flashblock_count - .record(ctx.flashblock_index() as f64); - debug!( - target: "payload_builder", - message = "Payload building complete, job cancelled during execution" + self.record_flashblocks_metrics( + &ctx, + flashblocks_per_block, + &span, + "Payload building complete, channel closed or job cancelled", ); - span.record("flashblock_count", ctx.flashblock_index()); return Ok(()); } self.ws_pub @@ -563,27 +557,45 @@ where } } None => { - // Exit loop if channel closed or cancelled - ctx.metrics.block_built_success.increment(1); - ctx.metrics - .flashblock_count - .record(ctx.flashblock_index() as f64); - ctx.metrics - .missing_flashblocks_count - .record(flashblocks_per_block.saturating_sub(ctx.flashblock_index()) as f64); - debug!( - target: "payload_builder", - message = "Payload building complete, channel closed or job cancelled", - missing_falshblocks = flashblocks_per_block.saturating_sub(ctx.flashblock_index()), - reduced_flashblocks = self.config.flashblocks_per_block().saturating_sub(flashblocks_per_block), + self.record_flashblocks_metrics( + &ctx, + flashblocks_per_block, + &span, + "Payload building complete, channel closed or job cancelled", ); - span.record("flashblock_count", ctx.flashblock_index()); return Ok(()); } } } } + /// Do some logging and metric recording when we stop build flashblocks + fn record_flashblocks_metrics( + &self, + ctx: &OpPayloadBuilderCtx, + flashblocks_per_block: u64, + span: &tracing::Span, + message: &str, + ) { + ctx.metrics.block_built_success.increment(1); + ctx.metrics + .flashblock_count + .record(ctx.flashblock_index() as f64); + ctx.metrics + .missing_flashblocks_count + .record(flashblocks_per_block.saturating_sub(ctx.flashblock_index()) as f64); + + debug!( + target: "payload_builder", + message = message, + flashblocks_per_block = flashblocks_per_block, + flashblock_index = ctx.flashblock_index(), + config_flashblocks_per_block = self.config.flashblocks_per_block(), + ); + + span.record("flashblock_count", ctx.flashblock_index()); + } + /// Spawn task that will send new flashblock level cancel token in steady intervals (first interval /// may vary if --flashblocks.dynamic enabled) pub fn spawn_timer_task( From 6ba6cdb375e7e724245fa741fb692cd69e6ddd8b Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Thu, 7 Aug 2025 06:14:36 -0400 Subject: [PATCH 168/262] fix: record num txs built with flashblocks enabled (#227) * record num txs built correctly with flashblocks enabled * Fix metrics --------- Co-authored-by: Solar Mithril --- .../src/builders/flashblocks/payload.rs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 4af16050..e4bbb5ca 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -300,13 +300,6 @@ where payload_id = fb_payload.payload_id.to_string(), ); - 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); - if ctx.attributes().no_tx_pool { info!( target: "payload_builder", @@ -320,6 +313,12 @@ where ctx.metrics .total_block_built_gauge .set(total_block_building_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); // return early since we don't need to build a block with transactions from the pool return Ok(()); @@ -458,6 +457,7 @@ where if block_cancel.is_cancelled() { self.record_flashblocks_metrics( &ctx, + &info, flashblocks_per_block, &span, "Payload building complete, channel closed or job cancelled", @@ -513,6 +513,7 @@ where if block_cancel.is_cancelled() { self.record_flashblocks_metrics( &ctx, + &info, flashblocks_per_block, &span, "Payload building complete, channel closed or job cancelled", @@ -559,6 +560,7 @@ where None => { self.record_flashblocks_metrics( &ctx, + &info, flashblocks_per_block, &span, "Payload building complete, channel closed or job cancelled", @@ -573,6 +575,7 @@ where fn record_flashblocks_metrics( &self, ctx: &OpPayloadBuilderCtx, + info: &ExecutionInfo, flashblocks_per_block: u64, span: &tracing::Span, message: &str, @@ -584,6 +587,12 @@ where ctx.metrics .missing_flashblocks_count .record(flashblocks_per_block.saturating_sub(ctx.flashblock_index()) as f64); + 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); debug!( target: "payload_builder", From 344f68a76028c5176cc687681de914c0efe1e5a2 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Tue, 12 Aug 2025 06:10:28 -0400 Subject: [PATCH 169/262] combine eth api modifications (#231) --- crates/builder/op-rbuilder/src/launcher.rs | 15 +-- .../op-rbuilder/src/revert_protection.rs | 96 +++++++------------ .../src/tests/framework/instance.rs | 16 ++-- 3 files changed, 49 insertions(+), 78 deletions(-) diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index 6ada7e29..7379c91d 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -7,7 +7,7 @@ use crate::{ metrics::{record_flag_gauge_metrics, VERSION}, monitor_tx_pool::monitor_tx_pool, primitives::reth::engine_api_builder::OpEngineApiBuilder, - revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}, + revert_protection::{EthApiExtServer, RevertProtectionExt}, tx::FBPooledTransaction, }; use core::fmt::Debug; @@ -149,14 +149,15 @@ where let pool = ctx.pool().clone(); let provider = ctx.provider().clone(); - let revert_protection_ext = - RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); + let revert_protection_ext = RevertProtectionExt::new( + pool, + provider, + ctx.registry.eth_api().clone(), + reverted_cache, + ); ctx.modules - .merge_configured(revert_protection_ext.bundle_api().into_rpc())?; - ctx.modules.replace_configured( - revert_protection_ext.eth_api(reverted_cache).into_rpc(), - )?; + .add_or_replace_configured(revert_protection_ext.into_rpc())?; } Ok(()) diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 13b887e8..0d497017 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -19,21 +19,13 @@ use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool}; use tracing::error; -// We have to split the RPC modules in two sets because we have methods that both -// replace an existing method and add a new one. -// Tracking change in Reth here to have a single method for both: -// https://github.com/paradigmxyz/reth/issues/16502 - // Namespace overrides for revert protection support #[cfg_attr(not(test), rpc(server, namespace = "eth"))] #[cfg_attr(test, rpc(server, client, namespace = "eth"))] -pub trait EthApiExt { +pub trait EthApiExt { #[method(name = "sendBundle")] async fn send_bundle(&self, tx: Bundle) -> RpcResult; -} -#[rpc(server, client, namespace = "eth")] -pub trait EthApiOverride { #[method(name = "getTransactionReceipt")] async fn transaction_receipt(&self, hash: B256) -> RpcResult>; } @@ -43,6 +35,7 @@ pub struct RevertProtectionExt { provider: Provider, eth_api: Eth, metrics: Arc, + reverted_cache: Cache, } impl RevertProtectionExt @@ -51,42 +44,29 @@ where Provider: Clone, Eth: Clone, { - pub fn new(pool: Pool, provider: Provider, eth_api: Eth) -> Self { + pub fn new( + pool: Pool, + provider: Provider, + eth_api: Eth, + reverted_cache: Cache, + ) -> Self { Self { pool, provider, eth_api, metrics: Arc::new(OpRBuilderMetrics::default()), - } - } - - pub fn bundle_api(&self) -> RevertProtectionBundleAPI { - RevertProtectionBundleAPI { - pool: self.pool.clone(), - provider: self.provider.clone(), - metrics: self.metrics.clone(), - } - } - - pub fn eth_api(&self, reverted_cache: Cache) -> RevertProtectionEthAPI { - RevertProtectionEthAPI { - eth_api: self.eth_api.clone(), reverted_cache, } } } -pub struct RevertProtectionBundleAPI { - pool: Pool, - provider: Provider, - metrics: Arc, -} - #[async_trait] -impl EthApiExtServer for RevertProtectionBundleAPI +impl EthApiExtServer> + for RevertProtectionExt where Pool: TransactionPool + Clone + 'static, Provider: StateProviderFactory + Send + Sync + Clone + 'static, + Eth: FullEthApi + Send + Sync + Clone + 'static, { async fn send_bundle(&self, bundle: Bundle) -> RpcResult { let request_start_time = Instant::now(); @@ -109,12 +89,33 @@ where bundle_result } + + async fn transaction_receipt( + &self, + hash: B256, + ) -> RpcResult>> { + match self.eth_api.transaction_receipt(hash).await.unwrap() { + Some(receipt) => Ok(Some(receipt)), + None => { + // Try to find the transaction in the reverted cache + if self.reverted_cache.get(&hash).await.is_some() { + return Err(EthApiError::InvalidParams( + "the transaction was dropped from the pool".into(), + ) + .into()); + } else { + return Ok(None); + } + } + } + } } -impl RevertProtectionBundleAPI +impl RevertProtectionExt where Pool: TransactionPool + Clone + 'static, Provider: StateProviderFactory + Send + Sync + Clone + 'static, + Eth: FullEthApi + Send + Sync + Clone + 'static, { async fn send_bundle_inner(&self, bundle: Bundle) -> RpcResult { let last_block_number = self @@ -161,34 +162,3 @@ where Ok(result) } } - -pub struct RevertProtectionEthAPI { - eth_api: Eth, - reverted_cache: Cache, -} - -#[async_trait] -impl EthApiOverrideServer> for RevertProtectionEthAPI -where - Eth: FullEthApi + Send + Sync + Clone + 'static, -{ - async fn transaction_receipt( - &self, - hash: B256, - ) -> RpcResult>> { - match self.eth_api.transaction_receipt(hash).await.unwrap() { - Some(receipt) => Ok(Some(receipt)), - None => { - // Try to find the transaction in the reverted cache - if self.reverted_cache.get(&hash).await.is_some() { - return Err(EthApiError::InvalidParams( - "the transaction was dropped from the pool".into(), - ) - .into()); - } else { - return Ok(None); - } - } - } - } -} diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index eb2c8cdb..2b2b486f 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -2,7 +2,7 @@ use crate::{ args::OpRbuilderArgs, builders::{BuilderConfig, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, primitives::reth::engine_api_builder::OpEngineApiBuilder, - revert_protection::{EthApiExtServer, EthApiOverrideServer, RevertProtectionExt}, + revert_protection::{EthApiExtServer, RevertProtectionExt}, tests::{ create_test_db, framework::{driver::ChainDriver, BUILDER_PRIVATE_KEY}, @@ -126,15 +126,15 @@ impl LocalInstance { let pool = ctx.pool().clone(); let provider = ctx.provider().clone(); - let revert_protection_ext = - RevertProtectionExt::new(pool, provider, ctx.registry.eth_api().clone()); + let revert_protection_ext = RevertProtectionExt::new( + pool, + provider, + ctx.registry.eth_api().clone(), + reverted_cache, + ); ctx.modules - .merge_configured(revert_protection_ext.bundle_api().into_rpc())?; - - ctx.modules.replace_configured( - revert_protection_ext.eth_api(reverted_cache).into_rpc(), - )?; + .add_or_replace_configured(revert_protection_ext.into_rpc())?; } Ok(()) From 9fcf51b209fe8356d13eabce444a50439b1e358d Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 13 Aug 2025 19:08:15 +0500 Subject: [PATCH 170/262] Add correct metric value (#234) --- .../op-rbuilder/src/builders/flashblocks/payload.rs | 11 ++++++++--- .../op-rbuilder/src/builders/flashblocks/wspub.rs | 6 +++--- crates/builder/op-rbuilder/src/metrics.rs | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index e4bbb5ca..ff5c75f9 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -290,9 +290,13 @@ where let (payload, fb_payload) = build_block(&mut state, &ctx, &mut info)?; best_payload.set(payload.clone()); - self.ws_pub + let flashblock_byte_size = self + .ws_pub .publish(&fb_payload) .map_err(PayloadBuilderError::other)?; + ctx.metrics + .flashblock_byte_size_histogram + .record(flashblock_byte_size as f64); info!( target: "payload_builder", @@ -520,7 +524,8 @@ where ); return Ok(()); } - self.ws_pub + let flashblock_byte_size = self + .ws_pub .publish(&fb_payload) .map_err(PayloadBuilderError::other)?; @@ -530,7 +535,7 @@ where .record(flashblock_build_start_time.elapsed()); ctx.metrics .flashblock_byte_size_histogram - .record(new_payload.block().size() as f64); + .record(flashblock_byte_size as f64); ctx.metrics .flashblock_num_tx_histogram .record(info.executed_transactions.len() as f64); diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index 3329478b..aa34ecd7 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -62,7 +62,7 @@ impl WebSocketPublisher { }) } - pub fn publish(&self, payload: &FlashblocksPayloadV1) -> io::Result<()> { + pub fn publish(&self, payload: &FlashblocksPayloadV1) -> io::Result { // Serialize the payload to a UTF-8 string // serialize only once, then just copy around only a pointer // to the serialized data for each subscription. @@ -76,12 +76,12 @@ impl WebSocketPublisher { let serialized = serde_json::to_string(payload)?; let utf8_bytes = Utf8Bytes::from(serialized); - + let size = utf8_bytes.len(); // Send the serialized payload to all subscribers self.pipe .send(utf8_bytes) .map_err(|e| io::Error::new(io::ErrorKind::ConnectionAborted, e))?; - Ok(()) + Ok(size) } } diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index cb8aa94d..cd0da11f 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -49,7 +49,7 @@ pub struct OpRBuilderMetrics { pub total_block_built_gauge: Gauge, /// Histogram of the time taken to build a Flashblock pub flashblock_build_duration: Histogram, - /// Flashblock byte size histogram + /// Flashblock UTF8 payload byte size histogram pub flashblock_byte_size_histogram: Histogram, /// Histogram of transactions in a Flashblock pub flashblock_num_tx_histogram: Histogram, From 387bdcc256c58c74ffe58d595cde7bfecf6d62d7 Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Wed, 13 Aug 2025 21:29:50 +0530 Subject: [PATCH 171/262] feat: add transaction gas limit (#214) * feat: add transaction gas limit * fix: update default * fix: update best txns * chore: add test for max txn gas * fix: apply suggestions * fix: tests * chore: update tests * fix: failing tests --- crates/builder/op-rbuilder/src/args/op.rs | 4 + .../op-rbuilder/src/builders/context.rs | 10 ++ .../src/builders/flashblocks/payload.rs | 1 + .../builder/op-rbuilder/src/builders/mod.rs | 4 + .../src/builders/standard/payload.rs | 1 + .../src/primitives/reth/execution.rs | 1 + .../op-rbuilder/src/tests/framework/utils.rs | 7 ++ crates/builder/op-rbuilder/src/tests/smoke.rs | 109 +++++++++++++++++- 8 files changed, 136 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index cd0d968d..f25fcd30 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -30,6 +30,10 @@ pub struct OpRbuilderArgs { )] pub chain_block_time: u64, + /// max gas a transaction can use + #[arg(long = "builder.max_gas_per_txn")] + pub max_gas_per_txn: Option, + /// Signals whether to log pool transaction events #[arg(long = "builder.log-pool-transactions", default_value = "false")] pub log_pool_transactions: bool, diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 3b8a0eb2..5725f2e4 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -70,6 +70,8 @@ pub struct OpPayloadBuilderCtx { pub metrics: Arc, /// Extra context for the payload builder pub extra_ctx: ExtraCtx, + /// Max gas that can be used by a transaction. + pub max_gas_per_txn: Option, } impl OpPayloadBuilderCtx { @@ -495,6 +497,14 @@ impl OpPayloadBuilderCtx { // add gas used by the transaction to cumulative gas used, before creating the // receipt let gas_used = result.gas_used(); + if let Some(max_gas_per_txn) = self.max_gas_per_txn { + if gas_used > max_gas_per_txn { + log_txn(TxnExecutionResult::MaxGasUsageExceeded); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + } + info.cumulative_gas_used += gas_used; // record tx da size info.cumulative_da_bytes_used += tx_da_size; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index ff5c75f9..edb76321 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -256,6 +256,7 @@ where flashblock_index: Arc::new(AtomicU64::new(0)), target_flashblock_count: self.config.flashblocks_per_block(), }, + max_gas_per_txn: self.config.max_gas_per_txn, }; let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 1c41f370..92dc8394 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -112,6 +112,8 @@ pub struct BuilderConfig { /// 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, } impl core::fmt::Debug for BuilderConfig { @@ -145,6 +147,7 @@ impl Default for BuilderConfig { da_config: OpDAConfig::default(), specific: S::default(), sampling_ratio: 100, + max_gas_per_txn: None, } } } @@ -164,6 +167,7 @@ where block_time_leeway: Duration::from_secs(args.extra_block_deadline_secs), da_config: Default::default(), sampling_ratio: args.telemetry.sampling_ratio, + max_gas_per_txn: args.max_gas_per_txn, specific: S::try_from(args)?, }) } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 6fbf6415..b36cbbe8 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -275,6 +275,7 @@ where builder_signer: self.config.builder_signer, metrics: self.metrics.clone(), extra_ctx: Default::default(), + max_gas_per_txn: self.config.max_gas_per_txn, }; let builder = OpBuilder::new(best); diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 43f1eb73..45acf963 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -19,6 +19,7 @@ pub enum TxnExecutionResult { Success, Reverted, RevertedAndExcluded, + MaxGasUsageExceeded, } #[derive(Default, Debug)] diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs index e734f4d8..a667c217 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -23,6 +23,7 @@ use super::{TransactionBuilder, FUNDED_PRIVATE_KEYS}; pub trait TransactionBuilderExt { fn random_valid_transfer(self) -> Self; fn random_reverting_transaction(self) -> Self; + fn random_big_transaction(self) -> Self; } impl TransactionBuilderExt for TransactionBuilder { @@ -33,6 +34,12 @@ impl TransactionBuilderExt for TransactionBuilder { fn random_reverting_transaction(self) -> Self { self.with_create().with_input(hex!("60006000fd").into()) // PUSH1 0x00 PUSH1 0x00 REVERT } + + fn random_big_transaction(self) -> Self { + // PUSH13 0x63ffffffff60005260046000f3 PUSH1 0x00 MSTORE PUSH1 0x02 PUSH1 0x0d PUSH1 0x13 PUSH1 0x00 CREATE2 + self.with_create() + .with_input(hex!("6c63ffffffff60005260046000f36000526002600d60136000f5").into()) + } } pub trait ChainDriverExt { diff --git a/crates/builder/op-rbuilder/src/tests/smoke.rs b/crates/builder/op-rbuilder/src/tests/smoke.rs index f4ee07c3..63cfa77b 100644 --- a/crates/builder/op-rbuilder/src/tests/smoke.rs +++ b/crates/builder/op-rbuilder/src/tests/smoke.rs @@ -1,5 +1,9 @@ -use crate::tests::{LocalInstance, TransactionBuilderExt}; +use crate::{ + args::OpRbuilderArgs, + tests::{LocalInstance, TransactionBuilderExt}, +}; use alloy_primitives::TxHash; + use core::{ sync::atomic::{AtomicBool, Ordering}, time::Duration, @@ -189,3 +193,106 @@ async fn test_no_tx_pool(rbuilder: LocalInstance) -> eyre::Result<()> { Ok(()) } + +#[rb_test(args = OpRbuilderArgs { + max_gas_per_txn: Some(25000), + ..Default::default() +})] +async fn chain_produces_big_tx_with_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + #[cfg(target_os = "linux")] + let driver = driver + .with_validation_node(crate::tests::ExternalNode::reth().await?) + .await?; + + // insert valid txn under limit + let tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await + .expect("Failed to send transaction"); + + // insert txn with gas usage above limit + let tx_high_gas = driver + .create_transaction() + .random_big_transaction() + .send() + .await + .expect("Failed to send transaction"); + + let block = driver.build_new_block_with_current_timestamp(None).await?; + let txs = block.transactions; + + if_standard! { + assert_eq!( + txs.len(), + 3, + "Should have 3 transactions" + ); + } + + if_flashblocks! { + assert_eq!( + txs.len(), + 4, + "Should have 4 transactions" + ); + } + + // assert we included the tx with gas under limit + let inclusion_result = txs.hashes().find(|hash| hash == tx.tx_hash()); + assert!(inclusion_result.is_some()); + + // assert we do not include the tx with gas above limit + let exclusion_result = txs.hashes().find(|hash| hash == tx_high_gas.tx_hash()); + assert!(exclusion_result.is_none()); + + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + ..Default::default() +})] +async fn chain_produces_big_tx_without_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + #[cfg(target_os = "linux")] + let driver = driver + .with_validation_node(crate::tests::ExternalNode::reth().await?) + .await?; + + // insert txn with gas usage but there is no limit + let tx = driver + .create_transaction() + .random_big_transaction() + .send() + .await + .expect("Failed to send transaction"); + + let block = driver.build_new_block_with_current_timestamp(None).await?; + let txs = block.transactions; + + // assert we included the tx + let inclusion_result = txs.hashes().find(|hash| hash == tx.tx_hash()); + assert!(inclusion_result.is_some()); + + if_standard! { + assert_eq!( + txs.len(), + 3, + "Should have 3 transactions" + ); + } + + if_flashblocks! { + assert_eq!( + txs.len(), + 4, + "Should have 4 transactions" + ); + } + + Ok(()) +} From 492f7142fbc98d477423a425495e79a2a445c7cb Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Wed, 20 Aug 2025 20:46:38 +0500 Subject: [PATCH 172/262] Add fix to preserve all executed blocks for flashblocks (#229) * Add ExecutedBlock push on every built flashblock * Update crates/op-rbuilder/src/builders/flashblocks/payload.rs --- .../src/builders/flashblocks/payload.rs | 34 +++++++++++++++++-- .../src/builders/flashblocks/service.rs | 8 +++++ .../src/primitives/reth/engine_api_builder.rs | 22 ++++++------ .../op-rbuilder/src/revert_protection.rs | 6 ++-- 4 files changed, 56 insertions(+), 14 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index edb76321..040d3bf9 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -24,8 +24,9 @@ use reth_node_api::{Block, NodePrimitives, 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_node::{OpBuiltPayload, OpEngineTypes, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; +use reth_payload_builder_primitives::Events; use reth_payload_util::BestPayloadTransactions; use reth_primitives_traits::RecoveredBlock; use reth_provider::{ @@ -44,7 +45,7 @@ use std::{ ops::{Div, Rem}, sync::{ atomic::{AtomicU64, Ordering}, - Arc, + Arc, OnceLock, }, time::Instant, }; @@ -119,6 +120,9 @@ pub struct OpPayloadBuilder { /// The end of builder transaction type #[allow(dead_code)] pub builder_tx: BT, + /// Builder events handle to send BuiltPayload events + pub payload_builder_handle: + Arc>>>, } impl OpPayloadBuilder { @@ -129,6 +133,9 @@ impl OpPayloadBuilder { client: Client, config: BuilderConfig, builder_tx: BT, + payload_builder_handle: Arc< + OnceLock>>, + >, ) -> eyre::Result { let metrics = Arc::new(OpRBuilderMetrics::default()); let ws_pub = WebSocketPublisher::new(config.specific.ws_addr, Arc::clone(&metrics))?.into(); @@ -140,6 +147,7 @@ impl OpPayloadBuilder { config, metrics, builder_tx, + payload_builder_handle, }) } } @@ -291,6 +299,8 @@ where let (payload, fb_payload) = build_block(&mut state, &ctx, &mut info)?; best_payload.set(payload.clone()); + self.send_payload_to_engine(payload); + let flashblock_byte_size = self .ws_pub .publish(&fb_payload) @@ -542,6 +552,7 @@ where .record(info.executed_transactions.len() as f64); best_payload.set(new_payload.clone()); + self.send_payload_to_engine(new_payload); // Update bundle_state for next iteration total_gas_per_batch += gas_per_batch; if let Some(da_limit) = da_per_batch { @@ -611,6 +622,25 @@ where span.record("flashblock_count", ctx.flashblock_index()); } + /// Sends built payload via payload builder handle broadcast channel to the engine + pub fn send_payload_to_engine(&self, payload: OpBuiltPayload) { + // Send built payload as created one + match self.payload_builder_handle.get() { + Some(handle) => { + let res = handle.send(Events::BuiltPayload(payload.clone())); + if let Err(e) = res { + error!( + message = "Failed to send payload via payload builder handle", + error = ?e, + ); + } + } + None => { + error!(message = "Payload builder handle is not setup, skipping sending payload") + } + } + } + /// Spawn task that will send new flashblock level cancel token in steady intervals (first interval /// may vary if --flashblocks.dynamic enabled) pub fn spawn_timer_task( diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index c045a0c2..412c3cab 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -13,6 +13,7 @@ use reth_node_builder::{components::PayloadServiceBuilder, BuilderContext}; use reth_optimism_evm::OpEvmConfig; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_provider::CanonStateSubscriptions; +use std::sync::Arc; pub struct FlashblocksServiceBuilder(pub BuilderConfig); @@ -28,12 +29,15 @@ impl FlashblocksServiceBuilder { Pool: PoolBounds, BT: BuilderTx + Unpin + Clone + Send + Sync + 'static, { + let once_lock = Arc::new(std::sync::OnceLock::new()); + let payload_builder = OpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), pool, ctx.provider().clone(), self.0.clone(), builder_tx, + once_lock.clone(), )?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); @@ -50,6 +54,10 @@ impl FlashblocksServiceBuilder { let (payload_service, payload_builder) = PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); + once_lock + .set(payload_service.payload_events_handle()) + .map_err(|_| eyre::eyre!("Cannot initialize payload service handle"))?; + ctx.task_executor() .spawn_critical("custom payload builder service", Box::pin(payload_service)); diff --git a/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs b/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs index 486602e1..c9a31816 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs @@ -1,9 +1,9 @@ //! RPC component builder use reth_node_api::AddOnsContext; -use reth_node_builder::rpc::{EngineApiBuilder, EngineValidatorBuilder}; -use reth_node_core::version::{CARGO_PKG_VERSION, CLIENT_CODE, VERGEN_GIT_SHA}; -use reth_optimism_node::{OpEngineTypes, OP_NAME_CLIENT}; +use reth_node_builder::rpc::{EngineApiBuilder, PayloadValidatorBuilder}; +use reth_node_core::version::{version_metadata, CLIENT_CODE}; +use reth_optimism_node::OpEngineTypes; use reth_optimism_rpc::engine::OP_ENGINE_CAPABILITIES; pub use reth_optimism_rpc::OpEngineApi; use reth_payload_builder::PayloadStore; @@ -22,7 +22,8 @@ use op_alloy_rpc_types_engine::{ OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4, OpPayloadAttributes, ProtocolVersion, SuperchainSignal, }; -use reth_node_api::{EngineTypes, EngineValidator}; +use reth::builder::NodeTypes; +use reth_node_api::{EngineApiValidator, EngineTypes}; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_rpc::OpEngineApiServer; use reth_rpc_api::IntoEngineApiRpcModule; @@ -49,7 +50,8 @@ where impl EngineApiBuilder for OpEngineApiBuilder where N: NodeComponents, - EV: EngineValidatorBuilder, + EV: PayloadValidatorBuilder, + EV::Validator: EngineApiValidator<::Payload>, { type EngineApi = OpEngineApiExt; @@ -61,9 +63,9 @@ where let engine_validator = engine_validator_builder.build(ctx).await?; let client = ClientVersionV1 { code: CLIENT_CODE, - name: OP_NAME_CLIENT.to_string(), - version: CARGO_PKG_VERSION.to_string(), - commit: VERGEN_GIT_SHA.to_string(), + name: version_metadata().name_client.to_string(), + version: version_metadata().cargo_pkg_version.to_string(), + commit: version_metadata().vergen_git_sha.to_string(), }; let inner = reth_rpc_engine_api::EngineApi::new( ctx.node.provider().clone(), @@ -90,7 +92,7 @@ impl OpEngineApiExt where Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static, Pool: TransactionPool + 'static, - Validator: EngineValidator, + Validator: EngineApiValidator, { pub fn new(engine: OpEngineApi) -> Self { Self { inner: engine } @@ -103,7 +105,7 @@ impl OpRbuilderEngineApiServer where Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static, Pool: TransactionPool + 'static, - Validator: EngineValidator, + Validator: EngineApiValidator, { async fn new_payload_v2(&self, payload: ExecutionPayloadInputV2) -> RpcResult { self.inner.new_payload_v2(payload).await diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index 0d497017..bbbc1c61 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -152,13 +152,15 @@ where .with_flashblock_number_max(conditional.flashblock_number_max) .with_conditional(conditional.transaction_conditional); - let hash = self + let outcome = self .pool .add_transaction(TransactionOrigin::Local, pool_transaction) .await .map_err(EthApiError::from)?; - let result = BundleResult { bundle_hash: hash }; + let result = BundleResult { + bundle_hash: outcome.hash, + }; Ok(result) } } From 2551b1cc8d06bebded1bdb6956e07a0d44181988 Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:48:16 +0200 Subject: [PATCH 173/262] fix: override clap long version envs (#235) --- crates/builder/op-rbuilder/src/args/mod.rs | 5 +++-- crates/builder/op-rbuilder/src/metrics.rs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index 0c0b1a4e..994e67db 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -1,6 +1,6 @@ use crate::{ builders::BuilderMode, - metrics::{CARGO_PKG_VERSION, VERGEN_GIT_SHA}, + metrics::{LONG_VERSION, SHORT_VERSION}, }; use clap_builder::{CommandFactory, FromArgMatches}; pub use op::{FlashblocksArgs, OpRbuilderArgs, TelemetryArgs}; @@ -82,7 +82,8 @@ impl CliExt for Cli { /// Parses commands and overrides versions fn set_version() -> Self { let matches = Cli::command() - .version(format!("{CARGO_PKG_VERSION} ({VERGEN_GIT_SHA})")) + .version(SHORT_VERSION) + .long_version(LONG_VERSION) .about("Block builder designed for the Optimism stack") .author("Flashbots") .name("op-rbuilder") diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index cd0da11f..e57269dd 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -24,6 +24,22 @@ pub const VERGEN_CARGO_FEATURES: &str = env!("VERGEN_CARGO_FEATURES"); /// The build profile name. pub const BUILD_PROFILE_NAME: &str = env!("OP_RBUILDER_BUILD_PROFILE"); +/// The short version information for op-rbuilder. +pub const SHORT_VERSION: &str = env!("OP_RBUILDER_SHORT_VERSION"); + +/// The long version information for op-rbuilder. +pub const LONG_VERSION: &str = concat!( + env!("OP_RBUILDER_LONG_VERSION_0"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_1"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_2"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_3"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_4"), +); + pub const VERSION: VersionInfo = VersionInfo { version: CARGO_PKG_VERSION, build_timestamp: VERGEN_BUILD_TIMESTAMP, From ff4a1557cb60f6728c557e28ab6cbb1b9b341dd0 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Fri, 22 Aug 2025 21:03:44 +0500 Subject: [PATCH 174/262] Improve best tx wrapper (#245) --- .../op-rbuilder/src/builders/context.rs | 2 +- .../src/builders/flashblocks/best_txs.rs | 228 ++++++--- .../src/builders/flashblocks/payload.rs | 37 +- .../src/builders/standard/payload.rs | 4 +- crates/builder/op-rbuilder/src/lib.rs | 2 + crates/builder/op-rbuilder/src/mock_tx.rs | 453 ++++++++++++++++++ crates/builder/op-rbuilder/src/tx.rs | 3 +- 7 files changed, 646 insertions(+), 83 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/mock_tx.rs diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 5725f2e4..cfa06136 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -326,7 +326,7 @@ impl OpPayloadBuilderCtx { &self, info: &mut ExecutionInfo, state: &mut State, - mut best_txs: impl PayloadTxsBounds, + best_txs: &mut impl PayloadTxsBounds, block_gas_limit: u64, block_da_limit: Option, ) -> Result, PayloadBuilderError> diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs index 1e7ad37f..9f224d4d 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs @@ -1,14 +1,7 @@ -use std::{ - collections::{BTreeMap, VecDeque}, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, - }, -}; - -use alloy_primitives::Address; +use alloy_primitives::{Address, TxHash}; use reth_payload_util::PayloadTransactions; use reth_transaction_pool::PoolTransaction; +use std::collections::HashSet; use tracing::debug; use crate::tx::MaybeFlashblockFilter; @@ -19,8 +12,10 @@ where I: PayloadTransactions, { inner: I, - current_flashblock_number: Arc, - early_transactions: BTreeMap>, + current_flashblock_number: u64, + // Transactions that were already commited to the state. Using them again would cause NonceTooLow + // so we skip them + commited_transactions: HashSet, } impl BestFlashblocksTxs @@ -28,13 +23,25 @@ where T: PoolTransaction, I: PayloadTransactions, { - pub fn new(inner: I, current_flashblock_number: Arc) -> Self { + pub fn new(inner: I) -> Self { Self { inner, - current_flashblock_number, - early_transactions: Default::default(), + current_flashblock_number: 0, + commited_transactions: Default::default(), } } + + /// Replaces current iterator with new one. We use it on new flashblock building, to refresh + /// priority boundaries + pub fn refresh_iterator(&mut self, inner: I, current_flashblock_number: u64) { + self.inner = inner; + self.current_flashblock_number = current_flashblock_number; + } + + /// Remove transaction from next iteration and it already in the state + pub fn mark_commited(&mut self, txs: Vec) { + self.commited_transactions.extend(txs); + } } impl PayloadTransactions for BestFlashblocksTxs @@ -46,66 +53,30 @@ where fn next(&mut self, ctx: ()) -> Option { loop { - let flashblock_number = self.current_flashblock_number.load(Ordering::Relaxed); - - // Check for new transactions that can be executed with the higher flashblock number - while let Some((&min_flashblock, _)) = self.early_transactions.first_key_value() { - if min_flashblock > flashblock_number { - break; - } - - if let Some(mut txs) = self.early_transactions.remove(&min_flashblock) { - while let Some(tx) = txs.pop_front() { - // Re-check max flashblock number just in case - if let Some(max) = tx.flashblock_number_max() { - if flashblock_number > max { - debug!( - target: "payload_builder", - sender = ?tx.sender(), - nonce = tx.nonce(), - current_flashblock = flashblock_number, - max_flashblock = max, - "Bundle flashblock max exceeded" - ); - self.mark_invalid(tx.sender(), tx.nonce()); - continue; - } - } - - // The vecdeque isn't modified in place so we need to replace it - if !txs.is_empty() { - self.early_transactions.insert(min_flashblock, txs); - } - - return Some(tx); - } - } - } - let tx = self.inner.next(ctx)?; + // Skip transaction we already included + if self.commited_transactions.contains(tx.hash()) { + continue; + } let flashblock_number_min = tx.flashblock_number_min(); let flashblock_number_max = tx.flashblock_number_max(); // Check min flashblock requirement if let Some(min) = flashblock_number_min { - if flashblock_number < min { - self.early_transactions - .entry(min) - .or_default() - .push_back(tx); + if self.current_flashblock_number < min { continue; } } // Check max flashblock requirement if let Some(max) = flashblock_number_max { - if flashblock_number > max { + if self.current_flashblock_number > max { debug!( target: "payload_builder", sender = ?tx.sender(), nonce = tx.nonce(), - current_flashblock = flashblock_number, + current_flashblock = self.current_flashblock_number, max_flashblock = max, "Bundle flashblock max exceeded" ); @@ -118,15 +89,144 @@ where } } + /// Proxy to inner iterator fn mark_invalid(&mut self, sender: Address, nonce: u64) { self.inner.mark_invalid(sender, nonce); + } +} + +#[cfg(test)] +mod tests { + use crate::{ + builders::flashblocks::best_txs::BestFlashblocksTxs, + mock_tx::{MockFbTransaction, MockFbTransactionFactory}, + }; + use alloy_consensus::Transaction; + use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; + use reth_transaction_pool::{pool::PendingPool, CoinbaseTipOrdering, PoolTransaction}; + use std::sync::Arc; + + #[test] + fn test_simple_case() { + let mut pool = PendingPool::new(CoinbaseTipOrdering::::default()); + let mut f = MockFbTransactionFactory::default(); + + // Add 3 regular transaction + let tx_1 = f.create_eip1559(); + let tx_2 = f.create_eip1559(); + let tx_3 = f.create_eip1559(); + pool.add_transaction(Arc::new(tx_1), 0); + pool.add_transaction(Arc::new(tx_2), 0); + pool.add_transaction(Arc::new(tx_3), 0); + + // Create iterator + let mut iterator = BestFlashblocksTxs::new(BestPayloadTransactions::new(pool.best())); + // ### First flashblock + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 0); + // Accept first tx + let tx1 = iterator.next(()).unwrap(); + // Invalidate second tx + let tx2 = iterator.next(()).unwrap(); + iterator.mark_invalid(tx2.sender(), tx2.nonce()); + // Accept third tx + let tx3 = iterator.next(()).unwrap(); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + // Mark transaction as commited + iterator.mark_commited(vec![*tx1.hash(), *tx3.hash()]); + + // ### Second flashblock + // It should not return txs 1 and 3, but should return 2 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 1); + let tx2 = iterator.next(()).unwrap(); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + // Mark transaction as commited + iterator.mark_commited(vec![*tx2.hash()]); + + // ### Third flashblock + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 2); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + } - // Clear early_transactions from this sender with a greater nonce as - // these transactions now will not execute because there would be a - // nonce gap - self.early_transactions.retain(|_, txs| { - txs.retain(|tx| !(tx.sender() == sender && tx.nonce() > nonce)); - !txs.is_empty() - }); + /// Test bundle cases + /// We won't mark transactions as commited to test that boundaries are respected + #[test] + fn test_bundle_case() { + let mut pool = PendingPool::new(CoinbaseTipOrdering::::default()); + let mut f = MockFbTransactionFactory::default(); + + // Add 4 fb transaction + let tx_1 = f.create_legacy_fb(None, None); + let tx_1_hash = *tx_1.hash(); + let tx_2 = f.create_legacy_fb(None, Some(1)); + let tx_2_hash = *tx_2.hash(); + let tx_3 = f.create_legacy_fb(Some(1), None); + let tx_3_hash = *tx_3.hash(); + let tx_4 = f.create_legacy_fb(Some(2), Some(3)); + let tx_4_hash = *tx_4.hash(); + pool.add_transaction(Arc::new(tx_1), 0); + pool.add_transaction(Arc::new(tx_2), 0); + pool.add_transaction(Arc::new(tx_3), 0); + pool.add_transaction(Arc::new(tx_4), 0); + + // Create iterator + let mut iterator = BestFlashblocksTxs::new(BestPayloadTransactions::new(pool.best())); + // ### First flashblock + // should contain txs 1 and 2 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 0); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx2 = iterator.next(()).unwrap(); + assert_eq!(tx2.hash(), &tx_2_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + + // ### Second flashblock + // should contain txs 1, 2, and 3 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 1); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx2 = iterator.next(()).unwrap(); + assert_eq!(tx2.hash(), &tx_2_hash); + let tx3 = iterator.next(()).unwrap(); + assert_eq!(tx3.hash(), &tx_3_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + + // ### Third flashblock + // should contain txs 1, 3, and 4 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 2); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx3 = iterator.next(()).unwrap(); + assert_eq!(tx3.hash(), &tx_3_hash); + let tx4 = iterator.next(()).unwrap(); + assert_eq!(tx4.hash(), &tx_4_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + + // ### Forth flashblock + // should contain txs 1, 3, and 4 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 3); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx3 = iterator.next(()).unwrap(); + assert_eq!(tx3.hash(), &tx_3_hash); + let tx4 = iterator.next(()).unwrap(); + assert_eq!(tx4.hash(), &tx_4_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); + + // ### Fifth flashblock + // should contain txs 1 and 3 + iterator.refresh_iterator(BestPayloadTransactions::new(pool.best()), 4); + let tx1 = iterator.next(()).unwrap(); + assert_eq!(tx1.hash(), &tx_1_hash); + let tx3 = iterator.next(()).unwrap(); + assert_eq!(tx3.hash(), &tx_3_hash); + // Check that it's empty + assert!(iterator.next(()).is_none(), "Iterator should be empty"); } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 040d3bf9..8f109e29 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -43,10 +43,7 @@ use rollup_boost::{ use serde::{Deserialize, Serialize}; use std::{ ops::{Div, Rem}, - sync::{ - atomic::{AtomicU64, Ordering}, - Arc, OnceLock, - }, + sync::{Arc, OnceLock}, time::Instant, }; use tokio::sync::{ @@ -65,7 +62,7 @@ struct ExtraExecutionInfo { #[derive(Debug, Default)] struct FlashblocksExtraCtx { /// Current flashblock index - pub flashblock_index: Arc, + pub flashblock_index: u64, /// Target flashblock count pub target_flashblock_count: u64, } @@ -73,7 +70,7 @@ struct FlashblocksExtraCtx { impl OpPayloadBuilderCtx { /// Returns the current flashblock index pub fn flashblock_index(&self) -> u64 { - self.extra_ctx.flashblock_index.load(Ordering::Relaxed) + self.extra_ctx.flashblock_index } /// Returns the target flashblock count @@ -83,10 +80,8 @@ impl OpPayloadBuilderCtx { /// Increments the flashblock index pub fn increment_flashblock_index(&mut self) -> u64 { - self.extra_ctx - .flashblock_index - .fetch_add(1, Ordering::Relaxed); - self.flashblock_index() + self.extra_ctx.flashblock_index += 1; + self.extra_ctx.flashblock_index } /// Sets the target flashblock count @@ -261,7 +256,7 @@ where builder_signer: self.config.builder_signer, metrics: Default::default(), extra_ctx: FlashblocksExtraCtx { - flashblock_index: Arc::new(AtomicU64::new(0)), + flashblock_index: 0, target_flashblock_count: self.config.flashblocks_per_block(), }, max_gas_per_txn: self.config.max_gas_per_txn, @@ -378,6 +373,11 @@ where *da_limit = da_limit.saturating_sub(builder_tx_da_size); } + // Create best_transaction iterator + let mut best_txs = BestFlashblocksTxs::new(BestPayloadTransactions::new( + self.pool + .best_transactions_with_attributes(ctx.best_transaction_attributes()), + )); // This channel coordinates flashblock building let (fb_cancel_token_rx, mut fb_cancel_token_tx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); @@ -443,13 +443,13 @@ where } let best_txs_start_time = Instant::now(); - let best_txs = BestFlashblocksTxs::new( + best_txs.refresh_iterator( BestPayloadTransactions::new( self.pool.best_transactions_with_attributes( ctx.best_transaction_attributes(), ), ), - ctx.extra_ctx.flashblock_index.clone(), + ctx.flashblock_index(), ); let transaction_pool_fetch_time = best_txs_start_time.elapsed(); ctx.metrics @@ -463,10 +463,19 @@ where ctx.execute_best_transactions( &mut info, &mut state, - best_txs, + &mut best_txs, total_gas_per_batch.min(ctx.block_gas_limit()), total_da_per_batch, )?; + // Extract last transactions + let new_transactions = info.executed_transactions + [info.extra.last_flashblock_index..] + .to_vec() + .iter() + .map(|tx| tx.tx_hash()) + .collect::>(); + best_txs.mark_commited(new_transactions); + // We got block cancelled, we won't need anything from the block at this point // Caution: this assume that block cancel token only cancelled when new FCU is received if block_cancel.is_cancelled() { diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index b36cbbe8..06ed5c70 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -404,7 +404,7 @@ impl OpBuilder<'_, Txs> { if !ctx.attributes().no_tx_pool { let best_txs_start_time = Instant::now(); - let best_txs = best(ctx.best_transaction_attributes()); + 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 @@ -417,7 +417,7 @@ impl OpBuilder<'_, Txs> { .execute_best_transactions( &mut info, state, - best_txs, + &mut best_txs, block_gas_limit, block_da_limit, )? diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index a11d81c6..9449e98c 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -10,5 +10,7 @@ pub mod traits; pub mod tx; pub mod tx_signer; +#[cfg(test)] +pub mod mock_tx; #[cfg(any(test, feature = "testing"))] pub mod tests; diff --git a/crates/builder/op-rbuilder/src/mock_tx.rs b/crates/builder/op-rbuilder/src/mock_tx.rs new file mode 100644 index 00000000..3cfd340e --- /dev/null +++ b/crates/builder/op-rbuilder/src/mock_tx.rs @@ -0,0 +1,453 @@ +use crate::tx::MaybeFlashblockFilter; +use alloy_consensus::{ + error::ValueError, transaction::Recovered, EthereumTxEnvelope, TxEip4844, TxEip4844WithSidecar, + TxType, +}; +use alloy_eips::{ + eip2930::AccessList, + eip4844::{env_settings::KzgSettings, BlobTransactionValidationError}, + eip7594::BlobTransactionSidecarVariant, + eip7702::SignedAuthorization, + Typed2718, +}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; +use reth::primitives::TransactionSigned; +use reth_primitives_traits::{InMemorySize, SignedTransaction}; +use reth_transaction_pool::{ + identifier::TransactionId, + test_utils::{MockTransaction, MockTransactionFactory}, + EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction, TransactionOrigin, + ValidPoolTransaction, +}; +use std::{sync::Arc, time::Instant}; + +/// A factory for creating and managing various types of mock transactions. +#[derive(Debug, Default)] +pub struct MockFbTransactionFactory { + pub(crate) factory: MockTransactionFactory, +} + +// === impl MockTransactionFactory === + +impl MockFbTransactionFactory { + /// Generates a transaction ID for the given [`MockTransaction`]. + pub fn tx_id(&mut self, tx: &MockFbTransaction) -> TransactionId { + self.factory.tx_id(&tx.inner) + } + + /// Validates a [`MockTransaction`] and returns a [`MockValidFbTx`]. + pub fn validated(&mut self, transaction: MockFbTransaction) -> MockValidFbTx { + self.validated_with_origin(TransactionOrigin::External, transaction) + } + + /// Validates a [`MockTransaction`] and returns a shared [`Arc`]. + pub fn validated_arc(&mut self, transaction: MockFbTransaction) -> Arc { + Arc::new(self.validated(transaction)) + } + + /// Converts the transaction into a validated transaction with a specified origin. + pub fn validated_with_origin( + &mut self, + origin: TransactionOrigin, + transaction: MockFbTransaction, + ) -> MockValidFbTx { + MockValidFbTx { + propagate: false, + transaction_id: self.tx_id(&transaction), + transaction, + timestamp: Instant::now(), + origin, + authority_ids: None, + } + } + + /// Creates a validated legacy [`MockTransaction`]. + pub fn create_legacy(&mut self) -> MockValidFbTx { + self.validated(MockFbTransaction { + inner: MockTransaction::legacy(), + reverted_hashes: None, + flashblock_number_max: None, + flashblock_number_min: None, + }) + } + + /// Creates a validated legacy [`MockTransaction`]. + pub fn create_legacy_fb(&mut self, min: Option, max: Option) -> MockValidFbTx { + self.validated(MockFbTransaction { + inner: MockTransaction::legacy(), + reverted_hashes: None, + flashblock_number_max: max, + flashblock_number_min: min, + }) + } + + /// Creates a validated EIP-1559 [`MockTransaction`]. + pub fn create_eip1559(&mut self) -> MockValidFbTx { + self.validated(MockFbTransaction { + inner: MockTransaction::eip1559(), + reverted_hashes: None, + flashblock_number_max: None, + flashblock_number_min: None, + }) + } + + /// Creates a validated EIP-4844 [`MockTransaction`]. + pub fn create_eip4844(&mut self) -> MockValidFbTx { + self.validated(MockFbTransaction { + inner: MockTransaction::eip4844(), + reverted_hashes: None, + flashblock_number_max: None, + flashblock_number_min: None, + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MockFbTransaction { + pub inner: MockTransaction, + /// reverted hashes for the transaction. If the transaction is a bundle, + /// this is the list of hashes of the transactions that reverted. If the + /// transaction is not a bundle, this is `None`. + pub reverted_hashes: Option>, + + pub flashblock_number_min: Option, + pub flashblock_number_max: Option, +} + +/// A validated transaction in the transaction pool, using [`MockTransaction`] as the transaction +/// type. +/// +/// This type is an alias for [`ValidPoolTransaction`]. +pub type MockValidFbTx = ValidPoolTransaction; + +impl PoolTransaction for MockFbTransaction { + type TryFromConsensusError = ValueError>; + + type Consensus = TransactionSigned; + + type Pooled = PooledTransactionVariant; + + fn into_consensus(self) -> Recovered { + self.inner.into() + } + + fn from_pooled(pooled: Recovered) -> Self { + Self { + inner: pooled.into(), + reverted_hashes: None, + flashblock_number_min: None, + flashblock_number_max: None, + } + } + + fn hash(&self) -> &TxHash { + self.inner.get_hash() + } + + fn sender(&self) -> Address { + *self.inner.get_sender() + } + + fn sender_ref(&self) -> &Address { + self.inner.get_sender() + } + + // Having `get_cost` from `make_setters_getters` would be cleaner but we didn't + // want to also generate the error-prone cost setters. For now cost should be + // correct at construction and auto-updated per field update via `update_cost`, + // not to be manually set. + fn cost(&self) -> &U256 { + match &self.inner { + MockTransaction::Legacy { cost, .. } + | MockTransaction::Eip2930 { cost, .. } + | MockTransaction::Eip1559 { cost, .. } + | MockTransaction::Eip4844 { cost, .. } + | MockTransaction::Eip7702 { cost, .. } => cost, + } + } + + /// Returns the encoded length of the transaction. + fn encoded_length(&self) -> usize { + self.inner.size() + } +} + +impl InMemorySize for MockFbTransaction { + fn size(&self) -> usize { + *self.inner.get_size() + } +} + +impl Typed2718 for MockFbTransaction { + fn ty(&self) -> u8 { + match self.inner { + MockTransaction::Legacy { .. } => TxType::Legacy.into(), + MockTransaction::Eip1559 { .. } => TxType::Eip1559.into(), + MockTransaction::Eip4844 { .. } => TxType::Eip4844.into(), + MockTransaction::Eip2930 { .. } => TxType::Eip2930.into(), + MockTransaction::Eip7702 { .. } => TxType::Eip7702.into(), + } + } +} + +impl alloy_consensus::Transaction for MockFbTransaction { + fn chain_id(&self) -> Option { + match &self.inner { + MockTransaction::Legacy { chain_id, .. } => *chain_id, + MockTransaction::Eip1559 { chain_id, .. } + | MockTransaction::Eip4844 { chain_id, .. } + | MockTransaction::Eip2930 { chain_id, .. } + | MockTransaction::Eip7702 { chain_id, .. } => Some(*chain_id), + } + } + + fn nonce(&self) -> u64 { + *self.inner.get_nonce() + } + + fn gas_limit(&self) -> u64 { + *self.inner.get_gas_limit() + } + + fn gas_price(&self) -> Option { + match &self.inner { + MockTransaction::Legacy { gas_price, .. } + | MockTransaction::Eip2930 { gas_price, .. } => Some(*gas_price), + _ => None, + } + } + + fn max_fee_per_gas(&self) -> u128 { + match &self.inner { + MockTransaction::Legacy { gas_price, .. } + | MockTransaction::Eip2930 { gas_price, .. } => *gas_price, + MockTransaction::Eip1559 { + max_fee_per_gas, .. + } + | MockTransaction::Eip4844 { + max_fee_per_gas, .. + } + | MockTransaction::Eip7702 { + max_fee_per_gas, .. + } => *max_fee_per_gas, + } + } + + fn max_priority_fee_per_gas(&self) -> Option { + match &self.inner { + MockTransaction::Legacy { .. } | MockTransaction::Eip2930 { .. } => None, + MockTransaction::Eip1559 { + max_priority_fee_per_gas, + .. + } + | MockTransaction::Eip4844 { + max_priority_fee_per_gas, + .. + } + | MockTransaction::Eip7702 { + max_priority_fee_per_gas, + .. + } => Some(*max_priority_fee_per_gas), + } + } + + fn max_fee_per_blob_gas(&self) -> Option { + match &self.inner { + MockTransaction::Eip4844 { + max_fee_per_blob_gas, + .. + } => Some(*max_fee_per_blob_gas), + _ => None, + } + } + + fn priority_fee_or_price(&self) -> u128 { + match &self.inner { + MockTransaction::Legacy { gas_price, .. } + | MockTransaction::Eip2930 { gas_price, .. } => *gas_price, + MockTransaction::Eip1559 { + max_priority_fee_per_gas, + .. + } + | MockTransaction::Eip4844 { + max_priority_fee_per_gas, + .. + } + | MockTransaction::Eip7702 { + max_priority_fee_per_gas, + .. + } => *max_priority_fee_per_gas, + } + } + + fn effective_gas_price(&self, base_fee: Option) -> u128 { + base_fee.map_or_else( + || self.max_fee_per_gas(), + |base_fee| { + // if the tip is greater than the max priority fee per gas, set it to the max + // priority fee per gas + base fee + let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128); + if let Some(max_tip) = self.max_priority_fee_per_gas() { + if tip > max_tip { + max_tip + base_fee as u128 + } else { + // otherwise return the max fee per gas + self.max_fee_per_gas() + } + } else { + self.max_fee_per_gas() + } + }, + ) + } + + fn is_dynamic_fee(&self) -> bool { + !matches!( + self.inner, + MockTransaction::Legacy { .. } | MockTransaction::Eip2930 { .. } + ) + } + + fn kind(&self) -> TxKind { + match &self.inner { + MockTransaction::Legacy { to, .. } + | MockTransaction::Eip1559 { to, .. } + | MockTransaction::Eip2930 { to, .. } => *to, + MockTransaction::Eip4844 { to, .. } | MockTransaction::Eip7702 { to, .. } => { + TxKind::Call(*to) + } + } + } + + fn is_create(&self) -> bool { + match &self.inner { + MockTransaction::Legacy { to, .. } + | MockTransaction::Eip1559 { to, .. } + | MockTransaction::Eip2930 { to, .. } => to.is_create(), + MockTransaction::Eip4844 { .. } | MockTransaction::Eip7702 { .. } => false, + } + } + + fn value(&self) -> U256 { + match &self.inner { + MockTransaction::Legacy { value, .. } + | MockTransaction::Eip1559 { value, .. } + | MockTransaction::Eip2930 { value, .. } + | MockTransaction::Eip4844 { value, .. } + | MockTransaction::Eip7702 { value, .. } => *value, + } + } + + fn input(&self) -> &Bytes { + self.inner.get_input() + } + + fn access_list(&self) -> Option<&AccessList> { + match &self.inner { + MockTransaction::Legacy { .. } => None, + MockTransaction::Eip1559 { + access_list: accesslist, + .. + } + | MockTransaction::Eip4844 { + access_list: accesslist, + .. + } + | MockTransaction::Eip2930 { + access_list: accesslist, + .. + } + | MockTransaction::Eip7702 { + access_list: accesslist, + .. + } => Some(accesslist), + } + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + match &self.inner { + MockTransaction::Eip4844 { + blob_versioned_hashes, + .. + } => Some(blob_versioned_hashes), + _ => None, + } + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + match &self.inner { + MockTransaction::Eip7702 { + authorization_list, .. + } => Some(authorization_list), + _ => None, + } + } +} + +impl EthPoolTransaction for MockFbTransaction { + fn take_blob(&mut self) -> EthBlobTransactionSidecar { + match &self.inner { + MockTransaction::Eip4844 { sidecar, .. } => { + EthBlobTransactionSidecar::Present(sidecar.clone()) + } + _ => EthBlobTransactionSidecar::None, + } + } + + fn try_into_pooled_eip4844( + self, + sidecar: Arc, + ) -> Option> { + let (tx, signer) = self.into_consensus().into_parts(); + tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar)) + .map(|tx| tx.with_signer(signer)) + .ok() + } + + fn try_from_eip4844( + tx: Recovered, + sidecar: BlobTransactionSidecarVariant, + ) -> Option { + let (tx, signer) = tx.into_parts(); + tx.try_into_pooled_eip4844(sidecar) + .map(|tx| tx.with_signer(signer)) + .ok() + .map(Self::from_pooled) + } + + fn validate_blob( + &self, + _blob: &BlobTransactionSidecarVariant, + _settings: &KzgSettings, + ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> { + match &self.inner { + MockTransaction::Eip4844 { .. } => Ok(()), + _ => Err(BlobTransactionValidationError::NotBlobTransaction( + self.inner.tx_type(), + )), + } + } +} + +pub type PooledTransactionVariant = + alloy_consensus::EthereumTxEnvelope>; + +impl MaybeFlashblockFilter for MockFbTransaction { + fn with_flashblock_number_min(mut self, flashblock_number_min: Option) -> Self { + self.flashblock_number_min = flashblock_number_min; + self + } + + fn with_flashblock_number_max(mut self, flashblock_number_max: Option) -> Self { + self.flashblock_number_max = flashblock_number_max; + self + } + + fn flashblock_number_min(&self) -> Option { + self.flashblock_number_min + } + + fn flashblock_number_max(&self) -> Option { + self.flashblock_number_max + } +} diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index 10c914fc..096d375a 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -1,5 +1,3 @@ -use std::{borrow::Cow, sync::Arc}; - use alloy_consensus::{conditional::BlockConditionalAttributes, BlobTransactionValidationError}; use alloy_eips::{eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization, Typed2718}; use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; @@ -12,6 +10,7 @@ use reth_optimism_txpool::{ use reth_primitives::{kzg::KzgSettings, Recovered}; use reth_primitives_traits::InMemorySize; use reth_transaction_pool::{EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction}; +use std::{borrow::Cow, sync::Arc}; pub trait FBPoolTransaction: MaybeRevertingTransaction + OpPooledTx + MaybeFlashblockFilter From fe5c09f8a9b025e803ed225f971020d70295735b Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Mon, 25 Aug 2025 06:42:41 -0400 Subject: [PATCH 175/262] docs: eth_sendBundle (#243) * doc comments for bundle params * add check for flashblock number range * markdown doc --- .../op-rbuilder/src/primitives/bundle.rs | 168 +++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index 073422a8..d1a4fa7a 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -3,16 +3,54 @@ use alloy_rpc_types_eth::erc4337::TransactionConditional; use reth_rpc_eth_types::EthApiError; use serde::{Deserialize, Serialize}; +/// Maximum number of blocks allowed in the block range for bundle execution. +/// +/// This constant limits how far into the future a bundle can be scheduled to +/// prevent excessive resource usage and ensure timely execution. When no +/// maximum block number is specified, this value is added to the current block +/// number to set the default upper bound. pub const MAX_BLOCK_RANGE_BLOCKS: u64 = 10; +/// A bundle represents a collection of transactions that should be executed +/// together with specific conditional constraints. +/// +/// Bundles allow for sophisticated transaction ordering and conditional +/// execution based on block numbers, flashblock numbers, and timestamps. They +/// are a key primitive in MEV (Maximal Extractable Value) strategies and block +/// building. +/// +/// # Validation +/// +/// The following validations are performed before adding the transaction to the +/// mempool: +/// - Block number ranges are valid (min ≤ max) +/// - Maximum block numbers are not in the past +/// - Block ranges don't exceed `MAX_BLOCK_RANGE_BLOCKS` (currently 10) +/// - There's only one transaction in the bundle +/// - Flashblock number ranges are valid (min ≤ max) #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct Bundle { + /// List of raw transaction data to be included in the bundle. + /// + /// Each transaction is represented as raw bytes that will be decoded and + /// executed in the specified order when the bundle conditions are met. #[serde(rename = "txs")] pub transactions: Vec, + /// Optional list of transaction hashes that are allowed to revert. + /// + /// By default, if any transaction in a bundle reverts, the entire bundle is + /// considered invalid. This field allows specific transactions to revert + /// without invalidating the bundle, enabling more sophisticated MEV + /// strategies. #[serde(rename = "revertingTxHashes")] pub reverting_hashes: Option>, + /// Minimum block number at which this bundle can be included. + /// + /// If specified, the bundle will only be considered for inclusion in blocks + /// at or after this block number. This allows for scheduling bundles for + /// future execution. #[serde( default, rename = "minBlockNumber", @@ -21,6 +59,11 @@ pub struct Bundle { )] pub block_number_min: Option, + /// Maximum block number at which this bundle can be included. + /// + /// If specified, the bundle will be considered invalid for inclusion in + /// blocks after this block number. If not specified, defaults to the + /// current block number plus `MAX_BLOCK_RANGE_BLOCKS`. #[serde( default, rename = "maxBlockNumber", @@ -29,6 +72,11 @@ pub struct Bundle { )] pub block_number_max: Option, + /// Minimum flashblock number at which this bundle can be included. + /// + /// Flashblocks are preconfirmations that are built incrementally. This + /// field along with `maxFlashblockNumber` allows bundles to be scheduled + /// for more precise execution. #[serde( default, rename = "minFlashblockNumber", @@ -37,6 +85,10 @@ pub struct Bundle { )] pub flashblock_number_min: Option, + /// Maximum flashblock number at which this bundle can be included. + /// + /// Similar to `minFlashblockNumber`, this sets an upper bound on which + /// flashblocks can include this bundle. #[serde( default, rename = "maxFlashblockNumber", @@ -45,7 +97,12 @@ pub struct Bundle { )] pub flashblock_number_max: Option, - // Not recommended because this is subject to the builder node clock + /// Minimum timestamp (Unix epoch seconds) for bundle inclusion. + /// + /// **Warning**: Not recommended for production use as it depends on the + /// builder node's clock, which may not be perfectly synchronized with + /// network time. Block number constraints are preferred for deterministic + /// behavior. #[serde( default, rename = "minTimestamp", @@ -53,7 +110,12 @@ pub struct Bundle { )] pub min_timestamp: Option, - // Not recommended because this is subject to the builder node clock + /// Maximum timestamp (Unix epoch seconds) for bundle inclusion. + /// + /// **Warning**: Not recommended for production use as it depends on the + /// builder node's clock, which may not be perfectly synchronized with + /// network time. Block number constraints are preferred for deterministic + /// behavior. #[serde( default, rename = "maxTimestamp", @@ -74,6 +136,9 @@ pub enum BundleConditionalError { MinGreaterThanMax { min: u64, max: u64 }, #[error("block_number_max ({max}) is a past block (current: {current})")] MaxBlockInPast { max: u64, current: u64 }, + /// To prevent resource exhaustion and ensure timely execution, bundles + /// cannot be scheduled more than `MAX_BLOCK_RANGE_BLOCKS` blocks into the + /// future. #[error( "block_number_max ({max}) is too high (current: {current}, max allowed: {max_allowed})" )] @@ -82,10 +147,15 @@ pub enum BundleConditionalError { current: u64, max_allowed: u64, }, + /// When no explicit maximum block number is provided, the system uses + /// `current_block + MAX_BLOCK_RANGE_BLOCKS` as the default maximum. This + /// error occurs when the specified minimum exceeds this default maximum. #[error( "block_number_min ({min}) is too high with default max range (max allowed: {max_allowed})" )] MinTooHighForDefaultRange { min: u64, max_allowed: u64 }, + #[error("flashblock_number_min ({min}) is greater than flashblock_number_max ({max})")] + FlashblockMinGreaterThanMax { min: u64, max: u64 }, } pub struct BundleConditional { @@ -144,6 +214,15 @@ impl Bundle { } } + // Validate flashblock number range + if let Some(min) = self.flashblock_number_min { + if let Some(max) = self.flashblock_number_max { + if min > max { + return Err(BundleConditionalError::FlashblockMinGreaterThanMax { min, max }); + } + } + } + Ok(BundleConditional { transaction_conditional: TransactionConditional { block_number_min, @@ -158,8 +237,18 @@ impl Bundle { } } +/// Result returned after successfully submitting a bundle for inclusion. +/// +/// This struct contains the unique identifier for the submitted bundle, which +/// can be used to track the bundle's status and inclusion in future blocks. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BundleResult { + /// Transaction hash of the single transaction in the bundle. + /// + /// This hash can be used to: + /// - Track bundle inclusion in blocks + /// - Query bundle status + /// - Reference the bundle in subsequent operations #[serde(rename = "bundleHash")] pub bundle_hash: B256, } @@ -334,4 +423,79 @@ mod tests { assert_eq!(result.block_number_min, Some(999)); assert_eq!(result.block_number_max, Some(1010)); } + + #[test] + fn test_bundle_conditional_flashblock_min_greater_than_max() { + let bundle = Bundle { + flashblock_number_min: Some(105), + flashblock_number_max: Some(100), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block); + + assert!(matches!( + result, + Err(BundleConditionalError::FlashblockMinGreaterThanMax { min: 105, max: 100 }) + )); + } + + #[test] + fn test_bundle_conditional_with_valid_flashblock_range() { + let bundle = Bundle { + flashblock_number_min: Some(100), + flashblock_number_max: Some(105), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.flashblock_number_min, Some(100)); + assert_eq!(result.flashblock_number_max, Some(105)); + } + + #[test] + fn test_bundle_conditional_with_only_flashblock_min() { + let bundle = Bundle { + flashblock_number_min: Some(100), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.flashblock_number_min, Some(100)); + assert_eq!(result.flashblock_number_max, None); + } + + #[test] + fn test_bundle_conditional_with_only_flashblock_max() { + let bundle = Bundle { + flashblock_number_max: Some(105), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.flashblock_number_min, None); + assert_eq!(result.flashblock_number_max, Some(105)); + } + + #[test] + fn test_bundle_conditional_flashblock_equal_values() { + let bundle = Bundle { + flashblock_number_min: Some(100), + flashblock_number_max: Some(100), + ..Default::default() + }; + + let last_block = 1000; + let result = bundle.conditional(last_block).unwrap(); + + assert_eq!(result.flashblock_number_min, Some(100)); + assert_eq!(result.flashblock_number_max, Some(100)); + } } From ddefb4f4011a56b38d4785067cda094c63c7f8b0 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Mon, 25 Aug 2025 09:00:40 -0400 Subject: [PATCH 176/262] update rust edition to 2024 (#244) --- crates/builder/op-rbuilder/src/args/mod.rs | 5 +- crates/builder/op-rbuilder/src/args/op.rs | 2 +- .../op-rbuilder/src/args/playground.rs | 4 +- .../op-rbuilder/src/builders/context.rs | 36 +++++------ .../src/builders/flashblocks/best_txs.rs | 2 +- .../src/builders/flashblocks/payload.rs | 24 ++++--- .../src/builders/flashblocks/service.rs | 8 +-- .../src/builders/flashblocks/wspub.rs | 5 +- .../op-rbuilder/src/builders/generator.rs | 8 +-- .../src/builders/standard/payload.rs | 12 ++-- .../op-rbuilder/src/flashtestations/args.rs | 2 +- .../src/flashtestations/service.rs | 4 +- .../src/flashtestations/tx_manager.rs | 4 +- crates/builder/op-rbuilder/src/launcher.rs | 4 +- crates/builder/op-rbuilder/src/metrics.rs | 2 +- crates/builder/op-rbuilder/src/mock_tx.rs | 14 ++--- .../op-rbuilder/src/primitives/bundle.rs | 2 +- .../src/primitives/reth/engine_api_builder.rs | 8 +-- .../op-rbuilder/src/primitives/telemetry.rs | 6 +- .../op-rbuilder/src/revert_protection.rs | 31 +++++----- .../op-rbuilder/src/tests/framework/apis.rs | 6 +- .../op-rbuilder/src/tests/framework/driver.rs | 4 +- .../src/tests/framework/external.rs | 8 +-- .../src/tests/framework/instance.rs | 7 +-- .../op-rbuilder/src/tests/framework/txs.rs | 13 ++-- .../op-rbuilder/src/tests/framework/utils.rs | 15 +++-- .../builder/op-rbuilder/src/tests/ordering.rs | 4 +- .../builder/op-rbuilder/src/tests/revert.rs | 62 +++++++++++-------- .../builder/op-rbuilder/src/tests/txpool.rs | 2 +- crates/builder/op-rbuilder/src/tx.rs | 17 ++--- crates/builder/op-rbuilder/src/tx_signer.rs | 8 +-- crates/builder/tdx-quote-provider/src/main.rs | 2 +- .../tdx-quote-provider/src/provider.rs | 2 +- .../builder/tdx-quote-provider/src/server.rs | 4 +- 34 files changed, 172 insertions(+), 165 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index 994e67db..4188e39c 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -56,10 +56,7 @@ impl CliExt for Cli { return self; }; - let options = match PlaygroundOptions::new(playground_dir) { - Ok(options) => options, - Err(e) => exit(e), - }; + let options = PlaygroundOptions::new(playground_dir).unwrap_or_else(|e| exit(e)); options.apply(self) } diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index f25fcd30..9c1d1fc2 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -5,7 +5,7 @@ //! clap [Args](clap::Args) for optimism rollup configuration use crate::{flashtestations::args::FlashtestationsArgs, tx_signer::Signer}; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use clap::Parser; use reth_optimism_cli::commands::Commands; use reth_optimism_node::args::RollupArgs; diff --git a/crates/builder/op-rbuilder/src/args/playground.rs b/crates/builder/op-rbuilder/src/args/playground.rs index b836f45b..c4cf2470 100644 --- a/crates/builder/op-rbuilder/src/args/playground.rs +++ b/crates/builder/op-rbuilder/src/args/playground.rs @@ -28,13 +28,13 @@ //! configurations. use alloy_primitives::hex; -use clap::{parser::ValueSource, CommandFactory}; +use clap::{CommandFactory, parser::ValueSource}; use core::{ net::{IpAddr, Ipv4Addr, SocketAddr}, ops::Range, time::Duration, }; -use eyre::{eyre, Result}; +use eyre::{Result, eyre}; use reth_cli::chainspec::ChainSpecParser; use reth_network_peers::TrustedPeer; use reth_optimism_chainspec::OpChainSpec; diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index cfa06136..4fac34c8 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -1,7 +1,7 @@ use alloy_consensus::{ - conditional::BlockConditionalAttributes, Eip658Value, Transaction, TxEip1559, + Eip658Value, Transaction, TxEip1559, conditional::BlockConditionalAttributes, }; -use alloy_eips::{eip7623::TOTAL_COST_FLOOR_PER_TOKEN, Encodable2718, Typed2718}; +use alloy_eips::{Encodable2718, Typed2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{Address, Bytes, TxKind, U256}; use alloy_rpc_types_eth::Withdrawals; @@ -12,7 +12,7 @@ use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::PayloadConfig; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_evm::{ - eth::receipt_builder::ReceiptBuilderCtx, ConfigureEvm, Evm, EvmEnv, EvmError, InvalidTxError, + ConfigureEvm, Evm, EvmEnv, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, }; use reth_node_api::PayloadBuilderError; use reth_optimism_chainspec::OpChainSpec; @@ -24,16 +24,16 @@ use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; use reth_optimism_txpool::{ conditional::MaybeConditionalTransaction, estimated_da_size::DataAvailabilitySized, - interop::{is_valid_interop, MaybeInteropTransaction}, + interop::{MaybeInteropTransaction, is_valid_interop}, }; use reth_payload_builder::PayloadId; use reth_primitives::{Recovered, SealedHeader}; use reth_primitives_traits::{InMemorySize, SignedTransaction}; use reth_provider::ProviderError; -use reth_revm::{context::Block, State}; +use reth_revm::{State, context::Block}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; use revm::{ - context::result::ResultAndState, interpreter::as_u64_saturated, Database, DatabaseCommit, + Database, DatabaseCommit, context::result::ResultAndState, interpreter::as_u64_saturated, }; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; @@ -397,24 +397,24 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; - if let Some(conditional) = conditional { - // TODO: ideally we should get this from the txpool stream - if !conditional.matches_block_attributes(&block_attr) { - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } + // TODO: ideally we should get this from the txpool stream + if let Some(conditional) = conditional + && !conditional.matches_block_attributes(&block_attr) + { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; } // TODO: remove this condition and feature once we are comfortable enabling interop for everything if cfg!(feature = "interop") { // We skip invalid cross chain txs, they would be removed on the next block update in // the maintenance job - if let Some(interop) = interop { - if !is_valid_interop(interop, self.config.attributes.timestamp()) { - log_txn(TxnExecutionResult::InteropFailed); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } + if let Some(interop) = interop + && !is_valid_interop(interop, self.config.attributes.timestamp()) + { + log_txn(TxnExecutionResult::InteropFailed); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs index 9f224d4d..e8d73bad 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs @@ -103,7 +103,7 @@ mod tests { }; use alloy_consensus::Transaction; use reth_payload_util::{BestPayloadTransactions, PayloadTransactions}; - use reth_transaction_pool::{pool::PendingPool, CoinbaseTipOrdering, PoolTransaction}; + use reth_transaction_pool::{CoinbaseTipOrdering, PoolTransaction, pool::PendingPool}; use std::sync::Arc; #[test] diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 8f109e29..04d36627 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -1,25 +1,25 @@ use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; use crate::{ builders::{ - context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}, + BuilderConfig, BuilderTx, + context::{OpPayloadBuilderCtx, estimate_gas_for_builder_tx}, flashblocks::{best_txs::BestFlashblocksTxs, config::FlashBlocksConfigExt}, generator::{BlockCell, BuildArguments}, - BuilderConfig, BuilderTx, }, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::{ClientBounds, PoolBounds}, }; use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, EMPTY_OMMER_ROOT_HASH, + BlockBody, EMPTY_OMMER_ROOT_HASH, Header, constants::EMPTY_WITHDRAWALS, proofs, }; -use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE, Encodable2718}; -use alloy_primitives::{map::foldhash::HashMap, Address, B256, U256}; +use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; +use alloy_primitives::{Address, B256, U256, map::foldhash::HashMap}; use core::time::Duration; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::BuildOutcome; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; -use reth_evm::{execute::BlockBuilder, ConfigureEvm}; +use reth_evm::{ConfigureEvm, execute::BlockBuilder}; use reth_node_api::{Block, NodePrimitives, PayloadBuilderError}; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; @@ -34,7 +34,7 @@ use reth_provider::{ StorageRootProvider, }; use reth_revm::{ - database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State, + State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, }; use revm::Database; use rollup_boost::{ @@ -48,7 +48,7 @@ use std::{ }; use tokio::sync::{ mpsc, - mpsc::{error::SendError, Sender}, + mpsc::{Sender, error::SendError}, }; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, metadata::Level, span, warn}; @@ -362,7 +362,9 @@ where if let Some(da_limit) = da_per_batch { // We error if we can't insert any tx aside from builder tx in flashblock if da_limit / 2 < builder_tx_da_size { - error!("Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included."); + error!( + "Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included." + ); } } let mut total_da_per_batch = da_per_batch; @@ -568,7 +570,9 @@ where if let Some(da) = total_da_per_batch.as_mut() { *da += da_limit; } else { - error!("Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set"); + error!( + "Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set" + ); } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index 412c3cab..ceea02f1 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -1,15 +1,15 @@ -use super::{payload::OpPayloadBuilder, FlashblocksConfig}; +use super::{FlashblocksConfig, payload::OpPayloadBuilder}; use crate::{ builders::{ - builder_tx::StandardBuilderTx, generator::BlockPayloadJobGenerator, BuilderConfig, - BuilderTx, + BuilderConfig, BuilderTx, builder_tx::StandardBuilderTx, + generator::BlockPayloadJobGenerator, }, flashtestations::service::spawn_flashtestations_service, traits::{NodeBounds, PoolBounds}, }; use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; use reth_node_api::NodeTypes; -use reth_node_builder::{components::PayloadServiceBuilder, BuilderContext}; +use reth_node_builder::{BuilderContext, components::PayloadServiceBuilder}; use reth_optimism_evm::OpEvmConfig; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_provider::CanonStateSubscriptions; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index aa34ecd7..cf8c485c 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -12,14 +12,13 @@ use std::{io, net::TcpListener, sync::Arc}; use tokio::{ net::TcpStream, sync::{ - broadcast::{self, error::RecvError, Receiver}, + broadcast::{self, Receiver, error::RecvError}, watch, }, }; use tokio_tungstenite::{ - accept_async, + WebSocketStream, accept_async, tungstenite::{Message, Utf8Bytes}, - WebSocketStream, }; use tracing::{debug, warn}; diff --git a/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs index 8b62d2c5..c7793083 100644 --- a/crates/builder/op-rbuilder/src/builders/generator.rs +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -20,7 +20,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; use tokio::{ - sync::{oneshot, Notify}, + sync::{Notify, oneshot}, time::{Duration, Sleep}, }; use tokio_util::sync::CancellationToken; @@ -471,14 +471,14 @@ mod tests { use reth::tasks::TokioTaskExecutor; use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_node_api::NodePrimitives; - use reth_optimism_payload_builder::{payload::OpPayloadBuilderAttributes, OpPayloadPrimitives}; + use reth_optimism_payload_builder::{OpPayloadPrimitives, payload::OpPayloadBuilderAttributes}; use reth_optimism_primitives::OpPrimitives; use reth_primitives::SealedBlock; use reth_provider::test_utils::MockEthProvider; - use reth_testing_utils::generators::{random_block_range, BlockRangeParams}; + use reth_testing_utils::generators::{BlockRangeParams, random_block_range}; use tokio::{ task, - time::{sleep, Duration}, + time::{Duration, sleep}, }; #[tokio::test] diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 06ed5c70..295302ec 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -1,21 +1,21 @@ use crate::{ - builders::{generator::BuildArguments, BuilderConfig}, + builders::{BuilderConfig, generator::BuildArguments}, flashtestations::service::spawn_flashtestations_service, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, }; use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, proofs, BlockBody, Header, EMPTY_OMMER_ROOT_HASH, + BlockBody, EMPTY_OMMER_ROOT_HASH, Header, constants::EMPTY_WITHDRAWALS, proofs, }; use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_primitives::U256; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; -use reth_evm::{execute::BlockBuilder, ConfigureEvm}; +use reth_evm::{ConfigureEvm, execute::BlockBuilder}; use reth_node_api::{Block, PayloadBuilderError}; -use reth_node_builder::{components::PayloadBuilderBuilder, BuilderContext}; +use reth_node_builder::{BuilderContext, components::PayloadBuilderBuilder}; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; @@ -28,7 +28,7 @@ use reth_provider::{ StorageRootProvider, }; use reth_revm::{ - database::StateProviderDatabase, db::states::bundle_state::BundleRetention, State, + State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, }; use reth_transaction_pool::{ BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool, @@ -38,7 +38,7 @@ use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; -use super::super::context::{estimate_gas_for_builder_tx, OpPayloadBuilderCtx}; +use super::super::context::{OpPayloadBuilderCtx, estimate_gas_for_builder_tx}; pub struct StandardPayloadBuilderBuilder(pub BuilderConfig<()>); diff --git a/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs index 4af83427..cc4f3646 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/args.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/args.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{utils::parse_ether, Address, U256}; +use alloy_primitives::{Address, U256, utils::parse_ether}; use crate::tx_signer::Signer; diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 650bce03..411ef4bf 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -9,12 +9,12 @@ use tracing::{info, warn}; use crate::{ builders::BuilderTx, traits::NodeBounds, - tx_signer::{generate_ethereum_keypair, Signer}, + tx_signer::{Signer, generate_ethereum_keypair}, }; use super::{ args::FlashtestationsArgs, - attestation::{get_attestation_provider, AttestationConfig, AttestationProvider}, + attestation::{AttestationConfig, AttestationProvider, get_attestation_provider}, tx_manager::TxManager, }; diff --git a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs index d6c6345c..9eabdd28 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs @@ -1,7 +1,7 @@ use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_network::ReceiptResponse; -use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, keccak256}; use alloy_rpc_types_eth::TransactionRequest; use alloy_transport::TransportResult; use op_alloy_consensus::OpTypedTransaction; @@ -12,7 +12,7 @@ use std::time::Duration; use alloy_provider::{PendingTransactionBuilder, Provider, ProviderBuilder}; use alloy_signer_local::PrivateKeySigner; -use alloy_sol_types::{sol, SolCall, SolValue}; +use alloy_sol_types::{SolCall, SolValue, sol}; use op_alloy_network::Optimism; use tracing::{debug, error, info}; diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index 7379c91d..08a541b6 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -4,7 +4,7 @@ use reth_optimism_rpc::OpEthApiBuilder; use crate::{ args::*, builders::{BuilderConfig, BuilderMode, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, - metrics::{record_flag_gauge_metrics, VERSION}, + metrics::{VERSION, record_flag_gauge_metrics}, monitor_tx_pool::monitor_tx_pool, primitives::reth::engine_api_builder::OpEngineApiBuilder, revert_protection::{EthApiExtServer, RevertProtectionExt}, @@ -18,8 +18,8 @@ use reth_db::mdbx::DatabaseEnv; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_cli::chainspec::OpChainSpecParser; use reth_optimism_node::{ - node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, OpNode, + node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, }; use reth_transaction_pool::TransactionPool; use std::{marker::PhantomData, sync::Arc}; diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index e57269dd..79658f76 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -1,7 +1,7 @@ use metrics::IntoF64; use reth_metrics::{ - metrics::{gauge, Counter, Gauge, Histogram}, Metrics, + metrics::{Counter, Gauge, Histogram, gauge}, }; use crate::args::OpRbuilderArgs; diff --git a/crates/builder/op-rbuilder/src/mock_tx.rs b/crates/builder/op-rbuilder/src/mock_tx.rs index 3cfd340e..3802fa9f 100644 --- a/crates/builder/op-rbuilder/src/mock_tx.rs +++ b/crates/builder/op-rbuilder/src/mock_tx.rs @@ -1,23 +1,23 @@ use crate::tx::MaybeFlashblockFilter; use alloy_consensus::{ - error::ValueError, transaction::Recovered, EthereumTxEnvelope, TxEip4844, TxEip4844WithSidecar, - TxType, + EthereumTxEnvelope, TxEip4844, TxEip4844WithSidecar, TxType, error::ValueError, + transaction::Recovered, }; use alloy_eips::{ + Typed2718, eip2930::AccessList, - eip4844::{env_settings::KzgSettings, BlobTransactionValidationError}, + eip4844::{BlobTransactionValidationError, env_settings::KzgSettings}, eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization, - Typed2718, }; -use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256}; use reth::primitives::TransactionSigned; use reth_primitives_traits::{InMemorySize, SignedTransaction}; use reth_transaction_pool::{ - identifier::TransactionId, - test_utils::{MockTransaction, MockTransactionFactory}, EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction, TransactionOrigin, ValidPoolTransaction, + identifier::TransactionId, + test_utils::{MockTransaction, MockTransactionFactory}, }; use std::{sync::Arc, time::Instant}; diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index d1a4fa7a..3c415ad5 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{Bytes, B256}; +use alloy_primitives::{B256, Bytes}; use alloy_rpc_types_eth::erc4337::TransactionConditional; use reth_rpc_eth_types::EthApiError; use serde::{Deserialize, Serialize}; diff --git a/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs b/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs index c9a31816..aaa733ca 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/engine_api_builder.rs @@ -2,22 +2,22 @@ use reth_node_api::AddOnsContext; use reth_node_builder::rpc::{EngineApiBuilder, PayloadValidatorBuilder}; -use reth_node_core::version::{version_metadata, CLIENT_CODE}; +use reth_node_core::version::{CLIENT_CODE, version_metadata}; use reth_optimism_node::OpEngineTypes; -use reth_optimism_rpc::engine::OP_ENGINE_CAPABILITIES; pub use reth_optimism_rpc::OpEngineApi; +use reth_optimism_rpc::engine::OP_ENGINE_CAPABILITIES; use reth_payload_builder::PayloadStore; use reth_rpc_engine_api::EngineCapabilities; use crate::traits::NodeComponents; use alloy_eips::eip7685::Requests; -use alloy_primitives::{BlockHash, B256, U64}; +use alloy_primitives::{B256, BlockHash, U64}; use alloy_rpc_types_engine::{ ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus, }; use jsonrpsee::proc_macros::rpc; -use jsonrpsee_core::{server::RpcModule, RpcResult}; +use jsonrpsee_core::{RpcResult, server::RpcModule}; use op_alloy_rpc_types_engine::{ OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4, OpPayloadAttributes, ProtocolVersion, SuperchainSignal, diff --git a/crates/builder/op-rbuilder/src/primitives/telemetry.rs b/crates/builder/op-rbuilder/src/primitives/telemetry.rs index f663156b..7e73ef2a 100644 --- a/crates/builder/op-rbuilder/src/primitives/telemetry.rs +++ b/crates/builder/op-rbuilder/src/primitives/telemetry.rs @@ -1,5 +1,5 @@ use crate::args::TelemetryArgs; -use tracing_subscriber::{filter::Targets, Layer}; +use tracing_subscriber::{Layer, filter::Targets}; /// Setup telemetry layer with sampling and custom endpoint configuration pub fn setup_telemetry_layer( @@ -9,10 +9,10 @@ pub fn setup_telemetry_layer( // Otlp uses evn vars inside if let Some(endpoint) = &args.otlp_endpoint { - std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint); + unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint) }; } if let Some(headers) = &args.otlp_headers { - std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", headers); + unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", headers) }; } // Create OTLP layer with custom configuration diff --git a/crates/builder/op-rbuilder/src/revert_protection.rs b/crates/builder/op-rbuilder/src/revert_protection.rs index bbbc1c61..f3fa8924 100644 --- a/crates/builder/op-rbuilder/src/revert_protection.rs +++ b/crates/builder/op-rbuilder/src/revert_protection.rs @@ -8,14 +8,14 @@ use crate::{ use alloy_json_rpc::RpcObject; use alloy_primitives::B256; use jsonrpsee::{ - core::{async_trait, RpcResult}, + core::{RpcResult, async_trait}, proc_macros::rpc, }; use moka::future::Cache; -use reth::rpc::api::eth::{helpers::FullEthApi, RpcReceipt}; -use reth_optimism_txpool::{conditional::MaybeConditionalTransaction, OpPooledTransaction}; +use reth::rpc::api::eth::{RpcReceipt, helpers::FullEthApi}; +use reth_optimism_txpool::{OpPooledTransaction, conditional::MaybeConditionalTransaction}; use reth_provider::StateProviderFactory; -use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; +use reth_rpc_eth_types::{EthApiError, utils::recover_raw_transaction}; use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool}; use tracing::error; @@ -94,19 +94,16 @@ where &self, hash: B256, ) -> RpcResult>> { - match self.eth_api.transaction_receipt(hash).await.unwrap() { - Some(receipt) => Ok(Some(receipt)), - None => { - // Try to find the transaction in the reverted cache - if self.reverted_cache.get(&hash).await.is_some() { - return Err(EthApiError::InvalidParams( - "the transaction was dropped from the pool".into(), - ) - .into()); - } else { - return Ok(None); - } - } + if let Some(receipt) = self.eth_api.transaction_receipt(hash).await.unwrap() { + Ok(Some(receipt)) + } else if self.reverted_cache.get(&hash).await.is_some() { + // Found the transaction in the reverted cache + Err( + EthApiError::InvalidParams("the transaction was dropped from the pool".into()) + .into(), + ) + } else { + Ok(None) } } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs index 645fd8b7..6ad0b98a 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -1,11 +1,11 @@ use super::DEFAULT_JWT_TOKEN; -use alloy_eips::{eip7685::Requests, BlockNumberOrTag}; +use alloy_eips::{BlockNumberOrTag, eip7685::Requests}; use alloy_primitives::B256; use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadStatus}; use core::{future::Future, marker::PhantomData}; use jsonrpsee::{ - core::{client::SubscriptionClientT, RpcResult}, + core::{RpcResult, client::SubscriptionClientT}, proc_macros::rpc, }; use op_alloy_rpc_types_engine::OpExecutionPayloadV4; @@ -74,7 +74,7 @@ pub struct EngineApi { } impl EngineApi

{ - async fn client(&self) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static { + async fn client(&self) -> impl SubscriptionClientT + Send + Sync + Unpin + 'static + use

{ P::client(self.jwt_secret, self.address.clone()).await } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/driver.rs b/crates/builder/op-rbuilder/src/tests/framework/driver.rs index 54e6833b..5f273c1c 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/driver.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/driver.rs @@ -1,7 +1,7 @@ use core::time::Duration; -use alloy_eips::{eip7685::Requests, BlockNumberOrTag, Encodable2718}; -use alloy_primitives::{address, hex, Bytes, TxKind, B256, U256}; +use alloy_eips::{BlockNumberOrTag, Encodable2718, eip7685::Requests}; +use alloy_primitives::{B256, Bytes, TxKind, U256, address, hex}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadAttributes, PayloadStatusEnum}; use alloy_rpc_types_eth::Block; diff --git a/crates/builder/op-rbuilder/src/tests/framework/external.rs b/crates/builder/op-rbuilder/src/tests/framework/external.rs index d8feb792..8bb99eda 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/external.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/external.rs @@ -1,6 +1,6 @@ use alloy_consensus::constants::EMPTY_WITHDRAWALS; -use alloy_eips::{eip7685::Requests, BlockNumberOrTag, Encodable2718}; -use alloy_primitives::{keccak256, private::alloy_rlp::Encodable, B256, U256}; +use alloy_eips::{BlockNumberOrTag, Encodable2718, eip7685::Requests}; +use alloy_primitives::{B256, U256, keccak256, private::alloy_rlp::Encodable}; use alloy_provider::{Identity, Provider, ProviderBuilder, RootProvider}; use alloy_rpc_types_engine::{ ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadStatusEnum, @@ -10,6 +10,7 @@ use op_alloy_network::Optimism; use op_alloy_rpc_types_engine::OpExecutionPayloadV4; use std::path::{Path, PathBuf}; use testcontainers::bollard::{ + Docker, container::{ AttachContainerOptions, Config, CreateContainerOptions, RemoveContainerOptions, StartContainerOptions, StopContainerOptions, @@ -17,7 +18,6 @@ use testcontainers::bollard::{ exec::{CreateExecOptions, StartExecResults}, image::CreateImageOptions, secret::{ContainerCreateResponse, HostConfig}, - Docker, }; use tokio::signal; use tracing::{debug, warn}; @@ -353,7 +353,7 @@ async fn relax_permissions(docker: &Docker, container: &str, path: &str) -> eyre return Err(eyre::eyre!( "Failed to relax permissions for {path}: {}", String::from_utf8_lossy(&message) - )) + )); } _ => continue, }; diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 2b2b486f..45bf5e62 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -4,9 +4,8 @@ use crate::{ primitives::reth::engine_api_builder::OpEngineApiBuilder, revert_protection::{EthApiExtServer, RevertProtectionExt}, tests::{ - create_test_db, - framework::{driver::ChainDriver, BUILDER_PRIVATE_KEY}, - EngineApi, Ipc, TransactionPoolObserver, + EngineApi, Ipc, TransactionPoolObserver, create_test_db, + framework::{BUILDER_PRIVATE_KEY, driver::ChainDriver}, }, tx::FBPooledTransaction, tx_signer::Signer, @@ -34,8 +33,8 @@ use reth_node_builder::{NodeBuilder, NodeConfig}; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_cli::commands::Commands; use reth_optimism_node::{ - node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, OpNode, + node::{OpAddOns, OpAddOnsBuilder, OpEngineValidatorBuilder, OpPoolBuilder}, }; use reth_optimism_rpc::OpEthApiBuilder; use reth_transaction_pool::{AllTransactionsEvents, TransactionPool}; diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index 7b49acde..24a1748d 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -4,8 +4,8 @@ use crate::{ tx_signer::Signer, }; use alloy_consensus::TxEip1559; -use alloy_eips::{eip2718::Encodable2718, BlockNumberOrTag}; -use alloy_primitives::{hex, Address, Bytes, TxHash, TxKind, B256, U256}; +use alloy_eips::{BlockNumberOrTag, eip2718::Encodable2718}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, hex}; use alloy_provider::{PendingTransactionBuilder, Provider, RootProvider}; use core::cmp::max; use dashmap::DashMap; @@ -134,15 +134,14 @@ impl TransactionBuilder { } pub async fn build(mut self) -> Recovered { - let signer = match self.signer { - Some(signer) => signer, - None => Signer::try_from_secret( + let signer = self.signer.unwrap_or_else(|| { + Signer::try_from_secret( FUNDED_PRIVATE_KEYS[self.key.unwrap_or(0) as usize] .parse() .expect("invalid hardcoded builder private key"), ) - .expect("Failed to create signer from hardcoded private key"), - }; + .expect("Failed to create signer from hardcoded private key") + }); let nonce = match self.nonce { Some(nonce) => nonce, diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs index a667c217..705a8d98 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -1,24 +1,23 @@ use crate::{ - tests::{framework::driver::ChainDriver, Protocol, ONE_ETH}, + tests::{ONE_ETH, Protocol, framework::driver::ChainDriver}, tx_signer::Signer, }; use alloy_eips::Encodable2718; -use alloy_primitives::{hex, Address, BlockHash, TxHash, TxKind, B256, U256}; +use alloy_primitives::{Address, B256, BlockHash, TxHash, TxKind, U256, hex}; use alloy_rpc_types_eth::{Block, BlockTransactionHashes}; use core::future::Future; use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; use op_alloy_rpc_types::Transaction; use reth_db::{ - init_db, - mdbx::{DatabaseArguments, MaxReadTransactionDuration, KILOBYTE, MEGABYTE}, - test_utils::{TempDatabase, ERROR_DB_CREATION}, - ClientVersion, DatabaseEnv, + ClientVersion, DatabaseEnv, init_db, + mdbx::{DatabaseArguments, KILOBYTE, MEGABYTE, MaxReadTransactionDuration}, + test_utils::{ERROR_DB_CREATION, TempDatabase}, }; use reth_node_core::{args::DatadirArgs, dirs::DataDirPath, node_config::NodeConfig}; use reth_optimism_chainspec::OpChainSpec; use std::sync::Arc; -use super::{TransactionBuilder, FUNDED_PRIVATE_KEYS}; +use super::{FUNDED_PRIVATE_KEYS, TransactionBuilder}; pub trait TransactionBuilderExt { fn random_valid_transfer(self) -> Self; @@ -50,7 +49,7 @@ pub trait ChainDriverExt { amount: u128, ) -> impl Future>; fn fund(&self, address: Address, amount: u128) - -> impl Future>; + -> impl Future>; fn first_funded_address(&self) -> Address { FUNDED_PRIVATE_KEYS[0] .parse() diff --git a/crates/builder/op-rbuilder/src/tests/ordering.rs b/crates/builder/op-rbuilder/src/tests/ordering.rs index 11cc496c..bba31b0e 100644 --- a/crates/builder/op-rbuilder/src/tests/ordering.rs +++ b/crates/builder/op-rbuilder/src/tests/ordering.rs @@ -1,6 +1,6 @@ -use crate::tests::{framework::ONE_ETH, ChainDriverExt, LocalInstance}; +use crate::tests::{ChainDriverExt, LocalInstance, framework::ONE_ETH}; use alloy_consensus::Transaction; -use futures::{future::join_all, stream, StreamExt}; +use futures::{StreamExt, future::join_all, stream}; use macros::rb_test; /// This test ensures that the transactions are ordered by fee priority in the block. diff --git a/crates/builder/op-rbuilder/src/tests/revert.rs b/crates/builder/op-rbuilder/src/tests/revert.rs index befce2c3..8037f996 100644 --- a/crates/builder/op-rbuilder/src/tests/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/revert.rs @@ -6,8 +6,8 @@ use crate::{ args::OpRbuilderArgs, primitives::bundle::MAX_BLOCK_RANGE_BLOCKS, tests::{ - BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, LocalInstance, - OpRbuilderArgsTestExt, TransactionBuilderExt, ONE_ETH, + BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, LocalInstance, ONE_ETH, + OpRbuilderArgsTestExt, TransactionBuilderExt, }, }; @@ -134,10 +134,12 @@ async fn bundle(rbuilder: LocalInstance) -> eyre::Result<()> { .await?; let block2 = driver.build_new_block().await?; // Block 2 - assert!(block2 - .transactions - .hashes() - .includes(valid_bundle.tx_hash())); + assert!( + block2 + .transactions + .hashes() + .includes(valid_bundle.tx_hash()) + ); let bundle_opts = BundleOpts { block_number_max: Some(4), @@ -285,13 +287,15 @@ async fn bundle_range_limits(rbuilder: LocalInstance) -> eyre::Result<()> { } // A bundle with a block out of range is invalid - assert!(send_bundle( - &driver, - Some(next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1), - None - ) - .await - .is_err()); + assert!( + send_bundle( + &driver, + Some(next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1), + None + ) + .await + .is_err() + ); // A bundle with a min block number higher than the max block is invalid assert!(send_bundle(&driver, Some(1), Some(2)).await.is_err()); @@ -302,9 +306,11 @@ async fn bundle_range_limits(rbuilder: LocalInstance) -> eyre::Result<()> { .await .is_ok() ); - assert!(send_bundle(&driver, Some(next_valid_block), Some(0)) - .await - .is_ok()); + assert!( + send_bundle(&driver, Some(next_valid_block), Some(0)) + .await + .is_ok() + ); // A bundle with a min block equal to max block is valid assert!( @@ -316,18 +322,24 @@ async fn bundle_range_limits(rbuilder: LocalInstance) -> eyre::Result<()> { // Test min-only cases (no max specified) // A bundle with only min block that's within the default range is valid let default_max = current_block + MAX_BLOCK_RANGE_BLOCKS; - assert!(send_bundle(&driver, None, Some(current_block)) - .await - .is_ok()); - assert!(send_bundle(&driver, None, Some(default_max - 1)) - .await - .is_ok()); + assert!( + send_bundle(&driver, None, Some(current_block)) + .await + .is_ok() + ); + assert!( + send_bundle(&driver, None, Some(default_max - 1)) + .await + .is_ok() + ); assert!(send_bundle(&driver, None, Some(default_max)).await.is_ok()); // A bundle with only min block that exceeds the default max range is invalid - assert!(send_bundle(&driver, None, Some(default_max + 1)) - .await - .is_err()); + assert!( + send_bundle(&driver, None, Some(default_max + 1)) + .await + .is_err() + ); Ok(()) } diff --git a/crates/builder/op-rbuilder/src/tests/txpool.rs b/crates/builder/op-rbuilder/src/tests/txpool.rs index 41f9b736..235ccd26 100644 --- a/crates/builder/op-rbuilder/src/tests/txpool.rs +++ b/crates/builder/op-rbuilder/src/tests/txpool.rs @@ -1,5 +1,5 @@ use crate::tests::{ - default_node_config, BlockTransactionsExt, ChainDriverExt, LocalInstance, ONE_ETH, + BlockTransactionsExt, ChainDriverExt, LocalInstance, ONE_ETH, default_node_config, }; use macros::rb_test; use reth::args::TxPoolArgs; diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index 096d375a..7626fff1 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -1,16 +1,17 @@ -use alloy_consensus::{conditional::BlockConditionalAttributes, BlobTransactionValidationError}; -use alloy_eips::{eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization, Typed2718}; -use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, U256}; -use alloy_rpc_types_eth::{erc4337::TransactionConditional, AccessList}; +use std::{borrow::Cow, sync::Arc}; + +use alloy_consensus::{BlobTransactionValidationError, conditional::BlockConditionalAttributes}; +use alloy_eips::{Typed2718, eip7594::BlobTransactionSidecarVariant, eip7702::SignedAuthorization}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256}; +use alloy_rpc_types_eth::{AccessList, erc4337::TransactionConditional}; use reth_optimism_primitives::OpTransactionSigned; use reth_optimism_txpool::{ - conditional::MaybeConditionalTransaction, estimated_da_size::DataAvailabilitySized, - interop::MaybeInteropTransaction, OpPooledTransaction, OpPooledTx, + OpPooledTransaction, OpPooledTx, conditional::MaybeConditionalTransaction, + estimated_da_size::DataAvailabilitySized, interop::MaybeInteropTransaction, }; -use reth_primitives::{kzg::KzgSettings, Recovered}; +use reth_primitives::{Recovered, kzg::KzgSettings}; use reth_primitives_traits::InMemorySize; use reth_transaction_pool::{EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction}; -use std::{borrow::Cow, sync::Arc}; pub trait FBPoolTransaction: MaybeRevertingTransaction + OpPooledTx + MaybeFlashblockFilter diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs index f10e5d41..71ef7a4b 100644 --- a/crates/builder/op-rbuilder/src/tx_signer.rs +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -1,11 +1,11 @@ use std::str::FromStr; use alloy_consensus::SignableTransaction; -use alloy_primitives::{Address, Signature, B256, U256}; +use alloy_primitives::{Address, B256, Signature, U256}; use op_alloy_consensus::OpTypedTransaction; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; -use secp256k1::{rand::rngs::OsRng, Message, PublicKey, Secp256k1, SecretKey, SECP256K1}; +use secp256k1::{Message, PublicKey, SECP256K1, Secp256k1, SecretKey, rand::rngs::OsRng}; use sha3::{Digest, Keccak256}; /// Simple struct to sign txs/messages. @@ -103,8 +103,8 @@ pub fn public_key_to_address(public_key: &PublicKey) -> Address { #[cfg(test)] mod test { use super::*; - use alloy_consensus::{transaction::SignerRecoverable, TxEip1559}; - use alloy_primitives::{address, fixed_bytes, TxKind as TransactionKind}; + use alloy_consensus::{TxEip1559, transaction::SignerRecoverable}; + use alloy_primitives::{TxKind as TransactionKind, address, fixed_bytes}; #[test] fn test_sign_transaction() { let secret = diff --git a/crates/builder/tdx-quote-provider/src/main.rs b/crates/builder/tdx-quote-provider/src/main.rs index ecadead0..a820ce97 100644 --- a/crates/builder/tdx-quote-provider/src/main.rs +++ b/crates/builder/tdx-quote-provider/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use dotenvy::dotenv; use metrics_exporter_prometheus::PrometheusBuilder; -use tracing::{info, Level}; +use tracing::{Level, info}; use tracing_subscriber::filter::EnvFilter; use crate::server::{Server, ServerConfig}; diff --git a/crates/builder/tdx-quote-provider/src/provider.rs b/crates/builder/tdx-quote-provider/src/provider.rs index fe6d21c9..0479edf8 100644 --- a/crates/builder/tdx-quote-provider/src/provider.rs +++ b/crates/builder/tdx-quote-provider/src/provider.rs @@ -1,5 +1,5 @@ use std::{fs::File, io::Read, sync::Arc}; -use tdx::{device::DeviceOptions, error::TdxError, Tdx}; +use tdx::{Tdx, device::DeviceOptions, error::TdxError}; use thiserror::Error; use tracing::info; diff --git a/crates/builder/tdx-quote-provider/src/server.rs b/crates/builder/tdx-quote-provider/src/server.rs index d355727a..b8d5e295 100644 --- a/crates/builder/tdx-quote-provider/src/server.rs +++ b/crates/builder/tdx-quote-provider/src/server.rs @@ -1,12 +1,12 @@ use std::{net::SocketAddr, sync::Arc, time::Instant}; use axum::{ + Router, body::Body, extract::{Path, State}, http::{Response, StatusCode}, response::IntoResponse, routing::get, - Router, }; use serde_json::json; use tokio::{net::TcpListener, signal}; @@ -14,7 +14,7 @@ use tracing::info; use crate::{ metrics::Metrics, - provider::{get_attestation_provider, AttestationConfig, AttestationProvider}, + provider::{AttestationConfig, AttestationProvider, get_attestation_provider}, }; /// Server configuration From 75f028253299550ab9df248ca317035677f911d0 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Fri, 5 Sep 2025 08:11:26 -0400 Subject: [PATCH 177/262] feat: address gas limiter (#253) * cli args * core impl * metrics * use the gas limiter * don't use optional gas limiter uses an "inner" struct to handle checking if it's enabled or not * cleanup stale addresses only every 100 blocks * unit tests * integration test --- crates/builder/op-rbuilder/Cargo.toml | 4 +- crates/builder/op-rbuilder/src/args/op.rs | 7 +- .../op-rbuilder/src/builders/context.rs | 13 + .../src/builders/flashblocks/payload.rs | 8 + .../builder/op-rbuilder/src/builders/mod.rs | 8 + .../src/builders/standard/payload.rs | 8 + .../op-rbuilder/src/gas_limiter/args.rs | 28 +++ .../op-rbuilder/src/gas_limiter/error.rs | 13 + .../op-rbuilder/src/gas_limiter/metrics.rs | 43 ++++ .../op-rbuilder/src/gas_limiter/mod.rs | 223 ++++++++++++++++++ crates/builder/op-rbuilder/src/lib.rs | 1 + .../op-rbuilder/src/tests/framework/utils.rs | 2 + .../op-rbuilder/src/tests/gas_limiter.rs | 121 ++++++++++ crates/builder/op-rbuilder/src/tests/mod.rs | 3 + 14 files changed, 478 insertions(+), 4 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/gas_limiter/args.rs create mode 100644 crates/builder/op-rbuilder/src/gas_limiter/error.rs create mode 100644 crates/builder/op-rbuilder/src/gas_limiter/metrics.rs create mode 100644 crates/builder/op-rbuilder/src/gas_limiter/mod.rs create mode 100644 crates/builder/op-rbuilder/src/tests/gas_limiter.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index a2bf891e..8c1c3b28 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -104,6 +104,7 @@ parking_lot.workspace = true url.workspace = true anyhow = "1" opentelemetry = { workspace = true, optional = true } +dashmap.workspace = true tower = "0.5" futures = "0.3" @@ -124,7 +125,6 @@ ureq = "2.10" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } -dashmap = { version = "6.1", optional = true } nanoid = { version = "0.4", optional = true } reth-ipc = { workspace = true, optional = true } tar = { version = "0.4", optional = true } @@ -146,7 +146,6 @@ alloy-provider = { workspace = true, default-features = true, features = [ ] } tempfile = "3.8" macros = { path = "src/tests/framework/macros" } -dashmap = { version = "6.1" } nanoid = { version = "0.4" } reth-ipc = { workspace = true } reth-node-builder = { workspace = true, features = ["test-utils"] } @@ -175,7 +174,6 @@ min-debug-logs = ["tracing/release_max_level_debug"] min-trace-logs = ["tracing/release_max_level_trace"] testing = [ - "dashmap", "nanoid", "reth-ipc", "reth-node-builder/test-utils", diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 9c1d1fc2..8b3bd5bd 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -4,7 +4,10 @@ //! clap [Args](clap::Args) for optimism rollup configuration -use crate::{flashtestations::args::FlashtestationsArgs, tx_signer::Signer}; +use crate::{ + flashtestations::args::FlashtestationsArgs, gas_limiter::args::GasLimiterArgs, + tx_signer::Signer, +}; use anyhow::{Result, anyhow}; use clap::Parser; use reth_optimism_cli::commands::Commands; @@ -60,6 +63,8 @@ pub struct OpRbuilderArgs { pub telemetry: TelemetryArgs, #[command(flatten)] pub flashtestations: FlashtestationsArgs, + #[command(flatten)] + pub gas_limiter: GasLimiterArgs, } impl Default for OpRbuilderArgs { diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 4fac34c8..7863d441 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -40,6 +40,7 @@ use tokio_util::sync::CancellationToken; use tracing::{debug, info, trace, warn}; use crate::{ + gas_limiter::AddressGasLimiter, metrics::OpRBuilderMetrics, primitives::reth::{ExecutionInfo, TxnExecutionResult}, traits::PayloadTxsBounds, @@ -72,6 +73,8 @@ pub struct OpPayloadBuilderCtx { pub extra_ctx: ExtraCtx, /// 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. + pub address_gas_limiter: AddressGasLimiter, } impl OpPayloadBuilderCtx { @@ -505,6 +508,16 @@ impl OpPayloadBuilderCtx { } } + if self + .address_gas_limiter + .consume_gas(tx.signer(), gas_used) + .is_err() + { + log_txn(TxnExecutionResult::MaxGasUsageExceeded); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + info.cumulative_gas_used += gas_used; // record tx da size info.cumulative_da_bytes_used += tx_da_size; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 04d36627..3110e1c6 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -6,6 +6,7 @@ use crate::{ flashblocks::{best_txs::BestFlashblocksTxs, config::FlashBlocksConfigExt}, generator::{BlockCell, BuildArguments}, }, + gas_limiter::AddressGasLimiter, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::{ClientBounds, PoolBounds}, @@ -118,6 +119,8 @@ pub struct OpPayloadBuilder { /// Builder events handle to send BuiltPayload events pub payload_builder_handle: Arc>>>, + /// Rate limiting based on gas. This is an optional feature. + pub address_gas_limiter: AddressGasLimiter, } impl OpPayloadBuilder { @@ -134,6 +137,7 @@ impl OpPayloadBuilder { ) -> eyre::Result { let metrics = Arc::new(OpRBuilderMetrics::default()); let ws_pub = WebSocketPublisher::new(config.specific.ws_addr, Arc::clone(&metrics))?.into(); + let address_gas_limiter = AddressGasLimiter::new(config.gas_limiter_config.clone()); Ok(Self { evm_config, pool, @@ -143,6 +147,7 @@ impl OpPayloadBuilder { metrics, builder_tx, payload_builder_handle, + address_gas_limiter, }) } } @@ -260,11 +265,14 @@ where target_flashblock_count: self.config.flashblocks_per_block(), }, max_gas_per_txn: self.config.max_gas_per_txn, + address_gas_limiter: self.address_gas_limiter.clone(), }; let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; let db = StateProviderDatabase::new(&state_provider); + self.address_gas_limiter.refresh(ctx.block_number()); + // 1. execute the pre steps and seal an early block with that let sequencer_tx_start_time = Instant::now(); let mut state = State::builder() diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 92dc8394..575e5bf4 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -10,6 +10,7 @@ use reth_optimism_payload_builder::config::OpDAConfig; use crate::{ args::OpRbuilderArgs, flashtestations::args::FlashtestationsArgs, + gas_limiter::args::GasLimiterArgs, traits::{NodeBounds, PoolBounds}, tx_signer::Signer, }; @@ -114,6 +115,9 @@ pub struct BuilderConfig { 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, } impl core::fmt::Debug for BuilderConfig { @@ -132,6 +136,8 @@ impl core::fmt::Debug for BuilderConfig { .field("block_time_leeway", &self.block_time_leeway) .field("da_config", &self.da_config) .field("specific", &self.specific) + .field("max_gas_per_txn", &self.max_gas_per_txn) + .field("gas_limiter_config", &self.gas_limiter_config) .finish() } } @@ -148,6 +154,7 @@ impl Default for BuilderConfig { specific: S::default(), sampling_ratio: 100, max_gas_per_txn: None, + gas_limiter_config: GasLimiterArgs::default(), } } } @@ -168,6 +175,7 @@ where da_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(), specific: S::try_from(args)?, }) } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 295302ec..c61fc32e 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -1,6 +1,7 @@ use crate::{ builders::{BuilderConfig, generator::BuildArguments}, flashtestations::service::spawn_flashtestations_service, + gas_limiter::AddressGasLimiter, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, @@ -99,6 +100,8 @@ pub struct StandardOpPayloadBuilder { 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, } impl StandardOpPayloadBuilder { @@ -109,6 +112,7 @@ impl StandardOpPayloadBuilder { client: Client, config: BuilderConfig<()>, ) -> Self { + let address_gas_limiter = AddressGasLimiter::new(config.gas_limiter_config.clone()); Self { pool, client, @@ -116,6 +120,7 @@ impl StandardOpPayloadBuilder { evm_config, best_transactions: (), metrics: Default::default(), + address_gas_limiter, } } } @@ -276,10 +281,13 @@ where 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(), }; 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(); diff --git a/crates/builder/op-rbuilder/src/gas_limiter/args.rs b/crates/builder/op-rbuilder/src/gas_limiter/args.rs new file mode 100644 index 00000000..ec7f8008 --- /dev/null +++ b/crates/builder/op-rbuilder/src/gas_limiter/args.rs @@ -0,0 +1,28 @@ +use clap::Args; + +#[derive(Debug, Clone, Default, PartialEq, Eq, Args)] +pub struct GasLimiterArgs { + /// Enable address-based gas rate limiting + #[arg(long = "gas-limiter.enabled", env)] + pub gas_limiter_enabled: bool, + + /// Maximum gas per address in token bucket. Defaults to 10 million gas. + #[arg( + long = "gas-limiter.max-gas-per-address", + env, + default_value = "10000000" + )] + pub max_gas_per_address: u64, + + /// Gas refill rate per block. Defaults to 1 million gas per block. + #[arg( + long = "gas-limiter.refill-rate-per-block", + env, + default_value = "1000000" + )] + pub refill_rate_per_block: u64, + + /// How many blocks to wait before cleaning up stale buckets for addresses. + #[arg(long = "gas-limiter.cleanup-interval", env, default_value = "100")] + pub cleanup_interval: u64, +} diff --git a/crates/builder/op-rbuilder/src/gas_limiter/error.rs b/crates/builder/op-rbuilder/src/gas_limiter/error.rs new file mode 100644 index 00000000..a85b7f77 --- /dev/null +++ b/crates/builder/op-rbuilder/src/gas_limiter/error.rs @@ -0,0 +1,13 @@ +use alloy_primitives::Address; + +#[derive(Debug, thiserror::Error)] +pub enum GasLimitError { + #[error( + "Address {address} exceeded gas limit: {requested} gwei requested, {available} gwei available" + )] + AddressLimitExceeded { + address: Address, + requested: u64, + available: u64, + }, +} diff --git a/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs b/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs new file mode 100644 index 00000000..e009b221 --- /dev/null +++ b/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs @@ -0,0 +1,43 @@ +use std::time::Duration; + +use metrics::{Counter, Gauge, Histogram}; +use reth_metrics::Metrics; + +use crate::gas_limiter::error::GasLimitError; + +#[derive(Metrics, Clone)] +#[metrics(scope = "op_rbuilder.gas_limiter")] +pub struct GasLimiterMetrics { + /// Transactions rejected by gas limits Labeled by reason: "per_address", + /// "global", "burst" + pub rejections: Counter, + + /// Time spent in rate limiting logic + pub check_time: Histogram, + + /// Number of addresses with active budgets + pub active_address_count: Gauge, + + /// Time to refill buckets + pub refresh_duration: Histogram, +} + +impl GasLimiterMetrics { + pub fn record_gas_check(&self, check_result: &Result, duration: Duration) { + if let Ok(created_new_bucket) = check_result { + if *created_new_bucket { + self.active_address_count.increment(1); + } + } else { + self.rejections.increment(1); + } + + self.check_time.record(duration); + } + + pub fn record_refresh(&self, removed_addresses: usize, duration: Duration) { + self.active_address_count + .decrement(removed_addresses as f64); + self.refresh_duration.record(duration); + } +} diff --git a/crates/builder/op-rbuilder/src/gas_limiter/mod.rs b/crates/builder/op-rbuilder/src/gas_limiter/mod.rs new file mode 100644 index 00000000..327a6749 --- /dev/null +++ b/crates/builder/op-rbuilder/src/gas_limiter/mod.rs @@ -0,0 +1,223 @@ +use std::{cmp::min, sync::Arc, time::Instant}; + +use alloy_primitives::Address; +use dashmap::DashMap; + +use crate::gas_limiter::{args::GasLimiterArgs, error::GasLimitError, metrics::GasLimiterMetrics}; + +pub mod args; +pub mod error; +mod metrics; + +#[derive(Debug, Clone)] +pub struct AddressGasLimiter { + inner: Option, +} + +#[derive(Debug, Clone)] +struct AddressGasLimiterInner { + config: GasLimiterArgs, + // We don't need an Arc> here, we can get away with RefCell, but + // the reth PayloadBuilder trait needs this to be Send + Sync + address_buckets: Arc>, + metrics: GasLimiterMetrics, +} + +#[derive(Debug, Clone)] +struct TokenBucket { + capacity: u64, + available: u64, +} + +impl AddressGasLimiter { + pub fn new(config: GasLimiterArgs) -> Self { + Self { + inner: AddressGasLimiterInner::try_new(config), + } + } + + /// Check if there's enough gas for this address and consume it. Returns + /// Ok(()) if there's enough otherwise returns an error. + pub fn consume_gas(&self, address: Address, gas_requested: u64) -> Result<(), GasLimitError> { + if let Some(inner) = &self.inner { + inner.consume_gas(address, gas_requested) + } else { + Ok(()) + } + } + + /// Should be called upon each new block. Refills buckets/Garbage collection + pub fn refresh(&self, block_number: u64) { + if let Some(inner) = self.inner.as_ref() { + inner.refresh(block_number) + } + } +} + +impl AddressGasLimiterInner { + fn try_new(config: GasLimiterArgs) -> Option { + if !config.gas_limiter_enabled { + return None; + } + + Some(Self { + config, + address_buckets: Default::default(), + metrics: Default::default(), + }) + } + + fn consume_gas_inner( + &self, + address: Address, + gas_requested: u64, + ) -> Result { + let mut created_new_bucket = false; + let mut bucket = self + .address_buckets + .entry(address) + // if we don't find a bucket we need to initialize a new one + .or_insert_with(|| { + created_new_bucket = true; + TokenBucket::new(self.config.max_gas_per_address) + }); + + if gas_requested > bucket.available { + return Err(GasLimitError::AddressLimitExceeded { + address, + requested: gas_requested, + available: bucket.available, + }); + } + + bucket.available -= gas_requested; + + Ok(created_new_bucket) + } + + fn consume_gas(&self, address: Address, gas_requested: u64) -> Result<(), GasLimitError> { + let start = Instant::now(); + let result = self.consume_gas_inner(address, gas_requested); + + self.metrics.record_gas_check(&result, start.elapsed()); + + result.map(|_| ()) + } + + fn refresh_inner(&self, block_number: u64) -> usize { + let active_addresses = self.address_buckets.len(); + + self.address_buckets.iter_mut().for_each(|mut bucket| { + bucket.available = min( + bucket.capacity, + bucket.available + self.config.refill_rate_per_block, + ) + }); + + // Only clean up stale buckets every `cleanup_interval` blocks + if block_number % self.config.cleanup_interval == 0 { + self.address_buckets + .retain(|_, bucket| bucket.available <= bucket.capacity); + } + + active_addresses - self.address_buckets.len() + } + + fn refresh(&self, block_number: u64) { + let start = Instant::now(); + let removed_addresses = self.refresh_inner(block_number); + + self.metrics + .record_refresh(removed_addresses, start.elapsed()); + } +} + +impl TokenBucket { + fn new(capacity: u64) -> Self { + Self { + capacity, + available: capacity, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::Address; + + fn create_test_config(max_gas: u64, refill_rate: u64, cleanup_interval: u64) -> GasLimiterArgs { + GasLimiterArgs { + gas_limiter_enabled: true, + max_gas_per_address: max_gas, + refill_rate_per_block: refill_rate, + cleanup_interval, + } + } + + fn test_address() -> Address { + Address::from([1u8; 20]) + } + + #[test] + fn test_basic_refill() { + let config = create_test_config(1000, 200, 10); + let limiter = AddressGasLimiter::new(config); + + // Consume all gas + assert!(limiter.consume_gas(test_address(), 1000).is_ok()); + assert!(limiter.consume_gas(test_address(), 1).is_err()); + + // Refill and check available gas increased + limiter.refresh(1); + assert!(limiter.consume_gas(test_address(), 200).is_ok()); + assert!(limiter.consume_gas(test_address(), 1).is_err()); + } + + #[test] + fn test_over_capacity_request() { + let config = create_test_config(1000, 100, 10); + let limiter = AddressGasLimiter::new(config); + + // Request more than capacity should fail + let result = limiter.consume_gas(test_address(), 1500); + assert!(result.is_err()); + + if let Err(GasLimitError::AddressLimitExceeded { available, .. }) = result { + assert_eq!(available, 1000); + } + + // Bucket should still be full after failed request + assert!(limiter.consume_gas(test_address(), 1000).is_ok()); + } + + #[test] + fn test_multiple_users() { + // Simulate more realistic scenario + let config = create_test_config(10_000_000, 1_000_000, 100); // 10M max, 1M refill + let limiter = AddressGasLimiter::new(config); + + let searcher1 = Address::from([0x1; 20]); + let searcher2 = Address::from([0x2; 20]); + let attacker = Address::from([0x3; 20]); + + // Normal searchers use reasonable amounts + assert!(limiter.consume_gas(searcher1, 500_000).is_ok()); + assert!(limiter.consume_gas(searcher2, 750_000).is_ok()); + + // Attacker tries to consume massive amounts + assert!(limiter.consume_gas(attacker, 15_000_000).is_err()); // Should fail - over capacity + assert!(limiter.consume_gas(attacker, 5_000_000).is_ok()); // Should succeed - within capacity + + // Attacker tries to consume more + assert!(limiter.consume_gas(attacker, 6_000_000).is_err()); // Should fail - would exceed remaining + + // New block - refill + limiter.refresh(1); + + // Everyone should get some gas back + assert!(limiter.consume_gas(searcher1, 1_000_000).is_ok()); // Had 9.5M + 1M refill, now 9.5M + assert!(limiter.consume_gas(searcher2, 1_000_000).is_ok()); // Had 9.25M + 1M refill, now 9.25M + assert!(limiter.consume_gas(attacker, 1_000_000).is_ok()); // Had 5M + 1M refill, now 5M + } +} diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index 9449e98c..7817ba2d 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,6 +1,7 @@ pub mod args; pub mod builders; pub mod flashtestations; +pub mod gas_limiter; pub mod launcher; pub mod metrics; mod monitor_tx_pool; diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs index 705a8d98..2cb5f51b 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -34,6 +34,8 @@ impl TransactionBuilderExt for TransactionBuilder { self.with_create().with_input(hex!("60006000fd").into()) // PUSH1 0x00 PUSH1 0x00 REVERT } + // This transaction is big in the sense that it uses a lot of gas. The exact + // amount it uses is 86220 gas. fn random_big_transaction(self) -> Self { // PUSH13 0x63ffffffff60005260046000f3 PUSH1 0x00 MSTORE PUSH1 0x02 PUSH1 0x0d PUSH1 0x13 PUSH1 0x00 CREATE2 self.with_create() diff --git a/crates/builder/op-rbuilder/src/tests/gas_limiter.rs b/crates/builder/op-rbuilder/src/tests/gas_limiter.rs new file mode 100644 index 00000000..00dfb035 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/gas_limiter.rs @@ -0,0 +1,121 @@ +use crate::{ + args::OpRbuilderArgs, + gas_limiter::args::GasLimiterArgs, + tests::{ChainDriverExt, LocalInstance, TransactionBuilderExt}, +}; +use macros::rb_test; +use std::collections::HashSet; +use tracing::info; + +/// Integration test for the gas limiter functionality. +/// Tests that gas limits are properly enforced during actual block building +/// and transaction execution. +#[rb_test(args = OpRbuilderArgs { + gas_limiter: GasLimiterArgs { + gas_limiter_enabled: true, + max_gas_per_address: 200000, // 200k gas per address - low for testing + refill_rate_per_block: 100000, // 100k gas refill per block + cleanup_interval: 100, + }, + ..Default::default() +})] +async fn gas_limiter_blocks_excessive_usage(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // Fund some accounts for testing + let funded_accounts = driver + .fund_accounts(2, 10_000_000_000_000_000_000u128) + .await?; // 10 ETH each + + // These transactions should not be throttled + let tx1 = driver + .create_transaction() + .with_signer(funded_accounts[0]) + .random_valid_transfer() + .send() + .await?; + + let tx2 = driver + .create_transaction() + .with_signer(funded_accounts[1]) + .random_valid_transfer() + .send() + .await?; + + // Build block and verify inclusion + let block = driver.build_new_block_with_current_timestamp(None).await?; + let tx_hashes: HashSet<_> = block.transactions.hashes().collect(); + + assert!(tx_hashes.contains(tx1.tx_hash()), "tx1 should be included"); + assert!(tx_hashes.contains(tx2.tx_hash()), "tx2 should be included"); + + // Send multiple big transactions from the same address - these should hit the gas limiter + let mut sent_txs = Vec::new(); + for i in 0..5 { + let big_tx = driver + .create_transaction() + .with_signer(funded_accounts[0]) + .random_big_transaction() + .send() + .await?; + sent_txs.push(*big_tx.tx_hash()); + info!( + "Sent big transaction {} from address {}", + i + 1, + funded_accounts[0].address + ); + } + + // Meanwhile, the other address should not be throttled + let legit_tx = driver + .create_transaction() + .with_signer(funded_accounts[1]) + .random_big_transaction() + .send() + .await?; + + let block = driver.build_new_block_with_current_timestamp(None).await?; + let tx_hashes: HashSet<_> = block.transactions.hashes().collect(); + + let included_count = sent_txs.iter().filter(|tx| tx_hashes.contains(*tx)).count(); + + // With gas limiting, we shouldn't get all 5 big transactions from the same + // address. We do this imprecise count because we haven't built a way of + // sending a tx that uses an exact amount of gas. + assert!( + included_count < 5, + "Gas limiter should have rejected some transactions, included: {}/5", + included_count + ); + assert!( + included_count > 0, + "Gas limiter should have allowed at least one transaction" + ); + + assert!( + tx_hashes.contains(legit_tx.tx_hash()), + "Transaction from different address should be included" + ); + + // After building new blocks, the limited address should get more capacity + for _ in 0..3 { + let _block = driver.build_new_block_with_current_timestamp(None).await?; + } + + let tx_after_refill = driver + .create_transaction() + .with_signer(funded_accounts[0]) + .random_valid_transfer() + .send() + .await?; + + let refill_block = driver.build_new_block_with_current_timestamp(None).await?; + let refill_tx_hashes: HashSet<_> = refill_block.transactions.hashes().collect(); + + assert!( + refill_tx_hashes.contains(tx_after_refill.tx_hash()), + "Transaction should succeed after refill" + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs index ecdda8bb..f32eccb6 100644 --- a/crates/builder/op-rbuilder/src/tests/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -8,6 +8,9 @@ mod flashblocks; #[cfg(test)] mod data_availability; +#[cfg(test)] +mod gas_limiter; + #[cfg(test)] mod ordering; From 2b3f5a7fbfbc327d61103c2826903b14444a60c7 Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:57:40 +0200 Subject: [PATCH 178/262] feat: add commit message and author in version metrics (#236) --- crates/builder/op-rbuilder/build.rs | 23 ++++++++++++++++++++--- crates/builder/op-rbuilder/src/metrics.rs | 18 ++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/crates/builder/op-rbuilder/build.rs b/crates/builder/op-rbuilder/build.rs index 3e8c774f..205e8fa7 100644 --- a/crates/builder/op-rbuilder/build.rs +++ b/crates/builder/op-rbuilder/build.rs @@ -43,6 +43,9 @@ fn main() -> Result<(), Box> { .describe(false, true, None) .dirty(true) .sha(false) + .commit_author_name(true) + .commit_author_email(true) + .commit_message(true) .build()?; emitter.add_instructions(&git_builder)?; @@ -51,6 +54,16 @@ fn main() -> Result<(), Box> { let sha = env::var("VERGEN_GIT_SHA")?; let sha_short = &sha[0..7]; + // Set short SHA + println!("cargo:rustc-env=VERGEN_GIT_SHA_SHORT={}", &sha[..8]); + + let author_name = env::var("VERGEN_GIT_COMMIT_AUTHOR_NAME")?; + let author_email = env::var("VERGEN_GIT_COMMIT_AUTHOR_EMAIL")?; + let author_full = format!("{} <{}>", author_name, author_email); + + // Set author full name + println!("cargo:rustc-env=VERGEN_GIT_COMMIT_AUTHOR={}", author_full); + let is_dirty = env::var("VERGEN_GIT_DIRTY")? == "true"; // > git describe --always --tags // if not on a tag: v0.2.0-beta.3-82-g1939939b @@ -59,9 +72,6 @@ fn main() -> Result<(), Box> { let version_suffix = if is_dirty || not_on_tag { "-dev" } else { "" }; println!("cargo:rustc-env=OP_RBUILDER_VERSION_SUFFIX={version_suffix}"); - // Set short SHA - println!("cargo:rustc-env=VERGEN_GIT_SHA_SHORT={}", &sha[..8]); - // Set the build profile let out_dir = env::var("OUT_DIR").unwrap(); let profile = out_dir.rsplit(std::path::MAIN_SEPARATOR).nth(3).unwrap(); @@ -86,6 +96,7 @@ fn main() -> Result<(), Box> { // - The build datetime // - The build features // - The build profile + // - The latest commit message and author // // Example: // @@ -95,6 +106,7 @@ fn main() -> Result<(), Box> { // Build Timestamp: 2023-05-19T01:47:19.815651705Z // Build Features: jemalloc // Build Profile: maxperf + // Latest Commit: 'message' by John Doe // ``` println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_0=Version: {pkg_version}{version_suffix}"); println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_1=Commit SHA: {sha}"); @@ -107,6 +119,11 @@ fn main() -> Result<(), Box> { env::var("VERGEN_CARGO_FEATURES")? ); println!("cargo:rustc-env=OP_RBUILDER_LONG_VERSION_4=Build Profile: {profile}"); + println!( + "cargo:rustc-env=OP_RBUILDER_LONG_VERSION_5=Latest Commit: '{}' by {}", + env::var("VERGEN_GIT_COMMIT_MESSAGE")?.trim_end(), + author_full + ); // The version information for op-rbuilder formatted for P2P (devp2p). // - The latest version from Cargo.toml diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 79658f76..df13bd1d 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -21,6 +21,10 @@ pub const VERGEN_CARGO_TARGET_TRIPLE: &str = env!("VERGEN_CARGO_TARGET_TRIPLE"); /// The build features. pub const VERGEN_CARGO_FEATURES: &str = env!("VERGEN_CARGO_FEATURES"); +/// The latest commit message and author name and email. +pub const VERGEN_GIT_AUTHOR: &str = env!("VERGEN_GIT_COMMIT_AUTHOR"); +pub const VERGEN_GIT_COMMIT_MESSAGE: &str = env!("VERGEN_GIT_COMMIT_MESSAGE"); + /// The build profile name. pub const BUILD_PROFILE_NAME: &str = env!("OP_RBUILDER_BUILD_PROFILE"); @@ -38,6 +42,8 @@ pub const LONG_VERSION: &str = concat!( env!("OP_RBUILDER_LONG_VERSION_3"), "\n", env!("OP_RBUILDER_LONG_VERSION_4"), + "\n", + env!("OP_RBUILDER_LONG_VERSION_5"), ); pub const VERSION: VersionInfo = VersionInfo { @@ -47,6 +53,8 @@ pub const VERSION: VersionInfo = VersionInfo { git_sha: VERGEN_GIT_SHA, target_triple: VERGEN_CARGO_TARGET_TRIPLE, build_profile: BUILD_PROFILE_NAME, + commit_author: VERGEN_GIT_AUTHOR, + commit_message: VERGEN_GIT_COMMIT_MESSAGE, }; /// op-rbuilder metrics @@ -198,18 +206,24 @@ pub struct VersionInfo { pub target_triple: &'static str, /// The build profile (e.g., debug or release). pub build_profile: &'static str, + /// The author of the latest commit. + pub commit_author: &'static str, + /// The message of the latest commit. + pub commit_message: &'static str, } impl VersionInfo { - /// This exposes reth's version information over prometheus. + /// This exposes op-rbuilder's version information over prometheus. pub fn register_version_metrics(&self) { - let labels: [(&str, &str); 6] = [ + let labels: [(&str, &str); 8] = [ ("version", self.version), ("build_timestamp", self.build_timestamp), ("cargo_features", self.cargo_features), ("git_sha", self.git_sha), ("target_triple", self.target_triple), ("build_profile", self.build_profile), + ("commit_author", self.commit_author), + ("commit_message", self.commit_message), ]; let gauge = gauge!("builder_info", &labels); From b4e586073f289d415bf8100e3d1d294475429451 Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Thu, 11 Sep 2025 19:59:42 +0200 Subject: [PATCH 179/262] fix: gracefull cancellation on payload build failure (#239) --- .../src/builders/flashblocks/payload.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 3110e1c6..3fc6fca2 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -389,11 +389,11 @@ where .best_transactions_with_attributes(ctx.best_transaction_attributes()), )); // This channel coordinates flashblock building - let (fb_cancel_token_rx, mut fb_cancel_token_tx) = + let (fb_cancel_token_tx, mut fb_cancel_token_rx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); self.spawn_timer_task( block_cancel.clone(), - fb_cancel_token_rx, + fb_cancel_token_tx, first_flashblock_offset, ); // Process flashblocks in a blocking loop @@ -413,7 +413,7 @@ where // Cancellation of this token means that we need to stop building flashblock. // If channel return None it means that we built all flashblock or parent_token got cancelled let fb_cancel_token = - tokio::task::block_in_place(|| fb_cancel_token_tx.blocking_recv()).flatten(); + tokio::task::block_in_place(|| fb_cancel_token_rx.blocking_recv()).flatten(); match fb_cancel_token { Some(cancel_token) => { @@ -528,6 +528,8 @@ where // Track invalid/bad block ctx.metrics.invalid_blocks_count.increment(1); error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), ctx.flashblock_index(), err); + // Signal cancellation of the block building job + block_cancel.cancel(); // Return the error return Err(err); } @@ -667,7 +669,7 @@ where pub fn spawn_timer_task( &self, block_cancel: CancellationToken, - flashblock_cancel_token_rx: Sender>, + fb_cancel_token_tx: Sender>, first_flashblock_offset: Duration, ) { let interval = self.config.specific.interval; @@ -678,7 +680,7 @@ where let mut timer = tokio::time::interval(first_flashblock_offset); timer.tick().await; let child_token = block_cancel.child_token(); - flashblock_cancel_token_rx + fb_cancel_token_tx .send(Some(child_token.clone())) .await?; timer.tick().await; @@ -691,7 +693,7 @@ where // Initiate fb job let child_token = block_cancel.child_token(); debug!(target: "payload_builder", "Sending child cancel token to execution loop"); - flashblock_cancel_token_rx + fb_cancel_token_tx .send(Some(child_token.clone())) .await?; timer.tick().await; From f8e900e242a628b8ce92cc618e1fe16537f39e1d Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:05:35 -0400 Subject: [PATCH 180/262] fix: flashblock contraints in bundle api (#259) * refactor BundleOpts to use with_ methods to set * print logs during tests * fix connecting to flashblocks ws url * fix propagating bundle params for flashblocks check * refactor flashblock ws listener in tests * test setting min=max flashblock param --- .../src/builders/flashblocks/best_txs.rs | 1 + .../op-rbuilder/src/tests/flashblocks.rs | 390 +++++------------- .../src/tests/framework/instance.rs | 109 ++++- .../src/tests/framework/macros/src/lib.rs | 14 +- .../op-rbuilder/src/tests/framework/txs.rs | 48 ++- .../op-rbuilder/src/tests/framework/utils.rs | 13 +- .../builder/op-rbuilder/src/tests/revert.rs | 144 ++++--- crates/builder/op-rbuilder/src/tx.rs | 4 +- 8 files changed, 352 insertions(+), 371 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs index e8d73bad..b2108aa7 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs @@ -74,6 +74,7 @@ where if self.current_flashblock_number > max { debug!( target: "payload_builder", + tx_hash = ?tx.hash(), sender = ?tx.sender(), nonce = tx.nonce(), current_flashblock = self.current_flashblock_number, diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index cd17f8ae..d492d5c1 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -1,11 +1,6 @@ use alloy_provider::Provider; -use futures::StreamExt; use macros::rb_test; -use parking_lot::Mutex; -use std::{sync::Arc, time::Duration}; -use tokio::task::JoinHandle; -use tokio_tungstenite::{connect_async, tungstenite::Message}; -use tokio_util::sync::CancellationToken; +use std::time::Duration; use crate::{ args::{FlashblocksArgs, OpRbuilderArgs}, @@ -26,30 +21,7 @@ use crate::{ })] async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - let cancellation_token = CancellationToken::new(); - let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); - - // Spawn WebSocket listener task - let cancellation_token_clone = cancellation_token.clone(); - let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; - let (_, mut read) = ws_stream.split(); - - loop { - tokio::select! { - _ = cancellation_token_clone.cancelled() => { - break Ok(()); - } - Some(Ok(Message::Text(text))) = read.next() => { - messages_clone.lock().push(text); - } - } - } - }); + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); // We align out block timestamps with current unix timestamp for _ in 0..10 { @@ -66,18 +38,10 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { tokio::time::sleep(std::time::Duration::from_secs(1)).await; } - cancellation_token.cancel(); - assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); - - assert!( - !received_messages - .lock() - .iter() - .any(|msg| msg.contains("Building flashblock")), - "No messages received from WebSocket" - ); + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(110, flashblocks.len()); - Ok(()) + flashblocks_listener.stop().await } #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -94,30 +58,7 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { })] async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - let cancellation_token = CancellationToken::new(); - let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); - - // Spawn WebSocket listener task - let cancellation_token_clone = cancellation_token.clone(); - let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; - let (_, mut read) = ws_stream.split(); - - loop { - tokio::select! { - _ = cancellation_token_clone.cancelled() => { - break Ok(()); - } - Some(Ok(Message::Text(text))) = read.next() => { - messages_clone.lock().push(text); - } - } - } - }); + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); // We align out block timestamps with current unix timestamp for _ in 0..10 { @@ -134,18 +75,10 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { tokio::time::sleep(std::time::Duration::from_secs(1)).await; } - cancellation_token.cancel(); - assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(60, flashblocks.len()); - assert!( - !received_messages - .lock() - .iter() - .any(|msg| msg.contains("Building flashblock")), - "No messages received from WebSocket" - ); - - Ok(()) + flashblocks_listener.stop().await } #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -162,30 +95,7 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { })] async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - let cancellation_token = CancellationToken::new(); - let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); - - // Spawn WebSocket listener task - let cancellation_token_clone = cancellation_token.clone(); - let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; - let (_, mut read) = ws_stream.split(); - - loop { - tokio::select! { - _ = cancellation_token_clone.cancelled() => { - break Ok(()); - } - Some(Ok(Message::Text(text))) = read.next() => { - messages_clone.lock().push(text); - } - } - } - }); + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); // We align out block timestamps with current unix timestamp for _ in 0..10 { @@ -202,18 +112,10 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { tokio::time::sleep(std::time::Duration::from_secs(1)).await; } - cancellation_token.cancel(); - assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); - - assert!( - !received_messages - .lock() - .iter() - .any(|msg| msg.contains("Building flashblock")), - "No messages received from WebSocket" - ); + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(60, flashblocks.len()); - Ok(()) + flashblocks_listener.stop().await } #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -230,30 +132,7 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { })] async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - let cancellation_token = CancellationToken::new(); - let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); - - // Spawn WebSocket listener task - let cancellation_token_clone = cancellation_token.clone(); - let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; - let (_, mut read) = ws_stream.split(); - - loop { - tokio::select! { - _ = cancellation_token_clone.cancelled() => { - break Ok(()); - } - Some(Ok(Message::Text(text))) = read.next() => { - messages_clone.lock().push(text); - } - } - } - }); + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); // We align out block timestamps with current unix timestamp for _ in 0..10 { @@ -270,18 +149,10 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { tokio::time::sleep(std::time::Duration::from_secs(1)).await; } - cancellation_token.cancel(); - assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(110, flashblocks.len()); - assert!( - !received_messages - .lock() - .iter() - .any(|msg| msg.contains("Building flashblock")), - "No messages received from WebSocket" - ); - - Ok(()) + flashblocks_listener.stop().await } #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -298,30 +169,7 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { })] async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - let cancellation_token = CancellationToken::new(); - let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); - - // Spawn WebSocket listener task - let cancellation_token_clone = cancellation_token.clone(); - let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; - let (_, mut read) = ws_stream.split(); - - loop { - tokio::select! { - _ = cancellation_token_clone.cancelled() => { - break Ok(()); - } - Some(Ok(Message::Text(text))) = read.next() => { - messages_clone.lock().push(text); - } - } - } - }); + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); // We align out block timestamps with current unix timestamp for i in 0..9 { @@ -340,18 +188,10 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> tokio::time::sleep(std::time::Duration::from_secs(1)).await; } - cancellation_token.cancel(); - assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); - - assert!( - !received_messages - .lock() - .iter() - .any(|msg| msg.contains("Building flashblock")), - "No messages received from WebSocket" - ); + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(34, flashblocks.len()); - Ok(()) + flashblocks_listener.stop().await } #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -368,30 +208,7 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> })] async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - let cancellation_token = CancellationToken::new(); - let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); - - // Spawn WebSocket listener task - let cancellation_token_clone = cancellation_token.clone(); - let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; - let (_, mut read) = ws_stream.split(); - - loop { - tokio::select! { - _ = cancellation_token_clone.cancelled() => { - break Ok(()); - } - Some(Ok(Message::Text(text))) = read.next() => { - messages_clone.lock().push(text); - } - } - } - }); + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); for _ in 0..5 { // send a valid transaction @@ -406,18 +223,11 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<() .await?; // We could only produce block with deposits + builder tx because of short time frame assert_eq!(block.transactions.len(), 2); - cancellation_token.cancel(); - assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); - assert!( - !received_messages - .lock() - .iter() - .any(|msg| msg.contains("Building flashblock")), - "No messages received from WebSocket" - ); + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(1, flashblocks.len()); - Ok(()) + flashblocks_listener.stop().await } #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -435,30 +245,7 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<() })] async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - let cancellation_token = CancellationToken::new(); - let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); - - // Spawn WebSocket listener task - let cancellation_token_clone = cancellation_token.clone(); - let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; - let (_, mut read) = ws_stream.split(); - - loop { - tokio::select! { - _ = cancellation_token_clone.cancelled() => { - break Ok(()); - } - Some(Ok(Message::Text(text))) = read.next() => { - messages_clone.lock().push(text); - } - } - } - }); + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); // Create two transactions and set their tips so that while ordinarily // tx2 would come before tx1 because its tip is bigger, now tx1 comes @@ -466,10 +253,7 @@ async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result< let tx1 = driver .create_transaction() .random_valid_transfer() - .with_bundle(BundleOpts { - flashblock_number_min: Some(0), - ..Default::default() - }) + .with_bundle(BundleOpts::default().with_flashblock_number_min(0)) .with_max_priority_fee_per_gas(0) .send() .await?; @@ -477,42 +261,32 @@ async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result< let tx2 = driver .create_transaction() .random_valid_transfer() - .with_bundle(BundleOpts { - flashblock_number_min: Some(3), - ..Default::default() - }) + .with_bundle(BundleOpts::default().with_flashblock_number_min(3)) .with_max_priority_fee_per_gas(10) .send() .await?; - let block1 = driver.build_new_block_with_current_timestamp(None).await?; + let _block1 = driver.build_new_block_with_current_timestamp(None).await?; // Check that tx1 comes before tx2 let tx1_hash = *tx1.tx_hash(); let tx2_hash = *tx2.tx_hash(); - let mut tx1_pos = None; - let mut tx2_pos = None; - - for (i, item) in block1.transactions.hashes().into_iter().enumerate() { - if item == tx1_hash { - tx1_pos = Some(i); - } - if item == tx2_hash { - tx2_pos = Some(i); - } - } + let tx1_pos = flashblocks_listener + .find_transaction_flashblock(&tx1_hash) + .unwrap(); + let tx2_pos = flashblocks_listener + .find_transaction_flashblock(&tx2_hash) + .unwrap(); - assert!(tx1_pos.is_some(), "tx {tx1_hash:?} not found"); - assert!(tx2_pos.is_some(), "tx {tx2_hash:?} not found"); assert!( - tx1_pos.unwrap() < tx2_pos.unwrap(), + tx1_pos < tx2_pos, "tx {tx1_hash:?} does not come before {tx2_hash:?}" ); - cancellation_token.cancel(); - assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(6, flashblocks.len()); - Ok(()) + flashblocks_listener.stop().await } #[rb_test(flashblocks, args = OpRbuilderArgs { @@ -530,30 +304,7 @@ async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result< })] async fn test_flashblock_max_filtering(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - - // Create a struct to hold received messages - let received_messages = Arc::new(Mutex::new(Vec::new())); - let messages_clone = received_messages.clone(); - let cancellation_token = CancellationToken::new(); - let flashblocks_ws_url = rbuilder.flashblocks_ws_url(); - - // Spawn WebSocket listener task - let cancellation_token_clone = cancellation_token.clone(); - let ws_handle: JoinHandle> = tokio::spawn(async move { - let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; - let (_, mut read) = ws_stream.split(); - - loop { - tokio::select! { - _ = cancellation_token_clone.cancelled() => { - break Ok(()); - } - Some(Ok(Message::Text(text))) = read.next() => { - messages_clone.lock().push(text); - } - } - } - }); + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); // Since we cannot directly trigger flashblock creation in tests, we // instead fill up the gas of flashblocks so that our tx with the @@ -575,18 +326,65 @@ async fn test_flashblock_max_filtering(rbuilder: LocalInstance) -> eyre::Result< let tx1 = driver .create_transaction() .random_valid_transfer() - .with_bundle(BundleOpts { - flashblock_number_max: Some(1), - ..Default::default() - }) + .with_bundle(BundleOpts::default().with_flashblock_number_max(1)) .send() .await?; let block = driver.build_new_block_with_current_timestamp(None).await?; assert!(!block.includes(tx1.tx_hash())); + assert!( + flashblocks_listener + .find_transaction_flashblock(tx1.tx_hash()) + .is_none() + ); + + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(6, flashblocks.len()); + + flashblocks_listener.stop().await +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + }, + ..Default::default() +})] +async fn test_flashblock_min_max_filtering(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + + let tx1 = driver + .create_transaction() + .random_valid_transfer() + .with_bundle( + BundleOpts::default() + .with_flashblock_number_max(2) + .with_flashblock_number_min(2), + ) + .send() + .await?; + + let _block = driver.build_new_block_with_current_timestamp(None).await?; + + // It ends up in the flashblock with index 3. Flashblock number and index + // are different. + assert_eq!( + 2 + 1, + flashblocks_listener + .find_transaction_flashblock(tx1.tx_hash()) + .unwrap() + ); - cancellation_token.cancel(); - assert!(ws_handle.await.is_ok(), "WebSocket listener task failed"); + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(6, flashblocks.len()); - Ok(()) + flashblocks_listener.stop().await } diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 45bf5e62..34f5f506 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -10,6 +10,7 @@ use crate::{ tx::FBPooledTransaction, tx_signer::Signer, }; +use alloy_primitives::B256; use alloy_provider::{Identity, ProviderBuilder, RootProvider}; use clap::Parser; use core::{ @@ -20,10 +21,11 @@ use core::{ task::{Context, Poll}, time::Duration, }; -use futures::FutureExt; +use futures::{FutureExt, StreamExt}; use moka::future::Cache; use nanoid::nanoid; use op_alloy_network::Optimism; +use parking_lot::Mutex; use reth::{ args::{DatadirArgs, NetworkArgs, RpcServerArgs}, core::exit::NodeExitFuture, @@ -38,8 +40,12 @@ use reth_optimism_node::{ }; use reth_optimism_rpc::OpEthApiBuilder; use reth_transaction_pool::{AllTransactionsEvents, TransactionPool}; +use rollup_boost::FlashblocksPayloadV1; use std::sync::{Arc, LazyLock}; -use tokio::sync::oneshot; +use tokio::{sync::oneshot, task::JoinHandle}; +use tokio_tungstenite::{connect_async, tungstenite::Message}; +use tokio_util::sync::CancellationToken; +use tracing::warn; /// Represents a type that emulates a local in-process instance of the OP builder node. /// This node uses IPC as the communication channel for the RPC server Engine API. @@ -225,6 +231,10 @@ impl LocalInstance { format!("ws://{ipaddr}:{port}/") } + pub fn spawn_flashblocks_listener(&self) -> FlashblocksListener { + FlashblocksListener::new(self.flashblocks_ws_url()) + } + pub fn rpc_ipc(&self) -> &str { &self.config.rpc.ipcpath } @@ -345,3 +355,98 @@ fn pool_component(args: &OpRbuilderArgs) -> OpPoolBuilder { rollup_args.supervisor_safety_level, ) } + +/// A utility for listening to flashblocks WebSocket messages during tests. +/// +/// This provides a reusable way to capture and inspect flashblocks that are produced +/// during test execution, eliminating the need for duplicate WebSocket listening code. +pub struct FlashblocksListener { + pub flashblocks: Arc>>, + pub cancellation_token: CancellationToken, + pub handle: JoinHandle>, +} + +impl FlashblocksListener { + /// Create a new flashblocks listener that connects to the given WebSocket URL. + /// + /// The listener will automatically parse incoming messages as FlashblocksPayloadV1. + fn new(flashblocks_ws_url: String) -> Self { + let flashblocks = Arc::new(Mutex::new(Vec::new())); + let cancellation_token = CancellationToken::new(); + + let flashblocks_clone = flashblocks.clone(); + let cancellation_token_clone = cancellation_token.clone(); + + let handle = tokio::spawn(async move { + let (ws_stream, _) = connect_async(flashblocks_ws_url).await?; + let (_, mut read) = ws_stream.split(); + + loop { + tokio::select! { + _ = cancellation_token_clone.cancelled() => { + break Ok(()); + } + Some(Ok(Message::Text(text))) = read.next() => { + let fb = serde_json::from_str(&text).unwrap(); + warn!("GOT FB: {fb:#?}"); + flashblocks_clone.lock().push(fb); + } + } + } + }); + + Self { + flashblocks, + cancellation_token, + handle, + } + } + + /// Get a snapshot of all received flashblocks + pub fn get_flashblocks(&self) -> Vec { + self.flashblocks.lock().clone() + } + + /// Find a flashblock by index + pub fn find_flashblock(&self, index: u64) -> Option { + self.flashblocks + .lock() + .iter() + .find(|fb| fb.index == index) + .cloned() + } + + /// Check if any flashblock contains the given transaction hash + pub fn contains_transaction(&self, tx_hash: &B256) -> bool { + let tx_hash_str = format!("{:#x}", tx_hash); + self.flashblocks.lock().iter().any(|fb| { + if let Some(receipts) = fb.metadata.get("receipts") { + if let Some(receipts_obj) = receipts.as_object() { + return receipts_obj.contains_key(&tx_hash_str); + } + } + false + }) + } + + /// Find which flashblock index contains the given transaction hash + pub fn find_transaction_flashblock(&self, tx_hash: &B256) -> Option { + let tx_hash_str = format!("{:#x}", tx_hash); + self.flashblocks.lock().iter().find_map(|fb| { + if let Some(receipts) = fb.metadata.get("receipts") { + if let Some(receipts_obj) = receipts.as_object() { + if receipts_obj.contains_key(&tx_hash_str) { + return Some(fb.index); + } + } + } + None + }) + } + + /// Stop the listener and wait for it to complete + pub async fn stop(self) -> eyre::Result<()> { + self.cancellation_token.cancel(); + self.handle.await? + } +} 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 64cb940a..daa88c7e 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 @@ -28,7 +28,7 @@ const BUILDER_VARIANTS: &[VariantInfo] = &[ { let mut args = #args; args.flashblocks.enabled = true; - args.flashblocks.flashblocks_port = 0; + args.flashblocks.flashblocks_port = crate::tests::get_available_port(); args } } @@ -38,7 +38,7 @@ const BUILDER_VARIANTS: &[VariantInfo] = &[ { let mut args = crate::args::OpRbuilderArgs::default(); args.flashblocks.enabled = true; - args.flashblocks.flashblocks_port = 0; + args.flashblocks.flashblocks_port = crate::tests::get_available_port(); args } } @@ -231,6 +231,16 @@ pub fn rb_test(args: TokenStream, input: TokenStream) -> TokenStream { generated_functions.push(quote! { #test_attribute async fn #test_name() -> eyre::Result<()> { + let subscriber = tracing_subscriber::fmt() + .with_env_filter(std::env::var("RUST_LOG") + .unwrap_or_else(|_| "info".to_string())) + .with_file(true) + .with_line_number(true) + .with_test_writer() + .finish(); + let _guard = tracing::subscriber::set_global_default(subscriber); + tracing::info!("{} start", stringify!(#test_name)); + let instance = #instance_init; #original_name(instance).await } diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index 24a1748d..7c91892a 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -25,12 +25,44 @@ use super::FUNDED_PRIVATE_KEYS; #[derive(Clone, Copy, Default)] pub struct BundleOpts { - pub block_number_min: Option, - pub block_number_max: Option, - pub flashblock_number_min: Option, - pub flashblock_number_max: Option, - pub min_timestamp: Option, - pub max_timestamp: Option, + block_number_min: Option, + block_number_max: Option, + flashblock_number_min: Option, + flashblock_number_max: Option, + min_timestamp: Option, + max_timestamp: Option, +} + +impl BundleOpts { + pub fn with_block_number_min(mut self, block_number_min: u64) -> Self { + self.block_number_min = Some(block_number_min); + self + } + + pub fn with_block_number_max(mut self, block_number_max: u64) -> Self { + self.block_number_max = Some(block_number_max); + self + } + + pub fn with_flashblock_number_min(mut self, flashblock_number_min: u64) -> Self { + self.flashblock_number_min = Some(flashblock_number_min); + self + } + + pub fn with_flashblock_number_max(mut self, flashblock_number_max: u64) -> Self { + self.flashblock_number_max = Some(flashblock_number_max); + self + } + + pub fn with_min_timestamp(mut self, min_timestamp: u64) -> Self { + self.min_timestamp = Some(min_timestamp); + self + } + + pub fn with_max_timestamp(mut self, max_timestamp: u64) -> Self { + self.max_timestamp = Some(max_timestamp); + self + } } #[derive(Clone)] @@ -197,8 +229,8 @@ impl TransactionBuilder { }, block_number_min: bundle_opts.block_number_min, block_number_max: bundle_opts.block_number_max, - flashblock_number_min: bundle_opts.block_number_min, - flashblock_number_max: bundle_opts.block_number_max, + flashblock_number_min: bundle_opts.flashblock_number_min, + flashblock_number_max: bundle_opts.flashblock_number_max, min_timestamp: bundle_opts.min_timestamp, max_timestamp: bundle_opts.max_timestamp, }; diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs index 2cb5f51b..a71361d2 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -15,7 +15,7 @@ use reth_db::{ }; use reth_node_core::{args::DatadirArgs, dirs::DataDirPath, node_config::NodeConfig}; use reth_optimism_chainspec::OpChainSpec; -use std::sync::Arc; +use std::{net::TcpListener, sync::Arc}; use super::{FUNDED_PRIVATE_KEYS, TransactionBuilder}; @@ -228,3 +228,14 @@ pub fn create_test_db(config: NodeConfig) -> Arc u16 { + TcpListener::bind("127.0.0.1:0") + .expect("Failed to bind to random port") + .local_addr() + .expect("Failed to get local address") + .port() +} diff --git a/crates/builder/op-rbuilder/src/tests/revert.rs b/crates/builder/op-rbuilder/src/tests/revert.rs index 8037f996..76e1c5b9 100644 --- a/crates/builder/op-rbuilder/src/tests/revert.rs +++ b/crates/builder/op-rbuilder/src/tests/revert.rs @@ -31,10 +31,9 @@ async fn monitor_transaction_gc(rbuilder: LocalInstance) -> eyre::Result<()> { .create_transaction() .random_reverting_transaction() .with_signer(accounts[i].clone()) - .with_bundle(BundleOpts { - block_number_max: Some(latest_block_number + i as u64 + 1), - ..Default::default() - }) + .with_bundle( + BundleOpts::default().with_block_number_max(latest_block_number + i as u64 + 1), + ) .send() .await?; pending_txn.push(txn); @@ -141,10 +140,7 @@ async fn bundle(rbuilder: LocalInstance) -> eyre::Result<()> { .includes(valid_bundle.tx_hash()) ); - let bundle_opts = BundleOpts { - block_number_max: Some(4), - ..Default::default() - }; + let bundle_opts = BundleOpts::default().with_block_number_max(4); let reverted_bundle = driver .create_transaction() @@ -183,10 +179,7 @@ async fn bundle_min_block_number(rbuilder: LocalInstance) -> eyre::Result<()> { .create_transaction() .with_revert() // the transaction reverts but it is included in the block .with_reverted_hash() - .with_bundle(BundleOpts { - block_number_min: Some(2), - ..Default::default() - }) + .with_bundle(BundleOpts::default().with_block_number_min(2)) .send() .await?; @@ -201,11 +194,11 @@ async fn bundle_min_block_number(rbuilder: LocalInstance) -> eyre::Result<()> { .create_transaction() .with_revert() .with_reverted_hash() - .with_bundle(BundleOpts { - block_number_max: Some(4), - block_number_min: Some(4), - ..Default::default() - }) + .with_bundle( + BundleOpts::default() + .with_block_number_max(4) + .with_block_number_min(4), + ) .send() .await?; @@ -232,10 +225,7 @@ async fn bundle_min_timestamp(rbuilder: LocalInstance) -> eyre::Result<()> { .create_transaction() .with_revert() // the transaction reverts but it is included in the block .with_reverted_hash() - .with_bundle(BundleOpts { - min_timestamp: Some(initial_timestamp + 2), - ..Default::default() - }) + .with_bundle(BundleOpts::default().with_min_timestamp(initial_timestamp + 2)) .send() .await?; @@ -261,84 +251,121 @@ async fn bundle_range_limits(rbuilder: LocalInstance) -> eyre::Result<()> { async fn send_bundle( driver: &ChainDriver, - block_number_max: Option, - block_number_min: Option, + bundle: BundleOpts, ) -> eyre::Result> { - driver - .create_transaction() - .with_bundle(BundleOpts { - block_number_max, - block_number_min, - ..Default::default() - }) - .send() - .await + driver.create_transaction().with_bundle(bundle).send().await } // Max block cannot be a past block - assert!(send_bundle(&driver, Some(1), None).await.is_err()); + assert!( + send_bundle(&driver, BundleOpts::default().with_block_number_max(1)) + .await + .is_err() + ); // Bundles are valid if their max block in in between the current block and the max block range let current_block = 2; let next_valid_block = current_block + 1; for i in next_valid_block..next_valid_block + MAX_BLOCK_RANGE_BLOCKS { - assert!(send_bundle(&driver, Some(i), None).await.is_ok()); + assert!( + send_bundle(&driver, BundleOpts::default().with_block_number_max(i)) + .await + .is_ok() + ); } // A bundle with a block out of range is invalid assert!( send_bundle( &driver, - Some(next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1), - None + BundleOpts::default() + .with_block_number_max(next_valid_block + MAX_BLOCK_RANGE_BLOCKS + 1) ) .await .is_err() ); // A bundle with a min block number higher than the max block is invalid - assert!(send_bundle(&driver, Some(1), Some(2)).await.is_err()); + assert!( + send_bundle( + &driver, + BundleOpts::default() + .with_block_number_max(1) + .with_block_number_min(2) + ) + .await + .is_err() + ); // A bundle with a min block number lower or equal to the current block is valid assert!( - send_bundle(&driver, Some(next_valid_block), Some(current_block)) - .await - .is_ok() + send_bundle( + &driver, + BundleOpts::default() + .with_block_number_max(next_valid_block) + .with_block_number_min(current_block) + ) + .await + .is_ok() ); assert!( - send_bundle(&driver, Some(next_valid_block), Some(0)) - .await - .is_ok() + send_bundle( + &driver, + BundleOpts::default().with_block_number_max(next_valid_block) + ) + .await + .is_ok() ); // A bundle with a min block equal to max block is valid assert!( - send_bundle(&driver, Some(next_valid_block), Some(next_valid_block)) - .await - .is_ok() + send_bundle( + &driver, + BundleOpts::default() + .with_block_number_max(next_valid_block) + .with_block_number_min(next_valid_block) + ) + .await + .is_ok() ); // Test min-only cases (no max specified) // A bundle with only min block that's within the default range is valid let default_max = current_block + MAX_BLOCK_RANGE_BLOCKS; assert!( - send_bundle(&driver, None, Some(current_block)) - .await - .is_ok() + send_bundle( + &driver, + BundleOpts::default().with_block_number_min(current_block) + ) + .await + .is_ok() ); assert!( - send_bundle(&driver, None, Some(default_max - 1)) - .await - .is_ok() + send_bundle( + &driver, + BundleOpts::default().with_block_number_min(default_max - 1) + ) + .await + .is_ok() + ); + assert!( + send_bundle( + &driver, + BundleOpts::default().with_block_number_min(default_max) + ) + .await + .is_ok() ); - assert!(send_bundle(&driver, None, Some(default_max)).await.is_ok()); // A bundle with only min block that exceeds the default max range is invalid assert!( - send_bundle(&driver, None, Some(default_max + 1)) - .await - .is_err() + send_bundle( + &driver, + BundleOpts::default().with_block_number_min(default_max + 1) + ) + .await + .is_err() ); Ok(()) @@ -387,10 +414,7 @@ async fn check_transaction_receipt_status_message(rbuilder: LocalInstance) -> ey let reverting_tx = driver .create_transaction() .random_reverting_transaction() - .with_bundle(BundleOpts { - block_number_max: Some(3), - ..Default::default() - }) + .with_bundle(BundleOpts::default().with_block_number_max(3)) .send() .await?; let tx_hash = reverting_tx.tx_hash(); diff --git a/crates/builder/op-rbuilder/src/tx.rs b/crates/builder/op-rbuilder/src/tx.rs index 7626fff1..64f7e43f 100644 --- a/crates/builder/op-rbuilder/src/tx.rs +++ b/crates/builder/op-rbuilder/src/tx.rs @@ -293,8 +293,8 @@ impl MaybeConditionalTransaction for FBPooledTransaction { FBPooledTransaction { inner: self.inner.with_conditional(conditional), reverted_hashes: self.reverted_hashes, - flashblock_number_min: None, - flashblock_number_max: None, + flashblock_number_min: self.flashblock_number_min, + flashblock_number_max: self.flashblock_number_max, } } } From b6e515f457eb22c16e81d218c3a8640d8ef4021d Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Fri, 12 Sep 2025 17:03:19 +0500 Subject: [PATCH 181/262] bump reth to 1.7.0 (#258) * bump reth * Add comm * Fix the problem --- crates/builder/op-rbuilder/Cargo.toml | 3 ++- crates/builder/op-rbuilder/src/tests/framework/instance.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 8c1c3b28..a7c22586 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -123,7 +123,8 @@ sha3 = "0.10" hex = "0.4" ureq = "2.10" -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", branch = "main" } +# TODO: change to rev from main once https://github.com/flashbots/rollup-boost/pull/401 is merged +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "0c1fb4ce7e00f0afc350f5bf7573b19da6d485ec" } nanoid = { version = "0.4", optional = true } reth-ipc = { workspace = true, optional = true } diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 34f5f506..041136c1 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -66,7 +66,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(args: OpRbuilderArgs) -> eyre::Result { - Self::new_with_config::

(args, default_node_config()).await + 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, From ccf9c8388ab15c6fd3d2acdc8b6c0e5e018b5f57 Mon Sep 17 00:00:00 2001 From: cody-wang-cb Date: Mon, 15 Sep 2025 20:30:24 -0400 Subject: [PATCH 182/262] flag to determine if calculating state root (#241) * calculate state root * fix tests * add no state root test * check no tx pool instead * comments * optimize * fix lint * use no tx pool * add condition --- crates/builder/op-rbuilder/src/args/op.rs | 8 ++ .../src/builders/flashblocks/config.rs | 7 ++ .../src/builders/flashblocks/payload.rs | 81 +++++++++++-------- .../op-rbuilder/src/tests/flashblocks.rs | 53 ++++++++++++ 4 files changed, 117 insertions(+), 32 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 8b3bd5bd..760249b5 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -147,6 +147,14 @@ pub struct FlashblocksArgs { env = "FLASHBLOCK_LEEWAY_TIME" )] pub flashblocks_leeway_time: u64, + + /// Should we calculate state root for each flashblock + #[arg( + long = "flashblocks.calculate-state-root", + default_value = "true", + env = "FLASHBLOCKS_CALCULATE_STATE_ROOT" + )] + pub flashblocks_calculate_state_root: bool, } impl Default for FlashblocksArgs { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index 702566be..64c52684 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -28,6 +28,9 @@ pub struct FlashblocksConfig { /// Disables dynamic flashblocks number adjustment based on FCU arrival time pub fixed: bool, + + /// Should we calculate state root for each flashblock + pub calculate_state_root: bool, } impl Default for FlashblocksConfig { @@ -37,6 +40,7 @@ impl Default for FlashblocksConfig { interval: Duration::from_millis(250), leeway_time: Duration::from_millis(50), fixed: false, + calculate_state_root: true, } } } @@ -56,11 +60,14 @@ impl TryFrom for FlashblocksConfig { let fixed = args.flashblocks.flashblocks_fixed; + let calculate_state_root = args.flashblocks.flashblocks_calculate_state_root; + Ok(Self { ws_addr, interval, leeway_time, fixed, + calculate_state_root, }) } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 3fc6fca2..5e0f45e6 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -37,6 +37,7 @@ use reth_provider::{ use reth_revm::{ State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, }; +use reth_trie::{HashedPostState, updates::TrieUpdates}; use revm::Database; use rollup_boost::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashblocksPayloadV1, @@ -222,6 +223,7 @@ where let chain_spec = self.client.chain_spec(); let timestamp = config.attributes.timestamp(); + let calculate_state_root = self.config.specific.calculate_state_root; let block_env_attributes = OpNextBlockEnvAttributes { timestamp, suggested_fee_recipient: config.attributes.suggested_fee_recipient(), @@ -299,18 +301,25 @@ where ctx.add_builder_tx(&mut info, &mut state, builder_tx_gas, message.clone()); } - let (payload, fb_payload) = build_block(&mut state, &ctx, &mut info)?; + let (payload, fb_payload) = build_block( + &mut state, + &ctx, + &mut info, + calculate_state_root || ctx.attributes().no_tx_pool, // need to calculate state root for CL sync + )?; best_payload.set(payload.clone()); self.send_payload_to_engine(payload); - - let flashblock_byte_size = self - .ws_pub - .publish(&fb_payload) - .map_err(PayloadBuilderError::other)?; - ctx.metrics - .flashblock_byte_size_histogram - .record(flashblock_byte_size as f64); + // not emitting flashblock if no_tx_pool in FCU, it's just syncing + if !ctx.attributes().no_tx_pool { + let flashblock_byte_size = self + .ws_pub + .publish(&fb_payload) + .map_err(PayloadBuilderError::other)?; + ctx.metrics + .flashblock_byte_size_histogram + .record(flashblock_byte_size as f64); + } info!( target: "payload_builder", @@ -513,7 +522,8 @@ where }; let total_block_built_duration = Instant::now(); - let build_result = build_block(&mut state, &ctx, &mut info); + let build_result = + build_block(&mut state, &ctx, &mut info, calculate_state_root); let total_block_built_duration = total_block_built_duration.elapsed(); ctx.metrics .total_block_built_duration @@ -823,6 +833,7 @@ fn build_block( state: &mut State, ctx: &OpPayloadBuilderCtx, info: &mut ExecutionInfo, + calculate_state_root: bool, ) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError> where DB: Database + AsRef

, @@ -867,28 +878,34 @@ where // TODO: maybe recreate state with bundle in here // // calculate the state root let state_root_start_time = Instant::now(); - let state_provider = state.database.as_ref(); - let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); - let (state_root, trie_output) = { - state - .database - .as_ref() - .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 mut state_root = B256::ZERO; + let mut trie_output = TrieUpdates::default(); + let mut hashed_state = HashedPostState::default(); + + if calculate_state_root { + let state_provider = state.database.as_ref(); + hashed_state = state_provider.hashed_post_state(execution_outcome.state()); + (state_root, trie_output) = { + state + .database + .as_ref() + .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 mut requests_hash = None; let withdrawals_root = if ctx diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index d492d5c1..eca81b4d 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -16,6 +16,7 @@ use crate::{ flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -53,6 +54,7 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -90,6 +92,7 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_block_time: 200, flashblocks_leeway_time: 50, flashblocks_fixed: true, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -127,6 +130,7 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_block_time: 200, flashblocks_leeway_time: 50, flashblocks_fixed: true, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -164,6 +168,7 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -203,6 +208,7 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> flashblocks_block_time: 200, flashblocks_leeway_time: 0, flashblocks_fixed: false, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -240,6 +246,7 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<() flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -299,6 +306,7 @@ async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result< flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -354,6 +362,7 @@ async fn test_flashblock_max_filtering(rbuilder: LocalInstance) -> eyre::Result< flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, + flashblocks_calculate_state_root: true, }, ..Default::default() })] @@ -388,3 +397,47 @@ async fn test_flashblock_min_max_filtering(rbuilder: LocalInstance) -> eyre::Res flashblocks_listener.stop().await } + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + flashblocks: FlashblocksArgs { + enabled: true, + flashblocks_port: 1239, + flashblocks_addr: "127.0.0.1".into(), + flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, + flashblocks_calculate_state_root: false, + }, + ..Default::default() +})] +async fn test_flashblocks_no_state_root_calculation(rbuilder: LocalInstance) -> eyre::Result<()> { + use alloy_primitives::B256; + + let driver = rbuilder.driver().await?; + + // Send a transaction to ensure block has some activity + let _tx = driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + + // Build a block with current timestamp (not historical) and calculate_state_root: false + let block = driver.build_new_block_with_current_timestamp(None).await?; + + // Verify that flashblocks are still produced (block should have transactions) + assert!( + block.transactions.len() > 2, + "Block should contain transactions" + ); // deposit + builder tx + user tx + + // Verify that state root is not calculated (should be zero) + assert_eq!( + block.header.state_root, + B256::ZERO, + "State root should be zero when calculate_state_root is false" + ); + + Ok(()) +} From 8341ed1f9a5add1aaaec0cdce877fa10b020849a Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Tue, 16 Sep 2025 13:00:45 -0400 Subject: [PATCH 183/262] refactor: add `unreachable_pub` warning and autofix warnings (#263) --- crates/builder/op-rbuilder/Cargo.toml | 3 ++ .../op-rbuilder/src/args/playground.rs | 6 +-- .../op-rbuilder/src/builders/builder_tx.rs | 2 +- .../op-rbuilder/src/builders/context.rs | 50 +++++++++---------- .../src/builders/flashblocks/best_txs.rs | 8 +-- .../src/builders/flashblocks/config.rs | 2 +- .../src/builders/flashblocks/payload.rs | 20 ++++---- .../src/builders/flashblocks/wspub.rs | 6 +-- .../op-rbuilder/src/builders/generator.rs | 28 +++++------ .../src/builders/standard/payload.rs | 8 +-- .../op-rbuilder/src/gas_limiter/metrics.rs | 10 ++-- .../op-rbuilder/src/monitor_tx_pool.rs | 2 +- 12 files changed, 76 insertions(+), 69 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index a7c22586..3d31df46 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -8,6 +8,9 @@ homepage.workspace = true repository.workspace = true default-run = "op-rbuilder" +[lints] +workspace = true + [dependencies] reth.workspace = true reth-optimism-node.workspace = true diff --git a/crates/builder/op-rbuilder/src/args/playground.rs b/crates/builder/op-rbuilder/src/args/playground.rs index c4cf2470..3ef24c1d 100644 --- a/crates/builder/op-rbuilder/src/args/playground.rs +++ b/crates/builder/op-rbuilder/src/args/playground.rs @@ -50,7 +50,7 @@ use url::{Host, Url}; use super::Cli; -pub struct PlaygroundOptions { +pub(super) struct PlaygroundOptions { /// Sets node.chain in NodeCommand pub chain: Arc, @@ -78,7 +78,7 @@ pub struct PlaygroundOptions { impl PlaygroundOptions { /// Creates a new `PlaygroundOptions` instance with the specified genesis path. - pub fn new(path: &Path) -> Result { + pub(super) fn new(path: &Path) -> Result { if !path.exists() { return Err(eyre!( "Playground data directory {} does not exist", @@ -112,7 +112,7 @@ impl PlaygroundOptions { }) } - pub fn apply(self, cli: Cli) -> Cli { + pub(super) fn apply(self, cli: Cli) -> Cli { let mut cli = cli; let Commands::Node(ref mut node) = cli.command else { // playground defaults are only relevant if running the node commands. diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index 3dcb8984..80c73c83 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -12,7 +12,7 @@ pub trait BuilderTx { // Scaffolding for how to construct the end of block builder transaction // This will be the regular end of block transaction without the TEE key #[derive(Clone)] -pub struct StandardBuilderTx { +pub(super) struct StandardBuilderTx { #[allow(dead_code)] pub signer: Option, } diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 7863d441..8129b9f5 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -50,7 +50,7 @@ use crate::{ /// Container type that holds all necessities to build a new payload. #[derive(Debug)] -pub struct OpPayloadBuilderCtx { +pub(super) 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 @@ -79,41 +79,41 @@ pub struct OpPayloadBuilderCtx { impl OpPayloadBuilderCtx { /// Returns the parent block the payload will be build on. - pub fn parent(&self) -> &SealedHeader { + pub(super) fn parent(&self) -> &SealedHeader { &self.config.parent_header } /// Returns the builder attributes. - pub const fn attributes(&self) -> &OpPayloadBuilderAttributes { + pub(super) const fn attributes(&self) -> &OpPayloadBuilderAttributes { &self.config.attributes } /// Returns the withdrawals if shanghai is active. - pub fn withdrawals(&self) -> Option<&Withdrawals> { + pub(super) fn withdrawals(&self) -> Option<&Withdrawals> { self.chain_spec .is_shanghai_active_at_timestamp(self.attributes().timestamp()) .then(|| &self.attributes().payload_attributes.withdrawals) } /// Returns the block gas limit to target. - pub fn block_gas_limit(&self) -> u64 { + pub(super) fn block_gas_limit(&self) -> u64 { self.attributes() .gas_limit .unwrap_or(self.evm_env.block_env.gas_limit) } /// Returns the block number for the block. - pub fn block_number(&self) -> u64 { + pub(super) fn block_number(&self) -> u64 { as_u64_saturated!(self.evm_env.block_env.number) } /// Returns the current base fee - pub fn base_fee(&self) -> u64 { + pub(super) fn base_fee(&self) -> u64 { self.evm_env.block_env.basefee } /// Returns the current blob gas price. - pub fn get_blob_gasprice(&self) -> Option { + pub(super) fn get_blob_gasprice(&self) -> Option { self.evm_env .block_env .blob_gasprice() @@ -123,7 +123,7 @@ impl OpPayloadBuilderCtx { /// Returns the blob fields for the header. /// /// This will always return `Some(0)` after ecotone. - pub fn blob_fields(&self) -> (Option, Option) { + pub(super) fn blob_fields(&self) -> (Option, Option) { // 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. @@ -137,7 +137,7 @@ impl OpPayloadBuilderCtx { /// Returns the extra data for the block. /// /// After holocene this extracts the extradata from the paylpad - pub fn extra_data(&self) -> Result { + pub(super) fn extra_data(&self) -> Result { if self.is_holocene_active() { self.attributes() .get_holocene_extra_data( @@ -152,52 +152,52 @@ impl OpPayloadBuilderCtx { } /// Returns the current fee settings for transactions from the mempool - pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { + pub(super) fn best_transaction_attributes(&self) -> BestTransactionsAttributes { BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) } /// Returns the unique id for this payload job. - pub fn payload_id(&self) -> PayloadId { + pub(super) fn payload_id(&self) -> PayloadId { self.attributes().payload_id() } /// Returns true if regolith is active for the payload. - pub fn is_regolith_active(&self) -> bool { + pub(super) fn is_regolith_active(&self) -> bool { self.chain_spec .is_regolith_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if ecotone is active for the payload. - pub fn is_ecotone_active(&self) -> bool { + pub(super) fn is_ecotone_active(&self) -> bool { self.chain_spec .is_ecotone_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if canyon is active for the payload. - pub fn is_canyon_active(&self) -> bool { + pub(super) fn is_canyon_active(&self) -> bool { self.chain_spec .is_canyon_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if holocene is active for the payload. - pub fn is_holocene_active(&self) -> bool { + pub(super) fn is_holocene_active(&self) -> bool { self.chain_spec .is_holocene_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if isthmus is active for the payload. - pub fn is_isthmus_active(&self) -> bool { + pub(super) fn is_isthmus_active(&self) -> bool { self.chain_spec .is_isthmus_active_at_timestamp(self.attributes().timestamp()) } /// Returns the chain id - pub fn chain_id(&self) -> u64 { + pub(super) fn chain_id(&self) -> u64 { self.chain_spec.chain_id() } /// Returns the builder signer - pub fn builder_signer(&self) -> Option { + pub(super) fn builder_signer(&self) -> Option { self.builder_signer } } @@ -236,7 +236,7 @@ impl OpPayloadBuilderCtx { } /// Executes all sequencer transactions that are included in the payload attributes. - pub fn execute_sequencer_transactions( + pub(super) fn execute_sequencer_transactions( &self, state: &mut State, ) -> Result, PayloadBuilderError> @@ -325,7 +325,7 @@ impl OpPayloadBuilderCtx { /// Executes the given best transactions and updates the execution info. /// /// Returns `Ok(Some(())` if the job was cancelled. - pub fn execute_best_transactions( + pub(super) fn execute_best_transactions( &self, info: &mut ExecutionInfo, state: &mut State, @@ -567,7 +567,7 @@ impl OpPayloadBuilderCtx { Ok(None) } - pub fn add_builder_tx( + pub(super) fn add_builder_tx( &self, info: &mut ExecutionInfo, state: &mut State, @@ -628,7 +628,7 @@ impl OpPayloadBuilderCtx { /// Calculates EIP 2718 builder transaction size // TODO: this function could be improved, ideally we shouldn't take mut ref to db and maybe // it's possible to do this without db at all - pub fn estimate_builder_tx_da_size( + pub(super) fn estimate_builder_tx_da_size( &self, state: &mut State, builder_tx_gas: u64, @@ -656,7 +656,7 @@ impl OpPayloadBuilderCtx { } } -pub fn estimate_gas_for_builder_tx(input: Vec) -> u64 { +pub(super) fn estimate_gas_for_builder_tx(input: Vec) -> u64 { // Count zero and non-zero bytes let (zero_bytes, nonzero_bytes) = input.iter().fold((0, 0), |(zeros, nonzeros), &byte| { if byte == 0 { @@ -678,7 +678,7 @@ pub fn estimate_gas_for_builder_tx(input: Vec) -> u64 { } /// Creates signed builder tx to Address::ZERO and specified message as input -pub fn signed_builder_tx( +pub(super) fn signed_builder_tx( state: &mut State, builder_tx_gas: u64, message: Vec, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs index b2108aa7..3d4021c7 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs @@ -6,7 +6,7 @@ use tracing::debug; use crate::tx::MaybeFlashblockFilter; -pub struct BestFlashblocksTxs +pub(super) struct BestFlashblocksTxs where T: PoolTransaction, I: PayloadTransactions, @@ -23,7 +23,7 @@ where T: PoolTransaction, I: PayloadTransactions, { - pub fn new(inner: I) -> Self { + pub(super) fn new(inner: I) -> Self { Self { inner, current_flashblock_number: 0, @@ -33,13 +33,13 @@ where /// Replaces current iterator with new one. We use it on new flashblock building, to refresh /// priority boundaries - pub fn refresh_iterator(&mut self, inner: I, current_flashblock_number: u64) { + pub(super) fn refresh_iterator(&mut self, inner: I, current_flashblock_number: u64) { self.inner = inner; self.current_flashblock_number = current_flashblock_number; } /// Remove transaction from next iteration and it already in the state - pub fn mark_commited(&mut self, txs: Vec) { + pub(super) fn mark_commited(&mut self, txs: Vec) { self.commited_transactions.extend(txs); } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index 64c52684..c852a354 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -72,7 +72,7 @@ impl TryFrom for FlashblocksConfig { } } -pub trait FlashBlocksConfigExt { +pub(super) trait FlashBlocksConfigExt { fn flashblocks_per_block(&self) -> u64; } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 5e0f45e6..df3b12aa 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -71,36 +71,36 @@ struct FlashblocksExtraCtx { impl OpPayloadBuilderCtx { /// Returns the current flashblock index - pub fn flashblock_index(&self) -> u64 { + pub(crate) fn flashblock_index(&self) -> u64 { self.extra_ctx.flashblock_index } /// Returns the target flashblock count - pub fn target_flashblock_count(&self) -> u64 { + pub(crate) fn target_flashblock_count(&self) -> u64 { self.extra_ctx.target_flashblock_count } /// Increments the flashblock index - pub fn increment_flashblock_index(&mut self) -> u64 { + pub(crate) fn increment_flashblock_index(&mut self) -> u64 { self.extra_ctx.flashblock_index += 1; self.extra_ctx.flashblock_index } /// Sets the target flashblock count - pub fn set_target_flashblock_count(&mut self, target_flashblock_count: u64) -> u64 { + pub(crate) fn set_target_flashblock_count(&mut self, target_flashblock_count: u64) -> u64 { self.extra_ctx.target_flashblock_count = target_flashblock_count; self.extra_ctx.target_flashblock_count } /// Returns if the flashblock is the last one - pub fn is_last_flashblock(&self) -> bool { + pub(crate) fn is_last_flashblock(&self) -> bool { self.flashblock_index() == self.target_flashblock_count() - 1 } } /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct OpPayloadBuilder { +pub(super) struct OpPayloadBuilder { /// The type responsible for creating the evm. pub evm_config: OpEvmConfig, /// The transaction pool @@ -126,7 +126,7 @@ pub struct OpPayloadBuilder { impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. - pub fn new( + pub(super) fn new( evm_config: OpEvmConfig, pool: Pool, client: Client, @@ -656,7 +656,7 @@ where } /// Sends built payload via payload builder handle broadcast channel to the engine - pub fn send_payload_to_engine(&self, payload: OpBuiltPayload) { + pub(super) fn send_payload_to_engine(&self, payload: OpBuiltPayload) { // Send built payload as created one match self.payload_builder_handle.get() { Some(handle) => { @@ -676,7 +676,7 @@ where /// Spawn task that will send new flashblock level cancel token in steady intervals (first interval /// may vary if --flashblocks.dynamic enabled) - pub fn spawn_timer_task( + pub(super) fn spawn_timer_task( &self, block_cancel: CancellationToken, fb_cancel_token_tx: Sender>, @@ -723,7 +723,7 @@ where /// Calculate number of flashblocks. /// If dynamic is enabled this function will take time drift into the account. - pub fn calculate_flashblocks(&self, timestamp: u64) -> (u64, Duration) { + pub(super) fn calculate_flashblocks(&self, timestamp: u64) -> (u64, Duration) { if self.config.specific.fixed { return ( self.config.flashblocks_per_block(), diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index cf8c485c..944edb91 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -28,7 +28,7 @@ use crate::metrics::OpRBuilderMetrics; /// updates about new flashblocks. It maintains a count of sent messages and active subscriptions. /// /// This is modelled as a `futures::Sink` that can be used to send `FlashblocksPayloadV1` messages. -pub struct WebSocketPublisher { +pub(super) struct WebSocketPublisher { sent: Arc, subs: Arc, term: watch::Sender, @@ -36,7 +36,7 @@ pub struct WebSocketPublisher { } impl WebSocketPublisher { - pub fn new(addr: SocketAddr, metrics: Arc) -> io::Result { + pub(super) fn new(addr: SocketAddr, metrics: Arc) -> io::Result { let (pipe, _) = broadcast::channel(100); let (term, _) = watch::channel(false); @@ -61,7 +61,7 @@ impl WebSocketPublisher { }) } - pub fn publish(&self, payload: &FlashblocksPayloadV1) -> io::Result { + pub(super) fn publish(&self, payload: &FlashblocksPayloadV1) -> io::Result { // Serialize the payload to a UTF-8 string // serialize only once, then just copy around only a pointer // to the serialized data for each subscription. diff --git a/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs index c7793083..be9bd89f 100644 --- a/crates/builder/op-rbuilder/src/builders/generator.rs +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -34,7 +34,7 @@ use tracing::info; /// /// Generic parameters `Pool` and `Client` represent the transaction pool and /// Ethereum client types. -pub trait PayloadBuilder: Send + Sync + Clone { +pub(super) trait PayloadBuilder: Send + Sync + Clone { /// The payload attributes type to accept for building. type Attributes: PayloadBuilderAttributes; /// The type of the built payload. @@ -61,7 +61,7 @@ pub trait PayloadBuilder: Send + Sync + Clone { /// The generator type that creates new jobs that builds empty blocks. #[derive(Debug)] -pub struct BlockPayloadJobGenerator { +pub(super) struct BlockPayloadJobGenerator { /// The client that can interact with the chain. client: Client, /// How to spawn building tasks @@ -87,7 +87,7 @@ pub struct BlockPayloadJobGenerator { impl BlockPayloadJobGenerator { /// Creates a new [EmptyBlockPayloadJobGenerator] with the given config and custom /// [PayloadBuilder] - pub fn with_builder( + pub(super) fn with_builder( client: Client, executor: Tasks, config: BasicPayloadJobGeneratorConfig, @@ -238,7 +238,7 @@ use std::{ }; /// A [PayloadJob] that builds empty blocks. -pub struct BlockPayloadJob +pub(super) struct BlockPayloadJob where Builder: PayloadBuilder, { @@ -296,7 +296,7 @@ where } } -pub struct BuildArguments { +pub(super) struct BuildArguments { /// Previously cached disk reads pub cached_reads: CachedReads, /// How to configure the payload. @@ -313,7 +313,7 @@ where Builder::Attributes: Unpin + Clone, Builder::BuiltPayload: Unpin + Clone, { - pub fn spawn_build_job(&mut self) { + pub(super) fn spawn_build_job(&mut self) { let builder = self.builder.clone(); let payload_config = self.config.clone(); let cell = self.cell.clone(); @@ -367,12 +367,12 @@ where } // A future that resolves when a payload becomes available in the BlockCell -pub struct ResolvePayload { +pub(super) struct ResolvePayload { future: WaitForValue, } impl ResolvePayload { - pub fn new(future: WaitForValue) -> Self { + pub(super) fn new(future: WaitForValue) -> Self { Self { future } } } @@ -389,39 +389,39 @@ impl Future for ResolvePayload { } #[derive(Clone)] -pub struct BlockCell { +pub(super) struct BlockCell { inner: Arc>>, notify: Arc, } impl BlockCell { - pub fn new() -> Self { + pub(super) fn new() -> Self { Self { inner: Arc::new(Mutex::new(None)), notify: Arc::new(Notify::new()), } } - pub fn set(&self, value: T) { + pub(super) fn set(&self, value: T) { let mut inner = self.inner.lock().unwrap(); *inner = Some(value); self.notify.notify_one(); } - pub fn get(&self) -> Option { + pub(super) fn get(&self) -> Option { let inner = self.inner.lock().unwrap(); inner.clone() } // Return a future that resolves when value is set - pub fn wait_for_value(&self) -> WaitForValue { + pub(super) fn wait_for_value(&self) -> WaitForValue { WaitForValue { cell: self.clone() } } } #[derive(Clone)] // Future that resolves when a value is set in BlockCell -pub struct WaitForValue { +pub(super) struct WaitForValue { cell: BlockCell, } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index c61fc32e..daebf7dd 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -336,7 +336,7 @@ where /// And finally /// 5. build the block: compute all roots (txs, state) #[derive(derive_more::Debug)] -pub struct OpBuilder<'a, Txs> { +pub(super) struct OpBuilder<'a, Txs> { /// Yields the best transaction to include if transactions from the mempool are allowed. best: Box Txs + 'a>, } @@ -351,14 +351,14 @@ impl<'a, Txs> OpBuilder<'a, Txs> { /// Holds the state after execution #[derive(Debug)] -pub struct ExecutedPayload { +pub(super) struct ExecutedPayload { /// Tracked execution info pub info: ExecutionInfo, } impl OpBuilder<'_, Txs> { /// Executes the payload and returns the outcome. - pub fn execute( + pub(super) fn execute( self, state: &mut State, ctx: &OpPayloadBuilderCtx, @@ -466,7 +466,7 @@ impl OpBuilder<'_, Txs> { } /// Builds the payload on top of the state. - pub fn build( + pub(super) fn build( self, mut state: State, ctx: OpPayloadBuilderCtx, diff --git a/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs b/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs index e009b221..f7898657 100644 --- a/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs +++ b/crates/builder/op-rbuilder/src/gas_limiter/metrics.rs @@ -7,7 +7,7 @@ use crate::gas_limiter::error::GasLimitError; #[derive(Metrics, Clone)] #[metrics(scope = "op_rbuilder.gas_limiter")] -pub struct GasLimiterMetrics { +pub(super) struct GasLimiterMetrics { /// Transactions rejected by gas limits Labeled by reason: "per_address", /// "global", "burst" pub rejections: Counter, @@ -23,7 +23,11 @@ pub struct GasLimiterMetrics { } impl GasLimiterMetrics { - pub fn record_gas_check(&self, check_result: &Result, duration: Duration) { + pub(super) fn record_gas_check( + &self, + check_result: &Result, + duration: Duration, + ) { if let Ok(created_new_bucket) = check_result { if *created_new_bucket { self.active_address_count.increment(1); @@ -35,7 +39,7 @@ impl GasLimiterMetrics { self.check_time.record(duration); } - pub fn record_refresh(&self, removed_addresses: usize, duration: Duration) { + pub(super) fn record_refresh(&self, removed_addresses: usize, duration: Duration) { self.active_address_count .decrement(removed_addresses as f64); self.refresh_duration.record(duration); diff --git a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs index 4554c5e6..a9cbbae6 100644 --- a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs +++ b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs @@ -5,7 +5,7 @@ use moka::future::Cache; use reth_transaction_pool::{AllTransactionsEvents, FullTransactionEvent}; use tracing::info; -pub async fn monitor_tx_pool( +pub(crate) async fn monitor_tx_pool( mut new_transactions: AllTransactionsEvents, reverted_cache: Cache, ) { From 7ba3416969f7340e671fff0d6315030df55b0321 Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 17 Sep 2025 10:21:29 -0700 Subject: [PATCH 184/262] Refactor payload builder to accept generic builder tx (#217) * Refactor payload builder to accept generic builder tx * Update crates/op-rbuilder/src/builders/builder_tx.rs Co-authored-by: Solar Mithril * Update crates/op-rbuilder/src/builders/builder_tx.rs Co-authored-by: Solar Mithril * Update crates/op-rbuilder/src/builders/flashblocks/service.rs Co-authored-by: Solar Mithril * Update crates/op-rbuilder/src/builders/builder_tx.rs Co-authored-by: Solar Mithril * fix lint * fix tests --------- Co-authored-by: Solar Mithril --- crates/builder/op-rbuilder/Cargo.toml | 1 + crates/builder/op-rbuilder/build.rs | 4 +- .../op-rbuilder/src/builders/builder_tx.rs | 173 +++++++++++++-- .../op-rbuilder/src/builders/context.rs | 202 ++---------------- .../src/builders/flashblocks/builder_tx.rs | 156 ++++++++++++++ .../src/builders/flashblocks/mod.rs | 4 +- .../src/builders/flashblocks/payload.rs | 108 ++++++---- .../src/builders/flashblocks/service.rs | 46 ++-- .../builder/op-rbuilder/src/builders/mod.rs | 3 +- .../src/builders/standard/builder_tx.rs | 146 +++++++++++++ .../op-rbuilder/src/builders/standard/mod.rs | 16 +- .../src/builders/standard/payload.rs | 161 ++++++-------- .../src/builders/standard/service.rs | 96 +++++++++ .../src/flashtestations/service.rs | 42 ++-- .../op-rbuilder/src/tests/flashblocks.rs | 17 +- .../src/tests/framework/instance.rs | 4 +- 16 files changed, 766 insertions(+), 413 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs create mode 100644 crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs create mode 100644 crates/builder/op-rbuilder/src/builders/standard/service.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 3d31df46..f91559de 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -62,6 +62,7 @@ alloy-primitives.workspace = true alloy-consensus.workspace = true alloy-contract.workspace = true alloy-eips.workspace = true +alloy-evm.workspace = true alloy-rpc-types-beacon.workspace = true alloy-rpc-types-engine.workspace = true alloy-transport-http.workspace = true diff --git a/crates/builder/op-rbuilder/build.rs b/crates/builder/op-rbuilder/build.rs index 205e8fa7..97acc1fe 100644 --- a/crates/builder/op-rbuilder/build.rs +++ b/crates/builder/op-rbuilder/build.rs @@ -59,10 +59,10 @@ fn main() -> Result<(), Box> { let author_name = env::var("VERGEN_GIT_COMMIT_AUTHOR_NAME")?; let author_email = env::var("VERGEN_GIT_COMMIT_AUTHOR_EMAIL")?; - let author_full = format!("{} <{}>", author_name, author_email); + let author_full = format!("{author_name} <{author_email}>"); // Set author full name - println!("cargo:rustc-env=VERGEN_GIT_COMMIT_AUTHOR={}", author_full); + println!("cargo:rustc-env=VERGEN_GIT_COMMIT_AUTHOR={author_full}"); let is_dirty = env::var("VERGEN_GIT_DIRTY")? == "true"; // > git describe --always --tags diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index 80c73c83..8ff168b1 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -1,32 +1,169 @@ +use alloy_evm::Database; +use alloy_primitives::{ + Address, + map::foldhash::{HashSet, HashSetExt}, +}; +use core::fmt::Debug; +use op_revm::OpTransactionError; +use reth_evm::{ConfigureEvm, Evm, eth::receipt_builder::ReceiptBuilderCtx}; +use reth_node_api::PayloadBuilderError; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; +use reth_provider::{ProviderError, StateProvider}; +use reth_revm::{ + State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, +}; +use revm::{ + DatabaseCommit, + context::result::{EVMError, ResultAndState}, +}; +use tracing::warn; -use crate::tx_signer::Signer; +use crate::{builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo}; -pub trait BuilderTx { - fn estimated_builder_tx_gas(&self) -> u64; - fn estimated_builder_tx_da_size(&self) -> Option; - fn signed_builder_tx(&self) -> Result, secp256k1::Error>; +#[derive(Debug, Clone)] +pub struct BuilderTransactionCtx { + pub gas_used: u64, + pub da_size: u64, + pub signed_tx: Recovered, } -// Scaffolding for how to construct the end of block builder transaction -// This will be the regular end of block transaction without the TEE key -#[derive(Clone)] -pub(super) struct StandardBuilderTx { - #[allow(dead_code)] - pub signer: Option, +/// Possible error variants during construction of builder txs. +#[derive(Debug, thiserror::Error)] +pub enum BuilderTransactionError { + /// Builder account load fails to get builder nonce + #[error("failed to load account {0}")] + AccountLoadFailed(Address), + /// Signature signing fails + #[error("failed to sign transaction: {0}")] + SigningError(secp256k1::Error), + /// Unrecoverable error during evm execution. + #[error("evm execution error {0}")] + EvmExecutionError(Box), + /// Any other builder transaction errors. + #[error(transparent)] + Other(Box), } -impl BuilderTx for StandardBuilderTx { - fn estimated_builder_tx_gas(&self) -> u64 { - todo!() +impl From for BuilderTransactionError { + fn from(error: secp256k1::Error) -> Self { + BuilderTransactionError::SigningError(error) } +} + +impl From> for BuilderTransactionError { + fn from(error: EVMError) -> Self { + BuilderTransactionError::EvmExecutionError(Box::new(error)) + } +} - fn estimated_builder_tx_da_size(&self) -> Option { - todo!() +impl From for PayloadBuilderError { + fn from(error: BuilderTransactionError) -> Self { + match error { + BuilderTransactionError::EvmExecutionError(e) => { + PayloadBuilderError::EvmExecutionError(e) + } + _ => PayloadBuilderError::Other(Box::new(error)), + } } +} + +pub trait BuilderTransactions: Debug { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError>; + + fn add_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + builder_ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + { + let mut evm = builder_ctx + .evm_config + .evm_with_env(&mut *db, builder_ctx.evm_env.clone()); + + let mut invalid: HashSet

= HashSet::new(); + + let builder_txs = + self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?; + for builder_tx in builder_txs.iter() { + if invalid.contains(&builder_tx.signed_tx.signer()) { + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); + continue; + } + + let ResultAndState { result, state } = evm + .transact(&builder_tx.signed_tx) + .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; + + if !result.is_success() { + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted"); + invalid.insert(builder_tx.signed_tx.signer()); + continue; + } + + // Add gas used by the transaction to cumulative gas used, before creating the receipt + let gas_used = result.gas_used(); + info.cumulative_gas_used += gas_used; + + let ctx = ReceiptBuilderCtx { + tx: builder_tx.signed_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(builder_ctx.build_receipt(ctx, None)); + + // Commit changes + evm.db_mut().commit(state); + + // Append sender and transaction to the respective lists + info.executed_senders.push(builder_tx.signed_tx.signer()); + info.executed_transactions + .push(builder_tx.signed_tx.clone().into_inner()); + } + + // Release the db reference by dropping evm + drop(evm); + + Ok(builder_txs) + } + } + + fn simulate_builder_txs_state( + &self, + state_provider: impl StateProvider + Clone, + builder_txs: Vec<&BuilderTransactionCtx>, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result>, BuilderTransactionError> { + let state = StateProviderDatabase::new(state_provider.clone()); + let mut simulation_state = State::builder() + .with_database(state) + .with_bundle_prestate(db.bundle_state.clone()) + .with_bundle_update() + .build(); + let mut evm = ctx + .evm_config + .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + + for builder_tx in builder_txs { + let ResultAndState { state, .. } = evm + .transact(&builder_tx.signed_tx) + .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; + + evm.db_mut().commit(state); + evm.db_mut().merge_transitions(BundleRetention::Reverts); + } - fn signed_builder_tx(&self) -> Result, secp256k1::Error> { - todo!() + Ok(simulation_state) } } diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 8129b9f5..95cb8579 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -1,12 +1,11 @@ -use alloy_consensus::{ - Eip658Value, Transaction, TxEip1559, conditional::BlockConditionalAttributes, -}; -use alloy_eips::{Encodable2718, Typed2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; +use alloy_consensus::{Eip658Value, Transaction, conditional::BlockConditionalAttributes}; +use alloy_eips::Typed2718; +use alloy_evm::Database; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_primitives::{Bytes, U256}; use alloy_rpc_types_eth::Withdrawals; use core::fmt::Debug; -use op_alloy_consensus::{OpDepositReceipt, OpTypedTransaction}; +use op_alloy_consensus::OpDepositReceipt; use op_revm::OpSpecId; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::PayloadConfig; @@ -27,17 +26,14 @@ use reth_optimism_txpool::{ interop::{MaybeInteropTransaction, is_valid_interop}, }; use reth_payload_builder::PayloadId; -use reth_primitives::{Recovered, SealedHeader}; +use reth_primitives::SealedHeader; use reth_primitives_traits::{InMemorySize, SignedTransaction}; -use reth_provider::ProviderError; use reth_revm::{State, context::Block}; use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction}; -use revm::{ - Database, DatabaseCommit, context::result::ResultAndState, interpreter::as_u64_saturated, -}; +use revm::{DatabaseCommit, context::result::ResultAndState, interpreter::as_u64_saturated}; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; -use tracing::{debug, info, trace, warn}; +use tracing::{debug, info, trace}; use crate::{ gas_limiter::AddressGasLimiter, @@ -50,7 +46,7 @@ use crate::{ /// Container type that holds all necessities to build a new payload. #[derive(Debug)] -pub(super) 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 @@ -195,16 +191,11 @@ impl OpPayloadBuilderCtx { pub(super) fn chain_id(&self) -> u64 { self.chain_spec.chain_id() } - - /// Returns the builder signer - pub(super) fn builder_signer(&self) -> Option { - self.builder_signer - } } impl OpPayloadBuilderCtx { /// Constructs a receipt for the given transaction. - fn build_receipt( + pub fn build_receipt( &self, ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, deposit_nonce: Option, @@ -236,18 +227,13 @@ 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, - state: &mut State, - ) -> Result, PayloadBuilderError> - where - DB: Database + std::fmt::Debug, - { + db: &mut State, + ) -> Result, PayloadBuilderError> { let mut info = ExecutionInfo::with_capacity(self.attributes().transactions.len()); - let mut evm = self - .evm_config - .evm_with_env(&mut *state, self.evm_env.clone()); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); for sequencer_tx in &self.attributes().transactions { // A sequencer's block should never contain blob transactions. @@ -325,17 +311,14 @@ 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, - state: &mut State, + db: &mut State, best_txs: &mut impl PayloadTxsBounds, block_gas_limit: u64, block_da_limit: Option, - ) -> Result, PayloadBuilderError> - where - DB: Database + std::fmt::Debug, - { + ) -> Result, PayloadBuilderError> { let execute_txs_start_time = Instant::now(); let mut num_txs_considered = 0; let mut num_txs_simulated = 0; @@ -344,9 +327,7 @@ impl OpPayloadBuilderCtx { let mut num_bundles_reverted = 0; let base_fee = self.base_fee(); let tx_da_limit = self.da_config.max_da_tx_size(); - let mut evm = self - .evm_config - .evm_with_env(&mut *state, self.evm_env.clone()); + let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); info!( target: "payload_builder", @@ -566,151 +547,4 @@ impl OpPayloadBuilderCtx { ); Ok(None) } - - pub(super) fn add_builder_tx( - &self, - info: &mut ExecutionInfo, - state: &mut State, - builder_tx_gas: u64, - message: Vec, - ) -> Option<()> - where - DB: Database + std::fmt::Debug, - { - self.builder_signer() - .map(|signer| { - let base_fee = self.base_fee(); - let chain_id = self.chain_id(); - // Create and sign the transaction - let builder_tx = - signed_builder_tx(state, builder_tx_gas, message, signer, base_fee, chain_id)?; - - let mut evm = self - .evm_config - .evm_with_env(&mut *state, self.evm_env.clone()); - - let ResultAndState { - result, - state: new_state, - } = evm - .transact(&builder_tx) - .map_err(|err| PayloadBuilderError::EvmExecutionError(Box::new(err)))?; - - // Add gas used by the transaction to cumulative gas used, before creating the receipt - info.cumulative_gas_used += result.gas_used(); - - let ctx = ReceiptBuilderCtx { - tx: builder_tx.inner(), - evm: &evm, - result, - state: &new_state, - cumulative_gas_used: info.cumulative_gas_used, - }; - info.receipts.push(self.build_receipt(ctx, None)); - - // Release the db reference by dropping evm - drop(evm); - // Commit changes - state.commit(new_state); - - // Append sender and transaction to the respective lists - info.executed_senders.push(builder_tx.signer()); - info.executed_transactions.push(builder_tx.into_inner()); - Ok(()) - }) - .transpose() - .unwrap_or_else(|err: PayloadBuilderError| { - warn!(target: "payload_builder", %err, "Failed to add builder transaction"); - None - }) - } - - /// Calculates EIP 2718 builder transaction size - // TODO: this function could be improved, ideally we shouldn't take mut ref to db and maybe - // it's possible to do this without db at all - pub(super) fn estimate_builder_tx_da_size( - &self, - state: &mut State, - builder_tx_gas: u64, - message: Vec, - ) -> Option - where - DB: Database, - { - self.builder_signer() - .map(|signer| { - let base_fee = self.base_fee(); - let chain_id = self.chain_id(); - // Create and sign the transaction - let builder_tx = - signed_builder_tx(state, builder_tx_gas, message, signer, base_fee, chain_id)?; - Ok(op_alloy_flz::tx_estimated_size_fjord_bytes( - builder_tx.encoded_2718().as_slice(), - )) - }) - .transpose() - .unwrap_or_else(|err: PayloadBuilderError| { - warn!(target: "payload_builder", %err, "Failed to add builder transaction"); - None - }) - } -} - -pub(super) fn estimate_gas_for_builder_tx(input: Vec) -> 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; - - std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) -} - -/// Creates signed builder tx to Address::ZERO and specified message as input -pub(super) fn signed_builder_tx( - state: &mut State, - builder_tx_gas: u64, - message: Vec, - signer: Signer, - base_fee: u64, - chain_id: u64, -) -> Result, PayloadBuilderError> -where - DB: Database, -{ - // Create message with block number for the builder to sign - let nonce = state - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::AccountLoadFailed(signer.address)) - })?; - - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit: builder_tx_gas, - max_fee_per_gas: 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(PayloadBuilderError::other)?; - - Ok(builder_tx) } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs new file mode 100644 index 00000000..be29e6aa --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -0,0 +1,156 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; +use alloy_evm::Database; +use alloy_primitives::{Address, TxKind}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::Recovered; +use reth_provider::StateProvider; +use reth_revm::State; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, + context::OpPayloadBuilderCtx, flashblocks::payload::FlashblocksExtraCtx, + }, + flashtestations::service::FlashtestationsBuilderTx, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +// This will be the end of block transaction of a regular block +#[derive(Debug, Clone)] +pub(super) struct FlashblocksBuilderTx { + pub signer: Option, + pub flashtestations_builder_tx: Option, +} + +impl FlashblocksBuilderTx { + pub(super) fn new( + signer: Option, + flashtestations_builder_tx: Option, + ) -> Self { + Self { + signer, + flashtestations_builder_tx, + } + } + + pub(super) fn simulate_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> 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, + })) + } + 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; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) + } + + fn signed_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + signer: Signer, + gas_used: u64, + message: Vec, + ) -> Result, BuilderTransactionError> { + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(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) + } +} + +impl BuilderTransactions for FlashblocksBuilderTx { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + + if ctx.is_first_flashblock() { + let flashblocks_builder_tx = self.simulate_builder_tx(ctx, db)?; + builder_txs.extend(flashblocks_builder_tx.clone()); + } + + if ctx.is_last_flashblock() { + let flashblocks_builder_tx = self.simulate_builder_tx(ctx, db)?; + builder_txs.extend(flashblocks_builder_tx.clone()); + if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { + // We only include flashtestations txs in the last flashblock + + let mut simulation_state = self.simulate_builder_txs_state::( + state_provider.clone(), + flashblocks_builder_tx.iter().collect(), + ctx, + db, + )?; + let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + &mut simulation_state, + )?; + builder_txs.extend(flashtestations_builder_txs); + } + } + 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 index da20a061..10503fa5 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs @@ -3,9 +3,9 @@ use crate::traits::{NodeBounds, PoolBounds}; use config::FlashblocksConfig; use service::FlashblocksServiceBuilder; -mod config; -//mod context; mod best_txs; +mod builder_tx; +mod config; mod payload; mod service; mod wspub; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index df3b12aa..ede6daf8 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -1,10 +1,11 @@ use super::{config::FlashblocksConfig, wspub::WebSocketPublisher}; use crate::{ builders::{ - BuilderConfig, BuilderTx, - context::{OpPayloadBuilderCtx, estimate_gas_for_builder_tx}, + BuilderConfig, + builder_tx::BuilderTransactions, + context::OpPayloadBuilderCtx, flashblocks::{best_txs::BestFlashblocksTxs, config::FlashBlocksConfigExt}, - generator::{BlockCell, BuildArguments}, + generator::{BlockCell, BuildArguments, PayloadBuilder}, }, gas_limiter::AddressGasLimiter, metrics::OpRBuilderMetrics, @@ -62,7 +63,7 @@ struct ExtraExecutionInfo { } #[derive(Debug, Default)] -struct FlashblocksExtraCtx { +pub struct FlashblocksExtraCtx { /// Current flashblock index pub flashblock_index: u64, /// Target flashblock count @@ -92,15 +93,20 @@ impl OpPayloadBuilderCtx { self.extra_ctx.target_flashblock_count } + /// Returns if the flashblock is the first fallback block + pub(crate) fn is_first_flashblock(&self) -> bool { + self.flashblock_index() == 0 + } + /// Returns if the flashblock is the last one pub(crate) fn is_last_flashblock(&self) -> bool { - self.flashblock_index() == self.target_flashblock_count() - 1 + 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 @@ -115,8 +121,7 @@ pub(super) struct OpPayloadBuilder { /// The metrics for the builder pub metrics: Arc, /// The end of builder transaction type - #[allow(dead_code)] - pub builder_tx: BT, + pub builder_tx: BuilderTx, /// Builder events handle to send BuiltPayload events pub payload_builder_handle: Arc>>>, @@ -124,14 +129,14 @@ pub(super) struct OpPayloadBuilder { pub address_gas_limiter: AddressGasLimiter, } -impl OpPayloadBuilder { +impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. pub(super) fn new( evm_config: OpEvmConfig, pool: Pool, client: Client, config: BuilderConfig, - builder_tx: BT, + builder_tx: BuilderTx, payload_builder_handle: Arc< OnceLock>>, >, @@ -153,12 +158,12 @@ 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, - BT: Clone + Send + Sync, + BuilderTx: Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; @@ -181,11 +186,11 @@ where } } -impl OpPayloadBuilder +impl OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BT: BuilderTx, + BuilderTx: BuilderTransactions, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -282,24 +287,22 @@ where .with_bundle_update() .build(); - // We subtract gas limit and da limit for builder transaction from the whole limit - let message = format!("Block Number: {}", ctx.block_number()).into_bytes(); - let builder_tx_gas = ctx - .builder_signer() - .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); - let builder_tx_da_size = ctx - .estimate_builder_tx_da_size(&mut state, builder_tx_gas, message.clone()) - .unwrap_or(0); - let mut info = execute_pre_steps(&mut state, &ctx)?; 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); - // If we have payload with txpool we add first builder tx right after deposits - if !ctx.attributes().no_tx_pool { - ctx.add_builder_tx(&mut info, &mut state, builder_tx_gas, message.clone()); - } + // We add first builder tx right after deposits + let builder_txs = if ctx.attributes().no_tx_pool { + vec![] + } else { + self.builder_tx + .add_builder_txs(&state_provider, &mut info, &ctx, &mut state)? + }; + + // We subtract gas limit and da limit for builder transaction from the whole limit + let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); + let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); let (payload, fb_payload) = build_block( &mut state, @@ -426,15 +429,17 @@ where match fb_cancel_token { Some(cancel_token) => { + // fallback block is index 0, so we need to increment here + ctx.increment_flashblock_index(); // We use fb_cancel_token inside context so we could exit from // execute_best_transaction without cancelling parent token ctx.cancel = cancel_token; // TODO: remove this - if ctx.flashblock_index() >= ctx.target_flashblock_count() { + if ctx.flashblock_index() > ctx.target_flashblock_count() { info!( target: "payload_builder", target = ctx.target_flashblock_count(), - flashblock_count = ctx.flashblock_index(), + flashblock_index = ctx.flashblock_index(), block_number = ctx.block_number(), "Skipping flashblock reached target", ); @@ -444,7 +449,7 @@ where info!( target: "payload_builder", block_number = ctx.block_number(), - flashblock_count = ctx.flashblock_index(), + flashblock_index = ctx.flashblock_index(), target_gas = total_gas_per_batch, gas_used = info.cumulative_gas_used, target_da = total_da_per_batch.unwrap_or(0), @@ -452,13 +457,21 @@ where "Building flashblock", ); let flashblock_build_start_time = Instant::now(); - // If it is the last flashblock, we need to account for the builder tx - if ctx.is_last_flashblock() { - total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); - // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size - if let Some(da_limit) = total_da_per_batch.as_mut() { - *da_limit = da_limit.saturating_sub(builder_tx_da_size); - } + + let builder_txs = self.builder_tx.simulate_builder_txs( + &state_provider, + &mut info, + &ctx, + &mut state, + )?; + let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); + let builder_tx_da_size: u64 = + builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + + total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); + // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size + if let Some(da_limit) = total_da_per_batch.as_mut() { + *da_limit = da_limit.saturating_sub(builder_tx_da_size); } let best_txs_start_time = Instant::now(); @@ -516,10 +529,12 @@ where .payload_tx_simulation_gauge .set(payload_tx_simulation_time); - // If it is the last flashblocks, add the builder txn to the block if enabled - if ctx.is_last_flashblock() { - ctx.add_builder_tx(&mut info, &mut state, builder_tx_gas, message.clone()); - }; + self.builder_tx.add_builder_txs( + &state_provider, + &mut info, + &ctx, + &mut state, + )?; let total_block_built_duration = Instant::now(); let build_result = @@ -544,7 +559,7 @@ where return Err(err); } Ok((new_payload, mut fb_payload)) => { - fb_payload.index = ctx.increment_flashblock_index(); // fallback block is index 0, so we need to increment here + fb_payload.index = ctx.flashblock_index(); fb_payload.base = None; // We check that child_job got cancelled before sending flashblock. @@ -599,7 +614,7 @@ where info!( target: "payload_builder", message = "Flashblock built", - flashblock_count = ctx.flashblock_index(), + flashblock_index = ctx.flashblock_index(), current_gas = info.cumulative_gas_used, current_da = info.cumulative_da_bytes_used, target_flashblocks = flashblocks_per_block, @@ -783,12 +798,11 @@ where } } -impl crate::builders::generator::PayloadBuilder - for OpPayloadBuilder +impl PayloadBuilder for OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BT: BuilderTx + Clone + Send + Sync, + BuilderTx: BuilderTransactions + Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index ceea02f1..a8b4d3ba 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -1,10 +1,12 @@ use super::{FlashblocksConfig, payload::OpPayloadBuilder}; use crate::{ builders::{ - BuilderConfig, BuilderTx, builder_tx::StandardBuilderTx, + BuilderConfig, + builder_tx::BuilderTransactions, + flashblocks::{builder_tx::FlashblocksBuilderTx, payload::FlashblocksExtraCtx}, generator::BlockPayloadJobGenerator, }, - flashtestations::service::spawn_flashtestations_service, + flashtestations::service::bootstrap_flashtestations, traits::{NodeBounds, PoolBounds}, }; use reth_basic_payload_builder::BasicPayloadJobGeneratorConfig; @@ -18,16 +20,16 @@ use std::sync::Arc; pub struct FlashblocksServiceBuilder(pub BuilderConfig); impl FlashblocksServiceBuilder { - fn spawn_payload_builder_service( + fn spawn_payload_builder_service( self, ctx: &BuilderContext, pool: Pool, - builder_tx: BT, + builder_tx: BuilderTx, ) -> eyre::Result::Payload>> where Node: NodeBounds, Pool: PoolBounds, - BT: BuilderTx + Unpin + Clone + Send + Sync + 'static, + BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static, { let once_lock = Arc::new(std::sync::OnceLock::new()); @@ -78,30 +80,22 @@ where pool: Pool, _: OpEvmConfig, ) -> eyre::Result::Payload>> { - tracing::debug!("Spawning flashblocks payload builder service"); let signer = self.0.builder_signer; - if self.0.flashtestations_config.flashtestations_enabled { - let flashtestations_service = match spawn_flashtestations_service( - self.0.flashtestations_config.clone(), - ctx, - ) - .await - { - Ok(service) => service, + let flashtestations_builder_tx = if self.0.flashtestations_config.flashtestations_enabled { + match bootstrap_flashtestations(self.0.flashtestations_config.clone(), ctx).await { + Ok(builder_tx) => Some(builder_tx), Err(e) => { - tracing::warn!(error = %e, "Failed to spawn flashtestations service, falling back to standard builder tx"); - return self.spawn_payload_builder_service( - ctx, - pool, - StandardBuilderTx { signer }, - ); + tracing::warn!(error = %e, "Failed to bootstrap flashtestations, builder will not include flashtestations txs"); + None } - }; - - if self.0.flashtestations_config.enable_block_proofs { - return self.spawn_payload_builder_service(ctx, pool, flashtestations_service); } - } - self.spawn_payload_builder_service(ctx, pool, StandardBuilderTx { signer }) + } else { + None + }; + self.spawn_payload_builder_service( + ctx, + pool, + FlashblocksBuilderTx::new(signer, flashtestations_builder_tx), + ) } } diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 575e5bf4..8dcde8eb 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -21,7 +21,8 @@ mod flashblocks; mod generator; mod standard; -pub use builder_tx::BuilderTx; +pub use builder_tx::{BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions}; +pub use context::OpPayloadBuilderCtx; pub use flashblocks::FlashblocksBuilder; pub use standard::StandardBuilder; diff --git a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs new file mode 100644 index 00000000..23c39f3c --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs @@ -0,0 +1,146 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; +use alloy_evm::Database; +use alloy_primitives::{Address, TxKind}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::Recovered; +use reth_provider::StateProvider; +use reth_revm::State; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, + context::OpPayloadBuilderCtx, + }, + flashtestations::service::FlashtestationsBuilderTx, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +// This will be the end of block transaction of a regular block +#[derive(Debug, Clone)] +pub(super) struct StandardBuilderTx { + pub signer: Option, + pub flashtestations_builder_tx: Option, +} + +impl StandardBuilderTx { + pub(super) fn new( + signer: Option, + flashtestations_builder_tx: Option, + ) -> Self { + Self { + signer, + flashtestations_builder_tx, + } + } + + pub(super) fn simulate_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> 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, + })) + } + 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; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) + } + + fn signed_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + signer: Signer, + gas_used: u64, + message: Vec, + ) -> Result, BuilderTransactionError> { + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(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) + } +} + +impl BuilderTransactions for StandardBuilderTx { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + let mut builder_txs = Vec::::new(); + let standard_builder_tx = self.simulate_builder_tx(ctx, db)?; + builder_txs.extend(standard_builder_tx.clone()); + if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { + let mut simulation_state = self.simulate_builder_txs_state::<()>( + state_provider.clone(), + standard_builder_tx.iter().collect(), + ctx, + db, + )?; + let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + &mut simulation_state, + )?; + builder_txs.extend(flashtestations_builder_txs); + } + 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 index 98e9b44f..e26bbc6c 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/mod.rs @@ -1,11 +1,13 @@ -use payload::StandardPayloadBuilderBuilder; -use reth_node_builder::components::BasicPayloadServiceBuilder; - -use crate::traits::{NodeBounds, PoolBounds}; +use crate::{ + builders::standard::service::StandardServiceBuilder, + traits::{NodeBounds, PoolBounds}, +}; use super::BuilderConfig; +mod builder_tx; mod payload; +mod service; /// Block building strategy that builds blocks using the standard approach by /// producing blocks every chain block time. @@ -15,7 +17,7 @@ impl super::PayloadBuilder for StandardBuilder { type Config = (); type ServiceBuilder - = BasicPayloadServiceBuilder + = StandardServiceBuilder where Node: NodeBounds, Pool: PoolBounds; @@ -27,8 +29,6 @@ impl super::PayloadBuilder for StandardBuilder { Node: NodeBounds, Pool: PoolBounds, { - Ok(BasicPayloadServiceBuilder::new( - StandardPayloadBuilderBuilder(config), - )) + 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 index daebf7dd..52acef4b 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -1,22 +1,22 @@ +use super::super::context::OpPayloadBuilderCtx; use crate::{ - builders::{BuilderConfig, generator::BuildArguments}, - flashtestations::service::spawn_flashtestations_service, + builders::{BuilderConfig, BuilderTransactions, generator::BuildArguments}, gas_limiter::AddressGasLimiter, metrics::OpRBuilderMetrics, primitives::reth::ExecutionInfo, - traits::{ClientBounds, NodeBounds, PayloadTxsBounds, PoolBounds}, + traits::{ClientBounds, PayloadTxsBounds, PoolBounds}, }; 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::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; use reth_evm::{ConfigureEvm, execute::BlockBuilder}; use reth_node_api::{Block, PayloadBuilderError}; -use reth_node_builder::{BuilderContext, components::PayloadBuilderBuilder}; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; @@ -24,69 +24,20 @@ use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned}; use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; use reth_primitives::RecoveredBlock; -use reth_provider::{ - ExecutionOutcome, HashedPostStateProvider, ProviderError, StateRootProvider, - StorageRootProvider, -}; +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 revm::Database; use std::{sync::Arc, time::Instant}; use tokio_util::sync::CancellationToken; use tracing::{error, info, warn}; -use super::super::context::{OpPayloadBuilderCtx, estimate_gas_for_builder_tx}; - -pub struct StandardPayloadBuilderBuilder(pub BuilderConfig<()>); - -impl PayloadBuilderBuilder for StandardPayloadBuilderBuilder -where - Node: NodeBounds, - Pool: PoolBounds, -{ - type PayloadBuilder = StandardOpPayloadBuilder; - - async fn build_payload_builder( - self, - ctx: &BuilderContext, - pool: Pool, - _evm_config: OpEvmConfig, - ) -> eyre::Result { - if self.0.flashtestations_config.flashtestations_enabled { - match spawn_flashtestations_service(self.0.flashtestations_config.clone(), ctx).await { - Ok(service) => service, - Err(e) => { - tracing::warn!(error = %e, "Failed to spawn flashtestations service, falling back to standard builder tx"); - return Ok(StandardOpPayloadBuilder::new( - OpEvmConfig::optimism(ctx.chain_spec()), - pool, - ctx.provider().clone(), - self.0.clone(), - )); - } - }; - - if self.0.flashtestations_config.enable_block_proofs { - // TODO: flashtestations end of block transaction - } - } - - Ok(StandardOpPayloadBuilder::new( - OpEvmConfig::optimism(ctx.chain_spec()), - pool, - ctx.provider().clone(), - self.0.clone(), - )) - } -} - /// Optimism's payload builder #[derive(Debug, Clone)] -pub struct StandardOpPayloadBuilder { +pub(super) struct StandardOpPayloadBuilder { /// The type responsible for creating the evm. pub evm_config: OpEvmConfig, /// The transaction pool @@ -102,15 +53,18 @@ pub struct StandardOpPayloadBuilder { 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 { +impl StandardOpPayloadBuilder { /// `OpPayloadBuilder` constructor. - pub fn new( + 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 { @@ -121,12 +75,15 @@ impl StandardOpPayloadBuilder { 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 trait OpPayloadTransactions: Clone + Send + Sync + Unpin + 'static { +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>( @@ -151,11 +108,12 @@ impl OpPayloadTransactions for () { } } -impl reth_basic_payload_builder::PayloadBuilder - for StandardOpPayloadBuilder +impl reth_basic_payload_builder::PayloadBuilder + for StandardOpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, + BuilderTx: BuilderTransactions + Clone + Send + Sync, Txs: OpPayloadTransactions, { type Attributes = OpPayloadBuilderAttributes; @@ -214,10 +172,11 @@ where } } -impl StandardOpPayloadBuilder +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 @@ -289,22 +248,21 @@ where 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 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, ctx) + 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, ctx) + builder.build(state, &state_provider, ctx, self.builder_tx.clone()) } .map(|out| { let total_block_building_time = block_build_start_time.elapsed(); @@ -358,28 +316,29 @@ pub(super) struct ExecutedPayload { impl OpBuilder<'_, Txs> { /// Executes the payload and returns the outcome. - pub(super) fn execute( + pub(crate) fn execute( self, - state: &mut State, + state_provider: impl StateProvider, + db: &mut State, ctx: &OpPayloadBuilderCtx, + builder_tx: BuilderTx, ) -> Result, PayloadBuilderError> where - DB: Database + AsRef

+ std::fmt::Debug, - P: StorageRootProvider, + 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(state, ctx.parent(), ctx.block_env_attributes.clone()) + .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(state)?; + 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); @@ -388,17 +347,16 @@ impl OpBuilder<'_, Txs> { // 4. if mem pool transactions are requested we execute them // gas reserved for builder tx - let message = format!("Block Number: {}", ctx.block_number()) - .as_bytes() - .to_vec(); - let builder_tx_gas = ctx - .builder_signer() - .map_or(0, |_| estimate_gas_for_builder_tx(message.clone())); - let block_gas_limit = ctx.block_gas_limit() - builder_tx_gas; + let builder_txs = builder_tx.simulate_builder_txs(&state_provider, &mut info, ctx, db)?; + 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 = ctx - .estimate_builder_tx_da_size(state, builder_tx_gas, message.clone()) - .unwrap_or(0); + 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() @@ -424,7 +382,7 @@ impl OpBuilder<'_, Txs> { if ctx .execute_best_transactions( &mut info, - state, + db, &mut best_txs, block_gas_limit, block_da_limit, @@ -436,13 +394,13 @@ impl OpBuilder<'_, Txs> { } // Add builder tx to the block - ctx.add_builder_tx(&mut info, state, builder_tx_gas, message); + builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db)?; let state_merge_start_time = Instant::now(); // merge all transitions into bundle state, this would apply the withdrawal balance changes // and 4788 contract call - state.merge_transitions(BundleRetention::Reverts); + db.merge_transitions(BundleRetention::Reverts); let state_transition_merge_time = state_merge_start_time.elapsed(); ctx.metrics @@ -466,24 +424,32 @@ impl OpBuilder<'_, Txs> { } /// Builds the payload on top of the state. - pub(super) fn build( + pub(super) fn build( self, - mut state: State, + state: impl Database, + state_provider: impl StateProvider, ctx: OpPayloadBuilderCtx, + builder_tx: BuilderTx, ) -> Result, PayloadBuilderError> where - DB: Database + AsRef

+ std::fmt::Debug, - P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + BuilderTx: BuilderTransactions, { - let ExecutedPayload { info } = match self.execute(&mut state, &ctx)? { - BuildOutcomeKind::Better { payload } | BuildOutcomeKind::Freeze(payload) => payload, - BuildOutcomeKind::Cancelled => return Ok(BuildOutcomeKind::Cancelled), - BuildOutcomeKind::Aborted { fees } => return Ok(BuildOutcomeKind::Aborted { fees }), - }; + 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(); let execution_outcome = ExecutionOutcome::new( - state.take_bundle(), + db.take_bundle(), vec![info.receipts], block_number, Vec::new(), @@ -504,12 +470,9 @@ impl OpBuilder<'_, Txs> { // calculate the state root let state_root_start_time = Instant::now(); - let state_provider = state.database.as_ref(); let hashed_state = state_provider.hashed_post_state(execution_outcome.state()); let (state_root, trie_output) = { - state - .database - .as_ref() + state_provider .state_root_with_updates(hashed_state.clone()) .inspect_err(|err| { warn!(target: "payload_builder", @@ -533,7 +496,7 @@ impl OpBuilder<'_, Txs> { // `l2tol1-message-passer` ( Some( - isthmus::withdrawals_root(execution_outcome.state(), state.database.as_ref()) + isthmus::withdrawals_root(execution_outcome.state(), state_provider) .map_err(PayloadBuilderError::other)?, ), Some(EMPTY_REQUESTS_HASH), diff --git a/crates/builder/op-rbuilder/src/builders/standard/service.rs b/crates/builder/op-rbuilder/src/builders/standard/service.rs new file mode 100644 index 00000000..c713b69c --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/standard/service.rs @@ -0,0 +1,96 @@ +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}, + }, + flashtestations::service::bootstrap_flashtestations, + traits::{NodeBounds, PoolBounds}, +}; + +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.clone(), + 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; + let flashtestations_builder_tx = if self.0.flashtestations_config.flashtestations_enabled { + match bootstrap_flashtestations::(self.0.flashtestations_config.clone(), ctx) + .await + { + Ok(builder_tx) => Some(builder_tx), + Err(e) => { + tracing::warn!(error = %e, "Failed to bootstrap flashtestations, builderb will not include flashtestations txs"); + None + } + } + } else { + None + }; + + self.spawn_payload_builder_service( + evm_config, + ctx, + pool, + StandardBuilderTx::new(signer, flashtestations_builder_tx), + ) + } +} diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 411ef4bf..ffcd2276 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -2,12 +2,17 @@ use std::sync::Arc; use alloy_primitives::U256; use reth_node_builder::BuilderContext; -use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; +use reth_provider::StateProvider; +use reth_revm::State; +use revm::Database; +use std::fmt::Debug; use tracing::{info, warn}; use crate::{ - builders::BuilderTx, + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, + }, + primitives::reth::ExecutionInfo, traits::NodeBounds, tx_signer::{Signer, generate_ethereum_keypair}, }; @@ -21,7 +26,7 @@ use super::{ #[derive(Clone)] pub struct FlashtestationsService { // Attestation provider generating attestations - attestation_provider: Arc>, + attestation_provider: Arc>, // Handles the onchain attestation and TEE block building proofs tx_manager: TxManager, // TEE service generated key @@ -86,24 +91,25 @@ impl FlashtestationsService { } } -impl BuilderTx for FlashtestationsService { - fn estimated_builder_tx_gas(&self) -> u64 { - todo!() - } - - fn estimated_builder_tx_da_size(&self) -> Option { - todo!() - } - - fn signed_builder_tx(&self) -> Result, secp256k1::Error> { - todo!() +#[derive(Debug, Clone)] +pub struct FlashtestationsBuilderTx {} + +impl BuilderTransactions for FlashtestationsBuilderTx { + fn simulate_builder_txs( + &self, + _state_provider: impl StateProvider + Clone, + _info: &mut ExecutionInfo, + _ctx: &OpPayloadBuilderCtx, + _db: &mut State, + ) -> Result, BuilderTransactionError> { + Ok(vec![]) } } -pub async fn spawn_flashtestations_service( +pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, ctx: &BuilderContext, -) -> eyre::Result +) -> eyre::Result where Node: NodeBounds, { @@ -131,7 +137,7 @@ where }, ); - Ok(flashtestations_service) + Ok(FlashtestationsBuilderTx {}) } #[cfg(test)] diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index eca81b4d..d9789457 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -189,7 +189,12 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> let block = driver .build_new_block_with_current_timestamp(Some(Duration::from_millis(i * 100))) .await?; - assert_eq!(block.transactions.len(), 8, "Got: {:?}", block.transactions); // 5 normal txn + deposit + 2 builder txn + assert_eq!( + block.transactions.len(), + 8, + "Got: {:#?}", + block.transactions + ); // 5 normal txn + deposit + 2 builder txn tokio::time::sleep(std::time::Duration::from_secs(1)).await; } @@ -383,17 +388,17 @@ async fn test_flashblock_min_max_filtering(rbuilder: LocalInstance) -> eyre::Res let _block = driver.build_new_block_with_current_timestamp(None).await?; - // It ends up in the flashblock with index 3. Flashblock number and index - // are different. + // It ends up in the 2nd flashblock assert_eq!( - 2 + 1, + 2, flashblocks_listener .find_transaction_flashblock(tx1.tx_hash()) - .unwrap() + .unwrap(), + "Transaction should be in the 2nd flashblock" ); let flashblocks = flashblocks_listener.get_flashblocks(); - assert_eq!(6, flashblocks.len()); + assert_eq!(6, flashblocks.len(), "Flashblocks length should be 6"); flashblocks_listener.stop().await } diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 041136c1..7a9ba14b 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -418,7 +418,7 @@ impl FlashblocksListener { /// Check if any flashblock contains the given transaction hash pub fn contains_transaction(&self, tx_hash: &B256) -> bool { - let tx_hash_str = format!("{:#x}", tx_hash); + let tx_hash_str = format!("{tx_hash:#x}"); self.flashblocks.lock().iter().any(|fb| { if let Some(receipts) = fb.metadata.get("receipts") { if let Some(receipts_obj) = receipts.as_object() { @@ -431,7 +431,7 @@ impl FlashblocksListener { /// Find which flashblock index contains the given transaction hash pub fn find_transaction_flashblock(&self, tx_hash: &B256) -> Option { - let tx_hash_str = format!("{:#x}", tx_hash); + let tx_hash_str = format!("{tx_hash:#x}"); self.flashblocks.lock().iter().find_map(|fb| { if let Some(receipts) = fb.metadata.get("receipts") { if let Some(receipts_obj) = receipts.as_object() { From 18137889b4f2f06db193946f2fea7d3df5f26af6 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:22:38 -0400 Subject: [PATCH 185/262] refactor: clean up and improve flashblocks `build_payload` (#260) --- .../src/builders/flashblocks/best_txs.rs | 20 +- .../src/builders/flashblocks/payload.rs | 574 ++++++++++-------- .../op-rbuilder/src/builders/generator.rs | 8 +- 3 files changed, 325 insertions(+), 277 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs index 3d4021c7..2f36c96d 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs @@ -1,7 +1,7 @@ use alloy_primitives::{Address, TxHash}; use reth_payload_util::PayloadTransactions; -use reth_transaction_pool::PoolTransaction; -use std::collections::HashSet; +use reth_transaction_pool::{PoolTransaction, ValidPoolTransaction}; +use std::{collections::HashSet, sync::Arc}; use tracing::debug; use crate::tx::MaybeFlashblockFilter; @@ -9,9 +9,9 @@ use crate::tx::MaybeFlashblockFilter; pub(super) struct BestFlashblocksTxs where T: PoolTransaction, - I: PayloadTransactions, + I: Iterator>>, { - inner: I, + inner: reth_payload_util::BestPayloadTransactions, current_flashblock_number: u64, // Transactions that were already commited to the state. Using them again would cause NonceTooLow // so we skip them @@ -21,9 +21,9 @@ where impl BestFlashblocksTxs where T: PoolTransaction, - I: PayloadTransactions, + I: Iterator>>, { - pub(super) fn new(inner: I) -> Self { + pub(super) fn new(inner: reth_payload_util::BestPayloadTransactions) -> Self { Self { inner, current_flashblock_number: 0, @@ -33,7 +33,11 @@ where /// Replaces current iterator with new one. We use it on new flashblock building, to refresh /// priority boundaries - pub(super) fn refresh_iterator(&mut self, inner: I, current_flashblock_number: u64) { + pub(super) fn refresh_iterator( + &mut self, + inner: reth_payload_util::BestPayloadTransactions, + current_flashblock_number: u64, + ) { self.inner = inner; self.current_flashblock_number = current_flashblock_number; } @@ -47,7 +51,7 @@ where impl PayloadTransactions for BestFlashblocksTxs where T: PoolTransaction + MaybeFlashblockFilter, - I: PayloadTransactions, + I: Iterator>>, { type Transaction = T; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index ede6daf8..05228f82 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -38,6 +38,7 @@ use reth_provider::{ use reth_revm::{ State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, }; +use reth_transaction_pool::TransactionPool; use reth_trie::{HashedPostState, updates::TrieUpdates}; use revm::Database; use rollup_boost::{ @@ -49,13 +50,23 @@ use std::{ sync::{Arc, OnceLock}, time::Instant, }; -use tokio::sync::{ - mpsc, - mpsc::{Sender, error::SendError}, -}; +use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, metadata::Level, span, warn}; +type NextBestFlashblocksTxs = BestFlashblocksTxs< + ::Transaction, + Box< + dyn reth_transaction_pool::BestTransactions< + Item = Arc< + reth_transaction_pool::ValidPoolTransaction< + ::Transaction, + >, + >, + >, + >, +>; + #[derive(Debug, Default)] struct ExtraExecutionInfo { /// Index of the last consumed flashblock @@ -65,9 +76,19 @@ struct ExtraExecutionInfo { #[derive(Debug, Default)] pub struct FlashblocksExtraCtx { /// Current flashblock index - pub flashblock_index: u64, - /// Target flashblock count - pub target_flashblock_count: u64, + 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 + total_da_per_batch: Option, + /// Gas limit per flashblock + gas_per_batch: u64, + /// DA bytes limit per flashblock + da_per_batch: Option, + /// Whether to calculate the state root for each flashblock + calculate_state_root: bool, } impl OpPayloadBuilderCtx { @@ -190,7 +211,7 @@ impl OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BuilderTx: BuilderTransactions, + BuilderTx: BuilderTransactions + Send + Sync, { /// Constructs an Optimism payload from the transactions sent via the /// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in @@ -200,7 +221,7 @@ where /// 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( + async fn build_payload( &self, args: BuildArguments, OpBuiltPayload>, best_payload: BlockCell, @@ -270,6 +291,11 @@ where extra_ctx: FlashblocksExtraCtx { flashblock_index: 0, target_flashblock_count: self.config.flashblocks_per_block(), + target_gas_for_batch: 0, + total_da_per_batch: None, + gas_per_batch: 0, + da_per_batch: None, + calculate_state_root, }, max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), @@ -373,7 +399,7 @@ where .first_flashblock_time_offset .record(first_flashblock_offset.as_millis() as f64); let gas_per_batch = ctx.block_gas_limit() / ctx.target_flashblock_count(); - let mut total_gas_per_batch = gas_per_batch; + let target_gas_for_batch = gas_per_batch; let da_per_batch = ctx .da_config .max_da_block_size() @@ -390,24 +416,59 @@ where let mut total_da_per_batch = da_per_batch; // Account for already included builder tx - total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); + ctx.extra_ctx.target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); if let Some(da_limit) = total_da_per_batch.as_mut() { *da_limit = da_limit.saturating_sub(builder_tx_da_size); } + ctx.extra_ctx.total_da_per_batch = total_da_per_batch; + ctx.extra_ctx.gas_per_batch = gas_per_batch; + ctx.extra_ctx.da_per_batch = da_per_batch; // Create best_transaction iterator let mut best_txs = BestFlashblocksTxs::new(BestPayloadTransactions::new( self.pool .best_transactions_with_attributes(ctx.best_transaction_attributes()), )); - // This channel coordinates flashblock building - let (fb_cancel_token_tx, mut fb_cancel_token_rx) = - mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); - self.spawn_timer_task( - block_cancel.clone(), - fb_cancel_token_tx, - first_flashblock_offset, - ); + let interval = self.config.specific.interval; + let (tx, mut rx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); + let mut fb_cancel = block_cancel.child_token(); + ctx.cancel = fb_cancel.clone(); + + tokio::spawn({ + let block_cancel = block_cancel.clone(); + + async move { + let mut timer = tokio::time::interval_at( + tokio::time::Instant::now() + .checked_add(first_flashblock_offset) + .expect("can add flashblock offset to current time"), + interval, + ); + + loop { + tokio::select! { + _ = timer.tick() => { + // cancel current payload building job + fb_cancel.cancel(); + fb_cancel = block_cancel.child_token(); + // this will tick at first_flashblock_offset, + // starting the second flashblock + if tx.send(fb_cancel.clone()).await.is_err() { + // receiver channel was dropped, return. + // this will only happen if the `build_payload` function returns, + // due to payload building error or the main cancellation token being + // cancelled. + return; + } + } + _ = block_cancel.cancelled() => { + return; + } + } + } + } + }); + // Process flashblocks in a blocking loop loop { let fb_span = if span.is_none() { @@ -421,208 +482,35 @@ where }; let _entered = fb_span.enter(); - // We get token from time loop. Token from this channel means that we need to start build flashblock - // Cancellation of this token means that we need to stop building flashblock. - // If channel return None it means that we built all flashblock or parent_token got cancelled - let fb_cancel_token = - tokio::task::block_in_place(|| fb_cancel_token_rx.blocking_recv()).flatten(); - - match fb_cancel_token { - Some(cancel_token) => { - // fallback block is index 0, so we need to increment here - ctx.increment_flashblock_index(); - // We use fb_cancel_token inside context so we could exit from - // execute_best_transaction without cancelling parent token - ctx.cancel = cancel_token; - // TODO: remove this - if ctx.flashblock_index() > ctx.target_flashblock_count() { - info!( - target: "payload_builder", - target = ctx.target_flashblock_count(), - flashblock_index = ctx.flashblock_index(), - block_number = ctx.block_number(), - "Skipping flashblock reached target", - ); - continue; - } - // Continue with flashblock building - info!( + // build first flashblock immediately + match self.build_next_flashblock( + &mut ctx, + &mut info, + &mut state, + &state_provider, + &mut best_txs, + &block_cancel, + &best_payload, + &fb_span, + ) { + Ok(()) => {} + Err(err) => { + error!( target: "payload_builder", - block_number = ctx.block_number(), - flashblock_index = ctx.flashblock_index(), - target_gas = total_gas_per_batch, - gas_used = info.cumulative_gas_used, - target_da = total_da_per_batch.unwrap_or(0), - da_used = info.cumulative_da_bytes_used, - "Building flashblock", - ); - let flashblock_build_start_time = Instant::now(); - - let builder_txs = self.builder_tx.simulate_builder_txs( - &state_provider, - &mut info, - &ctx, - &mut state, - )?; - let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); - let builder_tx_da_size: u64 = - builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); - - total_gas_per_batch = total_gas_per_batch.saturating_sub(builder_tx_gas); - // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size - if let Some(da_limit) = total_da_per_batch.as_mut() { - *da_limit = da_limit.saturating_sub(builder_tx_da_size); - } - - let best_txs_start_time = Instant::now(); - best_txs.refresh_iterator( - BestPayloadTransactions::new( - self.pool.best_transactions_with_attributes( - ctx.best_transaction_attributes(), - ), - ), + "Failed to build flashblock {}, flashblock {}: {}", + ctx.block_number(), ctx.flashblock_index(), + err ); - 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); - - let tx_execution_start_time = Instant::now(); - ctx.execute_best_transactions( - &mut info, - &mut state, - &mut best_txs, - total_gas_per_batch.min(ctx.block_gas_limit()), - total_da_per_batch, - )?; - // Extract last transactions - let new_transactions = info.executed_transactions - [info.extra.last_flashblock_index..] - .to_vec() - .iter() - .map(|tx| tx.tx_hash()) - .collect::>(); - best_txs.mark_commited(new_transactions); - - // We got block cancelled, we won't need anything from the block at this point - // Caution: this assume that block cancel token only cancelled when new FCU is received - if block_cancel.is_cancelled() { - self.record_flashblocks_metrics( - &ctx, - &info, - flashblocks_per_block, - &span, - "Payload building complete, channel closed or job cancelled", - ); - return Ok(()); - } - - let payload_tx_simulation_time = tx_execution_start_time.elapsed(); - ctx.metrics - .payload_tx_simulation_duration - .record(payload_tx_simulation_time); - ctx.metrics - .payload_tx_simulation_gauge - .set(payload_tx_simulation_time); - - self.builder_tx.add_builder_txs( - &state_provider, - &mut info, - &ctx, - &mut state, - )?; - - let total_block_built_duration = Instant::now(); - let build_result = - build_block(&mut state, &ctx, &mut info, calculate_state_root); - let total_block_built_duration = total_block_built_duration.elapsed(); - ctx.metrics - .total_block_built_duration - .record(total_block_built_duration); - ctx.metrics - .total_block_built_gauge - .set(total_block_built_duration); - - // Handle build errors with match pattern - match build_result { - Err(err) => { - // Track invalid/bad block - ctx.metrics.invalid_blocks_count.increment(1); - error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), ctx.flashblock_index(), err); - // Signal cancellation of the block building job - block_cancel.cancel(); - // Return the error - return Err(err); - } - Ok((new_payload, mut fb_payload)) => { - fb_payload.index = ctx.flashblock_index(); - fb_payload.base = None; - - // We check that child_job got cancelled before sending flashblock. - // This will ensure consistent timing between flashblocks. - tokio::task::block_in_place(|| { - tokio::runtime::Handle::current() - .block_on(async { ctx.cancel.cancelled().await }); - }); - - // If main token got canceled in here that means we received get_payload and we should drop everything and now update best_payload - // To ensure that we will return same blocks as rollup-boost (to leverage caches) - if block_cancel.is_cancelled() { - self.record_flashblocks_metrics( - &ctx, - &info, - flashblocks_per_block, - &span, - "Payload building complete, channel closed or job cancelled", - ); - return Ok(()); - } - let flashblock_byte_size = self - .ws_pub - .publish(&fb_payload) - .map_err(PayloadBuilderError::other)?; - - // Record flashblock build duration - ctx.metrics - .flashblock_build_duration - .record(flashblock_build_start_time.elapsed()); - ctx.metrics - .flashblock_byte_size_histogram - .record(flashblock_byte_size as f64); - ctx.metrics - .flashblock_num_tx_histogram - .record(info.executed_transactions.len() as f64); - - best_payload.set(new_payload.clone()); - self.send_payload_to_engine(new_payload); - // Update bundle_state for next iteration - total_gas_per_batch += gas_per_batch; - if let Some(da_limit) = da_per_batch { - if let Some(da) = total_da_per_batch.as_mut() { - *da += da_limit; - } else { - error!( - "Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set" - ); - } - } - - info!( - target: "payload_builder", - message = "Flashblock built", - flashblock_index = ctx.flashblock_index(), - current_gas = info.cumulative_gas_used, - current_da = info.cumulative_da_bytes_used, - target_flashblocks = flashblocks_per_block, - ); - } - } + return Err(err); } - None => { + } + + tokio::select! { + Some(fb_cancel) = rx.recv() => { + ctx.cancel = fb_cancel; + }, + _ = block_cancel.cancelled() => { self.record_flashblocks_metrics( &ctx, &info, @@ -636,6 +524,206 @@ where } } + #[allow(clippy::too_many_arguments)] + fn build_next_flashblock< + DB: Database + std::fmt::Debug + AsRef

, + P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, + >( + &self, + ctx: &mut OpPayloadBuilderCtx, + info: &mut ExecutionInfo, + state: &mut State, + state_provider: impl reth::providers::StateProvider + Clone, + best_txs: &mut NextBestFlashblocksTxs, + block_cancel: &CancellationToken, + best_payload: &BlockCell, + span: &tracing::Span, + ) -> Result<(), PayloadBuilderError> { + // fallback block is index 0, so we need to increment here + ctx.increment_flashblock_index(); + + // TODO: remove this + if ctx.flashblock_index() > ctx.target_flashblock_count() { + info!( + target: "payload_builder", + target = ctx.target_flashblock_count(), + flashblock_index = ctx.flashblock_index(), + block_number = ctx.block_number(), + "Skipping flashblock reached target", + ); + return Ok(()); + }; + + // Continue with flashblock building + let mut target_gas_for_batch = ctx.extra_ctx.target_gas_for_batch; + let mut total_da_per_batch = ctx.extra_ctx.total_da_per_batch; + + info!( + target: "payload_builder", + block_number = ctx.block_number(), + flashblock_index = ctx.flashblock_index(), + target_gas = target_gas_for_batch, + gas_used = info.cumulative_gas_used, + target_da = total_da_per_batch.unwrap_or(0), + da_used = info.cumulative_da_bytes_used, + "Building flashblock", + ); + let flashblock_build_start_time = Instant::now(); + + let builder_txs = + self.builder_tx + .simulate_builder_txs(&state_provider, info, ctx, state)?; + + let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); + let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); + + // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size + if let Some(da_limit) = total_da_per_batch.as_mut() { + *da_limit = da_limit.saturating_sub(builder_tx_da_size); + } + + let best_txs_start_time = Instant::now(); + best_txs.refresh_iterator( + BestPayloadTransactions::new( + self.pool + .best_transactions_with_attributes(ctx.best_transaction_attributes()), + ), + ctx.flashblock_index(), + ); + 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); + + let tx_execution_start_time = Instant::now(); + ctx.execute_best_transactions( + info, + state, + best_txs, + target_gas_for_batch.min(ctx.block_gas_limit()), + total_da_per_batch, + )?; + // Extract last transactions + let new_transactions = info.executed_transactions[info.extra.last_flashblock_index..] + .to_vec() + .iter() + .map(|tx| tx.tx_hash()) + .collect::>(); + best_txs.mark_commited(new_transactions); + + // We got block cancelled, we won't need anything from the block at this point + // Caution: this assume that block cancel token only cancelled when new FCU is received + if block_cancel.is_cancelled() { + self.record_flashblocks_metrics( + ctx, + info, + ctx.target_flashblock_count(), + span, + "Payload building complete, channel closed or job cancelled", + ); + return Ok(()); + } + + let payload_tx_simulation_time = tx_execution_start_time.elapsed(); + ctx.metrics + .payload_tx_simulation_duration + .record(payload_tx_simulation_time); + ctx.metrics + .payload_tx_simulation_gauge + .set(payload_tx_simulation_time); + + self.builder_tx + .add_builder_txs(&state_provider, info, ctx, state)?; + + let total_block_built_duration = Instant::now(); + let build_result = build_block( + state, + ctx, + info, + ctx.extra_ctx.calculate_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); + ctx.metrics + .total_block_built_gauge + .set(total_block_built_duration); + + // Handle build errors with match pattern + match build_result { + Err(err) => { + // Track invalid/bad block + ctx.metrics.invalid_blocks_count.increment(1); + error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), ctx.flashblock_index(), err); + // Return the error + return Err(err); + } + Ok((new_payload, mut fb_payload)) => { + fb_payload.index = ctx.flashblock_index(); + fb_payload.base = None; + + // If main token got canceled in here that means we received get_payload and we should drop everything and now update best_payload + // To ensure that we will return same blocks as rollup-boost (to leverage caches) + if block_cancel.is_cancelled() { + self.record_flashblocks_metrics( + ctx, + info, + ctx.target_flashblock_count(), + span, + "Payload building complete, channel closed or job cancelled", + ); + return Ok(()); + } + let flashblock_byte_size = self + .ws_pub + .publish(&fb_payload) + .map_err(PayloadBuilderError::other)?; + + // Record flashblock build duration + ctx.metrics + .flashblock_build_duration + .record(flashblock_build_start_time.elapsed()); + ctx.metrics + .flashblock_byte_size_histogram + .record(flashblock_byte_size as f64); + ctx.metrics + .flashblock_num_tx_histogram + .record(info.executed_transactions.len() as f64); + + best_payload.set(new_payload.clone()); + self.send_payload_to_engine(new_payload); + // Update bundle_state for next iteration + if let Some(da_limit) = ctx.extra_ctx.da_per_batch { + if let Some(da) = total_da_per_batch.as_mut() { + *da += da_limit; + } else { + error!( + "Builder end up in faulty invariant, if da_per_batch is set then total_da_per_batch must be set" + ); + } + } + + ctx.extra_ctx.target_gas_for_batch += ctx.extra_ctx.gas_per_batch; + ctx.extra_ctx.total_da_per_batch = total_da_per_batch; + + info!( + target: "payload_builder", + message = "Flashblock built", + flashblock_index = ctx.flashblock_index(), + current_gas = info.cumulative_gas_used, + current_da = info.cumulative_da_bytes_used, + target_flashblocks = ctx.target_flashblock_count(), + ); + } + } + Ok(()) + } + /// Do some logging and metric recording when we stop build flashblocks fn record_flashblocks_metrics( &self, @@ -689,53 +777,6 @@ where } } - /// Spawn task that will send new flashblock level cancel token in steady intervals (first interval - /// may vary if --flashblocks.dynamic enabled) - pub(super) fn spawn_timer_task( - &self, - block_cancel: CancellationToken, - fb_cancel_token_tx: Sender>, - first_flashblock_offset: Duration, - ) { - let interval = self.config.specific.interval; - tokio::spawn(async move { - let cancelled: Option>>> = block_cancel - .run_until_cancelled(async { - // Create first fb interval already started - let mut timer = tokio::time::interval(first_flashblock_offset); - timer.tick().await; - let child_token = block_cancel.child_token(); - fb_cancel_token_tx - .send(Some(child_token.clone())) - .await?; - timer.tick().await; - // Time to build flashblock has ended so we cancel the token - child_token.cancel(); - // We would start using regular intervals from here on - let mut timer = tokio::time::interval(interval); - timer.tick().await; - loop { - // Initiate fb job - let child_token = block_cancel.child_token(); - debug!(target: "payload_builder", "Sending child cancel token to execution loop"); - fb_cancel_token_tx - .send(Some(child_token.clone())) - .await?; - timer.tick().await; - debug!(target: "payload_builder", "Cancelling child token to complete flashblock"); - // Cancel job once time is up - child_token.cancel(); - } - }) - .await; - if let Some(Err(err)) = cancelled { - error!(target: "payload_builder", "Timer task encountered error: {err}"); - } else { - info!(target: "payload_builder", "Building job cancelled, stopping payload building"); - } - }); - } - /// 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) { @@ -798,6 +839,7 @@ where } } +#[async_trait::async_trait] impl PayloadBuilder for OpPayloadBuilder where Pool: PoolBounds, @@ -807,12 +849,12 @@ where type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; - fn try_build( + async fn try_build( &self, args: BuildArguments, best_payload: BlockCell, ) -> Result<(), PayloadBuilderError> { - self.build_payload(args, best_payload) + self.build_payload(args, best_payload).await } } diff --git a/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs index be9bd89f..3408751c 100644 --- a/crates/builder/op-rbuilder/src/builders/generator.rs +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -34,6 +34,7 @@ use tracing::info; /// /// Generic parameters `Pool` and `Client` represent the transaction pool and /// Ethereum client types. +#[async_trait::async_trait] pub(super) trait PayloadBuilder: Send + Sync + Clone { /// The payload attributes type to accept for building. type Attributes: PayloadBuilderAttributes; @@ -52,7 +53,7 @@ pub(super) trait PayloadBuilder: Send + Sync + Clone { /// # Returns /// /// A `Result` indicating the build outcome or an error. - fn try_build( + async fn try_build( &self, args: BuildArguments, best_payload: BlockCell, @@ -329,7 +330,7 @@ where cancel, }; - let result = builder.try_build(args, cell); + let result = builder.try_build(args, cell).await; let _ = tx.send(result); })); } @@ -605,6 +606,7 @@ mod tests { Cancelled, } + #[async_trait::async_trait] impl PayloadBuilder for MockBuilder where N: OpPayloadPrimitives, @@ -612,7 +614,7 @@ mod tests { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = MockPayload; - fn try_build( + async fn try_build( &self, args: BuildArguments, _best_payload: BlockCell, From d7b23c283e13509d2b8c70e10ddb349368be7f61 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:05:08 -0400 Subject: [PATCH 186/262] fix: check per-address gas limit before checking if the tx reverted (#266) --- .../op-rbuilder/src/builders/context.rs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 95cb8579..9604ced7 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -460,6 +460,21 @@ impl OpPayloadBuilderCtx { .record(tx_simulation_start_time.elapsed()); self.metrics.tx_byte_size.record(tx.inner().size() as f64); num_txs_simulated += 1; + + // Run the per-address gas limiting before checking if the tx has + // reverted or not, as this is a check against maliciously searchers + // sending txs that are expensive to compute but always revert. + let gas_used = result.gas_used(); + if self + .address_gas_limiter + .consume_gas(tx.signer(), gas_used) + .is_err() + { + log_txn(TxnExecutionResult::MaxGasUsageExceeded); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + if result.is_success() { log_txn(TxnExecutionResult::Success); num_txs_simulated_success += 1; @@ -480,7 +495,6 @@ impl OpPayloadBuilderCtx { // add gas used by the transaction to cumulative gas used, before creating the // receipt - let gas_used = result.gas_used(); if let Some(max_gas_per_txn) = self.max_gas_per_txn { if gas_used > max_gas_per_txn { log_txn(TxnExecutionResult::MaxGasUsageExceeded); @@ -489,16 +503,6 @@ impl OpPayloadBuilderCtx { } } - if self - .address_gas_limiter - .consume_gas(tx.signer(), gas_used) - .is_err() - { - log_txn(TxnExecutionResult::MaxGasUsageExceeded); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } - info.cumulative_gas_used += gas_used; // record tx da size info.cumulative_da_bytes_used += tx_da_size; From 9e1a24d2f5833b0afbae9996f61f4ab803e2f295 Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 19 Sep 2025 13:40:45 -0700 Subject: [PATCH 187/262] Add support for flashblocks number contract builder tx (#256) * Refactor payload builder to accept generic builder tx * Update crates/op-rbuilder/src/builders/builder_tx.rs Co-authored-by: Solar Mithril * Update crates/op-rbuilder/src/builders/builder_tx.rs Co-authored-by: Solar Mithril * Update crates/op-rbuilder/src/builders/builder_tx.rs Co-authored-by: Solar Mithril * Add support for flashblocks number contract builder tx * add docs * add builder tx to fallback block * fallback to legacy builder tx * allow top or bottom of block builder tx --------- Co-authored-by: Solar Mithril --- crates/builder/op-rbuilder/src/args/op.rs | 11 + .../op-rbuilder/src/builders/builder_tx.rs | 147 +++++++- .../src/builders/flashblocks/builder_tx.rs | 324 ++++++++++++++---- .../src/builders/flashblocks/config.rs | 12 + .../src/builders/flashblocks/payload.rs | 39 ++- .../src/builders/flashblocks/service.rs | 30 +- .../src/builders/standard/builder_tx.rs | 94 +---- .../src/builders/standard/payload.rs | 5 +- .../src/flashtestations/service.rs | 1 + .../op-rbuilder/src/tests/flashblocks.rs | 10 + 10 files changed, 488 insertions(+), 185 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 760249b5..bd860f15 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -8,6 +8,7 @@ use crate::{ flashtestations::args::FlashtestationsArgs, gas_limiter::args::GasLimiterArgs, tx_signer::Signer, }; +use alloy_primitives::Address; use anyhow::{Result, anyhow}; use clap::Parser; use reth_optimism_cli::commands::Commands; @@ -155,6 +156,16 @@ pub struct FlashblocksArgs { env = "FLASHBLOCKS_CALCULATE_STATE_ROOT" )] pub flashblocks_calculate_state_root: bool, + + /// Flashblocks number contract address + /// + /// This is the address of the contract that will be used to increment the flashblock number. + /// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx. + #[arg( + long = "flashblocks.number-contract-address", + env = "FLASHBLOCK_NUMBER_CONTRACT_ADDRESS" + )] + pub flashblocks_number_contract_address: Option

, } impl Default for FlashblocksArgs { diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index 8ff168b1..b79200d4 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -1,9 +1,12 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; use alloy_primitives::{ - Address, + Address, TxKind, map::foldhash::{HashSet, HashSetExt}, }; use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; use op_revm::OpTransactionError; use reth_evm::{ConfigureEvm, Evm, eth::receipt_builder::ReceiptBuilderCtx}; use reth_node_api::PayloadBuilderError; @@ -19,13 +22,15 @@ use revm::{ }; use tracing::warn; -use crate::{builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo}; +use crate::{ + builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer, +}; #[derive(Debug, Clone)] pub struct BuilderTransactionCtx { pub gas_used: u64, pub da_size: u64, - pub signed_tx: Recovered, + pub signed_tx: Option>, } /// Possible error variants during construction of builder txs. @@ -75,6 +80,7 @@ pub trait BuilderTransactions: Debug { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, + top_of_block: bool, ) -> Result, BuilderTransactionError>; fn add_builder_txs( @@ -83,6 +89,7 @@ pub trait BuilderTransactions: Debug { info: &mut ExecutionInfo, builder_ctx: &OpPayloadBuilderCtx, db: &mut State, + top_of_block: bool, ) -> Result, BuilderTransactionError> { { let mut evm = builder_ctx @@ -91,21 +98,30 @@ pub trait BuilderTransactions: Debug { let mut invalid: HashSet
= HashSet::new(); - let builder_txs = - self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?; + let builder_txs = self.simulate_builder_txs( + state_provider, + info, + builder_ctx, + evm.db_mut(), + top_of_block, + )?; for builder_tx in builder_txs.iter() { - if invalid.contains(&builder_tx.signed_tx.signer()) { - warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); + let signed_tx = match builder_tx.signed_tx.clone() { + Some(tx) => tx, + None => continue, + }; + if invalid.contains(&signed_tx.signer()) { + warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); continue; } let ResultAndState { result, state } = evm - .transact(&builder_tx.signed_tx) + .transact(&signed_tx) .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; if !result.is_success() { - warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted"); - invalid.insert(builder_tx.signed_tx.signer()); + warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder tx reverted"); + invalid.insert(signed_tx.signer()); continue; } @@ -114,7 +130,7 @@ pub trait BuilderTransactions: Debug { info.cumulative_gas_used += gas_used; let ctx = ReceiptBuilderCtx { - tx: builder_tx.signed_tx.inner(), + tx: signed_tx.inner(), evm: &evm, result, state: &state, @@ -126,9 +142,9 @@ pub trait BuilderTransactions: Debug { evm.db_mut().commit(state); // Append sender and transaction to the respective lists - info.executed_senders.push(builder_tx.signed_tx.signer()); + info.executed_senders.push(signed_tx.signer()); info.executed_transactions - .push(builder_tx.signed_tx.clone().into_inner()); + .push(signed_tx.clone().into_inner()); } // Release the db reference by dropping evm @@ -148,7 +164,7 @@ pub trait BuilderTransactions: Debug { let state = StateProviderDatabase::new(state_provider.clone()); let mut simulation_state = State::builder() .with_database(state) - .with_bundle_prestate(db.bundle_state.clone()) + .with_cached_prestate(db.cache.clone()) .with_bundle_update() .build(); let mut evm = ctx @@ -156,8 +172,12 @@ pub trait BuilderTransactions: Debug { .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); for builder_tx in builder_txs { + let signed_tx = match builder_tx.signed_tx.clone() { + Some(tx) => tx, + None => continue, + }; let ResultAndState { state, .. } = evm - .transact(&builder_tx.signed_tx) + .transact(&signed_tx) .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; evm.db_mut().commit(state); @@ -167,3 +187,100 @@ pub trait BuilderTransactions: Debug { Ok(simulation_state) } } + +#[derive(Debug, Clone)] +pub(super) struct BuilderTxBase { + pub signer: Option, +} + +impl BuilderTxBase { + pub(super) fn new(signer: Option) -> Self { + Self { signer } + } + + pub(super) fn simulate_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> 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: Some(signed_tx), + })) + } + 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; + + std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) + } + + fn signed_builder_tx( + &self, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + signer: Signer, + gas_used: u64, + message: Vec, + ) -> Result, BuilderTransactionError> { + let nonce = db + .load_cache_account(signer.address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(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) + } +} + +pub(super) fn get_nonce( + db: &mut State, + address: Address, +) -> Result { + db.load_cache_account(address) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs index be29e6aa..3445bb2e 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -1,28 +1,98 @@ use alloy_consensus::TxEip1559; -use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; -use alloy_evm::Database; -use alloy_primitives::{Address, TxKind}; +use alloy_eips::Encodable2718; +use alloy_evm::{Database, Evm}; +use alloy_op_evm::OpEvm; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_sol_types::{SolCall, SolError, sol}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; +use op_revm::OpHaltReason; +use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap}; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::StateProvider; -use reth_revm::State; +use reth_revm::{State, database::StateProviderDatabase}; +use revm::{ + context::result::{ExecutionResult, ResultAndState}, + inspector::NoOpInspector, +}; +use tracing::warn; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - context::OpPayloadBuilderCtx, flashblocks::payload::FlashblocksExtraCtx, + builder_tx::{BuilderTxBase, get_nonce}, + context::OpPayloadBuilderCtx, + flashblocks::payload::FlashblocksExtraCtx, }, flashtestations::service::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, tx_signer::Signer, }; +sol!( + // From https://github.com/Uniswap/flashblocks_number_contract/blob/main/src/FlashblockNumber.sol + #[sol(rpc, abi)] + interface IFlashblockNumber { + function incrementFlashblockNumber() external; + } + + // @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, thiserror::Error)] +pub(super) enum FlashblockNumberError { + #[error("non builder address: {0}")] + NonBuilderAddress(Address), + #[error("mismatched flashblock number: expected {0}, actual {1}")] + MismatchedFlashblockNumber(U256, U256), + #[error("unknown revert: {0}")] + Unknown(String), + #[error("halt: {0:?}")] + Halt(OpHaltReason), +} + +impl From for FlashblockNumberError { + fn from(value: Bytes) -> Self { + // Empty revert + if value.is_empty() { + return FlashblockNumberError::Unknown( + "Transaction reverted without reason".to_string(), + ); + } + + // Try to decode each custom error type + if let Ok(NonBuilderAddress { addr }) = NonBuilderAddress::abi_decode(&value) { + return FlashblockNumberError::NonBuilderAddress(addr); + } + + if let Ok(MismatchedFlashblockNumber { + expectedFlashblockNumber, + actualFlashblockNumber, + }) = MismatchedFlashblockNumber::abi_decode(&value) + { + return FlashblockNumberError::MismatchedFlashblockNumber( + expectedFlashblockNumber, + actualFlashblockNumber, + ); + } + + FlashblockNumberError::Unknown(hex::encode(value)) + } +} + // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct FlashblocksBuilderTx { - pub signer: Option, + pub base_builder_tx: BuilderTxBase, pub flashtestations_builder_tx: Option, } @@ -31,126 +101,242 @@ impl FlashblocksBuilderTx { signer: Option, flashtestations_builder_tx: Option, ) -> Self { + let base_builder_tx = BuilderTxBase::new(signer); Self { - signer, + base_builder_tx, flashtestations_builder_tx, } } +} - pub(super) fn simulate_builder_tx( +impl BuilderTransactions for FlashblocksBuilderTx { + fn simulate_builder_txs( &self, - ctx: &OpPayloadBuilderCtx, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, db: &mut State, - ) -> 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, - })) + top_of_block: bool, + ) -> 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, db)?; + builder_txs.extend(flashblocks_builder_tx.clone()); + } + + if ctx.is_last_flashblock() { + let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; + if let Some(tx) = flashblocks_builder_tx.clone() { + if top_of_block { + // don't commit the builder if top of block, we only return the gas used to reserve gas for the builder tx + builder_txs.push(BuilderTransactionCtx { + gas_used: tx.gas_used, + da_size: tx.da_size, + signed_tx: None, + }); + } else { + builder_txs.push(tx); + } + } + if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { + // We only include flashtestations txs in the last flashblock + let mut simulation_state = self.simulate_builder_txs_state::( + state_provider.clone(), + flashblocks_builder_tx.iter().collect(), + ctx, + db, + )?; + let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + &mut simulation_state, + top_of_block, + )?; + builder_txs.extend(flashtestations_builder_txs); } - None => Ok(None), } + Ok(builder_txs) } +} - 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) - } - }); +// This will be the end of block transaction of a regular block +#[derive(Debug, Clone)] +pub(super) struct FlashblocksNumberBuilderTx { + pub signer: Option, + pub flashblock_number_address: Address, + pub base_builder_tx: BuilderTxBase, + pub flashtestations_builder_tx: Option, +} - // 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; +impl FlashblocksNumberBuilderTx { + pub(super) fn new( + signer: Option, + flashblock_number_address: Address, + flashtestations_builder_tx: Option, + ) -> Self { + let base_builder_tx = BuilderTxBase::new(signer); + Self { + signer, + flashblock_number_address, + base_builder_tx, + flashtestations_builder_tx, + } + } - // 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; + fn estimate_flashblock_number_tx_gas( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + State>, + NoOpInspector, + PrecompilesMap, + >, + signer: &Signer, + nonce: u64, + ) -> Result { + let tx = self.signed_flashblock_number_tx(ctx, ctx.block_gas_limit(), nonce, signer)?; + let ResultAndState { result, .. } = match evm.transact(&tx) { + Ok(res) => res, + Err(err) => { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + }; - std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) + match result { + ExecutionResult::Success { gas_used, .. } => Ok(gas_used), + ExecutionResult::Revert { output, .. } => Err(BuilderTransactionError::Other( + Box::new(FlashblockNumberError::from(output)), + )), + ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::Other(Box::new( + FlashblockNumberError::Halt(reason), + ))), + } } - fn signed_builder_tx( + fn signed_flashblock_number_tx( &self, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - signer: Signer, - gas_used: u64, - message: Vec, - ) -> Result, BuilderTransactionError> { - let nonce = db - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| BuilderTransactionError::AccountLoadFailed(signer.address))?; - + ctx: &OpPayloadBuilderCtx, + gas_limit: u64, + nonce: u64, + signer: &Signer, + ) -> Result, secp256k1::Error> { + let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}.abi_encode(); // Create the EIP-1559 transaction let tx = OpTypedTransaction::Eip1559(TxEip1559 { chain_id: ctx.chain_id(), nonce, - gas_limit: gas_used, + gas_limit, 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(), + to: TxKind::Call(self.flashblock_number_address), + input: calldata.into(), ..Default::default() }); - // Sign the transaction - let builder_tx = signer - .sign_tx(tx) - .map_err(BuilderTransactionError::SigningError)?; - - Ok(builder_tx) + signer.sign_tx(tx) } } -impl BuilderTransactions for FlashblocksBuilderTx { +impl BuilderTransactions for FlashblocksNumberBuilderTx { fn simulate_builder_txs( &self, state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, + top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); + let state = StateProviderDatabase::new(state_provider.clone()); + let simulation_state = State::builder() + .with_database(state) + .with_cached_prestate(db.cache.clone()) + .with_bundle_update() + .build(); if ctx.is_first_flashblock() { - let flashblocks_builder_tx = self.simulate_builder_tx(ctx, db)?; + let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; builder_txs.extend(flashblocks_builder_tx.clone()); + } else { + // we increment the flashblock number for the next flashblock so we don't increment in the last flashblock + if let Some(signer) = &self.signer { + let mut evm = ctx + .evm_config + .evm_with_env(simulation_state, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + }); + + let nonce = get_nonce(evm.db_mut(), signer.address)?; + + let tx = match self.estimate_flashblock_number_tx_gas(ctx, &mut evm, signer, nonce) + { + Ok(gas_used) => { + // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer + let flashblocks_tx = self.signed_flashblock_number_tx( + ctx, + gas_used * 64 / 63, + nonce, + signer, + )?; + + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + flashblocks_tx.encoded_2718().as_slice(), + ); + Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: if top_of_block { + Some(flashblocks_tx) + } else { + None + }, // number tx at top of flashblock + }) + } + Err(e) => { + warn!(target: "builder_tx", error = ?e, "Flashblocks number contract tx simulation failed, defaulting to fallback builder tx"); + let builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; + if let Some(tx) = &builder_tx + && top_of_block + { + // don't commit the builder if top of block, we only return the gas used to reserve gas for the builder tx + Some(BuilderTransactionCtx { + gas_used: tx.gas_used, + da_size: tx.da_size, + signed_tx: None, + }) + } else { + builder_tx + } + } + }; + + builder_txs.extend(tx); + } } if ctx.is_last_flashblock() { - let flashblocks_builder_tx = self.simulate_builder_tx(ctx, db)?; - builder_txs.extend(flashblocks_builder_tx.clone()); if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { - // We only include flashtestations txs in the last flashblock - + let flashblocks_builder_txs = builder_txs.clone(); let mut simulation_state = self.simulate_builder_txs_state::( state_provider.clone(), - flashblocks_builder_tx.iter().collect(), + flashblocks_builder_txs.iter().collect(), ctx, db, )?; + // We only include flashtestations txs in the last flashblock let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( state_provider, info, ctx, &mut simulation_state, + top_of_block, )?; builder_txs.extend(flashtestations_builder_txs); } } + Ok(builder_txs) } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index c852a354..f2dca775 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -1,3 +1,5 @@ +use alloy_primitives::Address; + use crate::{args::OpRbuilderArgs, builders::BuilderConfig}; use core::{ net::{Ipv4Addr, SocketAddr}, @@ -31,6 +33,11 @@ pub struct FlashblocksConfig { /// Should we calculate state root for each flashblock pub calculate_state_root: bool, + + /// The address of the flashblocks number contract. + /// + /// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx. + pub flashblocks_number_contract_address: Option
, } impl Default for FlashblocksConfig { @@ -41,6 +48,7 @@ impl Default for FlashblocksConfig { leeway_time: Duration::from_millis(50), fixed: false, calculate_state_root: true, + flashblocks_number_contract_address: None, } } } @@ -62,12 +70,16 @@ impl TryFrom for FlashblocksConfig { let calculate_state_root = args.flashblocks.flashblocks_calculate_state_root; + let flashblocks_number_contract_address = + args.flashblocks.flashblocks_number_contract_address; + Ok(Self { ws_addr, interval, leeway_time, fixed, calculate_state_root, + flashblocks_number_contract_address, }) } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 05228f82..ae11a6da 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -322,8 +322,19 @@ where let builder_txs = if ctx.attributes().no_tx_pool { vec![] } else { - self.builder_tx - .add_builder_txs(&state_provider, &mut info, &ctx, &mut state)? + match self.builder_tx.add_builder_txs( + &state_provider, + &mut info, + &ctx, + &mut state, + true, + ) { + Ok(builder_txs) => builder_txs, + Err(e) => { + error!(target: "payload_builder", "Error adding builder txs to fallback block: {}", e); + vec![] + } + } }; // We subtract gas limit and da limit for builder transaction from the whole limit @@ -571,8 +582,16 @@ where let flashblock_build_start_time = Instant::now(); let builder_txs = - self.builder_tx - .simulate_builder_txs(&state_provider, info, ctx, state)?; + match self + .builder_tx + .add_builder_txs(&state_provider, info, ctx, state, true) + { + Ok(builder_txs) => builder_txs, + Err(e) => { + error!(target: "payload_builder", "Error simulating builder txs: {}", e); + vec![] + } + }; let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); @@ -636,8 +655,16 @@ where .payload_tx_simulation_gauge .set(payload_tx_simulation_time); - self.builder_tx - .add_builder_txs(&state_provider, info, ctx, state)?; + match self + .builder_tx + .add_builder_txs(&state_provider, info, ctx, state, false) + { + Ok(builder_txs) => builder_txs, + Err(e) => { + error!(target: "payload_builder", "Error simulating builder txs: {}", e); + vec![] + } + }; let total_block_built_duration = Instant::now(); let build_result = build_block( diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index a8b4d3ba..46ee8ae8 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -3,7 +3,10 @@ use crate::{ builders::{ BuilderConfig, builder_tx::BuilderTransactions, - flashblocks::{builder_tx::FlashblocksBuilderTx, payload::FlashblocksExtraCtx}, + flashblocks::{ + builder_tx::{FlashblocksBuilderTx, FlashblocksNumberBuilderTx}, + payload::FlashblocksExtraCtx, + }, generator::BlockPayloadJobGenerator, }, flashtestations::service::bootstrap_flashtestations, @@ -92,10 +95,25 @@ where } else { None }; - self.spawn_payload_builder_service( - ctx, - pool, - FlashblocksBuilderTx::new(signer, flashtestations_builder_tx), - ) + + if let Some(flashblocks_number_contract_address) = + self.0.specific.flashblocks_number_contract_address + { + self.spawn_payload_builder_service( + ctx, + pool, + FlashblocksNumberBuilderTx::new( + signer, + flashblocks_number_contract_address, + flashtestations_builder_tx, + ), + ) + } else { + self.spawn_payload_builder_service( + ctx, + pool, + FlashblocksBuilderTx::new(signer, flashtestations_builder_tx), + ) + } } } diff --git a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs index 23c39f3c..c4d154f6 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs @@ -1,18 +1,12 @@ -use alloy_consensus::TxEip1559; -use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; -use alloy_primitives::{Address, TxKind}; use core::fmt::Debug; -use op_alloy_consensus::OpTypedTransaction; -use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; use reth_provider::StateProvider; use reth_revm::State; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - context::OpPayloadBuilderCtx, + builder_tx::BuilderTxBase, context::OpPayloadBuilderCtx, }, flashtestations::service::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, @@ -22,7 +16,7 @@ use crate::{ // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct StandardBuilderTx { - pub signer: Option, + pub base_builder_tx: BuilderTxBase, pub flashtestations_builder_tx: Option, } @@ -31,88 +25,12 @@ impl StandardBuilderTx { signer: Option, flashtestations_builder_tx: Option, ) -> Self { + let base_builder_tx = BuilderTxBase::new(signer); Self { - signer, + base_builder_tx, flashtestations_builder_tx, } } - - pub(super) fn simulate_builder_tx( - &self, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> 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, - })) - } - 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; - - std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) - } - - fn signed_builder_tx( - &self, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - signer: Signer, - gas_used: u64, - message: Vec, - ) -> Result, BuilderTransactionError> { - let nonce = db - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| BuilderTransactionError::AccountLoadFailed(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) - } } impl BuilderTransactions for StandardBuilderTx { @@ -122,9 +40,10 @@ impl BuilderTransactions for StandardBuilderTx { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, + top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); - let standard_builder_tx = self.simulate_builder_tx(ctx, db)?; + let standard_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; builder_txs.extend(standard_builder_tx.clone()); if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { let mut simulation_state = self.simulate_builder_txs_state::<()>( @@ -138,6 +57,7 @@ impl BuilderTransactions for StandardBuilderTx { info, ctx, &mut simulation_state, + top_of_block, )?; builder_txs.extend(flashtestations_builder_txs); } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 52acef4b..c510a8ba 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -347,7 +347,8 @@ impl OpBuilder<'_, Txs> { // 4. if mem pool transactions are requested we execute them // gas reserved for builder tx - let builder_txs = builder_tx.simulate_builder_txs(&state_provider, &mut info, ctx, db)?; + let builder_txs = + builder_tx.simulate_builder_txs(&state_provider, &mut info, ctx, db, true)?; 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 { @@ -394,7 +395,7 @@ impl OpBuilder<'_, Txs> { } // Add builder tx to the block - builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db)?; + builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, false)?; let state_merge_start_time = Instant::now(); diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index ffcd2276..71dbca86 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -101,6 +101,7 @@ impl BuilderTransactions for Flashtestation _info: &mut ExecutionInfo, _ctx: &OpPayloadBuilderCtx, _db: &mut State, + _top_of_block: bool, ) -> Result, BuilderTransactionError> { Ok(vec![]) } diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index d9789457..8204af75 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -17,6 +17,7 @@ use crate::{ flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -55,6 +56,7 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -93,6 +95,7 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_leeway_time: 50, flashblocks_fixed: true, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -131,6 +134,7 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_leeway_time: 50, flashblocks_fixed: true, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -169,6 +173,7 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -214,6 +219,7 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> flashblocks_leeway_time: 0, flashblocks_fixed: false, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -252,6 +258,7 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<() flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -312,6 +319,7 @@ async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result< flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -368,6 +376,7 @@ async fn test_flashblock_max_filtering(rbuilder: LocalInstance) -> eyre::Result< flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_calculate_state_root: true, + flashblocks_number_contract_address: None, }, ..Default::default() })] @@ -413,6 +422,7 @@ async fn test_flashblock_min_max_filtering(rbuilder: LocalInstance) -> eyre::Res flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_calculate_state_root: false, + flashblocks_number_contract_address: None, }, ..Default::default() })] From e5c4c04e3601b2ab819f78cbb8e3b429b1e07241 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 25 Sep 2025 21:41:57 +0500 Subject: [PATCH 188/262] Bump reth to 1.8.1 (#274) * bump to 1.8.1 * bump rollup-boost v * bump tracing --- crates/builder/op-rbuilder/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index f91559de..0a60a2b3 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -127,8 +127,7 @@ sha3 = "0.10" hex = "0.4" ureq = "2.10" -# TODO: change to rev from main once https://github.com/flashbots/rollup-boost/pull/401 is merged -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "0c1fb4ce7e00f0afc350f5bf7573b19da6d485ec" } +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "b86af43969557bee18f17ec1d6bcd3e984f910b2" } nanoid = { version = "0.4", optional = true } reth-ipc = { workspace = true, optional = true } From 96d85dbe5dd376b97ed4af4ecb286ed303cebe4f Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Fri, 26 Sep 2025 02:13:39 +0900 Subject: [PATCH 189/262] feat: overwrite reth default cache directory (#238) * feat: overwrite reth default cache directory * doc: logs path --- crates/builder/op-rbuilder/Cargo.toml | 1 + crates/builder/op-rbuilder/src/args/mod.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 0a60a2b3..5a8ea941 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -136,6 +136,7 @@ ctor = { version = "0.4.2", optional = true } rlimit = { version = "0.10", optional = true } macros = { path = "src/tests/framework/macros", optional = true } testcontainers = "0.24.0" +dirs-next = "2.0.0" [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index 4188e39c..a89f3790 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -78,12 +78,17 @@ impl CliExt for Cli { /// Parses commands and overrides versions fn set_version() -> Self { + let logs_dir = dirs_next::cache_dir() + .map(|root| root.join("op-rbuilder/logs")) + .unwrap() + .into_os_string(); let matches = Cli::command() .version(SHORT_VERSION) .long_version(LONG_VERSION) .about("Block builder designed for the Optimism stack") .author("Flashbots") .name("op-rbuilder") + .mut_arg("log_file_directory", |arg| arg.default_value(logs_dir)) .get_matches(); Cli::from_arg_matches(&matches).expect("Parsing args") } From 2970863431ce016341cb53ae718d8fb84a7edd97 Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 25 Sep 2025 11:13:09 -0700 Subject: [PATCH 190/262] Add remote quote provider arg for flashtestations (#276) --- crates/builder/op-rbuilder/Cargo.toml | 1 + .../op-rbuilder/src/flashtestations/args.rs | 23 ++++++++++++------- .../src/flashtestations/attestation.rs | 23 ++++++++++--------- .../src/flashtestations/service.rs | 5 ++-- crates/builder/op-rbuilder/src/tx_signer.rs | 18 +++++++++++++++ 5 files changed, 49 insertions(+), 21 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 5a8ea941..f1fe8d1d 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -126,6 +126,7 @@ http = "1.0" sha3 = "0.10" hex = "0.4" ureq = "2.10" +k256 = "0.13.4" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "b86af43969557bee18f17ec1d6bcd3e984f910b2" } diff --git a/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs index cc4f3646..3e826639 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/args.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/args.rs @@ -23,17 +23,24 @@ pub struct FlashtestationsArgs { )] pub debug: bool, - // Debug url for attestations - #[arg(long = "flashtestations.debug-url", env = "FLASHTESTATIONS_DEBUG_URL")] - pub debug_url: Option, + // Debug static key for the tee key. DO NOT USE IN PRODUCTION + #[arg( + long = "flashtestations.debug-tee-key-seed", + env = "FLASHTESTATIONS_DEBUG_TEE_KEY_SEED", + default_value = "debug" + )] + pub debug_tee_key_seed: String, - /// The rpc url to post the onchain attestation requests to + // Remote url for attestations #[arg( - long = "flashtestations.rpc-url", - env = "FLASHTESTATIONS_RPC_URL", - default_value = "http://localhost:8545" + long = "flashtestations.quote-provider", + env = "FLASHTESTATIONS_QUOTE_PROVIDER" )] - pub rpc_url: String, + pub quote_provider: Option, + + /// The rpc url to post the onchain attestation requests to + #[arg(long = "flashtestations.rpc-url", env = "FLASHTESTATIONS_RPC_URL")] + pub rpc_url: Option, /// Funding key for the TEE key #[arg( diff --git a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs index e52f588a..534dfcb1 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs @@ -9,8 +9,8 @@ const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/ pub struct AttestationConfig { /// If true, uses the debug HTTP service instead of real TDX hardware pub debug: bool, - /// The URL of the debug HTTP service - pub debug_url: Option, + /// The URL of the quote provider + pub quote_provider: Option, } /// Trait for attestation providers @@ -18,18 +18,18 @@ pub trait AttestationProvider { fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result>; } -/// Debug HTTP service attestation provider -pub struct DebugAttestationProvider { +/// Remote attestation provider +pub struct RemoteAttestationProvider { service_url: String, } -impl DebugAttestationProvider { +impl RemoteAttestationProvider { pub fn new(service_url: String) -> Self { Self { service_url } } } -impl AttestationProvider for DebugAttestationProvider { +impl AttestationProvider for RemoteAttestationProvider { fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { let report_data_hex = hex::encode(report_data); let url = format!("{}/{}", self.service_url, report_data_hex); @@ -51,15 +51,16 @@ pub fn get_attestation_provider( config: AttestationConfig, ) -> Box { if config.debug { - Box::new(DebugAttestationProvider::new( + Box::new(RemoteAttestationProvider::new( config - .debug_url + .quote_provider .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), )) } else { - // TODO: replace with real attestation provider - Box::new(DebugAttestationProvider::new( - DEBUG_QUOTE_SERVICE_URL.to_string(), + Box::new(RemoteAttestationProvider::new( + config + .quote_provider + .expect("remote quote provider must be specified when not in debug mode"), )) } } diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 71dbca86..40f12652 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -47,14 +47,15 @@ impl FlashtestationsService { let attestation_provider = Arc::new(get_attestation_provider(AttestationConfig { debug: args.debug, - debug_url: args.debug_url, + quote_provider: args.quote_provider, })); let tx_manager = TxManager::new( tee_service_signer, args.funding_key .expect("funding key required when flashtestations enabled"), - args.rpc_url, + args.rpc_url + .expect("external rpc url required when flashtestations enabled"), args.registry_address .expect("registry address required when flashtestations enabled"), args.builder_policy_address diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs index 71ef7a4b..dd97aed4 100644 --- a/crates/builder/op-rbuilder/src/tx_signer.rs +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use alloy_consensus::SignableTransaction; use alloy_primitives::{Address, B256, Signature, U256}; +use k256::sha2::Sha256; use op_alloy_consensus::OpTypedTransaction; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; @@ -100,6 +101,23 @@ pub fn public_key_to_address(public_key: &PublicKey) -> Address { Address::from_slice(&hash[12..32]) } +// Generate a key deterministically from a seed for debug and testing +// Do not use in production +pub fn generate_key_from_seed(seed: &str) -> (SecretKey, PublicKey, Address) { + // Hash the seed + let mut hasher = Sha256::new(); + hasher.update(seed.as_bytes()); + let hash = hasher.finalize(); + + // Create signing key + let secp = Secp256k1::new(); + let private_key = SecretKey::from_slice(&hash).expect("Failed to create private key"); + let public_key = PublicKey::from_secret_key(&secp, &private_key); + let address = public_key_to_address(&public_key); + + (private_key, public_key, address) +} + #[cfg(test)] mod test { use super::*; From 13426ac5bf63dc4b8ae42ab35c5f08d0a6d69950 Mon Sep 17 00:00:00 2001 From: Ash Kunda <18058966+akundaz@users.noreply.github.com> Date: Tue, 30 Sep 2025 12:23:26 -0400 Subject: [PATCH 191/262] add metrics to track gas used by reverting txs (#273) --- .../builder/op-rbuilder/src/builders/context.rs | 16 ++++++---------- crates/builder/op-rbuilder/src/metrics.rs | 13 +++++++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 9604ced7..b49c697f 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -325,6 +325,7 @@ impl OpPayloadBuilderCtx { let mut num_txs_simulated_success = 0; let mut num_txs_simulated_fail = 0; let mut num_bundles_reverted = 0; + let mut reverted_gas_used = 0; let base_fee = self.base_fee(); let tx_da_limit = self.da_config.max_da_tx_size(); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); @@ -337,15 +338,6 @@ impl OpPayloadBuilderCtx { block_gas_limit = ?block_gas_limit, ); - // Remove once we merge Reth 1.4.4 - // Fixed in https://github.com/paradigmxyz/reth/pull/16514 - self.metrics - .da_block_size_limit - .set(block_da_limit.map_or(-1.0, |v| v as f64)); - self.metrics - .da_tx_size_limit - .set(tx_da_limit.map_or(-1.0, |v| v as f64)); - let block_attr = BlockConditionalAttributes { number: self.block_number(), timestamp: self.attributes().timestamp(), @@ -451,7 +443,7 @@ impl OpPayloadBuilderCtx { } // this is an error that we should treat as fatal for this attempt log_txn(TxnExecutionResult::EvmError); - return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))); + return Err(PayloadBuilderError::evm(err)); } }; @@ -478,8 +470,11 @@ impl OpPayloadBuilderCtx { if result.is_success() { log_txn(TxnExecutionResult::Success); num_txs_simulated_success += 1; + self.metrics.successful_tx_gas_used.record(gas_used as f64); } else { num_txs_simulated_fail += 1; + reverted_gas_used += gas_used as i32; + self.metrics.reverted_tx_gas_used.record(gas_used as f64); if is_bundle_tx { num_bundles_reverted += 1; } @@ -539,6 +534,7 @@ impl OpPayloadBuilderCtx { num_txs_simulated_success, num_txs_simulated_fail, num_bundles_reverted, + reverted_gas_used, ); debug!( diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index df13bd1d..dd43d958 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -123,14 +123,16 @@ pub struct OpRBuilderMetrics { pub payload_num_tx_simulated_fail: Histogram, /// Latest number of transactions in the payload that failed simulation pub payload_num_tx_simulated_fail_gauge: Gauge, + /// Histogram of gas used by successful transactions + pub successful_tx_gas_used: Histogram, + /// Histogram of gas used by reverted transactions + pub reverted_tx_gas_used: Histogram, + /// Gas used by reverted transactions in the latest block + pub payload_reverted_tx_gas_used: Gauge, /// Histogram of tx simulation duration pub tx_simulation_duration: Histogram, /// Byte size of transactions pub tx_byte_size: Histogram, - /// Da block size limit - pub da_block_size_limit: Gauge, - /// Da tx size limit - pub da_tx_size_limit: Gauge, /// How much less flashblocks we issue to be on time with block construction pub reduced_flashblocks_number: Histogram, /// How much less flashblocks we issued in reality, comparing to calculated number for block @@ -152,6 +154,7 @@ pub struct OpRBuilderMetrics { } impl OpRBuilderMetrics { + #[expect(clippy::too_many_arguments)] pub fn set_payload_builder_metrics( &self, payload_tx_simulation_time: impl IntoF64 + Copy, @@ -160,6 +163,7 @@ impl OpRBuilderMetrics { num_txs_simulated_success: impl IntoF64 + Copy, num_txs_simulated_fail: impl IntoF64 + Copy, num_bundles_reverted: impl IntoF64, + reverted_gas_used: impl IntoF64, ) { self.payload_tx_simulation_duration .record(payload_tx_simulation_time); @@ -178,6 +182,7 @@ impl OpRBuilderMetrics { self.payload_num_tx_simulated_fail_gauge .set(num_txs_simulated_fail); self.bundles_reverted.record(num_bundles_reverted); + self.payload_reverted_tx_gas_used.set(reverted_gas_used); } } From 4f5ecd3a72dc9af07c6876a6ea841979853181c7 Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 3 Oct 2025 10:40:47 -0700 Subject: [PATCH 192/262] Add flashblocks number integration tests (#277) * Add flashblocks number integration tests * comments * comments --- .../op-rbuilder/src/builders/builder_tx.rs | 69 +- .../src/builders/flashblocks/builder_tx.rs | 144 ++-- .../src/builders/flashblocks/payload.rs | 21 +- .../src/builders/standard/builder_tx.rs | 2 - .../src/builders/standard/payload.rs | 3 +- .../src/flashtestations/service.rs | 1 - .../src/primitives/reth/execution.rs | 18 +- .../op-rbuilder/src/tests/flashblocks.rs | 255 ++++++- .../contracts/BlockBuilderPolicy.json | 687 ++++++++++++++++++ .../contracts/FlashblocksNumberContract.json | 450 ++++++++++++ .../contracts/FlashtestationRegistry.json | 641 ++++++++++++++++ .../MockAutomataDcapAttestationFee.json | 86 +++ .../framework/artifacts/quote-output.bin | Bin 0 -> 597 bytes .../tests/framework/artifacts/test-quote.bin | Bin 0 -> 5006 bytes .../src/tests/framework/contracts.rs | 43 ++ .../op-rbuilder/src/tests/framework/driver.rs | 4 +- .../src/tests/framework/instance.rs | 13 +- .../op-rbuilder/src/tests/framework/mod.rs | 10 +- .../op-rbuilder/src/tests/framework/txs.rs | 19 +- .../op-rbuilder/src/tests/framework/utils.rs | 130 +++- 20 files changed, 2383 insertions(+), 213 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json create mode 100644 crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json create mode 100644 crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json create mode 100644 crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json create mode 100644 crates/builder/op-rbuilder/src/tests/framework/artifacts/quote-output.bin create mode 100644 crates/builder/op-rbuilder/src/tests/framework/artifacts/test-quote.bin create mode 100644 crates/builder/op-rbuilder/src/tests/framework/contracts.rs diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index b79200d4..a8ba3bd8 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -2,7 +2,7 @@ use alloy_consensus::TxEip1559; use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; use alloy_primitives::{ - Address, TxKind, + Address, B256, Log, TxKind, map::foldhash::{HashSet, HashSetExt}, }; use core::fmt::Debug; @@ -30,7 +30,22 @@ use crate::{ pub struct BuilderTransactionCtx { pub gas_used: u64, pub da_size: u64, - pub signed_tx: Option>, + pub signed_tx: Recovered, + // whether the transaction should be a top of block or + // bottom of block transaction + pub is_top_of_block: bool, +} + +impl BuilderTransactionCtx { + pub fn set_top_of_block(mut self) -> Self { + self.is_top_of_block = true; + self + } + + pub fn set_bottom_of_block(mut self) -> Self { + self.is_top_of_block = false; + self + } } /// Possible error variants during construction of builder txs. @@ -80,7 +95,6 @@ pub trait BuilderTransactions: Debug { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, - top_of_block: bool, ) -> Result, BuilderTransactionError>; fn add_builder_txs( @@ -98,30 +112,26 @@ pub trait BuilderTransactions: Debug { let mut invalid: HashSet
= HashSet::new(); - let builder_txs = self.simulate_builder_txs( - state_provider, - info, - builder_ctx, - evm.db_mut(), - top_of_block, - )?; + let builder_txs = + self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?; for builder_tx in builder_txs.iter() { - let signed_tx = match builder_tx.signed_tx.clone() { - Some(tx) => tx, - None => continue, - }; - if invalid.contains(&signed_tx.signer()) { - warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); + if builder_tx.is_top_of_block != top_of_block { + // don't commit tx if the buidler tx is not being added in the intended + // position in the block + continue; + } + if invalid.contains(&builder_tx.signed_tx.signer()) { + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder signer invalid as previous builder tx reverted"); continue; } let ResultAndState { result, state } = evm - .transact(&signed_tx) + .transact(&builder_tx.signed_tx) .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; if !result.is_success() { - warn!(target: "payload_builder", tx_hash = ?signed_tx.tx_hash(), "builder tx reverted"); - invalid.insert(signed_tx.signer()); + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted"); + invalid.insert(builder_tx.signed_tx.signer()); continue; } @@ -130,7 +140,7 @@ pub trait BuilderTransactions: Debug { info.cumulative_gas_used += gas_used; let ctx = ReceiptBuilderCtx { - tx: signed_tx.inner(), + tx: builder_tx.signed_tx.inner(), evm: &evm, result, state: &state, @@ -142,9 +152,9 @@ pub trait BuilderTransactions: Debug { evm.db_mut().commit(state); // Append sender and transaction to the respective lists - info.executed_senders.push(signed_tx.signer()); + info.executed_senders.push(builder_tx.signed_tx.signer()); info.executed_transactions - .push(signed_tx.clone().into_inner()); + .push(builder_tx.signed_tx.clone().into_inner()); } // Release the db reference by dropping evm @@ -172,12 +182,8 @@ pub trait BuilderTransactions: Debug { .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); for builder_tx in builder_txs { - let signed_tx = match builder_tx.signed_tx.clone() { - Some(tx) => tx, - None => continue, - }; let ResultAndState { state, .. } = evm - .transact(&signed_tx) + .transact(&builder_tx.signed_tx) .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; evm.db_mut().commit(state); @@ -214,7 +220,8 @@ impl BuilderTxBase { Ok(Some(BuilderTransactionCtx { gas_used, da_size, - signed_tx: Some(signed_tx), + signed_tx, + is_top_of_block: false, })) } None => Ok(None), @@ -276,7 +283,7 @@ impl BuilderTxBase { } } -pub(super) fn get_nonce( +pub(crate) fn get_nonce( db: &mut State, address: Address, ) -> Result { @@ -284,3 +291,7 @@ pub(super) fn get_nonce( .map(|acc| acc.account_info().unwrap_or_default().nonce) .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) } + +pub(crate) fn log_exists(logs: &[Log], topic: &B256) -> bool { + logs.iter().any(|log| log.topics().first() == Some(topic)) +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs index 3445bb2e..7588dd4e 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -2,8 +2,8 @@ use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_evm::{Database, Evm}; use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, Bytes, TxKind, U256}; -use alloy_sol_types::{SolCall, SolError, sol}; +use alloy_primitives::{Address, B256, TxKind}; +use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, sol}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; use op_revm::OpHaltReason; @@ -21,7 +21,7 @@ use tracing::warn; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - builder_tx::{BuilderTxBase, get_nonce}, + builder_tx::{BuilderTxBase, get_nonce, log_exists}, context::OpPayloadBuilderCtx, flashblocks::payload::FlashblocksExtraCtx, }, @@ -33,62 +33,34 @@ use crate::{ sol!( // From https://github.com/Uniswap/flashblocks_number_contract/blob/main/src/FlashblockNumber.sol #[sol(rpc, abi)] + #[derive(Debug)] interface IFlashblockNumber { function incrementFlashblockNumber() external; - } - // @notice Emitted when flashblock index is incremented - // @param newFlashblockIndex The new flashblock index (0-indexed within each L2 block) - event FlashblockIncremented(uint256 newFlashblockIndex); + // @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); + /// ----------------------------------------------------------------------- + /// Errors + /// ----------------------------------------------------------------------- + error NonBuilderAddress(address addr); + error MismatchedFlashblockNumber(uint256 expectedFlashblockNumber, uint256 actualFlashblockNumber); + } ); #[derive(Debug, thiserror::Error)] pub(super) enum FlashblockNumberError { - #[error("non builder address: {0}")] - NonBuilderAddress(Address), - #[error("mismatched flashblock number: expected {0}, actual {1}")] - MismatchedFlashblockNumber(U256, U256), - #[error("unknown revert: {0}")] - Unknown(String), + #[error("flashblocks number contract tx reverted: {0:?}")] + Revert(IFlashblockNumber::IFlashblockNumberErrors), + #[error("contract may be invalid, mismatch in log emitted: expected {0:?}")] + LogMismatch(B256), + #[error("unknown revert: {0} err: {1}")] + Unknown(String, Error), #[error("halt: {0:?}")] Halt(OpHaltReason), } -impl From for FlashblockNumberError { - fn from(value: Bytes) -> Self { - // Empty revert - if value.is_empty() { - return FlashblockNumberError::Unknown( - "Transaction reverted without reason".to_string(), - ); - } - - // Try to decode each custom error type - if let Ok(NonBuilderAddress { addr }) = NonBuilderAddress::abi_decode(&value) { - return FlashblockNumberError::NonBuilderAddress(addr); - } - - if let Ok(MismatchedFlashblockNumber { - expectedFlashblockNumber, - actualFlashblockNumber, - }) = MismatchedFlashblockNumber::abi_decode(&value) - { - return FlashblockNumberError::MismatchedFlashblockNumber( - expectedFlashblockNumber, - actualFlashblockNumber, - ); - } - - FlashblockNumberError::Unknown(hex::encode(value)) - } -} - // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct FlashblocksBuilderTx { @@ -116,7 +88,6 @@ impl BuilderTransactions for FlashblocksBuilderTx { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, - top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); @@ -126,24 +97,14 @@ impl BuilderTransactions for FlashblocksBuilderTx { } if ctx.is_last_flashblock() { - let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; - if let Some(tx) = flashblocks_builder_tx.clone() { - if top_of_block { - // don't commit the builder if top of block, we only return the gas used to reserve gas for the builder tx - builder_txs.push(BuilderTransactionCtx { - gas_used: tx.gas_used, - da_size: tx.da_size, - signed_tx: None, - }); - } else { - builder_txs.push(tx); - } - } + let base_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; + builder_txs.extend(base_tx.clone()); + if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { // We only include flashtestations txs in the last flashblock let mut simulation_state = self.simulate_builder_txs_state::( state_provider.clone(), - flashblocks_builder_tx.iter().collect(), + base_tx.iter().collect(), ctx, db, )?; @@ -152,7 +113,6 @@ impl BuilderTransactions for FlashblocksBuilderTx { info, ctx, &mut simulation_state, - top_of_block, )?; builder_txs.extend(flashtestations_builder_txs); } @@ -205,10 +165,27 @@ impl FlashblocksNumberBuilderTx { }; match result { - ExecutionResult::Success { gas_used, .. } => Ok(gas_used), - ExecutionResult::Revert { output, .. } => Err(BuilderTransactionError::Other( - Box::new(FlashblockNumberError::from(output)), - )), + ExecutionResult::Success { gas_used, logs, .. } => { + if log_exists( + &logs, + &IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH, + ) { + Ok(gas_used) + } else { + Err(BuilderTransactionError::Other(Box::new( + FlashblockNumberError::LogMismatch( + IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH, + ), + ))) + } + } + ExecutionResult::Revert { output, .. } => { + Err(BuilderTransactionError::Other(Box::new( + IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&output) + .map(FlashblockNumberError::Revert) + .unwrap_or_else(|e| FlashblockNumberError::Unknown(hex::encode(output), e)), + ))) + } ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::Other(Box::new( FlashblockNumberError::Halt(reason), ))), @@ -245,7 +222,6 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, - top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); let state = StateProviderDatabase::new(state_provider.clone()); @@ -256,8 +232,8 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { .build(); if ctx.is_first_flashblock() { - let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; - builder_txs.extend(flashblocks_builder_tx.clone()); + // fallback block builder tx + builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, db)?); } else { // we increment the flashblock number for the next flashblock so we don't increment in the last flashblock if let Some(signer) = &self.signer { @@ -274,7 +250,7 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { { Ok(gas_used) => { // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer - let flashblocks_tx = self.signed_flashblock_number_tx( + let signed_tx = self.signed_flashblock_number_tx( ctx, gas_used * 64 / 63, nonce, @@ -282,33 +258,20 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { )?; let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - flashblocks_tx.encoded_2718().as_slice(), + signed_tx.encoded_2718().as_slice(), ); Some(BuilderTransactionCtx { gas_used, da_size, - signed_tx: if top_of_block { - Some(flashblocks_tx) - } else { - None - }, // number tx at top of flashblock + signed_tx, + is_top_of_block: true, // number tx at top of flashblock }) } Err(e) => { warn!(target: "builder_tx", error = ?e, "Flashblocks number contract tx simulation failed, defaulting to fallback builder tx"); - let builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; - if let Some(tx) = &builder_tx - && top_of_block - { - // don't commit the builder if top of block, we only return the gas used to reserve gas for the builder tx - Some(BuilderTransactionCtx { - gas_used: tx.gas_used, - da_size: tx.da_size, - signed_tx: None, - }) - } else { - builder_tx - } + self.base_builder_tx + .simulate_builder_tx(ctx, db)? + .map(|tx| tx.set_top_of_block()) } }; @@ -331,7 +294,6 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { info, ctx, &mut simulation_state, - top_of_block, )?; builder_txs.extend(flashtestations_builder_txs); } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index ae11a6da..6a46e4c7 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -82,7 +82,7 @@ pub struct FlashblocksExtraCtx { /// Total gas left for the current flashblock target_gas_for_batch: u64, /// Total DA bytes left for the current flashblock - total_da_per_batch: Option, + target_da_for_batch: Option, /// Gas limit per flashblock gas_per_batch: u64, /// DA bytes limit per flashblock @@ -292,7 +292,7 @@ where flashblock_index: 0, target_flashblock_count: self.config.flashblocks_per_block(), target_gas_for_batch: 0, - total_da_per_batch: None, + target_da_for_batch: None, gas_per_batch: 0, da_per_batch: None, calculate_state_root, @@ -327,7 +327,7 @@ where &mut info, &ctx, &mut state, - true, + false, ) { Ok(builder_txs) => builder_txs, Err(e) => { @@ -431,7 +431,7 @@ where if let Some(da_limit) = total_da_per_batch.as_mut() { *da_limit = da_limit.saturating_sub(builder_tx_da_size); } - ctx.extra_ctx.total_da_per_batch = total_da_per_batch; + ctx.extra_ctx.target_da_for_batch = total_da_per_batch; ctx.extra_ctx.gas_per_batch = gas_per_batch; ctx.extra_ctx.da_per_batch = da_per_batch; @@ -567,7 +567,7 @@ where // Continue with flashblock building let mut target_gas_for_batch = ctx.extra_ctx.target_gas_for_batch; - let mut total_da_per_batch = ctx.extra_ctx.total_da_per_batch; + let mut target_da_per_batch = ctx.extra_ctx.target_da_for_batch; info!( target: "payload_builder", @@ -575,8 +575,9 @@ where flashblock_index = ctx.flashblock_index(), target_gas = target_gas_for_batch, gas_used = info.cumulative_gas_used, - target_da = total_da_per_batch.unwrap_or(0), + target_da = target_da_per_batch, da_used = info.cumulative_da_bytes_used, + block_gas_used = ctx.block_gas_limit(), "Building flashblock", ); let flashblock_build_start_time = Instant::now(); @@ -598,7 +599,7 @@ where target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size - if let Some(da_limit) = total_da_per_batch.as_mut() { + if let Some(da_limit) = target_da_per_batch.as_mut() { *da_limit = da_limit.saturating_sub(builder_tx_da_size); } @@ -624,7 +625,7 @@ where state, best_txs, target_gas_for_batch.min(ctx.block_gas_limit()), - total_da_per_batch, + target_da_per_batch, )?; // Extract last transactions let new_transactions = info.executed_transactions[info.extra.last_flashblock_index..] @@ -726,7 +727,7 @@ where self.send_payload_to_engine(new_payload); // Update bundle_state for next iteration if let Some(da_limit) = ctx.extra_ctx.da_per_batch { - if let Some(da) = total_da_per_batch.as_mut() { + if let Some(da) = target_da_per_batch.as_mut() { *da += da_limit; } else { error!( @@ -736,7 +737,7 @@ where } ctx.extra_ctx.target_gas_for_batch += ctx.extra_ctx.gas_per_batch; - ctx.extra_ctx.total_da_per_batch = total_da_per_batch; + ctx.extra_ctx.target_da_for_batch = target_da_per_batch; info!( target: "payload_builder", diff --git a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs index c4d154f6..c19f627d 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs @@ -40,7 +40,6 @@ impl BuilderTransactions for StandardBuilderTx { info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, db: &mut State, - top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); let standard_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; @@ -57,7 +56,6 @@ impl BuilderTransactions for StandardBuilderTx { info, ctx, &mut simulation_state, - top_of_block, )?; builder_txs.extend(flashtestations_builder_txs); } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index c510a8ba..0c0e5de0 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -347,8 +347,7 @@ impl OpBuilder<'_, Txs> { // 4. if mem pool transactions are requested we execute them // gas reserved for builder tx - let builder_txs = - builder_tx.simulate_builder_txs(&state_provider, &mut info, ctx, db, true)?; + let builder_txs = builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, true)?; 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 { diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 40f12652..bd93a777 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -102,7 +102,6 @@ impl BuilderTransactions for Flashtestation _info: &mut ExecutionInfo, _ctx: &OpPayloadBuilderCtx, _db: &mut State, - _top_of_block: bool, ) -> Result, BuilderTransactionError> { Ok(vec![]) } diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 45acf963..b2591c21 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -8,8 +8,10 @@ use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; #[derive(Debug, Display)] pub enum TxnExecutionResult { TransactionDALimitExceeded, - BlockDALimitExceeded, - TransactionGasLimitExceeded, + #[display("BlockDALimitExceeded: total_da_used={_0} tx_da_size={_1} block_da_limit={_2}")] + BlockDALimitExceeded(u64, u64, u64), + #[display("TransactionGasLimitExceeded: total_gas_used={_0} tx_gas_limit={_1}")] + TransactionGasLimitExceeded(u64, u64, u64), SequencerTransaction, NonceTooLow, InteropFailed, @@ -75,11 +77,19 @@ impl ExecutionInfo { if block_data_limit .is_some_and(|da_limit| self.cumulative_da_bytes_used + tx_da_size > da_limit) { - return Err(TxnExecutionResult::BlockDALimitExceeded); + return Err(TxnExecutionResult::BlockDALimitExceeded( + self.cumulative_da_bytes_used, + tx_da_size, + block_data_limit.unwrap_or_default(), + )); } if self.cumulative_gas_used + tx_gas_limit > block_gas_limit { - return Err(TxnExecutionResult::TransactionGasLimitExceeded); + return Err(TxnExecutionResult::TransactionGasLimitExceeded( + self.cumulative_gas_used, + tx_gas_limit, + block_gas_limit, + )); } Ok(()) } diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index 8204af75..f3e46dae 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -1,12 +1,25 @@ +use alloy_consensus::Transaction; +use alloy_eips::Decodable2718; +use alloy_primitives::{Address, TxHash, U256, address, b128, b256}; use alloy_provider::Provider; +use alloy_sol_types::SolCall; use macros::rb_test; +use op_alloy_consensus::OpTxEnvelope; use std::time::Duration; use crate::{ args::{FlashblocksArgs, OpRbuilderArgs}, - tests::{BlockTransactionsExt, BundleOpts, LocalInstance, TransactionBuilderExt}, + tests::{ + BUILDER_PRIVATE_KEY, BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, + FUNDED_PRIVATE_KEY, LocalInstance, ONE_ETH, TransactionBuilderExt, + flashblocks_number_contract::FlashblocksNumber, + }, + tx_signer::Signer, }; +// If the order of deployment from the signer changes the address will change +const FLASHBLOCKS_NUMBER_ADDRESS: Address = address!("5fbdb2315678afecb367f032d93f642f64180aa3"); + #[rb_test(flashblocks, args = OpRbuilderArgs { chain_block_time: 2000, flashblocks: FlashblocksArgs { @@ -16,8 +29,7 @@ use crate::{ flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -55,8 +67,7 @@ async fn smoke_dynamic_base(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -94,8 +105,7 @@ async fn smoke_dynamic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_block_time: 200, flashblocks_leeway_time: 50, flashblocks_fixed: true, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -133,8 +143,7 @@ async fn smoke_classic_unichain(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_block_time: 200, flashblocks_leeway_time: 50, flashblocks_fixed: true, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -172,8 +181,7 @@ async fn smoke_classic_base(rbuilder: LocalInstance) -> eyre::Result<()> { flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -217,9 +225,7 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, flashblocks_leeway_time: 0, - flashblocks_fixed: false, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -255,10 +261,7 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<() flashblocks_port: 1239, flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, - flashblocks_leeway_time: 100, - flashblocks_fixed: false, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -316,10 +319,7 @@ async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result< flashblocks_port: 1239, flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, - flashblocks_leeway_time: 100, - flashblocks_fixed: false, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -375,8 +375,7 @@ async fn test_flashblock_max_filtering(rbuilder: LocalInstance) -> eyre::Result< flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, - flashblocks_calculate_state_root: true, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -422,7 +421,7 @@ async fn test_flashblock_min_max_filtering(rbuilder: LocalInstance) -> eyre::Res flashblocks_leeway_time: 100, flashblocks_fixed: false, flashblocks_calculate_state_root: false, - flashblocks_number_contract_address: None, + ..Default::default() }, ..Default::default() })] @@ -456,3 +455,209 @@ async fn test_flashblocks_no_state_root_calculation(rbuilder: LocalInstance) -> Ok(()) } + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashblocks_number_contract_builder_tx(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let flashblocks_listener = rbuilder.spawn_flashblocks_listener(); + let provider = rbuilder.provider().await?; + + // Deploy flashblocks number contract which will be in flashblocks 1 + let deploy_tx = driver + .create_transaction() + .deploy_flashblock_number_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // Create valid transactions for flashblocks 2-4 + let user_transactions = create_flashblock_transactions(&driver, 2..5).await?; + + // Build block with deploy tx in first flashblock, and a random valid transfer in every other flashblock + let block = driver.build_new_block_with_current_timestamp(None).await?; + + // Verify contract deployment + let receipt = provider + .get_transaction_receipt(*deploy_tx.tx_hash()) + .await? + .expect("flashblock number contract deployment not mined"); + let contract_address = receipt + .inner + .contract_address + .expect("contract receipt does not contain flashblock number contract address"); + assert_eq!( + contract_address, FLASHBLOCKS_NUMBER_ADDRESS, + "Flashblocks number contract address mismatch" + ); + + // Verify first block structure + assert_eq!(block.transactions.len(), 10); + let txs = block + .transactions + .as_transactions() + .expect("transactions not in block"); + + // Verify builder txs (should be regular since builder tx is not registered yet) + verify_builder_txs( + &txs, + &[1, 2, 4, 6, 8], + Some(Address::ZERO), + "Should have regular builder tx", + ); + + // Verify deploy tx position + assert_eq!( + txs[3].inner.inner.tx_hash(), + *deploy_tx.tx_hash(), + "Deploy tx not in correct position" + ); + + // Verify user transactions + verify_user_tx_hashes(&txs, &[5, 7, 9], &user_transactions); + + // Initialize contract + let init_tx = driver + .create_transaction() + .init_flashblock_number_contract(true) + .with_to(contract_address) + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // Mine initialization + driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*init_tx.tx_hash()) + .await? + .expect("init tx not mined"); + + // Create user transactions for flashblocks 1 - 5 + let user_transactions = create_flashblock_transactions(&driver, 1..5).await?; + + // Build second block after initialization which will call the flashblock number contract + // with builder registered + let block = driver.build_new_block_with_current_timestamp(None).await?; + assert_eq!(block.transactions.len(), 10); + let txs = block + .transactions + .as_transactions() + .expect("transactions not in block"); + + // Fallback block should have regular builder tx after deposit tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "Fallback block should have regular builder tx" + ); + + // Other builder txs should call the contract + verify_builder_txs( + &txs, + &[2, 4, 6, 8], + Some(contract_address), + "Should call flashblocks contract", + ); + + // Verify user transactions, 3 blocks in total built + verify_user_tx_hashes(&txs, &[3, 5, 7, 9], &user_transactions); + + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(contract_address, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert_eq!( + current_number, + U256::from(7), + "Flashblock number not incremented correctly" + ); + + // Verify flashblocks + let flashblocks = flashblocks_listener.get_flashblocks(); + assert_eq!(flashblocks.len(), 15); + + // Verify builder tx in each flashblock + for (i, flashblock) in flashblocks.iter().enumerate() { + // In fallback blocks, builder tx is the 2nd tx (index 1) + // In regular flashblocks, builder tx is the 1st tx (index 0) + let is_fallback = i % 5 == 0; + let tx_index = if is_fallback { 1 } else { 0 }; + + let tx_bytes = flashblock.diff.transactions.get(tx_index).expect(&format!( + "Flashblock {} should have tx at index {}", + i, tx_index + )); + let tx = OpTxEnvelope::decode_2718(&mut tx_bytes.as_ref()) + .expect("failed to decode transaction"); + + let expected_to = if i < 7 || i == 10 { + Some(Address::ZERO) + } else { + Some(contract_address) + }; + + assert_eq!( + tx.to(), + expected_to, + "Flashblock {} builder tx (at index {}) should have to = {:?}", + i, + tx_index, + expected_to + ); + } + + flashblocks_listener.stop().await?; + Ok(()) +} + +// Helper to create transactions for flashblocks +async fn create_flashblock_transactions( + driver: &ChainDriver, + range: std::ops::Range, +) -> eyre::Result> { + let mut txs = Vec::new(); + for i in range { + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(i)) + .send() + .await?; + txs.push(*tx.tx_hash()); + } + Ok(txs) +} + +// Helper to verify builder transactions +fn verify_builder_txs( + block_txs: &[impl Transaction], + indices: &[usize], + expected_to: Option
, + msg: &str, +) { + for &idx in indices { + assert_eq!(block_txs[idx].to(), expected_to, "{} at index {}", msg, idx); + } +} + +// Helper to verify transaction matches +fn verify_user_tx_hashes( + block_txs: &[impl AsRef], + indices: &[usize], + expected_txs: &[TxHash], +) { + for (i, &idx) in indices.iter().enumerate() { + assert_eq!( + *block_txs[idx].as_ref().tx_hash(), + expected_txs[i], + "Transaction at index {} doesn't match", + idx + ); + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json new file mode 100644 index 00000000..6d95e833 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json @@ -0,0 +1,687 @@ +{ + "abi": [ + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { "name": "", "type": "string", "internalType": "string" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", + "inputs": [], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addWorkloadToPolicy", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "internalType": "WorkloadId" + }, + { + "name": "commitHash", + "type": "string", + "internalType": "string" + }, + { + "name": "sourceLocators", + "type": "string[]", + "internalType": "string[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "computeStructHash", + "inputs": [ + { "name": "version", "type": "uint8", "internalType": "uint8" }, + { + "name": "blockContentHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "domainSeparator", + "inputs": [], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { + "name": "fields", + "type": "bytes1", + "internalType": "bytes1" + }, + { "name": "name", "type": "string", "internalType": "string" }, + { + "name": "version", + "type": "string", + "internalType": "string" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getHashedTypeDataV4", + "inputs": [ + { + "name": "structHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getWorkloadMetadata", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "internalType": "WorkloadId" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IBlockBuilderPolicy.WorkloadMetadata", + "components": [ + { + "name": "commitHash", + "type": "string", + "internalType": "string" + }, + { + "name": "sourceLocators", + "type": "string[]", + "internalType": "string[]" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_initialOwner", + "type": "address", + "internalType": "address" + }, + { + "name": "_registry", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "isAllowedPolicy", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { "name": "allowed", "type": "bool", "internalType": "bool" }, + { "name": "", "type": "bytes32", "internalType": "WorkloadId" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "permitNonce", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { "name": "", "type": "address", "internalType": "address" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permitVerifyBlockBuilderProof", + "inputs": [ + { "name": "version", "type": "uint8", "internalType": "uint8" }, + { + "name": "blockContentHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "nonce", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "eip712Sig", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "registry", + "inputs": [], + "outputs": [ + { "name": "", "type": "address", "internalType": "address" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeWorkloadFromPolicy", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "internalType": "WorkloadId" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "verifyBlockBuilderProof", + "inputs": [ + { "name": "version", "type": "uint8", "internalType": "uint8" }, + { + "name": "blockContentHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "workloadIdForTDRegistration", + "inputs": [ + { + "name": "registration", + "type": "tuple", + "internalType": "struct IFlashtestationRegistry.RegisteredTEE", + "components": [ + { + "name": "isValid", + "type": "bool", + "internalType": "bool" + }, + { + "name": "rawQuote", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "parsedReportBody", + "type": "tuple", + "internalType": "struct TD10ReportBody", + "components": [ + { + "name": "teeTcbSvn", + "type": "bytes16", + "internalType": "bytes16" + }, + { + "name": "mrSeam", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "mrsignerSeam", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "seamAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "tdAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "xFAM", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "mrTd", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "mrConfigId", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "mrOwner", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "mrOwnerConfig", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "rtMr0", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "rtMr1", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "rtMr2", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "rtMr3", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "reportData", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "quoteHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "WorkloadId" } + ], + "stateMutability": "pure" + }, + { + "type": "event", + "name": "BlockBuilderProofVerified", + "inputs": [ + { + "name": "caller", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "workloadId", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + }, + { + "name": "blockContentHash", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "commitHash", + "type": "string", + "indexed": false, + "internalType": "string" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RegistrySet", + "inputs": [ + { + "name": "registry", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WorkloadAddedToPolicy", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "WorkloadRemovedFromPolicy", + "inputs": [ + { + "name": "workloadId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [ + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "ERC1967NonPayable", "inputs": [] }, + { "type": "error", "name": "EmptyCommitHash", "inputs": [] }, + { "type": "error", "name": "EmptySourceLocators", "inputs": [] }, + { "type": "error", "name": "FailedCall", "inputs": [] }, + { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { + "type": "error", + "name": "InvalidNonce", + "inputs": [ + { + "name": "expected", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "provided", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { "type": "error", "name": "InvalidRegistry", "inputs": [] }, + { "type": "error", "name": "NotInitializing", "inputs": [] }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { "name": "slot", "type": "bytes32", "internalType": "bytes32" } + ] + }, + { + "type": "error", + "name": "UnauthorizedBlockBuilder", + "inputs": [ + { + "name": "caller", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "WorkloadAlreadyInPolicy", "inputs": [] }, + { "type": "error", "name": "WorkloadNotInPolicy", "inputs": [] } + ], + "bytecode": { + "object": "0x60a0806040523460295730608052613263908161002e8239608051818181610a3801526110790152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80632dd8abfe14611e4c578063485cc955146116b05780634d37fc7a146113365780634f1ef286146110105780634f3a415a14610ab057806352d1902d146109f35780635c40e542146109095780636931164e146108cd578063715018a6146107f3578063730169231461079b5780637b1039991461074a5780637dec71a9146106f95780637ecebe001461069657806384b0196e146104f15780638da5cb5b14610481578063abd45d21146102fc578063ad3cb1cc1461027b578063b33d59da14610237578063d2753561146101e8578063f2fde38b1461019f5763f698da2514610100575f80fd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602061013861310e565b610140613178565b60405190838201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261019060c082612063565b519020604051908152f35b5f80fd5b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e66101d9611feb565b6101e1612d2c565b612b6a565b005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576040610229610224611feb565b6127d1565b825191151582526020820152f35b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e6610271611fad565b6024359033612c57565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576102f86040516102ba604082612063565b600581527f352e302e30000000000000000000000000000000000000000000000000000000602082015260405191829160208352602083019061215f565b0390f35b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576060602060405161033b81612047565b82815201526004355f525f60205260405f2060016040519161035c83612047565b61036581612695565b835201805461037381612349565b916103816040519384612063565b81835260208301905f5260205f205f915b838310610464576103c1868660208201908152604051928392602084525160406020850152606084019061215f565b9051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0838203016040840152815180825260208201916020808360051b8301019401925f915b8383106104155786860387f35b919395509193602080610452837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08660019603018752895161215f565b97019301930190928695949293610408565b60016020819261047385612695565b815201920192019190610392565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10054158061066d575b1561060f576105b36105586124b1565b6105606125c2565b60206105c1604051926105738385612063565b5f84525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e087019061215f565b90858203604087015261215f565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b8281106105f857505050500390f35b8351855286955093810193928101926001016105e9565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415610548565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff6106e2611feb565b165f526002602052602060405f2054604051908152f35b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742610735611fad565b6044359060243590612463565b604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206040517f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc8152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57610829612d2c565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206107426004356123c6565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57600435610943612d2c565b805f525f60205261095760405f20546122f8565b156109cb57805f525f602052600160405f2061097281612377565b018054905f8155816109a6575b827f56c387a9be1bf0e0e4f852c577a225db98e8253ad401d1b4ea73926f27d6af095f80a2005b5f5260205f20908101905b8181101561097f57806109c5600192612377565b016109b1565b7f22faf042000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163003610a885760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043560243567ffffffffffffffff811161019b57610b02903690600401611fbd565b919060443567ffffffffffffffff811161019b573660238201121561019b5780600401359167ffffffffffffffff831161019b5760248360051b8301019036821161019b57610b4f612d2c565b8515610fe8578315610fc057845f525f602052610b6f60405f20546122f8565b610f9857610b8b9060405196610b8488612047565b36916120de565b8552610b9683612349565b92610ba46040519485612063565b83526024820191602084015b828410610f57575050505060208301908152815f525f60205260405f20925192835167ffffffffffffffff8111610e2057610beb82546122f8565b601f8111610f27575b506020601f8211600114610e85579080610c469260019596975f92610e7a575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b81555b019051805190680100000000000000008211610e20578254828455808310610e4d575b50602001915f5260205f20915f905b828210610ca957847fcbb92e241e191fed6d0b0da0a918c7dcf595e77d868e2e3bf9e6b0b91589c7ad5f80a2005b805180519067ffffffffffffffff8211610e2057610cc786546122f8565b601f8111610de5575b50602090601f8311600114610d3f5792610d25836001959460209487965f92610d345750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b87555b01940191019092610c7b565b015190508b80610c14565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0831691875f52815f20925f5b818110610dcd5750936020936001969387969383889510610d96575b505050811b018755610d28565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558a8080610d89565b92936020600181928786015181550195019301610d6d565b610e1090875f5260205f20601f850160051c81019160208610610e16575b601f0160051c0190612361565b87610cd0565b9091508190610e03565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b835f528260205f2091820191015b818110610e685750610c6c565b80610e74600192612377565b01610e5b565b015190508780610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821695835f52815f20965f5b818110610f0f5750916001959697918487959410610ed8575b505050811b018155610c49565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080610ecb565b83830151895560019098019760209384019301610eb2565b610f5190835f5260205f20601f840160051c81019160208510610e1657601f0160051c0190612361565b85610bf4565b833567ffffffffffffffff811161019b5782013660438201121561019b57602091610f8d839236906044602482013591016120de565b815201930192610bb0565b7f72477348000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f6890d9d4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8423f262000000000000000000000000000000000000000000000000000000005f5260045ffd5b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611042611feb565b60243567ffffffffffffffff811161019b57611062903690600401612114565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168030149081156112f4575b50610a88576110b1612d2c565b73ffffffffffffffffffffffffffffffffffffffff8216916040517f52d1902d000000000000000000000000000000000000000000000000000000008152602081600481875afa5f91816112c0575b5061113157837f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8592036112955750813b1561126a57807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2815115611239575f808360206101e695519101845af43d15611231573d91611215836120a4565b926112236040519485612063565b83523d5f602085013e6131bd565b6060916131bd565b50503461124257005b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7faa1d49a4000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091506020813d6020116112ec575b816112dc60209383612063565b8101031261019b57519085611100565b3d91506112cf565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54161415836110a4565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043567ffffffffffffffff811161019b5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906113b08261200e565b8060040135801515810361019b578252602481013567ffffffffffffffff811161019b576113e49060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b5781016101e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906114398261202a565b60048101357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602481013567ffffffffffffffff811161019b5761148d9060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b576114b69060043691840101612114565b60408301526114c760648201612132565b60608301526114d860848201612132565b60808301526114e960a48201612132565b60a083015260c481013567ffffffffffffffff811161019b576115129060043691840101612114565b60c083015260e481013567ffffffffffffffff811161019b5761153b9060043691840101612114565b60e083015261010481013567ffffffffffffffff811161019b576115659060043691840101612114565b61010083015261012481013567ffffffffffffffff811161019b576115909060043691840101612114565b61012083015261014481013567ffffffffffffffff811161019b576115bb9060043691840101612114565b61014083015261016481013567ffffffffffffffff811161019b576115e69060043691840101612114565b61016083015261018481013567ffffffffffffffff811161019b576116119060043691840101612114565b6101808301526101a481013567ffffffffffffffff811161019b5761163c9060043691840101612114565b6101a08301526101c48101359067ffffffffffffffff821161019b5760046116679236920101612114565b6101c0820152604083015260648101359167ffffffffffffffff831161019b5760846107429261169f60209560043691840101612114565b6060840152013560808201526121a2565b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576116e7611feb565b60243573ffffffffffffffffffffffffffffffffffffffff811680910361019b577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c16159267ffffffffffffffff821680159081611e44575b6001149081611e3a575b159081611e31575b50611e0957818460017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006117c19516177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055611db4575b506117b9613028565b6101e1613028565b6117c9613028565b6040918251926117d98185612063565b601284527f426c6f636b4275696c646572506f6c696379000000000000000000000000000060208501528051936118108286612063565b600185527f31000000000000000000000000000000000000000000000000000000000000006020860152611842613028565b61184a613028565b80519067ffffffffffffffff8211610e20576118867fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546122f8565b601f8111611d47575b50602090601f8311600114611c67576118dc92915f9183610e7a5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102555b835167ffffffffffffffff8111610e205761193a7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546122f8565b601f8111611bfa575b50602094601f8211600114611b1c576119939293949582915f92611b115750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103555b5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100555f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101558215611ae957827fffffffffffffffffffffffff0000000000000000000000000000000000000000600154161760015551917f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b5f80a2611a5857005b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2917fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005560018152a1005b7f11a1e697000000000000000000000000000000000000000000000000000000005f5260045ffd5b015190508680610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52805f20915f5b888110611be257508360019596979810611bab575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103556119b6565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080611b7e565b91926020600181928685015181550194019201611b69565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52611c61907f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c81019160208510610e1657601f0160051c0190612361565b85611943565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52815f20925f5b818110611d2f5750908460019594939210611cf8575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102556118ff565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080611ccb565b92936020600181928786015181550195019301611cb5565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52611dae907f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f850160051c81019160208610610e1657601f0160051c0190612361565b8661188f565b7fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055846117b0565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b90501585611759565b303b159150611751565b859150611747565b3461019b5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611e83611fad565b602435604435916064359267ffffffffffffffff841161019b57611ed7611ed1611eb4611ee0963690600401611fbd565b9190611ec9611ec4868989612463565b6123c6565b9236916120de565b90612d98565b90959195612dd2565b73ffffffffffffffffffffffffffffffffffffffff841690815f52600260205260405f2054808203611f7f5750505f52600260205260405f20928354937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8514611f525760016101e695019055612c57565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7f06427aeb000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b6004359060ff8216820361019b57565b9181601f8401121561019b5782359167ffffffffffffffff831161019b576020838186019501011161019b57565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361019b57565b60a0810190811067ffffffffffffffff821117610e2057604052565b6101e0810190811067ffffffffffffffff821117610e2057604052565b6040810190811067ffffffffffffffff821117610e2057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610e2057604052565b67ffffffffffffffff8111610e2057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926120ea826120a4565b916120f86040519384612063565b82948184528183011161019b578281602093845f960137010152565b9080601f8301121561019b5781602061212f933591016120de565b90565b35907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b6040015160c081015190610140810151610160820151916101808101516101a082015160e08301519060a08401517fffffffffffffffff0000000000000000000000000000000000000000000000001678030000000000000000000000000000000000000000000000001893608001517fffffffff2fffffff00000000000000000000000000000000000000000000000016926040519687966020880199805160208192018c5e880160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e01917fffffffffffffffff0000000000000000000000000000000000000000000000001682526008820152037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0810182526010016122f29082612063565b51902090565b90600182811c9216801561233f575b602083101461231257565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f1691612307565b67ffffffffffffffff8111610e205760051b60200190565b81811061236c575050565b5f8155600101612361565b61238181546122f8565b908161238b575050565b81601f5f931160011461239d5750555b565b818352602083206123b991601f0160051c810190600101612361565b8082528160208120915555565b6042906123d161310e565b6123d9613178565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261242a60c082612063565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b916040519160ff60208401947f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc865216604084015260608301526080820152608081526122f260a082612063565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10254916124e3836122f8565b80835292600181169081156125855750600114612507575b61239b92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b81831061256957505090602061239b928201016124fb565b6020919350806001915483858901015201910190918492612551565b6020925061239b9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016124fb565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10354916125f4836122f8565b808352926001811690811561258557506001146126175761239b92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b81831061267957505090602061239b928201016124fb565b6020919350806001915483858901015201910190918492612661565b9060405191825f8254926126a8846122f8565b808452936001811690811561271157506001146126cd575b5061239b92500383612063565b90505f9291925260205f20905f915b8183106126f557505090602061239b928201015f6126c0565b60209193508060019154838589010152019101909184926126dc565b6020935061239b9592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201015f6126c0565b5190811515820361019b57565b81601f8201121561019b57805190612775826120a4565b926127836040519485612063565b8284526020838301011161019b57815f9260208093018386015e8301015290565b51907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b5f73ffffffffffffffffffffffffffffffffffffffff602481600154169360405194859384927f727310620000000000000000000000000000000000000000000000000000000084521660048301525afa908115612b5f575f9161286c575b5080511561286557612841906121a2565b805f525f60205261285560405f20546122f8565b61286057505f905f90565b600191565b505f905f90565b90503d805f833e61287d8183612063565b81019060408183031261019b5761289381612751565b5060208101519067ffffffffffffffff821161019b57019060a08282031261019b57604051916128c28361200e565b6128cb81612751565b8352602081015167ffffffffffffffff811161019b57826128ed91830161275e565b6020840152604081015167ffffffffffffffff811161019b5781016101e08184031261019b57604051906129208261202a565b80517fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602081015167ffffffffffffffff811161019b578461296d91830161275e565b6020830152604081015167ffffffffffffffff811161019b578461299291830161275e565b60408301526129a3606082016127a4565b60608301526129b4608082016127a4565b60808301526129c560a082016127a4565b60a083015260c081015167ffffffffffffffff811161019b57846129ea91830161275e565b60c083015260e081015167ffffffffffffffff811161019b5784612a0f91830161275e565b60e083015261010081015167ffffffffffffffff811161019b5784612a3591830161275e565b61010083015261012081015167ffffffffffffffff811161019b5784612a5c91830161275e565b61012083015261014081015167ffffffffffffffff811161019b5784612a8391830161275e565b61014083015261016081015167ffffffffffffffff811161019b5784612aaa91830161275e565b61016083015261018081015167ffffffffffffffff811161019b5784612ad191830161275e565b6101808301526101a081015167ffffffffffffffff811161019b5784612af891830161275e565b6101a08301526101c08101519067ffffffffffffffff821161019b57612b209185910161275e565b6101c08201526040840152606081015167ffffffffffffffff811161019b57608092612b4d91830161275e565b6060840152015160808201525f612830565b6040513d5f823e3d90fd5b73ffffffffffffffffffffffffffffffffffffffff168015612c2b5773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b91612c6183612eaa565b929015612cea57827f3fa039a23466a52e08acb25376ac7d81de184fa6549ffffb2fc920c47cb623ed949260ff612ce59373ffffffffffffffffffffffffffffffffffffffff965f525f602052612cba60405f20612695565b936040519788971687526020870152166040850152606084015260a0608084015260a083019061215f565b0390a1565b73ffffffffffffffffffffffffffffffffffffffff847f4c547670000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303612d6c57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b8151919060418303612dc857612dc19250602082015190606060408401519301515f1a9061307f565b9192909190565b50505f9160029190565b6004811015612e7d5780612de4575050565b60018103612e14577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b60028103612e4857507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600314612e525750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff600154166040517fa8af4ff500000000000000000000000000000000000000000000000000000000815260408160248173ffffffffffffffffffffffffffffffffffffffff8716958660048301525afa908115612b5f575f905f92612fe7575b5015612fde57815f52600360205260405f209160405193612f3f85612047565b60018454948587520154806020870152838515159182612fd4575b505015612f82575050505f525f602052612f7760405f20546122f8565b156128655751600191565b909250612f91919493506127d1565b93819291612fa0575b50509190565b60019060405192612fb084612047565b868452602084019182525f52600360205260405f2092518355519101555f80612f9a565b149050835f612f5a565b5050505f905f90565b9150506040813d604011613020575b8161300360409383612063565b8101031261019b57602061301682612751565b910151905f612f1f565b3d9150612ff6565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561305757565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411613103579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612b5f575f5173ffffffffffffffffffffffffffffffffffffffff8116156130f957905f905f90565b505f906001905f90565b5050505f9160039190565b6131166124b1565b8051908115613126576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1005480156131535790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b6131806125c2565b8051908115613190576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015480156131535790565b906131fa57508051156131d257805190602001fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b8151158061324d575b61320b575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b1561320356fea164736f6c634300081c000a", + "sourceMap": "1951:12842:96:-:0;;;;;;;1171:4:51;1163:13;;1951:12842:96;;;;;;1163:13:51;1951:12842:96;;;;;;;;;;;;;;", + "linkReferences": {} + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json new file mode 100644 index 00000000..1a3b1e6c --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json @@ -0,0 +1,450 @@ +{ + "abi": [ + { + "type": "function", + "name": "PERMIT_INCREMENT_TYPEHASH", + "inputs": [], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { "name": "", "type": "string", "internalType": "string" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "addBuilder", + "inputs": [ + { + "name": "builder", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "computeStructHash", + "inputs": [ + { + "name": "currentFlashblockNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { + "name": "fields", + "type": "bytes1", + "internalType": "bytes1" + }, + { "name": "name", "type": "string", "internalType": "string" }, + { + "name": "version", + "type": "string", + "internalType": "string" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "flashblockNumber", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getFlashblockNumber", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "hashTypedDataV4", + "inputs": [ + { + "name": "structHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "incrementFlashblockNumber", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + }, + { + "name": "_initialBuilders", + "type": "address[]", + "internalType": "address[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "isBuilder", + "inputs": [ + { "name": "", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { "name": "", "type": "address", "internalType": "address" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permitIncrementFlashblockNumber", + "inputs": [ + { + "name": "currentFlashblockNumber", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { "name": "", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeBuilder", + "inputs": [ + { + "name": "builder", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "BuilderAdded", + "inputs": [ + { + "name": "builder", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BuilderRemoved", + "inputs": [ + { + "name": "builder", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "FlashblockIncremented", + "inputs": [ + { + "name": "newFlashblockIndex", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "AddressIsAlreadyABuilder", + "inputs": [ + { "name": "addr", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "BuilderDoesNotExist", + "inputs": [ + { "name": "addr", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [ + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "ERC1967NonPayable", "inputs": [] }, + { "type": "error", "name": "FailedCall", "inputs": [] }, + { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { + "type": "error", + "name": "MismatchedFlashblockNumber", + "inputs": [ + { + "name": "expectedFlashblockNumber", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "actualFlashblockNumber", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NonBuilderAddress", + "inputs": [ + { "name": "addr", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "NotInitializing", "inputs": [] }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { "name": "slot", "type": "bytes32", "internalType": "bytes32" } + ] + } + ], + "bytecode": { + "object": "0x60a0806040523460295730608052611f7d908161002e8239608051818181611013015261114c0152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806325f5ffa41461147c5780633fd553e8146114455780634980f288146114095780634f1ef286146110e35780634f66ee131461108b57806352d1902d14610fce578063715018a614610ef457806384b0196e14610d4f5780638da5cb5b14610cdf578063946d920414610439578063a0a78ce01461025d578063ad3cb1cc146103b8578063b6b6b47514610350578063c91d762514610297578063e5b37c5d1461025d578063e7e3a27114610219578063ec9693861461012c5763f2fde38b146100df575f80fd5b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285761012661011961156b565b6101216118a2565b6117b5565b005b5f80fd5b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285773ffffffffffffffffffffffffffffffffffffffff61017861156b565b6101806118a2565b16805f52600160205260ff60405f205416156101ee57805f52600160205260405f207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541690557fc5a4a43135540d5e0967677a1ed86bf147f7c0e7dd757a109f4cff74c945f92e5f80a2005b7f677fda95000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128576020610255600435611770565b604051908152f35b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285760205f54604051908152f35b346101285760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285760043560243567ffffffffffffffff8111610128576102e9903690600401611609565b905f548082036103215761012661031c6103138561030e61030987611770565b611692565b611c7c565b90929192611cb6565b61190e565b907f53b6b59a000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285773ffffffffffffffffffffffffffffffffffffffff61039c61156b565b165f526001602052602060ff60405f2054166040519015158152f35b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128576104356040516103f760408261158e565b600581527f352e302e30000000000000000000000000000000000000000000000000000000602082015260405191829160208352602083019061164f565b0390f35b346101285760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285761047061156b565b6024359067ffffffffffffffff821161012857366023830112156101285781600401359167ffffffffffffffff8311610a76578260051b9060208201936104ba604051958661158e565b8452602460208501928201019036821161012857602401915b818310610cb2575050507ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c16159167ffffffffffffffff821680159081610caa575b6001149081610ca0575b159081610c97575b50610c6f57818360017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006105969516177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055610c1a575b5061058e611c25565b610121611c25565b61059e611c25565b6040908151926105ae838561158e565b601084527f466c617368626c6f636b4e756d6265720000000000000000000000000000000060208501528251936105e5848661158e565b600185527f31000000000000000000000000000000000000000000000000000000000000006020860152610617611c25565b61061f611c25565b80519067ffffffffffffffff8211610a7657819061065d7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546119ee565b601f8111610b8d575b50602090601f8311600114610aae575f92610aa3575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c1916177fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102555b835167ffffffffffffffff8111610a76576107097fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546119ee565b601f81116109f4575b50602094601f8211600114610916579481929394955f9261090b575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c1916177fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103555b5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100555f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101555f5b8151811015610871578073ffffffffffffffffffffffffffffffffffffffff6107f36001938561172f565b51165f5281602052845f20827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082541617905573ffffffffffffffffffffffffffffffffffffffff610845828561172f565b51167fc2dabef8a63ab48fcf728bbe8864a3fe01a9e19addc6ce314abd5b6f9a1dce665f80a2016107c8565b505061087957005b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2917fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00555160018152a1005b01519050858061072e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52805f20915f5b8881106109dc575083600195969798106109a5575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10355610780565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080610978565b91926020600181928685015181550194019201610963565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f527f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f830160051c81019160208410610a6c575b601f0160051c01905b818110610a615750610712565b5f8155600101610a54565b9091508190610a4b565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b01519050868061067c565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52815f20925f5b818110610b755750908460019594939210610b3e575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102556106ce565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080610b11565b92936020600181928786015181550195019301610afb565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f529091507f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f840160051c81019160208510610c10575b90601f859493920160051c01905b818110610c025750610666565b5f8155849350600101610bf5565b9091508190610be7565b7fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005584610585565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050158561052e565b303b159150610526565b84915061051c565b823573ffffffffffffffffffffffffffffffffffffffff81168103610128578152602092830192016104d3565b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012857602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100541580610ecb575b15610e6d57610e11610db6611a3f565b610dbe611b52565b6020610e1f60405192610dd1838561158e565b5f84525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e087019061164f565b90858203604087015261164f565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b828110610e5657505050500390f35b835185528695509381019392810192600101610e47565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415610da6565b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261012857610f2a6118a2565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285773ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001630036110635760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285760206040517f51de50fd99b637d778db8fe7a8a1966ddae5530dd6e4d8f693ffb315c812a24d8152f35b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285761111561156b565b60243567ffffffffffffffff811161012857611135903690600401611609565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168030149081156113c7575b50611063576111846118a2565b73ffffffffffffffffffffffffffffffffffffffff8216916040517f52d1902d000000000000000000000000000000000000000000000000000000008152602081600481875afa5f9181611393575b5061120457837f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8592036113685750813b1561133d57807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a281511561130c575f8083602061012695519101845af43d15611304573d916112e8836115cf565b926112f6604051948561158e565b83523d5f602085013e611ed7565b606091611ed7565b50503461131557005b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7faa1d49a4000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091506020813d6020116113bf575b816113af6020938361158e565b81010312610128575190856111d3565b3d91506113a2565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416141583611177565b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128576020610255600435611692565b34610128575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610128576101263361190e565b346101285760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101285773ffffffffffffffffffffffffffffffffffffffff6114c861156b565b6114d06118a2565b16805f52600160205260ff60405f20541661154057805f52600160205260405f2060017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008254161790557fc2dabef8a63ab48fcf728bbe8864a3fe01a9e19addc6ce314abd5b6f9a1dce665f80a2005b7fc0b0858c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361012857565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610a7657604052565b67ffffffffffffffff8111610a7657601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b81601f8201121561012857803590611620826115cf565b9261162e604051948561158e565b8284526020838301011161012857815f926020809301838601378301015290565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b60429061169d611e28565b6116a5611e92565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526116f660c08261158e565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b80518210156117435760209160051b010190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b60405160208101917f51de50fd99b637d778db8fe7a8a1966ddae5530dd6e4d8f693ffb315c812a24d83526040820152604081526117af60608261158e565b51902090565b73ffffffffffffffffffffffffffffffffffffffff1680156118765773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300541633036118e257565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff16805f52600160205260ff60405f205416156119c357505f547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461199657602060017ffaddd0b06e793a583e92393ad3f98637e560462ee98db1f2888f141124ee64ca9201805f55604051908152a1565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fdd9dbe80000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b90600182811c92168015611a35575b6020831014611a0857565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916119fd565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025491611a71836119ee565b8083529260018116908115611b155750600114611a97575b611a959250038361158e565b565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b818310611af9575050906020611a9592820101611a89565b6020919350806001915483858901015201910190918492611ae1565b60209250611a959491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b820101611a89565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035491611b84836119ee565b8083529260018116908115611b155750600114611ba757611a959250038361158e565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b818310611c09575050906020611a9592820101611a89565b6020919350806001915483858901015201910190918492611bf1565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c1615611c5457565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b8151919060418303611cac57611ca59250602082015190606060408401519301515f1a90611d8e565b9192909190565b50505f9160029190565b6004811015611d615780611cc8575050565b60018103611cf8577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b60028103611d2c57507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600314611d365750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411611e1d579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15611e12575f5173ffffffffffffffffffffffffffffffffffffffff811615611e0857905f905f90565b505f906001905f90565b6040513d5f823e3d90fd5b5050505f9160039190565b611e30611a3f565b8051908115611e40576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100548015611e6d5790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b611e9a611b52565b8051908115611eaa576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101548015611e6d5790565b90611f145750805115611eec57602081519101fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580611f67575b611f25575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b15611f1d56fea164736f6c634300081c000a", + "sourceMap": "1449:6259:60:-:0;;;;;;;1171:4:22;1163:13;;1449:6259:60;;;;;;1163:13:22;1449:6259:60;;;;;;;;;;;;;;", + "linkReferences": {} + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json new file mode 100644 index 00000000..d823e0c3 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/FlashtestationRegistry.json @@ -0,0 +1,641 @@ +{ + "abi": [ + { + "type": "function", + "name": "MAX_BYTES_SIZE", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "REGISTER_TYPEHASH", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "TD_REPORTDATA_LENGTH", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "attestationContract", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IAttestation" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "computeStructHash", + "inputs": [ + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "domainSeparator", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { "name": "fields", "type": "bytes1", "internalType": "bytes1" }, + { "name": "name", "type": "string", "internalType": "string" }, + { "name": "version", "type": "string", "internalType": "string" }, + { "name": "chainId", "type": "uint256", "internalType": "uint256" }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { "name": "salt", "type": "bytes32", "internalType": "bytes32" }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRegistration", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "", "type": "bool", "internalType": "bool" }, + { + "name": "", + "type": "tuple", + "internalType": "struct IFlashtestationRegistry.RegisteredTEE", + "components": [ + { "name": "isValid", "type": "bool", "internalType": "bool" }, + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "parsedReportBody", + "type": "tuple", + "internalType": "struct TD10ReportBody", + "components": [ + { + "name": "teeTcbSvn", + "type": "bytes16", + "internalType": "bytes16" + }, + { "name": "mrSeam", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrsignerSeam", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "seamAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "tdAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { "name": "xFAM", "type": "bytes8", "internalType": "bytes8" }, + { "name": "mrTd", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrConfigId", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "mrOwner", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrOwnerConfig", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "rtMr0", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr1", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr2", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr3", "type": "bytes", "internalType": "bytes" }, + { + "name": "reportData", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "quoteHash", + "type": "bytes32", + "internalType": "bytes32" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRegistrationStatus", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "isValid", "type": "bool", "internalType": "bool" }, + { "name": "quoteHash", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "hashTypedDataV4", + "inputs": [ + { "name": "structHash", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { + "name": "_attestationContract", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "invalidateAttestation", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "invalidatePreviousSignature", + "inputs": [ + { "name": "_nonce", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "permitNonce", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permitRegisterTEEService", + "inputs": [ + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "signature", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "registerTEEService", + "inputs": [ + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "registeredTEEs", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ], + "outputs": [ + { "name": "isValid", "type": "bool", "internalType": "bool" }, + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" }, + { + "name": "parsedReportBody", + "type": "tuple", + "internalType": "struct TD10ReportBody", + "components": [ + { + "name": "teeTcbSvn", + "type": "bytes16", + "internalType": "bytes16" + }, + { "name": "mrSeam", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrsignerSeam", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "seamAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { + "name": "tdAttributes", + "type": "bytes8", + "internalType": "bytes8" + }, + { "name": "xFAM", "type": "bytes8", "internalType": "bytes8" }, + { "name": "mrTd", "type": "bytes", "internalType": "bytes" }, + { "name": "mrConfigId", "type": "bytes", "internalType": "bytes" }, + { "name": "mrOwner", "type": "bytes", "internalType": "bytes" }, + { + "name": "mrOwnerConfig", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "rtMr0", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr1", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr2", "type": "bytes", "internalType": "bytes" }, + { "name": "rtMr3", "type": "bytes", "internalType": "bytes" }, + { "name": "reportData", "type": "bytes", "internalType": "bytes" } + ] + }, + { + "name": "extendedRegistrationData", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "quoteHash", "type": "bytes32", "internalType": "bytes32" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { "name": "newOwner", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { "name": "data", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PreviousSignatureInvalidated", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "invalidatedNonce", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "TEEServiceInvalidated", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "TEEServiceRegistered", + "inputs": [ + { + "name": "teeAddress", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "rawQuote", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "alreadyExists", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { "name": "target", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ByteSizeExceeded", + "inputs": [ + { "name": "size", "type": "uint256", "internalType": "uint256" } + ] + }, + { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { "name": "length", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [{ "name": "s", "type": "bytes32", "internalType": "bytes32" }] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { "type": "error", "name": "ERC1967NonPayable", "inputs": [] }, + { + "type": "error", + "name": "ExpiredSignature", + "inputs": [ + { "name": "deadline", "type": "uint256", "internalType": "uint256" } + ] + }, + { "type": "error", "name": "FailedCall", "inputs": [] }, + { "type": "error", "name": "InvalidAttestationContract", "inputs": [] }, + { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { + "type": "error", + "name": "InvalidNonce", + "inputs": [ + { "name": "expected", "type": "uint256", "internalType": "uint256" }, + { "name": "provided", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "InvalidQuote", + "inputs": [{ "name": "output", "type": "bytes", "internalType": "bytes" }] + }, + { + "type": "error", + "name": "InvalidQuoteLength", + "inputs": [ + { "name": "length", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "InvalidRegistrationDataHash", + "inputs": [ + { "name": "expected", "type": "bytes32", "internalType": "bytes32" }, + { "name": "received", "type": "bytes32", "internalType": "bytes32" } + ] + }, + { + "type": "error", + "name": "InvalidReportDataLength", + "inputs": [ + { "name": "length", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "InvalidTEEType", + "inputs": [ + { "name": "teeType", "type": "bytes4", "internalType": "bytes4" } + ] + }, + { + "type": "error", + "name": "InvalidTEEVersion", + "inputs": [ + { "name": "version", "type": "uint16", "internalType": "uint16" } + ] + }, + { "type": "error", "name": "NotInitializing", "inputs": [] }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "ReentrancyGuardReentrantCall", "inputs": [] }, + { + "type": "error", + "name": "SignerMustMatchTEEAddress", + "inputs": [ + { "name": "signer", "type": "address", "internalType": "address" }, + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "TEEIsStillValid", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "TEEServiceAlreadyInvalid", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "TEEServiceAlreadyRegistered", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "TEEServiceNotRegistered", + "inputs": [ + { "name": "teeAddress", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "UUPSUnauthorizedCallContext", "inputs": [] }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { "name": "slot", "type": "bytes32", "internalType": "bytes32" } + ] + } + ], + "bytecode": { + "object": "0x60a0806040523460295730608052614e62908161002e8239608051818181610a320152610c3e0152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80630634434a146101845780630ac3302b1461017f57806322ba2bbf1461017a578063485cc955146101755780634980f288146101705780634f1ef2861461016b57806352d1902d146101665780636a5306a314610161578063715018a61461015c57806372731062146101575780637ecebe001461015257806384b0196e1461014d578063878111121461014857806387be6d4e146101435780638da5cb5b1461013e578063a8af4ff514610139578063aaae748e14610134578063ad3cb1cc1461012f578063e41689521461012a578063f2fde38b14610125578063f698da2514610120578063f745cb301461011b5763f9b68b3114610116575f80fd5b611ac7565b611a11565b61163a565b6115f3565b6115ba565b61153d565b611503565b611492565b611422565b6113d2565b611340565b611221565b611110565b61105d565b610ce6565b610c8e565b610bf9565b6109b7565b61083e565b6105bd565b6102e9565b610248565b6101bb565b9181601f840112156101b75782359167ffffffffffffffff83116101b757602083818601950101116101b757565b5f80fd5b346101b75760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760043567ffffffffffffffff81116101b75761020a903690600401610189565b906024359067ffffffffffffffff82116101b757602092610232610240933690600401610189565b906044359260643594611c82565b604051908152f35b60a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760043567ffffffffffffffff81116101b757610292903690600401610189565b60243567ffffffffffffffff81116101b7576102b2903690600401610189565b6084359391606435916044359167ffffffffffffffff87116101b7576102df6102e7973690600401610189565b969095611d01565b005b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760043567ffffffffffffffff81116101b757610333903690600401610189565b60243567ffffffffffffffff81116101b757610353903690600401610189565b91909261035e612781565b61037861036c368484610981565b5161500081111561281a565b61038661036c368587610981565b6103c06103a75f5473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b5f60405180927f38d8480a00000000000000000000000000000000000000000000000000000000825281806103f988886004840161288b565b039134905af1908115610572575f905f9261054b575b508161041a9161289c565b61042390613349565b6101c081018051516104399060348110156128e5565b5161044390613526565b73ffffffffffffffffffffffffffffffffffffffff821696906104698333808b14612918565b610474368884610981565b805190602001208181149161048892612969565b610493368686610981565b80519060200120906104a582846136e1565b966104ae610926565b60018152946104be368989610981565b6020870152604086015236906104d392610981565b606084015260808301526105059073ffffffffffffffffffffffffffffffffffffffff165f52600160205260405f2090565b9061050f91612dce565b60405192839261051f9284612f62565b037f206fdb1a74851a8542447b8b6704db24a36b906a7297cc23c2b984dc357b997891a26102e76127f5565b61041a925061056c91503d805f833e61056481836108e5565b8101906126a7565b9161040f565b61272c565b6004359073ffffffffffffffffffffffffffffffffffffffff821682036101b757565b6024359073ffffffffffffffffffffffffffffffffffffffff821682036101b757565b346101b75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b7576105f4610577565b6105fc61059a565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054919067ffffffffffffffff61064360ff604086901c16159467ffffffffffffffff1690565b1680159081610836575b600114908161082c575b159081610823575b506107fb576106e091836106d760017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000007ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005416177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055565b61078057612071565b6106e657005b6107517fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055565b604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d290602090a1005b6107f6680100000000000000007fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005416177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055565b612071565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b9050155f61065f565b303b159150610657565b84915061064d565b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760206102406004356123af565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b60a0810190811067ffffffffffffffff8211176108c357604052565b61087a565b6101e0810190811067ffffffffffffffff8211176108c357604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176108c357604052565b6040519061093560a0836108e5565b565b604051906109356101e0836108e5565b67ffffffffffffffff81116108c357601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b92919261098d82610947565b9161099b60405193846108e5565b8294818452818301116101b7578281602093845f960137010152565b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b7576109e9610577565b60243567ffffffffffffffff81116101b757366023820112156101b757610a1a903690602481600401359101610981565b9073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016803014908115610bb7575b50610b8f57610a6a612f92565b604051917f52d1902d00000000000000000000000000000000000000000000000000000000835260208360048173ffffffffffffffffffffffffffffffffffffffff86165afa5f9381610b5e575b50610aff577f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5273ffffffffffffffffffffffffffffffffffffffff821660045260245ffd5b907f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8303610b31576102e79250613964565b7faa1d49a4000000000000000000000000000000000000000000000000000000005f52600483905260245ffd5b610b8191945060203d602011610b88575b610b7981836108e5565b810190612f83565b925f610ab8565b503d610b6f565b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc541614155f610a5d565b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163003610b8f5760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760206040517f95b0f36aa3383d49f247995a06db7a3bd7d07a2e7fe943cfdfc72b826979736a8152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757610d1c612f92565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b816101c0610fa0610f8c610f78610f64610f50610f3c610f28610f16610e80610e6d610fb29d6101e060208f8190610e5d8582517fffffffffffffffffffffffffffffffff00000000000000000000000000000000169052565b01519201526101e08d0190610dc0565b60408d01518c6040818403910152610dc0565b60608c8101517fffffffffffffffff00000000000000000000000000000000000000000000000016908c015260808c8101517fffffffffffffffff00000000000000000000000000000000000000000000000016908c015260a08c8101517fffffffffffffffff00000000000000000000000000000000000000000000000016908c015260c08c01518b820360c08d0152610dc0565b60e08b01518a820360e08c0152610dc0565b6101008a01518982036101008b0152610dc0565b6101208901518882036101208a0152610dc0565b610140880151878203610140890152610dc0565b610160870151868203610160880152610dc0565b610180860151858203610180870152610dc0565b6101a08501518482036101a0860152610dc0565b920151906101c0818403910152610dc0565b90565b90151581526040602082015281511515604082015260c06080611054611020610fed602087015160a0606088015260e0870190610dc0565b60408701517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08783030185880152610e03565b60608601517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc08683030160a0870152610dc0565b93015191015290565b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff6110a9610577565b5f60806040516110b8816108a7565b828152606060208201526110ca61244c565b6040820152606080820152015216805f52600160205260ff60405f205416905f5260016020526110fc60405f206124b9565b9061110c60405192839283610fb5565b0390f35b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff61115c610577565b165f526002602052602060405f2054604051908152f35b92939073ffffffffffffffffffffffffffffffffffffffff926111c76111d5927f0f00000000000000000000000000000000000000000000000000000000000000875260e0602088015260e0870190610dc0565b908582036040870152610dc0565b9360608401521660808201525f60a082015260c0818303910152602080835192838152019201905f5b81811061120b5750505090565b82518452602093840193909201916001016111fe565b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b7577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100541580611317575b156112b957611285612ffe565b61128d61310d565b9061110c60405161129f6020826108e5565b5f8082523660208301376040519384933091469186611173565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415611278565b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757600435335f52600260205261138c60405f20549182808214611fac565b335f52600260205260405f206113a28154612010565b90556040519081527faba960b001cf41ae7d1278e08bf0afa5081bfad043326cfe1e1d5ee266c9ac5260203392a2005b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757602073ffffffffffffffffffffffffffffffffffffffff5f5416604051908152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff6114de610577565b165f5260016020526040805f20601060ff825416910154825191151582526020820152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75760206040516150008152f35b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75761110c60405161157c6040826108e5565b600581527f352e302e300000000000000000000000000000000000000000000000000000006020820152604051918291602083526020830190610dc0565b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757602060405160348152f35b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b7576102e761162d610577565b611635612f92565b612526565b346101b7575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757611670614cc3565b611678614d2d565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526116c960c0826108e5565b519020604051908152602090f35b90600182811c9216801561171e575b60208310146116f157565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916116e6565b5f9291815491611737836116d7565b808352926001811690811561178c575060011461175357505050565b5f9081526020812093945091925b838310611772575060209250010190565b600181602092949394548385870101520191019190611761565b905060209495507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091509291921683830152151560051b010190565b906109356117dc9260405193848092611728565b03836108e5565b906119c1600c6117f1610937565b93611826611800825460801b90565b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000168652565b611832600182016117c8565b6020860152611843600282016117c8565b604086015261192d61190460038301546118896118608260c01b90565b7fffffffffffffffff0000000000000000000000000000000000000000000000001660608a0152565b6118dc608082901b7fffffffffffffffff000000000000000000000000000000000000000000000000167fffffffffffffffff0000000000000000000000000000000000000000000000001660808a0152565b60401b7fffffffffffffffff0000000000000000000000000000000000000000000000001690565b7fffffffffffffffff0000000000000000000000000000000000000000000000001660a0870152565b611939600482016117c8565b60c086015261194a600582016117c8565b60e086015261195b600682016117c8565b61010086015261196d600782016117c8565b61012086015261197f600882016117c8565b610140860152611991600982016117c8565b6101608601526119a3600a82016117c8565b6101808601526119b5600b82016117c8565b6101a0860152016117c8565b6101c0830152565b959493906080936119f0611a0c946119fe9315158a5260a060208b015260a08a0190610dc0565b9088820360408a0152610e03565b908682036060880152610dc0565b930152565b346101b75760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b75773ffffffffffffffffffffffffffffffffffffffff611a5d610577565b165f52600160205260405f2060ff81541661110c60405192611a8d84611a868160018501611728565b03856108e5565b611a99600282016117e3565b90601060405191611ab883611ab181600f8501611728565b03846108e5565b015491604051958695866119c9565b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101b757611bb2611afc610577565b611b04612781565b5f611b35611b308373ffffffffffffffffffffffffffffffffffffffff165f52600160205260405f2090565b6124b9565b611b5a83611b556020840193611b4f838651511515612613565b51151590565b61265d565b611b7b6103a7835473ffffffffffffffffffffffffffffffffffffffff1690565b90519060405180809681947f38d8480a0000000000000000000000000000000000000000000000000000000083526004830161271b565b039134905af180156105725781611be79173ffffffffffffffffffffffffffffffffffffffff945f91611c67575b5015612737565b611c3a611c128273ffffffffffffffffffffffffffffffffffffffff165f52600160205260405f2090565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008154169055565b167f5bb0bbb0993a623e10dd3579bf5b9403deba943e0bfe950b740d60209c9135ef5f80a26102e76127f5565b611c7b91503d805f833e61056481836108e5565b505f611be0565b611c9590611ca493929694963691610981565b60208151910120943691610981565b60208151910120916040519260208401947f95b0f36aa3383d49f247995a06db7a3bd7d07a2e7fe943cfdfc72b826979736a865260408501526060840152608083015260a082015260a08152611cfb60c0826108e5565b51902090565b9396611d3e90611d388598611d30611d2b8a8d899a611d479a8a9e9a8e611d26612781565b611c82565b6123af565b923691610981565b906131de565b9093919361324f565b611d7773ffffffffffffffffffffffffffffffffffffffff831697885f52600260205260405f2054808214611fac565b804211611f815750855f52600260205260405f20611d958154612010565b9055611da561036c368686610981565b611db361036c368785610981565b611dd46103a75f5473ffffffffffffffffffffffffffffffffffffffff1690565b5f60405180927f38d8480a0000000000000000000000000000000000000000000000000000000082528180611e0d8a8a6004840161288b565b039134905af1908115610572575f905f92611f62575b5081611e2e9161289c565b611e3790613349565b916101c08301805151603481101590611e4f916128e5565b51611e5990613526565b90928373ffffffffffffffffffffffffffffffffffffffff8116809a1491611e8092612918565b611e8b368884610981565b8051906020012081811491611e9f92612969565b611eaa368686610981565b8051906020012090611ebc82846136e1565b96611ec5610926565b6001815294611ed5368989610981565b602087015260408601523690611eea92610981565b60608401526080830152611f1c9073ffffffffffffffffffffffffffffffffffffffff165f52600160205260405f2090565b90611f2691612dce565b604051928392611f369284612f62565b037f206fdb1a74851a8542447b8b6704db24a36b906a7297cc23c2b984dc357b997891a26109356127f5565b611e2e9250611f7b91503d805f833e61056481836108e5565b91611e23565b7fbd2a913c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b15611fb5575050565b7f06427aeb000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461203d5760010190565b611fe3565b1561204957565b7f72cd95d7000000000000000000000000000000000000000000000000000000005f5260045ffd5b6120889092919261208061373f565b61163561373f565b60409182519261209881856108e5565b601684527f466c617368746573746174696f6e52656769737472790000000000000000000060208501526120ce815191826108e5565b600181527f3100000000000000000000000000000000000000000000000000000000000000602082015261210061373f565b61210861373f565b835167ffffffffffffffff81116108c35761214c816121477fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546116d7565b6129b6565b6020601f821160011461229457916121b7826121de9373ffffffffffffffffffffffffffffffffffffffff969561093598995f92612289575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10255613796565b6122065f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10055565b61222e5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10155565b61223661373f565b61223e61373f565b1661224a811515612042565b73ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff00000000000000000000000000000000000000005f5416175f55565b015190505f80612185565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d965f5b818110612397575092610935969773ffffffffffffffffffffffffffffffffffffffff969593600193836121de9710612360575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10255613796565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080612333565b838301518955600190980197602093840193016122ff565b6042906123ba614cc3565b6123c2614d2d565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261241360c0826108e5565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b60405190612459826108c8565b60606101c0835f81528260208201528260408201525f838201525f60808201525f60a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a08201520152565b906040516124c6816108a7565b60806010829460ff815416151584526040516124f0816124e98160018601611728565b03826108e5565b6020850152612501600282016117e3565b604085015260405161251a816124e981600f8601611728565b60608501520154910152565b73ffffffffffffffffffffffffffffffffffffffff1680156125e75773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b1561261b5750565b73ffffffffffffffffffffffffffffffffffffffff907fbb527454000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b156126655750565b73ffffffffffffffffffffffffffffffffffffffff907f138c0ee8000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b91906040838203126101b757825180151581036101b7579260208101519067ffffffffffffffff82116101b7570181601f820112156101b7578051906126ec82610947565b926126fa60405194856108e5565b828452602083830101116101b757815f9260208093018386015e8301015290565b906020610fb2928181520190610dc0565b6040513d5f823e3d90fd5b1561273f5750565b73ffffffffffffffffffffffffffffffffffffffff907f927b3443000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005c6127cd5760017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b7f3ee5aeb5000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005d565b156128225750565b7f9e24c2f6000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b916020610fb293818152019161284d565b156128a45750565b6128e1906040519182917f64d10cb10000000000000000000000000000000000000000000000000000000083526020600484018181520190610dc0565b0390fd5b156128ed5750565b7f4fe16298000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b15612921575050565b9073ffffffffffffffffffffffffffffffffffffffff80927f38e0a7e5000000000000000000000000000000000000000000000000000000005f52166004521660245260445ffd5b15612972575050565b7fcc14da59000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b8181106129ab575050565b5f81556001016129a0565b90601f82116129c3575050565b610935917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f5260205f20906020601f840160051c83019310612a0e575b601f0160051c01906129a0565b9091508190612a01565b9190601f8111612a2757505050565b610935925f5260205f20906020601f840160051c83019310612a0e57601f0160051c01906129a0565b919091825167ffffffffffffffff81116108c357612a7881612a7284546116d7565b84612a18565b6020601f8211600114612ac9578190612ac59394955f926122895750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b9055565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821690612afa845f5260205f2090565b915f5b818110612b5357509583600195969710612b1c575b505050811b019055565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080612b12565b9192602060018192868b015181550194019201612afd565b600c6101c061093593612bcf612ba182517fffffffffffffffffffffffffffffffff000000000000000000000000000000001690565b859060801c7fffffffffffffffffffffffffffffffff00000000000000000000000000000000825416179055565b612be0602082015160018601612a50565b612bf1604082015160028601612a50565b612d3760038501612c56612c2860608501517fffffffffffffffff0000000000000000000000000000000000000000000000001690565b829060c01c7fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000825416179055565b612cc7612c8660808501517fffffffffffffffff0000000000000000000000000000000000000000000000001690565b82547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff1660809190911c6fffffffffffffffff000000000000000016178255565b60a08301517fffffffffffffffff0000000000000000000000000000000000000000000000001681547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff1660409190911c77ffffffffffffffff0000000000000000000000000000000016179055565b612d4860c082015160048601612a50565b612d5960e082015160058601612a50565b612d6b61010082015160068601612a50565b612d7d61012082015160078601612a50565b612d8f61014082015160088601612a50565b612da161016082015160098601612a50565b612db3610180820151600a8601612a50565b612dc56101a0820151600b8601612a50565b01519101612a50565b90612e0781511515839060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b60018201602082015180519067ffffffffffffffff82116108c357612e3682612e3085546116d7565b85612a18565b602090601f8311600114612eb557826010959360809593612e89935f926122895750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b90555b612e9d604082015160028601612b6b565b612eae6060820151600f8601612a50565b0151910155565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0831691612ee7855f5260205f2090565b925f5b818110612f4a5750926001928592601098966080989610612f13575b505050811b019055612e8c565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f8080612f06565b92936020600181928786015181550195019301612eea565b91602091612f7b9195949560408552604085019161284d565b931515910152565b908160209103126101b7575190565b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303612fd257565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b6040517fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10254815f61302e836116d7565b80835292600181169081156130d05750600114613052575b610fb2925003826108e5565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b8183106130b4575050906020610fb292820101613046565b602091935080600191548385880101520191019091839261309c565b60209250610fb29491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b820101613046565b6040517fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10354815f61313d836116d7565b80835292600181169081156130d0575060011461316057610fb2925003826108e5565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b8183106131c2575050906020610fb292820101613046565b60209193508060019154838588010152019101909183926131aa565b815191906041830361320e576132079250602082015190606060408401519301515f1a90613a95565b9192909190565b50505f9160029190565b6004111561322257565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b61325881613218565b80613261575050565b61326a81613218565b6001810361329a577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b6132a381613218565b600281036132d757507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b806132e3600392613218565b146132eb5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b1561331e5750565b7fd915602a000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b61335161244c565b5080516002116101b7576133656002610947565b61337260405191826108e5565b600281526133806002610947565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602082019201368337816020840160025b60208110156134d857806134b957507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b51825182169119161790525190517fffff00000000000000000000000000000000000000000000000000000000000081169160028110613484575b505060f01c6004810361345957506134548161343e610fb293613b24565b805161344f90610255811015613316565b613cc9565b614af4565b7f940a5ec6000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7fffff0000000000000000000000000000000000000000000000000000000000009250829060020360031b1b16165f80613420565b6134cd6134c86134d292614d72565b614dad565b614d80565b906133e5565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156133b357611fe3565b80516014116101b7576135396014610947565b9161354760405193846108e5565b601483526135556014610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208501910136823760149381602085015b60208710156136945761360994959680155f1461367f57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b51825182169119161790525190517fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008116916014811061364a575b505060601c92613dd1565b60208151910151906020811061361d575090565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060200360031b1b1690565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009250829060140360031b1b16165f806135fe565b6134cd6134c861368e92614d72565b906135c3565b90815181526020810180911161203d57906020810180911161203d57957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811161203d5795613588565b73ffffffffffffffffffffffffffffffffffffffff16805f526001602052601060405f2001548092146137145750151590565b7ffb5bab5b000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561376e57565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b90815167ffffffffffffffff81116108c3576137fc816137d67fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546116d7565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103612a18565b602092601f821160011461386d57613849929382915f926122895750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10355565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216937f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75915f5b86811061394c5750836001959610613915575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10355565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f80806138eb565b919260206001819286850151815501940192016138d8565b90813b15613a535773ffffffffffffffffffffffffffffffffffffffff8216807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2805115613a2257613a1f91614c7f565b50565b505034613a2b57565b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff827f4c9c8ce3000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411613b19579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610572575f5173ffffffffffffffffffffffffffffffffffffffff811615613b0f57905f905f90565b505f906001905f90565b5050505f9160039190565b9081516006116101b757613b386004610947565b613b4560405191826108e5565b60048152613b536004610947565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208201920136833760226004940182905b6020861015613c7c577fffffffff00000000000000000000000000000000000000000000000000000000949580155f14613c6757507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b51825182169119161790525190518281169160048110613c52575b5050167f81000000000000000000000000000000000000000000000000000000000000008103613c275750565b7fea75591a000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b839250829060040360031b1b16165f80613bfa565b6134cd6134c8613c7692614d72565b90613bdf565b90815181526020810180911161203d57906020810180911161203d57947fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811161203d5794613b87565b8051610255116101b757613cde610248610947565b90613cec60405192836108e5565b6102488252613cfc610248610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060208401910136823790602d016102485b6020811015613d835780613d6e57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff905b518251821691191617905290565b6134cd6134c8613d7d92614d72565b90613d60565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811115613d2e57611fe3565b80516034116101b757613de46020610947565b90613df260405192836108e5565b60208252613e006020610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060340160205b6020811015613e475780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811115613e3157611fe3565b80516010116101b757613ea86010610947565b90613eb660405192836108e5565b60108252613ec46010610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060200160105b6020811015613f0b5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811115613ef557611fe3565b80516040116101b757613f6c6030610947565b90613f7a60405192836108e5565b60308252613f886030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237603080920190915b6020811015613fd05780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810190811115613fba57611fe3565b80516070116101b7576140316030610947565b9061403f60405192836108e5565b6030825261404d6030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060600160305b60208110156140945780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561407e57611fe3565b80516078116101b7576140f56008610947565b9061410360405192836108e5565b600882526141116008610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060900160085b60208110156141585780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561414257611fe3565b80516080116101b7576141b96008610947565b906141c760405192836108e5565b600882526141d56008610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060980160085b602081101561421c5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561420657611fe3565b80516088116101b75761427d6008610947565b9061428b60405192836108e5565b600882526142996008610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060a00160085b60208110156142e05780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156142ca57611fe3565b805160b8116101b7576143416030610947565b9061434f60405192836108e5565b6030825261435d6030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060a80160305b60208110156143a45780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561438e57611fe3565b805160e8116101b7576144056030610947565b9061441360405192836108e5565b603082526144216030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06020840191013682379060d80160305b60208110156144685780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561445257611fe3565b8051610118116101b7576144ca6030610947565b906144d860405192836108e5565b603082526144e66030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101080160305b602081101561452e5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561451857611fe3565b8051610148116101b7576145906030610947565b9061459e60405192836108e5565b603082526145ac6030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101380160305b60208110156145f45780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156145de57611fe3565b8051610178116101b7576146566030610947565b9061466460405192836108e5565b603082526146726030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101680160305b60208110156146ba5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156146a457611fe3565b80516101a8116101b75761471c6030610947565b9061472a60405192836108e5565b603082526147386030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101980160305b60208110156147805780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561476a57611fe3565b80516101d8116101b7576147e26030610947565b906147f060405192836108e5565b603082526147fe6030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101c80160305b60208110156148465780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081019081111561483057611fe3565b8051610208116101b7576148a86030610947565b906148b660405192836108e5565b603082526148c46030610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906101f80160305b602081101561490c5780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156148f657611fe3565b8051610248116101b75761496e6040610947565b9061497c60405192836108e5565b6040825261498a6040610947565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0602084019101368237906102280160405b60208110156149d25780613d6e57509192915050565b9091825181526020810180911161203d57916020810180911161203d57907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101908111156149bc57611fe3565b90602082519201517fffffffffffffffffffffffffffffffff0000000000000000000000000000000081169260108110614a58575050565b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000929350829060100360031b1b161690565b90602082519201517fffffffffffffffff00000000000000000000000000000000000000000000000081169260088110614ac2575050565b7fffffffffffffffff000000000000000000000000000000000000000000000000929350829060080360031b1b161690565b906119c1614b0061244c565b92614b3b614b15614b1083613e95565b614a20565b7fffffffffffffffffffffffffffffffff00000000000000000000000000000000168552565b614b4481613f59565b6020850152614b528161401e565b6040850152614b94614b6b614b66836140e2565b614a8a565b7fffffffffffffffff000000000000000000000000000000000000000000000000166060860152565b614bcc614ba3614b66836141a6565b7fffffffffffffffff000000000000000000000000000000000000000000000000166080860152565b614c04614bdb614b668361426a565b7fffffffffffffffff0000000000000000000000000000000000000000000000001660a0860152565b614c0d8161432e565b60c0850152614c1b816143f2565b60e0850152614c29816144b6565b610100850152614c388161457c565b610120850152614c4781614642565b610140850152614c5681614708565b610160850152614c65816147ce565b610180850152614c7481614894565b6101a085015261495a565b5f80610fb293602081519101845af43d15614cbb573d91614c9f83610947565b92614cad60405194856108e5565b83523d5f602085013e614dbc565b606091614dbc565b614ccb612ffe565b8051908115614cdb576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100548015614d085790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b614d3561310d565b8051908115614d45576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101548015614d085790565b602003906020821161203d57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161203d57565b601f811161203d576101000a90565b90614df95750805115614dd157805190602001fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580614e4c575b614e0a575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b15614e0256fea164736f6c634300081c000a", + "sourceMap": "1080:11972:97:-:0;;;;;;;1171:4:51;1163:13;;1080:11972:97;;;;;;1163:13:51;1080:11972:97;;;;;;;;;;;;;;", + "linkReferences": {} + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json new file mode 100644 index 00000000..1ccf04a5 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json @@ -0,0 +1,86 @@ +{ + "abi": [ + { + "type": "function", + "name": "baseFee", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "quoteResults", + "inputs": [ + { "name": "", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { "name": "success", "type": "bool", "internalType": "bool" }, + { "name": "output", "type": "bytes", "internalType": "bytes" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setBaseFee", + "inputs": [ + { + "name": "_baseFee", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setQuoteResult", + "inputs": [ + { + "name": "rawQuote", + "type": "bytes", + "internalType": "bytes" + }, + { "name": "_success", "type": "bool", "internalType": "bool" }, + { "name": "_output", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "verifyAndAttestOnChain", + "inputs": [ + { "name": "rawQuote", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { "name": "", "type": "bool", "internalType": "bool" }, + { "name": "", "type": "bytes", "internalType": "bytes" } + ], + "stateMutability": "payable" + }, + { + "type": "error", + "name": "InsufficientFee", + "inputs": [ + { + "name": "required", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "provided", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "bytecode": { + "object": "0x608080604052346015576106fa908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806338d8480a146103b957806346860698146103825780636ef25c3a1461034757806389e7a16d146102b957639321d73e14610050575f80fd5b346102b55760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b55760043567ffffffffffffffff81116102b55761009f90369060040161048d565b6024358015158091036102b5576044359167ffffffffffffffff83116102b55760206100d1600194369060040161056a565b94604051936100df8561050d565b845281840195865282604051938492833781015f8152030190209051151560ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00835416911617815501905190815167ffffffffffffffff81116102885761014782546105de565b601f8111610243575b50602092601f82116001146101aa57928192935f9261019f575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790555f80f35b015190505f8061016a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821693835f52805f20915f5b86811061022b57508360019596106101f4575b505050811b019055005b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690555f80806101ea565b919260206001819286850151815501940192016101d7565b825f5260205f20601f830160051c8101916020841061027e575b601f0160051c01905b8181106102735750610150565b5f8155600101610266565b909150819061025d565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f80fd5b346102b55760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b55760043567ffffffffffffffff81116102b557602061030b8192369060040161056a565b604051928184925191829101835e81015f815203019020610333600160ff835416920161062f565b90610343604051928392836104bb565b0390f35b346102b5575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b5576020600154604051908152f35b346102b55760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b557600435600155005b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102b55760043567ffffffffffffffff81116102b55761040390369060040161048d565b9060015480341061045e575060209082604051938492833781015f8152030190206104456001604051926104368461050d565b60ff815416151584520161062f565b90816020820152511515610343604051928392836104bb565b7fa458261b000000000000000000000000000000000000000000000000000000005f526004523460245260445ffd5b9181601f840112156102b55782359167ffffffffffffffff83116102b557602083818601950101116102b557565b90601f60206060947fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093151585526040828601528051918291826040880152018686015e5f8582860101520116010190565b6040810190811067ffffffffffffffff82111761028857604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761028857604052565b81601f820112156102b55780359067ffffffffffffffff821161028857604051926105bd60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8601160185610529565b828452602083830101116102b557815f926020809301838601378301015290565b90600182811c92168015610625575b60208310146105f857565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916105ed565b9060405191825f825492610642846105de565b80845293600181169081156106ad5750600114610669575b5061066792500383610529565b565b90505f9291925260205f20905f915b818310610691575050906020610667928201015f61065a565b6020919350806001915483858901015201910190918492610678565b602093506106679592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201015f61065a56fea164736f6c634300081c000a", + "sourceMap": "865:844:109:-:0;;;;;;;;;;;;;;;;;", + "linkReferences": {} + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/quote-output.bin b/crates/builder/op-rbuilder/src/tests/framework/artifacts/quote-output.bin new file mode 100644 index 0000000000000000000000000000000000000000..84b2c09b7eddca45fc903018179882d6a9ba4ac8 GIT binary patch literal 597 zcmZQzX=DI`4F~dpBr78`hyw>Q#}-y?pZRjx^8z&&+uoXWnV8 z$bDYTQSde5Xu{6MBg8g*=zYO+6G)rTr2ng z`1doxTsv}?zw-Qlz%Kmxt)}@syXGX#u`CD(Rwms#8tZs?>#A#9{EEtwx7+3kYvx|u zHGf5N*mdrVeWG_`&C1H0C-^?>w2}68|I=$VAw78Fa?!e)_{n;+SFf4Gc_pasLs^0O{)Nv>6P|JyJksAYo9*nmu74HY9|fPN z3Wc2)a0vKlQgdbPe1lsKEv0O!r6RElHq2P4LaJ{IW>yv)l@PjP#mH03xRsAr{7&Tw HQp^AVOmu>t literal 0 HcmV?d00001 diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-quote.bin b/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-quote.bin new file mode 100644 index 0000000000000000000000000000000000000000..057f9ed2d79147c67882abdb43fc9a927869e19d GIT binary patch literal 5006 zcmcgv3$PQ_8NNIOT;ZY!f}#|sIx4B$&FkjI){f`wE1P7K>@(T;YO=Z6WV3nQ++>p# zb$ql_tq2zFNTpcnDA!t)*0Js2qaCdkI#`FQ)j|4Di>Nc$;$vj2m7XN`LcmJ}nQ3M+ zn|%Ly{QvpC|9t;W@0;3pLtpo^=7%HhkJp7Cyc3)A=v~*(nECt}*!1tM?Xy?Cx%m}( z!{t}BN1Zff+W6@1+E^&Et&h!rm>4>uU$WD`+LjAihHh^ zRrx6KY-P`Z-B%WwPo^I>`S|{v+8(}V>Wt1TX2bTGp*y(!YGG6Vj!R=Nr#3A)@Z87S zMeNtbjqkDte*4Y%o}D+|d)t2>}}O#JHcZS{W`{rb+AsjIFYYmM&x<@(bvD(={F z@0g~%a!T&01%J76u+bn^bFbg>jl~@E>B>u1>*5_77hJdE>NU&ndFW@~Klvr$x@~)J zy5@Fwa?OH;`t=*>*ZB988Oz=0y^($MgY{1vd^uWu%loa}v+y?tp15bm^MAbcvu0@D zY5zEXp7O$}m&NvZR=l+7UVP_eH`iwj*XLjPfUo?SF=*CkVCUpwWo z{y7VGwYDD58rxAeuL!<=%d1cBnEsO&|8wS>ul#GP{r%@>m(PEA?Y5^DK6Clpi};ZX9`Nq3ELgOX-tf=h*vdtZ&QiB7 zzWx2n>y_Uu-t@==x7XGQXSpxA|61CYyx_LTTkmdvZOzv6Q(w!fFMfCRz~&d8$>a_icRaRs89@zJ9kDHr4y?i|+EgET)*ZpY8 zhM^_k;T6AIb=Bib`}(F#oi_cX88c^{jGZ!jPXF9fPn&o8`~_z$Ty*AHi_bo1$yZM5 zyQ;sh@8b3cQVbheN>GBt(kuZas)J%2%Thj@AYd@11B-<~XXS)LtBtgZ1I<7^oezv! zyj_aAm9XOBaEs7YHdIcswt-V(1OXdK~;Ty(i0QNv^!{_ zZ!W`_jkH0@9Avv*Syotf(e(^N>Ab6%(~4A+utBgLB8H9~c^`p2aWRw?RAd__Imj`v zRb@oZ;|Ds$5XAUziwXdo1u&YAU}mY3k2?nGKuIVYumz2VJVF;Kfr#Z&6z>WTC1m$N z)*F!u?xsTur3`0S^|5lvRZEMk3k+dGGGbMuS)(!l^DV-vO68)V>l}ypK@ud$w%rVf z9L10zu0v5tJKd3n(8vwSFxbjSIK~Zmpkg(K6%vkg1`9(}l7xAVA@oEQYB{q~&IANY zv&)UJi?&jt7uQOPone%M=2BW%2Aym&!PQe;0@YQQe>Cill!ggE!h?7!o~)=Y8a@5> zuv^8$ZfK-jkye`Y8>-~AutO|Mn(1Pskz~wz(smY-3Dyd<$s*uU2vv#>c&ms2QYAW~ z<(0aN$^~RM)LR(uO%HOw(@s~0v?w@W@okPX76}kzQTzf2L3W4&1_0<>h$ycH+qIBu zs!-%4IMK$jNem=15R>54WH^R)rjdb~WCDY5I0+s=I69!N zr@bMt$UsjRdO`^HPUyiyZG5s2!bh3FO@_Bah)iZcc=RZEtQYSMf$uf+xGEq#%)y}} zvm-(XNDPsNfPey!2M0f$b3i#hv3 zdJTu%)W{AF$_H;G2#^rq8sI3Nz*w^6)8b7(lXWBvX)sV{@NgvSFFFeuRdED`0z>$N zz5*^f#Wcu6nzHkfLzGP*hl*0!8o~zRHYF5OWQ`b%%fmiblFyK#rpIG|OE1Mb;zdQs z&kI_|a&)mH%yxYmt_iVIKu~EUG?$-o1S&jL3OZxFZR>P`@Ox$Mh~O}e(VL6`)b?$F zqU9*rL>IbJW9djBPCzm3^5aM;V6Qs#iUam#)S*`#uyNHJR|hamTX-`{0w)r=CP&H^ zC&3iLY|cI$;{u?`8bHS;TxSk8E1HM9~$gSvMy zC-4@fca$?gf>B9D_qU3K9$m*RwMUi-fFv6&icP2<1gZo~MdIj!MOkWKysbcVJJ@0% zifJUT11ciI0pF<0X(q%d<|68LrD^#*!$IhCQ7w|uO0_$sw%L$A`qV$&>2X3)fjrDEO{S8aDS3hOoj>z@-iKA+_6c)Wt+`D~ON4Ju;8 z3DQwfV@60Qb`CLs1WvSJkfVlmAqB}?f$kBw!@C8obu40scgfW5oaCavkwHWX7X?!@ zK&B%&&L>1k>{F(6H(?w08Rm#dyi4C88$G@SA-kqEO-dk9|^Uxv54aYYS%!GDlo)} z+LhU&%sI_!Cc>6%-6tyYh@WTdLJ3x_Bjd8L6nUueM2)I2(RX$m)nFs-=8sDHHs`Su zA$^{#oS7%Wj1N3v=%Y7FFK@fu;Ls8HUAIivZg7#8SUoH5cRs06c0si1o5 zZq_J}sv5?*X4F%x)>^7z!jvf~YLkw+YC#zC+GZ#Uu`g4v-R7-ZuGp8U*WTU~LvJ_T z=JXir^+=Xty)-@6dhM1g)uA=k^8!YSsD=fd&4Flqz{?i|hv+BrBB@2nWh-2d3E^6t z$quM_A0sCG9;jQArDGsZ0Z$M&M*?N8=17#i<$+paFzV;y&42(JNtuae1m34LszM@F PW&fAn_Po!Y$j|=*YH_CY literal 0 HcmV?d00001 diff --git a/crates/builder/op-rbuilder/src/tests/framework/contracts.rs b/crates/builder/op-rbuilder/src/tests/framework/contracts.rs new file mode 100644 index 00000000..5f9b2449 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/framework/contracts.rs @@ -0,0 +1,43 @@ +pub mod flashtestation_registry { + use crate::tests::framework::contracts::block_builder_policy::BlockBuilderPolicy::TD10ReportBody; + use alloy_sol_types::sol; + + sol!( + // https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216 + #[sol(rpc, abi)] + FlashtestationRegistry, + "src/tests/framework/artifacts/contracts/FlashtestationRegistry.json", + ); +} + +pub mod block_builder_policy { + use crate::tests::framework::contracts::block_builder_policy::BlockBuilderPolicy::TD10ReportBody; + use alloy_sol_types::sol; + + sol!( + // https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216 + #[sol(rpc, abi)] + BlockBuilderPolicy, + "src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json", + ); +} + +pub mod flashblocks_number_contract { + use alloy_sol_types::sol; + sol!( + // https://github.com/Uniswap/flashblocks_number_contract/tree/c21ca0aedc3ff4d1eecf20cd55abeb984080bc78 + #[sol(rpc, abi)] + FlashblocksNumber, + "src/tests/framework/artifacts/contracts/FlashblocksNumberContract.json", + ); +} + +pub mod mock_dcap_attestation { + use alloy_sol_types::sol; + sol!( + // https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216 + #[sol(rpc, abi)] + MockAutomataDcapAttestationFee, + "src/tests/framework/artifacts/contracts/MockAutomataDcapAttestationFee.json", + ); +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/driver.rs b/crates/builder/op-rbuilder/src/tests/framework/driver.rs index 5f273c1c..6e9f83f7 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/driver.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/driver.rs @@ -14,12 +14,10 @@ use rollup_boost::OpExecutionPayloadEnvelope; use super::{EngineApi, Ipc, LocalInstance, TransactionBuilder}; use crate::{ args::OpRbuilderArgs, - tests::{ExternalNode, Protocol}, + tests::{ExternalNode, Protocol, framework::DEFAULT_GAS_LIMIT}, tx_signer::Signer, }; -const DEFAULT_GAS_LIMIT: u64 = 10_000_000; - /// The ChainDriver is a type that allows driving the op builder node to build new blocks manually /// by calling the `build_new_block` method. It uses the Engine API to interact with the node /// and the provider to fetch blocks and transactions. diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 7a9ba14b..7721ae2b 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -4,8 +4,8 @@ use crate::{ primitives::reth::engine_api_builder::OpEngineApiBuilder, revert_protection::{EthApiExtServer, RevertProtectionExt}, tests::{ - EngineApi, Ipc, TransactionPoolObserver, create_test_db, - framework::{BUILDER_PRIVATE_KEY, driver::ChainDriver}, + EngineApi, Ipc, TransactionPoolObserver, builder_signer, create_test_db, + framework::driver::ChainDriver, }, tx::FBPooledTransaction, tx_signer::Signer, @@ -88,14 +88,7 @@ impl LocalInstance { let (txpool_ready_tx, txpool_ready_rx) = oneshot::channel::>(); - let signer = args.builder_signer.unwrap_or_else(|| { - Signer::try_from_secret( - BUILDER_PRIVATE_KEY - .parse() - .expect("Invalid builder private key"), - ) - .expect("Failed to create signer from private key") - }); + let signer = args.builder_signer.unwrap_or(builder_signer()); args.builder_signer = Some(signer); args.rollup_args.enable_tx_conditional = true; diff --git a/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs index d9c7bf1f..4e3171fb 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/mod.rs @@ -1,4 +1,5 @@ mod apis; +mod contracts; mod driver; mod external; mod instance; @@ -6,17 +7,20 @@ mod txs; mod utils; pub use apis::*; +pub use contracts::*; pub use driver::*; pub use external::*; pub use instance::*; pub use txs::*; pub use utils::*; -const BUILDER_PRIVATE_KEY: &str = +pub const BUILDER_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; -const FUNDED_PRIVATE_KEYS: &[&str] = - &["0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"]; +pub const FUNDED_PRIVATE_KEY: &str = + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + +pub const DEFAULT_GAS_LIMIT: u64 = 10_000_000; pub const DEFAULT_JWT_TOKEN: &str = "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index 7c91892a..e8133b05 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -1,5 +1,6 @@ use crate::{ primitives::bundle::{Bundle, BundleResult}, + tests::funded_signer, tx::FBPooledTransaction, tx_signer::Signer, }; @@ -21,8 +22,6 @@ use tracing::debug; use alloy_eips::eip1559::MIN_PROTOCOL_BASE_FEE; -use super::FUNDED_PRIVATE_KEYS; - #[derive(Clone, Copy, Default)] pub struct BundleOpts { block_number_min: Option, @@ -74,7 +73,6 @@ pub struct TransactionBuilder { tx: TxEip1559, bundle_opts: Option, with_reverted_hash: bool, - key: Option, } impl TransactionBuilder { @@ -91,7 +89,6 @@ impl TransactionBuilder { }, bundle_opts: None, with_reverted_hash: false, - key: None, } } @@ -105,11 +102,6 @@ impl TransactionBuilder { self } - pub fn with_key(mut self, key: u64) -> Self { - self.key = Some(key); - self - } - pub fn with_value(mut self, value: u128) -> Self { self.tx.value = U256::from(value); self @@ -166,14 +158,7 @@ impl TransactionBuilder { } pub async fn build(mut self) -> Recovered { - let signer = self.signer.unwrap_or_else(|| { - Signer::try_from_secret( - FUNDED_PRIVATE_KEYS[self.key.unwrap_or(0) as usize] - .parse() - .expect("invalid hardcoded builder private key"), - ) - .expect("Failed to create signer from hardcoded private key") - }); + let signer = self.signer.unwrap_or(funded_signer()); let nonce = match self.nonce { Some(nonce) => nonce, diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs index a71361d2..764b3fa3 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -1,10 +1,16 @@ use crate::{ - tests::{ONE_ETH, Protocol, framework::driver::ChainDriver}, + tests::{ + BUILDER_PRIVATE_KEY, Protocol, block_builder_policy::BlockBuilderPolicy, + flashblocks_number_contract::FlashblocksNumber, + flashtestation_registry::FlashtestationRegistry, framework::driver::ChainDriver, + mock_dcap_attestation::MockAutomataDcapAttestationFee, + }, tx_signer::Signer, }; use alloy_eips::Encodable2718; use alloy_primitives::{Address, B256, BlockHash, TxHash, TxKind, U256, hex}; use alloy_rpc_types_eth::{Block, BlockTransactionHashes}; +use alloy_sol_types::SolCall; use core::future::Future; use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; use op_alloy_rpc_types::Transaction; @@ -17,12 +23,22 @@ use reth_node_core::{args::DatadirArgs, dirs::DataDirPath, node_config::NodeConf use reth_optimism_chainspec::OpChainSpec; use std::{net::TcpListener, sync::Arc}; -use super::{FUNDED_PRIVATE_KEYS, TransactionBuilder}; +use super::{FUNDED_PRIVATE_KEY, TransactionBuilder}; pub trait TransactionBuilderExt { fn random_valid_transfer(self) -> Self; fn random_reverting_transaction(self) -> Self; fn random_big_transaction(self) -> Self; + // flashblocks number methods + fn deploy_flashblock_number_contract(self) -> Self; + fn init_flashblock_number_contract(self, register_builder: bool) -> Self; + // flashtestations methods + fn deploy_flashtestation_registry_contract(self) -> Self; + fn init_flashtestation_registry_contract(self, dcap_address: Address) -> Self; + fn deploy_builder_policy_contract(self) -> Self; + fn init_builder_policy_contract(self, registry_address: Address) -> Self; + fn deploy_mock_dcap_contract(self) -> Self; + fn add_mock_quote(self) -> Self; } impl TransactionBuilderExt for TransactionBuilder { @@ -41,10 +57,87 @@ impl TransactionBuilderExt for TransactionBuilder { self.with_create() .with_input(hex!("6c63ffffffff60005260046000f36000526002600d60136000f5").into()) } + + fn deploy_flashblock_number_contract(self) -> Self { + self.with_create() + .with_input(FlashblocksNumber::BYTECODE.clone()) + .with_gas_limit(2_000_000) // deployment costs ~1.6 million gas + } + + fn init_flashblock_number_contract(self, register_builder: bool) -> Self { + let builder_signer = builder_signer(); + let owner = funded_signer(); + + let init_data = FlashblocksNumber::initializeCall { + _owner: owner.address, + _initialBuilders: if register_builder { + vec![builder_signer.address] + } else { + vec![] + }, + } + .abi_encode(); + + self.with_input(init_data.into()) + } + + fn deploy_flashtestation_registry_contract(self) -> Self { + self.with_create() + .with_input(FlashtestationRegistry::BYTECODE.clone()) + .with_gas_limit(1_000_000) + } + + fn init_flashtestation_registry_contract(self, dcap_address: Address) -> Self { + let owner = funded_signer(); + + let init_data = FlashtestationRegistry::initializeCall { + owner: owner.address, + _attestationContract: dcap_address, + } + .abi_encode(); + + self.with_input(init_data.into()) + } + + fn deploy_builder_policy_contract(self) -> Self { + self.with_create() + .with_input(BlockBuilderPolicy::BYTECODE.clone()) + .with_gas_limit(1_000_000) + } + + fn init_builder_policy_contract(self, registry_address: Address) -> Self { + let owner = funded_signer(); + + let init_data = BlockBuilderPolicy::initializeCall { + _initialOwner: owner.address, + _registry: registry_address, + } + .abi_encode(); + + self.with_input(init_data.into()) + } + + fn deploy_mock_dcap_contract(self) -> Self { + self.with_create() + .with_input(MockAutomataDcapAttestationFee::BYTECODE.clone()) + .with_gas_limit(1_000_000) + } + + fn add_mock_quote(self) -> Self { + let quote = MockAutomataDcapAttestationFee::setQuoteResultCall { + // quote from http://ns31695324.ip-141-94-163.eu:10080/attest for builder key + rawQuote: include_bytes!("./artifacts/test-quote.bin").into(), + _success: true, + // response from verifyAndAttestOnChain from the real automata dcap contract on + // unichain sepolia 0x95175096a9B74165BE0ac84260cc14Fc1c0EF5FF + _output: include_bytes!("./artifacts/quote-output.bin").into(), + } + .abi_encode(); + self.with_input(quote.into()).with_gas_limit(500_000) + } } pub trait ChainDriverExt { - fn fund_default_accounts(&self) -> impl Future>; fn fund_many( &self, addresses: Vec
, @@ -52,11 +145,6 @@ pub trait ChainDriverExt { ) -> impl Future>; fn fund(&self, address: Address, amount: u128) -> impl Future>; - fn first_funded_address(&self) -> Address { - FUNDED_PRIVATE_KEYS[0] - .parse() - .expect("Invalid funded private key") - } fn fund_accounts( &self, @@ -81,14 +169,6 @@ pub trait ChainDriverExt { } impl ChainDriverExt for ChainDriver

{ - async fn fund_default_accounts(&self) -> eyre::Result<()> { - for key in FUNDED_PRIVATE_KEYS { - let signer: Signer = key.parse()?; - self.fund(signer.address, ONE_ETH).await?; - } - Ok(()) - } - async fn fund_many(&self, addresses: Vec

, amount: u128) -> eyre::Result { let mut txs = Vec::with_capacity(addresses.len()); @@ -239,3 +319,21 @@ pub fn get_available_port() -> u16 { .expect("Failed to get local address") .port() } + +pub fn builder_signer() -> Signer { + Signer::try_from_secret( + BUILDER_PRIVATE_KEY + .parse() + .expect("invalid hardcoded builder private key"), + ) + .expect("Failed to create signer from hardcoded builder private key") +} + +pub fn funded_signer() -> Signer { + Signer::try_from_secret( + FUNDED_PRIVATE_KEY + .parse() + .expect("invalid hardcoded funded private key"), + ) + .expect("Failed to create signer from hardcoded funded private key") +} From 1da98957064bad44effd5409d4633869308c6a61 Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 3 Oct 2025 10:47:34 -0700 Subject: [PATCH 193/262] Update flashtestation service with latest contracts (#281) --- crates/builder/op-rbuilder/Cargo.toml | 8 +- .../op-rbuilder/src/builders/builder_tx.rs | 23 +- .../op-rbuilder/src/builders/context.rs | 2 +- .../builder/op-rbuilder/src/builders/mod.rs | 5 +- .../src/builders/standard/payload.rs | 14 +- .../op-rbuilder/src/flashtestations/args.rs | 16 +- .../src/flashtestations/attestation.rs | 45 ++-- .../op-rbuilder/src/flashtestations/mod.rs | 95 +++++++ .../src/flashtestations/service.rs | 248 ++++++++---------- .../src/flashtestations/tx_manager.rs | 169 ++++-------- 10 files changed, 330 insertions(+), 295 deletions(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index f1fe8d1d..c674691c 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -125,7 +125,7 @@ moka = "0.12" http = "1.0" sha3 = "0.10" hex = "0.4" -ureq = "2.10" +reqwest = "0.12.23" k256 = "0.13.4" rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "b86af43969557bee18f17ec1d6bcd3e984f910b2" } @@ -136,6 +136,9 @@ tar = { version = "0.4", optional = true } ctor = { version = "0.4.2", optional = true } rlimit = { version = "0.10", optional = true } macros = { path = "src/tests/framework/macros", optional = true } +hyper = { version = "1.7.0", features = ["http1"], optional = true } +hyper-util = { version = "0.1.11", optional = true } +http-body-util = { version = "0.1.3", optional = true } testcontainers = "0.24.0" dirs-next = "2.0.0" @@ -157,6 +160,9 @@ reth-ipc = { workspace = true } reth-node-builder = { workspace = true, features = ["test-utils"] } ctor = "0.4.2" rlimit = { version = "0.10" } +hyper = { version = "1.7.0", features = ["http1"] } +hyper-util = { version = "0.1.11" } +http-body-util = { version = "0.1.3" } [features] default = ["jemalloc"] diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index a8ba3bd8..0cd8d5e8 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -2,7 +2,7 @@ use alloy_consensus::TxEip1559; use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; use alloy_primitives::{ - Address, B256, Log, TxKind, + Address, B256, Log, TxKind, U256, map::foldhash::{HashSet, HashSetExt}, }; use core::fmt::Debug; @@ -13,9 +13,7 @@ use reth_node_api::PayloadBuilderError; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::{ProviderError, StateProvider}; -use reth_revm::{ - State, database::StateProviderDatabase, db::states::bundle_state::BundleRetention, -}; +use reth_revm::{State, database::StateProviderDatabase}; use revm::{ DatabaseCommit, context::result::{EVMError, ResultAndState}, @@ -57,6 +55,9 @@ pub enum BuilderTransactionError { /// Signature signing fails #[error("failed to sign transaction: {0}")] SigningError(secp256k1::Error), + /// Invalid tx errors during evm execution. + #[error("invalid transaction error {0}")] + InvalidTransactionError(Box), /// Unrecoverable error during evm execution. #[error("evm execution error {0}")] EvmExecutionError(Box), @@ -187,7 +188,6 @@ pub trait BuilderTransactions: Debug { .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; evm.db_mut().commit(state); - evm.db_mut().merge_transitions(BundleRetention::Reverts); } Ok(simulation_state) @@ -283,7 +283,7 @@ impl BuilderTxBase { } } -pub(crate) fn get_nonce( +pub fn get_nonce( db: &mut State, address: Address, ) -> Result { @@ -292,6 +292,15 @@ pub(crate) fn get_nonce( .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) } -pub(crate) fn log_exists(logs: &[Log], topic: &B256) -> bool { +pub fn get_balance( + db: &mut State, + address: Address, +) -> Result { + db.load_cache_account(address) + .map(|acc| acc.account_info().unwrap_or_default().balance) + .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) +} + +pub fn log_exists(logs: &[Log], topic: &B256) -> bool { logs.iter().any(|log| log.topics().first() == Some(topic)) } diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index b49c697f..71f10c83 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -480,7 +480,7 @@ impl OpPayloadBuilderCtx { } if exclude_reverting_txs { log_txn(TxnExecutionResult::RevertedAndExcluded); - info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), "skipping reverted transaction"); + info!(target: "payload_builder", tx_hash = ?tx.tx_hash(), result = ?result, "skipping reverted transaction"); best_txs.mark_invalid(tx.signer(), tx.nonce()); continue; } else { diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 8dcde8eb..9dbd949c 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -21,7 +21,10 @@ mod flashblocks; mod generator; mod standard; -pub use builder_tx::{BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions}; +pub use builder_tx::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, get_balance, get_nonce, + log_exists, +}; pub use context::OpPayloadBuilderCtx; pub use flashblocks::FlashblocksBuilder; pub use standard::StandardBuilder; diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 0c0e5de0..66e0adda 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -347,7 +347,15 @@ impl OpBuilder<'_, Txs> { // 4. if mem pool transactions are requested we execute them // gas reserved for builder tx - let builder_txs = builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, true)?; + 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 { @@ -394,7 +402,9 @@ impl OpBuilder<'_, Txs> { } // Add builder tx to the block - builder_tx.add_builder_txs(&state_provider, &mut info, ctx, db, false)?; + 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(); diff --git a/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs index 3e826639..54d5d837 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/args.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/args.rs @@ -1,10 +1,12 @@ use alloy_primitives::{Address, U256, utils::parse_ether}; +use clap::Parser; +use reth_optimism_cli::commands::Commands; -use crate::tx_signer::Signer; +use crate::{args::Cli, tx_signer::Signer}; /// Parameters for Flashtestations configuration /// The names in the struct are prefixed with `flashtestations` -#[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] pub struct FlashtestationsArgs { /// When set to true, the builder will initiate the flashtestations /// workflow within the bootstrapping and block building process. @@ -91,3 +93,13 @@ pub struct FlashtestationsArgs { )] pub builder_proof_version: u8, } + +impl Default for FlashtestationsArgs { + fn default() -> Self { + let args = Cli::parse_from(["dummy", "node"]); + let Commands::Node(node_command) = args.command else { + unreachable!() + }; + node_command.ext.flashtestations + } +} diff --git a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs index 534dfcb1..39c99539 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs @@ -1,6 +1,5 @@ -use std::io::Read; +use reqwest::Client; use tracing::info; -use ureq; const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/attest"; @@ -12,55 +11,55 @@ pub struct AttestationConfig { /// The URL of the quote provider pub quote_provider: Option, } - -/// Trait for attestation providers -pub trait AttestationProvider { - fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result>; -} - /// Remote attestation provider +#[derive(Debug, Clone)] pub struct RemoteAttestationProvider { + client: Client, service_url: String, } impl RemoteAttestationProvider { pub fn new(service_url: String) -> Self { - Self { service_url } + let client = Client::new(); + Self { + client, + service_url, + } } } -impl AttestationProvider for RemoteAttestationProvider { - fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { +impl RemoteAttestationProvider { + pub async fn get_attestation(&self, report_data: [u8; 64]) -> eyre::Result> { let report_data_hex = hex::encode(report_data); let url = format!("{}/{}", self.service_url, report_data_hex); info!(target: "flashtestations", url = url, "fetching quote in debug mode"); - let response = ureq::get(&url) + let response = self + .client + .get(&url) .timeout(std::time::Duration::from_secs(10)) - .call()?; - - let mut body = Vec::new(); - response.into_reader().read_to_end(&mut body)?; + .send() + .await? + .error_for_status()?; + let body = response.bytes().await?.to_vec(); Ok(body) } } -pub fn get_attestation_provider( - config: AttestationConfig, -) -> Box { +pub fn get_attestation_provider(config: AttestationConfig) -> RemoteAttestationProvider { if config.debug { - Box::new(RemoteAttestationProvider::new( + RemoteAttestationProvider::new( config .quote_provider .unwrap_or(DEBUG_QUOTE_SERVICE_URL.to_string()), - )) + ) } else { - Box::new(RemoteAttestationProvider::new( + RemoteAttestationProvider::new( config .quote_provider .expect("remote quote provider must be specified when not in debug mode"), - )) + ) } } diff --git a/crates/builder/op-rbuilder/src/flashtestations/mod.rs b/crates/builder/op-rbuilder/src/flashtestations/mod.rs index a5b16e73..6f9c2743 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/mod.rs @@ -1,3 +1,98 @@ +use alloy_sol_types::{Error, sol}; +use op_revm::OpHaltReason; + +// https://github.com/flashbots/flashtestations/commit/7cc7f68492fe672a823dd2dead649793aac1f216 +sol!( + #[sol(rpc, abi)] + #[derive(Debug)] + interface IFlashtestationRegistry { + function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external; + + /// @notice Emitted when a TEE service is registered + /// @param teeAddress The address of the TEE service + /// @param rawQuote The raw quote from the TEE device + /// @param alreadyExists Whether the TEE service is already registered + event TEEServiceRegistered(address indexed teeAddress, bytes rawQuote, bool alreadyExists); + + /// @notice Emitted when the attestation contract is the 0x0 address + error InvalidAttestationContract(); + /// @notice Emitted when the signature is expired because the deadline has passed + error ExpiredSignature(uint256 deadline); + /// @notice Emitted when the quote is invalid according to the Automata DCAP Attestation contract + error InvalidQuote(bytes output); + /// @notice Emitted when the report data length is too short + error InvalidReportDataLength(uint256 length); + /// @notice Emitted when the registration data hash does not match the expected hash + error InvalidRegistrationDataHash(bytes32 expected, bytes32 received); + /// @notice Emitted when the byte size is exceeded + error ByteSizeExceeded(uint256 size); + /// @notice Emitted when the TEE service is already registered when registering + error TEEServiceAlreadyRegistered(address teeAddress); + /// @notice Emitted when the signer doesn't match the TEE address + error SignerMustMatchTEEAddress(address signer, address teeAddress); + /// @notice Emitted when the TEE service is not registered + error TEEServiceNotRegistered(address teeAddress); + /// @notice Emitted when the TEE service is already invalid when trying to invalidate a TEE registration + error TEEServiceAlreadyInvalid(address teeAddress); + /// @notice Emitted when the TEE service is still valid when trying to invalidate a TEE registration + error TEEIsStillValid(address teeAddress); + /// @notice Emitted when the nonce is invalid when verifying a signature + error InvalidNonce(uint256 expected, uint256 provided); + } + + #[sol(rpc, abi)] + #[derive(Debug)] + interface IBlockBuilderPolicy { + function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + + /// @notice Emitted when a block builder proof is successfully verified + /// @param caller The address that called the verification function (TEE address) + /// @param workloadId The workload identifier of the TEE + /// @param version The flashtestation protocol version used + /// @param blockContentHash The hash of the block content + /// @param commitHash The git commit hash associated with the workload + event BlockBuilderProofVerified( + address caller, bytes32 workloadId, uint8 version, bytes32 blockContentHash, string commitHash + ); + + /// @notice Emitted when the registry is the 0x0 address + error InvalidRegistry(); + /// @notice Emitted when a workload to be added is already in the policy + error WorkloadAlreadyInPolicy(); + /// @notice Emitted when a workload to be removed is not in the policy + error WorkloadNotInPolicy(); + /// @notice Emitted when the address is not in the approvedWorkloads mapping + error UnauthorizedBlockBuilder(address caller); + /// @notice Emitted when the nonce is invalid + error InvalidNonce(uint256 expected, uint256 provided); + /// @notice Emitted when the commit hash is empty + error EmptyCommitHash(); + /// @notice Emitted when the source locators array is empty + error EmptySourceLocators(); + } + + struct BlockData { + bytes32 parentHash; + uint256 blockNumber; + uint256 timestamp; + bytes32[] transactionHashes; + } + + type WorkloadId is bytes32; +); + +#[derive(Debug, thiserror::Error)] +pub enum FlashtestationRevertReason { + #[error("flashtestation registry error: {0:?}")] + FlashtestationRegistry(IFlashtestationRegistry::IFlashtestationRegistryErrors), + #[error("block builder policy error: {0:?}")] + BlockBuilderPolicy(IBlockBuilderPolicy::IBlockBuilderPolicyErrors), + #[error("unknown revert: {0} err: {1}")] + Unknown(String, Error), + #[error("halt: {0:?}")] + Halt(OpHaltReason), +} + pub mod args; pub mod attestation; pub mod service; diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index bd93a777..83dd613f 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -1,6 +1,4 @@ -use std::sync::Arc; - -use alloy_primitives::U256; +use alloy_primitives::{Bytes, keccak256}; use reth_node_builder::BuilderContext; use reth_provider::StateProvider; use reth_revm::State; @@ -14,99 +12,15 @@ use crate::{ }, primitives::reth::ExecutionInfo, traits::NodeBounds, - tx_signer::{Signer, generate_ethereum_keypair}, + tx_signer::{Signer, generate_ethereum_keypair, generate_key_from_seed}, }; use super::{ args::FlashtestationsArgs, - attestation::{AttestationConfig, AttestationProvider, get_attestation_provider}, + attestation::{AttestationConfig, get_attestation_provider}, tx_manager::TxManager, }; -#[derive(Clone)] -pub struct FlashtestationsService { - // Attestation provider generating attestations - attestation_provider: Arc>, - // Handles the onchain attestation and TEE block building proofs - tx_manager: TxManager, - // TEE service generated key - tee_service_signer: Signer, - // Funding amount for the TEE signer - funding_amount: U256, -} - -// TODO: FlashtestationsService error types -impl FlashtestationsService { - pub fn new(args: FlashtestationsArgs) -> Self { - let (private_key, public_key, address) = generate_ethereum_keypair(); - let tee_service_signer = Signer { - address, - pubkey: public_key, - secret: private_key, - }; - - let attestation_provider = Arc::new(get_attestation_provider(AttestationConfig { - debug: args.debug, - quote_provider: args.quote_provider, - })); - - let tx_manager = TxManager::new( - tee_service_signer, - args.funding_key - .expect("funding key required when flashtestations enabled"), - args.rpc_url - .expect("external rpc url required when flashtestations enabled"), - args.registry_address - .expect("registry address required when flashtestations enabled"), - args.builder_policy_address - .expect("builder policy address required when flashtestations enabled"), - args.builder_proof_version, - ); - - Self { - attestation_provider, - tx_manager, - tee_service_signer, - funding_amount: args.funding_amount, - } - } - - pub async fn bootstrap(&self) -> eyre::Result<()> { - // Prepare report data with public key (64 bytes, no 0x04 prefix) - let mut report_data = [0u8; 64]; - let pubkey_uncompressed = self.tee_service_signer.pubkey.serialize_uncompressed(); - report_data.copy_from_slice(&pubkey_uncompressed[1..65]); // Skip 0x04 prefix - - // Request TDX attestation - info!(target: "flashtestations", "requesting TDX attestation"); - let attestation = self.attestation_provider.get_attestation(report_data)?; - - // Submit report onchain by registering the key of the tee service - self.tx_manager - .fund_and_register_tee_service(attestation, self.funding_amount) - .await - } - - pub async fn clean_up(&self) -> eyre::Result<()> { - self.tx_manager.clean_up().await - } -} - -#[derive(Debug, Clone)] -pub struct FlashtestationsBuilderTx {} - -impl BuilderTransactions for FlashtestationsBuilderTx { - fn simulate_builder_txs( - &self, - _state_provider: impl StateProvider + Clone, - _info: &mut ExecutionInfo, - _ctx: &OpPayloadBuilderCtx, - _db: &mut State, - ) -> Result, BuilderTransactionError> { - Ok(vec![]) - } -} - pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, ctx: &BuilderContext, @@ -114,74 +28,124 @@ pub async fn bootstrap_flashtestations( where Node: NodeBounds, { - info!("Flashtestations enabled"); + let (private_key, public_key, address) = if args.debug { + info!("Flashtestations debug mode enabled, generating debug key"); + // Generate deterministic key for debugging purposes + generate_key_from_seed(&args.debug_tee_key_seed) + } else { + generate_ethereum_keypair() + }; + + info!("Flashtestations key generated: {}", address); + + let tee_service_signer = Signer { + address, + pubkey: public_key, + secret: private_key, + }; + + let funding_key = args + .funding_key + .expect("funding key required when flashtestations enabled"); + let registry_address = args + .registry_address + .expect("registry address required when flashtestations enabled"); + let _builder_policy_address = args + .builder_policy_address + .expect("builder policy address required when flashtestations enabled"); + + let attestation_provider = get_attestation_provider(AttestationConfig { + debug: args.debug, + quote_provider: args.quote_provider, + }); + + // Prepare report data: + // - TEE address (20 bytes) at reportData[0:20] + // - Extended registration data hash (32 bytes) at reportData[20:52] + // - Total: 52 bytes, padded to 64 bytes with zeros + + // Extract TEE address as 20 bytes + let tee_address_bytes: [u8; 20] = tee_service_signer.address.into(); + + // Calculate keccak256 hash of empty bytes (32 bytes) + let ext_data = Bytes::from(b""); + let ext_data_hash = keccak256(&ext_data); + + // Create 64-byte report data array + let mut report_data = [0u8; 64]; + + // Copy TEE address (20 bytes) to positions 0-19 + report_data[0..20].copy_from_slice(&tee_address_bytes); + + // Copy extended registration data hash (32 bytes) to positions 20-51 + report_data[20..52].copy_from_slice(ext_data_hash.as_ref()); + + // Request TDX attestation + info!(target: "flashtestations", "requesting TDX attestation"); + let attestation = attestation_provider.get_attestation(report_data).await?; + + #[allow(dead_code)] + let (tx_manager, _registered) = if let Some(rpc_url) = args.rpc_url { + let tx_manager = TxManager::new( + tee_service_signer, + funding_key, + rpc_url.clone(), + registry_address, + ); + // Submit report onchain by registering the key of the tee service + match tx_manager + .fund_and_register_tee_service( + attestation.clone(), + ext_data.clone(), + args.funding_amount, + ) + .await + { + Ok(_) => (Some(tx_manager), true), + Err(e) => { + warn!(error = %e, "Failed to register tee service via rpc"); + (Some(tx_manager), false) + } + } + } else { + (None, false) + }; - let flashtestations_service = FlashtestationsService::new(args.clone()); - // Generates new key and registers the attestation onchain - flashtestations_service.bootstrap().await?; + let flashtestations_builder_tx = FlashtestationsBuilderTx {}; - let flashtestations_clone = flashtestations_service.clone(); ctx.task_executor() .spawn_critical_with_graceful_shutdown_signal( "flashtestations clean up task", |shutdown| { Box::pin(async move { let graceful_guard = shutdown.await; - if let Err(e) = flashtestations_clone.clean_up().await { - warn!( - error = %e, - "Failed to complete clean up for flashtestations service", - ) - }; + if let Some(tx_manager) = tx_manager { + if let Err(e) = tx_manager.clean_up().await { + warn!( + error = %e, + "Failed to complete clean up for flashtestations service", + ); + } + } drop(graceful_guard) }) }, ); - Ok(FlashtestationsBuilderTx {}) + Ok(flashtestations_builder_tx) } -#[cfg(test)] -mod tests { - use alloy_primitives::Address; - use secp256k1::{PublicKey, Secp256k1, SecretKey}; - use sha3::{Digest, Keccak256}; - - use crate::tx_signer::public_key_to_address; - - /// Derives Ethereum address from report data using the same logic as the Solidity contract - fn derive_ethereum_address_from_report_data(pubkey_64_bytes: &[u8]) -> Address { - // This exactly matches the Solidity implementation: - // address(uint160(uint256(keccak256(reportData)))) - - // Step 1: keccak256(reportData) - let hash = Keccak256::digest(pubkey_64_bytes); - - // Step 2: Take last 20 bytes (same as uint256 -> uint160 conversion) - let mut address_bytes = [0u8; 20]; - address_bytes.copy_from_slice(&hash[12..32]); - - Address::from(address_bytes) - } - - #[test] - fn test_address_derivation_matches() { - // Test that our manual derivation is correct - let secp = Secp256k1::new(); - let private_key = SecretKey::from_slice(&[0x01; 32]).unwrap(); - let public_key = PublicKey::from_secret_key(&secp, &private_key); - - // Get address using our implementation - let our_address = public_key_to_address(&public_key); - - // Get address using our manual derivation (matching Solidity) - let pubkey_bytes = public_key.serialize_uncompressed(); - let report_data = &pubkey_bytes[1..65]; // Skip 0x04 prefix - let manual_address = derive_ethereum_address_from_report_data(report_data); +#[derive(Debug, Clone)] +pub struct FlashtestationsBuilderTx {} - assert_eq!( - our_address, manual_address, - "Address derivation should match" - ); +impl BuilderTransactions for FlashtestationsBuilderTx { + fn simulate_builder_txs( + &self, + _state_provider: impl StateProvider + Clone, + _info: &mut ExecutionInfo, + _ctx: &OpPayloadBuilderCtx, + _db: &mut State, + ) -> Result, BuilderTransactionError> { + Ok(vec![]) } } diff --git a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs index 9eabdd28..a49f50a3 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs @@ -1,41 +1,34 @@ -use alloy_consensus::TxEip1559; -use alloy_eips::Encodable2718; +use alloy_json_rpc::RpcError; use alloy_network::ReceiptResponse; -use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, keccak256}; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256}; use alloy_rpc_types_eth::TransactionRequest; -use alloy_transport::TransportResult; -use op_alloy_consensus::OpTypedTransaction; -use reth_optimism_node::OpBuiltPayload; -use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; +use alloy_sol_types::SolCall; +use alloy_transport::{TransportError, TransportErrorKind, TransportResult}; +use k256::ecdsa; use std::time::Duration; -use alloy_provider::{PendingTransactionBuilder, Provider, ProviderBuilder}; +use alloy_provider::{ + PendingTransactionBuilder, PendingTransactionError, Provider, ProviderBuilder, +}; use alloy_signer_local::PrivateKeySigner; -use alloy_sol_types::{SolCall, SolValue, sol}; use op_alloy_network::Optimism; -use tracing::{debug, error, info}; - -use crate::tx_signer::Signer; - -sol!( - #[sol(rpc, abi)] - interface IFlashtestationRegistry { - function registerTEEService(bytes calldata rawQuote) external; - } - - #[sol(rpc, abi)] - interface IBlockBuilderPolicy { - function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; - } - - struct BlockData { - bytes32 parentHash; - uint256 blockNumber; - uint256 timestamp; - bytes32[] transactionHashes; - } -); +use tracing::{debug, info, warn}; + +use crate::{flashtestations::IFlashtestationRegistry, tx_signer::Signer}; + +#[derive(Debug, thiserror::Error)] +pub enum TxManagerError { + #[error("rpc error: {0}")] + RpcError(#[from] TransportError), + #[error("tx reverted: {0}")] + TxReverted(TxHash), + #[error("error checking tx confirmation: {0}")] + TxConfirmationError(PendingTransactionError), + #[error("tx rpc error: {0}")] + TxRpcError(RpcError), + #[error("signer error: {0}")] + SignerError(ecdsa::Error), +} #[derive(Debug, Clone)] pub struct TxManager { @@ -43,8 +36,6 @@ pub struct TxManager { funding_signer: Signer, rpc_url: String, registry_address: Address, - builder_policy_address: Address, - builder_proof_version: u8, } impl TxManager { @@ -53,21 +44,23 @@ impl TxManager { funding_signer: Signer, rpc_url: String, registry_address: Address, - builder_policy_address: Address, - builder_proof_version: u8, ) -> Self { Self { tee_service_signer, funding_signer, rpc_url, registry_address, - builder_policy_address, - builder_proof_version, } } - pub async fn fund_address(&self, from: Signer, to: Address, amount: U256) -> eyre::Result<()> { - let funding_wallet = PrivateKeySigner::from_bytes(&from.secret.secret_bytes().into())?; + pub async fn fund_address( + &self, + from: Signer, + to: Address, + amount: U256, + ) -> Result<(), TxManagerError> { + let funding_wallet = PrivateKeySigner::from_bytes(&from.secret.secret_bytes().into()) + .map_err(TxManagerError::SignerError)?; let provider = ProviderBuilder::new() .disable_recommended_fillers() .fetch_chain_id() @@ -91,36 +84,41 @@ impl TxManager { match Self::process_pending_tx(provider.send_transaction(funding_tx.into()).await).await { Ok(tx_hash) => { info!(target: "flashtestations", tx_hash = %tx_hash, "funding transaction confirmed successfully"); + Ok(()) } Err(e) => { - error!(target: "flashtestations", error = %e, "funding transaction failed"); - return Err(e); + warn!(target: "flashtestations", error = %e, "funding transaction failed"); + Err(e) } } - - Ok(()) } pub async fn fund_and_register_tee_service( &self, attestation: Vec, + extra_registration_data: Bytes, funding_amount: U256, - ) -> eyre::Result<()> { + ) -> Result<(), TxManagerError> { info!(target: "flashtestations", "funding TEE address at {}", self.tee_service_signer.address); self.fund_address( self.funding_signer, self.tee_service_signer.address, funding_amount, ) - .await?; + .await + .unwrap_or_else(|e| { + warn!(target: "flashtestations", error = %e, "Failed to fund TEE address, attempting to register without funding"); + }); let quote_bytes = Bytes::from(attestation); let wallet = - PrivateKeySigner::from_bytes(&self.tee_service_signer.secret.secret_bytes().into())?; + PrivateKeySigner::from_bytes(&self.tee_service_signer.secret.secret_bytes().into()) + .map_err(TxManagerError::SignerError)?; let provider = ProviderBuilder::new() .disable_recommended_fillers() .fetch_chain_id() .with_gas_estimation() + .with_cached_nonce_management() .wallet(wallet) .network::() .connect(self.rpc_url.as_str()) @@ -128,16 +126,14 @@ impl TxManager { info!(target: "flashtestations", "submitting quote to registry at {}", self.registry_address); - // TODO: add retries let calldata = IFlashtestationRegistry::registerTEEServiceCall { rawQuote: quote_bytes, + extendedRegistrationData: extra_registration_data, } .abi_encode(); let tx = TransactionRequest { from: Some(self.tee_service_signer.address), to: Some(TxKind::Call(self.registry_address)), - // gas: Some(10_000_000), // Set gas limit manually as the contract is gas heavy - nonce: Some(0), input: calldata.into(), ..Default::default() }; @@ -147,43 +143,13 @@ impl TxManager { Ok(()) } Err(e) => { - error!(target: "flashtestations", error = %e, "attestation transaction failed to be sent"); + warn!(target: "flashtestations", error = %e, "attestation transaction failed to be sent"); Err(e) } } } - pub fn signed_block_builder_proof( - &self, - payload: OpBuiltPayload, - gas_limit: u64, - base_fee: u64, - chain_id: u64, - nonce: u64, - ) -> Result, secp256k1::Error> { - let block_content_hash = Self::compute_block_content_hash(payload); - - info!(target: "flashtestations", block_content_hash = ?block_content_hash, "submitting block builder proof transaction"); - let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { - version: self.builder_proof_version, - blockContentHash: block_content_hash, - } - .abi_encode(); - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas: base_fee.into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(self.builder_policy_address), - input: calldata.into(), - ..Default::default() - }); - self.tee_service_signer.sign_tx(tx) - } - - pub async fn clean_up(&self) -> eyre::Result<()> { + pub async fn clean_up(&self) -> Result<(), TxManagerError> { info!(target: "flashtestations", "sending funds back from TEE generated key to funding address"); let provider = ProviderBuilder::new() .disable_recommended_fillers() @@ -202,7 +168,7 @@ impl TxManager { self.funding_signer.address, balance.saturating_sub(gas_cost), ) - .await? + .await?; } Ok(()) } @@ -210,7 +176,7 @@ impl TxManager { /// Processes a pending transaction and logs whether the transaction succeeded or not async fn process_pending_tx( pending_tx_result: TransportResult>, - ) -> eyre::Result { + ) -> Result { match pending_tx_result { Ok(pending_tx) => { let tx_hash = *pending_tx.tx_hash(); @@ -226,42 +192,13 @@ impl TxManager { if receipt.status() { Ok(receipt.transaction_hash()) } else { - Err(eyre::eyre!("Transaction reverted: {}", tx_hash)) + Err(TxManagerError::TxReverted(tx_hash)) } } - Err(e) => Err(e.into()), + Err(e) => Err(TxManagerError::TxConfirmationError(e)), } } - Err(e) => Err(e.into()), + Err(e) => Err(TxManagerError::TxRpcError(e)), } } - - /// Computes the block content hash according to the formula: - /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) - fn compute_block_content_hash(payload: OpBuiltPayload) -> B256 { - let block = payload.block(); - let body = block.clone().into_body(); - let transactions = body.transactions(); - - // Create ordered list of transaction hashes - let transaction_hashes: Vec = transactions - .map(|tx| { - // RLP encode the transaction and hash it - let mut encoded = Vec::new(); - tx.encode_2718(&mut encoded); - keccak256(&encoded) - }) - .collect(); - - // Create struct and ABI encode - let block_data = BlockData { - parentHash: block.parent_hash, - blockNumber: U256::from(block.number), - timestamp: U256::from(block.timestamp), - transactionHashes: transaction_hashes, - }; - - let encoded = block_data.abi_encode(); - keccak256(&encoded) - } } From 49b1d8c03fceef4141c637d339729ba132fe0d5c Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 9 Oct 2025 11:49:18 -0700 Subject: [PATCH 194/262] Flag to save tee key to local file (#286) * Flag to save tee key to lcoal file * fix error handling * lint * comments * atomic file creation and permission --- .../op-rbuilder/src/flashtestations/args.rs | 8 ++ .../src/flashtestations/service.rs | 93 +++++++++++++++---- crates/builder/op-rbuilder/src/tx_signer.rs | 16 +++- 3 files changed, 96 insertions(+), 21 deletions(-) diff --git a/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs index 54d5d837..598a8a8f 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/args.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/args.rs @@ -33,6 +33,14 @@ pub struct FlashtestationsArgs { )] pub debug_tee_key_seed: String, + /// Path to save ephemeral TEE key between restarts + #[arg( + long = "flashtestations.tee-key-path", + env = "FLASHTESTATIONS_TEE_KEY_PATH", + default_value = "/run/flashtestation.key" + )] + pub flashtestations_key_path: String, + // Remote url for attestations #[arg( long = "flashtestations.quote-provider", diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 83dd613f..237aef7d 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -1,9 +1,15 @@ -use alloy_primitives::{Bytes, keccak256}; +use alloy_primitives::{B256, Bytes, keccak256}; use reth_node_builder::BuilderContext; use reth_provider::StateProvider; use reth_revm::State; use revm::Database; -use std::fmt::Debug; +use std::{ + fmt::Debug, + fs::{self, OpenOptions}, + io::Write, + os::unix::fs::OpenOptionsExt, + path::Path, +}; use tracing::{info, warn}; use crate::{ @@ -12,7 +18,7 @@ use crate::{ }, primitives::reth::ExecutionInfo, traits::NodeBounds, - tx_signer::{Signer, generate_ethereum_keypair, generate_key_from_seed}, + tx_signer::{Signer, generate_key_from_seed, generate_signer}, }; use super::{ @@ -28,21 +34,16 @@ pub async fn bootstrap_flashtestations( where Node: NodeBounds, { - let (private_key, public_key, address) = if args.debug { - info!("Flashtestations debug mode enabled, generating debug key"); - // Generate deterministic key for debugging purposes - generate_key_from_seed(&args.debug_tee_key_seed) - } else { - generate_ethereum_keypair() - }; + let tee_service_signer = load_or_generate_tee_key( + &args.flashtestations_key_path, + args.debug, + &args.debug_tee_key_seed, + )?; - info!("Flashtestations key generated: {}", address); - - let tee_service_signer = Signer { - address, - pubkey: public_key, - secret: private_key, - }; + info!( + "Flashtestations TEE address: {}", + tee_service_signer.address + ); let funding_key = args .funding_key @@ -135,6 +136,64 @@ where Ok(flashtestations_builder_tx) } +/// Load ephemeral TEE key from file, or generate and save a new one +fn load_or_generate_tee_key(key_path: &str, debug: bool, debug_seed: &str) -> eyre::Result { + if debug { + info!("Flashtestations debug mode enabled, generating debug key from seed"); + return Ok(generate_key_from_seed(debug_seed)); + } + + let path = Path::new(key_path); + + if let Some(signer) = load_tee_key(path) { + return Ok(signer); + } + + // Generate new key + info!("Generating new ephemeral TEE key"); + let signer = generate_signer(); + + let key_hex = hex::encode(signer.secret.secret_bytes()); + + // Create file with 0600 permissions atomically + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .mode(0o600) + .open(path) + .and_then(|mut file| file.write_all(key_hex.as_bytes())) + .inspect_err(|e| warn!("Failed to write key to {}: {:?}", key_path, e)) + .ok(); + + Ok(signer) +} + +fn load_tee_key(path: &Path) -> Option { + // Try to load existing key + if !path.exists() { + return None; + } + + info!("Loading TEE key from {:?}", path); + let key_hex = fs::read_to_string(path) + .inspect_err(|e| warn!("failed to read key file: {:?}", e)) + .ok()?; + + let secret_bytes = B256::try_from( + hex::decode(key_hex.trim()) + .inspect_err(|e| warn!("failed to decode hex from file {:?}", e)) + .ok()? + .as_slice(), + ) + .inspect_err(|e| warn!("failed to parse key from file: {:?}", e)) + .ok()?; + + Signer::try_from_secret(secret_bytes) + .inspect_err(|e| warn!("failed to create signer from key: {:?}", e)) + .ok() +} + #[derive(Debug, Clone)] pub struct FlashtestationsBuilderTx {} diff --git a/crates/builder/op-rbuilder/src/tx_signer.rs b/crates/builder/op-rbuilder/src/tx_signer.rs index dd97aed4..6b8c86d9 100644 --- a/crates/builder/op-rbuilder/src/tx_signer.rs +++ b/crates/builder/op-rbuilder/src/tx_signer.rs @@ -74,7 +74,7 @@ impl FromStr for Signer { } } -pub fn generate_ethereum_keypair() -> (SecretKey, PublicKey, Address) { +pub fn generate_signer() -> Signer { let secp = Secp256k1::new(); // Generate cryptographically secure random private key @@ -86,7 +86,11 @@ pub fn generate_ethereum_keypair() -> (SecretKey, PublicKey, Address) { // Derive Ethereum address let address = public_key_to_address(&public_key); - (private_key, public_key, address) + Signer { + address, + pubkey: public_key, + secret: private_key, + } } /// Converts a public key to an Ethereum address @@ -103,7 +107,7 @@ pub fn public_key_to_address(public_key: &PublicKey) -> Address { // Generate a key deterministically from a seed for debug and testing // Do not use in production -pub fn generate_key_from_seed(seed: &str) -> (SecretKey, PublicKey, Address) { +pub fn generate_key_from_seed(seed: &str) -> Signer { // Hash the seed let mut hasher = Sha256::new(); hasher.update(seed.as_bytes()); @@ -115,7 +119,11 @@ pub fn generate_key_from_seed(seed: &str) -> (SecretKey, PublicKey, Address) { let public_key = PublicKey::from_secret_key(&secp, &private_key); let address = public_key_to_address(&public_key); - (private_key, public_key, address) + Signer { + address, + pubkey: public_key, + secret: private_key, + } } #[cfg(test)] From c8bff334cbdc850b4c21dc36a7db0ba3e0b32f73 Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 16 Oct 2025 11:16:49 -0700 Subject: [PATCH 195/262] Add flashtestation builder tx and registration in block (#282) --- .../op-rbuilder/src/builders/builder_tx.rs | 9 + .../op-rbuilder/src/builders/context.rs | 46 +- .../src/builders/flashblocks/builder_tx.rs | 22 +- .../src/builders/standard/builder_tx.rs | 2 +- .../src/flashtestations/builder_tx.rs | 603 ++++++++++++++++++ .../op-rbuilder/src/flashtestations/mod.rs | 4 + .../src/flashtestations/service.rs | 42 +- 7 files changed, 670 insertions(+), 58 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index 0cd8d5e8..15ecc0d8 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -89,6 +89,15 @@ impl From for PayloadBuilderError { } } +impl BuilderTransactionError { + pub fn other(error: E) -> Self + where + E: core::error::Error + Send + Sync + 'static, + { + BuilderTransactionError::Other(Box::new(error)) + } +} + pub trait BuilderTransactions: Debug { fn simulate_builder_txs( &self, diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 71f10c83..a18facf5 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -2,7 +2,7 @@ use alloy_consensus::{Eip658Value, Transaction, conditional::BlockConditionalAtt use alloy_eips::Typed2718; use alloy_evm::Database; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; -use alloy_primitives::{Bytes, U256}; +use alloy_primitives::{BlockHash, Bytes, U256}; use alloy_rpc_types_eth::Withdrawals; use core::fmt::Debug; use op_alloy_consensus::OpDepositReceipt; @@ -75,41 +75,51 @@ pub struct OpPayloadBuilderCtx { impl OpPayloadBuilderCtx { /// Returns the parent block the payload will be build on. - pub(super) fn parent(&self) -> &SealedHeader { + pub fn parent(&self) -> &SealedHeader { &self.config.parent_header } + /// Returns the parent hash + pub fn parent_hash(&self) -> BlockHash { + self.parent().hash() + } + + /// Returns the timestamp + pub fn timestamp(&self) -> u64 { + self.attributes().timestamp() + } + /// Returns the builder attributes. pub(super) const fn attributes(&self) -> &OpPayloadBuilderAttributes { &self.config.attributes } /// Returns the withdrawals if shanghai is active. - pub(super) fn withdrawals(&self) -> Option<&Withdrawals> { + pub fn withdrawals(&self) -> Option<&Withdrawals> { self.chain_spec .is_shanghai_active_at_timestamp(self.attributes().timestamp()) .then(|| &self.attributes().payload_attributes.withdrawals) } /// Returns the block gas limit to target. - pub(super) fn block_gas_limit(&self) -> u64 { + pub fn block_gas_limit(&self) -> u64 { self.attributes() .gas_limit .unwrap_or(self.evm_env.block_env.gas_limit) } /// Returns the block number for the block. - pub(super) fn block_number(&self) -> u64 { + pub fn block_number(&self) -> u64 { as_u64_saturated!(self.evm_env.block_env.number) } /// Returns the current base fee - pub(super) fn base_fee(&self) -> u64 { + pub fn base_fee(&self) -> u64 { self.evm_env.block_env.basefee } /// Returns the current blob gas price. - pub(super) fn get_blob_gasprice(&self) -> Option { + pub fn get_blob_gasprice(&self) -> Option { self.evm_env .block_env .blob_gasprice() @@ -119,7 +129,7 @@ impl OpPayloadBuilderCtx { /// Returns the blob fields for the header. /// /// This will always return `Some(0)` after ecotone. - pub(super) fn blob_fields(&self) -> (Option, Option) { + pub fn blob_fields(&self) -> (Option, Option) { // 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. @@ -132,8 +142,8 @@ impl OpPayloadBuilderCtx { /// Returns the extra data for the block. /// - /// After holocene this extracts the extradata from the paylpad - pub(super) fn extra_data(&self) -> Result { + /// After holocene this extracts the extradata from the payload + pub fn extra_data(&self) -> Result { if self.is_holocene_active() { self.attributes() .get_holocene_extra_data( @@ -148,47 +158,47 @@ impl OpPayloadBuilderCtx { } /// Returns the current fee settings for transactions from the mempool - pub(super) fn best_transaction_attributes(&self) -> BestTransactionsAttributes { + pub fn best_transaction_attributes(&self) -> BestTransactionsAttributes { BestTransactionsAttributes::new(self.base_fee(), self.get_blob_gasprice()) } /// Returns the unique id for this payload job. - pub(super) fn payload_id(&self) -> PayloadId { + pub fn payload_id(&self) -> PayloadId { self.attributes().payload_id() } /// Returns true if regolith is active for the payload. - pub(super) fn is_regolith_active(&self) -> bool { + pub fn is_regolith_active(&self) -> bool { self.chain_spec .is_regolith_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if ecotone is active for the payload. - pub(super) fn is_ecotone_active(&self) -> bool { + pub fn is_ecotone_active(&self) -> bool { self.chain_spec .is_ecotone_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if canyon is active for the payload. - pub(super) fn is_canyon_active(&self) -> bool { + pub fn is_canyon_active(&self) -> bool { self.chain_spec .is_canyon_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if holocene is active for the payload. - pub(super) fn is_holocene_active(&self) -> bool { + pub fn is_holocene_active(&self) -> bool { self.chain_spec .is_holocene_active_at_timestamp(self.attributes().timestamp()) } /// Returns true if isthmus is active for the payload. - pub(super) fn is_isthmus_active(&self) -> bool { + pub fn is_isthmus_active(&self) -> bool { self.chain_spec .is_isthmus_active_at_timestamp(self.attributes().timestamp()) } /// Returns the chain id - pub(super) fn chain_id(&self) -> u64 { + pub fn chain_id(&self) -> u64 { self.chain_spec.chain_id() } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs index 7588dd4e..b1b16906 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -25,7 +25,7 @@ use crate::{ context::OpPayloadBuilderCtx, flashblocks::payload::FlashblocksExtraCtx, }, - flashtestations::service::FlashtestationsBuilderTx, + flashtestations::builder_tx::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, tx_signer::Signer, }; @@ -172,23 +172,21 @@ impl FlashblocksNumberBuilderTx { ) { Ok(gas_used) } else { - Err(BuilderTransactionError::Other(Box::new( + Err(BuilderTransactionError::other( FlashblockNumberError::LogMismatch( IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH, ), - ))) + )) } } - ExecutionResult::Revert { output, .. } => { - Err(BuilderTransactionError::Other(Box::new( - IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&output) - .map(FlashblockNumberError::Revert) - .unwrap_or_else(|e| FlashblockNumberError::Unknown(hex::encode(output), e)), - ))) - } - ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::Other(Box::new( + ExecutionResult::Revert { output, .. } => Err(BuilderTransactionError::other( + IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&output) + .map(FlashblockNumberError::Revert) + .unwrap_or_else(|e| FlashblockNumberError::Unknown(hex::encode(output), e)), + )), + ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::other( FlashblockNumberError::Halt(reason), - ))), + )), } } diff --git a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs index c19f627d..75a159ad 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs @@ -8,7 +8,7 @@ use crate::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, builder_tx::BuilderTxBase, context::OpPayloadBuilderCtx, }, - flashtestations::service::FlashtestationsBuilderTx, + flashtestations::builder_tx::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, tx_signer::Signer, }; diff --git a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs new file mode 100644 index 00000000..ee5e793c --- /dev/null +++ b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs @@ -0,0 +1,603 @@ +use alloy_consensus::TxEip1559; +use alloy_eips::Encodable2718; +use alloy_evm::Database; +use alloy_op_evm::OpEvm; +use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256, map::foldhash::HashMap}; +use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, SolValue}; +use core::fmt::Debug; +use op_alloy_consensus::OpTypedTransaction; +use reth_evm::{ConfigureEvm, Evm, EvmError, precompiles::PrecompilesMap}; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::{Log, Recovered}; +use reth_provider::StateProvider; +use reth_revm::{State, database::StateProviderDatabase}; +use revm::{ + DatabaseCommit, + context::result::{ExecutionResult, ResultAndState}, + inspector::NoOpInspector, + state::Account, +}; +use std::sync::OnceLock; +use tracing::{debug, info}; + +use crate::{ + builders::{ + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, + get_balance, get_nonce, + }, + flashtestations::{ + BlockData, FlashtestationRevertReason, + IBlockBuilderPolicy::{self, BlockBuilderProofVerified}, + IFlashtestationRegistry::{self, TEEServiceRegistered}, + }, + primitives::reth::ExecutionInfo, + tx_signer::Signer, +}; + +pub struct FlashtestationsBuilderTxArgs { + pub attestation: Vec, + pub extra_registration_data: Bytes, + pub tee_service_signer: Signer, + pub funding_key: Signer, + pub funding_amount: U256, + pub registry_address: Address, + pub builder_policy_address: Address, + pub builder_proof_version: u8, + pub enable_block_proofs: bool, + pub registered: bool, +} + +#[derive(Debug, Clone)] +pub struct FlashtestationsBuilderTx { + // Attestation for the builder + attestation: Vec, + // Extra registration data for the builder + extra_registration_data: Bytes, + // TEE service generated key + tee_service_signer: Signer, + // Funding key for the TEE signer + funding_key: Signer, + // Funding amount for the TEE signer + funding_amount: U256, + // Registry address for the attestation + registry_address: Address, + // Builder policy address for the block builder proof + builder_policy_address: Address, + // Builder proof version + builder_proof_version: u8, + // Whether the workload and address has been registered + registered: OnceLock, + // Whether block proofs are enabled + enable_block_proofs: bool, +} + +#[derive(Debug, Default)] +pub struct TxSimulateResult { + pub gas_used: u64, + pub success: bool, + pub state_changes: HashMap, + pub revert_reason: Option, + pub logs: Vec, +} + +impl FlashtestationsBuilderTx { + pub fn new(args: FlashtestationsBuilderTxArgs) -> Self { + Self { + attestation: args.attestation, + extra_registration_data: args.extra_registration_data, + tee_service_signer: args.tee_service_signer, + funding_key: args.funding_key, + funding_amount: args.funding_amount, + registry_address: args.registry_address, + builder_policy_address: args.builder_policy_address, + builder_proof_version: args.builder_proof_version, + registered: OnceLock::new(), + enable_block_proofs: args.enable_block_proofs, + } + } + + fn signed_funding_tx( + &self, + to: Address, + from: Signer, + amount: U256, + base_fee: u64, + chain_id: u64, + nonce: u64, + ) -> Result, secp256k1::Error> { + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id, + nonce, + gas_limit: 21000, + max_fee_per_gas: base_fee.into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(to), + value: amount, + ..Default::default() + }); + from.sign_tx(tx) + } + + fn signed_register_tee_service_tx( + &self, + attestation: Vec, + gas_limit: u64, + base_fee: u64, + chain_id: u64, + nonce: u64, + ) -> Result, secp256k1::Error> { + let quote_bytes = Bytes::from(attestation); + let calldata = IFlashtestationRegistry::registerTEEServiceCall { + rawQuote: quote_bytes, + extendedRegistrationData: self.extra_registration_data.clone(), + } + .abi_encode(); + + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id, + nonce, + gas_limit, + max_fee_per_gas: base_fee.into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(self.registry_address), + input: calldata.into(), + ..Default::default() + }); + self.tee_service_signer.sign_tx(tx) + } + + fn signed_block_builder_proof_tx( + &self, + block_content_hash: B256, + ctx: &OpPayloadBuilderCtx, + gas_limit: u64, + nonce: u64, + ) -> Result, secp256k1::Error> { + let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { + version: self.builder_proof_version, + blockContentHash: block_content_hash, + } + .abi_encode(); + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + gas_limit, + max_fee_per_gas: ctx.base_fee().into(), + max_priority_fee_per_gas: 0, + to: TxKind::Call(self.builder_policy_address), + input: calldata.into(), + ..Default::default() + }); + self.tee_service_signer.sign_tx(tx) + } + + /// Computes the block content hash according to the formula: + /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) + /// https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#block-building-process + fn compute_block_content_hash( + transactions: Vec, + parent_hash: B256, + block_number: u64, + timestamp: u64, + ) -> B256 { + // Create ordered list of transaction hashes + let transaction_hashes: Vec = transactions + .iter() + .map(|tx| { + // RLP encode the transaction and hash it + let mut encoded = Vec::new(); + tx.encode_2718(&mut encoded); + keccak256(&encoded) + }) + .collect(); + + // Create struct and ABI encode + let block_data = BlockData { + parentHash: parent_hash, + blockNumber: U256::from(block_number), + timestamp: U256::from(timestamp), + transactionHashes: transaction_hashes, + }; + + let encoded = block_data.abi_encode(); + keccak256(&encoded) + } + + fn simulate_register_tee_service_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + + let register_tx = self.signed_register_tee_service_tx( + self.attestation.clone(), + ctx.block_gas_limit(), + ctx.base_fee(), + ctx.chain_id(), + nonce, + )?; + let ResultAndState { result, state } = match evm.transact(®ister_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; + match result { + ExecutionResult::Success { gas_used, logs, .. } => Ok(TxSimulateResult { + gas_used, + success: true, + state_changes: state, + revert_reason: None, + logs, + }), + ExecutionResult::Revert { output, gas_used } => { + let revert_reason = + IFlashtestationRegistry::IFlashtestationRegistryErrors::abi_decode(&output) + .map(FlashtestationRevertReason::FlashtestationRegistry) + .unwrap_or_else(|e| { + FlashtestationRevertReason::Unknown(hex::encode(output), e) + }); + Ok(TxSimulateResult { + gas_used, + success: false, + state_changes: state, + revert_reason: Some(revert_reason), + logs: vec![], + }) + } + ExecutionResult::Halt { reason, .. } => Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::Halt(reason)), + logs: vec![], + }), + } + } + + fn check_tee_address_registered_log(&self, logs: &[Log], address: Address) -> bool { + for log in logs { + if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) { + if let Ok(decoded) = TEEServiceRegistered::decode_log(log) { + if decoded.teeAddress == address { + return true; + } + }; + } + } + false + } + + fn simulate_verify_block_proof_tx( + &self, + block_content_hash: B256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result { + let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + + let verify_block_proof_tx = self.signed_block_builder_proof_tx( + block_content_hash, + ctx, + ctx.block_gas_limit(), + nonce, + )?; + let ResultAndState { result, state } = match evm.transact(&verify_block_proof_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; + match result { + ExecutionResult::Success { gas_used, logs, .. } => Ok(TxSimulateResult { + gas_used, + success: true, + state_changes: state, + revert_reason: None, + logs, + }), + ExecutionResult::Revert { output, gas_used } => { + let revert_reason = + IBlockBuilderPolicy::IBlockBuilderPolicyErrors::abi_decode(&output) + .map(FlashtestationRevertReason::BlockBuilderPolicy) + .unwrap_or_else(|e| { + FlashtestationRevertReason::Unknown(hex::encode(output), e) + }); + Ok(TxSimulateResult { + gas_used, + success: false, + state_changes: state, + revert_reason: Some(revert_reason), + logs: vec![], + }) + } + ExecutionResult::Halt { reason, .. } => Ok(TxSimulateResult { + gas_used: 0, + success: false, + state_changes: state, + revert_reason: Some(FlashtestationRevertReason::Halt(reason)), + logs: vec![], + }), + } + } + + fn check_verify_block_proof_log(&self, logs: &[Log]) -> bool { + for log in logs { + if log.topics().first() == Some(&BlockBuilderProofVerified::SIGNATURE_HASH) { + return true; + } + } + false + } + + fn fund_tee_service_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result, BuilderTransactionError> { + let balance = get_balance(evm.db_mut(), self.tee_service_signer.address)?; + if balance.is_zero() { + let funding_nonce = get_nonce(evm.db_mut(), self.funding_key.address)?; + let funding_tx = self.signed_funding_tx( + self.tee_service_signer.address, + self.funding_key, + self.funding_amount, + ctx.base_fee(), + ctx.chain_id(), + funding_nonce, + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(funding_tx.encoded_2718().as_slice()); + let ResultAndState { state, .. } = match evm.transact(&funding_tx) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; + info!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?funding_tx.tx_hash(), "adding funding tx to builder txs"); + evm.db_mut().commit(state); + Ok(Some(BuilderTransactionCtx { + gas_used: 21000, + da_size, + signed_tx: funding_tx, + is_top_of_block: false, + })) + } else { + Ok(None) + } + } + + fn register_tee_service_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result<(Option, bool), BuilderTransactionError> { + let TxSimulateResult { + gas_used, + success, + state_changes, + revert_reason, + logs, + } = self.simulate_register_tee_service_tx(ctx, evm)?; + if success { + if !self.check_tee_address_registered_log(&logs, self.tee_service_signer.address) { + Err(BuilderTransactionError::other( + FlashtestationRevertReason::LogMismatch( + self.registry_address, + TEEServiceRegistered::SIGNATURE_HASH, + ), + )) + } else { + let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + let register_tx = self.signed_register_tee_service_tx( + self.attestation.clone(), + gas_used * 64 / 63, // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer + ctx.base_fee(), + ctx.chain_id(), + nonce, + )?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + register_tx.encoded_2718().as_slice(), + ); + info!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?register_tx.tx_hash(), "adding register tee tx to builder txs"); + evm.db_mut().commit(state_changes); + Ok(( + Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: register_tx, + is_top_of_block: false, + }), + false, + )) + } + } else if let Some(FlashtestationRevertReason::FlashtestationRegistry( + IFlashtestationRegistry::IFlashtestationRegistryErrors::TEEServiceAlreadyRegistered(_), + )) = revert_reason + { + Ok((None, true)) + } else { + Err(BuilderTransactionError::other(revert_reason.unwrap_or( + FlashtestationRevertReason::Unknown( + "unknown revert".into(), + Error::Other("unknown revert".into()), + ), + ))) + } + } + + fn verify_block_proof_tx( + &self, + transactions: Vec, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm< + &mut State>, + NoOpInspector, + PrecompilesMap, + >, + ) -> Result, BuilderTransactionError> { + let block_content_hash = Self::compute_block_content_hash( + transactions.clone(), + ctx.parent_hash(), + ctx.block_number(), + ctx.timestamp(), + ); + + let TxSimulateResult { + gas_used, + success, + revert_reason, + logs, + .. + } = self.simulate_verify_block_proof_tx(block_content_hash, ctx, evm)?; + if success { + if !self.check_verify_block_proof_log(&logs) { + Err(BuilderTransactionError::other( + FlashtestationRevertReason::LogMismatch( + self.builder_policy_address, + BlockBuilderProofVerified::SIGNATURE_HASH, + ), + )) + } else { + let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; + // Due to EIP-150, only 63/64 of available gas is forwarded to external calls so need to add a buffer + let verify_block_proof_tx = self.signed_block_builder_proof_tx( + block_content_hash, + ctx, + gas_used * 64 / 63, + nonce, + )?; + let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( + verify_block_proof_tx.encoded_2718().as_slice(), + ); + debug!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?verify_block_proof_tx.tx_hash(), "adding verify block proof tx to builder txs"); + Ok(Some(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx: verify_block_proof_tx, + is_top_of_block: false, + })) + } + } else { + Err(BuilderTransactionError::other(revert_reason.unwrap_or( + FlashtestationRevertReason::Unknown( + "unknown revert".into(), + Error::Other("unknown revert".into()), + ), + ))) + } + } + + fn set_registered( + &self, + state_provider: impl StateProvider + Clone, + ctx: &OpPayloadBuilderCtx, + ) -> Result<(), BuilderTransactionError> { + let state = StateProviderDatabase::new(state_provider.clone()); + let mut simulation_state = State::builder() + .with_database(state) + .with_bundle_update() + .build(); + let mut evm = ctx + .evm_config + .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + }); + match self.register_tee_service_tx(ctx, &mut evm) { + Ok((_, registered)) => { + if registered { + let _ = self.registered.set(registered); + } + Ok(()) + } + Err(e) => Err(BuilderTransactionError::other(e)), + } + } +} + +impl BuilderTransactions for FlashtestationsBuilderTx { + fn simulate_builder_txs( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result, BuilderTransactionError> { + // set registered simulating against the committed state + if !self.registered.get().unwrap_or(&false) { + self.set_registered(state_provider.clone(), ctx)?; + } + + let state = StateProviderDatabase::new(state_provider.clone()); + let mut simulation_state = State::builder() + .with_database(state) + .with_bundle_prestate(db.bundle_state.clone()) + .with_bundle_update() + .build(); + + let mut evm = ctx + .evm_config + .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + evm.modify_cfg(|cfg| { + cfg.disable_balance_check = true; + }); + + let mut builder_txs = Vec::::new(); + + if !self.registered.get().unwrap_or(&false) { + info!(target: "flashtestations", "tee service not registered yet, attempting to register"); + builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); + let (register_tx, _) = self.register_tee_service_tx(ctx, &mut evm)?; + builder_txs.extend(register_tx); + } + + if self.enable_block_proofs { + // add verify block proof tx + builder_txs.extend(self.verify_block_proof_tx( + info.executed_transactions.clone(), + ctx, + &mut evm, + )?); + } + Ok(builder_txs) + } +} diff --git a/crates/builder/op-rbuilder/src/flashtestations/mod.rs b/crates/builder/op-rbuilder/src/flashtestations/mod.rs index 6f9c2743..a2a50611 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/mod.rs @@ -1,3 +1,4 @@ +use alloy_primitives::{Address, B256}; use alloy_sol_types::{Error, sol}; use op_revm::OpHaltReason; @@ -87,6 +88,8 @@ pub enum FlashtestationRevertReason { FlashtestationRegistry(IFlashtestationRegistry::IFlashtestationRegistryErrors), #[error("block builder policy error: {0:?}")] BlockBuilderPolicy(IBlockBuilderPolicy::IBlockBuilderPolicyErrors), + #[error("contract {0:?} may be invalid, mismatch in log emitted: expected {1:?}")] + LogMismatch(Address, B256), #[error("unknown revert: {0} err: {1}")] Unknown(String, Error), #[error("halt: {0:?}")] @@ -95,5 +98,6 @@ pub enum FlashtestationRevertReason { pub mod args; pub mod attestation; +pub mod builder_tx; pub mod service; pub mod tx_manager; diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 237aef7d..5b9162cd 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -1,10 +1,6 @@ use alloy_primitives::{B256, Bytes, keccak256}; use reth_node_builder::BuilderContext; -use reth_provider::StateProvider; -use reth_revm::State; -use revm::Database; use std::{ - fmt::Debug, fs::{self, OpenOptions}, io::Write, os::unix::fs::OpenOptionsExt, @@ -13,10 +9,7 @@ use std::{ use tracing::{info, warn}; use crate::{ - builders::{ - BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, - }, - primitives::reth::ExecutionInfo, + flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, traits::NodeBounds, tx_signer::{Signer, generate_key_from_seed, generate_signer}, }; @@ -51,7 +44,7 @@ where let registry_address = args .registry_address .expect("registry address required when flashtestations enabled"); - let _builder_policy_address = args + let builder_policy_address = args .builder_policy_address .expect("builder policy address required when flashtestations enabled"); @@ -85,8 +78,7 @@ where info!(target: "flashtestations", "requesting TDX attestation"); let attestation = attestation_provider.get_attestation(report_data).await?; - #[allow(dead_code)] - let (tx_manager, _registered) = if let Some(rpc_url) = args.rpc_url { + let (tx_manager, registered) = if let Some(rpc_url) = args.rpc_url { let tx_manager = TxManager::new( tee_service_signer, funding_key, @@ -112,7 +104,18 @@ where (None, false) }; - let flashtestations_builder_tx = FlashtestationsBuilderTx {}; + let flashtestations_builder_tx = FlashtestationsBuilderTx::new(FlashtestationsBuilderTxArgs { + attestation, + extra_registration_data: ext_data, + tee_service_signer, + funding_key, + funding_amount: args.funding_amount, + registry_address, + builder_policy_address, + builder_proof_version: args.builder_proof_version, + enable_block_proofs: args.enable_block_proofs, + registered, + }); ctx.task_executor() .spawn_critical_with_graceful_shutdown_signal( @@ -193,18 +196,3 @@ fn load_tee_key(path: &Path) -> Option { .inspect_err(|e| warn!("failed to create signer from key: {:?}", e)) .ok() } - -#[derive(Debug, Clone)] -pub struct FlashtestationsBuilderTx {} - -impl BuilderTransactions for FlashtestationsBuilderTx { - fn simulate_builder_txs( - &self, - _state_provider: impl StateProvider + Clone, - _info: &mut ExecutionInfo, - _ctx: &OpPayloadBuilderCtx, - _db: &mut State, - ) -> Result, BuilderTransactionError> { - Ok(vec![]) - } -} From c2b90f4f6a29478edda2b97a87f1353ea64cd325 Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 16 Oct 2025 11:34:55 -0700 Subject: [PATCH 196/262] Add flashtestations integration tests (#283) * Add flashtestation builder tx and registration in block * copilot comments * Add flashtestations integration tests * logging improvements * Update crates/op-rbuilder/src/tests/flashtestations.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/builder/op-rbuilder/Cargo.toml | 3 + .../src/flashtestations/attestation.rs | 2 +- .../src/flashtestations/builder_tx.rs | 19 +- .../op-rbuilder/src/tests/flashblocks.rs | 12 +- .../op-rbuilder/src/tests/flashtestations.rs | 450 ++++++++++++++++++ .../framework/artifacts/genesis.json.tmpl | 6 + .../framework/artifacts/quote-output.bin | Bin 597 -> 597 bytes .../tests/framework/artifacts/test-quote.bin | Bin 5006 -> 5006 bytes .../src/tests/framework/instance.rs | 155 +++++- .../op-rbuilder/src/tests/framework/mod.rs | 23 +- .../op-rbuilder/src/tests/framework/utils.rs | 63 ++- crates/builder/op-rbuilder/src/tests/mod.rs | 17 + 12 files changed, 715 insertions(+), 35 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/tests/flashtestations.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index c674691c..8ae25146 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -192,6 +192,9 @@ testing = [ "ctor", "macros", "rlimit", + "hyper", + "hyper-util", + "http-body-util", ] interop = [] diff --git a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs index 39c99539..b38dcf5a 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs @@ -33,7 +33,7 @@ impl RemoteAttestationProvider { let report_data_hex = hex::encode(report_data); let url = format!("{}/{}", self.service_url, report_data_hex); - info!(target: "flashtestations", url = url, "fetching quote in debug mode"); + info!(target: "flashtestations", url = url, "fetching quote from remote attestation provider"); let response = self .client diff --git a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs index ee5e793c..716d2520 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs @@ -17,7 +17,7 @@ use revm::{ inspector::NoOpInspector, state::Account, }; -use std::sync::OnceLock; +use std::sync::{Arc, atomic::AtomicBool}; use tracing::{debug, info}; use crate::{ @@ -66,7 +66,7 @@ pub struct FlashtestationsBuilderTx { // Builder proof version builder_proof_version: u8, // Whether the workload and address has been registered - registered: OnceLock, + registered: Arc, // Whether block proofs are enabled enable_block_proofs: bool, } @@ -91,7 +91,7 @@ impl FlashtestationsBuilderTx { registry_address: args.registry_address, builder_policy_address: args.builder_policy_address, builder_proof_version: args.builder_proof_version, - registered: OnceLock::new(), + registered: Arc::new(AtomicBool::new(args.registered)), enable_block_proofs: args.enable_block_proofs, } } @@ -178,7 +178,7 @@ impl FlashtestationsBuilderTx { /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) /// https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#block-building-process fn compute_block_content_hash( - transactions: Vec, + transactions: &[OpTransactionSigned], parent_hash: B256, block_number: u64, timestamp: u64, @@ -475,7 +475,7 @@ impl FlashtestationsBuilderTx { >, ) -> Result, BuilderTransactionError> { let block_content_hash = Self::compute_block_content_hash( - transactions.clone(), + &transactions, ctx.parent_hash(), ctx.block_number(), ctx.timestamp(), @@ -545,11 +545,12 @@ impl FlashtestationsBuilderTx { match self.register_tee_service_tx(ctx, &mut evm) { Ok((_, registered)) => { if registered { - let _ = self.registered.set(registered); + self.registered + .store(true, std::sync::atomic::Ordering::SeqCst); } Ok(()) } - Err(e) => Err(BuilderTransactionError::other(e)), + Err(e) => Err(e), } } } @@ -563,7 +564,7 @@ impl BuilderTransactions for Flashtestation db: &mut State, ) -> Result, BuilderTransactionError> { // set registered simulating against the committed state - if !self.registered.get().unwrap_or(&false) { + if !self.registered.load(std::sync::atomic::Ordering::SeqCst) { self.set_registered(state_provider.clone(), ctx)?; } @@ -583,7 +584,7 @@ impl BuilderTransactions for Flashtestation let mut builder_txs = Vec::::new(); - if !self.registered.get().unwrap_or(&false) { + if !self.registered.load(std::sync::atomic::Ordering::SeqCst) { info!(target: "flashtestations", "tee service not registered yet, attempting to register"); builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); let (register_tx, _) = self.register_tee_service_tx(ctx, &mut evm)?; diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index f3e46dae..78104828 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -1,8 +1,7 @@ use alloy_consensus::Transaction; use alloy_eips::Decodable2718; -use alloy_primitives::{Address, TxHash, U256, address, b128, b256}; +use alloy_primitives::{Address, TxHash, U256}; use alloy_provider::Provider; -use alloy_sol_types::SolCall; use macros::rb_test; use op_alloy_consensus::OpTxEnvelope; use std::time::Duration; @@ -10,16 +9,11 @@ use std::time::Duration; use crate::{ args::{FlashblocksArgs, OpRbuilderArgs}, tests::{ - BUILDER_PRIVATE_KEY, BlockTransactionsExt, BundleOpts, ChainDriver, ChainDriverExt, - FUNDED_PRIVATE_KEY, LocalInstance, ONE_ETH, TransactionBuilderExt, - flashblocks_number_contract::FlashblocksNumber, + BlockTransactionsExt, BundleOpts, ChainDriver, FLASHBLOCKS_NUMBER_ADDRESS, LocalInstance, + TransactionBuilderExt, flashblocks_number_contract::FlashblocksNumber, }, - tx_signer::Signer, }; -// If the order of deployment from the signer changes the address will change -const FLASHBLOCKS_NUMBER_ADDRESS: Address = address!("5fbdb2315678afecb367f032d93f642f64180aa3"); - #[rb_test(flashblocks, args = OpRbuilderArgs { chain_block_time: 2000, flashblocks: FlashblocksArgs { diff --git a/crates/builder/op-rbuilder/src/tests/flashtestations.rs b/crates/builder/op-rbuilder/src/tests/flashtestations.rs new file mode 100644 index 00000000..c12deb3e --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/flashtestations.rs @@ -0,0 +1,450 @@ +use alloy_consensus::Transaction; +use alloy_network::TransactionResponse; +use alloy_primitives::{Address, U256}; +use alloy_provider::{Provider, RootProvider}; +use macros::{if_flashblocks, if_standard, rb_test}; +use op_alloy_network::Optimism; + +use crate::{ + args::{FlashblocksArgs, OpRbuilderArgs}, + flashtestations::args::FlashtestationsArgs, + tests::{ + BLOCK_BUILDER_POLICY_ADDRESS, BundleOpts, ChainDriver, ChainDriverExt, + FLASHBLOCKS_NUMBER_ADDRESS, FLASHTESTATION_REGISTRY_ADDRESS, LocalInstance, + MOCK_DCAP_ADDRESS, TEE_DEBUG_ADDRESS, TransactionBuilderExt, + flashblocks_number_contract::FlashblocksNumber, + flashtestation_registry::FlashtestationRegistry, flashtestations_signer, + }, +}; + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_registrations(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true).await?; + let block: alloy_rpc_types_eth::Block = + driver.build_new_block_with_current_timestamp(None).await?; + // check the builder tx, funding tx and registration tx is in the block + let num_txs = block.transactions.len(); + assert!(num_txs >= 3, "Expected at least 3 transactions in block"); + println!( + "block transactions {:#?}", + &block.transactions.clone().into_transactions_vec() + ); + let last_3_txs = &block.transactions.into_transactions_vec()[num_txs - 3..]; + // Check builder tx + assert_eq!( + last_3_txs[0].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + // Check funding tx + assert_eq!( + last_3_txs[1].to(), + Some(TEE_DEBUG_ADDRESS), + "funding tx should send to tee address" + ); + assert!( + last_3_txs[1] + .value() + .eq(&rbuilder.args().flashtestations.funding_amount), + "funding tx should have correct amount" + ); + // Check registration tx + assert_eq!( + last_3_txs[2].to(), + Some(FLASHTESTATION_REGISTRY_ADDRESS), + "registration tx should call registry" + ); + let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); + let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?; + assert!(result._1.isValid, "The tee key is not registered"); + + // check builder does not try to register again + let block = driver.build_new_block_with_current_timestamp(None).await?; + let num_txs = block.transactions.len(); + if_flashblocks!( + assert!(num_txs == 3, "Expected at 3 transactions in block"); // deposit + 2 builder tx + ); + if_standard!( + assert!(num_txs == 2, "Expected at 2 transactions in block"); // deposit + builder tx + ); + + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_block_proofs(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true).await?; + driver.build_new_block_with_current_timestamp(None).await?; + + // check registered + let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); + let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?; + assert!(result._1.isValid, "The tee key is not registered"); + + // check that only the builder tx and block proof is in the block + let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; + let txs = block.transactions.into_transactions_vec(); + + if_flashblocks!( + assert_eq!(txs.len(), 5, "Expected at 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + ); + if_standard!( + assert_eq!(txs.len(), 4, "Expected at 4 transactions in block"); // deposit + valid tx + builder tx + end of block proof + ); + let last_3_txs = &txs[txs.len() - 3..]; + // Check valid transaction + assert_eq!( + last_3_txs[0].inner.tx_hash(), + tx_hash, + "tx hash for valid transaction should match" + ); + // Check builder tx + assert_eq!( + last_3_txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + // Check builder proof + assert_eq!( + last_3_txs[2].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "builder tx should send to zero address" + ); + Ok(()) +} + +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + ..Default::default() + }, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashblock_number_contract(&driver, &provider, true).await?; + setup_flashtestation_contracts(&driver, &provider, true).await?; + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + // 1 deposit tx, 1 fallback builder tx, 4 flashblocks number tx, valid tx, funding tx, registration tx, block proof + let txs = block.transactions.into_transactions_vec(); + assert_eq!(txs.len(), 10, "Expected at 10 transactions in block"); + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "fallback builder tx should send to zero address" + ); + // flashblocks number contract + for i in 2..6 { + assert_eq!( + txs[i].to(), + Some(FLASHBLOCKS_NUMBER_ADDRESS), + "builder tx should send to flashblocks number contract" + ); + } + // check regular tx + assert_eq!( + txs[6].tx_hash(), + *tx.tx_hash(), + "bundle tx was not in block" + ); + // check funding, registration and block proof tx + assert_eq!( + txs[7].to(), + Some(TEE_DEBUG_ADDRESS), + "funding tx should send to tee address" + ); + assert_eq!( + txs[8].to(), + Some(FLASHTESTATION_REGISTRY_ADDRESS), + "registration tx should call registry" + ); + assert_eq!( + txs[9].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "block proof tx should call block policy address" + ); + + // add a user transaction to ensure the flashblock number builder tx is top of block + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + // check the flashblocks number tx and block proof is in the block + let txs = block.transactions.into_transactions_vec(); + assert_eq!(txs.len(), 8, "Expected at 5 transactions in block"); + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "fallback builder tx should send to zero address" + ); + // flashblocks number contract + for i in 2..6 { + assert_eq!( + txs[i].to(), + Some(FLASHBLOCKS_NUMBER_ADDRESS), + "builder tx should send to flashblocks number contract" + ); + } + // user tx + assert_eq!( + txs[6].tx_hash(), + *tx.tx_hash(), + "bundle tx was not in block" + ); + // block proof + assert_eq!( + txs[7].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "block proof tx should call block policy address" + ); + + let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); + let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?; + assert!(result._1.isValid, "The tee key is not registered"); + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert!( + current_number.gt(&U256::from(8)), // contract deployments incremented the number but we built at least 2 full blocks + "Flashblock number not incremented" + ); + Ok(()) +} + +async fn setup_flashtestation_contracts( + driver: &ChainDriver, + provider: &RootProvider, + authorize_workload: bool, +) -> eyre::Result<()> { + // deploy the mock contract and register a mock quote + let mock_dcap_deploy_tx = driver + .create_transaction() + .deploy_mock_dcap_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // Add test quote + let mock_quote_tx = driver + .create_transaction() + .add_mock_quote() + .with_to(MOCK_DCAP_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // deploy the flashtestations registry contract + let flashtestations_registry_tx = driver + .create_transaction() + .deploy_flashtestation_registry_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // init the flashtestation registry contract + let init_registry = driver + .create_transaction() + .init_flashtestation_registry_contract(MOCK_DCAP_ADDRESS) + .with_to(FLASHTESTATION_REGISTRY_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // deploy the block builder policy contract + let block_builder_policy_tx = driver + .create_transaction() + .deploy_builder_policy_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // init the builder block policy contract + let init_builder_policy = driver + .create_transaction() + .init_builder_policy_contract(FLASHTESTATION_REGISTRY_ADDRESS) + .with_to(BLOCK_BUILDER_POLICY_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // include the deployment and initialization in a block + driver.build_new_block_with_current_timestamp(None).await?; + + if authorize_workload { + // add the workload id to the block builder policy + let add_workload = driver + .create_transaction() + .add_workload_to_policy() + .with_to(BLOCK_BUILDER_POLICY_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*add_workload.tx_hash()) + .await? + .expect("add workload to builder policy tx not mined"); + } + + // Verify mock dcap contract deployment + let receipt = provider + .get_transaction_receipt(*mock_dcap_deploy_tx.tx_hash()) + .await? + .expect("mock dcap contract deployment not mined"); + let mock_dcap_address = receipt + .inner + .contract_address + .expect("contract receipt does not contain flashblock number contract address"); + assert_eq!( + mock_dcap_address, MOCK_DCAP_ADDRESS, + "mock dcap contract address mismatch" + ); + // verify mock quote added + provider + .get_transaction_receipt(*mock_quote_tx.tx_hash()) + .await? + .expect("add mock quote not mined"); + // verify flashtestations registry contract deployment + let receipt = provider + .get_transaction_receipt(*flashtestations_registry_tx.tx_hash()) + .await?; + let flashtestations_registry_address = receipt + .expect("flashtestations registry contract deployment not mined") + .inner + .contract_address + .expect("contract receipt does not contain flashtestations registry contract address"); + assert_eq!( + flashtestations_registry_address, FLASHTESTATION_REGISTRY_ADDRESS, + "flashtestations registry contract address mismatch" + ); + // verify flashtestations registry contract initialization + provider + .get_transaction_receipt(*init_registry.tx_hash()) + .await? + .expect("init registry tx not mined"); + + // verify block builder policy contract deployment + let receipt = provider + .get_transaction_receipt(*block_builder_policy_tx.tx_hash()) + .await?; + let block_builder_policy_address = receipt + .expect("block builder policy contract deployment not mined") + .inner + .contract_address + .expect("contract receipt does not contain block builder policy contract address"); + assert_eq!( + block_builder_policy_address, BLOCK_BUILDER_POLICY_ADDRESS, + "block builder policy contract address mismatch" + ); + // verify block builder policy contract initialization + provider + .get_transaction_receipt(*init_builder_policy.tx_hash()) + .await? + .expect("init builder policy tx not mined"); + + Ok(()) +} + +async fn setup_flashblock_number_contract( + driver: &ChainDriver, + provider: &RootProvider, + authorize_builder: bool, +) -> eyre::Result<()> { + // Deploy flashblocks number contract + let deploy_tx = driver + .create_transaction() + .deploy_flashblock_number_contract() + .with_bundle(BundleOpts::default()) + .send() + .await?; + + // Initialize contract + let init_tx = driver + .create_transaction() + .init_flashblock_number_contract(authorize_builder) + .with_to(FLASHBLOCKS_NUMBER_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + driver.build_new_block_with_current_timestamp(None).await?; + + // Verify contract deployment + let receipt = provider + .get_transaction_receipt(*deploy_tx.tx_hash()) + .await? + .expect("flashblock number contract deployment not mined"); + let contract_address = receipt + .inner + .contract_address + .expect("contract receipt does not contain flashblock number contract address"); + assert_eq!( + contract_address, FLASHBLOCKS_NUMBER_ADDRESS, + "Flashblocks number contract address mismatch" + ); + + // Verify initialization + provider + .get_transaction_receipt(*init_tx.tx_hash()) + .await? + .expect("init tx not mined"); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl b/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl index dddb61d7..39a6f53e 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl @@ -869,6 +869,12 @@ "fabb0ac9d68b0b445fb7357272ff202c5651694a": { "balance": "0x21e19e0c9bab2400000" }, + "23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "balance": "0x21e19e0c9bab2400000" + }, + "a0ee7a142d267c1f36714e4a8f75612f20a79720": { + "balance": "0x21e19e0c9bab2400000" + }, "fb1bffc9d739b8d520daf37df666da4c687191ea": { "code": "0x6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611647578063f698da2514611698578063f8dc5dd9146116c3578063ffa1ad741461173e57610231565b8063e19a9dd91461139b578063e318b52b146113ec578063e75235b81461147d578063e86637db146114a857610231565b8063cc2f8452116100d1578063cc2f8452146110e8578063d4d9bdcd146111b5578063d8d11f78146111f0578063e009cfde1461132a57610231565b8063affed0e014610d94578063b4faba0914610dbf578063b63e800d14610ea7578063c4ca3a9c1461101757610231565b80635624b25b1161017a5780636a761202116101495780636a761202146109945780637d83297414610b50578063934f3a1114610bbf578063a0e67e2b14610d2857610231565b80635624b25b146107fb5780635ae6bd37146108b9578063610b592514610908578063694e80c31461095957610231565b80632f54bf6e116101b65780632f54bf6e146104d35780633408e4701461053a578063468721a7146105655780635229073f1461067a57610231565b80630d582f131461029e57806312fb68e0146102f95780632d9ad53d1461046c57610231565b36610231573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023d57600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061027257600080f35b36600080373360601b365260008060143601600080855af13d6000803e80610299573d6000fd5b3d6000f35b3480156102aa57600080fd5b506102f7600480360360408110156102c157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506117ce565b005b34801561030557600080fd5b5061046a6004803603608081101561031c57600080fd5b81019080803590602001909291908035906020019064010000000081111561034357600080fd5b82018360208201111561035557600080fd5b8035906020019184600183028401116401000000008311171561037757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103da57600080fd5b8201836020820111156103ec57600080fd5b8035906020019184600183028401116401000000008311171561040e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611bbe565b005b34801561047857600080fd5b506104bb6004803603602081101561048f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612440565b60405180821515815260200191505060405180910390f35b3480156104df57600080fd5b50610522600480360360208110156104f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612512565b60405180821515815260200191505060405180910390f35b34801561054657600080fd5b5061054f6125e4565b6040518082815260200191505060405180910390f35b34801561057157600080fd5b506106626004803603608081101561058857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105cf57600080fd5b8201836020820111156105e157600080fd5b8035906020019184600183028401116401000000008311171561060357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506125f1565b60405180821515815260200191505060405180910390f35b34801561068657600080fd5b506107776004803603608081101561069d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106e457600080fd5b8201836020820111156106f657600080fd5b8035906020019184600183028401116401000000008311171561071857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506126fc565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107bf5780820151818401526020810190506107a4565b50505050905090810190601f1680156107ec5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561080757600080fd5b5061083e6004803603604081101561081e57600080fd5b810190808035906020019092919080359060200190929190505050612732565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561087e578082015181840152602081019050610863565b50505050905090810190601f1680156108ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108c557600080fd5b506108f2600480360360208110156108dc57600080fd5b81019080803590602001909291905050506127b9565b6040518082815260200191505060405180910390f35b34801561091457600080fd5b506109576004803603602081101561092b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506127d1565b005b34801561096557600080fd5b506109926004803603602081101561097c57600080fd5b8101908080359060200190929190505050612b63565b005b610b3860048036036101408110156109ab57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109f257600080fd5b820183602082011115610a0457600080fd5b80359060200191846001830284011164010000000083111715610a2657600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610ab257600080fd5b820183602082011115610ac457600080fd5b80359060200191846001830284011164010000000083111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612c9d565b60405180821515815260200191505060405180910390f35b348015610b5c57600080fd5b50610ba960048036036040811015610b7357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050612edc565b6040518082815260200191505060405180910390f35b348015610bcb57600080fd5b50610d2660048036036060811015610be257600080fd5b810190808035906020019092919080359060200190640100000000811115610c0957600080fd5b820183602082011115610c1b57600080fd5b80359060200191846001830284011164010000000083111715610c3d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610ca057600080fd5b820183602082011115610cb257600080fd5b80359060200191846001830284011164010000000083111715610cd457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612f01565b005b348015610d3457600080fd5b50610d3d612f90565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d80578082015181840152602081019050610d65565b505050509050019250505060405180910390f35b348015610da057600080fd5b50610da9613139565b6040518082815260200191505060405180910390f35b348015610dcb57600080fd5b50610ea560048036036040811015610de257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1f57600080fd5b820183602082011115610e3157600080fd5b80359060200191846001830284011164010000000083111715610e5357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929050505061313f565b005b348015610eb357600080fd5b506110156004803603610100811015610ecb57600080fd5b8101908080359060200190640100000000811115610ee857600080fd5b820183602082011115610efa57600080fd5b80359060200191846020830284011164010000000083111715610f1c57600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f6757600080fd5b820183602082011115610f7957600080fd5b80359060200191846001830284011164010000000083111715610f9b57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613161565b005b34801561102357600080fd5b506110d26004803603608081101561103a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561108157600080fd5b82018360208201111561109357600080fd5b803590602001918460018302840111640100000000831117156110b557600080fd5b9091929391929390803560ff16906020019092919050505061331f565b6040518082815260200191505060405180910390f35b3480156110f457600080fd5b506111416004803603604081101561110b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613447565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156111a0578082015181840152602081019050611185565b50505050905001935050505060405180910390f35b3480156111c157600080fd5b506111ee600480360360208110156111d857600080fd5b8101908080359060200190929190505050613639565b005b3480156111fc57600080fd5b50611314600480360361014081101561121457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561125b57600080fd5b82018360208201111561126d57600080fd5b8035906020019184600183028401116401000000008311171561128f57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506137d8565b6040518082815260200191505060405180910390f35b34801561133657600080fd5b506113996004803603604081101561134d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613805565b005b3480156113a757600080fd5b506113ea600480360360208110156113be57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613b96565b005b3480156113f857600080fd5b5061147b6004803603606081101561140f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613c1a565b005b34801561148957600080fd5b5061149261428c565b6040518082815260200191505060405180910390f35b3480156114b457600080fd5b506115cc60048036036101408110156114cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561151357600080fd5b82018360208201111561152557600080fd5b8035906020019184600183028401116401000000008311171561154757600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050614296565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561160c5780820151818401526020810190506115f1565b50505050905090810190601f1680156116395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561165357600080fd5b506116966004803603602081101561166a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061443e565b005b3480156116a457600080fd5b506116ad61449f565b6040518082815260200191505060405180910390f35b3480156116cf57600080fd5b5061173c600480360360608110156116e657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061451d565b005b34801561174a57600080fd5b50611753614950565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611793578082015181840152602081019050611778565b50505050905090810190601f1680156117c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6117d6614989565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156118405750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561187857503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6118ea576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146119eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055507f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2682604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414611bba57611bb981612b63565b5b5050565b611bd2604182614a2c90919063ffffffff16565b82511015611c48576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b8681101561243457611c648882614a66565b80945081955082965050505060008460ff16141561206d578260001c9450611c96604188614a2c90919063ffffffff16565b8260001c1015611d0e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611d2760208460001c614a9590919063ffffffff16565b1115611d9b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611dd182611dc360208760001c614a9590919063ffffffff16565b614a9590919063ffffffff16565b1115611e45576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611ee7578082015181840152602081019050611ecc565b50505050905090810190601f168015611f145780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611f4d578082015181840152602081019050611f32565b50505050905090810190601f168015611f7a5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f9957600080fd5b505afa158015611fad573d6000803e3d6000fd5b505050506040513d6020811015611fc357600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612066576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b50506122b2565b60018460ff161415612181578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061210a57506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b61217c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6122b1565b601e8460ff1611156122495760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612238573d6000803e3d6000fd5b5050506020604051035194506122b0565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156122a3573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161180156123795750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123b25750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b612424576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611c52565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff161415801561250b5750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125dd5750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b60007fb648d3644f584ed1c2232d53c46d87e693586486ad0d1175f8656013110b714e3386868686604051808673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018481526020018060200183600181111561266b57fe5b8152602001828103825284818151815260200191508051906020019080838360005b838110156126a857808201518184015260208101905061268d565b50505050905090810190601f1680156126d55780820380516001836020036101000a031916815260200191505b50965050505050505060405180910390a16126f285858585614ab4565b9050949350505050565b6000606061270c868686866125f1565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff8111801561275057600080fd5b506040519080825280601f01601f1916602001820160405280156127835781602001600182028036833780820191505090505b50905060005b838110156127ae57808501548060208302602085010152508080600101915050612789565b508091505092915050565b60076020528060005260406000206000915090505481565b6127d9614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156128435750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6128b5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146129b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b612b6b614989565b600354811115612be3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612c5a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000606060055433600454604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405160208183030381529060405290507f66753cd2356569ee081232e3be8909b950e0a76c1f8460c3a5e3c2be32b11bed8d8d8d8d8d8d8d8d8d8d8d8c604051808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612d5057fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018060200184810384528e8e82818152602001925080828437600081840152601f19601f820116905080830192505050848103835286818151815260200191508051906020019080838360005b83811015612e0a578082015181840152602081019050612def565b50505050905090810190601f168015612e375780820380516001836020036101000a031916815260200191505b50848103825285818151815260200191508051906020019080838360005b83811015612e70578082015181840152602081019050612e55565b50505050905090810190601f168015612e9d5780820380516001836020036101000a031916815260200191505b509f5050505050505050505050505050505060405180910390a1612eca8d8d8d8d8d8d8d8d8d8d8d614c9a565b9150509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b6000600454905060008111612f7e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b612f8a84848484611bbe565b50505050565b6060600060035467ffffffffffffffff81118015612fad57600080fd5b50604051908082528060200260200182016040528015612fdc5781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613130578083838151811061308757fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508180600101925050613046565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6131ac8a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050896151d7565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146131ea576131e9846156d7565b5b6132388787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050615706565b60008211156132525761325082600060018685615941565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b6000805a9050613376878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050865a615b47565b61337f57600080fd5b60005a8203905080604051602001808281526020019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561340c5780820151818401526020810190506133f1565b50505050905090810190601f1680156134395780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561346257600080fd5b506040519080825280602002602001820160405280156134915781602001602082028036833780820191505090505b509150600080600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156135645750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561356f57508482105b1561362a578084838151811061358157fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081806001019250506134fa565b80925081845250509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561373b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b60006137ed8c8c8c8c8c8c8c8c8c8c8c614296565b8051906020012090509b9a5050505050505050505050565b61380d614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156138775750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b6138e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146139e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613b9e614989565b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181557f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa282604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613c22614989565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613c8c5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015613cc457503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613d36576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613e37576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614158015613ea15750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b613f13576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614013576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a17f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561432757fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b6143b361449f565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b614446614989565b61444f816156d7565b7f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6144cd6125e4565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b614525614989565b8060016003540310156145a0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561460a5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b61467c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461477c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1806004541461494b5761494a81612b63565b5b505050565b6040518060400160405280600581526020017f312e332e3000000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614a2a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614a3f5760009050614a60565b6000828402905082848281614a5057fe5b0414614a5b57600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614aaa57600080fd5b8091505092915050565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614158015614b7f5750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b614bf1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b614bfe858585855a615b47565b90508015614c4e573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a2614c92565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b6000806000614cb48e8e8e8e8e8e8e8e8e8e600554614296565b905060056000815480929190600101919050555080805190602001209150614cdd828286612f01565b506000614ce8615b93565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614614ece578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115614d8b57fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015614e5d578082015181840152602081019050614e42565b50505050905090810190601f168015614e8a5780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015614eb557600080fd5b505af1158015614ec9573d6000803e3d6000fd5b505050505b6101f4614ef56109c48b01603f60408d0281614ee657fe5b04615bc490919063ffffffff16565b015a1015614f6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a9050614fd48f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d14614fc9578e614fcf565b6109c45a035b615b47565b9350614fe95a82615bde90919063ffffffff16565b90508380614ff8575060008a14155b80615004575060008814155b615076576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808911156150905761508d828b8b8b8b615941565b90505b84156150da577f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e8482604051808381526020018281526020019250505060405180910390a161511a565b7f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d238482604051808381526020018281526020019250505060405180910390a15b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146151c6578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b1580156151ad57600080fd5b505af11580156151c1573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b60006004541461524f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b81518111156152c6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600181101561533d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b835181101561564357600084828151811061535d57fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156153d15750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561540957503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561544157508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b6154b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146155b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550809250508080600101915050615346565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614615808576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161461593d576158ca8260008360015a615b47565b61593c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161461597e5782615980565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161415615a98576159ea3a86106159c7573a6159c9565b855b6159dc888a614a9590919063ffffffff16565b614a2c90919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050615a93576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615b3d565b615abd85615aaf888a614a9590919063ffffffff16565b614a2c90919063ffffffff16565b9150615aca848284615bfe565b615b3c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600180811115615b5557fe5b836001811115615b6157fe5b1415615b7a576000808551602087018986f49050615b8a565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b600081831015615bd45781615bd6565b825b905092915050565b600082821115615bed57600080fd5b600082840390508091505092915050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d60008114615ca55760208114615cad5760009350615cb8565b819350615cb8565b600051158215171593505b505050939250505056fea2646970667358221220047fac33099ca576d1c4f1ac6a8abdb0396e42ad6a397d2cb2f4dc1624cc0c5b64736f6c63430007060033", "balance": "0x0", diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/quote-output.bin b/crates/builder/op-rbuilder/src/tests/framework/artifacts/quote-output.bin index 84b2c09b7eddca45fc903018179882d6a9ba4ac8..70a42bbf551977f685c409f3e180f7414160aa2e 100644 GIT binary patch delta 72 zcmcc0a+PI+D3e3hN6$~`51;A&ahBJ8cJJMBC3m+kM=!ZCwtZK&nN(N1>CSQH15X*Y V1)pkCx32zmgC(|gNdW^A000Yv9+Ut8 delta 31 ncmcc0a+PI+D3eIR%*ukJ5<+*Z7Ez?F;E8pvXKiS diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-quote.bin b/crates/builder/op-rbuilder/src/tests/framework/artifacts/test-quote.bin index 057f9ed2d79147c67882abdb43fc9a927869e19d..aea0077d4f1f781b044dc52df472195762f46c16 100644 GIT binary patch delta 146 zcmeBE?^EAk!Bp?KR{NNhLgdeF%E7kn|7Ld7R(iM}z2wH&_FdU#QeEw)JI9$1JZ0Dx ze5y&^y871*me|%M1q?{wi~s|}P5zUbf0|!SQdr~PBvU?p!>kv6=8m&V3hkzwC-JCE u-ty!o1K*Uu13J$`+&0bJe_3|kMM-u|^<-O}iwdl7_srfDuz3&DB>@14MLk;p delta 105 zcmV-v0G9ubCypnuI06)KnR#%<7!uq%0StWswGIsz+, pool_observer: TransactionPoolObserver, + attestation_server: Option, } impl LocalInstance { @@ -92,6 +100,15 @@ impl LocalInstance { args.builder_signer = Some(signer); args.rollup_args.enable_tx_conditional = true; + let attestation_server = if args.flashtestations.flashtestations_enabled { + let server = spawn_attestation_provider().await?; + args.flashtestations.quote_provider = Some(server.url()); + tracing::info!("Started attestation server at {}", server.url()); + Some(server) + } else { + None + }; + 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(); @@ -168,6 +185,7 @@ impl LocalInstance { _node_handle: node_handle, task_manager: Some(task_manager), pool_observer: TransactionPoolObserver::new(pool_monitor, reverted_cache_clone), + attestation_server, }) } @@ -244,6 +262,10 @@ impl LocalInstance { &self.pool_observer } + pub const fn attestation_server(&self) -> &Option { + &self.attestation_server + } + pub async fn driver(&self) -> eyre::Result> { ChainDriver::::local(self).await } @@ -349,6 +371,13 @@ fn pool_component(args: &OpRbuilderArgs) -> OpPoolBuilder { ) } +async fn spawn_attestation_provider() -> eyre::Result { + let quote = include_bytes!("./artifacts/test-quote.bin"); + let mut service = AttestationServer::new(TEE_DEBUG_ADDRESS, Bytes::new(), quote.into()); + service.start().await?; + Ok(service) +} + /// A utility for listening to flashblocks WebSocket messages during tests. /// /// This provides a reusable way to capture and inspect flashblocks that are produced @@ -443,3 +472,119 @@ impl FlashblocksListener { self.handle.await? } } + +/// A utility service to spawn a server that returns a mock quote for an attestation request +pub struct AttestationServer { + tee_address: Address, + extra_registration_data: Bytes, + mock_attestation: Bytes, + server_handle: Option>, + shutdown_tx: Option>, + port: u16, + error_on_request: bool, +} + +impl AttestationServer { + pub fn new( + tee_address: Address, + extra_registration_data: Bytes, + mock_attestation: Bytes, + ) -> Self { + AttestationServer { + tee_address, + extra_registration_data, + mock_attestation, + server_handle: None, + shutdown_tx: None, + port: 0, + error_on_request: false, + } + } + + pub fn set_error(&mut self, error: bool) { + self.error_on_request = error; + } + + pub async fn start(&mut self) -> eyre::Result { + self.port = get_available_port(); + let addr = SocketAddr::from(([127, 0, 0, 1], self.port)); + let listener = TcpListener::bind(addr).await?; + + let mock_attestation = self.mock_attestation.clone(); + // Concatenate tee_address bytes and extra_registration_data bytes, then hex encode + let combined = [ + self.tee_address.as_slice(), // 20 bytes address + keccak256(self.extra_registration_data.clone()).as_slice(), // 32 byte hash + &[0u8; 12], // padding to 64 bytes + ] + .concat(); + let set_error = self.error_on_request; + + let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>(); + self.shutdown_tx = Some(shutdown_tx); + + // Create the service + self.server_handle = Some(tokio::spawn(async move { + loop { + let mock_attestation = mock_attestation.clone(); + let expected_path = format!("/{}", hex::encode(&combined)); + tokio::select! { + // Handle shutdown signal + _ = &mut shutdown_rx => { + break; + } + result = listener.accept() => { + let (stream, _) = result.expect("failed to accept attestation request"); + + tokio::task::spawn(async move { + let service = service_fn(move |req: Request| { + let response = + if set_error { + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Full::new(HyperBytes::new())) + .unwrap() + } + else if req.uri().path() == expected_path { + Response::builder() + .header("content-type", "application/octet-stream") + .body(Full::new(mock_attestation.clone().into())) + .unwrap() + } else { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(HyperBytes::new())) + .unwrap() + }; + async { Ok::<_, hyper::Error>(response) } + }); + + let io = TokioIo::new(stream); + if let Err(err) = http1::Builder::new().serve_connection(io, service).await { + tracing::error!(message = "Error serving attestations", error = %err); + } + }); + } + } + } + })); + + // Give the spawned task a chance to start + tokio::task::yield_now().await; + + Ok(self.port) + } + + pub fn url(&self) -> String { + format!("http://127.0.0.1:{}", self.port) + } +} + +impl Drop for AttestationServer { + fn drop(&mut self) { + if let Some(tx) = self.shutdown_tx.take() { + let _ = tx.send(()); + } + tracing::info!("AttestationServer dropped, terminating server"); + } +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs index 4e3171fb..26e24f0d 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/mod.rs @@ -6,6 +6,7 @@ mod instance; mod txs; mod utils; +use alloy_primitives::{B256, b256}; pub use apis::*; pub use contracts::*; pub use driver::*; @@ -14,11 +15,18 @@ pub use instance::*; pub use txs::*; pub use utils::*; +// anvil default key[1] pub const BUILDER_PRIVATE_KEY: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; - +// anvil default key[0] pub const FUNDED_PRIVATE_KEY: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; +// anvil default key[8] +pub const FLASHBLOCKS_DEPLOY_KEY: &str = + "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97"; +// anvil default key[9] +pub const FLASHTESTATION_DEPLOY_KEY: &str = + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"; pub const DEFAULT_GAS_LIMIT: u64 = 10_000_000; @@ -27,6 +35,19 @@ pub const DEFAULT_JWT_TOKEN: &str = pub const ONE_ETH: u128 = 1_000_000_000_000_000_000; +// flashtestations constants +pub const TEE_DEBUG_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("6Af149F267e1e62dFc431F2de6deeEC7224746f4"); + +pub const WORKLOAD_ID: B256 = + b256!("f724e7d117f5655cf33beefdfc7d31e930278fcb65cf6d1de632595e97ca82b2"); + +pub const SOURCE_LOCATORS: &[&str] = &[ + "https://github.com/flashbots/flashbots-images/commit/53d431f58a0d1a76f6711518ef8d876ce8181fc2", +]; + +pub const COMMIT_HASH: &str = "53d431f58a0d1a76f6711518ef8d876ce8181fc2"; + /// This gets invoked before any tests, when the cargo test framework loads the test library. /// It injects itself into #[ctor::ctor] diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs index 764b3fa3..35a5f2a5 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -1,6 +1,7 @@ use crate::{ tests::{ - BUILDER_PRIVATE_KEY, Protocol, block_builder_policy::BlockBuilderPolicy, + BUILDER_PRIVATE_KEY, COMMIT_HASH, FLASHBLOCKS_DEPLOY_KEY, FLASHTESTATION_DEPLOY_KEY, + Protocol, SOURCE_LOCATORS, WORKLOAD_ID, block_builder_policy::BlockBuilderPolicy, flashblocks_number_contract::FlashblocksNumber, flashtestation_registry::FlashtestationRegistry, framework::driver::ChainDriver, mock_dcap_attestation::MockAutomataDcapAttestationFee, @@ -37,6 +38,7 @@ pub trait TransactionBuilderExt { fn init_flashtestation_registry_contract(self, dcap_address: Address) -> Self; fn deploy_builder_policy_contract(self) -> Self; fn init_builder_policy_contract(self, registry_address: Address) -> Self; + fn add_workload_to_policy(self) -> Self; fn deploy_mock_dcap_contract(self) -> Self; fn add_mock_quote(self) -> Self; } @@ -62,11 +64,12 @@ impl TransactionBuilderExt for TransactionBuilder { self.with_create() .with_input(FlashblocksNumber::BYTECODE.clone()) .with_gas_limit(2_000_000) // deployment costs ~1.6 million gas + .with_signer(flashblocks_number_signer()) } fn init_flashblock_number_contract(self, register_builder: bool) -> Self { let builder_signer = builder_signer(); - let owner = funded_signer(); + let owner = flashblocks_number_signer(); let init_data = FlashblocksNumber::initializeCall { _owner: owner.address, @@ -79,16 +82,18 @@ impl TransactionBuilderExt for TransactionBuilder { .abi_encode(); self.with_input(init_data.into()) + .with_signer(flashblocks_number_signer()) } fn deploy_flashtestation_registry_contract(self) -> Self { self.with_create() .with_input(FlashtestationRegistry::BYTECODE.clone()) - .with_gas_limit(1_000_000) + .with_gas_limit(5_000_000) + .with_signer(flashtestations_signer()) } fn init_flashtestation_registry_contract(self, dcap_address: Address) -> Self { - let owner = funded_signer(); + let owner = flashtestations_signer(); let init_data = FlashtestationRegistry::initializeCall { owner: owner.address, @@ -96,17 +101,18 @@ impl TransactionBuilderExt for TransactionBuilder { } .abi_encode(); - self.with_input(init_data.into()) + self.with_input(init_data.into()).with_signer(owner) } fn deploy_builder_policy_contract(self) -> Self { self.with_create() .with_input(BlockBuilderPolicy::BYTECODE.clone()) - .with_gas_limit(1_000_000) + .with_gas_limit(3_000_000) + .with_signer(flashtestations_signer()) } fn init_builder_policy_contract(self, registry_address: Address) -> Self { - let owner = funded_signer(); + let owner = flashtestations_signer(); let init_data = BlockBuilderPolicy::initializeCall { _initialOwner: owner.address, @@ -115,12 +121,29 @@ impl TransactionBuilderExt for TransactionBuilder { .abi_encode(); self.with_input(init_data.into()) + .with_signer(flashtestations_signer()) + } + + fn add_workload_to_policy(self) -> Self { + let workload = BlockBuilderPolicy::addWorkloadToPolicyCall { + workloadId: WORKLOAD_ID, + commitHash: COMMIT_HASH.to_string(), + sourceLocators: SOURCE_LOCATORS + .iter() + .map(|source| source.to_string()) + .collect(), + } + .abi_encode(); + + self.with_input(workload.into()) + .with_signer(flashtestations_signer()) } fn deploy_mock_dcap_contract(self) -> Self { self.with_create() .with_input(MockAutomataDcapAttestationFee::BYTECODE.clone()) .with_gas_limit(1_000_000) + .with_signer(flashtestations_signer()) } fn add_mock_quote(self) -> Self { @@ -133,7 +156,9 @@ impl TransactionBuilderExt for TransactionBuilder { _output: include_bytes!("./artifacts/quote-output.bin").into(), } .abi_encode(); - self.with_input(quote.into()).with_gas_limit(500_000) + self.with_input(quote.into()) + .with_gas_limit(500_000) + .with_signer(flashtestations_signer()) } } @@ -163,7 +188,7 @@ pub trait ChainDriverExt { &self, ) -> impl Future)>>; - fn build_new_block_with_reverrting_transaction( + fn build_new_block_with_reverting_transaction( &self, ) -> impl Future)>>; } @@ -226,7 +251,7 @@ impl ChainDriverExt for ChainDriver

{ Ok((*tx.tx_hash(), self.build_new_block().await?)) } - async fn build_new_block_with_reverrting_transaction( + async fn build_new_block_with_reverting_transaction( &self, ) -> eyre::Result<(TxHash, Block)> { let tx = self @@ -337,3 +362,21 @@ pub fn funded_signer() -> Signer { ) .expect("Failed to create signer from hardcoded funded private key") } + +pub fn flashblocks_number_signer() -> Signer { + Signer::try_from_secret( + FLASHBLOCKS_DEPLOY_KEY + .parse() + .expect("invalid hardcoded flashblocks number deployer private key"), + ) + .expect("Failed to create signer from hardcoded flashblocks number deployer private key") +} + +pub fn flashtestations_signer() -> Signer { + Signer::try_from_secret( + FLASHTESTATION_DEPLOY_KEY + .parse() + .expect("invalid hardcoded flashtestations deployer private key"), + ) + .expect("Failed to create signer from hardcoded flashtestations deployer private key") +} diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs index f32eccb6..cb5646f8 100644 --- a/crates/builder/op-rbuilder/src/tests/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -5,6 +5,9 @@ pub use framework::*; #[cfg(test)] mod flashblocks; +#[cfg(test)] +mod flashtestations; + #[cfg(test)] mod data_availability; @@ -22,3 +25,17 @@ mod smoke; #[cfg(test)] mod txpool; + +// If the order of deployment from the signer changes the address will change +#[cfg(test)] +const FLASHBLOCKS_NUMBER_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("95bd8d42f30351685e96c62eddc0d0613bf9a87a"); +#[cfg(test)] +const MOCK_DCAP_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("700b6a60ce7eaaea56f065753d8dcb9653dbad35"); +#[cfg(test)] +const FLASHTESTATION_REGISTRY_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("b19b36b1456e65e3a6d514d3f715f204bd59f431"); +#[cfg(test)] +const BLOCK_BUILDER_POLICY_ADDRESS: alloy_primitives::Address = + alloy_primitives::address!("e1aa25618fa0c7a1cfdab5d6b456af611873b629"); From 3422c78ab0fe3d8ff3a91593d23c2d00e6b3f6bf Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:14:55 -0400 Subject: [PATCH 197/262] chore: add unused_async lint, deny unreachable_pub (#299) --- crates/builder/op-rbuilder/src/bin/tester/main.rs | 2 +- crates/builder/op-rbuilder/src/tests/framework/apis.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/bin/tester/main.rs b/crates/builder/op-rbuilder/src/bin/tester/main.rs index 9345a8c0..52c4812e 100644 --- a/crates/builder/op-rbuilder/src/bin/tester/main.rs +++ b/crates/builder/op-rbuilder/src/bin/tester/main.rs @@ -50,7 +50,7 @@ async fn main() -> eyre::Result<()> { let cli = Cli::parse(); match cli.command { - Commands::Genesis { output } => generate_genesis(output).await, + Commands::Genesis { output } => generate_genesis(output), Commands::Run { validation, .. } => run_system(validation).await, Commands::Deposit { address, amount } => { let engine_api = EngineApi::with_http("http://localhost:4444"); diff --git a/crates/builder/op-rbuilder/src/tests/framework/apis.rs b/crates/builder/op-rbuilder/src/tests/framework/apis.rs index 6ad0b98a..5638293b 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/apis.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/apis.rs @@ -205,7 +205,7 @@ pub trait BlockApi { ) -> RpcResult>; } -pub async fn generate_genesis(output: Option) -> eyre::Result<()> { +pub fn generate_genesis(output: Option) -> eyre::Result<()> { // Read the template file let template = include_str!("artifacts/genesis.json.tmpl"); From 14cb6b73b73b01ada5fa53cba36f09ca81fc3511 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:57:07 -0400 Subject: [PATCH 198/262] refactor: clean up flashblocks context in payload builder (#297) --- .../op-rbuilder/src/builders/context.rs | 10 +- .../src/builders/flashblocks/payload.rs | 315 ++++++++++-------- 2 files changed, 178 insertions(+), 147 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index a18facf5..032e97e6 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -74,6 +74,14 @@ pub struct OpPayloadBuilderCtx { } impl OpPayloadBuilderCtx { + pub(super) fn with_cancel(self, cancel: CancellationToken) -> Self { + Self { cancel, ..self } + } + + pub(super) fn with_extra_ctx(self, extra_ctx: ExtraCtx) -> Self { + Self { extra_ctx, ..self } + } + /// Returns the parent block the payload will be build on. pub fn parent(&self) -> &SealedHeader { &self.config.parent_header @@ -340,7 +348,7 @@ impl OpPayloadBuilderCtx { let tx_da_limit = self.da_config.max_da_tx_size(); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); - info!( + debug!( target: "payload_builder", message = "Executing best transactions", block_da_limit = ?block_da_limit, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 6a46e4c7..8acb810b 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -18,6 +18,7 @@ use alloy_consensus::{ use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_primitives::{Address, B256, U256, map::foldhash::HashMap}; use core::time::Duration; +use eyre::WrapErr as _; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::BuildOutcome; use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; @@ -68,12 +69,12 @@ type NextBestFlashblocksTxs = BestFlashblocksTxs< >; #[derive(Debug, Default)] -struct ExtraExecutionInfo { +pub(super) struct ExtraExecutionInfo { /// Index of the last consumed flashblock - pub last_flashblock_index: usize, + last_flashblock_index: usize, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct FlashblocksExtraCtx { /// Current flashblock index flashblock_index: u64, @@ -91,6 +92,17 @@ pub struct FlashblocksExtraCtx { calculate_state_root: bool, } +impl FlashblocksExtraCtx { + fn next(self, target_gas_for_batch: u64, target_da_for_batch: Option) -> Self { + Self { + flashblock_index: self.flashblock_index + 1, + target_gas_for_batch, + target_da_for_batch, + ..self + } + } +} + impl OpPayloadBuilderCtx { /// Returns the current flashblock index pub(crate) fn flashblock_index(&self) -> u64 { @@ -102,18 +114,6 @@ impl OpPayloadBuilderCtx { self.extra_ctx.target_flashblock_count } - /// Increments the flashblock index - pub(crate) fn increment_flashblock_index(&mut self) -> u64 { - self.extra_ctx.flashblock_index += 1; - self.extra_ctx.flashblock_index - } - - /// Sets the target flashblock count - pub(crate) fn set_target_flashblock_count(&mut self, target_flashblock_count: u64) -> u64 { - self.extra_ctx.target_flashblock_count = target_flashblock_count; - self.extra_ctx.target_flashblock_count - } - /// Returns if the flashblock is the first fallback block pub(crate) fn is_first_flashblock(&self) -> bool { self.flashblock_index() == 0 @@ -213,43 +213,16 @@ where Client: ClientBounds, BuilderTx: BuilderTransactions + Send + Sync, { - /// 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. - async fn build_payload( + fn get_op_payload_builder_ctx( &self, - args: BuildArguments, OpBuiltPayload>, - best_payload: BlockCell, - ) -> Result<(), PayloadBuilderError> { - let block_build_start_time = Instant::now(); - let BuildArguments { - mut cached_reads, - config, - cancel: block_cancel, - } = args; - - // We log only every 100th block to reduce usage - let span = if cfg!(feature = "telemetry") - && config.parent_header.number % self.config.sampling_ratio == 0 - { - span!(Level::INFO, "build_payload") - } else { - tracing::Span::none() - }; - let _entered = span.enter(); - span.record( - "payload_id", - config.attributes.payload_attributes.id.to_string(), - ); - + config: reth_basic_payload_builder::PayloadConfig< + OpPayloadBuilderAttributes, + >, + cancel: CancellationToken, + extra_ctx: FlashblocksExtraCtx, + ) -> eyre::Result> { let chain_spec = self.client.chain_spec(); let timestamp = config.attributes.timestamp(); - let calculate_state_root = self.config.specific.calculate_state_root; let block_env_attributes = OpNextBlockEnvAttributes { timestamp, suggested_fee_recipient: config.attributes.suggested_fee_recipient(), @@ -266,7 +239,7 @@ where config .attributes .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) - .map_err(PayloadBuilderError::other)? + .wrap_err("failed to get holocene extra data for flashblocks payload builder")? } else { Default::default() }, @@ -275,35 +248,74 @@ where let evm_env = self .evm_config .next_evm_env(&config.parent_header, &block_env_attributes) - .map_err(PayloadBuilderError::other)?; + .wrap_err("failed to create next evm env")?; - let mut ctx = OpPayloadBuilderCtx:: { + Ok(OpPayloadBuilderCtx:: { evm_config: self.evm_config.clone(), - chain_spec: self.client.chain_spec(), + chain_spec, config, evm_env, block_env_attributes, - // Here we use parent token because child token handing is only for proper flashblocks - cancel: block_cancel.clone(), + cancel, da_config: self.config.da_config.clone(), builder_signer: self.config.builder_signer, metrics: Default::default(), - extra_ctx: FlashblocksExtraCtx { - flashblock_index: 0, - target_flashblock_count: self.config.flashblocks_per_block(), - target_gas_for_batch: 0, - target_da_for_batch: None, - gas_per_batch: 0, - da_per_batch: None, - calculate_state_root, - }, + extra_ctx, max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.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. + async fn build_payload( + &self, + args: BuildArguments, OpBuiltPayload>, + best_payload: BlockCell, + ) -> Result<(), PayloadBuilderError> { + let block_build_start_time = Instant::now(); + let BuildArguments { + mut cached_reads, + config, + cancel: block_cancel, + } = args; + + // We log only every 100th block to reduce usage + let span = if cfg!(feature = "telemetry") + && config.parent_header.number % self.config.sampling_ratio == 0 + { + span!(Level::INFO, "build_payload") + } else { + tracing::Span::none() }; + let _entered = span.enter(); + span.record( + "payload_id", + config.attributes.payload_attributes.id.to_string(), + ); + + let timestamp = config.attributes.timestamp(); + let calculate_state_root = self.config.specific.calculate_state_root; + let ctx = self + .get_op_payload_builder_ctx( + config.clone(), + block_cancel.clone(), + FlashblocksExtraCtx { + target_flashblock_count: self.config.flashblocks_per_block(), + calculate_state_root, + ..Default::default() + }, + ) + .map_err(|e| PayloadBuilderError::Other(e.into()))?; let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; let db = StateProviderDatabase::new(&state_provider); - self.address_gas_limiter.refresh(ctx.block_number()); // 1. execute the pre steps and seal an early block with that @@ -348,8 +360,15 @@ where calculate_state_root || ctx.attributes().no_tx_pool, // need to calculate state root for CL sync )?; - best_payload.set(payload.clone()); - self.send_payload_to_engine(payload); + self.send_payload_to_engine(payload.clone()); + best_payload.set(payload); + + info!( + target: "payload_builder", + message = "Fallback block built", + payload_id = fb_payload.payload_id.to_string(), + ); + // not emitting flashblock if no_tx_pool in FCU, it's just syncing if !ctx.attributes().no_tx_pool { let flashblock_byte_size = self @@ -361,12 +380,6 @@ where .record(flashblock_byte_size as f64); } - info!( - target: "payload_builder", - message = "Fallback block built", - payload_id = fb_payload.payload_id.to_string(), - ); - if ctx.attributes().no_tx_pool { info!( target: "payload_builder", @@ -393,11 +406,10 @@ where // We adjust our flashblocks timings based on time_drift if dynamic adjustment enable let (flashblocks_per_block, first_flashblock_offset) = self.calculate_flashblocks(timestamp); - ctx.set_target_flashblock_count(flashblocks_per_block); info!( target: "payload_builder", message = "Performed flashblocks timing derivation", - flashblocks_per_block = ctx.target_flashblock_count(), + flashblocks_per_block, first_flashblock_offset = first_flashblock_offset.as_millis(), flashblocks_interval = self.config.specific.interval.as_millis(), ); @@ -409,12 +421,12 @@ where ctx.metrics .first_flashblock_time_offset .record(first_flashblock_offset.as_millis() as f64); - let gas_per_batch = ctx.block_gas_limit() / ctx.target_flashblock_count(); + let gas_per_batch = ctx.block_gas_limit() / flashblocks_per_block; let target_gas_for_batch = gas_per_batch; let da_per_batch = ctx .da_config .max_da_block_size() - .map(|da_limit| da_limit / ctx.target_flashblock_count()); + .map(|da_limit| da_limit / flashblocks_per_block); // Check that builder tx won't affect fb limit too much if let Some(da_limit) = da_per_batch { // We error if we can't insert any tx aside from builder tx in flashblock @@ -424,16 +436,26 @@ where ); } } - let mut total_da_per_batch = da_per_batch; + let mut target_da_for_batch = da_per_batch; // Account for already included builder tx - ctx.extra_ctx.target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); - if let Some(da_limit) = total_da_per_batch.as_mut() { + if let Some(da_limit) = target_da_for_batch.as_mut() { *da_limit = da_limit.saturating_sub(builder_tx_da_size); } - ctx.extra_ctx.target_da_for_batch = total_da_per_batch; - ctx.extra_ctx.gas_per_batch = gas_per_batch; - ctx.extra_ctx.da_per_batch = da_per_batch; + let extra_ctx = FlashblocksExtraCtx { + flashblock_index: 1, + target_flashblock_count: flashblocks_per_block, + target_gas_for_batch: target_gas_for_batch.saturating_sub(builder_tx_gas), + target_da_for_batch, + gas_per_batch, + da_per_batch, + calculate_state_root, + }; + + let mut fb_cancel = block_cancel.child_token(); + let mut ctx = self + .get_op_payload_builder_ctx(config, fb_cancel.clone(), extra_ctx) + .map_err(|e| PayloadBuilderError::Other(e.into()))?; // Create best_transaction iterator let mut best_txs = BestFlashblocksTxs::new(BestPayloadTransactions::new( @@ -442,8 +464,6 @@ where )); let interval = self.config.specific.interval; let (tx, mut rx) = mpsc::channel((self.config.flashblocks_per_block() + 1) as usize); - let mut fb_cancel = block_cancel.child_token(); - ctx.cancel = fb_cancel.clone(); tokio::spawn({ let block_cancel = block_cancel.clone(); @@ -493,9 +513,20 @@ where }; let _entered = fb_span.enter(); + if ctx.flashblock_index() > ctx.target_flashblock_count() { + self.record_flashblocks_metrics( + &ctx, + &info, + flashblocks_per_block, + &span, + "Payload building complete, target flashblock count reached", + ); + return Ok(()); + } + // build first flashblock immediately - match self.build_next_flashblock( - &mut ctx, + let next_flashblocks_ctx = match self.build_next_flashblock( + &ctx, &mut info, &mut state, &state_provider, @@ -504,22 +535,32 @@ where &best_payload, &fb_span, ) { - Ok(()) => {} + Ok(Some(next_flashblocks_ctx)) => next_flashblocks_ctx, + Ok(None) => { + self.record_flashblocks_metrics( + &ctx, + &info, + flashblocks_per_block, + &span, + "Payload building complete, job cancelled or target flashblock count reached", + ); + return Ok(()); + } Err(err) => { error!( target: "payload_builder", - "Failed to build flashblock {}, flashblock {}: {}", - ctx.block_number(), + "Failed to build flashblock {} for block number {}: {}", ctx.flashblock_index(), + ctx.block_number(), err ); - return Err(err); + return Err(PayloadBuilderError::Other(err.into())); } - } + }; tokio::select! { Some(fb_cancel) = rx.recv() => { - ctx.cancel = fb_cancel; + ctx = ctx.with_cancel(fb_cancel).with_extra_ctx(next_flashblocks_ctx); }, _ = block_cancel.cancelled() => { self.record_flashblocks_metrics( @@ -541,7 +582,7 @@ where P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, >( &self, - ctx: &mut OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, info: &mut ExecutionInfo, state: &mut State, state_provider: impl reth::providers::StateProvider + Clone, @@ -549,33 +590,18 @@ where block_cancel: &CancellationToken, best_payload: &BlockCell, span: &tracing::Span, - ) -> Result<(), PayloadBuilderError> { - // fallback block is index 0, so we need to increment here - ctx.increment_flashblock_index(); - - // TODO: remove this - if ctx.flashblock_index() > ctx.target_flashblock_count() { - info!( - target: "payload_builder", - target = ctx.target_flashblock_count(), - flashblock_index = ctx.flashblock_index(), - block_number = ctx.block_number(), - "Skipping flashblock reached target", - ); - return Ok(()); - }; - - // Continue with flashblock building + ) -> 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_per_batch = ctx.extra_ctx.target_da_for_batch; + let mut target_da_for_batch = ctx.extra_ctx.target_da_for_batch; info!( target: "payload_builder", block_number = ctx.block_number(), - flashblock_index = ctx.flashblock_index(), + flashblock_index, target_gas = target_gas_for_batch, gas_used = info.cumulative_gas_used, - target_da = target_da_per_batch, + target_da = target_da_for_batch, da_used = info.cumulative_da_bytes_used, block_gas_used = ctx.block_gas_limit(), "Building flashblock", @@ -599,7 +625,7 @@ where target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size - if let Some(da_limit) = target_da_per_batch.as_mut() { + if let Some(da_limit) = target_da_for_batch.as_mut() { *da_limit = da_limit.saturating_sub(builder_tx_da_size); } @@ -609,7 +635,7 @@ where self.pool .best_transactions_with_attributes(ctx.best_transaction_attributes()), ), - ctx.flashblock_index(), + flashblock_index, ); let transaction_pool_fetch_time = best_txs_start_time.elapsed(); ctx.metrics @@ -625,8 +651,9 @@ where state, best_txs, target_gas_for_batch.min(ctx.block_gas_limit()), - target_da_per_batch, - )?; + target_da_for_batch, + ) + .wrap_err("failed to execute best transactions")?; // Extract last transactions let new_transactions = info.executed_transactions[info.extra.last_flashblock_index..] .to_vec() @@ -645,7 +672,7 @@ where span, "Payload building complete, channel closed or job cancelled", ); - return Ok(()); + return Ok(None); } let payload_tx_simulation_time = tx_execution_start_time.elapsed(); @@ -656,15 +683,11 @@ where .payload_tx_simulation_gauge .set(payload_tx_simulation_time); - match self + if let Err(e) = self .builder_tx .add_builder_txs(&state_provider, info, ctx, state, false) { - Ok(builder_txs) => builder_txs, - Err(e) => { - error!(target: "payload_builder", "Error simulating builder txs: {}", e); - vec![] - } + error!(target: "payload_builder", "Error simulating builder txs: {}", e); }; let total_block_built_duration = Instant::now(); @@ -682,17 +705,13 @@ where .total_block_built_gauge .set(total_block_built_duration); - // Handle build errors with match pattern match build_result { Err(err) => { - // Track invalid/bad block ctx.metrics.invalid_blocks_count.increment(1); - error!(target: "payload_builder", "Failed to build block {}, flashblock {}: {}", ctx.block_number(), ctx.flashblock_index(), err); - // Return the error - return Err(err); + Err(err).wrap_err("failed to build payload") } Ok((new_payload, mut fb_payload)) => { - fb_payload.index = ctx.flashblock_index(); + fb_payload.index = flashblock_index; fb_payload.base = None; // If main token got canceled in here that means we received get_payload and we should drop everything and now update best_payload @@ -705,12 +724,14 @@ where span, "Payload building complete, channel closed or job cancelled", ); - return Ok(()); + return Ok(None); } let flashblock_byte_size = self .ws_pub .publish(&fb_payload) - .map_err(PayloadBuilderError::other)?; + .wrap_err("failed to publish flashblock via websocket")?; + self.send_payload_to_engine(new_payload.clone()); + best_payload.set(new_payload); // Record flashblock build duration ctx.metrics @@ -723,11 +744,9 @@ where .flashblock_num_tx_histogram .record(info.executed_transactions.len() as f64); - best_payload.set(new_payload.clone()); - self.send_payload_to_engine(new_payload); // Update bundle_state for next iteration if let Some(da_limit) = ctx.extra_ctx.da_per_batch { - if let Some(da) = target_da_per_batch.as_mut() { + if let Some(da) = target_da_for_batch.as_mut() { *da += da_limit; } else { error!( @@ -736,20 +755,25 @@ where } } - ctx.extra_ctx.target_gas_for_batch += ctx.extra_ctx.gas_per_batch; - ctx.extra_ctx.target_da_for_batch = target_da_per_batch; + let target_gas_for_batch = + ctx.extra_ctx.target_gas_for_batch + ctx.extra_ctx.gas_per_batch; + let next_extra_ctx = ctx + .extra_ctx + .clone() + .next(target_gas_for_batch, target_da_for_batch); info!( target: "payload_builder", message = "Flashblock built", - flashblock_index = ctx.flashblock_index(), + flashblock_index = flashblock_index, current_gas = info.cumulative_gas_used, current_da = info.cumulative_da_bytes_used, target_flashblocks = ctx.target_flashblock_count(), ); + + Ok(Some(next_extra_ctx)) } } - Ok(()) } /// Do some logging and metric recording when we stop build flashblocks @@ -780,7 +804,6 @@ where message = message, flashblocks_per_block = flashblocks_per_block, flashblock_index = ctx.flashblock_index(), - config_flashblocks_per_block = self.config.flashblocks_per_block(), ); span.record("flashblock_count", ctx.flashblock_index()); @@ -804,7 +827,6 @@ 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) { @@ -815,6 +837,7 @@ where self.config.specific.interval - self.config.specific.leeway_time, ); } + // We use this system time to determine remining time to build a block // Things to consider: // FCU(a) - FCU with attributes @@ -907,13 +930,13 @@ where .map_err(PayloadBuilderError::other)? .apply_pre_execution_changes()?; - // 3. execute sequencer transactions + // 2. execute sequencer transactions let info = ctx.execute_sequencer_transactions(state)?; Ok(info) } -fn build_block( +pub(super) fn build_block( state: &mut State, ctx: &OpPayloadBuilderCtx, info: &mut ExecutionInfo, @@ -960,7 +983,7 @@ where .expect("Number is in range"); // TODO: maybe recreate state with bundle in here - // // calculate the state root + // calculate the state root let state_root_start_time = Instant::now(); let mut state_root = B256::ZERO; let mut trie_output = TrieUpdates::default(); @@ -1068,7 +1091,7 @@ where }, trie: ExecutedTrieUpdates::Present(Arc::new(trie_output)), }; - info!(target: "payload_builder", message = "Executed block created"); + debug!(target: "payload_builder", message = "Executed block created"); let sealed_block = Arc::new(block.seal_slow()); debug!(target: "payload_builder", ?sealed_block, "sealed built block"); From 8107f8719ea67dedad2d2dfb56831a4ca94a212c Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 22 Oct 2025 12:20:13 -0700 Subject: [PATCH 199/262] Add permit flashtestations tx calls from builder (#285) * Add permit flashtestations tx calls from builder * move simumlation calls to builder tx * refactor contract simulation * refactor flashtestations builder tx * fix test comments * comments --- .../op-rbuilder/src/builders/builder_tx.rs | 304 +++++-- .../src/builders/flashblocks/builder_tx.rs | 147 ++-- .../src/builders/flashblocks/payload.rs | 21 +- .../src/builders/flashblocks/service.rs | 17 +- .../builder/op-rbuilder/src/builders/mod.rs | 4 +- .../src/builders/standard/builder_tx.rs | 33 +- .../src/builders/standard/service.rs | 6 +- .../op-rbuilder/src/flashtestations/args.rs | 8 + .../src/flashtestations/builder_tx.rs | 772 +++++++++--------- .../op-rbuilder/src/flashtestations/mod.rs | 55 +- .../src/flashtestations/service.rs | 27 +- .../op-rbuilder/src/tests/flashtestations.rs | 388 ++++++--- crates/builder/op-rbuilder/src/tests/mod.rs | 4 +- 13 files changed, 1103 insertions(+), 683 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index 15ecc0d8..89b21997 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -1,29 +1,48 @@ use alloy_consensus::TxEip1559; use alloy_eips::{Encodable2718, eip7623::TOTAL_COST_FLOOR_PER_TOKEN}; use alloy_evm::Database; +use alloy_op_evm::OpEvm; use alloy_primitives::{ - Address, B256, Log, TxKind, U256, - map::foldhash::{HashSet, HashSetExt}, + Address, B256, Bytes, TxKind, U256, + map::foldhash::{HashMap, HashSet, HashSetExt}, }; +use alloy_sol_types::{ContractError, Revert, SolCall, SolError, SolInterface}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; -use op_revm::OpTransactionError; -use reth_evm::{ConfigureEvm, Evm, eth::receipt_builder::ReceiptBuilderCtx}; +use op_alloy_rpc_types::OpTransactionRequest; +use op_revm::{OpHaltReason, OpTransactionError}; +use reth_evm::{ + ConfigureEvm, Evm, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, + precompiles::PrecompilesMap, +}; use reth_node_api::PayloadBuilderError; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::{ProviderError, StateProvider}; use reth_revm::{State, database::StateProviderDatabase}; +use reth_rpc_api::eth::{EthTxEnvError, transaction::TryIntoTxEnv}; use revm::{ - DatabaseCommit, - context::result::{EVMError, ResultAndState}, + DatabaseCommit, DatabaseRef, + context::{ + ContextTr, + result::{EVMError, ExecutionResult, ResultAndState}, + }, + inspector::NoOpInspector, + state::Account, }; -use tracing::warn; +use tracing::{trace, warn}; use crate::{ builders::context::OpPayloadBuilderCtx, primitives::reth::ExecutionInfo, tx_signer::Signer, }; +#[derive(Debug, Default)] +pub struct SimulationSuccessResult { + pub gas_used: u64, + pub output: T::Return, + pub state_changes: HashMap, +} + #[derive(Debug, Clone)] pub struct BuilderTransactionCtx { pub gas_used: u64, @@ -46,6 +65,14 @@ impl BuilderTransactionCtx { } } +#[derive(Debug, thiserror::Error)] +pub enum InvalidContractDataError { + #[error("did not find expected logs expected {0:?} but got {1:?}")] + InvalidLogs(Vec, Vec), + #[error("could not decode output from contract call")] + OutputAbiDecodeError, +} + /// Possible error variants during construction of builder txs. #[derive(Debug, thiserror::Error)] pub enum BuilderTransactionError { @@ -55,6 +82,15 @@ pub enum BuilderTransactionError { /// Signature signing fails #[error("failed to sign transaction: {0}")] SigningError(secp256k1::Error), + /// Invalid contract errors indicating the contract is incorrect + #[error("contract {0} may be incorrect, invalid contract data: {1}")] + InvalidContract(Address, InvalidContractDataError), + /// Transaction halted execution + #[error("transaction to {0} halted {1:?}")] + TransactionHalted(Address, OpHaltReason), + /// Transaction reverted + #[error("transaction to {0} reverted {1}")] + TransactionReverted(Address, Revert), /// Invalid tx errors during evm execution. #[error("invalid transaction error {0}")] InvalidTransactionError(Box), @@ -78,6 +114,12 @@ impl From> for BuilderTransactionErr } } +impl From for BuilderTransactionError { + fn from(error: EthTxEnvError) -> Self { + BuilderTransactionError::EvmExecutionError(Box::new(error)) + } +} + impl From for PayloadBuilderError { fn from(error: BuilderTransactionError) -> Self { match error { @@ -90,24 +132,46 @@ impl From for PayloadBuilderError { } impl BuilderTransactionError { - pub fn other(error: E) -> Self - where - E: core::error::Error + Send + Sync + 'static, - { + pub fn other(error: impl core::error::Error + Send + Sync + 'static) -> Self { BuilderTransactionError::Other(Box::new(error)) } + + pub fn msg(msg: impl core::fmt::Display) -> Self { + Self::Other(msg.to_string().into()) + } } -pub trait BuilderTransactions: Debug { - fn simulate_builder_txs( +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( &self, state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State, + db: &mut State, + top_of_block: bool, ) -> Result, BuilderTransactionError>; - fn add_builder_txs( + fn simulate_builder_txs_with_state_copy( + &self, + state_provider: impl StateProvider + Clone, + info: &mut ExecutionInfo, + ctx: &OpPayloadBuilderCtx, + db: &State, + top_of_block: bool, + ) -> Result, BuilderTransactionError> { + let mut simulation_state = self.new_simulation_state(state_provider.clone(), db); + self.simulate_builder_txs( + state_provider, + info, + ctx, + &mut simulation_state, + top_of_block, + ) + } + + fn add_builder_txs( &self, state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, @@ -116,14 +180,20 @@ pub trait BuilderTransactions: Debug { top_of_block: bool, ) -> Result, BuilderTransactionError> { { + let builder_txs = self.simulate_builder_txs_with_state_copy( + state_provider, + info, + builder_ctx, + db, + top_of_block, + )?; + let mut evm = builder_ctx .evm_config .evm_with_env(&mut *db, builder_ctx.evm_env.clone()); let mut invalid: HashSet

= HashSet::new(); - let builder_txs = - self.simulate_builder_txs(state_provider, info, builder_ctx, evm.db_mut())?; for builder_tx in builder_txs.iter() { if builder_tx.is_top_of_block != top_of_block { // don't commit tx if the buidler tx is not being added in the intended @@ -135,12 +205,29 @@ pub trait BuilderTransactions: Debug { continue; } - let ResultAndState { result, state } = evm - .transact(&builder_tx.signed_tx) - .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; + let ResultAndState { result, state } = match evm.transact(&builder_tx.signed_tx) { + Ok(res) => res, + Err(err) => { + if let Some(err) = err.as_invalid_tx_err() { + if err.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %err, ?builder_tx.signed_tx, "skipping nonce too low builder transaction"); + } else { + // if the transaction is invalid, we can skip it and all of its + // descendants + trace!(target: "payload_builder", %err, ?builder_tx.signed_tx, "skipping invalid builder transaction and its descendants"); + invalid.insert(builder_tx.signed_tx.signer()); + } + + continue; + } + // this is an error that we should treat as fatal for this attempt + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + }; if !result.is_success() { - warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), "builder tx reverted"); + warn!(target: "payload_builder", tx_hash = ?builder_tx.signed_tx.tx_hash(), result = ?result, "builder tx reverted"); invalid.insert(builder_tx.signed_tx.signer()); continue; } @@ -174,49 +261,152 @@ pub trait BuilderTransactions: Debug { } } - fn simulate_builder_txs_state( + // Creates a copy of the state to simulate against + fn new_simulation_state( &self, - state_provider: impl StateProvider + Clone, - builder_txs: Vec<&BuilderTransactionCtx>, - ctx: &OpPayloadBuilderCtx, - db: &mut State, - ) -> Result>, BuilderTransactionError> { - let state = StateProviderDatabase::new(state_provider.clone()); - let mut simulation_state = State::builder() + state_provider: impl StateProvider, + db: &State, + ) -> State> { + let state = StateProviderDatabase::new(state_provider); + + State::builder() .with_database(state) .with_cached_prestate(db.cache.clone()) .with_bundle_update() - .build(); - let mut evm = ctx - .evm_config - .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + .build() + } - for builder_tx in builder_txs { + fn sign_tx( + &self, + to: Address, + from: Signer, + gas_used: u64, + calldata: Bytes, + ctx: &OpPayloadBuilderCtx, + db: impl DatabaseRef, + ) -> Result, BuilderTransactionError> { + let nonce = get_nonce(db, from.address)?; + // Create the EIP-1559 transaction + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce, + // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer + gas_limit: gas_used * 64 / 63, + max_fee_per_gas: ctx.base_fee().into(), + to: TxKind::Call(to), + input: calldata, + ..Default::default() + }); + Ok(from.sign_tx(tx)?) + } + + fn commit_txs( + &self, + signed_txs: Vec>, + ctx: &OpPayloadBuilderCtx, + db: &mut State, + ) -> Result<(), BuilderTransactionError> { + let mut evm = ctx.evm_config.evm_with_env(&mut *db, ctx.evm_env.clone()); + for signed_tx in signed_txs { let ResultAndState { state, .. } = evm - .transact(&builder_tx.signed_tx) + .transact(&signed_tx) .map_err(|err| BuilderTransactionError::EvmExecutionError(Box::new(err)))?; - - evm.db_mut().commit(state); + evm.db_mut().commit(state) } + Ok(()) + } - Ok(simulation_state) + fn simulate_call( + &self, + tx: OpTransactionRequest, + expected_logs: Vec, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + let tx_env = tx.try_into_tx_env(evm.cfg(), evm.block())?; + let to = tx_env.base.kind.into_to().unwrap_or_default(); + + let ResultAndState { result, state } = match evm.transact(tx_env) { + Ok(res) => res, + Err(err) => { + if err.is_invalid_tx_err() { + return Err(BuilderTransactionError::InvalidTransactionError(Box::new( + err, + ))); + } else { + return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); + } + } + }; + + match result { + ExecutionResult::Success { + output, + gas_used, + logs, + .. + } => { + let topics: HashSet = logs + .into_iter() + .flat_map(|log| log.topics().to_vec()) + .collect(); + if !expected_logs + .iter() + .all(|expected_topic| topics.contains(expected_topic)) + { + return Err(BuilderTransactionError::InvalidContract( + to, + InvalidContractDataError::InvalidLogs( + expected_logs, + topics.into_iter().collect(), + ), + )); + } + let return_output = T::abi_decode_returns(&output.into_data()).map_err(|_| { + BuilderTransactionError::InvalidContract( + to, + InvalidContractDataError::OutputAbiDecodeError, + ) + })?; + Ok(SimulationSuccessResult:: { + gas_used, + output: return_output, + state_changes: state, + }) + } + ExecutionResult::Revert { output, .. } => { + let revert = ContractError::::abi_decode(&output) + .map(|reason| Revert::from(format!("{reason:?}"))) + .or_else(|_| Revert::abi_decode(&output)) + .unwrap_or_else(|_| { + Revert::from(format!("unknown revert: {}", hex::encode(&output))) + }); + Err(BuilderTransactionError::TransactionReverted(to, revert)) + } + ExecutionResult::Halt { reason, .. } => { + Err(BuilderTransactionError::TransactionHalted(to, reason)) + } + } } } #[derive(Debug, Clone)] -pub(super) struct BuilderTxBase { +pub(super) struct BuilderTxBase { pub signer: Option, + _marker: std::marker::PhantomData, } -impl BuilderTxBase { +impl BuilderTxBase { pub(super) fn new(signer: Option) -> Self { - Self { signer } + Self { + signer, + _marker: std::marker::PhantomData, + } } - pub(super) fn simulate_builder_tx( + pub(super) fn simulate_builder_tx( &self, ctx: &OpPayloadBuilderCtx, - db: &mut State, + db: impl DatabaseRef, ) -> Result, BuilderTransactionError> { match self.signer { Some(signer) => { @@ -258,18 +448,15 @@ impl BuilderTxBase { std::cmp::max(zero_cost + nonzero_cost + 21_000, floor_gas) } - fn signed_builder_tx( + fn signed_builder_tx( &self, ctx: &OpPayloadBuilderCtx, - db: &mut State, + db: impl DatabaseRef, signer: Signer, gas_used: u64, message: Vec, ) -> Result, BuilderTransactionError> { - let nonce = db - .load_cache_account(signer.address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) - .map_err(|_| BuilderTransactionError::AccountLoadFailed(signer.address))?; + let nonce = get_nonce(db, signer.address)?; // Create the EIP-1559 transaction let tx = OpTypedTransaction::Eip1559(TxEip1559 { @@ -292,24 +479,17 @@ impl BuilderTxBase { } } -pub fn get_nonce( - db: &mut State, - address: Address, -) -> Result { - db.load_cache_account(address) - .map(|acc| acc.account_info().unwrap_or_default().nonce) +pub fn get_nonce(db: impl DatabaseRef, address: Address) -> Result { + db.basic_ref(address) + .map(|acc| acc.unwrap_or_default().nonce) .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) } pub fn get_balance( - db: &mut State, + db: impl DatabaseRef, address: Address, ) -> Result { - db.load_cache_account(address) - .map(|acc| acc.account_info().unwrap_or_default().balance) + db.basic_ref(address) + .map(|acc| acc.unwrap_or_default().balance) .map_err(|_| BuilderTransactionError::AccountLoadFailed(address)) } - -pub fn log_exists(logs: &[Log], topic: &B256) -> bool { - logs.iter().any(|log| log.topics().first() == Some(topic)) -} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs index b1b16906..04afc64d 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -2,7 +2,7 @@ use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_evm::{Database, Evm}; use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, B256, TxKind}; +use alloy_primitives::{Address, TxKind}; use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, sol}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; @@ -11,8 +11,9 @@ use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap}; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::StateProvider; -use reth_revm::{State, database::StateProviderDatabase}; +use reth_revm::State; use revm::{ + DatabaseRef, context::result::{ExecutionResult, ResultAndState}, inspector::NoOpInspector, }; @@ -21,9 +22,10 @@ use tracing::warn; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - builder_tx::{BuilderTxBase, get_nonce, log_exists}, + InvalidContractDataError, + builder_tx::{BuilderTxBase, get_nonce}, context::OpPayloadBuilderCtx, - flashblocks::payload::FlashblocksExtraCtx, + flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, }, flashtestations::builder_tx::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, @@ -53,8 +55,6 @@ sol!( pub(super) enum FlashblockNumberError { #[error("flashblocks number contract tx reverted: {0:?}")] Revert(IFlashblockNumber::IFlashblockNumberErrors), - #[error("contract may be invalid, mismatch in log emitted: expected {0:?}")] - LogMismatch(B256), #[error("unknown revert: {0} err: {1}")] Unknown(String, Error), #[error("halt: {0:?}")] @@ -64,14 +64,17 @@ pub(super) enum FlashblockNumberError { // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct FlashblocksBuilderTx { - pub base_builder_tx: BuilderTxBase, - pub flashtestations_builder_tx: Option, + pub base_builder_tx: BuilderTxBase, + pub flashtestations_builder_tx: + Option>, } impl FlashblocksBuilderTx { pub(super) fn new( signer: Option, - flashtestations_builder_tx: Option, + flashtestations_builder_tx: Option< + FlashtestationsBuilderTx, + >, ) -> Self { let base_builder_tx = BuilderTxBase::new(signer); Self { @@ -81,40 +84,46 @@ impl FlashblocksBuilderTx { } } -impl BuilderTransactions for FlashblocksBuilderTx { - fn simulate_builder_txs( +impl BuilderTransactions for FlashblocksBuilderTx { + fn simulate_builder_txs( &self, state_provider: impl StateProvider + Clone, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State, + db: &mut State, + top_of_block: bool, ) -> 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, db)?; + let flashblocks_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; builder_txs.extend(flashblocks_builder_tx.clone()); } if ctx.is_last_flashblock() { - let base_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; + let base_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; builder_txs.extend(base_tx.clone()); if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { + // Commit state that is included to get the correct nonce + if let Some(builder_tx) = base_tx { + self.commit_txs(vec![builder_tx.signed_tx], ctx, &mut *db)?; + } // We only include flashtestations txs in the last flashblock - let mut simulation_state = self.simulate_builder_txs_state::( - state_provider.clone(), - base_tx.iter().collect(), - ctx, - db, - )?; - let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( + match flashtestations_builder_tx.simulate_builder_txs( state_provider, info, ctx, - &mut simulation_state, - )?; - builder_txs.extend(flashtestations_builder_txs); + db, + top_of_block, + ) { + Ok(flashtestations_builder_txs) => { + builder_txs.extend(flashtestations_builder_txs) + } + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx") + } + } } } Ok(builder_txs) @@ -126,15 +135,18 @@ impl BuilderTransactions for FlashblocksBuilderTx { pub(super) struct FlashblocksNumberBuilderTx { pub signer: Option, pub flashblock_number_address: Address, - pub base_builder_tx: BuilderTxBase, - pub flashtestations_builder_tx: Option, + pub base_builder_tx: BuilderTxBase, + pub flashtestations_builder_tx: + Option>, } impl FlashblocksNumberBuilderTx { pub(super) fn new( signer: Option, flashblock_number_address: Address, - flashtestations_builder_tx: Option, + flashtestations_builder_tx: Option< + FlashtestationsBuilderTx, + >, ) -> Self { let base_builder_tx = BuilderTxBase::new(signer); Self { @@ -145,14 +157,11 @@ impl FlashblocksNumberBuilderTx { } } + // TODO: remove and clean up in favour of simulate_call() fn estimate_flashblock_number_tx_gas( &self, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - State>, - NoOpInspector, - PrecompilesMap, - >, + evm: &mut OpEvm, signer: &Signer, nonce: u64, ) -> Result { @@ -166,15 +175,17 @@ impl FlashblocksNumberBuilderTx { match result { ExecutionResult::Success { gas_used, logs, .. } => { - if log_exists( - &logs, - &IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH, - ) { + if logs.iter().any(|log| { + log.topics().first() + == Some(&IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH) + }) { Ok(gas_used) } else { - Err(BuilderTransactionError::other( - FlashblockNumberError::LogMismatch( - IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH, + Err(BuilderTransactionError::InvalidContract( + self.flashblock_number_address, + InvalidContractDataError::InvalidLogs( + vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH], + vec![], ), )) } @@ -213,33 +224,29 @@ impl FlashblocksNumberBuilderTx { } } -impl BuilderTransactions for FlashblocksNumberBuilderTx { - fn simulate_builder_txs( +impl BuilderTransactions + for FlashblocksNumberBuilderTx +{ + fn simulate_builder_txs( &self, state_provider: impl StateProvider + Clone, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State, + db: &mut State, + top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); - let state = StateProviderDatabase::new(state_provider.clone()); - let simulation_state = State::builder() - .with_database(state) - .with_cached_prestate(db.cache.clone()) - .with_bundle_update() - .build(); if ctx.is_first_flashblock() { // fallback block builder tx - builder_txs.extend(self.base_builder_tx.simulate_builder_tx(ctx, db)?); + 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 if let Some(signer) = &self.signer { - let mut evm = ctx - .evm_config - .evm_with_env(simulation_state, ctx.evm_env.clone()); + 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 nonce = get_nonce(evm.db_mut(), signer.address)?; @@ -268,7 +275,7 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { 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, db)? + .simulate_builder_tx(ctx, &mut *db)? .map(|tx| tx.set_top_of_block()) } }; @@ -279,21 +286,29 @@ impl BuilderTransactions for FlashblocksNumberBuilderTx { if ctx.is_last_flashblock() { if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { - let flashblocks_builder_txs = builder_txs.clone(); - let mut simulation_state = self.simulate_builder_txs_state::( - state_provider.clone(), - flashblocks_builder_txs.iter().collect(), - ctx, - db, - )?; + // Commit state that should be included to compute the correct nonce + let flashblocks_builder_txs = builder_txs + .iter() + .filter(|tx| tx.is_top_of_block == top_of_block) + .map(|tx| tx.signed_tx.clone()) + .collect(); + self.commit_txs(flashblocks_builder_txs, ctx, &mut *db)?; + // We only include flashtestations txs in the last flashblock - let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( + match flashtestations_builder_tx.simulate_builder_txs( state_provider, info, ctx, - &mut simulation_state, - )?; - builder_txs.extend(flashtestations_builder_txs); + db, + top_of_block, + ) { + Ok(flashtestations_builder_txs) => { + builder_txs.extend(flashtestations_builder_txs) + } + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx") + } + } } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 8acb810b..e350260b 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -68,8 +68,8 @@ type NextBestFlashblocksTxs = BestFlashblocksTxs< >, >; -#[derive(Debug, Default)] -pub(super) struct ExtraExecutionInfo { +#[derive(Debug, Default, Clone)] +pub(super) struct FlashblocksExecutionInfo { /// Index of the last consumed flashblock last_flashblock_index: usize, } @@ -211,7 +211,7 @@ impl OpPayloadBuilder where Pool: PoolBounds, Client: ClientBounds, - BuilderTx: BuilderTransactions + Send + Sync, + BuilderTx: BuilderTransactions + Send + Sync, { fn get_op_payload_builder_ctx( &self, @@ -526,7 +526,7 @@ where // build first flashblock immediately let next_flashblocks_ctx = match self.build_next_flashblock( - &ctx, + &mut ctx, &mut info, &mut state, &state_provider, @@ -582,8 +582,8 @@ where P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, >( &self, - ctx: &OpPayloadBuilderCtx, - info: &mut ExecutionInfo, + ctx: &mut OpPayloadBuilderCtx, + info: &mut ExecutionInfo, state: &mut State, state_provider: impl reth::providers::StateProvider + Clone, best_txs: &mut NextBestFlashblocksTxs, @@ -780,7 +780,7 @@ where fn record_flashblocks_metrics( &self, ctx: &OpPayloadBuilderCtx, - info: &ExecutionInfo, + info: &ExecutionInfo, flashblocks_per_block: u64, span: &tracing::Span, message: &str, @@ -895,7 +895,8 @@ impl PayloadBuilder for OpPayloadBuilder + Clone + Send + Sync, + BuilderTx: + BuilderTransactions + Clone + Send + Sync, { type Attributes = OpPayloadBuilderAttributes; type BuiltPayload = OpBuiltPayload; @@ -919,7 +920,7 @@ struct FlashblocksMetadata { fn execute_pre_steps( state: &mut State, ctx: &OpPayloadBuilderCtx, -) -> Result, PayloadBuilderError> +) -> Result, PayloadBuilderError> where DB: Database + std::fmt::Debug, ExtraCtx: std::fmt::Debug + Default, @@ -939,7 +940,7 @@ where pub(super) fn build_block( state: &mut State, ctx: &OpPayloadBuilderCtx, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, calculate_state_root: bool, ) -> Result<(OpBuiltPayload, FlashblocksPayloadV1), PayloadBuilderError> where diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index 46ee8ae8..584252df 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -5,7 +5,7 @@ use crate::{ builder_tx::BuilderTransactions, flashblocks::{ builder_tx::{FlashblocksBuilderTx, FlashblocksNumberBuilderTx}, - payload::FlashblocksExtraCtx, + payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, }, generator::BlockPayloadJobGenerator, }, @@ -32,7 +32,12 @@ impl FlashblocksServiceBuilder { where Node: NodeBounds, Pool: PoolBounds, - BuilderTx: BuilderTransactions + Unpin + Clone + Send + Sync + 'static, + BuilderTx: BuilderTransactions + + Unpin + + Clone + + Send + + Sync + + 'static, { let once_lock = Arc::new(std::sync::OnceLock::new()); @@ -84,8 +89,12 @@ where _: OpEvmConfig, ) -> eyre::Result::Payload>> { let signer = self.0.builder_signer; - let flashtestations_builder_tx = if self.0.flashtestations_config.flashtestations_enabled { - match bootstrap_flashtestations(self.0.flashtestations_config.clone(), ctx).await { + let flashtestations_builder_tx = if let Some(builder_key) = signer + && self.0.flashtestations_config.flashtestations_enabled + { + match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key, ctx) + .await + { Ok(builder_tx) => Some(builder_tx), Err(e) => { tracing::warn!(error = %e, "Failed to bootstrap flashtestations, builder will not include flashtestations txs"); diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 9dbd949c..c733d111 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -22,8 +22,8 @@ mod generator; mod standard; pub use builder_tx::{ - BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, get_balance, get_nonce, - log_exists, + BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, InvalidContractDataError, + SimulationSuccessResult, get_balance, get_nonce, }; pub use context::OpPayloadBuilderCtx; pub use flashblocks::FlashblocksBuilder; diff --git a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs index 75a159ad..ececc887 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/builder_tx.rs @@ -2,6 +2,8 @@ use alloy_evm::Database; use core::fmt::Debug; use reth_provider::StateProvider; use reth_revm::State; +use revm::DatabaseRef; +use tracing::warn; use crate::{ builders::{ @@ -34,30 +36,33 @@ impl StandardBuilderTx { } impl BuilderTransactions for StandardBuilderTx { - fn simulate_builder_txs( + fn simulate_builder_txs( &self, state_provider: impl StateProvider + Clone, - info: &mut ExecutionInfo, + info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State, + db: &mut State, + top_of_block: bool, ) -> Result, BuilderTransactionError> { let mut builder_txs = Vec::::new(); - let standard_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, db)?; + let standard_builder_tx = self.base_builder_tx.simulate_builder_tx(ctx, &mut *db)?; builder_txs.extend(standard_builder_tx.clone()); if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { - let mut simulation_state = self.simulate_builder_txs_state::<()>( - state_provider.clone(), - standard_builder_tx.iter().collect(), - ctx, - db, - )?; - let flashtestations_builder_txs = flashtestations_builder_tx.simulate_builder_txs( + if let Some(builder_tx) = standard_builder_tx { + self.commit_txs(vec![builder_tx.signed_tx], ctx, db)?; + } + match flashtestations_builder_tx.simulate_builder_txs( state_provider, info, ctx, - &mut simulation_state, - )?; - builder_txs.extend(flashtestations_builder_txs); + db, + top_of_block, + ) { + Ok(flashtestations_builder_txs) => builder_txs.extend(flashtestations_builder_txs), + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx") + } + } } Ok(builder_txs) } diff --git a/crates/builder/op-rbuilder/src/builders/standard/service.rs b/crates/builder/op-rbuilder/src/builders/standard/service.rs index c713b69c..faf252b1 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/service.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/service.rs @@ -72,8 +72,10 @@ where evm_config: OpEvmConfig, ) -> eyre::Result::Payload>> { let signer = self.0.builder_signer; - let flashtestations_builder_tx = if self.0.flashtestations_config.flashtestations_enabled { - match bootstrap_flashtestations::(self.0.flashtestations_config.clone(), ctx) + let flashtestations_builder_tx = if let Some(builder_key) = signer + && self.0.flashtestations_config.flashtestations_enabled + { + match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key, ctx) .await { Ok(builder_tx) => Some(builder_tx), diff --git a/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs index 598a8a8f..fa3601bd 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/args.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/args.rs @@ -100,6 +100,14 @@ pub struct FlashtestationsArgs { default_value = "1" )] pub builder_proof_version: u8, + + /// Use permit for the flashtestation builder tx + #[arg( + long = "flashtestations.use-permit", + env = "FLASHTESTATIONS_USE_PERMIT", + default_value = "false" + )] + pub flashtestations_use_permit: bool, } impl Default for FlashtestationsArgs { diff --git a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs index 716d2520..9e7f3086 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs @@ -2,32 +2,31 @@ use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_evm::Database; use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, B256, Bytes, TxKind, U256, keccak256, map::foldhash::HashMap}; -use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, SolValue}; +use alloy_primitives::{Address, B256, Bytes, Signature, TxKind, U256, keccak256}; +use alloy_rpc_types_eth::TransactionInput; +use alloy_sol_types::{SolCall, SolEvent, SolValue}; use core::fmt::Debug; use op_alloy_consensus::OpTypedTransaction; +use op_alloy_rpc_types::OpTransactionRequest; use reth_evm::{ConfigureEvm, Evm, EvmError, precompiles::PrecompilesMap}; use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::{Log, Recovered}; use reth_provider::StateProvider; use reth_revm::{State, database::StateProviderDatabase}; use revm::{ - DatabaseCommit, - context::result::{ExecutionResult, ResultAndState}, - inspector::NoOpInspector, - state::Account, + DatabaseCommit, DatabaseRef, context::result::ResultAndState, inspector::NoOpInspector, }; use std::sync::{Arc, atomic::AtomicBool}; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, - get_balance, get_nonce, + SimulationSuccessResult, get_balance, get_nonce, }, flashtestations::{ - BlockData, FlashtestationRevertReason, + BlockData, IBlockBuilderPolicy::{self, BlockBuilderProofVerified}, + IERC20Permit, IFlashtestationRegistry::{self, TEEServiceRegistered}, }, primitives::reth::ExecutionInfo, @@ -45,10 +44,16 @@ pub struct FlashtestationsBuilderTxArgs { pub builder_proof_version: u8, pub enable_block_proofs: bool, pub registered: bool, + pub use_permit: bool, + pub builder_key: Signer, } #[derive(Debug, Clone)] -pub struct FlashtestationsBuilderTx { +pub struct FlashtestationsBuilderTx +where + ExtraCtx: Debug + Default, + Extra: Debug + Default, +{ // Attestation for the builder attestation: Vec, // Extra registration data for the builder @@ -69,18 +74,19 @@ pub struct FlashtestationsBuilderTx { registered: Arc, // Whether block proofs are enabled enable_block_proofs: bool, + // Whether to use permit for the flashtestation builder tx + use_permit: bool, + // Builder key for the flashtestation permit tx + builder_signer: Signer, + // Extra context and data + _marker: std::marker::PhantomData<(ExtraCtx, Extra)>, } -#[derive(Debug, Default)] -pub struct TxSimulateResult { - pub gas_used: u64, - pub success: bool, - pub state_changes: HashMap, - pub revert_reason: Option, - pub logs: Vec, -} - -impl FlashtestationsBuilderTx { +impl FlashtestationsBuilderTx +where + ExtraCtx: Debug + Default, + Extra: Debug + Default, +{ pub fn new(args: FlashtestationsBuilderTxArgs) -> Self { Self { attestation: args.attestation, @@ -93,87 +99,12 @@ impl FlashtestationsBuilderTx { builder_proof_version: args.builder_proof_version, registered: Arc::new(AtomicBool::new(args.registered)), enable_block_proofs: args.enable_block_proofs, + use_permit: args.use_permit, + builder_signer: args.builder_key, + _marker: std::marker::PhantomData, } } - fn signed_funding_tx( - &self, - to: Address, - from: Signer, - amount: U256, - base_fee: u64, - chain_id: u64, - nonce: u64, - ) -> Result, secp256k1::Error> { - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit: 21000, - max_fee_per_gas: base_fee.into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(to), - value: amount, - ..Default::default() - }); - from.sign_tx(tx) - } - - fn signed_register_tee_service_tx( - &self, - attestation: Vec, - gas_limit: u64, - base_fee: u64, - chain_id: u64, - nonce: u64, - ) -> Result, secp256k1::Error> { - let quote_bytes = Bytes::from(attestation); - let calldata = IFlashtestationRegistry::registerTEEServiceCall { - rawQuote: quote_bytes, - extendedRegistrationData: self.extra_registration_data.clone(), - } - .abi_encode(); - - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id, - nonce, - gas_limit, - max_fee_per_gas: base_fee.into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(self.registry_address), - input: calldata.into(), - ..Default::default() - }); - self.tee_service_signer.sign_tx(tx) - } - - fn signed_block_builder_proof_tx( - &self, - block_content_hash: B256, - ctx: &OpPayloadBuilderCtx, - gas_limit: u64, - nonce: u64, - ) -> Result, secp256k1::Error> { - let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { - version: self.builder_proof_version, - blockContentHash: block_content_hash, - } - .abi_encode(); - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: ctx.chain_id(), - nonce, - gas_limit, - max_fee_per_gas: ctx.base_fee().into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(self.builder_policy_address), - input: calldata.into(), - ..Default::default() - }); - self.tee_service_signer.sign_tx(tx) - } - /// Computes the block content hash according to the formula: /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) /// https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#block-building-process @@ -206,174 +137,25 @@ impl FlashtestationsBuilderTx { keccak256(&encoded) } - fn simulate_register_tee_service_tx( - &self, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; - - let register_tx = self.signed_register_tee_service_tx( - self.attestation.clone(), - ctx.block_gas_limit(), - ctx.base_fee(), - ctx.chain_id(), - nonce, - )?; - let ResultAndState { result, state } = match evm.transact(®ister_tx) { - Ok(res) => res, - Err(err) => { - if err.is_invalid_tx_err() { - return Err(BuilderTransactionError::InvalidTransactionError(Box::new( - err, - ))); - } else { - return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); - } - } - }; - match result { - ExecutionResult::Success { gas_used, logs, .. } => Ok(TxSimulateResult { - gas_used, - success: true, - state_changes: state, - revert_reason: None, - logs, - }), - ExecutionResult::Revert { output, gas_used } => { - let revert_reason = - IFlashtestationRegistry::IFlashtestationRegistryErrors::abi_decode(&output) - .map(FlashtestationRevertReason::FlashtestationRegistry) - .unwrap_or_else(|e| { - FlashtestationRevertReason::Unknown(hex::encode(output), e) - }); - Ok(TxSimulateResult { - gas_used, - success: false, - state_changes: state, - revert_reason: Some(revert_reason), - logs: vec![], - }) - } - ExecutionResult::Halt { reason, .. } => Ok(TxSimulateResult { - gas_used: 0, - success: false, - state_changes: state, - revert_reason: Some(FlashtestationRevertReason::Halt(reason)), - logs: vec![], - }), - } - } - - fn check_tee_address_registered_log(&self, logs: &[Log], address: Address) -> bool { - for log in logs { - if log.topics().first() == Some(&TEEServiceRegistered::SIGNATURE_HASH) { - if let Ok(decoded) = TEEServiceRegistered::decode_log(log) { - if decoded.teeAddress == address { - return true; - } - }; - } - } - false - } - - fn simulate_verify_block_proof_tx( + // TODO: deprecate in favour of permit calls + fn fund_tee_service_tx( &self, - block_content_hash: B256, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result { - let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; - - let verify_block_proof_tx = self.signed_block_builder_proof_tx( - block_content_hash, - ctx, - ctx.block_gas_limit(), - nonce, - )?; - let ResultAndState { result, state } = match evm.transact(&verify_block_proof_tx) { - Ok(res) => res, - Err(err) => { - if err.is_invalid_tx_err() { - return Err(BuilderTransactionError::InvalidTransactionError(Box::new( - err, - ))); - } else { - return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); - } - } - }; - match result { - ExecutionResult::Success { gas_used, logs, .. } => Ok(TxSimulateResult { - gas_used, - success: true, - state_changes: state, - revert_reason: None, - logs, - }), - ExecutionResult::Revert { output, gas_used } => { - let revert_reason = - IBlockBuilderPolicy::IBlockBuilderPolicyErrors::abi_decode(&output) - .map(FlashtestationRevertReason::BlockBuilderPolicy) - .unwrap_or_else(|e| { - FlashtestationRevertReason::Unknown(hex::encode(output), e) - }); - Ok(TxSimulateResult { - gas_used, - success: false, - state_changes: state, - revert_reason: Some(revert_reason), - logs: vec![], - }) - } - ExecutionResult::Halt { reason, .. } => Ok(TxSimulateResult { - gas_used: 0, - success: false, - state_changes: state, - revert_reason: Some(FlashtestationRevertReason::Halt(reason)), - logs: vec![], - }), - } - } - - fn check_verify_block_proof_log(&self, logs: &[Log]) -> bool { - for log in logs { - if log.topics().first() == Some(&BlockBuilderProofVerified::SIGNATURE_HASH) { - return true; - } - } - false - } - - fn fund_tee_service_tx( - &self, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, + evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, ) -> Result, BuilderTransactionError> { - let balance = get_balance(evm.db_mut(), self.tee_service_signer.address)?; + let balance = get_balance(evm.db(), self.tee_service_signer.address)?; if balance.is_zero() { - let funding_nonce = get_nonce(evm.db_mut(), self.funding_key.address)?; - let funding_tx = self.signed_funding_tx( - self.tee_service_signer.address, - self.funding_key, - self.funding_amount, - ctx.base_fee(), - ctx.chain_id(), - funding_nonce, - )?; + let funding_nonce = get_nonce(evm.db(), self.funding_key.address)?; + let tx = OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: ctx.chain_id(), + nonce: funding_nonce, + gas_limit: 21000, + max_fee_per_gas: ctx.base_fee().into(), + to: TxKind::Call(self.tee_service_signer.address), + value: self.funding_amount, + ..Default::default() + }); + let funding_tx = self.funding_key.sign_tx(tx)?; let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes(funding_tx.encoded_2718().as_slice()); let ResultAndState { state, .. } = match evm.transact(&funding_tx) { @@ -401,79 +183,54 @@ impl FlashtestationsBuilderTx { } } - fn register_tee_service_tx( + // TODO: deprecate in favour of permit calls + fn register_tee_service_tx( &self, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result<(Option, bool), BuilderTransactionError> { - let TxSimulateResult { + evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, + ) -> Result { + let calldata = IFlashtestationRegistry::registerTEEServiceCall { + rawQuote: self.attestation.clone().into(), + extendedRegistrationData: self.extra_registration_data.clone(), + }; + let SimulationSuccessResult { gas_used, - success, state_changes, - revert_reason, - logs, - } = self.simulate_register_tee_service_tx(ctx, evm)?; - if success { - if !self.check_tee_address_registered_log(&logs, self.tee_service_signer.address) { - Err(BuilderTransactionError::other( - FlashtestationRevertReason::LogMismatch( - self.registry_address, - TEEServiceRegistered::SIGNATURE_HASH, - ), - )) - } else { - let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; - let register_tx = self.signed_register_tee_service_tx( - self.attestation.clone(), - gas_used * 64 / 63, // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer - ctx.base_fee(), - ctx.chain_id(), - nonce, - )?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - register_tx.encoded_2718().as_slice(), - ); - info!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?register_tx.tx_hash(), "adding register tee tx to builder txs"); - evm.db_mut().commit(state_changes); - Ok(( - Some(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx: register_tx, - is_top_of_block: false, - }), - false, - )) - } - } else if let Some(FlashtestationRevertReason::FlashtestationRegistry( - IFlashtestationRegistry::IFlashtestationRegistryErrors::TEEServiceAlreadyRegistered(_), - )) = revert_reason - { - Ok((None, true)) - } else { - Err(BuilderTransactionError::other(revert_reason.unwrap_or( - FlashtestationRevertReason::Unknown( - "unknown revert".into(), - Error::Other("unknown revert".into()), - ), - ))) - } + .. + } = self.flashtestation_call( + self.registry_address, + calldata.clone(), + vec![TEEServiceRegistered::SIGNATURE_HASH], + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.registry_address, + self.tee_service_signer, + gas_used, + calldata.abi_encode().into(), + ctx, + evm.db(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + // commit the register transaction state so the block proof transaction can succeed + evm.db_mut().commit(state_changes); + Ok(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + }) } - fn verify_block_proof_tx( + // TODO: remove in favour of permit calls + fn verify_block_proof_tx( &self, transactions: Vec, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm< - &mut State>, - NoOpInspector, - PrecompilesMap, - >, - ) -> Result, BuilderTransactionError> { + evm: &mut OpEvm, + ) -> Result { let block_content_hash = Self::compute_block_content_hash( &transactions, ctx.parent_hash(), @@ -481,52 +238,36 @@ impl FlashtestationsBuilderTx { ctx.timestamp(), ); - let TxSimulateResult { + let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { + blockContentHash: block_content_hash, + version: self.builder_proof_version, + }; + let SimulationSuccessResult { gas_used, .. } = self.flashtestation_call( + self.builder_policy_address, + calldata.clone(), + vec![BlockBuilderProofVerified::SIGNATURE_HASH], + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.builder_policy_address, + self.tee_service_signer, gas_used, - success, - revert_reason, - logs, - .. - } = self.simulate_verify_block_proof_tx(block_content_hash, ctx, evm)?; - if success { - if !self.check_verify_block_proof_log(&logs) { - Err(BuilderTransactionError::other( - FlashtestationRevertReason::LogMismatch( - self.builder_policy_address, - BlockBuilderProofVerified::SIGNATURE_HASH, - ), - )) - } else { - let nonce = get_nonce(evm.db_mut(), self.tee_service_signer.address)?; - // Due to EIP-150, only 63/64 of available gas is forwarded to external calls so need to add a buffer - let verify_block_proof_tx = self.signed_block_builder_proof_tx( - block_content_hash, - ctx, - gas_used * 64 / 63, - nonce, - )?; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - verify_block_proof_tx.encoded_2718().as_slice(), - ); - debug!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?verify_block_proof_tx.tx_hash(), "adding verify block proof tx to builder txs"); - Ok(Some(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx: verify_block_proof_tx, - is_top_of_block: false, - })) - } - } else { - Err(BuilderTransactionError::other(revert_reason.unwrap_or( - FlashtestationRevertReason::Unknown( - "unknown revert".into(), - Error::Other("unknown revert".into()), - ), - ))) - } + calldata.abi_encode().into(), + ctx, + evm.db(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + }) } - fn set_registered( + fn set_registered( &self, state_provider: impl StateProvider + Clone, ctx: &OpPayloadBuilderCtx, @@ -542,62 +283,287 @@ impl FlashtestationsBuilderTx { evm.modify_cfg(|cfg| { cfg.disable_balance_check = true; }); - match self.register_tee_service_tx(ctx, &mut evm) { - Ok((_, registered)) => { - if registered { - self.registered - .store(true, std::sync::atomic::Ordering::SeqCst); - } - Ok(()) - } - Err(e) => Err(e), + let calldata = IFlashtestationRegistry::getRegistrationStatusCall { + teeAddress: self.tee_service_signer.address, + }; + let SimulationSuccessResult { output, .. } = + self.flashtestation_contract_read(self.registry_address, calldata, ctx, &mut evm)?; + if output.isValid { + self.registered + .store(true, std::sync::atomic::Ordering::SeqCst); + } + Ok(()) + } + + fn get_permit_nonce( + &self, + contract_address: Address, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let calldata = IERC20Permit::noncesCall { + owner: self.tee_service_signer.address, + }; + let SimulationSuccessResult { output, .. } = + self.flashtestation_contract_read(contract_address, calldata, ctx, evm)?; + Ok(output) + } + + fn registration_permit_signature( + &self, + permit_nonce: U256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let struct_hash_calldata = IFlashtestationRegistry::computeStructHashCall { + rawQuote: self.attestation.clone().into(), + extendedRegistrationData: self.extra_registration_data.clone(), + nonce: permit_nonce, + deadline: U256::from(ctx.timestamp()), + }; + let SimulationSuccessResult { output, .. } = self.flashtestation_contract_read( + self.registry_address, + struct_hash_calldata, + ctx, + evm, + )?; + let typed_data_hash_calldata = + IFlashtestationRegistry::hashTypedDataV4Call { structHash: output }; + let SimulationSuccessResult { output, .. } = self.flashtestation_contract_read( + self.registry_address, + typed_data_hash_calldata, + ctx, + evm, + )?; + let signature = self.tee_service_signer.sign_message(output)?; + Ok(signature) + } + + fn signed_registration_permit_tx( + &self, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, + ) -> Result { + let permit_nonce = self.get_permit_nonce(self.registry_address, ctx, evm)?; + let signature = self.registration_permit_signature(permit_nonce, ctx, evm)?; + let calldata = IFlashtestationRegistry::permitRegisterTEEServiceCall { + rawQuote: self.attestation.clone().into(), + extendedRegistrationData: self.extra_registration_data.clone(), + nonce: permit_nonce, + deadline: U256::from(ctx.timestamp()), + signature: signature.as_bytes().into(), + }; + let SimulationSuccessResult { + gas_used, + state_changes, + .. + } = self.flashtestation_call( + self.registry_address, + calldata.clone(), + vec![TEEServiceRegistered::SIGNATURE_HASH], + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.registry_address, + self.builder_signer, + gas_used, + calldata.abi_encode().into(), + ctx, + evm.db(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + // commit the register transaction state so the block proof transaction can succeed + evm.db_mut().commit(state_changes); + Ok(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + }) + } + + fn block_proof_permit_signature( + &self, + permit_nonce: U256, + block_content_hash: B256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let struct_hash_calldata = IBlockBuilderPolicy::computeStructHashCall { + version: self.builder_proof_version, + blockContentHash: block_content_hash, + nonce: permit_nonce, + }; + let SimulationSuccessResult { output, .. } = self.flashtestation_contract_read( + self.builder_policy_address, + struct_hash_calldata, + ctx, + evm, + )?; + let typed_data_hash_calldata = + IBlockBuilderPolicy::getHashedTypeDataV4Call { structHash: output }; + let SimulationSuccessResult { output, .. } = self.flashtestation_contract_read( + self.builder_policy_address, + typed_data_hash_calldata, + ctx, + evm, + )?; + let signature = self.tee_service_signer.sign_message(output)?; + Ok(signature) + } + + fn signed_block_proof_permit_tx( + &self, + transactions: &[OpTransactionSigned], + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let permit_nonce = self.get_permit_nonce(self.builder_policy_address, ctx, evm)?; + let block_content_hash = Self::compute_block_content_hash( + transactions, + ctx.parent_hash(), + ctx.block_number(), + ctx.timestamp(), + ); + let signature = + self.block_proof_permit_signature(permit_nonce, block_content_hash, ctx, evm)?; + let calldata = IBlockBuilderPolicy::permitVerifyBlockBuilderProofCall { + blockContentHash: block_content_hash, + nonce: permit_nonce, + version: self.builder_proof_version, + eip712Sig: signature.as_bytes().into(), + }; + let SimulationSuccessResult { gas_used, .. } = self.flashtestation_call( + self.builder_policy_address, + calldata.clone(), + vec![BlockBuilderProofVerified::SIGNATURE_HASH], + ctx, + evm, + )?; + let signed_tx = self.sign_tx( + self.builder_policy_address, + self.builder_signer, + gas_used, + calldata.abi_encode().into(), + ctx, + evm.db(), + )?; + let da_size = + op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); + Ok(BuilderTransactionCtx { + gas_used, + da_size, + signed_tx, + is_top_of_block: false, + }) + } + + fn flashtestation_contract_read( + &self, + contract_address: Address, + calldata: T, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + self.flashtestation_call(contract_address, calldata, vec![], ctx, evm) + } + + fn flashtestation_call( + &self, + contract_address: Address, + calldata: T, + expected_topics: 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(contract_address) + .from(self.tee_service_signer.address) // use tee key as signer for simulations + .nonce(get_nonce(evm.db(), self.tee_service_signer.address)?) + .input(TransactionInput::new(calldata.abi_encode().into())); + if contract_address == self.registry_address { + self.simulate_call::( + tx_req, + expected_topics, + evm, + ) + } else if contract_address == self.builder_policy_address { + self.simulate_call::( + tx_req, + expected_topics, + evm, + ) + } else { + Err(BuilderTransactionError::msg( + "invalid contract address for flashtestations", + )) } } } -impl BuilderTransactions for FlashtestationsBuilderTx { - fn simulate_builder_txs( +impl BuilderTransactions + for FlashtestationsBuilderTx +where + ExtraCtx: Debug + Default, + Extra: Debug + Default, +{ + fn simulate_builder_txs( &self, state_provider: impl StateProvider + Clone, info: &mut ExecutionInfo, ctx: &OpPayloadBuilderCtx, - db: &mut State, + db: &mut State, + _top_of_block: bool, ) -> Result, BuilderTransactionError> { - // set registered simulating against the committed state + // set registered by simulating against the committed state if !self.registered.load(std::sync::atomic::Ordering::SeqCst) { - self.set_registered(state_provider.clone(), ctx)?; + self.set_registered(state_provider, ctx)?; } - let state = StateProviderDatabase::new(state_provider.clone()); - let mut simulation_state = State::builder() - .with_database(state) - .with_bundle_prestate(db.bundle_state.clone()) - .with_bundle_update() - .build(); - - let mut evm = ctx - .evm_config - .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); + 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 mut builder_txs = Vec::::new(); if !self.registered.load(std::sync::atomic::Ordering::SeqCst) { info!(target: "flashtestations", "tee service not registered yet, attempting to register"); - builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); - let (register_tx, _) = self.register_tee_service_tx(ctx, &mut evm)?; - builder_txs.extend(register_tx); + let register_tx = if self.use_permit { + self.signed_registration_permit_tx(ctx, &mut evm)? + } else { + builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); + self.register_tee_service_tx(ctx, &mut evm)? + }; + builder_txs.push(register_tx); } + // don't return on error for block proof as previous txs in builder_txs will not be returned if self.enable_block_proofs { - // add verify block proof tx - builder_txs.extend(self.verify_block_proof_tx( - info.executed_transactions.clone(), - ctx, - &mut evm, - )?); + if self.use_permit { + debug!(target: "flashtestations", "adding permit verify block proof tx"); + match self.signed_block_proof_permit_tx(&info.executed_transactions, ctx, &mut evm) + { + Ok(block_proof_tx) => builder_txs.push(block_proof_tx), + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add permit block proof transaction") + } + } + } else { + // add verify block proof tx + match self.verify_block_proof_tx(info.executed_transactions.clone(), ctx, &mut evm) + { + Ok(block_proof_tx) => builder_txs.push(block_proof_tx), + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add block proof transaction") + } + }; + } } Ok(builder_txs) } diff --git a/crates/builder/op-rbuilder/src/flashtestations/mod.rs b/crates/builder/op-rbuilder/src/flashtestations/mod.rs index a2a50611..f80a2e0c 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/mod.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/mod.rs @@ -1,6 +1,4 @@ -use alloy_primitives::{Address, B256}; -use alloy_sol_types::{Error, sol}; -use op_revm::OpHaltReason; +use alloy_sol_types::sol; // https://github.com/flashbots/flashtestations/commit/7cc7f68492fe672a823dd2dead649793aac1f216 sol!( @@ -9,6 +7,25 @@ sol!( interface IFlashtestationRegistry { function registerTEEService(bytes calldata rawQuote, bytes calldata extendedRegistrationData) external; + function permitRegisterTEEService( + bytes calldata rawQuote, + bytes calldata extendedRegistrationData, + uint256 nonce, + uint256 deadline, + bytes calldata signature + ) external payable; + + function computeStructHash( + bytes calldata rawQuote, + bytes calldata extendedRegistrationData, + uint256 nonce, + uint256 deadline + ) external pure returns (bytes32); + + function hashTypedDataV4(bytes32 structHash) external view returns (bytes32); + + function getRegistrationStatus(address teeAddress) external view returns (bool isValid, bytes32 quoteHash); + /// @notice Emitted when a TEE service is registered /// @param teeAddress The address of the TEE service /// @param rawQuote The raw quote from the TEE device @@ -46,6 +63,20 @@ sol!( interface IBlockBuilderPolicy { function verifyBlockBuilderProof(uint8 version, bytes32 blockContentHash) external; + function permitVerifyBlockBuilderProof( + uint8 version, + bytes32 blockContentHash, + uint256 nonce, + bytes calldata eip712Sig + ) external; + + function computeStructHash(uint8 version, bytes32 blockContentHash, uint256 nonce) + external + pure + returns (bytes32); + + function getHashedTypeDataV4(bytes32 structHash) external view returns (bytes32); + /// @notice Emitted when a block builder proof is successfully verified /// @param caller The address that called the verification function (TEE address) /// @param workloadId The workload identifier of the TEE @@ -72,6 +103,10 @@ sol!( error EmptySourceLocators(); } + interface IERC20Permit { + function nonces(address owner) external view returns (uint256); + } + struct BlockData { bytes32 parentHash; uint256 blockNumber; @@ -82,20 +117,6 @@ sol!( type WorkloadId is bytes32; ); -#[derive(Debug, thiserror::Error)] -pub enum FlashtestationRevertReason { - #[error("flashtestation registry error: {0:?}")] - FlashtestationRegistry(IFlashtestationRegistry::IFlashtestationRegistryErrors), - #[error("block builder policy error: {0:?}")] - BlockBuilderPolicy(IBlockBuilderPolicy::IBlockBuilderPolicyErrors), - #[error("contract {0:?} may be invalid, mismatch in log emitted: expected {1:?}")] - LogMismatch(Address, B256), - #[error("unknown revert: {0} err: {1}")] - Unknown(String, Error), - #[error("halt: {0:?}")] - Halt(OpHaltReason), -} - pub mod args; pub mod attestation; pub mod builder_tx; diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 5b9162cd..747fd2ab 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -8,24 +8,27 @@ use std::{ }; use tracing::{info, warn}; -use crate::{ - flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, - traits::NodeBounds, - tx_signer::{Signer, generate_key_from_seed, generate_signer}, -}; - use super::{ args::FlashtestationsArgs, attestation::{AttestationConfig, get_attestation_provider}, tx_manager::TxManager, }; +use crate::{ + flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, + traits::NodeBounds, + tx_signer::{Signer, generate_key_from_seed, generate_signer}, +}; +use std::fmt::Debug; -pub async fn bootstrap_flashtestations( +pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, + builder_key: Signer, ctx: &BuilderContext, -) -> eyre::Result +) -> eyre::Result> where Node: NodeBounds, + ExtraCtx: Debug + Default, + Extra: Debug + Default, { let tee_service_signer = load_or_generate_tee_key( &args.flashtestations_key_path, @@ -78,7 +81,11 @@ where info!(target: "flashtestations", "requesting TDX attestation"); let attestation = attestation_provider.get_attestation(report_data).await?; - let (tx_manager, registered) = if let Some(rpc_url) = args.rpc_url { + // TODO: support permit with an external rpc, skip this step if using permit signatures + // since the permit txs are signed by the builder key and will result in nonce issues + let (tx_manager, registered) = if let Some(rpc_url) = args.rpc_url + && !args.flashtestations_use_permit + { let tx_manager = TxManager::new( tee_service_signer, funding_key, @@ -115,6 +122,8 @@ where builder_proof_version: args.builder_proof_version, enable_block_proofs: args.enable_block_proofs, registered, + use_permit: args.flashtestations_use_permit, + builder_key, }); ctx.task_executor() diff --git a/crates/builder/op-rbuilder/src/tests/flashtestations.rs b/crates/builder/op-rbuilder/src/tests/flashtestations.rs index c12deb3e..544eb5d8 100644 --- a/crates/builder/op-rbuilder/src/tests/flashtestations.rs +++ b/crates/builder/op-rbuilder/src/tests/flashtestations.rs @@ -12,8 +12,11 @@ use crate::{ BLOCK_BUILDER_POLICY_ADDRESS, BundleOpts, ChainDriver, ChainDriverExt, FLASHBLOCKS_NUMBER_ADDRESS, FLASHTESTATION_REGISTRY_ADDRESS, LocalInstance, MOCK_DCAP_ADDRESS, TEE_DEBUG_ADDRESS, TransactionBuilderExt, + block_builder_policy::{BlockBuilderPolicy, IFlashtestationRegistry::RegisteredTEE}, + builder_signer, flashblocks_number_contract::FlashblocksNumber, - flashtestation_registry::FlashtestationRegistry, flashtestations_signer, + flashtestation_registry::FlashtestationRegistry, + flashtestations_signer, }, }; @@ -33,53 +36,15 @@ use crate::{ async fn test_flashtestations_registrations(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let provider = rbuilder.provider().await?; - setup_flashtestation_contracts(&driver, &provider, true).await?; - let block: alloy_rpc_types_eth::Block = - driver.build_new_block_with_current_timestamp(None).await?; - // check the builder tx, funding tx and registration tx is in the block - let num_txs = block.transactions.len(); - assert!(num_txs >= 3, "Expected at least 3 transactions in block"); - println!( - "block transactions {:#?}", - &block.transactions.clone().into_transactions_vec() - ); - let last_3_txs = &block.transactions.into_transactions_vec()[num_txs - 3..]; - // Check builder tx - assert_eq!( - last_3_txs[0].to(), - Some(Address::ZERO), - "builder tx should send to zero address" - ); - // Check funding tx - assert_eq!( - last_3_txs[1].to(), - Some(TEE_DEBUG_ADDRESS), - "funding tx should send to tee address" - ); - assert!( - last_3_txs[1] - .value() - .eq(&rbuilder.args().flashtestations.funding_amount), - "funding tx should have correct amount" - ); - // Check registration tx - assert_eq!( - last_3_txs[2].to(), - Some(FLASHTESTATION_REGISTRY_ADDRESS), - "registration tx should call registry" - ); - let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); - let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?; - assert!(result._1.isValid, "The tee key is not registered"); - + setup_flashtestation_contracts(&driver, &provider, true, true).await?; // check builder does not try to register again let block = driver.build_new_block_with_current_timestamp(None).await?; let num_txs = block.transactions.len(); if_flashblocks!( - assert!(num_txs == 3, "Expected at 3 transactions in block"); // deposit + 2 builder tx + assert!(num_txs == 3, "Expected 3 transactions in block"); // deposit + 2 builder tx ); if_standard!( - assert!(num_txs == 2, "Expected at 2 transactions in block"); // deposit + builder tx + assert!(num_txs == 2, "Expected 2 transactions in block"); // deposit + builder tx ); Ok(()) @@ -102,20 +67,12 @@ async fn test_flashtestations_registrations(rbuilder: LocalInstance) -> eyre::Re async fn test_flashtestations_block_proofs(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let provider = rbuilder.provider().await?; - setup_flashtestation_contracts(&driver, &provider, true).await?; - driver.build_new_block_with_current_timestamp(None).await?; - - // check registered - let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); - let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?; - assert!(result._1.isValid, "The tee key is not registered"); - + setup_flashtestation_contracts(&driver, &provider, true, true).await?; // check that only the builder tx and block proof is in the block let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; let txs = block.transactions.into_transactions_vec(); - if_flashblocks!( - assert_eq!(txs.len(), 5, "Expected at 4 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof + assert_eq!(txs.len(), 5, "Expected 5 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof // Check builder tx assert_eq!( txs[1].to(), @@ -124,7 +81,7 @@ async fn test_flashtestations_block_proofs(rbuilder: LocalInstance) -> eyre::Res ); ); if_standard!( - assert_eq!(txs.len(), 4, "Expected at 4 transactions in block"); // deposit + valid tx + builder tx + end of block proof + assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + builder tx + end of block proof ); let last_3_txs = &txs[txs.len() - 3..]; // Check valid transaction @@ -148,6 +105,116 @@ async fn test_flashtestations_block_proofs(rbuilder: LocalInstance) -> eyre::Res Ok(()) } +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_invalid_quote(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, false, false).await?; + // verify not registered + let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); + let result = contract + .getRegistrationStatus(TEE_DEBUG_ADDRESS) + .call() + .await?; + assert!( + !result.isValid, + "The tee key is registered for invalid quote" + ); + // check that only regular builder tx is in the block + let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; + let txs = block.transactions.into_transactions_vec(); + + if_flashblocks!( + assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + ); + if_standard!( + assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + ); + let last_txs = &txs[txs.len() - 2..]; + // Check user transaction + assert_eq!( + last_txs[0].inner.tx_hash(), + tx_hash, + "tx hash for user transaction should match" + ); + // Check builder tx + assert_eq!( + last_txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, false).await?; + // check that only the regular builder tx is in the block + let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; + let txs = block.transactions.into_transactions_vec(); + + if_flashblocks!( + assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx + // Check builder tx + assert_eq!( + txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + ); + if_standard!( + assert_eq!(txs.len(), 3, "Expected 3 transactions in block"); // deposit + valid tx + builder tx + ); + let last_txs = &txs[txs.len() - 2..]; + // Check user transaction + assert_eq!( + last_txs[0].inner.tx_hash(), + tx_hash, + "tx hash for user transaction should match" + ); + // Check builder tx + assert_eq!( + last_txs[1].to(), + Some(Address::ZERO), + "builder tx should send to zero address" + ); + Ok(()) +} + #[rb_test(flashblocks, args = OpRbuilderArgs { chain_block_time: 1000, enable_revert_protection: true, @@ -170,7 +237,7 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e let driver = rbuilder.driver().await?; let provider = rbuilder.provider().await?; setup_flashblock_number_contract(&driver, &provider, true).await?; - setup_flashtestation_contracts(&driver, &provider, true).await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; let tx = driver .create_transaction() .random_valid_transfer() @@ -178,9 +245,9 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e .send() .await?; let block = driver.build_new_block_with_current_timestamp(None).await?; - // 1 deposit tx, 1 fallback builder tx, 4 flashblocks number tx, valid tx, funding tx, registration tx, block proof + // 1 deposit tx, 1 fallback builder tx, 4 flashblocks number tx, valid tx, block proof let txs = block.transactions.into_transactions_vec(); - assert_eq!(txs.len(), 10, "Expected at 10 transactions in block"); + assert_eq!(txs.len(), 8, "Expected 8 transactions in block"); // Check builder tx assert_eq!( txs[1].to(), @@ -192,7 +259,8 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e assert_eq!( txs[i].to(), Some(FLASHBLOCKS_NUMBER_ADDRESS), - "builder tx should send to flashblocks number contract" + "builder tx should send to flashblocks number contract at index {}", + i ); } // check regular tx @@ -201,24 +269,138 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e *tx.tx_hash(), "bundle tx was not in block" ); - // check funding, registration and block proof tx + // check block proof tx assert_eq!( txs[7].to(), - Some(TEE_DEBUG_ADDRESS), - "funding tx should send to tee address" + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "block proof tx should call block policy address" + ); + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert!( + current_number.gt(&U256::from(8)), // contract deployments incremented the number but we built at least 2 full blocks + "Flashblock number not incremented" + ); + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + flashtestations_use_permit: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_registration(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + // check builder does not try to register again + let block = driver.build_new_block_with_current_timestamp(None).await?; + let num_txs = block.transactions.len(); + if_flashblocks!( + assert!(num_txs == 3, "Expected 3 transactions in block"); // deposit + 2 builder tx + ); + if_standard!( + assert!(num_txs == 2, "Expected 2 transactions in block"); // deposit + builder tx ); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); + Ok(()) +} + +#[rb_test(args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + enable_block_proofs: true, + flashtestations_use_permit: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_block_proof(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + // check builder does not try to register again + let block = driver.build_new_block_with_current_timestamp(None).await?; + let num_txs = block.transactions.len(); + if_flashblocks!( + assert!(num_txs == 4, "Expected 4 transactions in block"); // deposit + 2 builder tx + 1 block proof + ); + if_standard!( + assert!(num_txs == 3, "Expected 3 transactions in block"); // deposit + 2 builder tx + ); + let last_2_txs = &block.transactions.into_transactions_vec()[num_txs - 2..]; + // Check builder tx assert_eq!( - txs[8].to(), - Some(FLASHTESTATION_REGISTRY_ADDRESS), - "registration tx should call registry" + last_2_txs[0].to(), + Some(Address::ZERO), + "builder tx should send to zero address" ); + // check builder proof assert_eq!( - txs[9].to(), + last_2_txs[1].to(), Some(BLOCK_BUILDER_POLICY_ADDRESS), - "block proof tx should call block policy address" + "builder tx should send to flashtestations builder policy address" + ); + assert_eq!( + last_2_txs[1].from(), + builder_signer().address, + "block proof tx should come from builder address" ); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); + + Ok(()) +} - // add a user transaction to ensure the flashblock number builder tx is top of block +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + ..Default::default() + }, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + funding_key: Some(flashtestations_signer()), + debug: true, + flashtestations_use_permit: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_with_flashblocks_number_contract( + rbuilder: LocalInstance, +) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + setup_flashblock_number_contract(&driver, &provider, true).await?; let tx = driver .create_transaction() .random_valid_transfer() @@ -226,44 +408,47 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e .send() .await?; let block = driver.build_new_block_with_current_timestamp(None).await?; - // check the flashblocks number tx and block proof is in the block + // check the builder tx, funding tx and registration tx is in the block + let num_txs = block.transactions.len(); let txs = block.transactions.into_transactions_vec(); - assert_eq!(txs.len(), 8, "Expected at 5 transactions in block"); + // // 1 deposit tx, 1 regular builder tx, 4 flashblocks number tx, 1 user tx, 1 block proof tx + assert_eq!(num_txs, 8, "Expected 8 transactions in block"); // Check builder tx assert_eq!( txs[1].to(), Some(Address::ZERO), - "fallback builder tx should send to zero address" + "builder tx should send to zero address" ); // flashblocks number contract for i in 2..6 { assert_eq!( txs[i].to(), Some(FLASHBLOCKS_NUMBER_ADDRESS), - "builder tx should send to flashblocks number contract" + "builder tx should send to flashblocks number contract at index {}", + i ); } // user tx assert_eq!( txs[6].tx_hash(), *tx.tx_hash(), - "bundle tx was not in block" + "user tx should be in correct position in block" ); - // block proof assert_eq!( txs[7].to(), Some(BLOCK_BUILDER_POLICY_ADDRESS), - "block proof tx should call block policy address" + "builder tx should send verify block builder proof tx" ); - - let contract = FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); - let result = contract.getRegistration(TEE_DEBUG_ADDRESS).call().await?; - assert!(result._1.isValid, "The tee key is not registered"); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); // Verify flashblock number incremented correctly let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); let current_number = contract.getFlashblockNumber().call().await?; assert!( - current_number.gt(&U256::from(8)), // contract deployments incremented the number but we built at least 2 full blocks + current_number.gt(&U256::from(4)), // contract deployments incremented the number but we built at least 1 full block "Flashblock number not incremented" ); Ok(()) @@ -272,6 +457,7 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e async fn setup_flashtestation_contracts( driver: &ChainDriver, provider: &RootProvider, + add_quote: bool, authorize_workload: bool, ) -> eyre::Result<()> { // deploy the mock contract and register a mock quote @@ -282,15 +468,6 @@ async fn setup_flashtestation_contracts( .send() .await?; - // Add test quote - let mock_quote_tx = driver - .create_transaction() - .add_mock_quote() - .with_to(MOCK_DCAP_ADDRESS) - .with_bundle(BundleOpts::default()) - .send() - .await?; - // deploy the flashtestations registry contract let flashtestations_registry_tx = driver .create_transaction() @@ -328,6 +505,30 @@ async fn setup_flashtestation_contracts( // include the deployment and initialization in a block driver.build_new_block_with_current_timestamp(None).await?; + if add_quote { + // Add test quote + let mock_quote_tx = driver + .create_transaction() + .add_mock_quote() + .with_to(MOCK_DCAP_ADDRESS) + .with_bundle(BundleOpts::default()) + .send() + .await?; + driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*mock_quote_tx.tx_hash()) + .await? + .expect("add mock quote not mined"); + // verify registered + let registry_contract = + FlashtestationRegistry::new(FLASHTESTATION_REGISTRY_ADDRESS, provider.clone()); + let registration = registry_contract + .getRegistration(TEE_DEBUG_ADDRESS) + .call() + .await?; + assert!(registration._0, "The tee key is not registered"); + } + if authorize_workload { // add the workload id to the block builder policy let add_workload = driver @@ -342,6 +543,14 @@ async fn setup_flashtestation_contracts( .get_transaction_receipt(*add_workload.tx_hash()) .await? .expect("add workload to builder policy tx not mined"); + // verify workload id added + let policy_contract = + BlockBuilderPolicy::new(BLOCK_BUILDER_POLICY_ADDRESS, provider.clone()); + let is_allowed = policy_contract + .isAllowedPolicy(TEE_DEBUG_ADDRESS) + .call() + .await?; + assert!(is_allowed.allowed, "The policy is not allowed") } // Verify mock dcap contract deployment @@ -357,11 +566,6 @@ async fn setup_flashtestation_contracts( mock_dcap_address, MOCK_DCAP_ADDRESS, "mock dcap contract address mismatch" ); - // verify mock quote added - provider - .get_transaction_receipt(*mock_quote_tx.tx_hash()) - .await? - .expect("add mock quote not mined"); // verify flashtestations registry contract deployment let receipt = provider .get_transaction_receipt(*flashtestations_registry_tx.tx_hash()) diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs index cb5646f8..17be8d09 100644 --- a/crates/builder/op-rbuilder/src/tests/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -35,7 +35,7 @@ const MOCK_DCAP_ADDRESS: alloy_primitives::Address = alloy_primitives::address!("700b6a60ce7eaaea56f065753d8dcb9653dbad35"); #[cfg(test)] const FLASHTESTATION_REGISTRY_ADDRESS: alloy_primitives::Address = - alloy_primitives::address!("b19b36b1456e65e3a6d514d3f715f204bd59f431"); + alloy_primitives::address!("a15bb66138824a1c7167f5e85b957d04dd34e468"); #[cfg(test)] const BLOCK_BUILDER_POLICY_ADDRESS: alloy_primitives::Address = - alloy_primitives::address!("e1aa25618fa0c7a1cfdab5d6b456af611873b629"); + alloy_primitives::address!("8ce361602b935680e8dec218b820ff5056beb7af"); From 0fe01d5abb3641420bbc8da8471bcbb7ac6a2889 Mon Sep 17 00:00:00 2001 From: shana Date: Thu, 23 Oct 2025 10:11:24 -0700 Subject: [PATCH 200/262] Remove non permit flashtestation calls (#302) --- .../src/builders/flashblocks/service.rs | 2 +- .../src/builders/standard/service.rs | 2 +- .../op-rbuilder/src/flashtestations/args.rs | 29 +-- .../src/flashtestations/builder_tx.rs | 183 +----------------- .../src/flashtestations/service.rs | 52 +---- .../src/flashtestations/tx_manager.rs | 153 +++++++-------- .../op-rbuilder/src/tests/flashtestations.rs | 98 +--------- 7 files changed, 93 insertions(+), 426 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index 584252df..fe23fdbf 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -92,7 +92,7 @@ where let flashtestations_builder_tx = if let Some(builder_key) = signer && self.0.flashtestations_config.flashtestations_enabled { - match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key, ctx) + match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key) .await { Ok(builder_tx) => Some(builder_tx), diff --git a/crates/builder/op-rbuilder/src/builders/standard/service.rs b/crates/builder/op-rbuilder/src/builders/standard/service.rs index faf252b1..39484e86 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/service.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/service.rs @@ -75,7 +75,7 @@ where let flashtestations_builder_tx = if let Some(builder_key) = signer && self.0.flashtestations_config.flashtestations_enabled { - match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key, ctx) + match bootstrap_flashtestations(self.0.flashtestations_config.clone(), builder_key) .await { Ok(builder_tx) => Some(builder_tx), diff --git a/crates/builder/op-rbuilder/src/flashtestations/args.rs b/crates/builder/op-rbuilder/src/flashtestations/args.rs index fa3601bd..7856bb01 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/args.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/args.rs @@ -1,8 +1,8 @@ -use alloy_primitives::{Address, U256, utils::parse_ether}; +use alloy_primitives::Address; use clap::Parser; use reth_optimism_cli::commands::Commands; -use crate::{args::Cli, tx_signer::Signer}; +use crate::args::Cli; /// Parameters for Flashtestations configuration /// The names in the struct are prefixed with `flashtestations` @@ -52,23 +52,6 @@ pub struct FlashtestationsArgs { #[arg(long = "flashtestations.rpc-url", env = "FLASHTESTATIONS_RPC_URL")] pub rpc_url: Option, - /// Funding key for the TEE key - #[arg( - long = "flashtestations.funding-key", - env = "FLASHTESTATIONS_FUNDING_KEY", - required_if_eq("flashtestations_enabled", "true") - )] - pub funding_key: Option, - - /// Funding amount for the generated signer - #[arg( - long = "flashtestations.funding-amount", - env = "FLASHTESTATIONS_FUNDING_AMOUNT", - default_value = "1", - value_parser = parse_ether - )] - pub funding_amount: U256, - /// Enable end of block TEE proof #[arg( long = "flashtestations.enable-block-proofs", @@ -100,14 +83,6 @@ pub struct FlashtestationsArgs { default_value = "1" )] pub builder_proof_version: u8, - - /// Use permit for the flashtestation builder tx - #[arg( - long = "flashtestations.use-permit", - env = "FLASHTESTATIONS_USE_PERMIT", - default_value = "false" - )] - pub flashtestations_use_permit: bool, } impl Default for FlashtestationsArgs { diff --git a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs index 9e7f3086..005dcab5 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs @@ -1,27 +1,23 @@ -use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_evm::Database; use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, B256, Bytes, Signature, TxKind, U256, keccak256}; +use alloy_primitives::{Address, B256, Bytes, Signature, U256, keccak256}; use alloy_rpc_types_eth::TransactionInput; use alloy_sol_types::{SolCall, SolEvent, SolValue}; use core::fmt::Debug; -use op_alloy_consensus::OpTypedTransaction; use op_alloy_rpc_types::OpTransactionRequest; -use reth_evm::{ConfigureEvm, Evm, EvmError, precompiles::PrecompilesMap}; +use reth_evm::{ConfigureEvm, Evm, precompiles::PrecompilesMap}; use reth_optimism_primitives::OpTransactionSigned; use reth_provider::StateProvider; use reth_revm::{State, database::StateProviderDatabase}; -use revm::{ - DatabaseCommit, DatabaseRef, context::result::ResultAndState, inspector::NoOpInspector, -}; +use revm::{DatabaseCommit, DatabaseRef, inspector::NoOpInspector}; use std::sync::{Arc, atomic::AtomicBool}; use tracing::{debug, info, warn}; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, OpPayloadBuilderCtx, - SimulationSuccessResult, get_balance, get_nonce, + SimulationSuccessResult, get_nonce, }, flashtestations::{ BlockData, @@ -37,14 +33,11 @@ pub struct FlashtestationsBuilderTxArgs { pub attestation: Vec, pub extra_registration_data: Bytes, pub tee_service_signer: Signer, - pub funding_key: Signer, - pub funding_amount: U256, pub registry_address: Address, pub builder_policy_address: Address, pub builder_proof_version: u8, pub enable_block_proofs: bool, pub registered: bool, - pub use_permit: bool, pub builder_key: Signer, } @@ -60,10 +53,6 @@ where extra_registration_data: Bytes, // TEE service generated key tee_service_signer: Signer, - // Funding key for the TEE signer - funding_key: Signer, - // Funding amount for the TEE signer - funding_amount: U256, // Registry address for the attestation registry_address: Address, // Builder policy address for the block builder proof @@ -74,8 +63,6 @@ where registered: Arc, // Whether block proofs are enabled enable_block_proofs: bool, - // Whether to use permit for the flashtestation builder tx - use_permit: bool, // Builder key for the flashtestation permit tx builder_signer: Signer, // Extra context and data @@ -92,14 +79,11 @@ where attestation: args.attestation, extra_registration_data: args.extra_registration_data, tee_service_signer: args.tee_service_signer, - funding_key: args.funding_key, - funding_amount: args.funding_amount, registry_address: args.registry_address, builder_policy_address: args.builder_policy_address, builder_proof_version: args.builder_proof_version, registered: Arc::new(AtomicBool::new(args.registered)), enable_block_proofs: args.enable_block_proofs, - use_permit: args.use_permit, builder_signer: args.builder_key, _marker: std::marker::PhantomData, } @@ -137,136 +121,6 @@ where keccak256(&encoded) } - // TODO: deprecate in favour of permit calls - fn fund_tee_service_tx( - &self, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, - ) -> Result, BuilderTransactionError> { - let balance = get_balance(evm.db(), self.tee_service_signer.address)?; - if balance.is_zero() { - let funding_nonce = get_nonce(evm.db(), self.funding_key.address)?; - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: ctx.chain_id(), - nonce: funding_nonce, - gas_limit: 21000, - max_fee_per_gas: ctx.base_fee().into(), - to: TxKind::Call(self.tee_service_signer.address), - value: self.funding_amount, - ..Default::default() - }); - let funding_tx = self.funding_key.sign_tx(tx)?; - let da_size = - op_alloy_flz::tx_estimated_size_fjord_bytes(funding_tx.encoded_2718().as_slice()); - let ResultAndState { state, .. } = match evm.transact(&funding_tx) { - Ok(res) => res, - Err(err) => { - if err.is_invalid_tx_err() { - return Err(BuilderTransactionError::InvalidTransactionError(Box::new( - err, - ))); - } else { - return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); - } - } - }; - info!(target: "flashtestations", block_number = ctx.block_number(), tx_hash = ?funding_tx.tx_hash(), "adding funding tx to builder txs"); - evm.db_mut().commit(state); - Ok(Some(BuilderTransactionCtx { - gas_used: 21000, - da_size, - signed_tx: funding_tx, - is_top_of_block: false, - })) - } else { - Ok(None) - } - } - - // TODO: deprecate in favour of permit calls - fn register_tee_service_tx( - &self, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm<&mut State, NoOpInspector, PrecompilesMap>, - ) -> Result { - let calldata = IFlashtestationRegistry::registerTEEServiceCall { - rawQuote: self.attestation.clone().into(), - extendedRegistrationData: self.extra_registration_data.clone(), - }; - let SimulationSuccessResult { - gas_used, - state_changes, - .. - } = self.flashtestation_call( - self.registry_address, - calldata.clone(), - vec![TEEServiceRegistered::SIGNATURE_HASH], - ctx, - evm, - )?; - let signed_tx = self.sign_tx( - self.registry_address, - self.tee_service_signer, - gas_used, - calldata.abi_encode().into(), - ctx, - evm.db(), - )?; - let da_size = - op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); - // commit the register transaction state so the block proof transaction can succeed - evm.db_mut().commit(state_changes); - Ok(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx, - is_top_of_block: false, - }) - } - - // TODO: remove in favour of permit calls - fn verify_block_proof_tx( - &self, - transactions: Vec, - ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm, - ) -> Result { - let block_content_hash = Self::compute_block_content_hash( - &transactions, - ctx.parent_hash(), - ctx.block_number(), - ctx.timestamp(), - ); - - let calldata = IBlockBuilderPolicy::verifyBlockBuilderProofCall { - blockContentHash: block_content_hash, - version: self.builder_proof_version, - }; - let SimulationSuccessResult { gas_used, .. } = self.flashtestation_call( - self.builder_policy_address, - calldata.clone(), - vec![BlockBuilderProofVerified::SIGNATURE_HASH], - ctx, - evm, - )?; - let signed_tx = self.sign_tx( - self.builder_policy_address, - self.tee_service_signer, - gas_used, - calldata.abi_encode().into(), - ctx, - evm.db(), - )?; - let da_size = - op_alloy_flz::tx_estimated_size_fjord_bytes(signed_tx.encoded_2718().as_slice()); - Ok(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx, - is_top_of_block: false, - }) - } - fn set_registered( &self, state_provider: impl StateProvider + Clone, @@ -534,35 +388,18 @@ where if !self.registered.load(std::sync::atomic::Ordering::SeqCst) { info!(target: "flashtestations", "tee service not registered yet, attempting to register"); - let register_tx = if self.use_permit { - self.signed_registration_permit_tx(ctx, &mut evm)? - } else { - builder_txs.extend(self.fund_tee_service_tx(ctx, &mut evm)?); - self.register_tee_service_tx(ctx, &mut evm)? - }; + let register_tx = self.signed_registration_permit_tx(ctx, &mut evm)?; builder_txs.push(register_tx); } // don't return on error for block proof as previous txs in builder_txs will not be returned if self.enable_block_proofs { - if self.use_permit { - debug!(target: "flashtestations", "adding permit verify block proof tx"); - match self.signed_block_proof_permit_tx(&info.executed_transactions, ctx, &mut evm) - { - Ok(block_proof_tx) => builder_txs.push(block_proof_tx), - Err(e) => { - warn!(target: "flashtestations", error = ?e, "failed to add permit block proof transaction") - } + debug!(target: "flashtestations", "adding permit verify block proof tx"); + match self.signed_block_proof_permit_tx(&info.executed_transactions, ctx, &mut evm) { + Ok(block_proof_tx) => builder_txs.push(block_proof_tx), + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add permit block proof transaction") } - } else { - // add verify block proof tx - match self.verify_block_proof_tx(info.executed_transactions.clone(), ctx, &mut evm) - { - Ok(block_proof_tx) => builder_txs.push(block_proof_tx), - Err(e) => { - warn!(target: "flashtestations", error = ?e, "failed to add block proof transaction") - } - }; } } Ok(builder_txs) diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 747fd2ab..3bd5b854 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -1,5 +1,4 @@ use alloy_primitives::{B256, Bytes, keccak256}; -use reth_node_builder::BuilderContext; use std::{ fs::{self, OpenOptions}, io::Write, @@ -15,18 +14,15 @@ use super::{ }; use crate::{ flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, - traits::NodeBounds, tx_signer::{Signer, generate_key_from_seed, generate_signer}, }; use std::fmt::Debug; -pub async fn bootstrap_flashtestations( +pub async fn bootstrap_flashtestations( args: FlashtestationsArgs, builder_key: Signer, - ctx: &BuilderContext, ) -> eyre::Result> where - Node: NodeBounds, ExtraCtx: Debug + Default, Extra: Debug + Default, { @@ -41,9 +37,6 @@ where tee_service_signer.address ); - let funding_key = args - .funding_key - .expect("funding key required when flashtestations enabled"); let registry_address = args .registry_address .expect("registry address required when flashtestations enabled"); @@ -81,70 +74,41 @@ where info!(target: "flashtestations", "requesting TDX attestation"); let attestation = attestation_provider.get_attestation(report_data).await?; - // TODO: support permit with an external rpc, skip this step if using permit signatures - // since the permit txs are signed by the builder key and will result in nonce issues - let (tx_manager, registered) = if let Some(rpc_url) = args.rpc_url - && !args.flashtestations_use_permit - { + // Use an external rpc when the builder is not the same as the builder actively building blocks onchain + let registered = if let Some(rpc_url) = args.rpc_url { let tx_manager = TxManager::new( tee_service_signer, - funding_key, + builder_key, rpc_url.clone(), registry_address, ); // Submit report onchain by registering the key of the tee service match tx_manager - .fund_and_register_tee_service( - attestation.clone(), - ext_data.clone(), - args.funding_amount, - ) + .register_tee_service(attestation.clone(), ext_data.clone()) .await { - Ok(_) => (Some(tx_manager), true), + Ok(_) => true, Err(e) => { warn!(error = %e, "Failed to register tee service via rpc"); - (Some(tx_manager), false) + false } } } else { - (None, false) + false }; let flashtestations_builder_tx = FlashtestationsBuilderTx::new(FlashtestationsBuilderTxArgs { attestation, extra_registration_data: ext_data, tee_service_signer, - funding_key, - funding_amount: args.funding_amount, registry_address, builder_policy_address, builder_proof_version: args.builder_proof_version, enable_block_proofs: args.enable_block_proofs, registered, - use_permit: args.flashtestations_use_permit, builder_key, }); - ctx.task_executor() - .spawn_critical_with_graceful_shutdown_signal( - "flashtestations clean up task", - |shutdown| { - Box::pin(async move { - let graceful_guard = shutdown.await; - if let Some(tx_manager) = tx_manager { - if let Err(e) = tx_manager.clean_up().await { - warn!( - error = %e, - "Failed to complete clean up for flashtestations service", - ); - } - } - drop(graceful_guard) - }) - }, - ); - Ok(flashtestations_builder_tx) } diff --git a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs index a49f50a3..d6412bf6 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/tx_manager.rs @@ -1,6 +1,6 @@ use alloy_json_rpc::RpcError; use alloy_network::ReceiptResponse; -use alloy_primitives::{Address, Bytes, TxHash, TxKind, U256}; +use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256}; use alloy_rpc_types_eth::TransactionRequest; use alloy_sol_types::SolCall; use alloy_transport::{TransportError, TransportErrorKind, TransportResult}; @@ -14,7 +14,13 @@ use alloy_signer_local::PrivateKeySigner; use op_alloy_network::Optimism; use tracing::{debug, info, warn}; -use crate::{flashtestations::IFlashtestationRegistry, tx_signer::Signer}; +use crate::{ + flashtestations::{ + IERC20Permit::{self}, + IFlashtestationRegistry, + }, + tx_signer::Signer, +}; #[derive(Debug, thiserror::Error)] pub enum TxManagerError { @@ -28,12 +34,14 @@ pub enum TxManagerError { TxRpcError(RpcError), #[error("signer error: {0}")] SignerError(ecdsa::Error), + #[error("error signing message: {0}")] + SignatureError(secp256k1::Error), } #[derive(Debug, Clone)] pub struct TxManager { tee_service_signer: Signer, - funding_signer: Signer, + builder_signer: Signer, rpc_url: String, registry_address: Address, } @@ -41,78 +49,27 @@ pub struct TxManager { impl TxManager { pub fn new( tee_service_signer: Signer, - funding_signer: Signer, + builder_signer: Signer, rpc_url: String, registry_address: Address, ) -> Self { Self { tee_service_signer, - funding_signer, + builder_signer, rpc_url, registry_address, } } - pub async fn fund_address( - &self, - from: Signer, - to: Address, - amount: U256, - ) -> Result<(), TxManagerError> { - let funding_wallet = PrivateKeySigner::from_bytes(&from.secret.secret_bytes().into()) - .map_err(TxManagerError::SignerError)?; - let provider = ProviderBuilder::new() - .disable_recommended_fillers() - .fetch_chain_id() - .with_gas_estimation() - .with_cached_nonce_management() - .wallet(funding_wallet) - .network::() - .connect(self.rpc_url.as_str()) - .await?; - - // Create funding transaction - let funding_tx = TransactionRequest { - from: Some(from.address), - to: Some(TxKind::Call(to)), - value: Some(amount), - gas: Some(21_000), // Standard gas for ETH transfer - ..Default::default() - }; - - // Send funding transaction - match Self::process_pending_tx(provider.send_transaction(funding_tx.into()).await).await { - Ok(tx_hash) => { - info!(target: "flashtestations", tx_hash = %tx_hash, "funding transaction confirmed successfully"); - Ok(()) - } - Err(e) => { - warn!(target: "flashtestations", error = %e, "funding transaction failed"); - Err(e) - } - } - } - - pub async fn fund_and_register_tee_service( + pub async fn register_tee_service( &self, attestation: Vec, extra_registration_data: Bytes, - funding_amount: U256, ) -> Result<(), TxManagerError> { info!(target: "flashtestations", "funding TEE address at {}", self.tee_service_signer.address); - self.fund_address( - self.funding_signer, - self.tee_service_signer.address, - funding_amount, - ) - .await - .unwrap_or_else(|e| { - warn!(target: "flashtestations", error = %e, "Failed to fund TEE address, attempting to register without funding"); - }); - let quote_bytes = Bytes::from(attestation); let wallet = - PrivateKeySigner::from_bytes(&self.tee_service_signer.secret.secret_bytes().into()) + PrivateKeySigner::from_bytes(&self.builder_signer.secret.secret_bytes().into()) .map_err(TxManagerError::SignerError)?; let provider = ProviderBuilder::new() .disable_recommended_fillers() @@ -126,9 +83,63 @@ impl TxManager { info!(target: "flashtestations", "submitting quote to registry at {}", self.registry_address); - let calldata = IFlashtestationRegistry::registerTEEServiceCall { + // Get permit nonce + let nonce_call = IERC20Permit::noncesCall { + owner: self.tee_service_signer.address, + }; + let nonce_tx = TransactionRequest { + to: Some(TxKind::Call(self.registry_address)), + input: nonce_call.abi_encode().into(), + ..Default::default() + }; + let nonce = U256::from_be_slice(provider.call(nonce_tx.into()).await?.as_ref()); + + // Set deadline 1 hour from now + let deadline = U256::from( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + + 3600, + ); + + // Call computeStructHash to get the struct hash + let struct_hash_call = IFlashtestationRegistry::computeStructHashCall { + rawQuote: quote_bytes.clone(), + extendedRegistrationData: extra_registration_data.clone(), + nonce, + deadline, + }; + let struct_hash_tx = TransactionRequest { + to: Some(TxKind::Call(self.registry_address)), + input: struct_hash_call.abi_encode().into(), + ..Default::default() + }; + let struct_hash = B256::from_slice(provider.call(struct_hash_tx.into()).await?.as_ref()); + + // Get typed data hash + let typed_hash_call = IFlashtestationRegistry::hashTypedDataV4Call { + structHash: struct_hash, + }; + let typed_hash_tx = TransactionRequest { + to: Some(TxKind::Call(self.registry_address)), + input: typed_hash_call.abi_encode().into(), + ..Default::default() + }; + let message_hash = B256::from_slice(provider.call(typed_hash_tx.into()).await?.as_ref()); + + // Sign the hash + let signature = self + .tee_service_signer + .sign_message(message_hash) + .map_err(TxManagerError::SignatureError)?; + + let calldata = IFlashtestationRegistry::permitRegisterTEEServiceCall { rawQuote: quote_bytes, extendedRegistrationData: extra_registration_data, + nonce, + deadline, + signature: signature.as_bytes().into(), } .abi_encode(); let tx = TransactionRequest { @@ -149,30 +160,6 @@ impl TxManager { } } - pub async fn clean_up(&self) -> Result<(), TxManagerError> { - info!(target: "flashtestations", "sending funds back from TEE generated key to funding address"); - let provider = ProviderBuilder::new() - .disable_recommended_fillers() - .network::() - .connect(self.rpc_url.as_str()) - .await?; - let balance = provider - .get_balance(self.tee_service_signer.address) - .await?; - let gas_estimate = 21_000u128; - let gas_price = provider.get_gas_price().await?; - let gas_cost = U256::from(gas_estimate * gas_price); - if balance.gt(&gas_cost) { - self.fund_address( - self.tee_service_signer, - self.funding_signer.address, - balance.saturating_sub(gas_cost), - ) - .await?; - } - Ok(()) - } - /// Processes a pending transaction and logs whether the transaction succeeded or not async fn process_pending_tx( pending_tx_result: TransportResult>, diff --git a/crates/builder/op-rbuilder/src/tests/flashtestations.rs b/crates/builder/op-rbuilder/src/tests/flashtestations.rs index 544eb5d8..c0cc0250 100644 --- a/crates/builder/op-rbuilder/src/tests/flashtestations.rs +++ b/crates/builder/op-rbuilder/src/tests/flashtestations.rs @@ -12,11 +12,9 @@ use crate::{ BLOCK_BUILDER_POLICY_ADDRESS, BundleOpts, ChainDriver, ChainDriverExt, FLASHBLOCKS_NUMBER_ADDRESS, FLASHTESTATION_REGISTRY_ADDRESS, LocalInstance, MOCK_DCAP_ADDRESS, TEE_DEBUG_ADDRESS, TransactionBuilderExt, - block_builder_policy::{BlockBuilderPolicy, IFlashtestationRegistry::RegisteredTEE}, - builder_signer, + block_builder_policy::BlockBuilderPolicy, builder_signer, flashblocks_number_contract::FlashblocksNumber, flashtestation_registry::FlashtestationRegistry, - flashtestations_signer, }, }; @@ -27,92 +25,6 @@ use crate::{ flashtestations_enabled: true, registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), - debug: true, - ..Default::default() - }, - ..Default::default() -})] -async fn test_flashtestations_registrations(rbuilder: LocalInstance) -> eyre::Result<()> { - let driver = rbuilder.driver().await?; - let provider = rbuilder.provider().await?; - setup_flashtestation_contracts(&driver, &provider, true, true).await?; - // check builder does not try to register again - let block = driver.build_new_block_with_current_timestamp(None).await?; - let num_txs = block.transactions.len(); - if_flashblocks!( - assert!(num_txs == 3, "Expected 3 transactions in block"); // deposit + 2 builder tx - ); - if_standard!( - assert!(num_txs == 2, "Expected 2 transactions in block"); // deposit + builder tx - ); - - Ok(()) -} - -#[rb_test(args = OpRbuilderArgs { - chain_block_time: 1000, - enable_revert_protection: true, - flashtestations: FlashtestationsArgs { - flashtestations_enabled: true, - registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), - builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), - debug: true, - enable_block_proofs: true, - ..Default::default() - }, - ..Default::default() -})] -async fn test_flashtestations_block_proofs(rbuilder: LocalInstance) -> eyre::Result<()> { - let driver = rbuilder.driver().await?; - let provider = rbuilder.provider().await?; - setup_flashtestation_contracts(&driver, &provider, true, true).await?; - // check that only the builder tx and block proof is in the block - let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; - let txs = block.transactions.into_transactions_vec(); - if_flashblocks!( - assert_eq!(txs.len(), 5, "Expected 5 transactions in block"); // deposit + valid tx + 2 builder tx + end of block proof - // Check builder tx - assert_eq!( - txs[1].to(), - Some(Address::ZERO), - "builder tx should send to zero address" - ); - ); - if_standard!( - assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + builder tx + end of block proof - ); - let last_3_txs = &txs[txs.len() - 3..]; - // Check valid transaction - assert_eq!( - last_3_txs[0].inner.tx_hash(), - tx_hash, - "tx hash for valid transaction should match" - ); - // Check builder tx - assert_eq!( - last_3_txs[1].to(), - Some(Address::ZERO), - "builder tx should send to zero address" - ); - // Check builder proof - assert_eq!( - last_3_txs[2].to(), - Some(BLOCK_BUILDER_POLICY_ADDRESS), - "builder tx should send to zero address" - ); - Ok(()) -} - -#[rb_test(args = OpRbuilderArgs { - chain_block_time: 1000, - enable_revert_protection: true, - flashtestations: FlashtestationsArgs { - flashtestations_enabled: true, - registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), - builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), debug: true, enable_block_proofs: true, ..Default::default() @@ -172,7 +84,6 @@ async fn test_flashtestations_invalid_quote(rbuilder: LocalInstance) -> eyre::Re flashtestations_enabled: true, registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), debug: true, enable_block_proofs: true, ..Default::default() @@ -226,7 +137,6 @@ async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> flashtestations_enabled: true, registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), debug: true, enable_block_proofs: true, ..Default::default() @@ -292,9 +202,7 @@ async fn test_flashtestations_with_number_contract(rbuilder: LocalInstance) -> e flashtestations_enabled: true, registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), debug: true, - flashtestations_use_permit: true, ..Default::default() }, ..Default::default() @@ -327,10 +235,8 @@ async fn test_flashtestations_permit_registration(rbuilder: LocalInstance) -> ey flashtestations_enabled: true, registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), debug: true, enable_block_proofs: true, - flashtestations_use_permit: true, ..Default::default() }, ..Default::default() @@ -386,9 +292,7 @@ async fn test_flashtestations_permit_block_proof(rbuilder: LocalInstance) -> eyr flashtestations_enabled: true, registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), - funding_key: Some(flashtestations_signer()), debug: true, - flashtestations_use_permit: true, enable_block_proofs: true, ..Default::default() }, From 1b7099367c25588d325605badd13c0f88d7b52dd Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Mon, 27 Oct 2025 17:04:19 -0400 Subject: [PATCH 201/262] feat: implement p2p layer and broadcast flashblocks (#275) --- crates/builder/op-rbuilder/Cargo.toml | 8 +- crates/builder/op-rbuilder/src/args/op.rs | 47 ++ .../op-rbuilder/src/builders/builder_tx.rs | 4 +- .../op-rbuilder/src/builders/context.rs | 4 +- .../src/builders/flashblocks/config.rs | 25 + .../src/builders/flashblocks/mod.rs | 2 + .../src/builders/flashblocks/p2p.rs | 60 ++ .../src/builders/flashblocks/payload.rs | 82 ++- .../builders/flashblocks/payload_handler.rs | 64 ++ .../src/builders/flashblocks/service.rs | 84 ++- .../src/builders/flashblocks/wspub.rs | 25 +- .../builder/op-rbuilder/src/builders/mod.rs | 2 + crates/builder/op-rbuilder/src/metrics.rs | 14 +- .../op-rbuilder/src/tests/flashblocks.rs | 5 + crates/builder/p2p/Cargo.toml | 28 + crates/builder/p2p/src/behaviour.rs | 106 ++++ crates/builder/p2p/src/lib.rs | 574 ++++++++++++++++++ crates/builder/p2p/src/outgoing.rs | 95 +++ 18 files changed, 1134 insertions(+), 95 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/p2p.rs create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs create mode 100644 crates/builder/p2p/Cargo.toml create mode 100644 crates/builder/p2p/src/behaviour.rs create mode 100644 crates/builder/p2p/src/lib.rs create mode 100644 crates/builder/p2p/src/outgoing.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 8ae25146..d49d4951 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -12,6 +12,8 @@ default-run = "op-rbuilder" workspace = true [dependencies] +p2p = { path = "../p2p" } + reth.workspace = true reth-optimism-node.workspace = true reth-optimism-cli.workspace = true @@ -109,10 +111,11 @@ url.workspace = true anyhow = "1" opentelemetry = { workspace = true, optional = true } dashmap.workspace = true +hex = { workspace = true } +futures = { workspace = true } +futures-util = { workspace = true } tower = "0.5" -futures = "0.3" -futures-util = "0.3.31" time = { version = "0.3.36", features = ["macros", "formatting", "parsing"] } chrono = "0.4" uuid = { version = "1.6.1", features = ["serde", "v5", "v4"] } @@ -124,7 +127,6 @@ serde_yaml = { version = "0.9" } moka = "0.12" http = "1.0" sha3 = "0.10" -hex = "0.4" reqwest = "0.12.23" k256 = "0.13.4" diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index bd860f15..b0b90f84 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -166,6 +166,10 @@ pub struct FlashblocksArgs { env = "FLASHBLOCK_NUMBER_CONTRACT_ADDRESS" )] pub flashblocks_number_contract_address: Option
, + + /// Flashblocks p2p configuration + #[command(flatten)] + pub p2p: FlashblocksP2pArgs, } impl Default for FlashblocksArgs { @@ -178,6 +182,49 @@ impl Default for FlashblocksArgs { } } +#[derive(Debug, Clone, PartialEq, Eq, clap::Args)] +pub struct FlashblocksP2pArgs { + /// Enable libp2p networking for flashblock propagation + #[arg( + long = "flashblocks.p2p_enabled", + env = "FLASHBLOCK_P2P_ENABLED", + default_value = "false" + )] + pub p2p_enabled: bool, + + /// Port for the flashblocks p2p node + #[arg( + long = "flashblocks.p2p_port", + env = "FLASHBLOCK_P2P_PORT", + default_value = "9009" + )] + pub p2p_port: u16, + + /// Path to the file containing a hex-encoded libp2p private key. + /// If the file does not exist, a new key will be generated. + #[arg( + long = "flashblocks.p2p_private_key_file", + env = "FLASHBLOCK_P2P_PRIVATE_KEY_FILE" + )] + pub p2p_private_key_file: Option, + + /// Comma-separated list of multiaddrs of known Flashblocks peers + /// Example: "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ,/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ" + #[arg( + long = "flashblocks.p2p_known_peers", + env = "FLASHBLOCK_P2P_KNOWN_PEERS" + )] + pub p2p_known_peers: Option, + + /// Maximum number of peers for the flashblocks p2p node + #[arg( + long = "flashblocks.p2p_max_peer_count", + env = "FLASHBLOCK_P2P_MAX_PEER_COUNT", + default_value = "50" + )] + pub p2p_max_peer_count: u32, +} + /// Parameters for telemetry configuration #[derive(Debug, Clone, Default, PartialEq, Eq, clap::Args)] pub struct TelemetryArgs { diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index 89b21997..11194a38 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -4,7 +4,7 @@ use alloy_evm::Database; use alloy_op_evm::OpEvm; use alloy_primitives::{ Address, B256, Bytes, TxKind, U256, - map::foldhash::{HashMap, HashSet, HashSetExt}, + map::{HashMap, HashSet}, }; use alloy_sol_types::{ContractError, Revert, SolCall, SolError, SolInterface}; use core::fmt::Debug; @@ -192,7 +192,7 @@ pub trait BuilderTransactions = HashSet::new(); + let mut invalid = HashSet::new(); for builder_tx in builder_txs.iter() { if builder_tx.is_top_of_block != top_of_block { diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 032e97e6..54894d28 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -544,9 +544,9 @@ impl OpPayloadBuilderCtx { info.executed_transactions.push(tx.into_inner()); } - let payload_tx_simulation_time = execute_txs_start_time.elapsed(); + let payload_transaction_simulation_time = execute_txs_start_time.elapsed(); self.metrics.set_payload_builder_metrics( - payload_tx_simulation_time, + payload_transaction_simulation_time, num_txs_considered, num_txs_simulated, num_txs_simulated_success, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index f2dca775..a3345edb 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -38,6 +38,21 @@ pub struct FlashblocksConfig { /// /// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx. pub flashblocks_number_contract_address: Option
, + + /// Whether to enable the p2p node for flashblocks + pub p2p_enabled: bool, + + /// Port for the p2p node + pub p2p_port: u16, + + /// Optional hex-encoded private key file path for the p2p node + pub p2p_private_key_file: Option, + + /// Comma-separated list of multiaddresses of known peers to connect to + pub p2p_known_peers: Option, + + /// Maximum number of peers for the p2p node + pub p2p_max_peer_count: u32, } impl Default for FlashblocksConfig { @@ -49,6 +64,11 @@ impl Default for FlashblocksConfig { fixed: false, calculate_state_root: true, flashblocks_number_contract_address: None, + p2p_enabled: false, + p2p_port: 9009, + p2p_private_key_file: None, + p2p_known_peers: None, + p2p_max_peer_count: 50, } } } @@ -80,6 +100,11 @@ impl TryFrom for FlashblocksConfig { fixed, calculate_state_root, flashblocks_number_contract_address, + p2p_enabled: args.flashblocks.p2p.p2p_enabled, + p2p_port: args.flashblocks.p2p.p2p_port, + p2p_private_key_file: args.flashblocks.p2p.p2p_private_key_file, + p2p_known_peers: args.flashblocks.p2p.p2p_known_peers, + p2p_max_peer_count: args.flashblocks.p2p.p2p_max_peer_count, }) } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs index 10503fa5..643bf39a 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs @@ -6,7 +6,9 @@ use service::FlashblocksServiceBuilder; mod best_txs; mod builder_tx; mod config; +mod p2p; mod payload; +mod payload_handler; mod service; mod wspub; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/p2p.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/p2p.rs new file mode 100644 index 00000000..9f947d95 --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/p2p.rs @@ -0,0 +1,60 @@ +use alloy_primitives::U256; +use reth::{core::primitives::SealedBlock, payload::PayloadId}; +use reth_optimism_payload_builder::OpBuiltPayload as RethOpBuiltPayload; +use reth_optimism_primitives::OpBlock; +use serde::{Deserialize, Serialize}; + +pub(super) const AGENT_VERSION: &str = "op-rbuilder/1.0.0"; +pub(super) const FLASHBLOCKS_STREAM_PROTOCOL: p2p::StreamProtocol = + p2p::StreamProtocol::new("/flashblocks/1.0.0"); + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub(super) enum Message { + OpBuiltPayload(OpBuiltPayload), +} + +impl p2p::Message for Message { + fn protocol(&self) -> p2p::StreamProtocol { + FLASHBLOCKS_STREAM_PROTOCOL + } +} + +/// Internal type analogous to [`reth_optimism_payload_builder::OpBuiltPayload`] +/// which additionally implements `Serialize` and `Deserialize` for p2p transmission. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub(crate) struct OpBuiltPayload { + /// Identifier of the payload + pub(crate) id: PayloadId, + /// Sealed block + pub(crate) block: SealedBlock, + /// The fees of the block + pub(crate) fees: U256, +} + +impl From for Message { + fn from(value: RethOpBuiltPayload) -> Self { + Message::OpBuiltPayload(value.into()) + } +} + +impl From for Message { + fn from(value: OpBuiltPayload) -> Self { + Message::OpBuiltPayload(value) + } +} + +impl From for RethOpBuiltPayload { + fn from(value: OpBuiltPayload) -> Self { + RethOpBuiltPayload::new(value.id, value.block.into(), value.fees, None) + } +} + +impl From for OpBuiltPayload { + fn from(value: RethOpBuiltPayload) -> Self { + OpBuiltPayload { + id: value.id(), + block: value.block().clone(), + fees: value.fees(), + } + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index e350260b..9e2237bc 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -27,9 +27,8 @@ use reth_node_api::{Block, NodePrimitives, 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, OpEngineTypes, OpPayloadBuilderAttributes}; +use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; -use reth_payload_builder_primitives::Events; use reth_payload_util::BestPayloadTransactions; use reth_primitives_traits::RecoveredBlock; use reth_provider::{ @@ -48,7 +47,7 @@ use rollup_boost::{ use serde::{Deserialize, Serialize}; use std::{ ops::{Div, Rem}, - sync::{Arc, OnceLock}, + sync::Arc, time::Instant, }; use tokio::sync::mpsc; @@ -134,6 +133,9 @@ pub(super) struct OpPayloadBuilder { pub pool: Pool, /// Node client pub client: Client, + /// Sender for sending built payloads to [`PayloadHandler`], + /// which broadcasts outgoing payloads via p2p. + pub payload_tx: mpsc::Sender, /// WebSocket publisher for broadcasting flashblocks /// to all connected subscribers. pub ws_pub: Arc, @@ -143,9 +145,6 @@ pub(super) struct OpPayloadBuilder { pub metrics: Arc, /// The end of builder transaction type pub builder_tx: BuilderTx, - /// Builder events handle to send BuiltPayload events - pub payload_builder_handle: - Arc>>>, /// Rate limiting based on gas. This is an optional feature. pub address_gas_limiter: AddressGasLimiter, } @@ -158,9 +157,7 @@ impl OpPayloadBuilder { client: Client, config: BuilderConfig, builder_tx: BuilderTx, - payload_builder_handle: Arc< - OnceLock>>, - >, + payload_tx: mpsc::Sender, ) -> eyre::Result { let metrics = Arc::new(OpRBuilderMetrics::default()); let ws_pub = WebSocketPublisher::new(config.specific.ws_addr, Arc::clone(&metrics))?.into(); @@ -169,11 +166,11 @@ impl OpPayloadBuilder { evm_config, pool, client, + payload_tx, ws_pub, config, metrics, builder_tx, - payload_builder_handle, address_gas_limiter, }) } @@ -360,7 +357,10 @@ where calculate_state_root || ctx.attributes().no_tx_pool, // need to calculate state root for CL sync )?; - self.send_payload_to_engine(payload.clone()); + self.payload_tx + .send(payload.clone()) + .await + .map_err(PayloadBuilderError::other)?; best_payload.set(payload); info!( @@ -525,16 +525,19 @@ where } // build first flashblock immediately - let next_flashblocks_ctx = match self.build_next_flashblock( - &mut ctx, - &mut info, - &mut state, - &state_provider, - &mut best_txs, - &block_cancel, - &best_payload, - &fb_span, - ) { + let next_flashblocks_ctx = match self + .build_next_flashblock( + &ctx, + &mut info, + &mut state, + &state_provider, + &mut best_txs, + &block_cancel, + &best_payload, + &fb_span, + ) + .await + { Ok(Some(next_flashblocks_ctx)) => next_flashblocks_ctx, Ok(None) => { self.record_flashblocks_metrics( @@ -577,12 +580,12 @@ where } #[allow(clippy::too_many_arguments)] - fn build_next_flashblock< + async fn build_next_flashblock< DB: Database + std::fmt::Debug + AsRef

, P: StateRootProvider + HashedPostStateProvider + StorageRootProvider, >( &self, - ctx: &mut OpPayloadBuilderCtx, + ctx: &OpPayloadBuilderCtx, info: &mut ExecutionInfo, state: &mut State, state_provider: impl reth::providers::StateProvider + Clone, @@ -675,13 +678,13 @@ where return Ok(None); } - let payload_tx_simulation_time = tx_execution_start_time.elapsed(); + let payload_transaction_simulation_time = tx_execution_start_time.elapsed(); ctx.metrics - .payload_tx_simulation_duration - .record(payload_tx_simulation_time); + .payload_transaction_simulation_duration + .record(payload_transaction_simulation_time); ctx.metrics - .payload_tx_simulation_gauge - .set(payload_tx_simulation_time); + .payload_transaction_simulation_gauge + .set(payload_transaction_simulation_time); if let Err(e) = self .builder_tx @@ -730,7 +733,10 @@ where .ws_pub .publish(&fb_payload) .wrap_err("failed to publish flashblock via websocket")?; - self.send_payload_to_engine(new_payload.clone()); + self.payload_tx + .send(new_payload.clone()) + .await + .wrap_err("failed to send built payload to handler")?; best_payload.set(new_payload); // Record flashblock build duration @@ -809,24 +815,6 @@ where span.record("flashblock_count", ctx.flashblock_index()); } - /// Sends built payload via payload builder handle broadcast channel to the engine - pub(super) fn send_payload_to_engine(&self, payload: OpBuiltPayload) { - // Send built payload as created one - match self.payload_builder_handle.get() { - Some(handle) => { - let res = handle.send(Events::BuiltPayload(payload.clone())); - if let Err(e) = res { - error!( - message = "Failed to send payload via payload builder handle", - error = ?e, - ); - } - } - None => { - error!(message = "Payload builder handle is not setup, skipping sending payload") - } - } - } /// 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) { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs new file mode 100644 index 00000000..e19bb19c --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs @@ -0,0 +1,64 @@ +use crate::builders::flashblocks::p2p::Message; +use reth_node_builder::Events; +use reth_optimism_node::OpEngineTypes; +use reth_optimism_payload_builder::OpBuiltPayload; +use tokio::sync::mpsc; +use tracing::warn; + +pub(crate) struct PayloadHandler { + // receives new payloads built by us. + built_rx: mpsc::Receiver, + // receives incoming p2p messages from peers. + p2p_rx: mpsc::Receiver, + // outgoing p2p channel to broadcast new payloads to peers. + p2p_tx: mpsc::Sender, + // sends a `Events::BuiltPayload` to the reth payload builder when a new payload is received. + payload_events_handle: tokio::sync::broadcast::Sender>, +} + +impl PayloadHandler { + pub(crate) fn new( + built_rx: mpsc::Receiver, + p2p_rx: mpsc::Receiver, + p2p_tx: mpsc::Sender, + payload_events_handle: tokio::sync::broadcast::Sender>, + ) -> Self { + Self { + built_rx, + p2p_rx, + p2p_tx, + payload_events_handle, + } + } + + pub(crate) async fn run(self) { + let Self { + mut built_rx, + mut p2p_rx, + p2p_tx, + payload_events_handle, + } = self; + + tracing::info!("flashblocks payload handler started"); + + loop { + tokio::select! { + Some(payload) = built_rx.recv() => { + if let Err(e) = payload_events_handle.send(Events::BuiltPayload(payload.clone())) { + warn!(e = ?e, "failed to send BuiltPayload event"); + } + // ignore error here; if p2p was disabled, the channel will be closed. + let _ = p2p_tx.send(payload.into()).await; + } + Some(message) = p2p_rx.recv() => { + match message { + Message::OpBuiltPayload(payload) => { + let payload: OpBuiltPayload = payload.into(); + let _ = payload_events_handle.send(Events::BuiltPayload(payload)); + } + } + } + } + } + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index fe23fdbf..68d4b036 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -5,20 +5,22 @@ use crate::{ builder_tx::BuilderTransactions, flashblocks::{ builder_tx::{FlashblocksBuilderTx, FlashblocksNumberBuilderTx}, + p2p::{AGENT_VERSION, FLASHBLOCKS_STREAM_PROTOCOL, Message}, payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, + payload_handler::PayloadHandler, }, generator::BlockPayloadJobGenerator, }, flashtestations::service::bootstrap_flashtestations, traits::{NodeBounds, PoolBounds}, }; +use eyre::WrapErr as _; use reth_basic_payload_builder::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 std::sync::Arc; pub struct FlashblocksServiceBuilder(pub BuilderConfig); @@ -39,16 +41,72 @@ impl FlashblocksServiceBuilder { + Sync + 'static, { - let once_lock = Arc::new(std::sync::OnceLock::new()); + let (incoming_message_rx, outgoing_message_tx) = if self.0.specific.p2p_enabled { + let mut builder = p2p::NodeBuilder::new(); + if let Some(ref private_key_file) = self.0.specific.p2p_private_key_file + && !private_key_file.is_empty() + { + let private_key_hex = std::fs::read_to_string(private_key_file) + .wrap_err_with(|| { + format!("failed to read p2p private key file: {private_key_file}") + })? + .trim() + .to_string(); + builder = builder.with_keypair_hex_string(private_key_hex); + } + + let known_peers: Vec = + if let Some(ref p2p_known_peers) = self.0.specific.p2p_known_peers { + p2p_known_peers + .split(',') + .map(|s| s.to_string()) + .filter_map(|s| s.parse().ok()) + .collect() + } else { + vec![] + }; + + let p2p::NodeBuildResult { + node, + outgoing_message_tx, + mut incoming_message_rxs, + } = builder + .with_agent_version(AGENT_VERSION.to_string()) + .with_protocol(FLASHBLOCKS_STREAM_PROTOCOL) + .with_known_peers(known_peers) + .with_port(self.0.specific.p2p_port) + .with_max_peer_count(self.0.specific.p2p_max_peer_count) + .try_build::() + .wrap_err("failed to build flashblocks p2p node")?; + let multiaddrs = node.multiaddrs(); + ctx.task_executor().spawn(async move { + if let Err(e) = node.run().await { + tracing::error!(error = %e, "p2p node exited"); + } + }); + tracing::info!(multiaddrs = ?multiaddrs, "flashblocks p2p node started"); + + let incoming_message_rx = incoming_message_rxs + .remove(&FLASHBLOCKS_STREAM_PROTOCOL) + .expect("flashblocks p2p protocol must be found in receiver map"); + (incoming_message_rx, outgoing_message_tx) + } else { + let (_incoming_message_tx, incoming_message_rx) = tokio::sync::mpsc::channel(16); + let (outgoing_message_tx, _outgoing_message_rx) = tokio::sync::mpsc::channel(16); + (incoming_message_rx, outgoing_message_tx) + }; + + let (built_payload_tx, built_payload_rx) = tokio::sync::mpsc::channel(16); let payload_builder = OpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), pool, ctx.provider().clone(), self.0.clone(), builder_tx, - once_lock.clone(), - )?; + built_payload_tx, + ) + .wrap_err("failed to create flashblocks payload builder")?; let payload_job_config = BasicPayloadJobGeneratorConfig::default(); @@ -61,19 +119,25 @@ impl FlashblocksServiceBuilder { self.0.block_time_leeway, ); - let (payload_service, payload_builder) = + let (payload_service, payload_builder_handle) = PayloadBuilderService::new(payload_generator, ctx.provider().canonical_state_stream()); - once_lock - .set(payload_service.payload_events_handle()) - .map_err(|_| eyre::eyre!("Cannot initialize payload service handle"))?; + let payload_handler = PayloadHandler::new( + built_payload_rx, + incoming_message_rx, + outgoing_message_tx, + payload_service.payload_events_handle(), + ); ctx.task_executor() .spawn_critical("custom payload builder service", Box::pin(payload_service)); + ctx.task_executor().spawn_critical( + "flashblocks payload handler", + Box::pin(payload_handler.run()), + ); tracing::info!("Flashblocks payload builder service started"); - - Ok(payload_builder) + Ok(payload_builder_handle) } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs index 944edb91..e2f003dc 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/wspub.rs @@ -1,11 +1,9 @@ use core::{ fmt::{Debug, Formatter}, net::SocketAddr, - pin::Pin, sync::atomic::{AtomicUsize, Ordering}, - task::{Context, Poll}, }; -use futures::{Sink, SinkExt}; +use futures::SinkExt; use futures_util::StreamExt; use rollup_boost::FlashblocksPayloadV1; use std::{io, net::TcpListener, sync::Arc}; @@ -238,24 +236,3 @@ impl Debug for WebSocketPublisher { .finish() } } - -impl Sink<&FlashblocksPayloadV1> for WebSocketPublisher { - type Error = eyre::Report; - - fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn start_send(self: Pin<&mut Self>, item: &FlashblocksPayloadV1) -> Result<(), Self::Error> { - self.publish(item)?; - Ok(()) - } - - fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } -} diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index c733d111..81c49cee 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -117,6 +117,7 @@ pub struct BuilderConfig { /// 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, @@ -139,6 +140,7 @@ impl core::fmt::Debug for BuilderConfig { .field("block_time", &self.block_time) .field("block_time_leeway", &self.block_time_leeway) .field("da_config", &self.da_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) diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index dd43d958..31fe081c 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -96,9 +96,9 @@ pub struct OpRBuilderMetrics { /// Latest state merge transitions duration pub state_transition_merge_gauge: Gauge, /// Histogram of the duration of payload simulation of all transactions - pub payload_tx_simulation_duration: Histogram, + pub payload_transaction_simulation_duration: Histogram, /// Latest payload simulation of all transactions duration - pub payload_tx_simulation_gauge: Gauge, + pub payload_transaction_simulation_gauge: Gauge, /// Number of transaction considered for inclusion in the block pub payload_num_tx_considered: Histogram, /// Latest number of transactions considered for inclusion in the block @@ -157,7 +157,7 @@ impl OpRBuilderMetrics { #[expect(clippy::too_many_arguments)] pub fn set_payload_builder_metrics( &self, - payload_tx_simulation_time: impl IntoF64 + Copy, + payload_transaction_simulation_time: impl IntoF64 + Copy, num_txs_considered: impl IntoF64 + Copy, num_txs_simulated: impl IntoF64 + Copy, num_txs_simulated_success: impl IntoF64 + Copy, @@ -165,10 +165,10 @@ impl OpRBuilderMetrics { num_bundles_reverted: impl IntoF64, reverted_gas_used: impl IntoF64, ) { - self.payload_tx_simulation_duration - .record(payload_tx_simulation_time); - self.payload_tx_simulation_gauge - .set(payload_tx_simulation_time); + self.payload_transaction_simulation_duration + .record(payload_transaction_simulation_time); + self.payload_transaction_simulation_gauge + .set(payload_transaction_simulation_time); self.payload_num_tx_considered.record(num_txs_considered); self.payload_num_tx_considered_gauge.set(num_txs_considered); self.payload_num_tx_simulated.record(num_txs_simulated); diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index 78104828..a623aaff 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -219,6 +219,7 @@ async fn unichain_dynamic_with_lag(rbuilder: LocalInstance) -> eyre::Result<()> flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, flashblocks_leeway_time: 0, + flashblocks_fixed: false, ..Default::default() }, ..Default::default() @@ -255,6 +256,8 @@ async fn dynamic_with_full_block_lag(rbuilder: LocalInstance) -> eyre::Result<() flashblocks_port: 1239, flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, ..Default::default() }, ..Default::default() @@ -313,6 +316,8 @@ async fn test_flashblock_min_filtering(rbuilder: LocalInstance) -> eyre::Result< flashblocks_port: 1239, flashblocks_addr: "127.0.0.1".into(), flashblocks_block_time: 200, + flashblocks_leeway_time: 100, + flashblocks_fixed: false, ..Default::default() }, ..Default::default() diff --git a/crates/builder/p2p/Cargo.toml b/crates/builder/p2p/Cargo.toml new file mode 100644 index 00000000..5f512c4a --- /dev/null +++ b/crates/builder/p2p/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "p2p" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +libp2p = { version = "0.56", features = ["identify", "ping", "noise", "tcp", "autonat", "mdns", "tokio", "cbor", "macros", "yamux"] } +libp2p-stream = "0.4.0-alpha" +multiaddr = "0.18" + +derive_more = { workspace = true, features = ["from"] } +eyre = { workspace = true } +futures = { workspace = true} +futures-util = { workspace = true } +hex = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true, features = [ "macros" ] } +tokio-util = { workspace = true, features = [ "compat", "codec" ] } +tracing = { workspace = true } + +[lints] +workspace = true diff --git a/crates/builder/p2p/src/behaviour.rs b/crates/builder/p2p/src/behaviour.rs new file mode 100644 index 00000000..140d309b --- /dev/null +++ b/crates/builder/p2p/src/behaviour.rs @@ -0,0 +1,106 @@ +use eyre::WrapErr as _; +use libp2p::{ + autonat, connection_limits, connection_limits::ConnectionLimits, identify, identity, mdns, + ping, swarm::NetworkBehaviour, +}; +use std::{convert::Infallible, time::Duration}; + +const PROTOCOL_VERSION: &str = "1.0.0"; + +#[derive(NetworkBehaviour)] +#[behaviour(to_swarm = "BehaviourEvent")] +pub(crate) struct Behaviour { + // connection gating + connection_limits: connection_limits::Behaviour, + + // discovery + mdns: mdns::tokio::Behaviour, + + // protocols + identify: identify::Behaviour, + ping: ping::Behaviour, + stream: libp2p_stream::Behaviour, + + // nat traversal + autonat: autonat::Behaviour, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, derive_more::From)] +pub(crate) enum BehaviourEvent { + Autonat(autonat::Event), + Identify(identify::Event), + Mdns(mdns::Event), + Ping(ping::Event), +} + +impl From<()> for BehaviourEvent { + fn from(_: ()) -> Self { + unreachable!("() cannot be converted to BehaviourEvent") + } +} + +impl From for BehaviourEvent { + fn from(_: Infallible) -> Self { + unreachable!("Infallible cannot be converted to BehaviourEvent") + } +} + +impl Behaviour { + pub(crate) fn new( + keypair: &identity::Keypair, + agent_version: String, + max_peer_count: u32, + ) -> eyre::Result { + let peer_id = keypair.public().to_peer_id(); + + let autonat = autonat::Behaviour::new(peer_id, autonat::Config::default()); + let mdns = mdns::tokio::Behaviour::new(mdns::Config::default(), peer_id) + .wrap_err("failed to create mDNS behaviour")?; + let connection_limits = connection_limits::Behaviour::new( + ConnectionLimits::default().with_max_established(Some(max_peer_count)), + ); + + let identify = identify::Behaviour::new( + identify::Config::new(PROTOCOL_VERSION.to_string(), keypair.public()) + .with_agent_version(agent_version), + ); + let ping = ping::Behaviour::new(ping::Config::new().with_interval(Duration::from_secs(10))); + let stream = libp2p_stream::Behaviour::new(); + + Ok(Self { + autonat, + connection_limits, + identify, + ping, + mdns, + stream, + }) + } + + pub(crate) fn new_control(&mut self) -> libp2p_stream::Control { + self.stream.new_control() + } +} + +impl BehaviourEvent { + pub(crate) fn handle(self) { + match self { + BehaviourEvent::Autonat(_event) => {} + BehaviourEvent::Identify(_event) => {} + BehaviourEvent::Mdns(event) => match event { + mdns::Event::Discovered(list) => { + for (peer_id, multiaddr) in list { + tracing::debug!("mDNS discovered peer {peer_id} at {multiaddr}"); + } + } + mdns::Event::Expired(list) => { + for (peer_id, multiaddr) in list { + tracing::debug!("mDNS expired peer {peer_id} at {multiaddr}"); + } + } + }, + BehaviourEvent::Ping(_event) => {} + } + } +} diff --git a/crates/builder/p2p/src/lib.rs b/crates/builder/p2p/src/lib.rs new file mode 100644 index 00000000..fba12aa1 --- /dev/null +++ b/crates/builder/p2p/src/lib.rs @@ -0,0 +1,574 @@ +mod behaviour; +mod outgoing; + +use behaviour::Behaviour; +use libp2p_stream::IncomingStreams; + +use eyre::Context; +use libp2p::{ + PeerId, Swarm, Transport as _, + identity::{self, ed25519}, + noise, + swarm::SwarmEvent, + tcp, yamux, +}; +use multiaddr::Protocol; +use std::{collections::HashMap, time::Duration}; +use tokio::sync::mpsc; +use tokio_util::sync::CancellationToken; +use tracing::{debug, warn}; + +pub use libp2p::{Multiaddr, StreamProtocol}; + +const DEFAULT_MAX_PEER_COUNT: u32 = 50; + +/// A message that can be sent between peers. +pub trait Message: + serde::Serialize + for<'de> serde::Deserialize<'de> + Send + Sync + Clone + std::fmt::Debug +{ + fn protocol(&self) -> StreamProtocol; + + fn to_string(&self) -> eyre::Result { + serde_json::to_string(self).wrap_err("failed to serialize message to string") + } + + fn from_str(s: &str) -> eyre::Result + where + Self: Sized, + { + serde_json::from_str(s).wrap_err("failed to deserialize message from string") + } +} + +/// The libp2p node. +/// +/// The current behaviour of the node regarding messaging protocols is as follows: +/// - for each supported protocol, the node will accept incoming streams from remote peers on that protocol. +/// - when a new connection is established with a peer, the node will open outbound streams to that peer for each supported protocol. +/// - when a new outgoing message is received on `outgoing_message_rx`, the node will broadcast that message to all connected peers that have an outbound stream open for the message's protocol. +/// - incoming messages received on incoming streams are handled by `IncomingStreamsHandler`, which reads messages from the stream and sends them to a channel for processing by the consumer of this library. +/// +/// Currently, there is no gossip implemented; messages are simply broadcast to connected peers. +pub struct Node { + /// The peer ID of this node. + peer_id: PeerId, + + /// The multiaddresses this node is listening on. + listen_addrs: Vec, + + /// The libp2p swarm, which contains the state of the network + /// and its behaviours. + swarm: Swarm, + + /// The multiaddresses of known peers to connect to on startup. + known_peers: Vec, + + /// Receiver for outgoing messages to be sent to peers. + outgoing_message_rx: mpsc::Receiver, + + /// Handler for managing outgoing streams to peers. + /// Used to determine what peers to broadcast to when a + /// new outgoing message is received on `outgoing_message_rx`. + outgoing_streams_handler: outgoing::StreamsHandler, + + /// Handlers for incoming streams (streams which remote peers have opened with us). + incoming_streams_handlers: Vec>, + + /// The protocols this node supports. + protocols: Vec, + + /// Cancellation token to shut down the node. + cancellation_token: CancellationToken, +} + +impl Node { + /// Returns the multiaddresses that this node is listening on, with the peer ID included. + pub fn multiaddrs(&self) -> Vec { + self.listen_addrs + .iter() + .map(|addr| { + addr.clone() + .with_p2p(self.peer_id) + .expect("can add peer ID to multiaddr") + }) + .collect() + } + + /// Runs the p2p node, dials known peers, and starts listening for incoming connections and messages. + /// + /// This function will run until the cancellation token is triggered. + /// If an error occurs, it will be logged, but the node will continue running. + pub async fn run(self) -> eyre::Result<()> { + use libp2p::futures::StreamExt as _; + + let Node { + peer_id: _, + listen_addrs, + mut swarm, + known_peers, + mut outgoing_message_rx, + mut outgoing_streams_handler, + cancellation_token, + incoming_streams_handlers, + protocols, + } = self; + + for addr in listen_addrs { + swarm + .listen_on(addr) + .wrap_err("swarm failed to listen on multiaddr")?; + } + + for mut address in known_peers { + let peer_id = match address.pop() { + Some(multiaddr::Protocol::P2p(peer_id)) => peer_id, + _ => { + eyre::bail!("no peer ID for known peer"); + } + }; + swarm.add_peer_address(peer_id, address.clone()); + swarm + .dial(address) + .wrap_err("swarm failed to dial known peer")?; + } + + let handles = incoming_streams_handlers + .into_iter() + .map(|handler| tokio::spawn(handler.run())) + .collect::>(); + + loop { + tokio::select! { + biased; + _ = cancellation_token.cancelled() => { + debug!("cancellation token triggered, shutting down node"); + handles.into_iter().for_each(|h| h.abort()); + break Ok(()); + } + Some(message) = outgoing_message_rx.recv() => { + let protocol = message.protocol(); + if let Err(e) = outgoing_streams_handler.broadcast_message(message).await { + warn!("failed to broadcast message on protocol {protocol}: {e:?}"); + } + } + event = swarm.select_next_some() => { + match event { + SwarmEvent::NewListenAddr { + address, + .. + } => { + debug!("new listen address: {address}"); + } + SwarmEvent::ExternalAddrConfirmed { address } => { + debug!("external address confirmed: {address}"); + } + SwarmEvent::ConnectionEstablished { + peer_id, + connection_id, + .. + } => { + // when a new connection is established, open outbound streams for each protocol + // and add them to the outgoing streams handler. + // + // If we already have a connection with this peer, close the new connection, + // as we only want one connection per peer. + debug!("connection established with peer {peer_id}"); + if outgoing_streams_handler.has_peer(&peer_id) { + swarm.close_connection(connection_id); + debug!("already have connection with peer {peer_id}, closed connection {connection_id}"); + } else { + for protocol in &protocols { + match swarm + .behaviour_mut() + .new_control() + .open_stream(peer_id, protocol.clone()) + .await + { + Ok(stream) => { outgoing_streams_handler.insert_peer_and_stream(peer_id, protocol.clone(), stream); + debug!("opened outbound stream with peer {peer_id} with protocol {protocol} on connection {connection_id}"); + } + Err(e) => { + warn!("failed to open stream with peer {peer_id} on connection {connection_id}: {e:?}"); + } + } + } + } + } + SwarmEvent::ConnectionClosed { + peer_id, + cause, + .. + } => { + debug!("connection closed with peer {peer_id}: {cause:?}"); + outgoing_streams_handler.remove_peer(&peer_id); + } + SwarmEvent::Behaviour(event) => event.handle(), + _ => continue, + } + }, + } + } + } +} + +pub struct NodeBuildResult { + pub node: Node, + pub outgoing_message_tx: mpsc::Sender, + pub incoming_message_rxs: HashMap>, +} + +pub struct NodeBuilder { + port: Option, + listen_addrs: Vec, + keypair_hex: Option, + known_peers: Vec, + agent_version: Option, + protocols: Vec, + max_peer_count: Option, + cancellation_token: Option, +} + +impl Default for NodeBuilder { + fn default() -> Self { + Self::new() + } +} + +impl NodeBuilder { + pub fn new() -> Self { + Self { + port: None, + listen_addrs: Vec::new(), + keypair_hex: None, + known_peers: Vec::new(), + agent_version: None, + protocols: Vec::new(), + max_peer_count: None, + cancellation_token: None, + } + } + + pub fn with_port(mut self, port: u16) -> Self { + self.port = Some(port); + self + } + + pub fn with_listen_addr(mut self, addr: libp2p::Multiaddr) -> Self { + self.listen_addrs.push(addr); + self + } + + pub fn with_keypair_hex_string(mut self, keypair_hex: String) -> Self { + self.keypair_hex = Some(keypair_hex); + self + } + + pub fn with_agent_version(mut self, agent_version: String) -> Self { + self.agent_version = Some(agent_version); + self + } + + pub fn with_protocol(mut self, protocol: StreamProtocol) -> Self { + self.protocols.push(protocol); + self + } + + pub fn with_max_peer_count(mut self, max_peer_count: u32) -> Self { + self.max_peer_count = Some(max_peer_count); + self + } + + pub fn with_known_peers(mut self, addresses: I) -> Self + where + I: IntoIterator, + T: Into, + { + for address in addresses { + self.known_peers.push(address.into()); + } + self + } + + pub fn try_build(self) -> eyre::Result> { + let Self { + port, + listen_addrs, + keypair_hex, + known_peers, + agent_version, + protocols, + max_peer_count, + cancellation_token, + } = self; + + // TODO: caller should be forced to provide this + let cancellation_token = cancellation_token.unwrap_or_default(); + + let Some(agent_version) = agent_version else { + eyre::bail!("agent version must be set"); + }; + + let keypair = match keypair_hex { + Some(hex) => { + let mut bytes = hex::decode(hex).wrap_err("failed to decode hex string")?; + let keypair = ed25519::Keypair::try_from_bytes(&mut bytes) + .wrap_err("failed to create keypair from bytes")?; + Some(keypair.into()) + } + None => None, + }; + let keypair = keypair.unwrap_or(identity::Keypair::generate_ed25519()); + let peer_id = keypair.public().to_peer_id(); + + let transport = create_transport(&keypair).wrap_err("failed to create transport")?; + let max_peer_count = max_peer_count.unwrap_or(DEFAULT_MAX_PEER_COUNT); + let mut behaviour = Behaviour::new(&keypair, agent_version, max_peer_count) + .context("failed to create behaviour")?; + let mut control = behaviour.new_control(); + + let mut incoming_streams_handlers = Vec::new(); + let mut incoming_message_rxs = HashMap::new(); + for protocol in &protocols { + let incoming_streams = control + .accept(protocol.clone()) + .wrap_err("failed to subscribe to incoming streams for flashblocks protocol")?; + let (incoming_streams_handler, message_rx) = IncomingStreamsHandler::new( + protocol.clone(), + incoming_streams, + cancellation_token.clone(), + ); + incoming_streams_handlers.push(incoming_streams_handler); + incoming_message_rxs.insert(protocol.clone(), message_rx); + } + + let swarm = libp2p::SwarmBuilder::with_existing_identity(keypair) + .with_tokio() + .with_other_transport(|_| transport)? + .with_behaviour(|_| behaviour)? + .with_swarm_config(|cfg| { + cfg.with_idle_connection_timeout(Duration::from_secs(u64::MAX)) // don't disconnect from idle peers + }) + .build(); + + // disallow providing listen addresses that have a peer ID in them, + // as we've specified the peer ID for this node above. + let mut listen_addrs: Vec = listen_addrs + .into_iter() + .filter(|addr| { + for protocol in addr.iter() { + if protocol == Protocol::P2p(peer_id) { + return false; + } + } + true + }) + .collect(); + if listen_addrs.is_empty() { + let port = port.unwrap_or(0); + let listen_addr = format!("/ip4/0.0.0.0/tcp/{port}") + .parse() + .expect("can parse valid multiaddr"); + listen_addrs.push(listen_addr); + } + + let (outgoing_message_tx, outgoing_message_rx) = tokio::sync::mpsc::channel(100); + + Ok(NodeBuildResult { + node: Node { + peer_id, + swarm, + listen_addrs, + known_peers, + outgoing_message_rx, + outgoing_streams_handler: outgoing::StreamsHandler::new(), + cancellation_token, + incoming_streams_handlers, + protocols, + }, + outgoing_message_tx, + incoming_message_rxs, + }) + } +} + +struct IncomingStreamsHandler { + protocol: StreamProtocol, + incoming: IncomingStreams, + tx: mpsc::Sender, + cancellation_token: CancellationToken, +} + +impl IncomingStreamsHandler { + fn new( + protocol: StreamProtocol, + incoming: IncomingStreams, + cancellation_token: CancellationToken, + ) -> (Self, mpsc::Receiver) { + const CHANNEL_SIZE: usize = 100; + let (tx, rx) = mpsc::channel(CHANNEL_SIZE); + ( + Self { + protocol, + incoming, + tx, + cancellation_token, + }, + rx, + ) + } + + async fn run(self) { + use futures::StreamExt as _; + + let Self { + protocol, + mut incoming, + tx, + cancellation_token, + } = self; + let mut handle_stream_futures = futures::stream::FuturesUnordered::new(); + + loop { + tokio::select! { + _ = cancellation_token.cancelled() => { + debug!("cancellation token triggered, shutting down incoming streams handler for protocol {protocol}"); + return; + } + Some((from, stream)) = incoming.next() => { + debug!("new incoming stream on protocol {protocol} from peer {from}"); + handle_stream_futures.push(tokio::spawn(handle_incoming_stream(from, stream, tx.clone()))); + } + Some(res) = handle_stream_futures.next() => { + match res { + Ok(Ok(())) => {} + Ok(Err(e)) => { + warn!("error handling incoming stream: {e:?}"); + } + Err(e) => { + warn!("task handling incoming stream panicked: {e:?}"); + } + } + } + } + } + } +} + +async fn handle_incoming_stream( + peer_id: PeerId, + stream: libp2p::Stream, + payload_tx: mpsc::Sender, +) -> eyre::Result<()> { + use futures::StreamExt as _; + use tokio_util::{ + codec::{FramedRead, LinesCodec}, + compat::FuturesAsyncReadCompatExt as _, + }; + + let codec = LinesCodec::new(); + let mut reader = FramedRead::new(stream.compat(), codec); + + while let Some(res) = reader.next().await { + match res { + Ok(str) => { + let payload = M::from_str(&str).wrap_err("failed to decode stream message")?; + debug!("got message from peer {peer_id}: {payload:?}"); + let _ = payload_tx.send(payload).await; + } + Err(e) => { + return Err(e).wrap_err(format!("failed to read from stream of peer {peer_id}")); + } + } + } + + Ok(()) +} + +fn create_transport( + keypair: &identity::Keypair, +) -> eyre::Result> { + let transport = tcp::tokio::Transport::new(tcp::Config::default()) + .upgrade(libp2p::core::upgrade::Version::V1) + .authenticate(noise::Config::new(keypair)?) + .multiplex(yamux::Config::default()) + .timeout(Duration::from_secs(20)) + .boxed(); + + Ok(transport) +} + +#[cfg(test)] +mod test { + use super::*; + + const TEST_AGENT_VERSION: &str = "test/1.0.0"; + const TEST_PROTOCOL: StreamProtocol = StreamProtocol::new("/test/1.0.0"); + + #[derive(Debug, PartialEq, Eq, Clone)] + struct TestMessage { + content: String, + } + + impl Message for TestMessage { + fn protocol(&self) -> StreamProtocol { + TEST_PROTOCOL + } + } + + impl serde::Serialize for TestMessage { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.content) + } + } + + impl<'de> serde::Deserialize<'de> for TestMessage { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(TestMessage { content: s }) + } + } + + #[tokio::test] + async fn two_nodes_can_connect_and_message() { + let NodeBuildResult { + node: node1, + outgoing_message_tx: _, + incoming_message_rxs: mut rx1, + } = NodeBuilder::new() + .with_listen_addr("/ip4/127.0.0.1/tcp/9000".parse().unwrap()) + .with_agent_version(TEST_AGENT_VERSION.to_string()) + .with_protocol(TEST_PROTOCOL) + .try_build::() + .unwrap(); + let NodeBuildResult { + node: node2, + outgoing_message_tx: tx2, + incoming_message_rxs: _, + } = NodeBuilder::new() + .with_known_peers(node1.multiaddrs()) + .with_protocol(TEST_PROTOCOL) + .with_listen_addr("/ip4/127.0.0.1/tcp/9001".parse().unwrap()) + .with_agent_version(TEST_AGENT_VERSION.to_string()) + .try_build::() + .unwrap(); + + tokio::spawn(async move { node1.run().await }); + tokio::spawn(async move { node2.run().await }); + // sleep to allow nodes to connect + tokio::time::sleep(Duration::from_secs(2)).await; + + let message = TestMessage { + content: "message".to_string(), + }; + tx2.send(message.clone()).await.unwrap(); + + let recv_message: TestMessage = rx1.remove(&TEST_PROTOCOL).unwrap().recv().await.unwrap(); + assert_eq!(recv_message, message); + } +} diff --git a/crates/builder/p2p/src/outgoing.rs b/crates/builder/p2p/src/outgoing.rs new file mode 100644 index 00000000..c948bde6 --- /dev/null +++ b/crates/builder/p2p/src/outgoing.rs @@ -0,0 +1,95 @@ +use crate::Message; +use eyre::Context; +use futures::stream::FuturesUnordered; +use libp2p::{PeerId, StreamProtocol, swarm::Stream}; +use std::collections::HashMap; +use tracing::{debug, warn}; + +pub(crate) struct StreamsHandler { + peers_to_stream: HashMap>, +} + +impl StreamsHandler { + pub(crate) fn new() -> Self { + Self { + peers_to_stream: HashMap::new(), + } + } + + pub(crate) fn has_peer(&self, peer: &PeerId) -> bool { + self.peers_to_stream.contains_key(peer) + } + + pub(crate) fn insert_peer_and_stream( + &mut self, + peer: PeerId, + protocol: StreamProtocol, + stream: Stream, + ) { + self.peers_to_stream + .entry(peer) + .or_default() + .insert(protocol, stream); + } + + pub(crate) fn remove_peer(&mut self, peer: &PeerId) { + self.peers_to_stream.remove(peer); + } + + pub(crate) async fn broadcast_message(&mut self, message: M) -> eyre::Result<()> { + use futures::{SinkExt as _, StreamExt as _}; + use tokio_util::{ + codec::{FramedWrite, LinesCodec}, + compat::FuturesAsyncReadCompatExt as _, + }; + + let protocol = message.protocol(); + let payload = message + .to_string() + .wrap_err("failed to serialize payload")?; + + let peers = self.peers_to_stream.keys().cloned().collect::>(); + let mut futures = FuturesUnordered::new(); + for peer in peers { + let protocol_to_stream = self + .peers_to_stream + .get_mut(&peer) + .expect("stream map must exist for peer"); + let Some(stream) = protocol_to_stream.remove(&protocol) else { + warn!("no stream for protocol {protocol:?} to peer {peer}"); + continue; + }; + let stream = stream.compat(); + let payload = payload.clone(); + let fut = async move { + let mut writer = FramedWrite::new(stream, LinesCodec::new()); + writer.send(payload).await?; + Ok::<(PeerId, libp2p::swarm::Stream), eyre::ErrReport>(( + peer, + writer.into_inner().into_inner(), + )) + }; + futures.push(fut); + } + while let Some(result) = futures.next().await { + match result { + Ok((peer, stream)) => { + let protocol_to_stream = self + .peers_to_stream + .get_mut(&peer) + .expect("stream map must exist for peer"); + protocol_to_stream.insert(protocol.clone(), stream); + } + Err(e) => { + warn!("failed to send payload to peer: {e:?}"); + } + } + } + debug!( + "broadcasted message to {} peers", + self.peers_to_stream.len() + ); + + Ok(()) + } +} From dc3608ee25fcabb73af8069572fa8acb5fe7b0a2 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:19:10 -0400 Subject: [PATCH 202/262] feat: implement flashblock sync over p2p (#288) --- .../src/builders/flashblocks/ctx.rs | 81 ++++ .../src/builders/flashblocks/mod.rs | 1 + .../src/builders/flashblocks/payload.rs | 4 +- .../builders/flashblocks/payload_handler.rs | 395 +++++++++++++++++- .../src/builders/flashblocks/service.rs | 21 +- crates/builder/op-rbuilder/src/metrics.rs | 8 +- crates/builder/p2p/src/behaviour.rs | 16 +- crates/builder/p2p/src/lib.rs | 43 +- crates/builder/p2p/src/outgoing.rs | 7 +- 9 files changed, 541 insertions(+), 35 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs new file mode 100644 index 00000000..c465445f --- /dev/null +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -0,0 +1,81 @@ +use crate::{ + builders::{BuilderConfig, OpPayloadBuilderCtx, flashblocks::FlashblocksConfig}, + gas_limiter::{AddressGasLimiter, args::GasLimiterArgs}, + metrics::OpRBuilderMetrics, + traits::ClientBounds, +}; +use op_revm::OpSpecId; +use reth_basic_payload_builder::PayloadConfig; +use reth_evm::EvmEnv; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_payload_builder::{OpPayloadBuilderAttributes, config::OpDAConfig}; +use reth_optimism_primitives::OpTransactionSigned; +use std::sync::Arc; +use tokio_util::sync::CancellationToken; + +#[derive(Debug, Clone)] +pub(super) struct OpPayloadSyncerCtx { + /// The type that knows how to perform system calls and configure the evm. + evm_config: OpEvmConfig, + /// The DA config for the payload builder + da_config: OpDAConfig, + /// The chainspec + chain_spec: Arc, + /// Max gas that can be used by a transaction. + max_gas_per_txn: Option, + /// The metrics for the builder + metrics: Arc, +} + +impl OpPayloadSyncerCtx { + pub(super) fn new( + client: &Client, + builder_config: BuilderConfig, + evm_config: OpEvmConfig, + metrics: Arc, + ) -> eyre::Result + where + Client: ClientBounds, + { + let chain_spec = client.chain_spec(); + Ok(Self { + evm_config, + da_config: builder_config.da_config.clone(), + chain_spec, + max_gas_per_txn: builder_config.max_gas_per_txn, + metrics, + }) + } + + pub(super) fn evm_config(&self) -> &OpEvmConfig { + &self.evm_config + } + + pub(super) fn max_gas_per_txn(&self) -> Option { + self.max_gas_per_txn + } + + pub(super) fn into_op_payload_builder_ctx( + self, + payload_config: PayloadConfig>, + evm_env: EvmEnv, + block_env_attributes: OpNextBlockEnvAttributes, + cancel: CancellationToken, + ) -> OpPayloadBuilderCtx { + OpPayloadBuilderCtx { + evm_config: self.evm_config, + da_config: self.da_config, + chain_spec: self.chain_spec, + config: payload_config, + evm_env, + block_env_attributes, + cancel, + builder_signer: None, + metrics: self.metrics, + extra_ctx: (), + max_gas_per_txn: self.max_gas_per_txn, + address_gas_limiter: AddressGasLimiter::new(GasLimiterArgs::default()), + } + } +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs index 643bf39a..87d4494c 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/mod.rs @@ -6,6 +6,7 @@ use service::FlashblocksServiceBuilder; mod best_txs; mod builder_tx; mod config; +mod ctx; mod p2p; mod payload; mod payload_handler; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 9e2237bc..89f19210 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -158,8 +158,8 @@ impl OpPayloadBuilder { config: BuilderConfig, builder_tx: BuilderTx, payload_tx: mpsc::Sender, + metrics: Arc, ) -> eyre::Result { - let metrics = Arc::new(OpRBuilderMetrics::default()); let ws_pub = WebSocketPublisher::new(config.specific.ws_addr, Arc::clone(&metrics))?.into(); let address_gas_limiter = AddressGasLimiter::new(config.gas_limiter_config.clone()); Ok(Self { @@ -710,7 +710,7 @@ where match build_result { Err(err) => { - ctx.metrics.invalid_blocks_count.increment(1); + ctx.metrics.invalid_built_blocks_count.increment(1); Err(err).wrap_err("failed to build payload") } Ok((new_payload, mut fb_payload)) => { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs index e19bb19c..4927a047 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs @@ -1,12 +1,35 @@ -use crate::builders::flashblocks::p2p::Message; +use crate::{ + builders::flashblocks::{ + ctx::OpPayloadSyncerCtx, p2p::Message, payload::FlashblocksExecutionInfo, + }, + primitives::reth::ExecutionInfo, + traits::ClientBounds, +}; +use alloy_evm::eth::receipt_builder::ReceiptBuilderCtx; +use alloy_primitives::B64; +use eyre::{WrapErr as _, bail}; +use op_alloy_consensus::OpTxEnvelope; +use reth::revm::{State, database::StateProviderDatabase}; +use reth_basic_payload_builder::PayloadConfig; +use reth_evm::FromRecoveredTx; use reth_node_builder::Events; -use reth_optimism_node::OpEngineTypes; +use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; +use reth_optimism_node::{OpEngineTypes, OpPayloadBuilderAttributes}; use reth_optimism_payload_builder::OpBuiltPayload; +use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; +use reth_payload_builder::EthPayloadBuilderAttributes; +use rollup_boost::FlashblocksPayloadV1; +use std::sync::Arc; use tokio::sync::mpsc; use tracing::warn; -pub(crate) struct PayloadHandler { - // receives new payloads built by us. +/// Handles newly built or received flashblock payloads. +/// +/// 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(crate) struct PayloadHandler { + // receives new payloads built by this builder. built_rx: mpsc::Receiver, // receives incoming p2p messages from peers. p2p_rx: mpsc::Receiver, @@ -14,20 +37,35 @@ pub(crate) struct PayloadHandler { p2p_tx: mpsc::Sender, // sends a `Events::BuiltPayload` to the reth payload builder when a new payload is received. payload_events_handle: tokio::sync::broadcast::Sender>, + // context required for execution of blocks during syncing + ctx: OpPayloadSyncerCtx, + // chain client + client: Client, + cancel: tokio_util::sync::CancellationToken, } -impl PayloadHandler { +impl PayloadHandler +where + Client: ClientBounds + 'static, +{ + #[allow(clippy::too_many_arguments)] pub(crate) fn new( built_rx: mpsc::Receiver, p2p_rx: mpsc::Receiver, p2p_tx: mpsc::Sender, payload_events_handle: tokio::sync::broadcast::Sender>, + ctx: OpPayloadSyncerCtx, + client: Client, + cancel: tokio_util::sync::CancellationToken, ) -> Self { Self { built_rx, p2p_rx, p2p_tx, payload_events_handle, + ctx, + client, + cancel, } } @@ -37,9 +75,12 @@ impl PayloadHandler { mut p2p_rx, p2p_tx, payload_events_handle, + ctx, + client, + cancel, } = self; - tracing::info!("flashblocks payload handler started"); + tracing::debug!("flashblocks payload handler started"); loop { tokio::select! { @@ -54,11 +95,351 @@ impl PayloadHandler { match message { Message::OpBuiltPayload(payload) => { let payload: OpBuiltPayload = payload.into(); - let _ = payload_events_handle.send(Events::BuiltPayload(payload)); + let ctx = ctx.clone(); + let client = client.clone(); + let payload_events_handle = payload_events_handle.clone(); + let cancel = cancel.clone(); + + // execute the flashblock on a thread where blocking is acceptable, + // as it's potentially a heavy operation + tokio::task::spawn_blocking(move || { + let res = execute_flashblock( + payload, + ctx, + client, + cancel, + ); + match res { + Ok((payload, _)) => { + tracing::info!(hash = payload.block().hash().to_string(), block_number = payload.block().header().number, "successfully executed received flashblock"); + let _ = payload_events_handle.send(Events::BuiltPayload(payload)); + } + Err(e) => { + tracing::error!(error = ?e, "failed to execute received flashblock"); + } + } + }); } } } + else => break, } } } } + +fn execute_flashblock( + payload: OpBuiltPayload, + ctx: OpPayloadSyncerCtx, + client: Client, + cancel: tokio_util::sync::CancellationToken, +) -> eyre::Result<(OpBuiltPayload, FlashblocksPayloadV1)> +where + Client: ClientBounds, +{ + use alloy_consensus::BlockHeader as _; + use reth::primitives::SealedHeader; + use reth_evm::{ConfigureEvm as _, execute::BlockBuilder as _}; + + let start = tokio::time::Instant::now(); + + tracing::info!(header = ?payload.block().header(), "executing flashblock"); + + let mut cached_reads = reth::revm::cached::CachedReads::default(); + let parent_hash = payload.block().sealed_header().parent_hash; + let parent_header = client + .header_by_id(parent_hash.into()) + .wrap_err("failed to get parent header")? + .ok_or_else(|| eyre::eyre!("parent header not found"))?; + + let state_provider = client + .state_by_block_hash(parent_hash) + .wrap_err("failed to get state for parent hash")?; + let db = StateProviderDatabase::new(&state_provider); + let mut state = State::builder() + .with_database(cached_reads.as_db_mut(db)) + .with_bundle_update() + .build(); + + let chain_spec = client.chain_spec(); + let timestamp = payload.block().header().timestamp(); + let block_env_attributes = OpNextBlockEnvAttributes { + timestamp, + suggested_fee_recipient: payload.block().sealed_header().beneficiary, + prev_randao: payload.block().sealed_header().mix_hash, + gas_limit: payload.block().sealed_header().gas_limit, + parent_beacon_block_root: payload.block().sealed_header().parent_beacon_block_root, + extra_data: payload.block().sealed_header().extra_data.clone(), + }; + + let evm_env = ctx + .evm_config() + .next_evm_env(&parent_header, &block_env_attributes) + .wrap_err("failed to create next evm env")?; + + ctx.evm_config() + .builder_for_next_block( + &mut state, + &Arc::new(SealedHeader::new(parent_header.clone(), parent_hash)), + block_env_attributes.clone(), + ) + .wrap_err("failed to create evm builder for next block")? + .apply_pre_execution_changes() + .wrap_err("failed to apply pre execution changes")?; + + let mut info = ExecutionInfo::with_capacity(payload.block().body().transactions.len()); + + let extra_data = payload.block().sealed_header().extra_data.clone(); + if extra_data.len() != 9 { + tracing::error!(len = extra_data.len(), data = ?extra_data, "invalid extra data length in flashblock"); + bail!("extra data length should be 9 bytes"); + } + + // see https://specs.optimism.io/protocol/holocene/exec-engine.html#eip-1559-parameters-in-block-header + let eip_1559_parameters: B64 = extra_data[1..9].try_into().unwrap(); + let payload_config = PayloadConfig::new( + Arc::new(SealedHeader::new(parent_header.clone(), parent_hash)), + OpPayloadBuilderAttributes { + eip_1559_params: Some(eip_1559_parameters), + payload_attributes: EthPayloadBuilderAttributes { + id: payload.id(), // unused + parent: parent_hash, // unused + suggested_fee_recipient: payload.block().sealed_header().beneficiary, + withdrawals: payload + .block() + .body() + .withdrawals + .clone() + .unwrap_or_default(), + parent_beacon_block_root: payload.block().sealed_header().parent_beacon_block_root, + timestamp, + prev_randao: payload.block().sealed_header().mix_hash, + }, + ..Default::default() + }, + ); + + execute_transactions( + &mut info, + &mut state, + payload.block().body().transactions.clone(), + payload.block().header().gas_used, + ctx.evm_config(), + evm_env.clone(), + ctx.max_gas_per_txn(), + is_canyon_active(&chain_spec, timestamp), + is_regolith_active(&chain_spec, timestamp), + ) + .wrap_err("failed to execute best transactions")?; + + let builder_ctx = ctx.into_op_payload_builder_ctx( + payload_config, + evm_env.clone(), + block_env_attributes, + cancel, + ); + + let (built_payload, fb_payload) = crate::builders::flashblocks::payload::build_block( + &mut state, + &builder_ctx, + &mut info, + true, + ) + .wrap_err("failed to build flashblock")?; + + builder_ctx + .metrics + .flashblock_sync_duration + .record(start.elapsed()); + + if built_payload.block().hash() != payload.block().hash() { + tracing::error!( + expected = %payload.block().hash(), + got = %built_payload.block().hash(), + "flashblock hash mismatch after execution" + ); + builder_ctx.metrics.invalid_synced_blocks_count.increment(1); + bail!("flashblock hash mismatch after execution"); + } + + builder_ctx.metrics.block_synced_success.increment(1); + + tracing::info!(header = ?built_payload.block().header(), "successfully executed flashblock"); + Ok((built_payload, fb_payload)) +} + +#[allow(clippy::too_many_arguments)] +fn execute_transactions( + info: &mut ExecutionInfo, + state: &mut State, + txs: Vec, + gas_limit: u64, + evm_config: &reth_optimism_evm::OpEvmConfig, + evm_env: alloy_evm::EvmEnv, + max_gas_per_txn: Option, + is_canyon_active: bool, + is_regolith_active: bool, +) -> eyre::Result<()> { + use alloy_evm::{Evm as _, EvmError as _}; + use op_revm::{OpTransaction, transaction::deposit::DepositTransactionParts}; + use reth_evm::ConfigureEvm as _; + use reth_primitives_traits::SignerRecoverable as _; + use revm::{ + DatabaseCommit as _, + context::{TxEnv, result::ResultAndState}, + }; + + let mut evm = evm_config.evm_with_env(&mut *state, evm_env); + + for tx in txs { + let sender = tx + .recover_signer() + .wrap_err("failed to recover tx signer")?; + let tx_env = TxEnv::from_recovered_tx(&tx, sender); + let executable_tx = match tx { + OpTxEnvelope::Deposit(ref tx) => { + let deposit = DepositTransactionParts { + mint: Some(tx.mint), + source_hash: tx.source_hash, + is_system_transaction: tx.is_system_transaction, + }; + OpTransaction { + base: tx_env, + enveloped_tx: None, + deposit, + } + } + OpTxEnvelope::Legacy(_) => { + let mut tx = OpTransaction::new(tx_env); + tx.enveloped_tx = Some(vec![0x00].into()); + tx + } + OpTxEnvelope::Eip2930(_) => { + let mut tx = OpTransaction::new(tx_env); + tx.enveloped_tx = Some(vec![0x00].into()); + tx + } + OpTxEnvelope::Eip1559(_) => { + let mut tx = OpTransaction::new(tx_env); + tx.enveloped_tx = Some(vec![0x00].into()); + tx + } + OpTxEnvelope::Eip7702(_) => { + let mut tx = OpTransaction::new(tx_env); + tx.enveloped_tx = Some(vec![0x00].into()); + tx + } + }; + + let ResultAndState { result, state } = match evm.transact_raw(executable_tx) { + Ok(res) => res, + Err(err) => { + if let Some(err) = err.as_invalid_tx_err() { + // TODO: what invalid txs are allowed in the block? + // reverting txs should be allowed (?) but not straight up invalid ones + tracing::error!(error = %err, "skipping invalid transaction in flashblock"); + continue; + } + return Err(err).wrap_err("failed to execute flashblock transaction"); + } + }; + + if let Some(max_gas_per_txn) = max_gas_per_txn { + if result.gas_used() > max_gas_per_txn { + return Err(eyre::eyre!( + "transaction exceeded max gas per txn limit in flashblock" + )); + } + } + + let tx_gas_used = result.gas_used(); + info.cumulative_gas_used = info + .cumulative_gas_used + .checked_add(tx_gas_used) + .ok_or_else(|| { + eyre::eyre!("total gas used overflowed when executing flashblock transactions") + })?; + if info.cumulative_gas_used > gas_limit { + bail!("flashblock exceeded gas limit when executing transactions"); + } + + let depositor_nonce = (is_regolith_active && tx.is_deposit()) + .then(|| { + evm.db_mut() + .load_cache_account(sender) + .map(|acc| acc.account_info().unwrap_or_default().nonce) + }) + .transpose() + .wrap_err("failed to get depositor nonce")?; + + let ctx = ReceiptBuilderCtx { + tx: &tx, + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + + info.receipts.push(build_receipt( + evm_config, + ctx, + depositor_nonce, + is_canyon_active, + )); + + evm.db_mut().commit(state); + + // append sender and transaction to the respective lists + info.executed_senders.push(sender); + info.executed_transactions.push(tx.clone()); + } + + Ok(()) +} + +fn build_receipt( + evm_config: &OpEvmConfig, + ctx: ReceiptBuilderCtx<'_, OpTransactionSigned, E>, + deposit_nonce: Option, + is_canyon_active: bool, +) -> OpReceipt { + use alloy_consensus::Eip658Value; + use alloy_op_evm::block::receipt_builder::OpReceiptBuilder as _; + use op_alloy_consensus::OpDepositReceipt; + use reth_evm::ConfigureEvm as _; + + let receipt_builder = evm_config.block_executor_factory().receipt_builder(); + match receipt_builder.build_receipt(ctx) { + Ok(receipt) => receipt, + Err(ctx) => { + let receipt = alloy_consensus::Receipt { + // Success flag was added in `EIP-658: Embedding transaction status code + // in receipts`. + status: Eip658Value::Eip658(ctx.result.is_success()), + cumulative_gas_used: ctx.cumulative_gas_used, + logs: ctx.result.into_logs(), + }; + + receipt_builder.build_deposit_receipt(OpDepositReceipt { + inner: receipt, + deposit_nonce, + // The deposit receipt version was introduced in Canyon to indicate an + // update to how receipt hashes should be computed + // when set. The state transition process ensures + // this is only set for post-Canyon deposit + // transactions. + deposit_receipt_version: is_canyon_active.then_some(1), + }) + } + } +} + +fn is_canyon_active(chain_spec: &OpChainSpec, timestamp: u64) -> bool { + use reth_optimism_chainspec::OpHardforks as _; + chain_spec.is_canyon_active_at_timestamp(timestamp) +} + +fn is_regolith_active(chain_spec: &OpChainSpec, timestamp: u64) -> bool { + use reth_optimism_chainspec::OpHardforks as _; + chain_spec.is_regolith_active_at_timestamp(timestamp) +} diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index 68d4b036..e11fa2f2 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -12,6 +12,7 @@ use crate::{ generator::BlockPayloadJobGenerator, }, flashtestations::service::bootstrap_flashtestations, + metrics::OpRBuilderMetrics, traits::{NodeBounds, PoolBounds}, }; use eyre::WrapErr as _; @@ -21,6 +22,7 @@ use reth_node_builder::{BuilderContext, components::PayloadServiceBuilder}; use reth_optimism_evm::OpEvmConfig; use reth_payload_builder::{PayloadBuilderHandle, PayloadBuilderService}; use reth_provider::CanonStateSubscriptions; +use std::sync::Arc; pub struct FlashblocksServiceBuilder(pub BuilderConfig); @@ -41,6 +43,10 @@ impl FlashblocksServiceBuilder { + Sync + 'static, { + // TODO: is there a different global token? + // this is effectively unused right now due to the usage of reth's `task_executor`. + let cancel = tokio_util::sync::CancellationToken::new(); + let (incoming_message_rx, outgoing_message_tx) = if self.0.specific.p2p_enabled { let mut builder = p2p::NodeBuilder::new(); @@ -76,6 +82,7 @@ impl FlashblocksServiceBuilder { .with_protocol(FLASHBLOCKS_STREAM_PROTOCOL) .with_known_peers(known_peers) .with_port(self.0.specific.p2p_port) + .with_cancellation_token(cancel.clone()) .with_max_peer_count(self.0.specific.p2p_max_peer_count) .try_build::() .wrap_err("failed to build flashblocks p2p node")?; @@ -97,6 +104,7 @@ impl FlashblocksServiceBuilder { (incoming_message_rx, outgoing_message_tx) }; + let metrics = Arc::new(OpRBuilderMetrics::default()); let (built_payload_tx, built_payload_rx) = tokio::sync::mpsc::channel(16); let payload_builder = OpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), @@ -105,9 +113,9 @@ impl FlashblocksServiceBuilder { self.0.clone(), builder_tx, built_payload_tx, + metrics.clone(), ) .wrap_err("failed to create flashblocks payload builder")?; - let payload_job_config = BasicPayloadJobGeneratorConfig::default(); let payload_generator = BlockPayloadJobGenerator::with_builder( @@ -122,11 +130,22 @@ 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( + &ctx.provider().clone(), + self.0, + OpEvmConfig::optimism(ctx.chain_spec()), + metrics.clone(), + ) + .wrap_err("failed to create flashblocks payload builder context")?; + let payload_handler = PayloadHandler::new( built_payload_rx, incoming_message_rx, outgoing_message_tx, payload_service.payload_events_handle(), + syncer_ctx, + ctx.provider().clone(), + cancel, ); ctx.task_executor() diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 31fe081c..c268c43c 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -63,6 +63,8 @@ pub const VERSION: VersionInfo = VersionInfo { pub struct OpRBuilderMetrics { /// Block built success pub block_built_success: Counter, + /// Block synced success + pub block_synced_success: Counter, /// Number of flashblocks added to block (Total per block) pub flashblock_count: Histogram, /// Number of messages sent @@ -73,12 +75,16 @@ pub struct OpRBuilderMetrics { pub total_block_built_gauge: Gauge, /// Histogram of the time taken to build a Flashblock pub flashblock_build_duration: Histogram, + /// Histogram of the time taken to sync a Flashblock + pub flashblock_sync_duration: Histogram, /// Flashblock UTF8 payload byte size histogram pub flashblock_byte_size_histogram: Histogram, /// Histogram of transactions in a Flashblock pub flashblock_num_tx_histogram: Histogram, /// Number of invalid blocks - pub invalid_blocks_count: Counter, + pub invalid_built_blocks_count: Counter, + /// Number of invalid synced blocks + pub invalid_synced_blocks_count: Counter, /// Histogram of fetching transactions from the pool duration pub transaction_pool_fetch_duration: Histogram, /// Latest time taken to fetch tx from the pool diff --git a/crates/builder/p2p/src/behaviour.rs b/crates/builder/p2p/src/behaviour.rs index 140d309b..bedfb78e 100644 --- a/crates/builder/p2p/src/behaviour.rs +++ b/crates/builder/p2p/src/behaviour.rs @@ -1,7 +1,9 @@ use eyre::WrapErr as _; use libp2p::{ - autonat, connection_limits, connection_limits::ConnectionLimits, identify, identity, mdns, - ping, swarm::NetworkBehaviour, + Swarm, autonat, + connection_limits::{self, ConnectionLimits}, + identify, identity, mdns, ping, + swarm::NetworkBehaviour, }; use std::{convert::Infallible, time::Duration}; @@ -84,14 +86,22 @@ impl Behaviour { } impl BehaviourEvent { - pub(crate) fn handle(self) { + pub(crate) fn handle(self, swarm: &mut Swarm) { match self { BehaviourEvent::Autonat(_event) => {} BehaviourEvent::Identify(_event) => {} BehaviourEvent::Mdns(event) => match event { mdns::Event::Discovered(list) => { for (peer_id, multiaddr) in list { + if swarm.is_connected(&peer_id) { + continue; + } + tracing::debug!("mDNS discovered peer {peer_id} at {multiaddr}"); + swarm.add_peer_address(peer_id, multiaddr); + swarm.dial(peer_id).unwrap_or_else(|e| { + tracing::error!("failed to dial mDNS discovered peer {peer_id}: {e}") + }); } } mdns::Event::Expired(list) => { diff --git a/crates/builder/p2p/src/lib.rs b/crates/builder/p2p/src/lib.rs index fba12aa1..757be3b2 100644 --- a/crates/builder/p2p/src/lib.rs +++ b/crates/builder/p2p/src/lib.rs @@ -147,6 +147,7 @@ impl Node { } Some(message) = outgoing_message_rx.recv() => { let protocol = message.protocol(); + debug!("received message to broadcast on protocol {protocol}"); if let Err(e) = outgoing_streams_handler.broadcast_message(message).await { warn!("failed to broadcast message on protocol {protocol}: {e:?}"); } @@ -169,28 +170,22 @@ impl Node { } => { // when a new connection is established, open outbound streams for each protocol // and add them to the outgoing streams handler. - // - // If we already have a connection with this peer, close the new connection, - // as we only want one connection per peer. debug!("connection established with peer {peer_id}"); - if outgoing_streams_handler.has_peer(&peer_id) { - swarm.close_connection(connection_id); - debug!("already have connection with peer {peer_id}, closed connection {connection_id}"); - } else { + if !outgoing_streams_handler.has_peer(&peer_id) { for protocol in &protocols { - match swarm - .behaviour_mut() - .new_control() - .open_stream(peer_id, protocol.clone()) - .await - { - Ok(stream) => { outgoing_streams_handler.insert_peer_and_stream(peer_id, protocol.clone(), stream); - debug!("opened outbound stream with peer {peer_id} with protocol {protocol} on connection {connection_id}"); + match swarm + .behaviour_mut() + .new_control() + .open_stream(peer_id, protocol.clone()) + .await + { + Ok(stream) => { outgoing_streams_handler.insert_peer_and_stream(peer_id, protocol.clone(), stream); + debug!("opened outbound stream with peer {peer_id} with protocol {protocol} on connection {connection_id}"); + } + Err(e) => { + warn!("failed to open stream with peer {peer_id} on connection {connection_id}: {e:?}"); + } } - Err(e) => { - warn!("failed to open stream with peer {peer_id} on connection {connection_id}: {e:?}"); - } - } } } } @@ -202,7 +197,7 @@ impl Node { debug!("connection closed with peer {peer_id}: {cause:?}"); outgoing_streams_handler.remove_peer(&peer_id); } - SwarmEvent::Behaviour(event) => event.handle(), + SwarmEvent::Behaviour(event) => event.handle(&mut swarm), _ => continue, } }, @@ -273,6 +268,14 @@ impl NodeBuilder { self } + pub fn with_cancellation_token( + mut self, + cancellation_token: tokio_util::sync::CancellationToken, + ) -> Self { + self.cancellation_token = Some(cancellation_token); + self + } + pub fn with_max_peer_count(mut self, max_peer_count: u32) -> Self { self.max_peer_count = Some(max_peer_count); self diff --git a/crates/builder/p2p/src/outgoing.rs b/crates/builder/p2p/src/outgoing.rs index c948bde6..2440e0f7 100644 --- a/crates/builder/p2p/src/outgoing.rs +++ b/crates/builder/p2p/src/outgoing.rs @@ -63,7 +63,10 @@ impl StreamsHandler { let payload = payload.clone(); let fut = async move { let mut writer = FramedWrite::new(stream, LinesCodec::new()); - writer.send(payload).await?; + writer + .send(payload) + .await + .wrap_err("failed to send message to peer")?; Ok::<(PeerId, libp2p::swarm::Stream), eyre::ErrReport>(( peer, writer.into_inner().into_inner(), @@ -71,6 +74,7 @@ impl StreamsHandler { }; futures.push(fut); } + while let Some(result) = futures.next().await { match result { Ok((peer, stream)) => { @@ -85,6 +89,7 @@ impl StreamsHandler { } } } + debug!( "broadcasted message to {} peers", self.peers_to_stream.len() From ba04e0a263d7d283dc5a70df63f89c16ff182985 Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 3 Nov 2025 19:01:15 +0400 Subject: [PATCH 203/262] reth bump (#306) --- crates/builder/op-rbuilder/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index d49d4951..da148101 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -130,7 +130,7 @@ sha3 = "0.10" reqwest = "0.12.23" k256 = "0.13.4" -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "b86af43969557bee18f17ec1d6bcd3e984f910b2" } +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "dd12e8e8366004b4758bfa0cfa98efa6929b7e9f" } nanoid = { version = "0.4", optional = true } reth-ipc = { workspace = true, optional = true } From 9ae42b5c4594b59cb797a801cc268270342c6879 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:02:59 -0800 Subject: [PATCH 204/262] feat: publish synced flashblocks to ws (#310) --- .../src/builders/flashblocks/ctx.rs | 4 +++ .../src/builders/flashblocks/payload.rs | 9 +++--- .../builders/flashblocks/payload_handler.rs | 28 +++++++++++++++++-- .../src/builders/flashblocks/service.rs | 11 ++++++-- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs index c465445f..b5a1bd47 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -56,6 +56,10 @@ impl OpPayloadSyncerCtx { self.max_gas_per_txn } + pub(super) fn metrics(&self) -> &Arc { + &self.metrics + } + pub(super) fn into_op_payload_builder_ctx( self, payload_config: PayloadConfig>, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 89f19210..01b8bc15 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -151,6 +151,7 @@ pub(super) struct OpPayloadBuilder { impl OpPayloadBuilder { /// `OpPayloadBuilder` constructor. + #[allow(clippy::too_many_arguments)] pub(super) fn new( evm_config: OpEvmConfig, pool: Pool, @@ -158,11 +159,11 @@ impl OpPayloadBuilder { config: BuilderConfig, builder_tx: BuilderTx, payload_tx: mpsc::Sender, + ws_pub: Arc, metrics: Arc, - ) -> eyre::Result { - let ws_pub = WebSocketPublisher::new(config.specific.ws_addr, Arc::clone(&metrics))?.into(); + ) -> Self { let address_gas_limiter = AddressGasLimiter::new(config.gas_limiter_config.clone()); - Ok(Self { + Self { evm_config, pool, client, @@ -172,7 +173,7 @@ impl OpPayloadBuilder { metrics, builder_tx, address_gas_limiter, - }) + } } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs index 4927a047..e39f74fb 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs @@ -1,6 +1,7 @@ use crate::{ builders::flashblocks::{ ctx::OpPayloadSyncerCtx, p2p::Message, payload::FlashblocksExecutionInfo, + wspub::WebSocketPublisher, }, primitives::reth::ExecutionInfo, traits::ClientBounds, @@ -37,6 +38,9 @@ pub(crate) struct PayloadHandler { p2p_tx: mpsc::Sender, // sends a `Events::BuiltPayload` to the reth payload builder when a new payload is received. payload_events_handle: tokio::sync::broadcast::Sender>, + /// WebSocket publisher for broadcasting flashblocks + /// to all connected subscribers. + ws_pub: Arc, // context required for execution of blocks during syncing ctx: OpPayloadSyncerCtx, // chain client @@ -55,6 +59,7 @@ where p2p_tx: mpsc::Sender, payload_events_handle: tokio::sync::broadcast::Sender>, ctx: OpPayloadSyncerCtx, + ws_pub: Arc, client: Client, cancel: tokio_util::sync::CancellationToken, ) -> Self { @@ -63,6 +68,7 @@ where p2p_rx, p2p_tx, payload_events_handle, + ws_pub, ctx, client, cancel, @@ -76,6 +82,7 @@ where p2p_tx, payload_events_handle, ctx, + ws_pub, client, cancel, } = self; @@ -98,11 +105,13 @@ where let ctx = ctx.clone(); let client = client.clone(); let payload_events_handle = payload_events_handle.clone(); + let ws_pub = ws_pub.clone(); let cancel = cancel.clone(); // execute the flashblock on a thread where blocking is acceptable, // as it's potentially a heavy operation tokio::task::spawn_blocking(move || { + let metrics = ctx.metrics().clone(); let res = execute_flashblock( payload, ctx, @@ -110,9 +119,24 @@ where cancel, ); match res { - Ok((payload, _)) => { + Ok((payload, fb_payload)) => { tracing::info!(hash = payload.block().hash().to_string(), block_number = payload.block().header().number, "successfully executed received flashblock"); - let _ = payload_events_handle.send(Events::BuiltPayload(payload)); + if let Err(e) = payload_events_handle.send(Events::BuiltPayload(payload)) { + warn!(e = ?e, "failed to send BuiltPayload event on synced block"); + } + + match ws_pub + .publish(&fb_payload) { + Ok(flashblock_byte_size) => { + metrics + .flashblock_byte_size_histogram + .record(flashblock_byte_size as f64); + } + Err(e) => { + tracing::warn!(error = ?e, "failed to publish flashblock to websocket subscribers"); + } + } + } Err(e) => { tracing::error!(error = ?e, "failed to execute received flashblock"); diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index e11fa2f2..a2d61e93 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -8,6 +8,7 @@ use crate::{ p2p::{AGENT_VERSION, FLASHBLOCKS_STREAM_PROTOCOL, Message}, payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, payload_handler::PayloadHandler, + wspub::WebSocketPublisher, }, generator::BlockPayloadJobGenerator, }, @@ -106,6 +107,11 @@ impl FlashblocksServiceBuilder { let metrics = Arc::new(OpRBuilderMetrics::default()); 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()) + .wrap_err("failed to create ws publisher")? + .into(); let payload_builder = OpPayloadBuilder::new( OpEvmConfig::optimism(ctx.chain_spec()), pool, @@ -113,9 +119,9 @@ impl FlashblocksServiceBuilder { self.0.clone(), builder_tx, built_payload_tx, + ws_pub.clone(), metrics.clone(), - ) - .wrap_err("failed to create flashblocks payload builder")?; + ); let payload_job_config = BasicPayloadJobGeneratorConfig::default(); let payload_generator = BlockPayloadJobGenerator::with_builder( @@ -144,6 +150,7 @@ impl FlashblocksServiceBuilder { outgoing_message_tx, payload_service.payload_events_handle(), syncer_ctx, + ws_pub, ctx.provider().clone(), cancel, ); From 5695cbaa5fe569ced7bfd6313a853fda4b87b472 Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 4 Nov 2025 10:28:32 -0800 Subject: [PATCH 205/262] Add permit functions for flashblocks number contract (#287) * Add permit flashtestations tx calls from builder * move simumlation calls to builder tx * Add permit functions for flashblocks number contract * refactor to simulate call * fix tests --- crates/builder/op-rbuilder/src/args/op.rs | 9 + .../op-rbuilder/src/builders/builder_tx.rs | 2 +- .../src/builders/flashblocks/builder_tx.rs | 266 ++++++++++-------- .../src/builders/flashblocks/config.rs | 8 + .../src/builders/flashblocks/service.rs | 9 +- .../src/flashtestations/builder_tx.rs | 31 +- .../op-rbuilder/src/tests/flashblocks.rs | 2 +- .../op-rbuilder/src/tests/flashtestations.rs | 116 +++++++- .../src/tests/framework/instance.rs | 2 - .../op-rbuilder/src/tests/framework/utils.rs | 8 + 10 files changed, 311 insertions(+), 142 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index b0b90f84..1d81c382 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -167,6 +167,15 @@ pub struct FlashblocksArgs { )] pub flashblocks_number_contract_address: Option

, + /// Use permit signatures if flashtestations is enabled with the flashtestation key + /// to increment the flashblocks number + #[arg( + long = "flashblocks.number-contract-use-permit", + env = "FLASHBLOCK_NUMBER_CONTRACT_USE_PERMIT", + default_value = "false" + )] + pub flashblocks_number_contract_use_permit: bool, + /// Flashblocks p2p configuration #[command(flatten)] pub p2p: FlashblocksP2pArgs, diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index 11194a38..7cc56b07 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -126,7 +126,7 @@ impl From for PayloadBuilderError { BuilderTransactionError::EvmExecutionError(e) => { PayloadBuilderError::EvmExecutionError(e) } - _ => PayloadBuilderError::Other(Box::new(error)), + _ => PayloadBuilderError::other(error), } } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs index 04afc64d..a53d67cd 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -1,31 +1,25 @@ -use alloy_consensus::TxEip1559; use alloy_eips::Encodable2718; use alloy_evm::{Database, Evm}; use alloy_op_evm::OpEvm; -use alloy_primitives::{Address, TxKind}; -use alloy_sol_types::{Error, SolCall, SolEvent, SolInterface, sol}; +use alloy_primitives::{Address, B256, Signature, U256}; +use alloy_rpc_types_eth::TransactionInput; +use alloy_sol_types::{SolCall, SolEvent, sol}; use core::fmt::Debug; -use op_alloy_consensus::OpTypedTransaction; -use op_revm::OpHaltReason; +use op_alloy_rpc_types::OpTransactionRequest; use reth_evm::{ConfigureEvm, precompiles::PrecompilesMap}; -use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives::Recovered; use reth_provider::StateProvider; use reth_revm::State; -use revm::{ - DatabaseRef, - context::result::{ExecutionResult, ResultAndState}, - inspector::NoOpInspector, -}; +use revm::{DatabaseRef, inspector::NoOpInspector}; use tracing::warn; use crate::{ builders::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, - InvalidContractDataError, - builder_tx::{BuilderTxBase, get_nonce}, + SimulationSuccessResult, + builder_tx::BuilderTxBase, context::OpPayloadBuilderCtx, flashblocks::payload::{FlashblocksExecutionInfo, FlashblocksExtraCtx}, + get_nonce, }, flashtestations::builder_tx::FlashtestationsBuilderTx, primitives::reth::ExecutionInfo, @@ -37,8 +31,17 @@ 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); @@ -51,16 +54,6 @@ sol!( } ); -#[derive(Debug, thiserror::Error)] -pub(super) enum FlashblockNumberError { - #[error("flashblocks number contract tx reverted: {0:?}")] - Revert(IFlashblockNumber::IFlashblockNumberErrors), - #[error("unknown revert: {0} err: {1}")] - Unknown(String, Error), - #[error("halt: {0:?}")] - Halt(OpHaltReason), -} - // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct FlashblocksBuilderTx { @@ -133,8 +126,9 @@ impl BuilderTransactions for Flas // This will be the end of block transaction of a regular block #[derive(Debug, Clone)] pub(super) struct FlashblocksNumberBuilderTx { - pub signer: Option, + pub signer: Signer, pub flashblock_number_address: Address, + pub use_permit: bool, pub base_builder_tx: BuilderTxBase, pub flashtestations_builder_tx: Option>, @@ -142,85 +136,128 @@ pub(super) struct FlashblocksNumberBuilderTx { impl FlashblocksNumberBuilderTx { pub(super) fn new( - signer: Option, + signer: Signer, flashblock_number_address: Address, + use_permit: bool, flashtestations_builder_tx: Option< FlashtestationsBuilderTx, >, ) -> Self { - let base_builder_tx = BuilderTxBase::new(signer); + let base_builder_tx = BuilderTxBase::new(Some(signer)); Self { signer, flashblock_number_address, + use_permit, base_builder_tx, flashtestations_builder_tx, } } - // TODO: remove and clean up in favour of simulate_call() - fn estimate_flashblock_number_tx_gas( + fn signed_increment_flashblocks_tx( &self, ctx: &OpPayloadBuilderCtx, - evm: &mut OpEvm, - signer: &Signer, - nonce: u64, - ) -> Result { - let tx = self.signed_flashblock_number_tx(ctx, ctx.block_gas_limit(), nonce, signer)?; - let ResultAndState { result, .. } = match evm.transact(&tx) { - Ok(res) => res, - Err(err) => { - return Err(BuilderTransactionError::EvmExecutionError(Box::new(err))); - } + evm: &mut OpEvm, + ) -> Result { + let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}; + self.increment_flashblocks_tx(calldata, ctx, evm) + } + + fn increment_flashblocks_permit_signature( + &self, + flashtestations_signer: &Signer, + current_flashblock_number: U256, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let struct_hash_calldata = IFlashblockNumber::computeStructHashCall { + currentFlashblockNumber: current_flashblock_number, }; + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_readonly_call(struct_hash_calldata, ctx, evm)?; + let typed_data_hash_calldata = + IFlashblockNumber::hashTypedDataV4Call { structHash: output }; + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_readonly_call(typed_data_hash_calldata, ctx, evm)?; + let signature = flashtestations_signer.sign_message(output)?; + Ok(signature) + } - match result { - ExecutionResult::Success { gas_used, logs, .. } => { - if logs.iter().any(|log| { - log.topics().first() - == Some(&IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH) - }) { - Ok(gas_used) - } else { - Err(BuilderTransactionError::InvalidContract( - self.flashblock_number_address, - InvalidContractDataError::InvalidLogs( - vec![IFlashblockNumber::FlashblockIncremented::SIGNATURE_HASH], - vec![], - ), - )) - } - } - ExecutionResult::Revert { output, .. } => Err(BuilderTransactionError::other( - IFlashblockNumber::IFlashblockNumberErrors::abi_decode(&output) - .map(FlashblockNumberError::Revert) - .unwrap_or_else(|e| FlashblockNumberError::Unknown(hex::encode(output), e)), - )), - ExecutionResult::Halt { reason, .. } => Err(BuilderTransactionError::other( - FlashblockNumberError::Halt(reason), - )), - } + fn signed_increment_flashblocks_permit_tx( + &self, + flashtestations_signer: &Signer, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result { + let current_flashblock_calldata = IFlashblockNumber::flashblockNumberCall {}; + let SimulationSuccessResult { output, .. } = + self.simulate_flashblocks_readonly_call(current_flashblock_calldata, ctx, evm)?; + let signature = + self.increment_flashblocks_permit_signature(flashtestations_signer, output, ctx, evm)?; + let calldata = IFlashblockNumber::permitIncrementFlashblockNumberCall { + currentFlashblockNumber: output, + signature: signature.as_bytes().into(), + }; + 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_readonly_call( + &self, + calldata: T, + ctx: &OpPayloadBuilderCtx, + evm: &mut OpEvm, + ) -> Result, BuilderTransactionError> { + self.simulate_flashblocks_call(calldata, vec![], ctx, evm) } - fn signed_flashblock_number_tx( + fn simulate_flashblocks_call( &self, + calldata: T, + expected_logs: Vec, ctx: &OpPayloadBuilderCtx, - gas_limit: u64, - nonce: u64, - signer: &Signer, - ) -> Result, secp256k1::Error> { - let calldata = IFlashblockNumber::incrementFlashblockNumberCall {}.abi_encode(); - // Create the EIP-1559 transaction - let tx = OpTypedTransaction::Eip1559(TxEip1559 { - chain_id: ctx.chain_id(), - nonce, - gas_limit, - max_fee_per_gas: ctx.base_fee().into(), - max_priority_fee_per_gas: 0, - to: TxKind::Call(self.flashblock_number_address), - input: calldata.into(), - ..Default::default() - }); - signer.sign_tx(tx) + 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) // use tee key as signer for simulations + .nonce(get_nonce(evm.db(), self.signer.address)?) + .input(TransactionInput::new(calldata.abi_encode().into())); + self.simulate_call::( + tx_req, + expected_logs, + evm, + ) } } @@ -242,46 +279,35 @@ impl BuilderTransactions 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 - if let Some(signer) = &self.signer { - 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 nonce = get_nonce(evm.db_mut(), signer.address)?; + 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 tx = match self.estimate_flashblock_number_tx_gas(ctx, &mut evm, signer, nonce) - { - Ok(gas_used) => { - // Due to EIP-150, 63/64 of available gas is forwarded to external calls so need to add a buffer - let signed_tx = self.signed_flashblock_number_tx( - ctx, - gas_used * 64 / 63, - nonce, - signer, - )?; + let flashblocks_num_tx = if let Some(flashtestations) = &self.flashtestations_builder_tx + && self.use_permit + { + self.signed_increment_flashblocks_permit_tx( + flashtestations.tee_signer(), + ctx, + &mut evm, + ) + } else { + self.signed_increment_flashblocks_tx(ctx, &mut evm) + }; - let da_size = op_alloy_flz::tx_estimated_size_fjord_bytes( - signed_tx.encoded_2718().as_slice(), - ); - Some(BuilderTransactionCtx { - gas_used, - da_size, - signed_tx, - is_top_of_block: true, // number tx at top of flashblock - }) - } - 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()) - } - }; + 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); - } + builder_txs.extend(tx); } if ctx.is_last_flashblock() { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index a3345edb..a47cc046 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -39,6 +39,9 @@ pub struct FlashblocksConfig { /// If set a builder tx will be added to the start of every flashblock instead of the regular builder tx. pub flashblocks_number_contract_address: Option
, + /// whether to use permit signatures for the contract calls + pub flashblocks_number_contract_use_permit: bool, + /// Whether to enable the p2p node for flashblocks pub p2p_enabled: bool, @@ -64,6 +67,7 @@ impl Default for FlashblocksConfig { fixed: false, calculate_state_root: true, flashblocks_number_contract_address: None, + flashblocks_number_contract_use_permit: false, p2p_enabled: false, p2p_port: 9009, p2p_private_key_file: None, @@ -93,6 +97,9 @@ impl TryFrom for FlashblocksConfig { let flashblocks_number_contract_address = args.flashblocks.flashblocks_number_contract_address; + let flashblocks_number_contract_use_permit = + args.flashblocks.flashblocks_number_contract_use_permit; + Ok(Self { ws_addr, interval, @@ -100,6 +107,7 @@ impl TryFrom for FlashblocksConfig { fixed, calculate_state_root, flashblocks_number_contract_address, + flashblocks_number_contract_use_permit, p2p_enabled: args.flashblocks.p2p.p2p_enabled, p2p_port: args.flashblocks.p2p.p2p_port, p2p_private_key_file: args.flashblocks.p2p.p2p_private_key_file, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index a2d61e93..f947ab4e 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -195,15 +195,18 @@ where None }; - if let Some(flashblocks_number_contract_address) = - self.0.specific.flashblocks_number_contract_address + if let Some(builder_signer) = signer + && let Some(flashblocks_number_contract_address) = + self.0.specific.flashblocks_number_contract_address { + let use_permit = self.0.specific.flashblocks_number_contract_use_permit; self.spawn_payload_builder_service( ctx, pool, FlashblocksNumberBuilderTx::new( - signer, + builder_signer, flashblocks_number_contract_address, + use_permit, flashtestations_builder_tx, ), ) diff --git a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs index 005dcab5..7651b846 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/builder_tx.rs @@ -89,6 +89,10 @@ where } } + pub fn tee_signer(&self) -> &Signer { + &self.tee_service_signer + } + /// Computes the block content hash according to the formula: /// keccak256(abi.encode(parentHash, blockNumber, timestamp, transactionHashes)) /// https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md#block-building-process @@ -136,12 +140,13 @@ where .evm_with_env(&mut simulation_state, ctx.evm_env.clone()); evm.modify_cfg(|cfg| { cfg.disable_balance_check = true; + cfg.disable_nonce_check = true; }); let calldata = IFlashtestationRegistry::getRegistrationStatusCall { teeAddress: self.tee_service_signer.address, }; let SimulationSuccessResult { output, .. } = - self.flashtestation_contract_read(self.registry_address, calldata, ctx, &mut evm)?; + self.flashtestations_contract_read(self.registry_address, calldata, ctx, &mut evm)?; if output.isValid { self.registered .store(true, std::sync::atomic::Ordering::SeqCst); @@ -159,7 +164,7 @@ where owner: self.tee_service_signer.address, }; let SimulationSuccessResult { output, .. } = - self.flashtestation_contract_read(contract_address, calldata, ctx, evm)?; + self.flashtestations_contract_read(contract_address, calldata, ctx, evm)?; Ok(output) } @@ -175,7 +180,7 @@ where nonce: permit_nonce, deadline: U256::from(ctx.timestamp()), }; - let SimulationSuccessResult { output, .. } = self.flashtestation_contract_read( + let SimulationSuccessResult { output, .. } = self.flashtestations_contract_read( self.registry_address, struct_hash_calldata, ctx, @@ -183,7 +188,7 @@ where )?; let typed_data_hash_calldata = IFlashtestationRegistry::hashTypedDataV4Call { structHash: output }; - let SimulationSuccessResult { output, .. } = self.flashtestation_contract_read( + let SimulationSuccessResult { output, .. } = self.flashtestations_contract_read( self.registry_address, typed_data_hash_calldata, ctx, @@ -211,7 +216,7 @@ where gas_used, state_changes, .. - } = self.flashtestation_call( + } = self.flashtestations_call( self.registry_address, calldata.clone(), vec![TEEServiceRegistered::SIGNATURE_HASH], @@ -250,7 +255,7 @@ where blockContentHash: block_content_hash, nonce: permit_nonce, }; - let SimulationSuccessResult { output, .. } = self.flashtestation_contract_read( + let SimulationSuccessResult { output, .. } = self.flashtestations_contract_read( self.builder_policy_address, struct_hash_calldata, ctx, @@ -258,7 +263,7 @@ where )?; let typed_data_hash_calldata = IBlockBuilderPolicy::getHashedTypeDataV4Call { structHash: output }; - let SimulationSuccessResult { output, .. } = self.flashtestation_contract_read( + let SimulationSuccessResult { output, .. } = self.flashtestations_contract_read( self.builder_policy_address, typed_data_hash_calldata, ctx, @@ -289,7 +294,7 @@ where version: self.builder_proof_version, eip712Sig: signature.as_bytes().into(), }; - let SimulationSuccessResult { gas_used, .. } = self.flashtestation_call( + let SimulationSuccessResult { gas_used, .. } = self.flashtestations_call( self.builder_policy_address, calldata.clone(), vec![BlockBuilderProofVerified::SIGNATURE_HASH], @@ -314,17 +319,17 @@ where }) } - fn flashtestation_contract_read( + fn flashtestations_contract_read( &self, contract_address: Address, calldata: T, ctx: &OpPayloadBuilderCtx, evm: &mut OpEvm, ) -> Result, BuilderTransactionError> { - self.flashtestation_call(contract_address, calldata, vec![], ctx, evm) + self.flashtestations_call(contract_address, calldata, vec![], ctx, evm) } - fn flashtestation_call( + fn flashtestations_call( &self, contract_address: Address, calldata: T, @@ -336,8 +341,8 @@ where .gas_limit(ctx.block_gas_limit()) .max_fee_per_gas(ctx.base_fee().into()) .to(contract_address) - .from(self.tee_service_signer.address) // use tee key as signer for simulations - .nonce(get_nonce(evm.db(), self.tee_service_signer.address)?) + .from(self.builder_signer.address) + .nonce(get_nonce(evm.db(), self.builder_signer.address)?) .input(TransactionInput::new(calldata.abi_encode().into())); if contract_address == self.registry_address { self.simulate_call::( diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index a623aaff..cd92da9f 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -526,7 +526,7 @@ async fn test_flashblocks_number_contract_builder_tx(rbuilder: LocalInstance) -> let init_tx = driver .create_transaction() .init_flashblock_number_contract(true) - .with_to(contract_address) + .with_to(FLASHBLOCKS_NUMBER_ADDRESS) .with_bundle(BundleOpts::default()) .send() .await?; diff --git a/crates/builder/op-rbuilder/src/tests/flashtestations.rs b/crates/builder/op-rbuilder/src/tests/flashtestations.rs index c0cc0250..002ce5cf 100644 --- a/crates/builder/op-rbuilder/src/tests/flashtestations.rs +++ b/crates/builder/op-rbuilder/src/tests/flashtestations.rs @@ -97,7 +97,6 @@ async fn test_flashtestations_unauthorized_workload(rbuilder: LocalInstance) -> // check that only the regular builder tx is in the block let (tx_hash, block) = driver.build_new_block_with_valid_transaction().await?; let txs = block.transactions.into_transactions_vec(); - if_flashblocks!( assert_eq!(txs.len(), 4, "Expected 4 transactions in block"); // deposit + valid tx + 2 builder tx // Check builder tx @@ -312,7 +311,6 @@ async fn test_flashtestations_permit_with_flashblocks_number_contract( .send() .await?; let block = driver.build_new_block_with_current_timestamp(None).await?; - // check the builder tx, funding tx and registration tx is in the block let num_txs = block.transactions.len(); let txs = block.transactions.into_transactions_vec(); // // 1 deposit tx, 1 regular builder tx, 4 flashblocks number tx, 1 user tx, 1 block proof tx @@ -358,6 +356,120 @@ async fn test_flashtestations_permit_with_flashblocks_number_contract( Ok(()) } +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 1000, + enable_revert_protection: true, + flashblocks: FlashblocksArgs { + flashblocks_number_contract_address: Some(FLASHBLOCKS_NUMBER_ADDRESS), + flashblocks_number_contract_use_permit: true, + ..Default::default() + }, + flashtestations: FlashtestationsArgs { + flashtestations_enabled: true, + registry_address: Some(FLASHTESTATION_REGISTRY_ADDRESS), + builder_policy_address: Some(BLOCK_BUILDER_POLICY_ADDRESS), + debug: true, + enable_block_proofs: true, + ..Default::default() + }, + ..Default::default() +})] +async fn test_flashtestations_permit_with_flashblocks_number_permit( + rbuilder: LocalInstance, +) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let provider = rbuilder.provider().await?; + setup_flashblock_number_contract(&driver, &provider, false).await?; + setup_flashtestation_contracts(&driver, &provider, true, true).await?; + // Verify flashblock number is not incremented and builder address is not authorized + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert!( + current_number.is_zero(), // contract deployments incremented the number but we built at least 1 full block + "Flashblock number should not be incremented" + ); + let is_authorized = contract.isBuilder(builder_signer().address).call().await?; + assert!(!is_authorized, "builder should not be authorized"); + + // add tee signer address to authorized builders + let add_builder_tx = driver + .create_transaction() + .add_authorized_builder(TEE_DEBUG_ADDRESS) + .with_to(FLASHBLOCKS_NUMBER_ADDRESS) + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + provider + .get_transaction_receipt(*add_builder_tx.tx_hash()) + .await? + .expect("add builder tx not mined"); + let num_txs = block.transactions.len(); + let txs = block.transactions.into_transactions_vec(); + // 1 deposit tx, 5 regular builder tx, 1 add builder tx, 1 block proof tx + assert_eq!(num_txs, 8, "Expected 8 transactions in block"); + // Check no transactions to the flashblocks number contract as tee signer is not authorized + for i in 1..6 { + assert_eq!( + txs[i].to(), + Some(Address::ZERO), + "builder tx should send to flashblocks number contract at index {}", + i + ); + } + // add builder tx + assert_eq!( + txs[6].tx_hash(), + *add_builder_tx.tx_hash(), + "add builder tx should be in correct position in block" + ); + assert_eq!( + txs[7].to(), + Some(BLOCK_BUILDER_POLICY_ADDRESS), + "builder tx should send verify block builder proof" + ); + + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_bundle(BundleOpts::default().with_flashblock_number_min(4)) + .send() + .await?; + let block = driver.build_new_block_with_current_timestamp(None).await?; + let txs = block.transactions.into_transactions_vec(); + // 1 deposit tx, 1 regular builder tx, 4 flashblocks builder tx, 1 user tx, 1 block proof tx + assert_eq!(txs.len(), 8, "Expected 8 transactions in block"); + // flashblocks number contract + for i in 2..6 { + assert_eq!( + txs[i].to(), + Some(FLASHBLOCKS_NUMBER_ADDRESS), + "builder tx should send to flashblocks number contract at index {}", + i + ); + } + // user tx + assert_eq!( + txs[6].tx_hash(), + *tx.tx_hash(), + "user tx should be in correct position in block" + ); + // check that the tee signer did not send any transactions + let balance = provider.get_balance(TEE_DEBUG_ADDRESS).await?; + assert!(balance.is_zero()); + let nonce = provider.get_transaction_count(TEE_DEBUG_ADDRESS).await?; + assert_eq!(nonce, 0); + // Verify flashblock number incremented correctly + let contract = FlashblocksNumber::new(FLASHBLOCKS_NUMBER_ADDRESS, provider.clone()); + let current_number = contract.getFlashblockNumber().call().await?; + assert_eq!( + current_number, + U256::from(4), + "Flashblock number not incremented correctly" + ); + Ok(()) +} + async fn setup_flashtestation_contracts( driver: &ChainDriver, provider: &RootProvider, diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index ccfadaf0..d698bf45 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -52,7 +52,6 @@ use std::{ use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; use tokio_tungstenite::{connect_async, tungstenite::Message}; use tokio_util::sync::CancellationToken; -use tracing::warn; /// Represents a type that emulates a local in-process instance of the OP builder node. /// This node uses IPC as the communication channel for the RPC server Engine API. @@ -410,7 +409,6 @@ impl FlashblocksListener { } Some(Ok(Message::Text(text))) = read.next() => { let fb = serde_json::from_str(&text).unwrap(); - warn!("GOT FB: {fb:#?}"); flashblocks_clone.lock().push(fb); } } diff --git a/crates/builder/op-rbuilder/src/tests/framework/utils.rs b/crates/builder/op-rbuilder/src/tests/framework/utils.rs index 35a5f2a5..99772de1 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/utils.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/utils.rs @@ -33,6 +33,7 @@ pub trait TransactionBuilderExt { // flashblocks number methods fn deploy_flashblock_number_contract(self) -> Self; fn init_flashblock_number_contract(self, register_builder: bool) -> Self; + fn add_authorized_builder(self, builder: Address) -> Self; // flashtestations methods fn deploy_flashtestation_registry_contract(self) -> Self; fn init_flashtestation_registry_contract(self, dcap_address: Address) -> Self; @@ -85,6 +86,13 @@ impl TransactionBuilderExt for TransactionBuilder { .with_signer(flashblocks_number_signer()) } + fn add_authorized_builder(self, builder: Address) -> Self { + let calldata = FlashblocksNumber::addBuilderCall { builder }.abi_encode(); + + self.with_input(calldata.into()) + .with_signer(flashblocks_number_signer()) + } + fn deploy_flashtestation_registry_contract(self) -> Self { self.with_create() .with_input(FlashtestationRegistry::BYTECODE.clone()) From ee102a6c077d4daf9b94ac0eb2e444c7d84e0369 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:38:25 -0800 Subject: [PATCH 206/262] remove ws publishing from synced flashblocks (#312) --- .../src/builders/flashblocks/ctx.rs | 4 ---- .../builders/flashblocks/payload_handler.rs | 24 +------------------ .../src/builders/flashblocks/service.rs | 1 - 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs index b5a1bd47..c465445f 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -56,10 +56,6 @@ impl OpPayloadSyncerCtx { self.max_gas_per_txn } - pub(super) fn metrics(&self) -> &Arc { - &self.metrics - } - pub(super) fn into_op_payload_builder_ctx( self, payload_config: PayloadConfig>, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs index e39f74fb..bc02c34e 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs @@ -1,7 +1,6 @@ use crate::{ builders::flashblocks::{ ctx::OpPayloadSyncerCtx, p2p::Message, payload::FlashblocksExecutionInfo, - wspub::WebSocketPublisher, }, primitives::reth::ExecutionInfo, traits::ClientBounds, @@ -38,9 +37,6 @@ pub(crate) struct PayloadHandler { p2p_tx: mpsc::Sender, // sends a `Events::BuiltPayload` to the reth payload builder when a new payload is received. payload_events_handle: tokio::sync::broadcast::Sender>, - /// WebSocket publisher for broadcasting flashblocks - /// to all connected subscribers. - ws_pub: Arc, // context required for execution of blocks during syncing ctx: OpPayloadSyncerCtx, // chain client @@ -59,7 +55,6 @@ where p2p_tx: mpsc::Sender, payload_events_handle: tokio::sync::broadcast::Sender>, ctx: OpPayloadSyncerCtx, - ws_pub: Arc, client: Client, cancel: tokio_util::sync::CancellationToken, ) -> Self { @@ -68,7 +63,6 @@ where p2p_rx, p2p_tx, payload_events_handle, - ws_pub, ctx, client, cancel, @@ -82,7 +76,6 @@ where p2p_tx, payload_events_handle, ctx, - ws_pub, client, cancel, } = self; @@ -105,13 +98,11 @@ where let ctx = ctx.clone(); let client = client.clone(); let payload_events_handle = payload_events_handle.clone(); - let ws_pub = ws_pub.clone(); let cancel = cancel.clone(); // execute the flashblock on a thread where blocking is acceptable, // as it's potentially a heavy operation tokio::task::spawn_blocking(move || { - let metrics = ctx.metrics().clone(); let res = execute_flashblock( payload, ctx, @@ -119,24 +110,11 @@ where cancel, ); match res { - Ok((payload, fb_payload)) => { + Ok((payload, _)) => { tracing::info!(hash = payload.block().hash().to_string(), block_number = payload.block().header().number, "successfully executed received flashblock"); if let Err(e) = payload_events_handle.send(Events::BuiltPayload(payload)) { warn!(e = ?e, "failed to send BuiltPayload event on synced block"); } - - match ws_pub - .publish(&fb_payload) { - Ok(flashblock_byte_size) => { - metrics - .flashblock_byte_size_histogram - .record(flashblock_byte_size as f64); - } - Err(e) => { - tracing::warn!(error = ?e, "failed to publish flashblock to websocket subscribers"); - } - } - } Err(e) => { tracing::error!(error = ?e, "failed to execute received flashblock"); diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs index f947ab4e..2c1e684b 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/service.rs @@ -150,7 +150,6 @@ impl FlashblocksServiceBuilder { outgoing_message_tx, payload_service.payload_events_handle(), syncer_ctx, - ws_pub, ctx.provider().clone(), cancel, ); From 229e6f89683937bfd6650618fe71619e2d9f2d08 Mon Sep 17 00:00:00 2001 From: shana Date: Mon, 10 Nov 2025 10:02:05 -0800 Subject: [PATCH 207/262] [breaking-change] Fix arg for calculating state root (#314) * Fix arg for calculating state root * fix tests --- crates/builder/op-rbuilder/src/args/op.rs | 10 +++++----- .../op-rbuilder/src/builders/flashblocks/config.rs | 10 +++++----- .../src/builders/flashblocks/payload.rs | 14 +++++++------- .../builder/op-rbuilder/src/tests/flashblocks.rs | 6 +++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 1d81c382..4e3d0308 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -149,13 +149,13 @@ pub struct FlashblocksArgs { )] pub flashblocks_leeway_time: u64, - /// Should we calculate state root for each flashblock + /// Whether to disable state root calculation for each flashblock #[arg( - long = "flashblocks.calculate-state-root", - default_value = "true", - env = "FLASHBLOCKS_CALCULATE_STATE_ROOT" + long = "flashblocks.disable-state-root", + default_value = "false", + env = "FLASHBLOCKS_DISABLE_STATE_ROOT" )] - pub flashblocks_calculate_state_root: bool, + pub flashblocks_disable_state_root: bool, /// Flashblocks number contract address /// diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs index a47cc046..cdc6ae91 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/config.rs @@ -31,8 +31,8 @@ pub struct FlashblocksConfig { /// Disables dynamic flashblocks number adjustment based on FCU arrival time pub fixed: bool, - /// Should we calculate state root for each flashblock - pub calculate_state_root: bool, + /// Should we disable state root calculation for each flashblock + pub disable_state_root: bool, /// The address of the flashblocks number contract. /// @@ -65,7 +65,7 @@ impl Default for FlashblocksConfig { interval: Duration::from_millis(250), leeway_time: Duration::from_millis(50), fixed: false, - calculate_state_root: true, + disable_state_root: false, flashblocks_number_contract_address: None, flashblocks_number_contract_use_permit: false, p2p_enabled: false, @@ -92,7 +92,7 @@ impl TryFrom for FlashblocksConfig { let fixed = args.flashblocks.flashblocks_fixed; - let calculate_state_root = args.flashblocks.flashblocks_calculate_state_root; + let disable_state_root = args.flashblocks.flashblocks_disable_state_root; let flashblocks_number_contract_address = args.flashblocks.flashblocks_number_contract_address; @@ -105,7 +105,7 @@ impl TryFrom for FlashblocksConfig { interval, leeway_time, fixed, - calculate_state_root, + disable_state_root, flashblocks_number_contract_address, flashblocks_number_contract_use_permit, p2p_enabled: args.flashblocks.p2p.p2p_enabled, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 01b8bc15..1dc36d24 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -87,8 +87,8 @@ pub struct FlashblocksExtraCtx { gas_per_batch: u64, /// DA bytes limit per flashblock da_per_batch: Option, - /// Whether to calculate the state root for each flashblock - calculate_state_root: bool, + /// Whether to disable state root calculation for each flashblock + disable_state_root: bool, } impl FlashblocksExtraCtx { @@ -299,14 +299,14 @@ where ); let timestamp = config.attributes.timestamp(); - let calculate_state_root = self.config.specific.calculate_state_root; + let disable_state_root = self.config.specific.disable_state_root; let ctx = self .get_op_payload_builder_ctx( config.clone(), block_cancel.clone(), FlashblocksExtraCtx { target_flashblock_count: self.config.flashblocks_per_block(), - calculate_state_root, + disable_state_root, ..Default::default() }, ) @@ -355,7 +355,7 @@ where &mut state, &ctx, &mut info, - calculate_state_root || ctx.attributes().no_tx_pool, // need to calculate state root for CL sync + !disable_state_root || ctx.attributes().no_tx_pool, // need to calculate state root for CL sync )?; self.payload_tx @@ -450,7 +450,7 @@ where target_da_for_batch, gas_per_batch, da_per_batch, - calculate_state_root, + disable_state_root, }; let mut fb_cancel = block_cancel.child_token(); @@ -699,7 +699,7 @@ where state, ctx, info, - ctx.extra_ctx.calculate_state_root || ctx.attributes().no_tx_pool, + !ctx.extra_ctx.disable_state_root || ctx.attributes().no_tx_pool, ); let total_block_built_duration = total_block_built_duration.elapsed(); ctx.metrics diff --git a/crates/builder/op-rbuilder/src/tests/flashblocks.rs b/crates/builder/op-rbuilder/src/tests/flashblocks.rs index cd92da9f..c60e9d5a 100644 --- a/crates/builder/op-rbuilder/src/tests/flashblocks.rs +++ b/crates/builder/op-rbuilder/src/tests/flashblocks.rs @@ -419,7 +419,7 @@ async fn test_flashblock_min_max_filtering(rbuilder: LocalInstance) -> eyre::Res flashblocks_block_time: 200, flashblocks_leeway_time: 100, flashblocks_fixed: false, - flashblocks_calculate_state_root: false, + flashblocks_disable_state_root: true, ..Default::default() }, ..Default::default() @@ -436,7 +436,7 @@ async fn test_flashblocks_no_state_root_calculation(rbuilder: LocalInstance) -> .send() .await?; - // Build a block with current timestamp (not historical) and calculate_state_root: false + // Build a block with current timestamp (not historical) and disable_state_root: true let block = driver.build_new_block_with_current_timestamp(None).await?; // Verify that flashblocks are still produced (block should have transactions) @@ -449,7 +449,7 @@ async fn test_flashblocks_no_state_root_calculation(rbuilder: LocalInstance) -> assert_eq!( block.header.state_root, B256::ZERO, - "State root should be zero when calculate_state_root is false" + "State root should be zero when disable_state_root is true" ); Ok(()) From 35c330bca6bea231d38444fffecc74dbcdd952bc Mon Sep 17 00:00:00 2001 From: Tobi Akerele Date: Mon, 10 Nov 2025 13:21:22 -0500 Subject: [PATCH 208/262] feat: Integrate downstream changes (Jovian hardfork + miner_setGasLimit + reth 1.9.1) (#316) * chore: Rbuilder updates for jovian hardfork (#16) * Jovian ready op-rbuilder * Add factoring in da footprint scalar * Bump reth to 1.9.0 * feat: respect miner_setGasLimit (#18) * respect miner_setGasLimit * fmt * chore: bump to reth 1.9.1 (#20) * wip: telemetry resolution * done * fix: Add gas_limit_config field and make block_gas_limit() public - Added gas_limit_config initialization in flashblocks context - Changed block_gas_limit() visibility from pub(super) to pub for flashtestations access - Removed unused Events import Note: Tests currently fail due to rollup-boost dependency version mismatch (op-alloy 0.20.0 vs 0.22.0) * chore: Update rollup-boost to v0.7.8 for reth 1.9.1 compatibility - Updated rollup-boost dependency from rev dd12e8e to tag v0.7.8 - Resolves op-alloy version mismatch (0.20.0 -> 0.22.0) - All tests now pass (94/94 passing) * chore: apply nightly formatting fixes Applied formatting fixes from cargo +nightly fmt to ensure code passes lint checks. Changes include proper brace placement and line formatting in flashblocks builder_tx and payload_handler. --------- Co-authored-by: Danyal Prout Co-authored-by: Haardik --- crates/builder/op-rbuilder/Cargo.toml | 3 +- crates/builder/op-rbuilder/src/args/mod.rs | 8 +- .../op-rbuilder/src/builders/context.rs | 40 ++++-- .../src/builders/flashblocks/best_txs.rs | 36 ++--- .../src/builders/flashblocks/builder_tx.rs | 44 +++--- .../src/builders/flashblocks/ctx.rs | 6 +- .../src/builders/flashblocks/payload.rs | 17 +-- .../builders/flashblocks/payload_handler.rs | 12 +- .../op-rbuilder/src/builders/generator.rs | 6 +- .../builder/op-rbuilder/src/builders/mod.rs | 8 +- .../src/builders/standard/payload.rs | 32 +++-- .../op-rbuilder/src/gas_limiter/mod.rs | 2 +- crates/builder/op-rbuilder/src/launcher.rs | 8 +- .../op-rbuilder/src/monitor_tx_pool.rs | 2 +- .../op-rbuilder/src/primitives/bundle.rs | 33 +++-- .../src/primitives/reth/execution.rs | 19 ++- .../op-rbuilder/src/primitives/telemetry.rs | 15 +- .../src/tests/framework/instance.rs | 21 +-- .../op-rbuilder/src/tests/framework/txs.rs | 2 +- .../op-rbuilder/src/tests/miner_gas_limit.rs | 134 ++++++++++++++++++ crates/builder/op-rbuilder/src/tests/mod.rs | 3 + 21 files changed, 322 insertions(+), 129 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index da148101..7a118df4 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -102,6 +102,7 @@ async-trait = { workspace = true } clap_builder = { workspace = true } clap.workspace = true derive_more.workspace = true +either.workspace = true metrics.workspace = true serde_json.workspace = true tokio-util.workspace = true @@ -130,7 +131,7 @@ sha3 = "0.10" reqwest = "0.12.23" k256 = "0.13.4" -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", rev = "dd12e8e8366004b4758bfa0cfa98efa6929b7e9f" } +rollup-boost = { git = "http://github.com/flashbots/rollup-boost", tag = "v0.7.8" } nanoid = { version = "0.4", optional = true } reth-ipc = { workspace = true, optional = true } diff --git a/crates/builder/op-rbuilder/src/args/mod.rs b/crates/builder/op-rbuilder/src/args/mod.rs index a89f3790..587cce8a 100644 --- a/crates/builder/op-rbuilder/src/args/mod.rs +++ b/crates/builder/op-rbuilder/src/args/mod.rs @@ -68,10 +68,10 @@ impl CliExt for Cli { /// 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 { - if node_command.ext.flashblocks.enabled { - return BuilderMode::Flashblocks; - } + if let Commands::Node(ref node_command) = self.command + && node_command.ext.flashblocks.enabled + { + return BuilderMode::Flashblocks; } BuilderMode::Standard } diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 54894d28..d8652831 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -12,13 +12,17 @@ use reth_basic_payload_builder::PayloadConfig; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_evm::{ ConfigureEvm, Evm, EvmEnv, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, + op_revm::L1BlockInfo, }; use reth_node_api::PayloadBuilderError; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; use reth_optimism_node::OpPayloadBuilderAttributes; -use reth_optimism_payload_builder::{config::OpDAConfig, error::OpPayloadBuilderError}; +use reth_optimism_payload_builder::{ + config::{OpDAConfig, OpGasLimitConfig}, + error::OpPayloadBuilderError, +}; use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; use reth_optimism_txpool::{ conditional::MaybeConditionalTransaction, @@ -51,6 +55,8 @@ pub struct OpPayloadBuilderCtx { pub evm_config: OpEvmConfig, /// The DA config for the payload builder pub da_config: OpDAConfig, + // Gas limit configuration for the payload builder + pub gas_limit_config: OpGasLimitConfig, /// The chainspec pub chain_spec: Arc, /// How to build the payload. @@ -111,9 +117,13 @@ impl OpPayloadBuilderCtx { /// Returns the block gas limit to target. pub fn block_gas_limit(&self) -> u64 { - self.attributes() - .gas_limit - .unwrap_or(self.evm_env.block_env.gas_limit) + match self.gas_limit_config.gas_limit() { + Some(gas_limit) => gas_limit, + None => self + .attributes() + .gas_limit + .unwrap_or(self.evm_env.block_env.gas_limit), + } } /// Returns the block number for the block. @@ -412,6 +422,15 @@ impl OpPayloadBuilderCtx { } } + let da_footprint_gas_scalar = self + .chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + .then_some( + L1BlockInfo::fetch_da_footprint_gas_scalar(evm.db_mut()).expect( + "DA footprint should always be available from the database post jovian", + ), + ); + // ensure we still have capacity for this transaction if let Err(result) = info.is_tx_over_limits( tx_da_size, @@ -419,6 +438,7 @@ impl OpPayloadBuilderCtx { tx_da_limit, block_da_limit, tx.gas_limit(), + da_footprint_gas_scalar, ) { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from @@ -508,12 +528,12 @@ impl OpPayloadBuilderCtx { // add gas used by the transaction to cumulative gas used, before creating the // receipt - if let Some(max_gas_per_txn) = self.max_gas_per_txn { - if gas_used > max_gas_per_txn { - log_txn(TxnExecutionResult::MaxGasUsageExceeded); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue; - } + if let Some(max_gas_per_txn) = self.max_gas_per_txn + && gas_used > max_gas_per_txn + { + log_txn(TxnExecutionResult::MaxGasUsageExceeded); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; } info.cumulative_gas_used += gas_used; diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs index 2f36c96d..ec2a45d1 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/best_txs.rs @@ -67,27 +67,27 @@ where let flashblock_number_max = tx.flashblock_number_max(); // Check min flashblock requirement - if let Some(min) = flashblock_number_min { - if self.current_flashblock_number < min { - continue; - } + if let Some(min) = flashblock_number_min + && self.current_flashblock_number < min + { + continue; } // Check max flashblock requirement - if let Some(max) = flashblock_number_max { - if self.current_flashblock_number > max { - debug!( - target: "payload_builder", - tx_hash = ?tx.hash(), - sender = ?tx.sender(), - nonce = tx.nonce(), - current_flashblock = self.current_flashblock_number, - max_flashblock = max, - "Bundle flashblock max exceeded" - ); - self.inner.mark_invalid(tx.sender(), tx.nonce()); - continue; - } + if let Some(max) = flashblock_number_max + && self.current_flashblock_number > max + { + debug!( + target: "payload_builder", + tx_hash = ?tx.hash(), + sender = ?tx.sender(), + nonce = tx.nonce(), + current_flashblock = self.current_flashblock_number, + max_flashblock = max, + "Bundle flashblock max exceeded" + ); + self.inner.mark_invalid(tx.sender(), tx.nonce()); + continue; } return Some(tx); diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs index a53d67cd..bc1063ac 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/builder_tx.rs @@ -310,30 +310,28 @@ impl BuilderTransactions builder_txs.extend(tx); } - if ctx.is_last_flashblock() { - if let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx { - // Commit state that should be included to compute the correct nonce - let flashblocks_builder_txs = builder_txs - .iter() - .filter(|tx| tx.is_top_of_block == top_of_block) - .map(|tx| tx.signed_tx.clone()) - .collect(); - self.commit_txs(flashblocks_builder_txs, ctx, &mut *db)?; + if ctx.is_last_flashblock() + && let Some(flashtestations_builder_tx) = &self.flashtestations_builder_tx + { + // Commit state that should be included to compute the correct nonce + let flashblocks_builder_txs = builder_txs + .iter() + .filter(|tx| tx.is_top_of_block == top_of_block) + .map(|tx| tx.signed_tx.clone()) + .collect(); + self.commit_txs(flashblocks_builder_txs, ctx, &mut *db)?; - // We only include flashtestations txs in the last flashblock - match flashtestations_builder_tx.simulate_builder_txs( - state_provider, - info, - ctx, - db, - top_of_block, - ) { - Ok(flashtestations_builder_txs) => { - builder_txs.extend(flashtestations_builder_txs) - } - Err(e) => { - warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx") - } + // We only include flashtestations txs in the last flashblock + match flashtestations_builder_tx.simulate_builder_txs( + state_provider, + info, + ctx, + db, + top_of_block, + ) { + Ok(flashtestations_builder_txs) => builder_txs.extend(flashtestations_builder_txs), + Err(e) => { + warn!(target: "flashtestations", error = ?e, "failed to add flashtestations builder tx") } } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs index c465445f..16d1a71c 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -9,7 +9,10 @@ use reth_basic_payload_builder::PayloadConfig; use reth_evm::EvmEnv; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; -use reth_optimism_payload_builder::{OpPayloadBuilderAttributes, config::OpDAConfig}; +use reth_optimism_payload_builder::{ + OpPayloadBuilderAttributes, + config::{OpDAConfig, OpGasLimitConfig}, +}; use reth_optimism_primitives::OpTransactionSigned; use std::sync::Arc; use tokio_util::sync::CancellationToken; @@ -66,6 +69,7 @@ impl OpPayloadSyncerCtx { OpPayloadBuilderCtx { evm_config: self.evm_config, da_config: self.da_config, + gas_limit_config: OpGasLimitConfig::default(), chain_spec: self.chain_spec, config: payload_config, evm_env, diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 1dc36d24..52bca2a1 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -18,10 +18,10 @@ use alloy_consensus::{ use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_primitives::{Address, B256, U256, map::foldhash::HashMap}; use core::time::Duration; +use either::Either; use eyre::WrapErr as _; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::BuildOutcome; -use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; use reth_evm::{ConfigureEvm, execute::BlockBuilder}; use reth_node_api::{Block, NodePrimitives, PayloadBuilderError}; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; @@ -29,6 +29,7 @@ use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; +use reth_payload_primitives::BuiltPayloadExecutedBlock; use reth_payload_util::BestPayloadTransactions; use reth_primitives_traits::RecoveredBlock; use reth_provider::{ @@ -256,6 +257,7 @@ where block_env_attributes, cancel, da_config: self.config.da_config.clone(), + gas_limit_config: self.config.gas_limit_config.clone(), builder_signer: self.config.builder_signer, metrics: Default::default(), extra_ctx, @@ -1073,13 +1075,12 @@ where let recovered_block = RecoveredBlock::new_unhashed(block.clone(), info.executed_senders.clone()); // create the executed block data - let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { - block: ExecutedBlock { - recovered_block: Arc::new(recovered_block), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - }, - trie: ExecutedTrieUpdates::Present(Arc::new(trie_output)), + + let executed = BuiltPayloadExecutedBlock { + recovered_block: Arc::new(recovered_block), + execution_output: Arc::new(execution_outcome), + hashed_state: Either::Left(Arc::new(hashed_state)), + trie_updates: Either::Left(Arc::new(trie_output)), }; debug!(target: "payload_builder", message = "Executed block created"); diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs index bc02c34e..96b6f683 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload_handler.rs @@ -346,12 +346,12 @@ fn execute_transactions( } }; - if let Some(max_gas_per_txn) = max_gas_per_txn { - if result.gas_used() > max_gas_per_txn { - return Err(eyre::eyre!( - "transaction exceeded max gas per txn limit in flashblock" - )); - } + if let Some(max_gas_per_txn) = max_gas_per_txn + && result.gas_used() > max_gas_per_txn + { + return Err(eyre::eyre!( + "transaction exceeded max gas per txn limit in flashblock" + )); } let tx_gas_used = result.gas_used(); diff --git a/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs index 3408751c..009d2a5f 100644 --- a/crates/builder/op-rbuilder/src/builders/generator.rs +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -470,8 +470,8 @@ mod tests { use alloy_primitives::U256; use rand::rng; use reth::tasks::TokioTaskExecutor; - use reth_chain_state::ExecutedBlockWithTrieUpdates; - use reth_node_api::NodePrimitives; + use reth_chain_state::ExecutedBlock; + use reth_node_api::{BuiltPayloadExecutedBlock, NodePrimitives}; use reth_optimism_payload_builder::{OpPayloadPrimitives, payload::OpPayloadBuilderAttributes}; use reth_optimism_primitives::OpPrimitives; use reth_primitives::SealedBlock; @@ -590,7 +590,7 @@ mod tests { } /// Returns the entire execution data for the built block, if available. - fn executed_block(&self) -> Option> { + fn executed_block(&self) -> Option> { None } diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 81c49cee..4edb2088 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -5,7 +5,7 @@ use core::{ }; use reth_node_builder::components::PayloadServiceBuilder; use reth_optimism_evm::OpEvmConfig; -use reth_optimism_payload_builder::config::OpDAConfig; +use reth_optimism_payload_builder::config::{OpDAConfig, OpGasLimitConfig}; use crate::{ args::OpRbuilderArgs, @@ -94,6 +94,9 @@ pub struct BuilderConfig { /// 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. @@ -140,6 +143,7 @@ impl core::fmt::Debug for BuilderConfig { .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) @@ -157,6 +161,7 @@ impl Default for BuilderConfig { 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, @@ -179,6 +184,7 @@ where 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(), diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 66e0adda..f36fba81 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -14,16 +14,16 @@ use alloy_evm::Database; use alloy_primitives::U256; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::{BuildOutcome, BuildOutcomeKind, MissingPayloadBehaviour}; -use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; 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::{OpPrimitives, OpTransactionSigned}; +use reth_optimism_primitives::OpTransactionSigned; 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, @@ -231,6 +231,7 @@ where 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, @@ -564,17 +565,18 @@ impl OpBuilder<'_, Txs> { info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); // create the executed block data - let executed: ExecutedBlockWithTrieUpdates = ExecutedBlockWithTrieUpdates { - block: ExecutedBlock { - recovered_block: Arc::new(RecoveredBlock::< - alloy_consensus::Block, - >::new_sealed( - sealed_block.as_ref().clone(), info.executed_senders - )), - execution_output: Arc::new(execution_outcome), - hashed_state: Arc::new(hashed_state), - }, - trie: ExecutedTrieUpdates::Present(Arc::new(trie_output)), + use either::Either; + use reth_payload_primitives::BuiltPayloadExecutedBlock; + let executed = BuiltPayloadExecutedBlock { + recovered_block: Arc::new( + RecoveredBlock::>::new_sealed( + sealed_block.as_ref().clone(), + info.executed_senders, + ), + ), + execution_output: Arc::new(execution_outcome), + hashed_state: Either::Left(Arc::new(hashed_state)), + trie_updates: Either::Left(Arc::new(trie_output)), }; let no_tx_pool = ctx.attributes().no_tx_pool; @@ -588,10 +590,10 @@ impl OpBuilder<'_, Txs> { ctx.metrics .payload_byte_size - .record(payload.block().size() as f64); + .record(InMemorySize::size(payload.block()) as f64); ctx.metrics .payload_byte_size_gauge - .set(payload.block().size() as f64); + .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 diff --git a/crates/builder/op-rbuilder/src/gas_limiter/mod.rs b/crates/builder/op-rbuilder/src/gas_limiter/mod.rs index 327a6749..71daae7c 100644 --- a/crates/builder/op-rbuilder/src/gas_limiter/mod.rs +++ b/crates/builder/op-rbuilder/src/gas_limiter/mod.rs @@ -115,7 +115,7 @@ impl AddressGasLimiterInner { }); // Only clean up stale buckets every `cleanup_interval` blocks - if block_number % self.config.cleanup_interval == 0 { + if block_number.is_multiple_of(self.config.cleanup_interval) { self.address_buckets .retain(|_, bucket| bucket.available <= bucket.capacity); } diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index 08a541b6..e487e016 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -36,8 +36,11 @@ pub fn launch() -> Result<()> { _ => Default::default(), }; - let mut cli_app = cli.configure(); + #[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; @@ -45,7 +48,6 @@ pub fn launch() -> Result<()> { cli_app.access_tracing_layers()?.add_layer(telemetry_layer); } - cli_app.init_tracing()?; match mode { BuilderMode::Standard => { tracing::info!("Starting OP builder in standard mode"); @@ -102,6 +104,7 @@ where 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; let op_node = OpNode::new(rollup_args.clone()); let reverted_cache = Cache::builder().max_capacity(100).build(); @@ -116,6 +119,7 @@ where .with_sequencer(rollup_args.sequencer.clone()) .with_enable_tx_conditional(rollup_args.enable_tx_conditional) .with_da_config(da_config) + .with_gas_limit_config(gas_limit_config) .build(); if cfg!(feature = "custom-engine-api") { let engine_builder: OpEngineApiBuilder = diff --git a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs index a9cbbae6..00b341f2 100644 --- a/crates/builder/op-rbuilder/src/monitor_tx_pool.rs +++ b/crates/builder/op-rbuilder/src/monitor_tx_pool.rs @@ -27,7 +27,7 @@ async fn transaction_event_log( "Transaction event received" ) } - FullTransactionEvent::Queued(hash) => { + FullTransactionEvent::Queued(hash, _) => { info!( target = "monitoring", tx_hash = hash.to_string(), diff --git a/crates/builder/op-rbuilder/src/primitives/bundle.rs b/crates/builder/op-rbuilder/src/primitives/bundle.rs index 3c415ad5..6e4c471b 100644 --- a/crates/builder/op-rbuilder/src/primitives/bundle.rs +++ b/crates/builder/op-rbuilder/src/primitives/bundle.rs @@ -175,10 +175,10 @@ impl Bundle { // Validate block number ranges if let Some(max) = block_number_max { // Check if min > max - if let Some(min) = block_number_min { - if min > max { - return Err(BundleConditionalError::MinGreaterThanMax { min, max }); - } + if let Some(min) = block_number_min + && min > max + { + return Err(BundleConditionalError::MinGreaterThanMax { min, max }); } // The max block cannot be a past block @@ -204,23 +204,22 @@ impl Bundle { block_number_max = Some(default_max); // Ensure that the new max is not smaller than the min - if let Some(min) = block_number_min { - if min > default_max { - return Err(BundleConditionalError::MinTooHighForDefaultRange { - min, - max_allowed: default_max, - }); - } + if let Some(min) = block_number_min + && min > default_max + { + return Err(BundleConditionalError::MinTooHighForDefaultRange { + min, + max_allowed: default_max, + }); } } // Validate flashblock number range - if let Some(min) = self.flashblock_number_min { - if let Some(max) = self.flashblock_number_max { - if min > max { - return Err(BundleConditionalError::FlashblockMinGreaterThanMax { min, max }); - } - } + if let Some(min) = self.flashblock_number_min + && let Some(max) = self.flashblock_number_max + && min > max + { + return Err(BundleConditionalError::FlashblockMinGreaterThanMax { min, max }); } Ok(BundleConditional { diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index b2591c21..e6804885 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -69,14 +69,14 @@ impl ExecutionInfo { tx_data_limit: Option, block_data_limit: Option, tx_gas_limit: u64, + da_footprint_gas_scalar: Option, ) -> Result<(), TxnExecutionResult> { if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) { return Err(TxnExecutionResult::TransactionDALimitExceeded); } + let total_da_bytes_used = self.cumulative_da_bytes_used.saturating_add(tx_da_size); - if block_data_limit - .is_some_and(|da_limit| self.cumulative_da_bytes_used + tx_da_size > da_limit) - { + if block_data_limit.is_some_and(|da_limit| total_da_bytes_used > da_limit) { return Err(TxnExecutionResult::BlockDALimitExceeded( self.cumulative_da_bytes_used, tx_da_size, @@ -84,6 +84,19 @@ impl ExecutionInfo { )); } + // Post Jovian: the tx DA footprint must be less than the block gas limit + if let Some(da_footprint_gas_scalar) = da_footprint_gas_scalar { + let tx_da_footprint = + total_da_bytes_used.saturating_mul(da_footprint_gas_scalar as u64); + if tx_da_footprint > block_gas_limit { + return Err(TxnExecutionResult::BlockDALimitExceeded( + total_da_bytes_used, + tx_da_size, + tx_da_footprint, + )); + } + } + if self.cumulative_gas_used + tx_gas_limit > block_gas_limit { return Err(TxnExecutionResult::TransactionGasLimitExceeded( self.cumulative_gas_used, diff --git a/crates/builder/op-rbuilder/src/primitives/telemetry.rs b/crates/builder/op-rbuilder/src/primitives/telemetry.rs index 7e73ef2a..dfedbcc5 100644 --- a/crates/builder/op-rbuilder/src/primitives/telemetry.rs +++ b/crates/builder/op-rbuilder/src/primitives/telemetry.rs @@ -1,5 +1,6 @@ use crate::args::TelemetryArgs; use tracing_subscriber::{Layer, filter::Targets}; +use url::Url; /// Setup telemetry layer with sampling and custom endpoint configuration pub fn setup_telemetry_layer( @@ -7,16 +8,22 @@ pub fn setup_telemetry_layer( ) -> eyre::Result> { use tracing::level_filters::LevelFilter; - // Otlp uses evn vars inside - if let Some(endpoint) = &args.otlp_endpoint { - unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", endpoint) }; + if args.otlp_endpoint.is_none() { + return Err(eyre::eyre!("OTLP endpoint is not set")); } + + // Otlp uses evn vars inside + if let Some(headers) = &args.otlp_headers { unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", headers) }; } // Create OTLP layer with custom configuration - let otlp_layer = reth_tracing_otlp::layer("op-rbuilder"); + let otlp_layer = reth_tracing_otlp::span_layer( + "op-rbuilder", + &Url::parse(args.otlp_endpoint.as_ref().unwrap()).expect("Invalid OTLP endpoint"), + reth_tracing_otlp::OtlpProtocol::Http, + )?; // Create a trace filter that sends more data to OTLP but less to stdout let trace_filter = Targets::new() diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index d698bf45..8c718491 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -111,6 +111,7 @@ impl LocalInstance { 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(); let addons: OpAddOns< _, @@ -121,6 +122,7 @@ impl LocalInstance { .with_sequencer(args.rollup_args.sequencer.clone()) .with_enable_tx_conditional(args.rollup_args.enable_tx_conditional) .with_da_config(da_config) + .with_gas_limit_config(gas_limit_config) .build(); let node_builder = NodeBuilder::<_, OpChainSpec>::new(config.clone()) @@ -440,10 +442,10 @@ impl FlashblocksListener { pub fn contains_transaction(&self, tx_hash: &B256) -> bool { let tx_hash_str = format!("{tx_hash:#x}"); self.flashblocks.lock().iter().any(|fb| { - if let Some(receipts) = fb.metadata.get("receipts") { - if let Some(receipts_obj) = receipts.as_object() { - return receipts_obj.contains_key(&tx_hash_str); - } + if let Some(receipts) = fb.metadata.get("receipts") + && let Some(receipts_obj) = receipts.as_object() + { + return receipts_obj.contains_key(&tx_hash_str); } false }) @@ -453,12 +455,11 @@ impl FlashblocksListener { pub fn find_transaction_flashblock(&self, tx_hash: &B256) -> Option { let tx_hash_str = format!("{tx_hash:#x}"); self.flashblocks.lock().iter().find_map(|fb| { - if let Some(receipts) = fb.metadata.get("receipts") { - if let Some(receipts_obj) = receipts.as_object() { - if receipts_obj.contains_key(&tx_hash_str) { - return Some(fb.index); - } - } + if let Some(receipts) = fb.metadata.get("receipts") + && let Some(receipts_obj) = receipts.as_object() + && receipts_obj.contains_key(&tx_hash_str) + { + return Some(fb.index); } None }) diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index e8133b05..ed6b6265 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -284,7 +284,7 @@ impl TransactionPoolObserver { tracing::debug!("Transaction pending: {hash}"); observations.entry(hash).or_default().push_back(TransactionEvent::Pending); }, - Some(FullTransactionEvent::Queued(hash)) => { + Some(FullTransactionEvent::Queued(hash, _)) => { tracing::debug!("Transaction queued: {hash}"); observations.entry(hash).or_default().push_back(TransactionEvent::Queued); }, diff --git a/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs b/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs new file mode 100644 index 00000000..f0aaa353 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs @@ -0,0 +1,134 @@ +use crate::tests::{BlockTransactionsExt, LocalInstance}; +use alloy_provider::Provider; +use macros::{if_flashblocks, if_standard, rb_test}; +use reth_primitives_traits::constants::MEGAGAS; +/// This test ensures that the miner gas limit is respected +/// We will set the limit to 60,000 and see that the builder will not include any transactions +#[rb_test] +async fn miner_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + let call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (60000,)) + .await?; + assert!(call, "miner_setGasLimit should be executed successfully"); + + let unfit_tx = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + // tx should not be included because the gas limit is less than the transaction gas + assert!( + !block.includes(unfit_tx.tx_hash()), + "transaction should not be included in the block" + ); + + Ok(()) +} + +/// This test ensures that block will fill up to the limit +/// Each transaction is 53000 gas +/// There is a deposit transaction for 24890 gas, and a builder transaction for 21600 gas +/// We will set our limit to 1Mgas and see that the builder includes 17 transactions +/// Total of 19 transactions including deposit + builder +/// In Flashblocks mode, there are 2 builder transactions, one at the beginning and one at the end of the block +/// So the total number of transactions in the block will be 20 for Flashblocks mode +#[rb_test] +async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + let call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (MEGAGAS,)) + .await?; + assert!(call, "miner_setGasLimit should be executed successfully"); + + let mut tx_hashes = Vec::new(); + for _ in 0..17 { + let tx = driver + .create_transaction() + .with_gas_limit(53000) + .with_max_priority_fee_per_gas(50) + .send() + .await?; + tx_hashes.push(tx.tx_hash().clone()); + } + let unfit_tx = driver + .create_transaction() + .with_gas_limit(53000) + .with_max_priority_fee_per_gas(50) + .send() + .await?; + + let block = driver.build_new_block().await?; + + for (i, tx_hash) in tx_hashes.iter().enumerate() { + assert!(block.includes(tx_hash), "tx{} should be in block", i); + } + assert!( + !block.includes(unfit_tx.tx_hash()), + "unfit tx should not be in block" + ); + + if_standard! { + assert_eq!( + block.transactions.len(), + 19, + "deposit + builder + 17 valid txs should be in the block" + ); + } + + if_flashblocks! { + assert_eq!( + block.transactions.len(), + 20, + "deposit + 2 builder + 17 valid txs should be in the block" + ); + } + + Ok(()) +} + +/// This test ensures that the gasLimit can be reset to the default value +/// by setting it to 0 +#[rb_test] +async fn reset_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + let call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (60000,)) + .await?; + assert!(call, "miner_setGasLimit should be executed successfully"); + + let unfit_tx = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + // tx should not be included because the gas limit is less than the transaction gas + assert!( + !block.includes(unfit_tx.tx_hash()), + "transaction should not be included in the block" + ); + + let reset_call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (0,)) + .await?; + assert!( + reset_call, + "miner_setGasLimit should be executed successfully" + ); + + let _ = driver.build_new_block().await?; + + let fit_tx = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + // tx should be included because the gas limit is reset to the default value + assert!( + block.includes(fit_tx.tx_hash()), + "transaction should be in block" + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs index 17be8d09..afeff1cc 100644 --- a/crates/builder/op-rbuilder/src/tests/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -11,6 +11,9 @@ mod flashtestations; #[cfg(test)] mod data_availability; +#[cfg(test)] +mod miner_gas_limit; + #[cfg(test)] mod gas_limiter; From a959e185bda55485d0f138e48ca7ea6ce4e5866d Mon Sep 17 00:00:00 2001 From: shana Date: Mon, 10 Nov 2025 10:27:55 -0800 Subject: [PATCH 209/262] Add workload id as metric to builder (#315) * Add workload id as metric to builder * more tdx measurements * add address * add address to metric * remove rpc * extract constants --- .../src/flashtestations/attestation.rs | 190 ++++++++++++++++++ .../src/flashtestations/service.rs | 4 + crates/builder/op-rbuilder/src/metrics.rs | 41 +++- 3 files changed, 234 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs index b38dcf5a..6af5cc24 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs @@ -1,8 +1,53 @@ use reqwest::Client; +use sha3::{Digest, Keccak256}; use tracing::info; const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/attest"; +// Raw TDX v4 quote structure constants +// Raw quote has a 48-byte header before the TD10ReportBody +const HEADER_LENGTH: usize = 48; +const TD_REPORT10_LENGTH: usize = 584; + +// TDX workload constants +const TD_XFAM_FPU: u64 = 0x0000000000000001; +const TD_XFAM_SSE: u64 = 0x0000000000000002; +const TD_TDATTRS_VE_DISABLED: u64 = 0x0000000010000000; +const TD_TDATTRS_PKS: u64 = 0x0000000040000000; +const TD_TDATTRS_KL: u64 = 0x0000000080000000; + +// TD10ReportBody field offsets +// These offsets correspond to the Solidity parseRawReportBody implementation +const OFFSET_TD_ATTRIBUTES: usize = 120; +const OFFSET_XFAM: usize = 128; +const OFFSET_MR_TD: usize = 136; +const OFFSET_MR_CONFIG_ID: usize = 184; +const OFFSET_MR_OWNER: usize = 232; +const OFFSET_MR_OWNER_CONFIG: usize = 280; +const OFFSET_RT_MR0: usize = 328; +const OFFSET_RT_MR1: usize = 376; +const OFFSET_RT_MR2: usize = 424; +const OFFSET_RT_MR3: usize = 472; + +// Field lengths +const MEASUREMENT_REGISTER_LENGTH: usize = 48; +const ATTRIBUTE_LENGTH: usize = 8; + +/// Parsed TDX quote report body containing measurement registers and attributes +#[derive(Debug, Clone)] +pub struct ParsedQuote { + pub mr_td: [u8; 48], + pub rt_mr0: [u8; 48], + pub rt_mr1: [u8; 48], + pub rt_mr2: [u8; 48], + pub rt_mr3: [u8; 48], + pub mr_config_id: [u8; 48], + pub mr_owner: [u8; 48], + pub mr_owner_config: [u8; 48], + pub xfam: u64, + pub td_attributes: u64, +} + /// Configuration for attestation #[derive(Default)] pub struct AttestationConfig { @@ -63,3 +108,148 @@ pub fn get_attestation_provider(config: AttestationConfig) -> RemoteAttestationP ) } } + +/// Parse the TDX report body from a raw quote +/// Extracts measurement registers and attributes according to TD10ReportBody specification +/// https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216 +pub fn parse_report_body(raw_quote: &[u8]) -> eyre::Result { + // Validate quote length + if raw_quote.len() < HEADER_LENGTH + TD_REPORT10_LENGTH { + eyre::bail!( + "invalid quote length: {}, expected at least {}", + raw_quote.len(), + HEADER_LENGTH + TD_REPORT10_LENGTH + ); + } + + // Skip the 48-byte header to get to the TD10ReportBody + let report_body = &raw_quote[HEADER_LENGTH..]; + + // Extract fields exactly as parseRawReportBody does in Solidity + // Using named offset constants to match Solidity implementation exactly + let mr_td: [u8; 48] = report_body[OFFSET_MR_TD..OFFSET_MR_TD + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract mr_td"))?; + let rt_mr0: [u8; 48] = report_body[OFFSET_RT_MR0..OFFSET_RT_MR0 + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract rt_mr0"))?; + let rt_mr1: [u8; 48] = report_body[OFFSET_RT_MR1..OFFSET_RT_MR1 + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract rt_mr1"))?; + let rt_mr2: [u8; 48] = report_body[OFFSET_RT_MR2..OFFSET_RT_MR2 + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract rt_mr2"))?; + let rt_mr3: [u8; 48] = report_body[OFFSET_RT_MR3..OFFSET_RT_MR3 + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract rt_mr3"))?; + let mr_config_id: [u8; 48] = report_body + [OFFSET_MR_CONFIG_ID..OFFSET_MR_CONFIG_ID + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract mr_config_id"))?; + let mr_owner: [u8; 48] = report_body + [OFFSET_MR_OWNER..OFFSET_MR_OWNER + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract mr_owner"))?; + let mr_owner_config: [u8; 48] = report_body + [OFFSET_MR_OWNER_CONFIG..OFFSET_MR_OWNER_CONFIG + MEASUREMENT_REGISTER_LENGTH] + .try_into() + .map_err(|_| eyre::eyre!("failed to extract mr_owner_config"))?; + + // Extract xFAM and tdAttributes (8 bytes each) + // In Solidity, bytes8 is treated as big-endian for bitwise operations + let xfam = u64::from_be_bytes( + report_body[OFFSET_XFAM..OFFSET_XFAM + ATTRIBUTE_LENGTH] + .try_into() + .map_err(|e| eyre::eyre!("failed to parse xfam: {}", e))?, + ); + let td_attributes = u64::from_be_bytes( + report_body[OFFSET_TD_ATTRIBUTES..OFFSET_TD_ATTRIBUTES + ATTRIBUTE_LENGTH] + .try_into() + .map_err(|e| eyre::eyre!("failed to parse td_attributes: {}", e))?, + ); + + Ok(ParsedQuote { + mr_td, + rt_mr0, + rt_mr1, + rt_mr2, + rt_mr3, + mr_config_id, + mr_owner, + mr_owner_config, + xfam, + td_attributes, + }) +} + +/// Compute workload ID from parsed quote data +/// This corresponds to QuoteParser.parseV4VerifierOutput in Solidity implementation +/// The workload ID uniquely identifies a TEE workload based on its measurement registers +pub fn compute_workload_id_from_parsed(parsed: &ParsedQuote) -> [u8; 32] { + // Apply transformations as per the Solidity implementation + // expectedXfamBits = TD_XFAM_FPU | TD_XFAM_SSE + let expected_xfam_bits = TD_XFAM_FPU | TD_XFAM_SSE; + + // ignoredTdAttributesBitmask = TD_TDATTRS_VE_DISABLED | TD_TDATTRS_PKS | TD_TDATTRS_KL + let ignored_td_attributes_bitmask = TD_TDATTRS_VE_DISABLED | TD_TDATTRS_PKS | TD_TDATTRS_KL; + + // Transform xFAM: xFAM ^ expectedXfamBits + let transformed_xfam = parsed.xfam ^ expected_xfam_bits; + + // Transform tdAttributes: tdAttributes & ~ignoredTdAttributesBitmask + let transformed_td_attributes = parsed.td_attributes & !ignored_td_attributes_bitmask; + + // Convert transformed values to bytes (big-endian, to match Solidity bytes8) + let xfam_bytes = transformed_xfam.to_be_bytes(); + let td_attributes_bytes = transformed_td_attributes.to_be_bytes(); + + // Concatenate all fields + let mut concatenated = Vec::new(); + concatenated.extend_from_slice(&parsed.mr_td); + concatenated.extend_from_slice(&parsed.rt_mr0); + concatenated.extend_from_slice(&parsed.rt_mr1); + concatenated.extend_from_slice(&parsed.rt_mr2); + concatenated.extend_from_slice(&parsed.rt_mr3); + concatenated.extend_from_slice(&parsed.mr_config_id); + concatenated.extend_from_slice(&xfam_bytes); + concatenated.extend_from_slice(&td_attributes_bytes); + + // Compute keccak256 hash + let mut hasher = Keccak256::new(); + hasher.update(&concatenated); + let result = hasher.finalize(); + + let mut workload_id = [0u8; 32]; + workload_id.copy_from_slice(&result); + + workload_id +} + +/// Compute workload ID from raw quote bytes +/// This is a convenience function that combines parsing and computation +pub fn compute_workload_id(raw_quote: &[u8]) -> eyre::Result<[u8; 32]> { + let parsed = parse_report_body(raw_quote)?; + Ok(compute_workload_id_from_parsed(&parsed)) +} + +#[cfg(test)] +mod tests { + use crate::tests::WORKLOAD_ID; + + use super::*; + + #[test] + fn test_compute_workload_id_from_test_quote() { + // Load the test quote output used in integration tests + let quote_output = include_bytes!("../tests/framework/artifacts/test-quote.bin"); + + // Compute the workload ID + let workload_id = compute_workload_id(quote_output) + .expect("failed to compute workload ID from test quote"); + + assert_eq!( + workload_id, WORKLOAD_ID, + "workload ID mismatch for test quote" + ); + } +} diff --git a/crates/builder/op-rbuilder/src/flashtestations/service.rs b/crates/builder/op-rbuilder/src/flashtestations/service.rs index 3bd5b854..73896119 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/service.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/service.rs @@ -14,6 +14,7 @@ use super::{ }; use crate::{ flashtestations::builder_tx::{FlashtestationsBuilderTx, FlashtestationsBuilderTxArgs}, + metrics::record_tee_metrics, tx_signer::{Signer, generate_key_from_seed, generate_signer}, }; use std::fmt::Debug; @@ -74,6 +75,9 @@ where info!(target: "flashtestations", "requesting TDX attestation"); let attestation = attestation_provider.get_attestation(report_data).await?; + // Record TEE metrics (workload ID, MRTD, RTMR0) + record_tee_metrics(&attestation, &tee_service_signer.address)?; + // Use an external rpc when the builder is not the same as the builder actively building blocks onchain let registered = if let Some(rpc_url) = args.rpc_url { let tx_manager = TxManager::new( diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index c268c43c..c381fed8 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -1,10 +1,14 @@ +use alloy_primitives::{Address, hex}; use metrics::IntoF64; use reth_metrics::{ Metrics, metrics::{Counter, Gauge, Histogram, gauge}, }; -use crate::args::OpRbuilderArgs; +use crate::{ + args::OpRbuilderArgs, + flashtestations::attestation::{compute_workload_id_from_parsed, parse_report_body}, +}; /// The latest version from Cargo.toml. pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -202,6 +206,41 @@ pub fn record_flag_gauge_metrics(builder_args: &OpRbuilderArgs) { .set(builder_args.enable_revert_protection as i32); } +/// Record TEE workload ID and measurement metrics +/// Parses the quote, computes workload ID, and records workload_id, mr_td (TEE measurement), and rt_mr0 (runtime measurement register 0) +/// These identify the trusted execution environment configuration provided by GCP +pub fn record_tee_metrics(raw_quote: &[u8], tee_address: &Address) -> eyre::Result<()> { + let parsed_quote = parse_report_body(raw_quote)?; + let workload_id = compute_workload_id_from_parsed(&parsed_quote); + + let workload_id_hex = hex::encode(workload_id); + let mr_td_hex = hex::encode(parsed_quote.mr_td); + let rt_mr0_hex = hex::encode(parsed_quote.rt_mr0); + + let tee_address_static: &'static str = Box::leak(tee_address.to_string().into_boxed_str()); + let workload_id_static: &'static str = Box::leak(workload_id_hex.into_boxed_str()); + let mr_td_static: &'static str = Box::leak(mr_td_hex.into_boxed_str()); + let rt_mr0_static: &'static str = Box::leak(rt_mr0_hex.into_boxed_str()); + + // Record TEE address + let tee_address_labels: [(&str, &str); 1] = [("tee_address", tee_address_static)]; + gauge!("op_rbuilder_tee_address", &tee_address_labels).set(1); + + // Record workload ID + let workload_labels: [(&str, &str); 1] = [("workload_id", workload_id_static)]; + gauge!("op_rbuilder_tee_workload_id", &workload_labels).set(1); + + // Record MRTD (TEE measurement) + let mr_td_labels: [(&str, &str); 1] = [("mr_td", mr_td_static)]; + gauge!("op_rbuilder_tee_mr_td", &mr_td_labels).set(1); + + // Record RTMR0 (runtime measurement register 0) + let rt_mr0_labels: [(&str, &str); 1] = [("rt_mr0", rt_mr0_static)]; + gauge!("op_rbuilder_tee_rt_mr0", &rt_mr0_labels).set(1); + + Ok(()) +} + /// Contains version information for the application. #[derive(Debug, Clone)] pub struct VersionInfo { From 1df3e0e644ea9606bd611113feb06e8da50c39c4 Mon Sep 17 00:00:00 2001 From: theo <80177219+theochap@users.noreply.github.com> Date: Wed, 12 Nov 2025 22:42:35 -0500 Subject: [PATCH 210/262] chore(deps/reth): bump reth to 1.9.2 (#318) --- crates/builder/op-rbuilder/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index 7a118df4..c312f643 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -131,7 +131,7 @@ sha3 = "0.10" reqwest = "0.12.23" k256 = "0.13.4" -rollup-boost = { git = "http://github.com/flashbots/rollup-boost", tag = "v0.7.8" } +rollup-boost.workspace = true nanoid = { version = "0.4", optional = true } reth-ipc = { workspace = true, optional = true } From 0efe6308b592872f4c8711ed92b8c4900bec0711 Mon Sep 17 00:00:00 2001 From: theo <80177219+theochap@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:12:17 -0500 Subject: [PATCH 211/262] chore(deps): bump reth (#321) --- crates/builder/op-rbuilder/src/builders/builder_tx.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index 7cc56b07..d8d99adb 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -12,7 +12,7 @@ use op_alloy_consensus::OpTypedTransaction; use op_alloy_rpc_types::OpTransactionRequest; use op_revm::{OpHaltReason, OpTransactionError}; use reth_evm::{ - ConfigureEvm, Evm, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, + ConfigureEvm, Evm, EvmEnv, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, precompiles::PrecompilesMap, }; use reth_node_api::PayloadBuilderError; @@ -20,7 +20,7 @@ use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::{ProviderError, StateProvider}; use reth_revm::{State, database::StateProviderDatabase}; -use reth_rpc_api::eth::{EthTxEnvError, transaction::TryIntoTxEnv}; +use reth_rpc_api::eth::{EthTxEnvError, TryIntoTxEnv}; use revm::{ DatabaseCommit, DatabaseRef, context::{ @@ -322,7 +322,10 @@ pub trait BuilderTransactions, evm: &mut OpEvm, ) -> Result, BuilderTransactionError> { - let tx_env = tx.try_into_tx_env(evm.cfg(), evm.block())?; + let tx_env = tx.try_into_tx_env(&EvmEnv { + cfg_env: evm.cfg().clone(), + block_env: evm.block().clone(), + })?; let to = tx_env.base.kind.into_to().unwrap_or_default(); let ResultAndState { result, state } = match evm.transact(tx_env) { From a2d43105bd5892a62a13b3c7369251c38135a13b Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Tue, 18 Nov 2025 11:17:26 -0600 Subject: [PATCH 212/262] fix: jovian hardfork tests & fixes (#320) * chore: fixes for jovian * blob fee fixes * Update tests to Jovian * run miner limit tests on both standard/flashblocks * Ensure builder transactions count towards DA usage * Increment DA usage for non-deposit sequencer transactions (e.g. via CL sync) --- .../op-rbuilder/src/builders/context.rs | 67 +++++--- .../src/builders/flashblocks/payload.rs | 37 +++-- .../op-rbuilder/src/builders/generator.rs | 3 +- .../src/builders/standard/payload.rs | 35 ++-- .../src/primitives/reth/execution.rs | 3 + .../src/tests/data_availability.rs | 3 +- crates/builder/op-rbuilder/src/tests/forks.rs | 150 ++++++++++++++++++ .../framework/artifacts/genesis.json.tmpl | 16 +- .../op-rbuilder/src/tests/framework/driver.rs | 44 +++-- .../op-rbuilder/src/tests/framework/mod.rs | 3 + .../op-rbuilder/src/tests/framework/txs.rs | 5 +- .../op-rbuilder/src/tests/miner_gas_limit.rs | 36 +++-- crates/builder/op-rbuilder/src/tests/mod.rs | 2 + 13 files changed, 326 insertions(+), 78 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/tests/forks.rs diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index d8652831..e1eb4f39 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -1,5 +1,5 @@ use alloy_consensus::{Eip658Value, Transaction, conditional::BlockConditionalAttributes}; -use alloy_eips::Typed2718; +use alloy_eips::{Encodable2718, Typed2718}; use alloy_evm::Database; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::{BlockHash, Bytes, U256}; @@ -146,12 +146,20 @@ impl OpPayloadBuilderCtx { /// Returns the blob fields for the header. /// - /// This will always return `Some(0)` after ecotone. - pub fn blob_fields(&self) -> (Option, Option) { - // 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. - if self.is_ecotone_active() { + /// This will return the culmative DA bytes * scalar after Jovian + /// after Ecotone, this will always return Some(0) as blobs aren't supported + /// pre Ecotone, these fields aren't used. + pub fn blob_fields( + &self, + info: &ExecutionInfo, + ) -> (Option, Option) { + if self.is_jovian_active() { + let scalar = info + .da_footprint_scalar + .expect("Scalar must be defined for Jovian blocks"); + let result = info.cumulative_da_bytes_used * scalar as u64; + (Some(0), Some(result)) + } else if self.is_ecotone_active() { (Some(0), Some(0)) } else { (None, None) @@ -162,7 +170,15 @@ impl OpPayloadBuilderCtx { /// /// After holocene this extracts the extradata from the payload pub fn extra_data(&self) -> Result { - if self.is_holocene_active() { + if self.is_jovian_active() { + self.attributes() + .get_jovian_extra_data( + self.chain_spec.base_fee_params_at_timestamp( + self.attributes().payload_attributes.timestamp, + ), + ) + .map_err(PayloadBuilderError::other) + } else if self.is_holocene_active() { self.attributes() .get_holocene_extra_data( self.chain_spec.base_fee_params_at_timestamp( @@ -215,6 +231,12 @@ impl OpPayloadBuilderCtx { .is_isthmus_active_at_timestamp(self.attributes().timestamp()) } + /// Returns true if isthmus is active for the payload. + pub fn is_jovian_active(&self) -> bool { + self.chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + } + /// Returns the chain id pub fn chain_id(&self) -> u64 { self.chain_spec.chain_id() @@ -316,6 +338,12 @@ impl OpPayloadBuilderCtx { let gas_used = result.gas_used(); info.cumulative_gas_used += gas_used; + if !sequencer_tx.is_deposit() { + info.cumulative_da_bytes_used += op_alloy_flz::tx_estimated_size_fjord_bytes( + sequencer_tx.encoded_2718().as_slice(), + ); + } + let ctx = ReceiptBuilderCtx { tx: sequencer_tx.inner(), evm: &evm, @@ -323,6 +351,7 @@ impl OpPayloadBuilderCtx { state: &state, cumulative_gas_used: info.cumulative_gas_used, }; + info.receipts.push(self.build_receipt(ctx, depositor_nonce)); // commit changes @@ -333,6 +362,16 @@ impl OpPayloadBuilderCtx { info.executed_transactions.push(sequencer_tx.into_inner()); } + let da_footprint_gas_scalar = self + .chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + .then(|| { + L1BlockInfo::fetch_da_footprint_gas_scalar(evm.db_mut()) + .expect("DA footprint should always be available from the database post jovian") + }); + + info.da_footprint_scalar = da_footprint_gas_scalar; + Ok(info) } @@ -355,6 +394,7 @@ impl OpPayloadBuilderCtx { let mut num_bundles_reverted = 0; let mut reverted_gas_used = 0; let base_fee = self.base_fee(); + let tx_da_limit = self.da_config.max_da_tx_size(); let mut evm = self.evm_config.evm_with_env(&mut *db, self.evm_env.clone()); @@ -422,15 +462,6 @@ impl OpPayloadBuilderCtx { } } - let da_footprint_gas_scalar = self - .chain_spec - .is_jovian_active_at_timestamp(self.attributes().timestamp()) - .then_some( - L1BlockInfo::fetch_da_footprint_gas_scalar(evm.db_mut()).expect( - "DA footprint should always be available from the database post jovian", - ), - ); - // ensure we still have capacity for this transaction if let Err(result) = info.is_tx_over_limits( tx_da_size, @@ -438,7 +469,7 @@ impl OpPayloadBuilderCtx { tx_da_limit, block_da_limit, tx.gas_limit(), - da_footprint_gas_scalar, + info.da_footprint_scalar, ) { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 52bca2a1..02753bf5 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -22,6 +22,7 @@ use either::Either; use eyre::WrapErr as _; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::BuildOutcome; +use reth_chainspec::EthChainSpec; use reth_evm::{ConfigureEvm, execute::BlockBuilder}; use reth_node_api::{Block, NodePrimitives, PayloadBuilderError}; use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; @@ -222,6 +223,21 @@ where ) -> eyre::Result> { 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)) + .wrap_err("failed to get holocene extra data for flashblocks payload builder")? + } else if chain_spec.is_holocene_active_at_timestamp(timestamp) { + config + .attributes + .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) + .wrap_err("failed to get holocene extra data for flashblocks payload builder")? + } else { + Default::default() + }; + let block_env_attributes = OpNextBlockEnvAttributes { timestamp, suggested_fee_recipient: config.attributes.suggested_fee_recipient(), @@ -234,18 +250,12 @@ where .attributes .payload_attributes .parent_beacon_block_root, - extra_data: if chain_spec.is_holocene_active_at_timestamp(timestamp) { - config - .attributes - .get_holocene_extra_data(chain_spec.base_fee_params_at_timestamp(timestamp)) - .wrap_err("failed to get holocene extra data for flashblocks payload builder")? - } else { - Default::default() - }, + extra_data, }; - let evm_env = self - .evm_config + let evm_config = self.evm_config.clone(); + + let evm_env = evm_config .next_evm_env(&config.parent_header, &block_env_attributes) .wrap_err("failed to create next evm env")?; @@ -352,6 +362,7 @@ where // We subtract gas limit and da limit for builder transaction from the whole limit let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + info.cumulative_da_bytes_used += builder_tx_da_size; let (payload, fb_payload) = build_block( &mut state, @@ -628,6 +639,7 @@ where let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); + info.cumulative_da_bytes_used += builder_tx_da_size; target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size @@ -1032,10 +1044,7 @@ where // create the block header let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); - // 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(); + let (excess_blob_gas, blob_gas_used) = ctx.blob_fields(info); let extra_data = ctx.extra_data()?; let header = Header { diff --git a/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs index 009d2a5f..041c274d 100644 --- a/crates/builder/op-rbuilder/src/builders/generator.rs +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -134,7 +134,7 @@ where type Job = BlockPayloadJob; /// This is invoked when the node receives payload attributes from the beacon node via - /// `engine_forkchoiceUpdatedV1` + /// `engine_forkchoiceUpdatedVX` fn new_payload_job( &self, attributes: ::Attributes, @@ -470,7 +470,6 @@ mod tests { use alloy_primitives::U256; use rand::rng; use reth::tasks::TokioTaskExecutor; - use reth_chain_state::ExecutedBlock; use reth_node_api::{BuiltPayloadExecutedBlock, NodePrimitives}; use reth_optimism_payload_builder::{OpPayloadPrimitives, payload::OpPayloadBuilderAttributes}; use reth_optimism_primitives::OpPrimitives; diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index f36fba81..4907c5c2 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -201,6 +201,21 @@ where 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(), @@ -213,14 +228,7 @@ where .attributes .payload_attributes .parent_beacon_block_root, - extra_data: 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() - }, + extra_data, }; let evm_env = self @@ -358,6 +366,7 @@ impl OpBuilder<'_, Txs> { }; 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!( @@ -366,6 +375,7 @@ impl OpBuilder<'_, Txs> { } // 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); + info.cumulative_da_bytes_used += builder_tx_da_size; let block_da_limit = ctx .da_config .max_da_block_size() @@ -459,6 +469,11 @@ impl OpBuilder<'_, Txs> { }; 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], @@ -521,10 +536,6 @@ impl OpBuilder<'_, Txs> { // create the block header let transactions_root = proofs::calculate_transaction_root(&info.executed_transactions); - // 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(); let extra_data = ctx.extra_data()?; let header = Header { diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index e6804885..9ce998d1 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -40,6 +40,8 @@ pub struct ExecutionInfo { pub total_fees: U256, /// Extra execution information that can be attached by individual builders. pub extra: Extra, + /// DA Footprint Scalar for Jovian + pub da_footprint_scalar: Option, } impl ExecutionInfo { @@ -53,6 +55,7 @@ impl ExecutionInfo { cumulative_da_bytes_used: 0, total_fees: U256::ZERO, extra: Default::default(), + da_footprint_scalar: None, } } diff --git a/crates/builder/op-rbuilder/src/tests/data_availability.rs b/crates/builder/op-rbuilder/src/tests/data_availability.rs index ffee176f..0e856c9f 100644 --- a/crates/builder/op-rbuilder/src/tests/data_availability.rs +++ b/crates/builder/op-rbuilder/src/tests/data_availability.rs @@ -64,9 +64,10 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; // Set block big enough so it could fit 3 transactions without tx size limit + // Deposit transactions also count towards DA and there is one deposit txn in this block too let call = driver .provider() - .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 3)) + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 4)) .await?; assert!(call, "miner_setMaxDASize should be executed successfully"); diff --git a/crates/builder/op-rbuilder/src/tests/forks.rs b/crates/builder/op-rbuilder/src/tests/forks.rs new file mode 100644 index 00000000..d136c375 --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/forks.rs @@ -0,0 +1,150 @@ +use crate::tests::{BlockTransactionsExt, LocalInstance}; +use alloy_eips::{BlockNumberOrTag::Latest, Encodable2718, eip1559::MIN_PROTOCOL_BASE_FEE}; +use alloy_primitives::bytes; +use macros::{if_flashblocks, if_standard, rb_test}; +use std::time::Duration; + +#[rb_test] +async fn jovian_block_parameters_set(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let tx_one = driver.create_transaction().send().await?; + let tx_two = driver.create_transaction().send().await?; + let block = driver.build_new_block().await?; + + assert!(block.includes(tx_one.tx_hash())); + assert!(block.includes(tx_two.tx_hash())); + + assert!(block.header.excess_blob_gas.is_some()); + + assert!(block.header.blob_gas_used.is_some()); + + // Two user transactions + two builder transactions, all minimum size + if_flashblocks! { + assert_eq!(block.header.blob_gas_used.unwrap(), 160_000); + } + + // Two user transactions + one builder transactions, all minimum size + if_standard! { + assert_eq!(block.header.blob_gas_used.unwrap(), 120_000); + } + + // Version byte + assert_eq!(block.header.extra_data.slice(0..1), bytes!("0x01")); + + // Min Base Fee of zero by default + assert_eq!( + block.header.extra_data.slice(9..=16), + bytes!("0x0000000000000000"), + ); + + Ok(()) +} + +#[rb_test] +async fn jovian_no_tx_pool_sync(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let block = driver + .build_new_block_with_txs_timestamp(vec![], Some(true), None, None, Some(0)) + .await?; + + // Deposit transaction + user transaction + if_flashblocks! { + assert_eq!(block.transactions.len(), 1); + assert_eq!(block.header.blob_gas_used, Some(0)); + } + + // Standard includes a builder transaction when no-tx-pool is set + if_standard! { + assert_eq!(block.transactions.len(), 2); + assert_eq!(block.header.blob_gas_used, Some(40_000)); + } + + let tx = driver.create_transaction().build().await; + let block = driver + .build_new_block_with_txs_timestamp( + vec![tx.encoded_2718().into()], + Some(true), + None, + None, + Some(0), + ) + .await?; + + // Deposit transaction + user transaction + if_flashblocks! { + assert_eq!(block.transactions.len(), 2); + assert_eq!(block.header.blob_gas_used, Some(40_000)); + } + + // Standard includes a builder transaction when no-tx-pool is set + if_standard! { + assert_eq!(block.transactions.len(), 3); + assert_eq!(block.header.blob_gas_used, Some(80_000)); + } + + Ok(()) +} + +#[rb_test] +async fn jovian_minimum_base_fee(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let genesis = driver + .get_block(Latest) + .await? + .expect("must have genesis block"); + + assert_eq!(genesis.header.base_fee_per_gas, Some(1)); + + let min_base_fee = Some(MIN_PROTOCOL_BASE_FEE * 2); + + let block_timestamp = Duration::from_secs(genesis.header.timestamp) + Duration::from_secs(1); + let block_one = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, min_base_fee) + .await?; + + assert_eq!( + block_one.header.extra_data.slice(9..=16), + bytes!("0x000000000000000E"), + ); + + let overpriced_tx = driver + .create_transaction() + .with_max_fee_per_gas(MIN_PROTOCOL_BASE_FEE as u128 * 4) + .send() + .await?; + let underpriced_tx = driver + .create_transaction() + .with_max_fee_per_gas(MIN_PROTOCOL_BASE_FEE as u128) + .send() + .await?; + + let block_timestamp = Duration::from_secs(block_one.header.timestamp) + Duration::from_secs(1); + let block_two = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, min_base_fee) + .await?; + + assert_eq!( + block_two.header.extra_data.slice(9..=16), + bytes!("0x000000000000000E"), + ); + + assert!(block_two.includes(overpriced_tx.tx_hash())); + assert!(!block_two.includes(underpriced_tx.tx_hash())); + + Ok(()) +} + +#[rb_test] +async fn jovian_minimum_fee_must_be_set(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let genesis = driver + .get_block(Latest) + .await? + .expect("must have genesis block"); + let block_timestamp = Duration::from_secs(genesis.header.timestamp) + Duration::from_secs(1); + let response = driver + .build_new_block_with_txs_timestamp(vec![], None, Some(block_timestamp), None, None) + .await; + assert!(response.is_err()); + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl b/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl index 39a6f53e..824b494e 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/genesis.json.tmpl @@ -23,7 +23,9 @@ "ecotoneTime": 0, "fjordTime": 0, "graniteTime": 0, + "holoceneTime": 0, "isthmusTime": 0, + "jovianTime": 0, "pragueTime": 0, "terminalTotalDifficulty": 0, "terminalTotalDifficultyPassed": true, @@ -36,12 +38,23 @@ }, "nonce": "0x0", "timestamp": "", - "extraData": "0x", + "extraData": "0x0100000032000000020000000000000000", "gasLimit": "0x1c9c380", "difficulty": "0x0", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x4200000000000000000000000000000000000011", "alloc": { + "4200000000000000000000000000000000000015": { + "balance": "0x1", + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106dd565b610224565b6100a86100a33660046106f8565b610296565b6040516100b5919061077b565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106dd565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ee565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060c565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81815560405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a25050565b60006106367fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038381556040805173ffffffffffffffffffffffffffffffffffffffff80851682528616602082015292935090917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d857600080fd5b919050565b6000602082840312156106ef57600080fd5b610412826106b4565b60008060006040848603121561070d57600080fd5b610716846106b4565b9250602084013567ffffffffffffffff8082111561073357600080fd5b818601915086601f83011261074757600080fd5b81358181111561075657600080fd5b87602082850101111561076857600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a85785810183015185820160400152820161078c565b818111156107ba576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a", + "storage": { + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000003ba4007f5c922fbb33c454b41ea7a1f11e83df2c" + } + }, + "3Ba4007f5C922FBb33C454B41ea7a1f11E83df2C": { + "balance": "0x1", + "code": "0x608060405234801561001057600080fd5b50600436106101985760003560e01c806364ca23ef116100e3578063c59859181161008c578063e81b2c6d11610066578063e81b2c6d146103f0578063f8206140146103f9578063fe3d57101461040257600080fd5b8063c598591814610375578063d844471514610395578063e591b282146103ce57600080fd5b80638b239f73116100bd5780638b239f73146103435780639e8c49661461034c578063b80777ea1461035557600080fd5b806364ca23ef146102ff57806368d5dca6146103135780638381f58a1461032f57600080fd5b80634397dfef1161014557806354fd4d501161011f57806354fd4d501461027b578063550fcdc9146102bd5780635cf24969146102f657600080fd5b80634397dfef1461021a578063440a5e20146102425780634d5d9a2a1461024a57600080fd5b806316d3bc7f1161017657806316d3bc7f146101d657806321326849146102035780633db6be2b1461021257600080fd5b8063015d8eb91461019d578063098999be146101b257806309bd5a60146101ba575b600080fd5b6101b06101ab366004610623565b610433565b005b6101b0610572565b6101c360025481565b6040519081526020015b60405180910390f35b6008546101ea9067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101cd565b604051600081526020016101cd565b6101b0610585565b6040805173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815260126020820152016101cd565b6101b06105af565b6008546102669068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101cd565b60408051808201909152600581527f312e372e3000000000000000000000000000000000000000000000000000000060208201525b6040516101cd9190610695565b60408051808201909152600381527f455448000000000000000000000000000000000000000000000000000000000060208201526102b0565b6101c360015481565b6003546101ea9067ffffffffffffffff1681565b6003546102669068010000000000000000900463ffffffff1681565b6000546101ea9067ffffffffffffffff1681565b6101c360055481565b6101c360065481565b6000546101ea9068010000000000000000900467ffffffffffffffff1681565b600354610266906c01000000000000000000000000900463ffffffff1681565b60408051808201909152600581527f457468657200000000000000000000000000000000000000000000000000000060208201526102b0565b60405173deaddeaddeaddeaddeaddeaddeaddeaddead000181526020016101cd565b6101c360045481565b6101c360075481565b600854610420906c01000000000000000000000000900461ffff1681565b60405161ffff90911681526020016101cd565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104da576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61057a6105af565b60a43560a01c600855565b61058d6105af565b6dffff00000000000000000000000060b03560901c1660a43560a01c17600855565b73deaddeaddeaddeaddeaddeaddeaddeaddead00013381146105d957633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b803567ffffffffffffffff8116811461061e57600080fd5b919050565b600080600080600080600080610100898b03121561064057600080fd5b61064989610606565b975061065760208a01610606565b9650604089013595506060890135945061067360808a01610606565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b818110156106c2578581018301518582016040015282016106a6565b818111156106d4576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a" + }, "70997970C51812dc3A010C7d01b50e0d17dc79C8": { "balance": "0x21e19e0c9bab2400000" }, @@ -888,4 +901,3 @@ "excessBlobGas": "0x0", "blobGasUsed": "0x0" } - diff --git a/crates/builder/op-rbuilder/src/tests/framework/driver.rs b/crates/builder/op-rbuilder/src/tests/framework/driver.rs index 6e9f83f7..5246bd5e 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/driver.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/driver.rs @@ -1,7 +1,7 @@ use core::time::Duration; use alloy_eips::{BlockNumberOrTag, Encodable2718, eip7685::Requests}; -use alloy_primitives::{B256, Bytes, TxKind, U256, address, hex}; +use alloy_primitives::{B64, B256, Bytes, TxKind, U256, address, hex}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadAttributes, PayloadStatusEnum}; use alloy_rpc_types_eth::Block; @@ -14,7 +14,10 @@ use rollup_boost::OpExecutionPayloadEnvelope; use super::{EngineApi, Ipc, LocalInstance, TransactionBuilder}; use crate::{ args::OpRbuilderArgs, - tests::{ExternalNode, Protocol, framework::DEFAULT_GAS_LIMIT}, + tests::{ + DEFAULT_DENOMINATOR, DEFAULT_ELASTICITY, ExternalNode, Protocol, + framework::DEFAULT_GAS_LIMIT, + }, tx_signer::Signer, }; @@ -93,7 +96,7 @@ impl ChainDriver { // public test api impl ChainDriver { pub async fn build_new_block_with_no_tx_pool(&self) -> eyre::Result> { - self.build_new_block_with_txs_timestamp(vec![], Some(true), None, None) + self.build_new_block_with_txs_timestamp(vec![], Some(true), None, None, Some(0)) .await } @@ -107,7 +110,7 @@ impl ChainDriver { &self, timestamp_jitter: Option, ) -> eyre::Result> { - self.build_new_block_with_txs_timestamp(vec![], None, None, timestamp_jitter) + self.build_new_block_with_txs_timestamp(vec![], None, None, timestamp_jitter, Some(0)) .await } @@ -119,6 +122,7 @@ impl ChainDriver { block_timestamp: Option, // Amount of time to lag before sending FCU. This tests late FCU scenarios timestamp_jitter: Option, + min_base_fee: Option, ) -> eyre::Result> { let latest = self.latest().await?; @@ -138,7 +142,7 @@ impl ChainDriver { value: U256::default(), gas_limit: 210000, is_system_transaction: false, - input: FJORD_DATA.into(), + input: JOVIAN_DATA.into(), }; // Create a temporary signer for the deposit @@ -172,6 +176,10 @@ impl ChainDriver { tokio::time::sleep(sleep_time).await; } } + + let eip_1559_params: u64 = + ((DEFAULT_DENOMINATOR as u64) << 32) | (DEFAULT_ELASTICITY as u64); + let fcu_result = self .fcu(OpPayloadAttributes { payload_attributes: PayloadAttributes { @@ -183,7 +191,8 @@ impl ChainDriver { transactions: Some(vec![block_info_tx].into_iter().chain(txs).collect()), gas_limit: Some(self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT)), no_tx_pool, - ..Default::default() + min_base_fee, + eip_1559_params: Some(B64::from(eip_1559_params)), }) .await?; @@ -212,9 +221,11 @@ impl ChainDriver { let payload = OpExecutionPayloadEnvelope::V4(self.engine_api.get_payload(payload_id).await?); + let OpExecutionPayloadEnvelope::V4(payload) = payload else { return Err(eyre::eyre!("Expected V4 payload, got something else")); }; + let payload = payload.execution_payload; if self @@ -228,13 +239,14 @@ impl ChainDriver { } let new_block_hash = payload.payload_inner.payload_inner.payload_inner.block_hash; + self.engine_api .update_forkchoice(latest.header.hash, new_block_hash, None) .await?; let block = self .provider - .get_block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .get_block_by_number(BlockNumberOrTag::Latest) .full() .await? .ok_or_else(|| eyre::eyre!("Failed to get latest block after building new block"))?; @@ -264,7 +276,7 @@ impl ChainDriver { let latest_timestamp = Duration::from_secs(latest.header.timestamp); let block_timestamp = latest_timestamp + Self::MIN_BLOCK_TIME; - self.build_new_block_with_txs_timestamp(txs, None, Some(block_timestamp), None) + self.build_new_block_with_txs_timestamp(txs, None, Some(block_timestamp), None, Some(0)) .await } @@ -331,12 +343,20 @@ impl ChainDriver { } // L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0) -// // https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1 -const FJORD_DATA: &[u8] = &hex!( - "440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a +// It has the following modifications: +// 1. Function signature support Jovian: cast sig "setL1BlockValuesJovian()" => 0x3db6be2b +// 2. Zero operator fee scalar +// 3. Zero operator fee constant +// 4. DA footprint of 400 applied +// See: // https://specs.optimism.io/protocol/jovian/l1-attributes.html for Jovian specs. +const JOVIAN_DATA: &[u8] = &hex!( + "3db6be2b0000146b000f79c500000000000000040000000066d052e700000000013ad8a 3000000000000000000000000000000000000000000000000000000003ef12787000000 00000000000000000000000000000000000000000000000000000000012fdf87b89884a 61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a5900000000000000000000 - 00006887246668a3b87f54deb3b94ba47a6f63f32985" + 00006887246668a3b87f54deb3b94ba47a6f63f32985 + 00000000 + 0000000000000000 + 0190" ); diff --git a/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs index 26e24f0d..554c01cb 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/mod.rs @@ -30,6 +30,9 @@ pub const FLASHTESTATION_DEPLOY_KEY: &str = pub const DEFAULT_GAS_LIMIT: u64 = 10_000_000; +pub const DEFAULT_DENOMINATOR: u32 = 50; + +pub const DEFAULT_ELASTICITY: u32 = 2; pub const DEFAULT_JWT_TOKEN: &str = "688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a"; diff --git a/crates/builder/op-rbuilder/src/tests/framework/txs.rs b/crates/builder/op-rbuilder/src/tests/framework/txs.rs index ed6b6265..35872764 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/txs.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/txs.rs @@ -188,7 +188,10 @@ impl TransactionBuilder { }; self.tx.nonce = nonce; - self.tx.max_fee_per_gas = base_fee + self.tx.max_priority_fee_per_gas; + + if self.tx.max_fee_per_gas == 0 { + self.tx.max_fee_per_gas = base_fee + self.tx.max_priority_fee_per_gas; + } signer .sign_tx(OpTypedTransaction::Eip1559(self.tx)) diff --git a/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs b/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs index f0aaa353..619509db 100644 --- a/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs +++ b/crates/builder/op-rbuilder/src/tests/miner_gas_limit.rs @@ -1,7 +1,7 @@ use crate::tests::{BlockTransactionsExt, LocalInstance}; use alloy_provider::Provider; use macros::{if_flashblocks, if_standard, rb_test}; -use reth_primitives_traits::constants::MEGAGAS; + /// This test ensures that the miner gas limit is respected /// We will set the limit to 60,000 and see that the builder will not include any transactions #[rb_test] @@ -26,29 +26,28 @@ async fn miner_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { Ok(()) } -/// This test ensures that block will fill up to the limit -/// Each transaction is 53000 gas -/// There is a deposit transaction for 24890 gas, and a builder transaction for 21600 gas -/// We will set our limit to 1Mgas and see that the builder includes 17 transactions -/// Total of 19 transactions including deposit + builder -/// In Flashblocks mode, there are 2 builder transactions, one at the beginning and one at the end of the block -/// So the total number of transactions in the block will be 20 for Flashblocks mode +/// This test ensures that block will fill up to the limit, each transaction is 53,000 gas +/// We will set our limit to 1Mgas and ensure that throttling occurs +/// There is a deposit transaction for 182,706 gas, and builder transactions are 21,600 gas +/// +/// Standard = (785,000 - 182,706 - 21,600) / 53,000 = 10.95 = 10 transactions can fit +/// Flashblocks = (785,000 - 182,706 - 21,600 - 21,600) / 53,000 = 10.54 = 10 transactions can fit #[rb_test] async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let call = driver .provider() - .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (MEGAGAS,)) + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (785_000,)) .await?; assert!(call, "miner_setGasLimit should be executed successfully"); let mut tx_hashes = Vec::new(); - for _ in 0..17 { + for _ in 0..10 { let tx = driver .create_transaction() .with_gas_limit(53000) - .with_max_priority_fee_per_gas(50) + .with_max_priority_fee_per_gas(100) .send() .await?; tx_hashes.push(tx.tx_hash().clone()); @@ -63,7 +62,12 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { let block = driver.build_new_block().await?; for (i, tx_hash) in tx_hashes.iter().enumerate() { - assert!(block.includes(tx_hash), "tx{} should be in block", i); + assert!( + block.includes(tx_hash), + "tx i={} hash={} should be in block", + i, + tx_hash + ); } assert!( !block.includes(unfit_tx.tx_hash()), @@ -73,16 +77,16 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { if_standard! { assert_eq!( block.transactions.len(), - 19, - "deposit + builder + 17 valid txs should be in the block" + 12, + "deposit + builder + 15 valid txs should be in the block" ); } if_flashblocks! { assert_eq!( block.transactions.len(), - 20, - "deposit + 2 builder + 17 valid txs should be in the block" + 13, + "deposit + builder + 15 valid txs should be in the block" ); } diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs index afeff1cc..fd202a89 100644 --- a/crates/builder/op-rbuilder/src/tests/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -29,6 +29,8 @@ mod smoke; #[cfg(test)] mod txpool; +#[cfg(test)] +mod forks; // If the order of deployment from the signer changes the address will change #[cfg(test)] const FLASHBLOCKS_NUMBER_ADDRESS: alloy_primitives::Address = From f06b7f2d8d7ae4c125c7b45e4552bc143d7bfd2e Mon Sep 17 00:00:00 2001 From: shana Date: Wed, 19 Nov 2025 12:47:56 -0800 Subject: [PATCH 213/262] Add blob gas used to flashblocks delta (#325) * Add blob gas used to delta * update rollup-boost * update rollup-boost tag --- .../op-rbuilder/src/builders/flashblocks/payload.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 02753bf5..cb91116e 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -298,7 +298,10 @@ where // We log only every 100th block to reduce usage let span = if cfg!(feature = "telemetry") - && config.parent_header.number % self.config.sampling_ratio == 0 + && config + .parent_header + .number + .is_multiple_of(self.config.sampling_ratio) { span!(Level::INFO, "build_payload") } else { @@ -1127,6 +1130,8 @@ where block_number: ctx.parent().number + 1, }; + let (_, blob_gas_used) = ctx.blob_fields(info); + // Prepare the flashblocks message let fb_payload = FlashblocksPayloadV1 { payload_id: ctx.payload_id(), @@ -1155,6 +1160,7 @@ where transactions: new_transactions_encoded, withdrawals: ctx.withdrawals().cloned().unwrap_or_default().to_vec(), withdrawals_root: withdrawals_root.unwrap_or_default(), + blob_gas_used, }, metadata: serde_json::to_value(&metadata).unwrap_or_default(), }; From 0144fd4b6cec9122c860ddc2466b55058118957c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Mon, 10 Nov 2025 12:59:53 -0600 Subject: [PATCH 214/262] feat: resoure metering rpc/lookup --- crates/builder/op-rbuilder/Cargo.toml | 3 + crates/builder/op-rbuilder/src/args/op.rs | 9 + .../op-rbuilder/src/builders/context.rs | 5 + .../src/builders/flashblocks/ctx.rs | 5 + .../src/builders/flashblocks/payload.rs | 1 + .../builder/op-rbuilder/src/builders/mod.rs | 9 + .../op-rbuilder/src/builders/standard/mod.rs | 3 +- .../src/builders/standard/payload.rs | 1 + crates/builder/op-rbuilder/src/launcher.rs | 6 + crates/builder/op-rbuilder/src/lib.rs | 1 + crates/builder/op-rbuilder/src/metrics.rs | 6 + .../op-rbuilder/src/resource_metering.rs | 218 ++++++++++++++++++ 12 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 crates/builder/op-rbuilder/src/resource_metering.rs diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index c312f643..a0f5c8d2 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -14,6 +14,8 @@ workspace = true [dependencies] p2p = { path = "../p2p" } +tips-core.workspace = true + reth.workspace = true reth-optimism-node.workspace = true reth-optimism-cli.workspace = true @@ -112,6 +114,7 @@ url.workspace = true anyhow = "1" opentelemetry = { workspace = true, optional = true } dashmap.workspace = true +concurrent-queue.workspace = true hex = { workspace = true } futures = { workspace = true } futures-util = { workspace = true } diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 4e3d0308..4dc0663c 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -48,6 +48,15 @@ pub struct OpRbuilderArgs { /// Whether to enable revert protection by default #[arg(long = "builder.enable-revert-protection", default_value = "false")] pub enable_revert_protection: bool, + /// Whether to enable TIPS Resource Metering + #[arg(long = "builder.enable-resource-metering", default_value = "false")] + pub enable_resource_metering: bool, + /// Whether to enable TIPS Resource Metering + #[arg( + long = "builder.resource-metering-buffer-size", + default_value = "10000" + )] + pub resource_metering_buffer_size: usize, /// Path to builder playgorund to automatically start up the node connected to it #[arg( diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index e1eb4f39..040a59c8 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -43,6 +43,7 @@ use crate::{ gas_limiter::AddressGasLimiter, metrics::OpRBuilderMetrics, primitives::reth::{ExecutionInfo, TxnExecutionResult}, + resource_metering::ResourceMetering, traits::PayloadTxsBounds, tx::MaybeRevertingTransaction, tx_signer::Signer, @@ -77,6 +78,8 @@ pub struct OpPayloadBuilderCtx { pub max_gas_per_txn: Option, /// Rate limiting based on gas. This is an optional feature. pub address_gas_limiter: AddressGasLimiter, + /// Per transaction resource metering information + pub resource_metering: ResourceMetering, } impl OpPayloadBuilderCtx { @@ -441,6 +444,8 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; + let _resource_usage = self.resource_metering.get(&tx_hash); + // TODO: ideally we should get this from the txpool stream if let Some(conditional) = conditional && !conditional.matches_block_attributes(&block_attr) diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs index 16d1a71c..28cbae76 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -2,6 +2,7 @@ use crate::{ builders::{BuilderConfig, OpPayloadBuilderCtx, flashblocks::FlashblocksConfig}, gas_limiter::{AddressGasLimiter, args::GasLimiterArgs}, metrics::OpRBuilderMetrics, + resource_metering::ResourceMetering, traits::ClientBounds, }; use op_revm::OpSpecId; @@ -29,6 +30,8 @@ pub(super) struct OpPayloadSyncerCtx { max_gas_per_txn: Option, /// The metrics for the builder metrics: Arc, + /// Resource metering tracking + resource_metering: ResourceMetering, } impl OpPayloadSyncerCtx { @@ -48,6 +51,7 @@ impl OpPayloadSyncerCtx { chain_spec, max_gas_per_txn: builder_config.max_gas_per_txn, metrics, + resource_metering: builder_config.resource_metering, }) } @@ -80,6 +84,7 @@ impl OpPayloadSyncerCtx { extra_ctx: (), max_gas_per_txn: self.max_gas_per_txn, address_gas_limiter: AddressGasLimiter::new(GasLimiterArgs::default()), + resource_metering: self.resource_metering.clone(), } } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index cb91116e..74fcf923 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -273,6 +273,7 @@ where extra_ctx, max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), + resource_metering: self.config.resource_metering.clone(), }) } diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 4edb2088..48ce625b 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -21,6 +21,7 @@ mod flashblocks; mod generator; mod standard; +use crate::resource_metering::ResourceMetering; pub use builder_tx::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, InvalidContractDataError, SimulationSuccessResult, get_balance, get_nonce, @@ -126,6 +127,9 @@ pub struct BuilderConfig { /// Address gas limiter stuff pub gas_limiter_config: GasLimiterArgs, + + /// Resource metering context + pub resource_metering: ResourceMetering, } impl core::fmt::Debug for BuilderConfig { @@ -166,6 +170,7 @@ impl Default for BuilderConfig { sampling_ratio: 100, max_gas_per_txn: None, gas_limiter_config: GasLimiterArgs::default(), + resource_metering: ResourceMetering::default(), } } } @@ -188,6 +193,10 @@ where sampling_ratio: args.telemetry.sampling_ratio, max_gas_per_txn: args.max_gas_per_txn, gas_limiter_config: args.gas_limiter.clone(), + resource_metering: ResourceMetering::new( + args.enable_resource_metering, + args.resource_metering_buffer_size, + ), specific: S::try_from(args)?, }) } diff --git a/crates/builder/op-rbuilder/src/builders/standard/mod.rs b/crates/builder/op-rbuilder/src/builders/standard/mod.rs index e26bbc6c..15b006a5 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/mod.rs @@ -1,10 +1,9 @@ +use super::BuilderConfig; use crate::{ builders::standard::service::StandardServiceBuilder, traits::{NodeBounds, PoolBounds}, }; -use super::BuilderConfig; - mod builder_tx; mod payload; mod service; diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 4907c5c2..858357fc 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -250,6 +250,7 @@ where extra_ctx: Default::default(), max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), + resource_metering: self.config.resource_metering.clone(), }; let builder = OpBuilder::new(best); diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index e487e016..5d7c6f84 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -7,6 +7,7 @@ use crate::{ metrics::{VERSION, record_flag_gauge_metrics}, monitor_tx_pool::monitor_tx_pool, primitives::reth::engine_api_builder::OpEngineApiBuilder, + resource_metering::{BaseApiExtServer, ResourceMeteringExt}, revert_protection::{EthApiExtServer, RevertProtectionExt}, tx::FBPooledTransaction, }; @@ -109,6 +110,7 @@ where let op_node = OpNode::new(rollup_args.clone()); let reverted_cache = Cache::builder().max_capacity(100).build(); let reverted_cache_copy = reverted_cache.clone(); + let resource_metering = builder_config.resource_metering.clone(); let mut addons: OpAddOns< _, @@ -164,6 +166,10 @@ where .add_or_replace_configured(revert_protection_ext.into_rpc())?; } + let resource_metering_ext = ResourceMeteringExt::new(resource_metering); + ctx.modules + .add_or_replace_configured(resource_metering_ext.into_rpc())?; + Ok(()) }) .on_node_started(move |ctx| { diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index 7817ba2d..f61c39b0 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -13,5 +13,6 @@ pub mod tx_signer; #[cfg(test)] pub mod mock_tx; +mod resource_metering; #[cfg(any(test, feature = "testing"))] pub mod tests; diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index c381fed8..75ebb874 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -161,6 +161,12 @@ pub struct OpRBuilderMetrics { pub bundles_reverted: Histogram, /// Histogram of eth_sendBundle request duration pub bundle_receive_duration: Histogram, + /// Count of the number of times transactions had metering information + pub metering_known_transaction: Counter, + /// Count of the number of times transactions did not have any metering information + pub metering_unknown_transaction: Counter, + /// Count of the number of times we were unable to resolve metering information due to locking + pub metering_locked_transaction: Counter, } impl OpRBuilderMetrics { diff --git a/crates/builder/op-rbuilder/src/resource_metering.rs b/crates/builder/op-rbuilder/src/resource_metering.rs new file mode 100644 index 00000000..21d68f5e --- /dev/null +++ b/crates/builder/op-rbuilder/src/resource_metering.rs @@ -0,0 +1,218 @@ +use crate::metrics::OpRBuilderMetrics; +use alloy_primitives::TxHash; +use concurrent_queue::{ConcurrentQueue, PopError}; +use dashmap::try_result::TryResult; +use jsonrpsee::{ + core::{RpcResult, async_trait}, + proc_macros::rpc, +}; +use std::{ + fmt::Debug, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, +}; +use tips_core::MeterBundleResponse; + +struct Data { + enabled: AtomicBool, + by_tx_hash: dashmap::DashMap, + lru: ConcurrentQueue, +} + +#[derive(Clone)] +pub struct ResourceMetering { + data: Arc, + metrics: OpRBuilderMetrics, +} + +impl Debug for ResourceMetering { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ResourceMetering") + .field("enabled", &self.data.enabled) + .field("by_tx_hash", &self.data.by_tx_hash.len()) + .finish() + } +} + +impl ResourceMetering { + pub(crate) fn insert(&self, tx: TxHash, metering_info: MeterBundleResponse) { + let to_remove = if self.data.lru.is_full() { + match self.data.lru.pop() { + Ok(tx_hash) => Some(tx_hash), + Err(PopError::Empty) => None, + Err(PopError::Closed) => None, + } + } else { + None + }; + + if let Some(tx_hash) = to_remove { + self.data.by_tx_hash.remove(&tx_hash); + } + + self.data.by_tx_hash.insert(tx, metering_info); + } + + pub(crate) fn clear(&self) { + self.data.by_tx_hash.clear(); + } + + pub(crate) fn set_enabled(&self, enabled: bool) { + self.data.enabled.store(enabled, Ordering::Relaxed); + } + + pub(crate) fn get(&self, tx: &TxHash) -> Option { + if !self.data.enabled.load(Ordering::Relaxed) { + return None; + } + + match self.data.by_tx_hash.try_get(tx) { + TryResult::Present(result) => { + self.metrics.metering_known_transaction.increment(1); + Some(result.clone()) + } + TryResult::Absent => { + self.metrics.metering_unknown_transaction.increment(1); + None + } + TryResult::Locked => { + self.metrics.metering_locked_transaction.increment(1); + None + } + } + } +} + +impl Default for ResourceMetering { + fn default() -> Self { + Self::new(false, 10_000) + } +} + +impl ResourceMetering { + pub fn new(enabled: bool, buffer_size: usize) -> Self { + Self { + data: Arc::new(Data { + by_tx_hash: dashmap::DashMap::new(), + enabled: AtomicBool::new(enabled), + lru: ConcurrentQueue::bounded(buffer_size), + }), + metrics: OpRBuilderMetrics::default(), + } + } +} + +// Namespace overrides for ingesting resource metering +#[cfg_attr(not(test), rpc(server, namespace = "base"))] +#[cfg_attr(test, rpc(server, client, namespace = "base"))] +pub trait BaseApiExt { + #[method(name = "setMeteringInformation")] + async fn set_metering_information( + &self, + tx_hash: TxHash, + meter: MeterBundleResponse, + ) -> RpcResult<()>; + + #[method(name = "setMeteringEnabled")] + async fn set_metering_enabled(&self, enabled: bool) -> RpcResult<()>; + + #[method(name = "clearMeteringInformation")] + async fn clear_metering_information(&self) -> RpcResult<()>; +} + +pub(crate) struct ResourceMeteringExt { + metering_info: ResourceMetering, +} + +impl ResourceMeteringExt { + pub(crate) fn new(metering_info: ResourceMetering) -> Self { + Self { metering_info } + } +} + +#[async_trait] +impl BaseApiExtServer for ResourceMeteringExt { + async fn set_metering_information( + &self, + tx_hash: TxHash, + metering: MeterBundleResponse, + ) -> RpcResult<()> { + self.metering_info.insert(tx_hash, metering); + Ok(()) + } + + async fn set_metering_enabled(&self, enabled: bool) -> RpcResult<()> { + self.metering_info.set_enabled(enabled); + Ok(()) + } + + async fn clear_metering_information(&self) -> RpcResult<()> { + self.metering_info.clear(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{B256, TxHash, U256}; + use tips_core::MeterBundleResponse; + + fn create_test_metering(gas_used: u64) -> MeterBundleResponse { + MeterBundleResponse { + bundle_hash: B256::random(), + bundle_gas_price: U256::from(123), + coinbase_diff: U256::from(123), + eth_sent_to_coinbase: U256::from(123), + gas_fees: U256::from(123), + results: vec![], + state_block_number: 4, + state_flashblock_index: None, + total_gas_used: gas_used, + total_execution_time_us: 533, + } + } + + #[test] + fn test_basic_insert_get_and_enable_disable() { + let metering = ResourceMetering::default(); + let tx_hash = TxHash::random(); + let meter_data = create_test_metering(21000); + + metering.insert(tx_hash, meter_data); + assert!(metering.get(&tx_hash).is_none()); + + metering.set_enabled(true); + assert_eq!(metering.get(&tx_hash).unwrap().total_gas_used, 21000); + + metering.insert(tx_hash, create_test_metering(50000)); + assert_eq!(metering.get(&tx_hash).unwrap().total_gas_used, 50000); + + metering.set_enabled(false); + assert!(metering.get(&tx_hash).is_none()); + + metering.set_enabled(true); + assert!(metering.get(&TxHash::random()).is_none()); + } + + #[test] + fn test_clear() { + let metering = ResourceMetering::new(true, 100); + + let tx1 = TxHash::random(); + let tx2 = TxHash::random(); + + metering.insert(tx1, create_test_metering(1000)); + metering.insert(tx2, create_test_metering(2000)); + + assert!(metering.get(&tx1).is_some()); + assert!(metering.get(&tx2).is_some()); + + metering.clear(); + + assert!(metering.get(&tx1).is_none()); + assert!(metering.get(&tx2).is_none()); + } +} From 3fe6981807ee22f2dedd43b6381758486cb3e3a2 Mon Sep 17 00:00:00 2001 From: Alex Melville Date: Mon, 24 Nov 2025 11:04:47 -0500 Subject: [PATCH 215/262] update flashtestation logic to use new workload ID computation (#331) To account for a bug in the xfam and tdAttributes bitmasking logic, we changed the way the workload ID is calculated in the BlockBuilderPolicy. That PR is here: https://github.com/flashbots/flashtestations/pull/53 This means the workload ID calculation needs to change in any upstream services, which includes op-rbuilder. I've made the same change that I made in the solidity contract here in the attestation.rs logic where op-rbuilder calculates the workload ID. The tests have been updated to use the new correct workload ID, which I obtained by running the BlockBuilderPolicy:workloadIdForTDRegistration function on the same TEE registration that is used in the tests. --- .../src/flashtestations/attestation.rs | 28 +- .../contracts/BlockBuilderPolicy.json | 10287 +++++++++++++++- .../op-rbuilder/src/tests/framework/mod.rs | 2 +- 3 files changed, 10258 insertions(+), 59 deletions(-) diff --git a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs index 6af5cc24..df712f98 100644 --- a/crates/builder/op-rbuilder/src/flashtestations/attestation.rs +++ b/crates/builder/op-rbuilder/src/flashtestations/attestation.rs @@ -9,13 +9,6 @@ const DEBUG_QUOTE_SERVICE_URL: &str = "http://ns31695324.ip-141-94-163.eu:10080/ const HEADER_LENGTH: usize = 48; const TD_REPORT10_LENGTH: usize = 584; -// TDX workload constants -const TD_XFAM_FPU: u64 = 0x0000000000000001; -const TD_XFAM_SSE: u64 = 0x0000000000000002; -const TD_TDATTRS_VE_DISABLED: u64 = 0x0000000010000000; -const TD_TDATTRS_PKS: u64 = 0x0000000040000000; -const TD_TDATTRS_KL: u64 = 0x0000000080000000; - // TD10ReportBody field offsets // These offsets correspond to the Solidity parseRawReportBody implementation const OFFSET_TD_ATTRIBUTES: usize = 120; @@ -186,23 +179,6 @@ pub fn parse_report_body(raw_quote: &[u8]) -> eyre::Result { /// This corresponds to QuoteParser.parseV4VerifierOutput in Solidity implementation /// The workload ID uniquely identifies a TEE workload based on its measurement registers pub fn compute_workload_id_from_parsed(parsed: &ParsedQuote) -> [u8; 32] { - // Apply transformations as per the Solidity implementation - // expectedXfamBits = TD_XFAM_FPU | TD_XFAM_SSE - let expected_xfam_bits = TD_XFAM_FPU | TD_XFAM_SSE; - - // ignoredTdAttributesBitmask = TD_TDATTRS_VE_DISABLED | TD_TDATTRS_PKS | TD_TDATTRS_KL - let ignored_td_attributes_bitmask = TD_TDATTRS_VE_DISABLED | TD_TDATTRS_PKS | TD_TDATTRS_KL; - - // Transform xFAM: xFAM ^ expectedXfamBits - let transformed_xfam = parsed.xfam ^ expected_xfam_bits; - - // Transform tdAttributes: tdAttributes & ~ignoredTdAttributesBitmask - let transformed_td_attributes = parsed.td_attributes & !ignored_td_attributes_bitmask; - - // Convert transformed values to bytes (big-endian, to match Solidity bytes8) - let xfam_bytes = transformed_xfam.to_be_bytes(); - let td_attributes_bytes = transformed_td_attributes.to_be_bytes(); - // Concatenate all fields let mut concatenated = Vec::new(); concatenated.extend_from_slice(&parsed.mr_td); @@ -211,8 +187,8 @@ pub fn compute_workload_id_from_parsed(parsed: &ParsedQuote) -> [u8; 32] { concatenated.extend_from_slice(&parsed.rt_mr2); concatenated.extend_from_slice(&parsed.rt_mr3); concatenated.extend_from_slice(&parsed.mr_config_id); - concatenated.extend_from_slice(&xfam_bytes); - concatenated.extend_from_slice(&td_attributes_bytes); + concatenated.extend_from_slice(&parsed.xfam.to_be_bytes()); + concatenated.extend_from_slice(&parsed.td_attributes.to_be_bytes()); // Compute keccak256 hash let mut hasher = Keccak256::new(); diff --git a/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json index 6d95e833..b661a1b2 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json +++ b/crates/builder/op-rbuilder/src/tests/framework/artifacts/contracts/BlockBuilderPolicy.json @@ -5,7 +5,11 @@ "name": "UPGRADE_INTERFACE_VERSION", "inputs": [], "outputs": [ - { "name": "", "type": "string", "internalType": "string" } + { + "name": "", + "type": "string", + "internalType": "string" + } ], "stateMutability": "view" }, @@ -14,7 +18,11 @@ "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", "inputs": [], "outputs": [ - { "name": "", "type": "bytes32", "internalType": "bytes32" } + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], "stateMutability": "view" }, @@ -45,7 +53,11 @@ "type": "function", "name": "computeStructHash", "inputs": [ - { "name": "version", "type": "uint8", "internalType": "uint8" }, + { + "name": "version", + "type": "uint8", + "internalType": "uint8" + }, { "name": "blockContentHash", "type": "bytes32", @@ -58,7 +70,11 @@ } ], "outputs": [ - { "name": "", "type": "bytes32", "internalType": "bytes32" } + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], "stateMutability": "pure" }, @@ -67,7 +83,11 @@ "name": "domainSeparator", "inputs": [], "outputs": [ - { "name": "", "type": "bytes32", "internalType": "bytes32" } + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], "stateMutability": "view" }, @@ -81,7 +101,11 @@ "type": "bytes1", "internalType": "bytes1" }, - { "name": "name", "type": "string", "internalType": "string" }, + { + "name": "name", + "type": "string", + "internalType": "string" + }, { "name": "version", "type": "string", @@ -121,7 +145,11 @@ } ], "outputs": [ - { "name": "", "type": "bytes32", "internalType": "bytes32" } + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], "stateMutability": "view" }, @@ -185,8 +213,16 @@ } ], "outputs": [ - { "name": "allowed", "type": "bool", "internalType": "bool" }, - { "name": "", "type": "bytes32", "internalType": "WorkloadId" } + { + "name": "allowed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "", + "type": "bytes32", + "internalType": "WorkloadId" + } ], "stateMutability": "view" }, @@ -214,7 +250,11 @@ "name": "owner", "inputs": [], "outputs": [ - { "name": "", "type": "address", "internalType": "address" } + { + "name": "", + "type": "address", + "internalType": "address" + } ], "stateMutability": "view" }, @@ -222,7 +262,11 @@ "type": "function", "name": "permitVerifyBlockBuilderProof", "inputs": [ - { "name": "version", "type": "uint8", "internalType": "uint8" }, + { + "name": "version", + "type": "uint8", + "internalType": "uint8" + }, { "name": "blockContentHash", "type": "bytes32", @@ -247,7 +291,11 @@ "name": "proxiableUUID", "inputs": [], "outputs": [ - { "name": "", "type": "bytes32", "internalType": "bytes32" } + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], "stateMutability": "view" }, @@ -256,7 +304,11 @@ "name": "registry", "inputs": [], "outputs": [ - { "name": "", "type": "address", "internalType": "address" } + { + "name": "", + "type": "address", + "internalType": "address" + } ], "stateMutability": "view" }, @@ -302,7 +354,11 @@ "type": "address", "internalType": "address" }, - { "name": "data", "type": "bytes", "internalType": "bytes" } + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } ], "outputs": [], "stateMutability": "payable" @@ -311,7 +367,11 @@ "type": "function", "name": "verifyBlockBuilderProof", "inputs": [ - { "name": "version", "type": "uint8", "internalType": "uint8" }, + { + "name": "version", + "type": "uint8", + "internalType": "uint8" + }, { "name": "blockContentHash", "type": "bytes32", @@ -436,7 +496,11 @@ } ], "outputs": [ - { "name": "", "type": "bytes32", "internalType": "WorkloadId" } + { + "name": "", + "type": "bytes32", + "internalType": "WorkloadId" + } ], "stateMutability": "pure" }, @@ -578,7 +642,11 @@ } ] }, - { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, + { + "type": "error", + "name": "ECDSAInvalidSignature", + "inputs": [] + }, { "type": "error", "name": "ECDSAInvalidSignatureLength", @@ -594,7 +662,11 @@ "type": "error", "name": "ECDSAInvalidSignatureS", "inputs": [ - { "name": "s", "type": "bytes32", "internalType": "bytes32" } + { + "name": "s", + "type": "bytes32", + "internalType": "bytes32" + } ] }, { @@ -608,11 +680,31 @@ } ] }, - { "type": "error", "name": "ERC1967NonPayable", "inputs": [] }, - { "type": "error", "name": "EmptyCommitHash", "inputs": [] }, - { "type": "error", "name": "EmptySourceLocators", "inputs": [] }, - { "type": "error", "name": "FailedCall", "inputs": [] }, - { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { + "type": "error", + "name": "ERC1967NonPayable", + "inputs": [] + }, + { + "type": "error", + "name": "EmptyCommitHash", + "inputs": [] + }, + { + "type": "error", + "name": "EmptySourceLocators", + "inputs": [] + }, + { + "type": "error", + "name": "FailedCall", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, { "type": "error", "name": "InvalidNonce", @@ -629,8 +721,16 @@ } ] }, - { "type": "error", "name": "InvalidRegistry", "inputs": [] }, - { "type": "error", "name": "NotInitializing", "inputs": [] }, + { + "type": "error", + "name": "InvalidRegistry", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, { "type": "error", "name": "OwnableInvalidOwner", @@ -662,7 +762,11 @@ "type": "error", "name": "UUPSUnsupportedProxiableUUID", "inputs": [ - { "name": "slot", "type": "bytes32", "internalType": "bytes32" } + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } ] }, { @@ -676,12 +780,10131 @@ } ] }, - { "type": "error", "name": "WorkloadAlreadyInPolicy", "inputs": [] }, - { "type": "error", "name": "WorkloadNotInPolicy", "inputs": [] } + { + "type": "error", + "name": "WorkloadAlreadyInPolicy", + "inputs": [] + }, + { + "type": "error", + "name": "WorkloadNotInPolicy", + "inputs": [] + } ], "bytecode": { - "object": "0x60a0806040523460295730608052613263908161002e8239608051818181610a3801526110790152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80632dd8abfe14611e4c578063485cc955146116b05780634d37fc7a146113365780634f1ef286146110105780634f3a415a14610ab057806352d1902d146109f35780635c40e542146109095780636931164e146108cd578063715018a6146107f3578063730169231461079b5780637b1039991461074a5780637dec71a9146106f95780637ecebe001461069657806384b0196e146104f15780638da5cb5b14610481578063abd45d21146102fc578063ad3cb1cc1461027b578063b33d59da14610237578063d2753561146101e8578063f2fde38b1461019f5763f698da2514610100575f80fd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602061013861310e565b610140613178565b60405190838201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261019060c082612063565b519020604051908152f35b5f80fd5b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e66101d9611feb565b6101e1612d2c565b612b6a565b005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576040610229610224611feb565b6127d1565b825191151582526020820152f35b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e6610271611fad565b6024359033612c57565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576102f86040516102ba604082612063565b600581527f352e302e30000000000000000000000000000000000000000000000000000000602082015260405191829160208352602083019061215f565b0390f35b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576060602060405161033b81612047565b82815201526004355f525f60205260405f2060016040519161035c83612047565b61036581612695565b835201805461037381612349565b916103816040519384612063565b81835260208301905f5260205f205f915b838310610464576103c1868660208201908152604051928392602084525160406020850152606084019061215f565b9051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0838203016040840152815180825260208201916020808360051b8301019401925f915b8383106104155786860387f35b919395509193602080610452837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08660019603018752895161215f565b97019301930190928695949293610408565b60016020819261047385612695565b815201920192019190610392565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10054158061066d575b1561060f576105b36105586124b1565b6105606125c2565b60206105c1604051926105738385612063565b5f84525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e087019061215f565b90858203604087015261215f565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b8281106105f857505050500390f35b8351855286955093810193928101926001016105e9565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415610548565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff6106e2611feb565b165f526002602052602060405f2054604051908152f35b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742610735611fad565b6044359060243590612463565b604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206040517f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc8152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57610829612d2c565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206107426004356123c6565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57600435610943612d2c565b805f525f60205261095760405f20546122f8565b156109cb57805f525f602052600160405f2061097281612377565b018054905f8155816109a6575b827f56c387a9be1bf0e0e4f852c577a225db98e8253ad401d1b4ea73926f27d6af095f80a2005b5f5260205f20908101905b8181101561097f57806109c5600192612377565b016109b1565b7f22faf042000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163003610a885760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043560243567ffffffffffffffff811161019b57610b02903690600401611fbd565b919060443567ffffffffffffffff811161019b573660238201121561019b5780600401359167ffffffffffffffff831161019b5760248360051b8301019036821161019b57610b4f612d2c565b8515610fe8578315610fc057845f525f602052610b6f60405f20546122f8565b610f9857610b8b9060405196610b8488612047565b36916120de565b8552610b9683612349565b92610ba46040519485612063565b83526024820191602084015b828410610f57575050505060208301908152815f525f60205260405f20925192835167ffffffffffffffff8111610e2057610beb82546122f8565b601f8111610f27575b506020601f8211600114610e85579080610c469260019596975f92610e7a575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b81555b019051805190680100000000000000008211610e20578254828455808310610e4d575b50602001915f5260205f20915f905b828210610ca957847fcbb92e241e191fed6d0b0da0a918c7dcf595e77d868e2e3bf9e6b0b91589c7ad5f80a2005b805180519067ffffffffffffffff8211610e2057610cc786546122f8565b601f8111610de5575b50602090601f8311600114610d3f5792610d25836001959460209487965f92610d345750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b87555b01940191019092610c7b565b015190508b80610c14565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0831691875f52815f20925f5b818110610dcd5750936020936001969387969383889510610d96575b505050811b018755610d28565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558a8080610d89565b92936020600181928786015181550195019301610d6d565b610e1090875f5260205f20601f850160051c81019160208610610e16575b601f0160051c0190612361565b87610cd0565b9091508190610e03565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b835f528260205f2091820191015b818110610e685750610c6c565b80610e74600192612377565b01610e5b565b015190508780610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821695835f52815f20965f5b818110610f0f5750916001959697918487959410610ed8575b505050811b018155610c49565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080610ecb565b83830151895560019098019760209384019301610eb2565b610f5190835f5260205f20601f840160051c81019160208510610e1657601f0160051c0190612361565b85610bf4565b833567ffffffffffffffff811161019b5782013660438201121561019b57602091610f8d839236906044602482013591016120de565b815201930192610bb0565b7f72477348000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f6890d9d4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8423f262000000000000000000000000000000000000000000000000000000005f5260045ffd5b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611042611feb565b60243567ffffffffffffffff811161019b57611062903690600401612114565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168030149081156112f4575b50610a88576110b1612d2c565b73ffffffffffffffffffffffffffffffffffffffff8216916040517f52d1902d000000000000000000000000000000000000000000000000000000008152602081600481875afa5f91816112c0575b5061113157837f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8592036112955750813b1561126a57807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2815115611239575f808360206101e695519101845af43d15611231573d91611215836120a4565b926112236040519485612063565b83523d5f602085013e6131bd565b6060916131bd565b50503461124257005b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7faa1d49a4000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091506020813d6020116112ec575b816112dc60209383612063565b8101031261019b57519085611100565b3d91506112cf565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54161415836110a4565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043567ffffffffffffffff811161019b5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906113b08261200e565b8060040135801515810361019b578252602481013567ffffffffffffffff811161019b576113e49060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b5781016101e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906114398261202a565b60048101357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602481013567ffffffffffffffff811161019b5761148d9060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b576114b69060043691840101612114565b60408301526114c760648201612132565b60608301526114d860848201612132565b60808301526114e960a48201612132565b60a083015260c481013567ffffffffffffffff811161019b576115129060043691840101612114565b60c083015260e481013567ffffffffffffffff811161019b5761153b9060043691840101612114565b60e083015261010481013567ffffffffffffffff811161019b576115659060043691840101612114565b61010083015261012481013567ffffffffffffffff811161019b576115909060043691840101612114565b61012083015261014481013567ffffffffffffffff811161019b576115bb9060043691840101612114565b61014083015261016481013567ffffffffffffffff811161019b576115e69060043691840101612114565b61016083015261018481013567ffffffffffffffff811161019b576116119060043691840101612114565b6101808301526101a481013567ffffffffffffffff811161019b5761163c9060043691840101612114565b6101a08301526101c48101359067ffffffffffffffff821161019b5760046116679236920101612114565b6101c0820152604083015260648101359167ffffffffffffffff831161019b5760846107429261169f60209560043691840101612114565b6060840152013560808201526121a2565b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576116e7611feb565b60243573ffffffffffffffffffffffffffffffffffffffff811680910361019b577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c16159267ffffffffffffffff821680159081611e44575b6001149081611e3a575b159081611e31575b50611e0957818460017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006117c19516177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055611db4575b506117b9613028565b6101e1613028565b6117c9613028565b6040918251926117d98185612063565b601284527f426c6f636b4275696c646572506f6c696379000000000000000000000000000060208501528051936118108286612063565b600185527f31000000000000000000000000000000000000000000000000000000000000006020860152611842613028565b61184a613028565b80519067ffffffffffffffff8211610e20576118867fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546122f8565b601f8111611d47575b50602090601f8311600114611c67576118dc92915f9183610e7a5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102555b835167ffffffffffffffff8111610e205761193a7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546122f8565b601f8111611bfa575b50602094601f8211600114611b1c576119939293949582915f92611b115750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103555b5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100555f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101558215611ae957827fffffffffffffffffffffffff0000000000000000000000000000000000000000600154161760015551917f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b5f80a2611a5857005b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2917fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005560018152a1005b7f11a1e697000000000000000000000000000000000000000000000000000000005f5260045ffd5b015190508680610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52805f20915f5b888110611be257508360019596979810611bab575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103556119b6565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080611b7e565b91926020600181928685015181550194019201611b69565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52611c61907f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c81019160208510610e1657601f0160051c0190612361565b85611943565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52815f20925f5b818110611d2f5750908460019594939210611cf8575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102556118ff565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080611ccb565b92936020600181928786015181550195019301611cb5565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52611dae907f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f850160051c81019160208610610e1657601f0160051c0190612361565b8661188f565b7fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055846117b0565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b90501585611759565b303b159150611751565b859150611747565b3461019b5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611e83611fad565b602435604435916064359267ffffffffffffffff841161019b57611ed7611ed1611eb4611ee0963690600401611fbd565b9190611ec9611ec4868989612463565b6123c6565b9236916120de565b90612d98565b90959195612dd2565b73ffffffffffffffffffffffffffffffffffffffff841690815f52600260205260405f2054808203611f7f5750505f52600260205260405f20928354937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8514611f525760016101e695019055612c57565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7f06427aeb000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b6004359060ff8216820361019b57565b9181601f8401121561019b5782359167ffffffffffffffff831161019b576020838186019501011161019b57565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361019b57565b60a0810190811067ffffffffffffffff821117610e2057604052565b6101e0810190811067ffffffffffffffff821117610e2057604052565b6040810190811067ffffffffffffffff821117610e2057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610e2057604052565b67ffffffffffffffff8111610e2057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926120ea826120a4565b916120f86040519384612063565b82948184528183011161019b578281602093845f960137010152565b9080601f8301121561019b5781602061212f933591016120de565b90565b35907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b6040015160c081015190610140810151610160820151916101808101516101a082015160e08301519060a08401517fffffffffffffffff0000000000000000000000000000000000000000000000001678030000000000000000000000000000000000000000000000001893608001517fffffffff2fffffff00000000000000000000000000000000000000000000000016926040519687966020880199805160208192018c5e880160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e01917fffffffffffffffff0000000000000000000000000000000000000000000000001682526008820152037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0810182526010016122f29082612063565b51902090565b90600182811c9216801561233f575b602083101461231257565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f1691612307565b67ffffffffffffffff8111610e205760051b60200190565b81811061236c575050565b5f8155600101612361565b61238181546122f8565b908161238b575050565b81601f5f931160011461239d5750555b565b818352602083206123b991601f0160051c810190600101612361565b8082528160208120915555565b6042906123d161310e565b6123d9613178565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261242a60c082612063565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b916040519160ff60208401947f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc865216604084015260608301526080820152608081526122f260a082612063565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10254916124e3836122f8565b80835292600181169081156125855750600114612507575b61239b92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b81831061256957505090602061239b928201016124fb565b6020919350806001915483858901015201910190918492612551565b6020925061239b9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016124fb565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10354916125f4836122f8565b808352926001811690811561258557506001146126175761239b92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b81831061267957505090602061239b928201016124fb565b6020919350806001915483858901015201910190918492612661565b9060405191825f8254926126a8846122f8565b808452936001811690811561271157506001146126cd575b5061239b92500383612063565b90505f9291925260205f20905f915b8183106126f557505090602061239b928201015f6126c0565b60209193508060019154838589010152019101909184926126dc565b6020935061239b9592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201015f6126c0565b5190811515820361019b57565b81601f8201121561019b57805190612775826120a4565b926127836040519485612063565b8284526020838301011161019b57815f9260208093018386015e8301015290565b51907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b5f73ffffffffffffffffffffffffffffffffffffffff602481600154169360405194859384927f727310620000000000000000000000000000000000000000000000000000000084521660048301525afa908115612b5f575f9161286c575b5080511561286557612841906121a2565b805f525f60205261285560405f20546122f8565b61286057505f905f90565b600191565b505f905f90565b90503d805f833e61287d8183612063565b81019060408183031261019b5761289381612751565b5060208101519067ffffffffffffffff821161019b57019060a08282031261019b57604051916128c28361200e565b6128cb81612751565b8352602081015167ffffffffffffffff811161019b57826128ed91830161275e565b6020840152604081015167ffffffffffffffff811161019b5781016101e08184031261019b57604051906129208261202a565b80517fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602081015167ffffffffffffffff811161019b578461296d91830161275e565b6020830152604081015167ffffffffffffffff811161019b578461299291830161275e565b60408301526129a3606082016127a4565b60608301526129b4608082016127a4565b60808301526129c560a082016127a4565b60a083015260c081015167ffffffffffffffff811161019b57846129ea91830161275e565b60c083015260e081015167ffffffffffffffff811161019b5784612a0f91830161275e565b60e083015261010081015167ffffffffffffffff811161019b5784612a3591830161275e565b61010083015261012081015167ffffffffffffffff811161019b5784612a5c91830161275e565b61012083015261014081015167ffffffffffffffff811161019b5784612a8391830161275e565b61014083015261016081015167ffffffffffffffff811161019b5784612aaa91830161275e565b61016083015261018081015167ffffffffffffffff811161019b5784612ad191830161275e565b6101808301526101a081015167ffffffffffffffff811161019b5784612af891830161275e565b6101a08301526101c08101519067ffffffffffffffff821161019b57612b209185910161275e565b6101c08201526040840152606081015167ffffffffffffffff811161019b57608092612b4d91830161275e565b6060840152015160808201525f612830565b6040513d5f823e3d90fd5b73ffffffffffffffffffffffffffffffffffffffff168015612c2b5773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b91612c6183612eaa565b929015612cea57827f3fa039a23466a52e08acb25376ac7d81de184fa6549ffffb2fc920c47cb623ed949260ff612ce59373ffffffffffffffffffffffffffffffffffffffff965f525f602052612cba60405f20612695565b936040519788971687526020870152166040850152606084015260a0608084015260a083019061215f565b0390a1565b73ffffffffffffffffffffffffffffffffffffffff847f4c547670000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303612d6c57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b8151919060418303612dc857612dc19250602082015190606060408401519301515f1a9061307f565b9192909190565b50505f9160029190565b6004811015612e7d5780612de4575050565b60018103612e14577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b60028103612e4857507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600314612e525750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff600154166040517fa8af4ff500000000000000000000000000000000000000000000000000000000815260408160248173ffffffffffffffffffffffffffffffffffffffff8716958660048301525afa908115612b5f575f905f92612fe7575b5015612fde57815f52600360205260405f209160405193612f3f85612047565b60018454948587520154806020870152838515159182612fd4575b505015612f82575050505f525f602052612f7760405f20546122f8565b156128655751600191565b909250612f91919493506127d1565b93819291612fa0575b50509190565b60019060405192612fb084612047565b868452602084019182525f52600360205260405f2092518355519101555f80612f9a565b149050835f612f5a565b5050505f905f90565b9150506040813d604011613020575b8161300360409383612063565b8101031261019b57602061301682612751565b910151905f612f1f565b3d9150612ff6565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561305757565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411613103579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612b5f575f5173ffffffffffffffffffffffffffffffffffffffff8116156130f957905f905f90565b505f906001905f90565b5050505f9160039190565b6131166124b1565b8051908115613126576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1005480156131535790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b6131806125c2565b8051908115613190576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015480156131535790565b906131fa57508051156131d257805190602001fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b8151158061324d575b61320b575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b1561320356fea164736f6c634300081c000a", - "sourceMap": "1951:12842:96:-:0;;;;;;;1171:4:51;1163:13;;1951:12842:96;;;;;;1163:13:51;1951:12842:96;;;;;;;;;;;;;;", + "object": "0x60a0806040523460295730608052613226908161002e8239608051818181610a3801526110790152f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80632dd8abfe14611e4c578063485cc955146116b05780634d37fc7a146113365780634f1ef286146110105780634f3a415a14610ab057806352d1902d146109f35780635c40e542146109095780636931164e146108cd578063715018a6146107f3578063730169231461079b5780637b1039991461074a5780637dec71a9146106f95780637ecebe001461069657806384b0196e146104f15780638da5cb5b14610481578063abd45d21146102fc578063ad3cb1cc1461027b578063b33d59da14610237578063d2753561146101e8578063f2fde38b1461019f5763f698da2514610100575f80fd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206101386130d1565b61014061313b565b60405190838201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261019060c082612063565b519020604051908152f35b5f80fd5b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e66101d9611feb565b6101e1612cef565b612b2d565b005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576040610229610224611feb565b612794565b825191151582526020820152f35b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e6610271611fad565b6024359033612c1a565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576102f86040516102ba604082612063565b600581527f352e302e30000000000000000000000000000000000000000000000000000000602082015260405191829160208352602083019061215f565b0390f35b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576060602060405161033b81612047565b82815201526004355f525f60205260405f2060016040519161035c83612047565b61036581612658565b83520180546103738161230c565b916103816040519384612063565b81835260208301905f5260205f205f915b838310610464576103c1868660208201908152604051928392602084525160406020850152606084019061215f565b9051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0838203016040840152815180825260208201916020808360051b8301019401925f915b8383106104155786860387f35b919395509193602080610452837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08660019603018752895161215f565b97019301930190928695949293610408565b60016020819261047385612658565b815201920192019190610392565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10054158061066d575b1561060f576105b3610558612474565b610560612585565b60206105c1604051926105738385612063565b5f84525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e087019061215f565b90858203604087015261215f565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b8281106105f857505050500390f35b8351855286955093810193928101926001016105e9565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415610548565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff6106e2611feb565b165f526002602052602060405f2054604051908152f35b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742610735611fad565b6044359060243590612426565b604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206040517f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc8152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57610829612cef565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742600435612389565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57600435610943612cef565b805f525f60205261095760405f20546122bb565b156109cb57805f525f602052600160405f206109728161233a565b018054905f8155816109a6575b827f56c387a9be1bf0e0e4f852c577a225db98e8253ad401d1b4ea73926f27d6af095f80a2005b5f5260205f20908101905b8181101561097f57806109c560019261233a565b016109b1565b7f22faf042000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163003610a885760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043560243567ffffffffffffffff811161019b57610b02903690600401611fbd565b919060443567ffffffffffffffff811161019b573660238201121561019b5780600401359167ffffffffffffffff831161019b5760248360051b8301019036821161019b57610b4f612cef565b8515610fe8578315610fc057845f525f602052610b6f60405f20546122bb565b610f9857610b8b9060405196610b8488612047565b36916120de565b8552610b968361230c565b92610ba46040519485612063565b83526024820191602084015b828410610f57575050505060208301908152815f525f60205260405f20925192835167ffffffffffffffff8111610e2057610beb82546122bb565b601f8111610f27575b506020601f8211600114610e85579080610c469260019596975f92610e7a575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b81555b019051805190680100000000000000008211610e20578254828455808310610e4d575b50602001915f5260205f20915f905b828210610ca957847fcbb92e241e191fed6d0b0da0a918c7dcf595e77d868e2e3bf9e6b0b91589c7ad5f80a2005b805180519067ffffffffffffffff8211610e2057610cc786546122bb565b601f8111610de5575b50602090601f8311600114610d3f5792610d25836001959460209487965f92610d345750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b87555b01940191019092610c7b565b015190508b80610c14565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0831691875f52815f20925f5b818110610dcd5750936020936001969387969383889510610d96575b505050811b018755610d28565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558a8080610d89565b92936020600181928786015181550195019301610d6d565b610e1090875f5260205f20601f850160051c81019160208610610e16575b601f0160051c0190612324565b87610cd0565b9091508190610e03565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b835f528260205f2091820191015b818110610e685750610c6c565b80610e7460019261233a565b01610e5b565b015190508780610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821695835f52815f20965f5b818110610f0f5750916001959697918487959410610ed8575b505050811b018155610c49565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080610ecb565b83830151895560019098019760209384019301610eb2565b610f5190835f5260205f20601f840160051c81019160208510610e1657601f0160051c0190612324565b85610bf4565b833567ffffffffffffffff811161019b5782013660438201121561019b57602091610f8d839236906044602482013591016120de565b815201930192610bb0565b7f72477348000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f6890d9d4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8423f262000000000000000000000000000000000000000000000000000000005f5260045ffd5b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611042611feb565b60243567ffffffffffffffff811161019b57611062903690600401612114565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168030149081156112f4575b50610a88576110b1612cef565b73ffffffffffffffffffffffffffffffffffffffff8216916040517f52d1902d000000000000000000000000000000000000000000000000000000008152602081600481875afa5f91816112c0575b5061113157837f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8592036112955750813b1561126a57807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2815115611239575f808360206101e695519101845af43d15611231573d91611215836120a4565b926112236040519485612063565b83523d5f602085013e613180565b606091613180565b50503461124257005b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7faa1d49a4000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091506020813d6020116112ec575b816112dc60209383612063565b8101031261019b57519085611100565b3d91506112cf565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54161415836110a4565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043567ffffffffffffffff811161019b5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906113b08261200e565b8060040135801515810361019b578252602481013567ffffffffffffffff811161019b576113e49060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b5781016101e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906114398261202a565b60048101357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602481013567ffffffffffffffff811161019b5761148d9060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b576114b69060043691840101612114565b60408301526114c760648201612132565b60608301526114d860848201612132565b60808301526114e960a48201612132565b60a083015260c481013567ffffffffffffffff811161019b576115129060043691840101612114565b60c083015260e481013567ffffffffffffffff811161019b5761153b9060043691840101612114565b60e083015261010481013567ffffffffffffffff811161019b576115659060043691840101612114565b61010083015261012481013567ffffffffffffffff811161019b576115909060043691840101612114565b61012083015261014481013567ffffffffffffffff811161019b576115bb9060043691840101612114565b61014083015261016481013567ffffffffffffffff811161019b576115e69060043691840101612114565b61016083015261018481013567ffffffffffffffff811161019b576116119060043691840101612114565b6101808301526101a481013567ffffffffffffffff811161019b5761163c9060043691840101612114565b6101a08301526101c48101359067ffffffffffffffff821161019b5760046116679236920101612114565b6101c0820152604083015260648101359167ffffffffffffffff831161019b5760846107429261169f60209560043691840101612114565b6060840152013560808201526121a2565b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576116e7611feb565b60243573ffffffffffffffffffffffffffffffffffffffff811680910361019b577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c16159267ffffffffffffffff821680159081611e44575b6001149081611e3a575b159081611e31575b50611e0957818460017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006117c19516177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055611db4575b506117b9612feb565b6101e1612feb565b6117c9612feb565b6040918251926117d98185612063565b601284527f426c6f636b4275696c646572506f6c696379000000000000000000000000000060208501528051936118108286612063565b600185527f31000000000000000000000000000000000000000000000000000000000000006020860152611842612feb565b61184a612feb565b80519067ffffffffffffffff8211610e20576118867fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546122bb565b601f8111611d47575b50602090601f8311600114611c67576118dc92915f9183610e7a5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102555b835167ffffffffffffffff8111610e205761193a7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546122bb565b601f8111611bfa575b50602094601f8211600114611b1c576119939293949582915f92611b115750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103555b5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100555f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101558215611ae957827fffffffffffffffffffffffff0000000000000000000000000000000000000000600154161760015551917f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b5f80a2611a5857005b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2917fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005560018152a1005b7f11a1e697000000000000000000000000000000000000000000000000000000005f5260045ffd5b015190508680610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52805f20915f5b888110611be257508360019596979810611bab575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103556119b6565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080611b7e565b91926020600181928685015181550194019201611b69565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52611c61907f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c81019160208510610e1657601f0160051c0190612324565b85611943565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52815f20925f5b818110611d2f5750908460019594939210611cf8575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102556118ff565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080611ccb565b92936020600181928786015181550195019301611cb5565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52611dae907f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f850160051c81019160208610610e1657601f0160051c0190612324565b8661188f565b7fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055846117b0565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b90501585611759565b303b159150611751565b859150611747565b3461019b5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611e83611fad565b602435604435916064359267ffffffffffffffff841161019b57611ed7611ed1611eb4611ee0963690600401611fbd565b9190611ec9611ec4868989612426565b612389565b9236916120de565b90612d5b565b90959195612d95565b73ffffffffffffffffffffffffffffffffffffffff841690815f52600260205260405f2054808203611f7f5750505f52600260205260405f20928354937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8514611f525760016101e695019055612c1a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7f06427aeb000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b6004359060ff8216820361019b57565b9181601f8401121561019b5782359167ffffffffffffffff831161019b576020838186019501011161019b57565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361019b57565b60a0810190811067ffffffffffffffff821117610e2057604052565b6101e0810190811067ffffffffffffffff821117610e2057604052565b6040810190811067ffffffffffffffff821117610e2057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610e2057604052565b67ffffffffffffffff8111610e2057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926120ea826120a4565b916120f86040519384612063565b82948184528183011161019b578281602093845f960137010152565b9080601f8301121561019b5781602061212f933591016120de565b90565b35907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b6040015160c081015190610140810151610160820151916101808101516101a082015160e08301519060a08401517fffffffffffffffff0000000000000000000000000000000000000000000000001693608001517fffffffffffffffff00000000000000000000000000000000000000000000000016926040519687966020880199805160208192018c5e880160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e019182526008820152037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0810182526010016122b59082612063565b51902090565b90600182811c92168015612302575b60208310146122d557565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916122ca565b67ffffffffffffffff8111610e205760051b60200190565b81811061232f575050565b5f8155600101612324565b61234481546122bb565b908161234e575050565b81601f5f93116001146123605750555b565b8183526020832061237c91601f0160051c810190600101612324565b8082528160208120915555565b6042906123946130d1565b61239c61313b565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526123ed60c082612063565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b916040519160ff60208401947f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc865216604084015260608301526080820152608081526122b560a082612063565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10254916124a6836122bb565b808352926001811690811561254857506001146124ca575b61235e92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b81831061252c57505090602061235e928201016124be565b6020919350806001915483858901015201910190918492612514565b6020925061235e9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016124be565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10354916125b7836122bb565b808352926001811690811561254857506001146125da5761235e92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b81831061263c57505090602061235e928201016124be565b6020919350806001915483858901015201910190918492612624565b9060405191825f82549261266b846122bb565b80845293600181169081156126d45750600114612690575b5061235e92500383612063565b90505f9291925260205f20905f915b8183106126b857505090602061235e928201015f612683565b602091935080600191548385890101520191019091849261269f565b6020935061235e9592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201015f612683565b5190811515820361019b57565b81601f8201121561019b57805190612738826120a4565b926127466040519485612063565b8284526020838301011161019b57815f9260208093018386015e8301015290565b51907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b5f73ffffffffffffffffffffffffffffffffffffffff602481600154169360405194859384927f727310620000000000000000000000000000000000000000000000000000000084521660048301525afa908115612b22575f9161282f575b5080511561282857612804906121a2565b805f525f60205261281860405f20546122bb565b61282357505f905f90565b600191565b505f905f90565b90503d805f833e6128408183612063565b81019060408183031261019b5761285681612714565b5060208101519067ffffffffffffffff821161019b57019060a08282031261019b57604051916128858361200e565b61288e81612714565b8352602081015167ffffffffffffffff811161019b57826128b0918301612721565b6020840152604081015167ffffffffffffffff811161019b5781016101e08184031261019b57604051906128e38261202a565b80517fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602081015167ffffffffffffffff811161019b5784612930918301612721565b6020830152604081015167ffffffffffffffff811161019b5784612955918301612721565b604083015261296660608201612767565b606083015261297760808201612767565b608083015261298860a08201612767565b60a083015260c081015167ffffffffffffffff811161019b57846129ad918301612721565b60c083015260e081015167ffffffffffffffff811161019b57846129d2918301612721565b60e083015261010081015167ffffffffffffffff811161019b57846129f8918301612721565b61010083015261012081015167ffffffffffffffff811161019b5784612a1f918301612721565b61012083015261014081015167ffffffffffffffff811161019b5784612a46918301612721565b61014083015261016081015167ffffffffffffffff811161019b5784612a6d918301612721565b61016083015261018081015167ffffffffffffffff811161019b5784612a94918301612721565b6101808301526101a081015167ffffffffffffffff811161019b5784612abb918301612721565b6101a08301526101c08101519067ffffffffffffffff821161019b57612ae391859101612721565b6101c08201526040840152606081015167ffffffffffffffff811161019b57608092612b10918301612721565b6060840152015160808201525f6127f3565b6040513d5f823e3d90fd5b73ffffffffffffffffffffffffffffffffffffffff168015612bee5773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b91612c2483612e6d565b929015612cad57827f3fa039a23466a52e08acb25376ac7d81de184fa6549ffffb2fc920c47cb623ed949260ff612ca89373ffffffffffffffffffffffffffffffffffffffff965f525f602052612c7d60405f20612658565b936040519788971687526020870152166040850152606084015260a0608084015260a083019061215f565b0390a1565b73ffffffffffffffffffffffffffffffffffffffff847f4c547670000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303612d2f57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b8151919060418303612d8b57612d849250602082015190606060408401519301515f1a90613042565b9192909190565b50505f9160029190565b6004811015612e405780612da7575050565b60018103612dd7577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b60028103612e0b57507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600314612e155750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff600154166040517fa8af4ff500000000000000000000000000000000000000000000000000000000815260408160248173ffffffffffffffffffffffffffffffffffffffff8716958660048301525afa908115612b22575f905f92612faa575b5015612fa157815f52600360205260405f209160405193612f0285612047565b60018454948587520154806020870152838515159182612f97575b505015612f45575050505f525f602052612f3a60405f20546122bb565b156128285751600191565b909250612f5491949350612794565b93819291612f63575b50509190565b60019060405192612f7384612047565b868452602084019182525f52600360205260405f2092518355519101555f80612f5d565b149050835f612f1d565b5050505f905f90565b9150506040813d604011612fe3575b81612fc660409383612063565b8101031261019b576020612fd982612714565b910151905f612ee2565b3d9150612fb9565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561301a57565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116130c6579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612b22575f5173ffffffffffffffffffffffffffffffffffffffff8116156130bc57905f905f90565b505f906001905f90565b5050505f9160039190565b6130d9612474565b80519081156130e9576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1005480156131165790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b613143612585565b8051908115613153576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015480156131165790565b906131bd575080511561319557602081519101fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580613210575b6131ce575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156131c656fea164736f6c634300081c000a", + "sourceMap": "2021:11391:71:-:0;;;;;;;1171:4:32;1163:13;;2021:11391:71;;;;;;1163:13:32;2021:11391:71;;;;;;;;;;;;;;", "linkReferences": {} - } -} + }, + "deployedBytecode": { + "object": "0x60806040526004361015610011575f80fd5b5f3560e01c80632dd8abfe14611e4c578063485cc955146116b05780634d37fc7a146113365780634f1ef286146110105780634f3a415a14610ab057806352d1902d146109f35780635c40e542146109095780636931164e146108cd578063715018a6146107f3578063730169231461079b5780637b1039991461074a5780637dec71a9146106f95780637ecebe001461069657806384b0196e146104f15780638da5cb5b14610481578063abd45d21146102fc578063ad3cb1cc1461027b578063b33d59da14610237578063d2753561146101e8578063f2fde38b1461019f5763f698da2514610100575f80fd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206101386130d1565b61014061313b565b60405190838201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a0815261019060c082612063565b519020604051908152f35b5f80fd5b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e66101d9611feb565b6101e1612cef565b612b2d565b005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576040610229610224611feb565b612794565b825191151582526020820152f35b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576101e6610271611fad565b6024359033612c1a565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576102f86040516102ba604082612063565b600581527f352e302e30000000000000000000000000000000000000000000000000000000602082015260405191829160208352602083019061215f565b0390f35b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576060602060405161033b81612047565b82815201526004355f525f60205260405f2060016040519161035c83612047565b61036581612658565b83520180546103738161230c565b916103816040519384612063565b81835260208301905f5260205f205f915b838310610464576103c1868660208201908152604051928392602084525160406020850152606084019061215f565b9051907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0838203016040840152815180825260208201916020808360051b8301019401925f915b8383106104155786860387f35b919395509193602080610452837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08660019603018752895161215f565b97019301930190928695949293610408565b60016020819261047385612658565b815201920192019190610392565b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b577fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10054158061066d575b1561060f576105b3610558612474565b610560612585565b60206105c1604051926105738385612063565b5f84525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e08588015260e087019061215f565b90858203604087015261215f565b4660608501523060808501525f60a085015283810360c08501528180845192838152019301915f5b8281106105f857505050500390f35b8351855286955093810193928101926001016105e9565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a656400000000000000000000006044820152fd5b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015415610548565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff6106e2611feb565b165f526002602052602060405f2054604051908152f35b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742610735611fad565b6044359060243590612426565b604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760206040517f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc8152f35b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57610829612cef565b5f73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300547fffffffffffffffffffffffff000000000000000000000000000000000000000081167f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576020610742600435612389565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57600435610943612cef565b805f525f60205261095760405f20546122bb565b156109cb57805f525f602052600160405f206109728161233a565b018054905f8155816109a6575b827f56c387a9be1bf0e0e4f852c577a225db98e8253ad401d1b4ea73926f27d6af095f80a2005b5f5260205f20908101905b8181101561097f57806109c560019261233a565b016109b1565b7f22faf042000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163003610a885760206040517f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8152f35b7fe07c8dba000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461019b5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043560243567ffffffffffffffff811161019b57610b02903690600401611fbd565b919060443567ffffffffffffffff811161019b573660238201121561019b5780600401359167ffffffffffffffff831161019b5760248360051b8301019036821161019b57610b4f612cef565b8515610fe8578315610fc057845f525f602052610b6f60405f20546122bb565b610f9857610b8b9060405196610b8488612047565b36916120de565b8552610b968361230c565b92610ba46040519485612063565b83526024820191602084015b828410610f57575050505060208301908152815f525f60205260405f20925192835167ffffffffffffffff8111610e2057610beb82546122bb565b601f8111610f27575b506020601f8211600114610e85579080610c469260019596975f92610e7a575b50507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b81555b019051805190680100000000000000008211610e20578254828455808310610e4d575b50602001915f5260205f20915f905b828210610ca957847fcbb92e241e191fed6d0b0da0a918c7dcf595e77d868e2e3bf9e6b0b91589c7ad5f80a2005b805180519067ffffffffffffffff8211610e2057610cc786546122bb565b601f8111610de5575b50602090601f8311600114610d3f5792610d25836001959460209487965f92610d345750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b87555b01940191019092610c7b565b015190508b80610c14565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0831691875f52815f20925f5b818110610dcd5750936020936001969387969383889510610d96575b505050811b018755610d28565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c191690558a8080610d89565b92936020600181928786015181550195019301610d6d565b610e1090875f5260205f20601f850160051c81019160208610610e16575b601f0160051c0190612324565b87610cd0565b9091508190610e03565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b835f528260205f2091820191015b818110610e685750610c6c565b80610e7460019261233a565b01610e5b565b015190508780610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0821695835f52815f20965f5b818110610f0f5750916001959697918487959410610ed8575b505050811b018155610c49565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080610ecb565b83830151895560019098019760209384019301610eb2565b610f5190835f5260205f20601f840160051c81019160208510610e1657601f0160051c0190612324565b85610bf4565b833567ffffffffffffffff811161019b5782013660438201121561019b57602091610f8d839236906044602482013591016120de565b815201930192610bb0565b7f72477348000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f6890d9d4000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f8423f262000000000000000000000000000000000000000000000000000000005f5260045ffd5b60407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611042611feb565b60243567ffffffffffffffff811161019b57611062903690600401612114565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168030149081156112f4575b50610a88576110b1612cef565b73ffffffffffffffffffffffffffffffffffffffff8216916040517f52d1902d000000000000000000000000000000000000000000000000000000008152602081600481875afa5f91816112c0575b5061113157837f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8592036112955750813b1561126a57807fffffffffffffffffffffffff00000000000000000000000000000000000000007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5416177f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc557fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b5f80a2815115611239575f808360206101e695519101845af43d15611231573d91611215836120a4565b926112236040519485612063565b83523d5f602085013e613180565b606091613180565b50503461124257005b7fb398979f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f4c9c8ce3000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7faa1d49a4000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091506020813d6020116112ec575b816112dc60209383612063565b8101031261019b57519085611100565b3d91506112cf565b905073ffffffffffffffffffffffffffffffffffffffff7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc54161415836110a4565b3461019b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b5760043567ffffffffffffffff811161019b5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906113b08261200e565b8060040135801515810361019b578252602481013567ffffffffffffffff811161019b576113e49060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b5781016101e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc823603011261019b57604051906114398261202a565b60048101357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602481013567ffffffffffffffff811161019b5761148d9060043691840101612114565b6020830152604481013567ffffffffffffffff811161019b576114b69060043691840101612114565b60408301526114c760648201612132565b60608301526114d860848201612132565b60808301526114e960a48201612132565b60a083015260c481013567ffffffffffffffff811161019b576115129060043691840101612114565b60c083015260e481013567ffffffffffffffff811161019b5761153b9060043691840101612114565b60e083015261010481013567ffffffffffffffff811161019b576115659060043691840101612114565b61010083015261012481013567ffffffffffffffff811161019b576115909060043691840101612114565b61012083015261014481013567ffffffffffffffff811161019b576115bb9060043691840101612114565b61014083015261016481013567ffffffffffffffff811161019b576115e69060043691840101612114565b61016083015261018481013567ffffffffffffffff811161019b576116119060043691840101612114565b6101808301526101a481013567ffffffffffffffff811161019b5761163c9060043691840101612114565b6101a08301526101c48101359067ffffffffffffffff821161019b5760046116679236920101612114565b6101c0820152604083015260648101359167ffffffffffffffff831161019b5760846107429261169f60209560043691840101612114565b6060840152013560808201526121a2565b3461019b5760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b576116e7611feb565b60243573ffffffffffffffffffffffffffffffffffffffff811680910361019b577ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460ff8160401c16159267ffffffffffffffff821680159081611e44575b6001149081611e3a575b159081611e31575b50611e0957818460017fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006117c19516177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055611db4575b506117b9612feb565b6101e1612feb565b6117c9612feb565b6040918251926117d98185612063565b601284527f426c6f636b4275696c646572506f6c696379000000000000000000000000000060208501528051936118108286612063565b600185527f31000000000000000000000000000000000000000000000000000000000000006020860152611842612feb565b61184a612feb565b80519067ffffffffffffffff8211610e20576118867fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102546122bb565b601f8111611d47575b50602090601f8311600114611c67576118dc92915f9183610e7a5750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102555b835167ffffffffffffffff8111610e205761193a7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103546122bb565b601f8111611bfa575b50602094601f8211600114611b1c576119939293949582915f92611b115750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8260011b9260031b1c19161790565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103555b5f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100555f7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d101558215611ae957827fffffffffffffffffffffffff0000000000000000000000000000000000000000600154161760015551917f27fe5f0c1c3b1ed427cc63d0f05759ffdecf9aec9e18d31ef366fc8a6cb5dc3b5f80a2611a5857005b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2917fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054167ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005560018152a1005b7f11a1e697000000000000000000000000000000000000000000000000000000005f5260045ffd5b015190508680610c14565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08216957fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52805f20915f5b888110611be257508360019596979810611bab575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d103556119b6565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055858080611b7e565b91926020600181928685015181550194019201611b69565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f52611c61907f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b75601f840160051c81019160208510610e1657601f0160051c0190612324565b85611943565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08316917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52815f20925f5b818110611d2f5750908460019594939210611cf8575b505050811b017fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102556118ff565b01517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88460031b161c19169055868080611ccb565b92936020600181928786015181550195019301611cb5565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f52611dae907f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d601f850160051c81019160208610610e1657601f0160051c0190612324565b8661188f565b7fffffffffffffffffffffffffffffffffffffffffffffff0000000000000000001668010000000000000001177ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0055846117b0565b7ff92ee8a9000000000000000000000000000000000000000000000000000000005f5260045ffd5b90501585611759565b303b159150611751565b859150611747565b3461019b5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261019b57611e83611fad565b602435604435916064359267ffffffffffffffff841161019b57611ed7611ed1611eb4611ee0963690600401611fbd565b9190611ec9611ec4868989612426565b612389565b9236916120de565b90612d5b565b90959195612d95565b73ffffffffffffffffffffffffffffffffffffffff841690815f52600260205260405f2054808203611f7f5750505f52600260205260405f20928354937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8514611f525760016101e695019055612c1a565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b7f06427aeb000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b6004359060ff8216820361019b57565b9181601f8401121561019b5782359167ffffffffffffffff831161019b576020838186019501011161019b57565b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361019b57565b60a0810190811067ffffffffffffffff821117610e2057604052565b6101e0810190811067ffffffffffffffff821117610e2057604052565b6040810190811067ffffffffffffffff821117610e2057604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117610e2057604052565b67ffffffffffffffff8111610e2057601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b9291926120ea826120a4565b916120f86040519384612063565b82948184528183011161019b578281602093845f960137010152565b9080601f8301121561019b5781602061212f933591016120de565b90565b35907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f602080948051918291828752018686015e5f8582860101520116010190565b6040015160c081015190610140810151610160820151916101808101516101a082015160e08301519060a08401517fffffffffffffffff0000000000000000000000000000000000000000000000001693608001517fffffffffffffffff00000000000000000000000000000000000000000000000016926040519687966020880199805160208192018c5e880160208101915f83528051926020849201905e016020015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e015f815281516020819301825e019182526008820152037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0810182526010016122b59082612063565b51902090565b90600182811c92168015612302575b60208310146122d557565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b91607f16916122ca565b67ffffffffffffffff8111610e205760051b60200190565b81811061232f575050565b5f8155600101612324565b61234481546122bb565b908161234e575050565b81601f5f93116001146123605750555b565b8183526020832061237c91601f0160051c810190600101612324565b8082528160208120915555565b6042906123946130d1565b61239c61313b565b6040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526123ed60c082612063565b51902090604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522090565b916040519160ff60208401947f93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc865216604084015260608301526080820152608081526122b560a082612063565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10254916124a6836122bb565b808352926001811690811561254857506001146124ca575b61235e92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1025f90815290917f42ad5d3e1f2e6e70edcf6d991b8a3023d3fca8047a131592f9edb9fd9b89d57d5b81831061252c57505090602061235e928201016124be565b6020919350806001915483858901015201910190918492612514565b6020925061235e9491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b8201016124be565b604051905f827fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10354916125b7836122bb565b808352926001811690811561254857506001146125da5761235e92500383612063565b507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1035f90815290917f5f9ce34815f8e11431c7bb75a8e6886a91478f7ffc1dbb0a98dc240fddd76b755b81831061263c57505090602061235e928201016124be565b6020919350806001915483858901015201910190918492612624565b9060405191825f82549261266b846122bb565b80845293600181169081156126d45750600114612690575b5061235e92500383612063565b90505f9291925260205f20905f915b8183106126b857505090602061235e928201015f612683565b602091935080600191548385890101520191019091849261269f565b6020935061235e9592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0091501682840152151560051b8201015f612683565b5190811515820361019b57565b81601f8201121561019b57805190612738826120a4565b926127466040519485612063565b8284526020838301011161019b57815f9260208093018386015e8301015290565b51907fffffffffffffffff0000000000000000000000000000000000000000000000008216820361019b57565b5f73ffffffffffffffffffffffffffffffffffffffff602481600154169360405194859384927f727310620000000000000000000000000000000000000000000000000000000084521660048301525afa908115612b22575f9161282f575b5080511561282857612804906121a2565b805f525f60205261281860405f20546122bb565b61282357505f905f90565b600191565b505f905f90565b90503d805f833e6128408183612063565b81019060408183031261019b5761285681612714565b5060208101519067ffffffffffffffff821161019b57019060a08282031261019b57604051916128858361200e565b61288e81612714565b8352602081015167ffffffffffffffff811161019b57826128b0918301612721565b6020840152604081015167ffffffffffffffff811161019b5781016101e08184031261019b57604051906128e38261202a565b80517fffffffffffffffffffffffffffffffff000000000000000000000000000000008116810361019b578252602081015167ffffffffffffffff811161019b5784612930918301612721565b6020830152604081015167ffffffffffffffff811161019b5784612955918301612721565b604083015261296660608201612767565b606083015261297760808201612767565b608083015261298860a08201612767565b60a083015260c081015167ffffffffffffffff811161019b57846129ad918301612721565b60c083015260e081015167ffffffffffffffff811161019b57846129d2918301612721565b60e083015261010081015167ffffffffffffffff811161019b57846129f8918301612721565b61010083015261012081015167ffffffffffffffff811161019b5784612a1f918301612721565b61012083015261014081015167ffffffffffffffff811161019b5784612a46918301612721565b61014083015261016081015167ffffffffffffffff811161019b5784612a6d918301612721565b61016083015261018081015167ffffffffffffffff811161019b5784612a94918301612721565b6101808301526101a081015167ffffffffffffffff811161019b5784612abb918301612721565b6101a08301526101c08101519067ffffffffffffffff821161019b57612ae391859101612721565b6101c08201526040840152606081015167ffffffffffffffff811161019b57608092612b10918301612721565b6060840152015160808201525f6127f3565b6040513d5f823e3d90fd5b73ffffffffffffffffffffffffffffffffffffffff168015612bee5773ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054827fffffffffffffffffffffffff00000000000000000000000000000000000000008216177f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930055167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e05f80a3565b7f1e4fbdf7000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b91612c2483612e6d565b929015612cad57827f3fa039a23466a52e08acb25376ac7d81de184fa6549ffffb2fc920c47cb623ed949260ff612ca89373ffffffffffffffffffffffffffffffffffffffff965f525f602052612c7d60405f20612658565b936040519788971687526020870152166040850152606084015260a0608084015260a083019061215f565b0390a1565b73ffffffffffffffffffffffffffffffffffffffff847f4c547670000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930054163303612d2f57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b8151919060418303612d8b57612d849250602082015190606060408401519301515f1a90613042565b9192909190565b50505f9160029190565b6004811015612e405780612da7575050565b60018103612dd7577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b60028103612e0b57507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600314612e155750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b73ffffffffffffffffffffffffffffffffffffffff600154166040517fa8af4ff500000000000000000000000000000000000000000000000000000000815260408160248173ffffffffffffffffffffffffffffffffffffffff8716958660048301525afa908115612b22575f905f92612faa575b5015612fa157815f52600360205260405f209160405193612f0285612047565b60018454948587520154806020870152838515159182612f97575b505015612f45575050505f525f602052612f3a60405f20546122bb565b156128285751600191565b909250612f5491949350612794565b93819291612f63575b50509190565b60019060405192612f7384612047565b868452602084019182525f52600360205260405f2092518355519101555f80612f5d565b149050835f612f1d565b5050505f905f90565b9150506040813d604011612fe3575b81612fc660409383612063565b8101031261019b576020612fd982612714565b910151905f612ee2565b3d9150612fb9565b60ff7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005460401c161561301a57565b7fd7e6bcf8000000000000000000000000000000000000000000000000000000005f5260045ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084116130c6579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15612b22575f5173ffffffffffffffffffffffffffffffffffffffff8116156130bc57905f905f90565b505f906001905f90565b5050505f9160039190565b6130d9612474565b80519081156130e9576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1005480156131165790565b507fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47090565b613143612585565b8051908115613153576020012090565b50507fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1015480156131165790565b906131bd575080511561319557602081519101fd5b7fd6bda275000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580613210575b6131ce575090565b73ffffffffffffffffffffffffffffffffffffffff907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156131c656fea164736f6c634300081c000a", + "sourceMap": "2021:11391:71:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4107:92:35;4129:17;;:::i;:::-;4148:20;;:::i;:::-;2021:11391:71;;4107:92:35;;;;2021:11391:71;1959:95:35;2021:11391:71;;;1959:95:35;;2021:11391:71;1959:95:35;;;2021:11391:71;4170:13:35;1959:95;;;2021:11391:71;4193:4:35;1959:95;;;2021:11391:71;1959:95:35;4107:92;;;;;;:::i;:::-;2021:11391:71;4097:103:35;;2021:11391:71;;;;;;;;;;;;;;;;;;;;;2357:1:30;2021:11391:71;;:::i;:::-;2303:62:30;;:::i;:::-;2357:1;:::i;:::-;2021:11391:71;;;;;;;;;;;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;4428:16;2021:11391;;:::i;:::-;;;4407:10;;4428:16;:::i;2021:11391::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1280:65:30;2021:11391:71;;;;;;;;;;;;;;;;;;;2692:64:35;2021:11391:71;5647:18:35;:43;;;2021:11391:71;;;;;;;:::i;:::-;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;5835:13:35;2021:11391:71;;;;5870:4:35;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5647:43:35;2021:11391:71;5669:16:35;2021:11391:71;5669:21:35;5647:43;;2021:11391:71;;;;;;;;;;;;;;:::i;:::-;;;;3077:64;2021:11391;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;3007:23;2021:11391;;;;;;;;;;;;;;;;;;;;;;2361:90;2021:11391;;;;;;;;;;;;;;2303:62:30;;:::i;:::-;2021:11391:71;;1280:65:30;2021:11391:71;;;;1280:65:30;2021:11391:71;;3975:40:30;;;;2021:11391:71;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;2303:62:30;;:::i;:::-;2021:11391:71;;;;;;;;;;;;:::i;:::-;12350:59;2021:11391;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;12537:38;;2021:11391;12537:38;;2021:11391;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;5090:6:32;2021:11391:71;5081:4:32;5073:23;5069:145;;2021:11391:71;;;811:66:41;2021:11391:71;;;5069:145:32;5174:29;2021:11391:71;5174:29:32;2021:11391:71;;5174:29:32;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2303:62:30;;:::i;:::-;11561:28:71;;2021:11391;;11627:25;;2021:11391;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;11972:74;;;;2021:11391;11972:74;;2021:11391;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12062:34;;2021:11391;12062:34;;2021:11391;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;:::i;:::-;;4667:6:32;2021:11391:71;4658:4:32;;4650:23;:120;;;;;2021:11391:71;4633:251:32;;;2303:62:30;;:::i;:::-;2021:11391:71;;;;;;;6131:52:32;;2021:11391:71;6131:52:32;2021:11391:71;6131:52:32;;;;2021:11391:71;;6131:52:32;;;2021:11391:71;-1:-1:-1;6127:437:32;;6493:60;;2021:11391:71;6493:60:32;2021:11391:71;;;;6493:60:32;6127:437;6225:40;811:66:41;6225:40:32;;;6221:120;;1748:29:41;;;:34;1744:119;;2021:11391:71;;811:66:41;2021:11391:71;;;811:66:41;2021:11391:71;2407:36:41;2021:11391:71;2407:36:41;;2021:11391:71;;2458:15:41;:11;;2021:11391:71;4065:25:48;;2021:11391:71;4107:55:48;4065:25;;;;;;;2021:11391:71;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;4107:55:48;:::i;2021:11391:71:-;;;4107:55:48;:::i;2454:148:41:-;6163:9;;;6159:70;;2021:11391:71;6159:70:41;6199:19;2021:11391:71;6199:19:41;2021:11391:71;;6199:19:41;1744:119;1805:47;2021:11391:71;1805:47:41;2021:11391:71;;;;1805:47:41;6221:120:32;6292:34;2021:11391:71;6292:34:32;2021:11391:71;;;;6292:34:32;6131:52;;;;2021:11391:71;6131:52:32;;2021:11391:71;6131:52:32;;;;;;2021:11391:71;6131:52:32;;;:::i;:::-;;;2021:11391:71;;;;;6131:52:32;;;;;;;-1:-1:-1;6131:52:32;;4650:120;2021:11391:71;;;811:66:41;2021:11391:71;;4728:42:32;;4650:120;;;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;3147:66:31;2021:11391:71;;;;;;4301:16:31;2021:11391:71;;;;4724:16:31;;:34;;;;2021:11391:71;4803:1:31;4788:16;:50;;;;2021:11391:71;4853:13:31;:30;;;;2021:11391:71;4849:91:31;;;2021:11391:71;;4803:1:31;2021:11391:71;6959:1:31;2021:11391:71;;;3147:66:31;2021:11391:71;4977:67:31;;2021:11391:71;6891:76:31;;;:::i;:::-;;;:::i;6959:1::-;6891:76;;:::i;:::-;2021:11391:71;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;:::i;:::-;4803:1:31;2021:11391:71;;;;;;;6891:76:31;;:::i;:::-;;;:::i;:::-;2021:11391:71;;;;;;;;;3652:7:35;2021:11391:71;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3652:7:35;2021:11391:71;;;;;;;;;;3676:10:35;2021:11391:71;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3676:10:35;2021:11391:71;;;2692:64:35;2021:11391:71;;3788:16:35;2021:11391:71;3897:23;;2021:11391;;;;4803:1:31;2021:11391:71;;;4803:1:31;2021:11391:71;;3986:22;;2021:11391;3986:22;;5064:101:31;;2021:11391:71;5064:101:31;2021:11391:71;5140:14:31;2021:11391:71;;3147:66:31;2021:11391:71;;3147:66:31;2021:11391:71;4803:1:31;2021:11391:71;;5140:14:31;2021:11391:71;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;3676:10:35;2021:11391:71;;;;;;;;;;;;;;;4803:1:31;2021:11391:71;;;;;;;;;;;;;;3676:10:35;2021:11391:71;;;;;;;;;3676:10:35;2021:11391:71;;;;;;;;;;;;;;;;4803:1:31;2021:11391:71;;;;;;;;;;;;;;;;3676:10:35;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;3652:7:35;2021:11391:71;;;;;;;;;;;;;;;;4803:1:31;2021:11391:71;;;;;;;;;;;;;;3652:7:35;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;4803:1:31;2021:11391:71;;;;;;;;;;;;;;;;3652:7:35;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;4977:67:31;2021:11391:71;;;;3147:66:31;2021:11391:71;4977:67:31;;;4849:91;4906:23;2021:11391:71;4906:23:31;2021:11391:71;;4906:23:31;4853:30;4870:13;;;4853:30;;;4788:50;4816:4;4808:25;:30;;-1:-1:-1;4788:50:31;;4724:34;;;-1:-1:-1;4724:34:31;;2021:11391:71;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;3871:27:55;2021:11391:71;;3927:8:55;2021:11391:71;;;;;;:::i;:::-;4772:51;;4752:72;4772:51;;;;;:::i;:::-;4752:72;:::i;:::-;2021:11391;;;;:::i;:::-;3871:27:55;;:::i;:::-;3927:8;;;;;:::i;:::-;2021:11391:71;;;;;;;4943:6;2021:11391;;;;;;4979:22;;;2021:11391;;;;;;4943:6;2021:11391;;;;;;;;;;;;;;;5199:16;2021:11391;;;;5199:16;:::i;2021:11391::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;:::o;:::-;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;:::o;:::-;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;:::o;10512:815::-;10793:29;;;:34;;;;10849:35;;;;;10906;;;;10963;;;;;11020;;;;11118:40;;;;11180:34;;;;2021:11391;;;11236:42;;;2021:11391;;;;10793:29;2021:11391;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;10732:578;;10512:815;:::o;2021:11391::-;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;:::o;:::-;;;;;;;;;;;;;:::i;:::-;;;;;;;:::o;:::-;;;-1:-1:-1;2021:11391:71;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;:::o;12855:131::-;3993:249:56;12855:131:71;4129:17:35;;:::i;:::-;4148:20;;:::i;:::-;2021:11391:71;;4107:92:35;;;;2021:11391:71;1959:95:35;2021:11391:71;;;1959:95:35;;2021:11391:71;1959:95:35;;;2021:11391:71;4170:13:35;1959:95;;;2021:11391:71;4193:4:35;1959:95;;;2021:11391:71;1959:95:35;4107:92;;;;;;:::i;:::-;2021:11391:71;4097:103:35;;3993:249:56;2021:11391:71;3993:249:56;;;;;;;;;;;;;;12855:131:71;:::o;13032:229::-;;2021:11391;;13172:81;2021:11391;13172:81;;;2021:11391;2361:90;2021:11391;;;;;;;;;;;;;;;;13172:81;;;;;;:::i;2021:11391::-;;;;-1:-1:-1;2021:11391:71;6311:7:35;2021:11391:71;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;6311:7:35;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;6696:10:35;2021:11391:71;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;6696:10:35;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;-1:-1:-1;2021:11391:71;;;;;-1:-1:-1;2021:11391:71;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;:::i;:::-;;;;;;;;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;:::o;6750:906::-;-1:-1:-1;2021:11391:71;6999:60;2021:11391;7022:8;2021:11391;;;;;6999:60;;;;;2021:11391;6999:60;;2021:11391;6999:60;;;2021:11391;6999:60;;;;;;;-1:-1:-1;6999:60:71;;;6750:906;2021:11391;;;;7226:86;;7346:41;;;:::i;:::-;2021:11391;-1:-1:-1;2021:11391:71;-1:-1:-1;2021:11391:71;;;;-1:-1:-1;2021:11391:71;;;:::i;:::-;7472:133;;7615:34;-1:-1:-1;7615:34:71;-1:-1:-1;6750:906:71;:::o;7472:133::-;7022:8;;7569:25::o;7226:86::-;7267:34;-1:-1:-1;7267:34:71;-1:-1:-1;7267:34:71;:::o;6999:60::-;;;;;-1:-1:-1;6999:60:71;;;;;;:::i;:::-;;;2021:11391;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::i;:::-;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;6999:60;;;;2021:11391;;;-1:-1:-1;2021:11391:71;;;;;3405:215:30;2021:11391:71;;3489:22:30;;3485:91;;2021:11391:71;1280:65:30;2021:11391:71;;;;;;1280:65:30;2021:11391:71;;3975:40:30;3509:1;3975:40;;3405:215::o;3485:91::-;3534:31;3509:1;3534:31;3509:1;3534:31;2021:11391:71;;3509:1:30;3534:31;5648:1056:71;;5900:34;;;:::i;:::-;2021:11391;;;;;;6608:89;2021:11391;;;;;;;6552:17;2021:11391;6552:17;2021:11391;;;;6552:17;2021:11391;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;6608:89;;;5648:1056::o;2021:11391::-;;;;;;;;;;;;2658:162:30;2021:11391:71;1280:65:30;2021:11391:71;;966:10:33;2717:23:30;2713:101;;2658:162::o;2713:101::-;2763:40;-1:-1:-1;2763:40:30;966:10:33;2763:40:30;2021:11391:71;;-1:-1:-1;2763:40:30;2129:778:55;2021:11391:71;;;2129:778:55;2319:2;2299:22;;2319:2;;2751:25;2535:196;;;;;;;;;;;;;;;-1:-1:-1;2535:196:55;2751:25;;:::i;:::-;2744:32;;;;;:::o;2295:606::-;2807:83;;2823:1;2807:83;2827:35;2807:83;;:::o;7280:532::-;2021:11391:71;;;;;;7366:29:55;;;7411:7;;:::o;7362:444::-;2021:11391:71;7462:38:55;;2021:11391:71;;7523:23:55;7375:20;7523:23;2021:11391:71;7375:20:55;7523:23;7458:348;7576:35;7567:44;;7576:35;;7634:46;;7375:20;7634:46;2021:11391:71;;;7375:20:55;7634:46;7563:243;7710:30;7701:39;7697:109;;7563:243;7280:532::o;7697:109::-;7763:32;7375:20;7763:32;2021:11391:71;;;7375:20:55;7763:32;2021:11391:71;;7375:20:55;2021:11391:71;;;;;7375:20:55;2021:11391:71;8892:1574;2021:11391;9107:8;2021:11391;;;;;9084:66;;2021:11391;;9084:66;2021:11391;;;;9084:66;;;;;2021:11391;9084:66;;;;;;;-1:-1:-1;;;9084:66:71;;;8892:1574;9164:8;;9160:73;;2021:11391;-1:-1:-1;2021:11391:71;9338:15;2021:11391;;;-1:-1:-1;2021:11391:71;;;;;;;;:::i;:::-;9107:8;2021:11391;;;;;;;;;;;;;9536:21;;;;:54;;;;8892:1574;-1:-1:-1;;9532:928:71;;;2021:11391;;;-1:-1:-1;2021:11391:71;-1:-1:-1;2021:11391:71;;;;-1:-1:-1;2021:11391:71;;;:::i;:::-;9709:64;2021:11391;;;9107:8;;9793:32::o;9532:928::-;10170:27;;;;;;;;;:::i;:::-;10212:195;;;;;;9532:928;10421:28;;;;:::o;10212:195::-;9107:8;2021:11391;;;;;;;:::i;:::-;;;;;10330:62;;2021:11391;;;-1:-1:-1;2021:11391:71;9338:15;2021:11391;;;-1:-1:-1;2021:11391:71;;;;;;;;;10212:195;;;;9536:54;9561:29;;-1:-1:-1;9536:54:71;;;;9160:73;9188:34;;;-1:-1:-1;9188:34:71;-1:-1:-1;9188:34:71;:::o;9084:66::-;;;;2021:11391;9084:66;;2021:11391;9084:66;;;;;;2021:11391;9084:66;;;:::i;:::-;;;2021:11391;;;;;;;;:::i;:::-;;;;9084:66;;;;;;;-1:-1:-1;9084:66:71;;7082:141:31;2021:11391:71;3147:66:31;2021:11391:71;;;;7148:18:31;7144:73;;7082:141::o;7144:73::-;7189:17;-1:-1:-1;7189:17:31;;-1:-1:-1;7189:17:31;5203:1551:55;;;6283:66;6270:79;;6266:164;;2021:11391:71;;;;;;-1:-1:-1;2021:11391:71;;;;;;;;;;;;;;;;;;;6541:24:55;;;;;;;;;-1:-1:-1;6541:24:55;2021:11391:71;;;6579:20:55;6575:113;;6698:49;-1:-1:-1;6698:49:55;-1:-1:-1;5203:1551:55;:::o;6575:113::-;6615:62;-1:-1:-1;6615:62:55;6541:24;6615:62;-1:-1:-1;6615:62:55;:::o;6266:164::-;6365:54;;;6381:1;6365:54;6385:30;6365:54;;:::o;6928:687:35:-;2021:11391:71;;:::i;:::-;;;;7100:22:35;;;;2021:11391:71;;7145:22:35;7138:29;:::o;7096:513::-;-1:-1:-1;;2692:64:35;2021:11391:71;7473:15:35;;;;7508:17;:::o;7469:130::-;7564:20;7571:13;7564:20;:::o;7836:723::-;2021:11391:71;;:::i;:::-;;;;8017:25:35;;;;2021:11391:71;;8065:25:35;8058:32;:::o;8013:540::-;-1:-1:-1;;8377:16:35;2021:11391:71;8411:18:35;;;;8449:20;:::o;4437:582:48:-;;4609:8;;-1:-1:-1;2021:11391:71;;5690:21:48;:17;;5815:105;;;;;;5686:301;5957:19;5710:1;5957:19;;5710:1;5957:19;4605:408;2021:11391:71;;4857:22:48;:49;;;4605:408;4853:119;;4985:17;;:::o;4853:119::-;2021:11391:71;4933:24:48;;4878:1;4933:24;2021:11391:71;4933:24:48;2021:11391:71;;4878:1:48;4933:24;4857:49;4883:18;;;:23;4857:49;", + "linkReferences": {}, + "immutableReferences": { + "48412": [ + { + "start": 2616, + "length": 32 + }, + { + "start": 4217, + "length": 32 + } + ] + } + }, + "methodIdentifiers": { + "UPGRADE_INTERFACE_VERSION()": "ad3cb1cc", + "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH()": "73016923", + "addWorkloadToPolicy(bytes32,string,string[])": "4f3a415a", + "computeStructHash(uint8,bytes32,uint256)": "7dec71a9", + "domainSeparator()": "f698da25", + "eip712Domain()": "84b0196e", + "getHashedTypeDataV4(bytes32)": "6931164e", + "getWorkloadMetadata(bytes32)": "abd45d21", + "initialize(address,address)": "485cc955", + "isAllowedPolicy(address)": "d2753561", + "nonces(address)": "7ecebe00", + "owner()": "8da5cb5b", + "permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)": "2dd8abfe", + "proxiableUUID()": "52d1902d", + "registry()": "7b103999", + "removeWorkloadFromPolicy(bytes32)": "5c40e542", + "renounceOwnership()": "715018a6", + "transferOwnership(address)": "f2fde38b", + "upgradeToAndCall(address,bytes)": "4f1ef286", + "verifyBlockBuilderProof(uint8,bytes32)": "b33d59da", + "workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))": "4d37fc7a" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ECDSAInvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"ECDSAInvalidSignatureLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"ECDSAInvalidSignatureS\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptyCommitHash\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"EmptySourceLocators\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expected\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"provided\",\"type\":\"uint256\"}],\"name\":\"InvalidNonce\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidRegistry\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UUPSUnauthorizedCallContext\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"slot\",\"type\":\"bytes32\"}],\"name\":\"UUPSUnsupportedProxiableUUID\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"}],\"name\":\"UnauthorizedBlockBuilder\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkloadAlreadyInPolicy\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WorkloadNotInPolicy\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"caller\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"workloadId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"blockContentHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"commitHash\",\"type\":\"string\"}],\"name\":\"BlockBuilderProofVerified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"EIP712DomainChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"registry\",\"type\":\"address\"}],\"name\":\"RegistrySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workloadId\",\"type\":\"bytes32\"}],\"name\":\"WorkloadAddedToPolicy\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"workloadId\",\"type\":\"bytes32\"}],\"name\":\"WorkloadRemovedFromPolicy\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"UPGRADE_INTERFACE_VERSION\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"WorkloadId\",\"name\":\"workloadId\",\"type\":\"bytes32\"},{\"internalType\":\"string\",\"name\":\"commitHash\",\"type\":\"string\"},{\"internalType\":\"string[]\",\"name\":\"sourceLocators\",\"type\":\"string[]\"}],\"name\":\"addWorkloadToPolicy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"blockContentHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"name\":\"computeStructHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"domainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip712Domain\",\"outputs\":[{\"internalType\":\"bytes1\",\"name\":\"fields\",\"type\":\"bytes1\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"verifyingContract\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"salt\",\"type\":\"bytes32\"},{\"internalType\":\"uint256[]\",\"name\":\"extensions\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"structHash\",\"type\":\"bytes32\"}],\"name\":\"getHashedTypeDataV4\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"WorkloadId\",\"name\":\"workloadId\",\"type\":\"bytes32\"}],\"name\":\"getWorkloadMetadata\",\"outputs\":[{\"components\":[{\"internalType\":\"string\",\"name\":\"commitHash\",\"type\":\"string\"},{\"internalType\":\"string[]\",\"name\":\"sourceLocators\",\"type\":\"string[]\"}],\"internalType\":\"struct IBlockBuilderPolicy.WorkloadMetadata\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_initialOwner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_registry\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"teeAddress\",\"type\":\"address\"}],\"name\":\"isAllowedPolicy\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"allowed\",\"type\":\"bool\"},{\"internalType\":\"WorkloadId\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"teeAddress\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"permitNonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"blockContentHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"eip712Sig\",\"type\":\"bytes\"}],\"name\":\"permitVerifyBlockBuilderProof\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"registry\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"WorkloadId\",\"name\":\"workloadId\",\"type\":\"bytes32\"}],\"name\":\"removeWorkloadFromPolicy\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"blockContentHash\",\"type\":\"bytes32\"}],\"name\":\"verifyBlockBuilderProof\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"isValid\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"rawQuote\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"bytes16\",\"name\":\"teeTcbSvn\",\"type\":\"bytes16\"},{\"internalType\":\"bytes\",\"name\":\"mrSeam\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"mrsignerSeam\",\"type\":\"bytes\"},{\"internalType\":\"bytes8\",\"name\":\"seamAttributes\",\"type\":\"bytes8\"},{\"internalType\":\"bytes8\",\"name\":\"tdAttributes\",\"type\":\"bytes8\"},{\"internalType\":\"bytes8\",\"name\":\"xFAM\",\"type\":\"bytes8\"},{\"internalType\":\"bytes\",\"name\":\"mrTd\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"mrConfigId\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"mrOwner\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"mrOwnerConfig\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rtMr0\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rtMr1\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rtMr2\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"rtMr3\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"reportData\",\"type\":\"bytes\"}],\"internalType\":\"struct TD10ReportBody\",\"name\":\"parsedReportBody\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"extendedRegistrationData\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"quoteHash\",\"type\":\"bytes32\"}],\"internalType\":\"struct IFlashtestationRegistry.RegisteredTEE\",\"name\":\"registration\",\"type\":\"tuple\"}],\"name\":\"workloadIdForTDRegistration\",\"outputs\":[{\"internalType\":\"WorkloadId\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ECDSAInvalidSignature()\":[{\"details\":\"The signature derives the `address(0)`.\"}],\"ECDSAInvalidSignatureLength(uint256)\":[{\"details\":\"The signature has an invalid length.\"}],\"ECDSAInvalidSignatureS(bytes32)\":[{\"details\":\"The signature has an S value that is in the upper half order.\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"UUPSUnauthorizedCallContext()\":[{\"details\":\"The call is from an unauthorized context.\"}],\"UUPSUnsupportedProxiableUUID(bytes32)\":[{\"details\":\"The storage `slot` is unsupported as a UUID.\"}]},\"events\":{\"BlockBuilderProofVerified(address,bytes32,uint8,bytes32,string)\":{\"params\":{\"blockContentHash\":\"The hash of the block content\",\"caller\":\"The address that called the verification function (TEE address)\",\"commitHash\":\"The git commit hash associated with the workload\",\"version\":\"The flashtestation protocol version used\",\"workloadId\":\"The workload identifier of the TEE\"}},\"EIP712DomainChanged()\":{\"details\":\"MAY be emitted to signal that the domain could have changed.\"},\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"RegistrySet(address)\":{\"params\":{\"registry\":\"The address of the registry\"}},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"},\"WorkloadAddedToPolicy(bytes32)\":{\"params\":{\"workloadId\":\"The workload identifier\"}},\"WorkloadRemovedFromPolicy(bytes32)\":{\"params\":{\"workloadId\":\"The workload identifier\"}}},\"kind\":\"dev\",\"methods\":{\"addWorkloadToPolicy(bytes32,string,string[])\":{\"details\":\"The commitHash solves the following problem; The only way for a smart contract like BlockBuilderPolicy to verify that a TEE (identified by its workloadId) is running a specific piece of code (for instance, op-rbuilder) is to reproducibly build that workload onchain. This is prohibitively expensive, so instead we rely on a permissioned multisig (the owner of this contract) to add a commit hash to the policy whenever it adds a new workloadId. We're already relying on the owner to verify that the workloadId is valid, so we can also assume the owner will not add a commit hash that is not associated with the workloadId. If the owner did act maliciously, this can easily be determined offchain by an honest actor building the TEE image from the given commit hash, deriving the image's workloadId, and then comparing it to the workloadId stored on the policy that is associated with the commit hash. If the workloadId is different, this can be used to prove that the owner acted maliciously. In the honest case, this Policy serves as a source of truth for which source code of build software (i.e. the commit hash) is used to build the TEE image identified by the workloadId.\",\"params\":{\"commitHash\":\"The 40-character hexadecimal commit hash of the git repository whose source code is used to build the TEE image identified by the workloadId\",\"sourceLocators\":\"An array of URIs pointing to the source code\",\"workloadId\":\"The workload identifier\"}},\"computeStructHash(uint8,bytes32,uint256)\":{\"params\":{\"blockContentHash\":\"The hash of the block content\",\"nonce\":\"The nonce to use for the EIP-712 signature\",\"version\":\"The version of the flashtestation's protocol\"},\"returns\":{\"_0\":\"The struct hash for the EIP-712 signature\"}},\"domainSeparator()\":{\"details\":\"This is useful for when both onchain and offchain users want to compute the domain separator for the EIP-712 signature, and then use it to verify the signature\",\"returns\":{\"_0\":\"The domain separator for the EIP-712 signature\"}},\"eip712Domain()\":{\"details\":\"returns the fields and values that describe the domain separator used by this contract for EIP-712 signature.\"},\"getHashedTypeDataV4(bytes32)\":{\"params\":{\"structHash\":\"The struct hash for the EIP-712 signature\"},\"returns\":{\"_0\":\"The digest for the EIP-712 signature\"}},\"getWorkloadMetadata(bytes32)\":{\"details\":\"This is only updateable by governance (i.e. the owner) of the Policy contract Adding and removing a workload is O(1)\",\"params\":{\"workloadId\":\"The workload identifier to query\"},\"returns\":{\"_0\":\"The metadata associated with the workload\"}},\"initialize(address,address)\":{\"params\":{\"_initialOwner\":\"The address of the initial owner of the contract\",\"_registry\":\"The address of the registry contract\"}},\"isAllowedPolicy(address)\":{\"params\":{\"teeAddress\":\"The TEE-controlled address\"},\"returns\":{\"_1\":\"The workloadId of the TEE that is using an approved workload in the policy, or 0 if the TEE is not using an approved workload in the policy\",\"allowed\":\"True if the TEE is using an approved workload in the policy\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)\":{\"details\":\"This function is useful if you do not want to deal with the operational difficulties of keeping your TEE-controlled addresses funded, but note that because of the larger number of function arguments, will cost more gas than the non-EIP-712 verifyBlockBuilderProof function\",\"params\":{\"blockContentHash\":\"The hash of the block content\",\"eip712Sig\":\"The EIP-712 signature of the verification message\",\"nonce\":\"The nonce to use for the EIP-712 signature\",\"version\":\"The version of the flashtestation's protocol used to generate the block builder proof\"}},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"removeWorkloadFromPolicy(bytes32)\":{\"params\":{\"workloadId\":\"The workload identifier\"}},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"},\"verifyBlockBuilderProof(uint8,bytes32)\":{\"details\":\"If you do not want to deal with the operational difficulties of keeping your TEE-controlled addresses funded, you can use the permitVerifyBlockBuilderProof function instead which costs more gas, but allows any EOA to submit a block builder proof on behalf of a TEE\",\"params\":{\"blockContentHash\":\"The hash of the block content\",\"version\":\"The version of the flashtestation's protocol used to generate the block builder proof\"}},\"workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))\":{\"details\":\"Think of the workload identifier as the version of the application for governance. The workloadId verifiably maps to a version of source code that builds the TEE VM image\",\"params\":{\"registration\":\"The registration data from a TEE device\"},\"returns\":{\"_0\":\"workloadId The computed workload identifier\"}}},\"stateVariables\":{\"__gap\":{\"details\":\"Storage gap to allow for future storage variable additions in upgradesThis reserves 46 storage slots (out of 50 total - 4 used for approvedWorkloads, registry, nonces, and cachedWorkloads)\"},\"approvedWorkloads\":{\"details\":\"This is only updateable by governance (i.e. the owner) of the Policy contract Adding and removing a workload is O(1). This means the critical `_cachedIsAllowedPolicy` function is O(1) since we can directly check if a workloadId exists in the mapping\"},\"cachedWorkloads\":{\"details\":\"Maps teeAddress to cached workload information for gas optimization\"}},\"title\":\"BlockBuilderPolicy\",\"version\":1},\"userdoc\":{\"errors\":{\"EmptyCommitHash()\":[{\"notice\":\"Emitted when the commit hash is empty\"}],\"EmptySourceLocators()\":[{\"notice\":\"Emitted when the source locators array is empty\"}],\"InvalidNonce(uint256,uint256)\":[{\"notice\":\"Emitted when the nonce is invalid\"}],\"InvalidRegistry()\":[{\"notice\":\"Emitted when the registry is the 0x0 address\"}],\"UnauthorizedBlockBuilder(address)\":[{\"notice\":\"Emitted when the address is not in the approvedWorkloads mapping\"}],\"WorkloadAlreadyInPolicy()\":[{\"notice\":\"Emitted when a workload to be added is already in the policy\"}],\"WorkloadNotInPolicy()\":[{\"notice\":\"Emitted when a workload to be removed is not in the policy\"}]},\"events\":{\"BlockBuilderProofVerified(address,bytes32,uint8,bytes32,string)\":{\"notice\":\"Emitted when a block builder proof is successfully verified\"},\"RegistrySet(address)\":{\"notice\":\"Emitted when the registry is set in the initializer\"},\"WorkloadAddedToPolicy(bytes32)\":{\"notice\":\"Emitted when a workload is added to the policy\"},\"WorkloadRemovedFromPolicy(bytes32)\":{\"notice\":\"Emitted when a workload is removed from the policy\"}},\"kind\":\"user\",\"methods\":{\"VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH()\":{\"notice\":\"EIP-712 Typehash, used in the permitVerifyBlockBuilderProof function\"},\"addWorkloadToPolicy(bytes32,string,string[])\":{\"notice\":\"Add a workload to a policy (governance only)Only the owner of this contract can add workloads to the policy and it is the responsibility of the owner to ensure that the workload is valid otherwise the address associated with this workload has full power to do anything who's authorization is based on this policy\"},\"computeStructHash(uint8,bytes32,uint256)\":{\"notice\":\"Computes the struct hash for the EIP-712 signature\"},\"domainSeparator()\":{\"notice\":\"Returns the domain separator for the EIP-712 signature\"},\"getHashedTypeDataV4(bytes32)\":{\"notice\":\"Computes the digest for the EIP-712 signature\"},\"getWorkloadMetadata(bytes32)\":{\"notice\":\"Mapping from workloadId to its metadata (commit hash and source locators)\"},\"initialize(address,address)\":{\"notice\":\"Initializer to set the FlashtestationRegistry contract which verifies TEE quotes and the initial owner of the contract\"},\"isAllowedPolicy(address)\":{\"notice\":\"Check if this TEE-controlled address has registered a valid TEE workload with the registry, and if the workload is approved under this policy\"},\"nonces(address)\":{\"notice\":\"Tracks nonces for EIP-712 signatures to prevent replay attacks\"},\"permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)\":{\"notice\":\"Verify a block builder proof with a Flashtestation Transaction using EIP-712 signaturesThis function allows any EOA to submit a block builder proof on behalf of a TEEThe TEE must sign a proper EIP-712-formatted message, and the signer must match a TEE-controlled address whose associated workload is approved under this policy\"},\"registry()\":{\"notice\":\"Address of the FlashtestationRegistry contract that verifies TEE quotes\"},\"removeWorkloadFromPolicy(bytes32)\":{\"notice\":\"Remove a workload from a policy (governance only)\"},\"verifyBlockBuilderProof(uint8,bytes32)\":{\"notice\":\"Verify a block builder proof with a Flashtestation TransactionThis function will only succeed if the caller is a registered TEE-controlled address from an attested TEE and the TEE is running an approved block builder workload (see `addWorkloadToPolicy`)The blockContentHash is a keccak256 hash of a subset of the block header, as specified by the version. See the [flashtestations spec](https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md) for more details\"},\"workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))\":{\"notice\":\"Application specific mapping of registration data to a workload identifier\"}},\"notice\":\"A reference implementation of a policy contract for the FlashtestationRegistryA Policy is a collection of related WorkloadIds. A Policy exists to specify which WorkloadIds are valid for a particular purpose, in this case for remote block building. It also exists to handle the problem that TEE workloads will need to change multiple times a year, either because of Intel DCAP Endorsement updates or updates to the TEE configuration (and thus its WorkloadId). Without Policies, consumer contracts that makes use of Flashtestations would need to be updated every time a TEE workload changes, which is a costly and error-prone process. Instead, consumer contracts need only check if a TEE address is allowed under any workload in a Policy, and the FlashtestationRegistry will handle the rest\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/BlockBuilderPolicy.sol\":\"BlockBuilderPolicy\"},\"evmVersion\":\"prague\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":44444444},\"remappings\":[\":@automata-network/on-chain-pccs/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":@sp1-contracts/=lib/automata-dcap-attestation/evm/lib/sp1-contracts/contracts/src/\",\":automata-dcap-attestation/=lib/automata-dcap-attestation/evm/\",\":automata-on-chain-pccs/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/\",\":ds-test/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\",\":risc0-ethereum/=lib/automata-dcap-attestation/evm/lib/risc0-ethereum/\",\":risc0/=lib/automata-dcap-attestation/evm/lib/risc0-ethereum/contracts/src/\",\":solady/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/\",\":solmate/=lib/solmate/\",\":sp1-contracts/=lib/automata-dcap-attestation/evm/lib/sp1-contracts/contracts/\"],\"viaIR\":true},\"sources\":{\"lib/automata-dcap-attestation/evm/contracts/types/CommonStruct.sol\":{\"keccak256\":\"0xc1968270cffd0b96268c9695d81647e707a6e1e7c1b851c1d6b1f9af6a88d4f2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d4e56c233fbb8e448961f5b847b92a8e725cc56ed9e0ad764d790f1cd4896245\",\"dweb:/ipfs/QmVoUKR62HT6DKaR3rNnNr5w1CmDSbrDriX41ycTAzKodz\"]},\"lib/automata-dcap-attestation/evm/contracts/types/Constants.sol\":{\"keccak256\":\"0xcfaac1552ee9277f6b0e4e38bdee7d2e07d1bdfe188c294c5c3f37f152b1d099\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://89a8a55be811abd8cc8af234d489c64a84f08023061ade4e67dbbaf11468cdc4\",\"dweb:/ipfs/QmR1TWqBcAEFbtteg2xjVG4VJXeFVWoAQidR3jyWuaGpUf\"]},\"lib/automata-dcap-attestation/evm/contracts/types/V4Structs.sol\":{\"keccak256\":\"0x39726e2cc0de02e8b34c0da2e6885fa9d18b7905970b9745e5310fb12cde9dd4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ddf5ab4f4188de35bb9c03137b207d56d69629283b9cb683eebdfafe7ff015ee\",\"dweb:/ipfs/QmTmYBmBEUbxMESjeaF6cSPMSK5P8hv16tDXTk78LXiKpz\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/DateTimeLib.sol\":{\"keccak256\":\"0x3945a985a34bc905beb149f7196f1dba021a60abc2f178ab2eb3a28ed4f94741\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://828a14ebcf4bd777c73ecbedd726819ac37c3b2e4fab7c8fe263f860db725f72\",\"dweb:/ipfs/QmNW32zDLCVjbGecmZvxAaSdmDudHQSsdrFfpMYX6baGAv\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/JSONParserLib.sol\":{\"keccak256\":\"0x8d4aeacf0459b28b40b63c9d331dc95cf7d751ca9961c83c89c1ad73b9b3cd81\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://42cc6de802e8c17668ae82518caa4c053e82f1884329d1d924fa7b9fccf5041f\",\"dweb:/ipfs/QmPGLfqWXDCjjcZ2VEG8kRwasGFxR4u62RpLLBuLqXy9wP\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/LibString.sol\":{\"keccak256\":\"0x74ec81acbea6db4afe149ab51e26961bcb801af42f7af98242be71b866066200\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://16bb49824fcfa9667aeed0eb515fdefda1016225085cf78ed852078c67168229\",\"dweb:/ipfs/QmZ59xrx5QLSx5N5CiTLrfwsPKR7kpK4RRpiEWSMEpvDzQ\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/helpers/FmspcTcbHelper.sol\":{\"keccak256\":\"0x3041391dfb9e2dcb6ed27e9b89445a9206c5348da3f2753785c22c832878f226\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://883bd1cb7d425828b8b9c92052f16fbcc893e6cdd175e3616a99b857b723a808\",\"dweb:/ipfs/QmZWqGm2D9AFrxzAsad7xkLSKuH1NQb7VEdFKwJbumj8EU\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/helpers/X509Helper.sol\":{\"keccak256\":\"0x4b4a56e80792b64604975e52d1aad2e075bd49c94b36814aa6461fbd69b6a03e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7ea019efcc3904401a694b5704d7cb219b20f7cc11a5d4268b0015628fb71713\",\"dweb:/ipfs/QmezJCskd9hVoERyUNk6Y9qK8oQxmen5HqasNgz8diyk6V\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/Asn1Decode.sol\":{\"keccak256\":\"0xc7a6f959ce3d98244e28ff140c0f3eb5fae6349d93e250a49cbf9fdd0e7ac8e6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7c0760856218edc38ead344bd2587118a2a72a23d0d410e57c7e48251f313e3c\",\"dweb:/ipfs/QmdNu9g5GWgEuS1ShgCqHckE1cSjfpt9LJb2t1A2CKAFfB\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/BytesUtils.sol\":{\"keccak256\":\"0x9a9028cd3bc73fb8ff9404907c980df141fd06351f9ea79b32754d05cae25f85\",\"license\":\"BSD 2-Clause License\",\"urls\":[\"bzz-raw://eaf5b855f3013a40e5d6b62fd9a7b7077a06b1750fabc53b8514ba4cf0006bed\",\"dweb:/ipfs/QmXKHL2zH51od64bYa8pifwEQF8UzccPBE2x7K5uvq4JeQ\"]},\"lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/DateTimeUtils.sol\":{\"keccak256\":\"0xee25670c66115185f0f0ab6070ade945faff8f75b502b70c2597a02868a02a4f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5c08c26dbd2e3196b3e16f7b8d2daa29c472997a845d5dc731f3bf17b7784d02\",\"dweb:/ipfs/QmUMwg7SvYQpgwcnDkGM9vnau2SqoYzwsGcB8CM2MLZueP\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6\",\"dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08\",\"dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0x574a7451e42724f7de29e2855c392a8a5020acd695169466a18459467d719d63\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5bc189f63b639ee173dd7b6fecc39baf7113bf161776aea22b34c57fdd1872ec\",\"dweb:/ipfs/QmZAf2VtjDLRULqjJkde6LNsxAg12tUqpPqgUQQZbAjgtZ\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9\",\"dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardTransientUpgradeable.sol\":{\"keccak256\":\"0x391a52a14dfcbe1a9ca16f1c052481de32242cf45714d92dab81be2a987e4bba\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://248b69f99e0452696ce5a2c90aac5602f496e2a697dacd5510d050f0dc833a3c\",\"dweb:/ipfs/QmcYkMiFQhTs2AW5fmcV5a3XQAGdQBUz1Y2NQD4RvBrNTM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol\":{\"keccak256\":\"0x89374b2a634f0a9c08f5891b6ecce0179bc2e0577819c787ed3268ca428c2459\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f13d2572e5bdd55e483dfac069aac47603644071616a41fce699e94368e38c13\",\"dweb:/ipfs/QmfKeyNT6vyb99vJQatPZ88UyZgXNmAiHUXSWnaR1TPE11\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c\",\"dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol\":{\"keccak256\":\"0xfb223a85dd0b2175cfbbaa325a744e2cd74ecd17c3df2b77b0722f991d2725ee\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://84bf1dea0589ec49c8d15d559cc6d86ee493048a89b2d4adb60fbe705a3d89ae\",\"dweb:/ipfs/Qmd56n556d529wk2pRMhYhm5nhMDhviwereodDikjs68w1\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422\",\"dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a\",\"dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d\",\"dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x6d0ae6e206645341fd122d278c2cb643dea260c190531f2f3f6a0426e77b00c0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://032d1201d839435be2c85b72e33206b3ea980c569d6ebf7fa57d811ab580a82f\",\"dweb:/ipfs/QmeqQjAtMvdZT2tG7zm39itcRJkuwu8AEReK6WRnLJ18DD\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Panic.sol\":{\"keccak256\":\"0xf7fe324703a64fc51702311dc51562d5cb1497734f074e4f483bfb6717572d7a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c6a5ff4f9fd8649b7ee20800b7fa387d3465bd77cf20c2d1068cd5c98e1ed57a\",\"dweb:/ipfs/QmVSaVJf9FXFhdYEYeCEfjMVHrxDh5qL4CGkxdMWpQCrqG\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Strings.sol\":{\"keccak256\":\"0xad148d59f05165f9217d0a9e1ac8f772abb02ea6aaad8a756315c532bf79f9f4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://15e3599867c2182f5831e9268b274b2ef2047825837df6b4d81c9e89254b093e\",\"dweb:/ipfs/QmZbL7XAYr5RmaNaooPgZRmcDXaudfsYQfYD9y5iAECvpS\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/TransientSlot.sol\":{\"keccak256\":\"0xac673fa1e374d9e6107504af363333e3e5f6344d2e83faf57d9bfd41d77cc946\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://5982478dbbb218e9dd5a6e83f5c0e8d1654ddf20178484b43ef21dd2246809de\",\"dweb:/ipfs/QmaB1hS68n2kG8vTbt7EPEzmrGhkUbfiFyykGGLsAr9X22\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol\":{\"keccak256\":\"0x69f54c02b7d81d505910ec198c11ed4c6a728418a868b906b4a0cf29946fda84\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8e25e4bdb7ae1f21d23bfee996e22736fc0ab44cfabedac82a757b1edc5623b9\",\"dweb:/ipfs/QmQdWQvB6JCP9ZMbzi8EvQ1PTETqkcTWrbcVurS7DKpa5n\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol\":{\"keccak256\":\"0x26670fef37d4adf55570ba78815eec5f31cb017e708f61886add4fc4da665631\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b16d45febff462bafd8a5669f904796a835baf607df58a8461916d3bf4f08c59\",\"dweb:/ipfs/QmU2eJFpjmT4vxeJWJyLeQb8Xht1kdB8Y6MKLDPFA9WPux\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0x1225214420c83ebcca88f2ae2b50f053aaa7df7bd684c3e878d334627f2edfc6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6c5fab4970634f9ab9a620983dc1c8a30153981a0b1a521666e269d0a11399d3\",\"dweb:/ipfs/QmVRnBC575MESGkEHndjujtR7qub2FzU9RWy9eKLp4hPZB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol\":{\"keccak256\":\"0x195533c86d0ef72bcc06456a4f66a9b941f38eb403739b00f21fd7c1abd1ae54\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b1d578337048cad08c1c03041cca5978eff5428aa130c781b271ad9e5566e1f8\",\"dweb:/ipfs/QmPFKL2r9CBsMwmUqqdcFPfHZB2qcs9g1HDrPxzWSxomvy\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0xb1970fac7b64e6c09611e6691791e848d5e3fe410fa5899e7df2e0afd77a99e3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://db5fbb3dddd8b7047465b62575d96231ba8a2774d37fb4737fbf23340fabbb03\",\"dweb:/ipfs/QmVUSvooZKEdEdap619tcJjTLcAuH6QBdZqAzWwnAXZAWJ\"]},\"src/BlockBuilderPolicy.sol\":{\"keccak256\":\"0x7c3d3ba04441108b91651c8b9c4dee65b2c3bb7e71fc0a55f1d3bd08ad8635cb\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://48d897893c99de682f4a44c3158f7bdf489c5d8557800906142cc300e2318806\",\"dweb:/ipfs/QmSjnpDXmwHcHei6dX6gQzMWUz3cZFF2JfKFH2yXnGQu3c\"]},\"src/FlashtestationRegistry.sol\":{\"keccak256\":\"0x7409a55e63172d86293b538b0dc0c55ff69c00b541f9b7a26709c8309cb194c2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e35b2e23522c9dcd0bfa45e5ea70d623f7a9fb61a353e00492f64a140a31eeab\",\"dweb:/ipfs/QmbaPWPRsrd42C79hgS4Nrx2mAba4rEzRqz8guFUAVM6js\"]},\"src/interfaces/IAttestation.sol\":{\"keccak256\":\"0x2171e821adc778a1d451e06df44f8efd56648425489f1032c52bb90dd2adedb8\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://887140a9dfb7384d1dd7ae58001cc2e36fba193493ca89376b91be41b788b79a\",\"dweb:/ipfs/QmVH8KdzFFDtLipA3WvojpRMc2uSmGjKnCAdgwkLHVq6Lf\"]},\"src/interfaces/IBlockBuilderPolicy.sol\":{\"keccak256\":\"0x54a5fd427b7e2b926ff3f6eb361211f2a84a2c52fa8c641471e53b6bbe859d97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4a8ca0f86c046fa571befee3dc0c8a839ec83d932d999c09fa3a701c8e842901\",\"dweb:/ipfs/QmP2pbZ1Xwhg2avcBxpsjL6vLGcS5vyoeqUDRq4xWNDKXw\"]},\"src/interfaces/IFlashtestationRegistry.sol\":{\"keccak256\":\"0xe21137526212ed0946cd655935b1d1efb214520047dfb84053ecc1bc00095db8\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://589ad601f708e5e8b0c66760ea18b08fb028c61919f5d5222cf8916f1c283365\",\"dweb:/ipfs/QmPGsofEZJigrAcSwawQ8EyUtyv15RmRZ36L2j97H1G6tK\"]},\"src/utils/QuoteParser.sol\":{\"keccak256\":\"0xf8462661ddcf40037053c2f1527e8f56b12211ee2311fd6c3b8979ed392df702\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://20a30b5ea00b46fff0d3624d5a4a04cad774457775ea362ed0d17cf1f454deeb\",\"dweb:/ipfs/QmbFB8yZdRj9j7id9ShHb7cxFFnKiaygLa6Abexa1b5tRG\"]}},\"version\":1}", + "metadata": { + "compiler": { + "version": "0.8.28+commit.7893614a" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "type": "error", + "name": "AddressEmptyCode" + }, + { + "inputs": [], + "type": "error", + "name": "ECDSAInvalidSignature" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "type": "error", + "name": "ECDSAInvalidSignatureLength" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "type": "error", + "name": "ECDSAInvalidSignatureS" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "type": "error", + "name": "ERC1967InvalidImplementation" + }, + { + "inputs": [], + "type": "error", + "name": "ERC1967NonPayable" + }, + { + "inputs": [], + "type": "error", + "name": "EmptyCommitHash" + }, + { + "inputs": [], + "type": "error", + "name": "EmptySourceLocators" + }, + { + "inputs": [], + "type": "error", + "name": "FailedCall" + }, + { + "inputs": [], + "type": "error", + "name": "InvalidInitialization" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "provided", + "type": "uint256" + } + ], + "type": "error", + "name": "InvalidNonce" + }, + { + "inputs": [], + "type": "error", + "name": "InvalidRegistry" + }, + { + "inputs": [], + "type": "error", + "name": "NotInitializing" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "type": "error", + "name": "OwnableInvalidOwner" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "type": "error", + "name": "OwnableUnauthorizedAccount" + }, + { + "inputs": [], + "type": "error", + "name": "UUPSUnauthorizedCallContext" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "type": "error", + "name": "UUPSUnsupportedProxiableUUID" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address" + } + ], + "type": "error", + "name": "UnauthorizedBlockBuilder" + }, + { + "inputs": [], + "type": "error", + "name": "WorkloadAlreadyInPolicy" + }, + { + "inputs": [], + "type": "error", + "name": "WorkloadNotInPolicy" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "caller", + "type": "address", + "indexed": false + }, + { + "internalType": "bytes32", + "name": "workloadId", + "type": "bytes32", + "indexed": false + }, + { + "internalType": "uint8", + "name": "version", + "type": "uint8", + "indexed": false + }, + { + "internalType": "bytes32", + "name": "blockContentHash", + "type": "bytes32", + "indexed": false + }, + { + "internalType": "string", + "name": "commitHash", + "type": "string", + "indexed": false + } + ], + "type": "event", + "name": "BlockBuilderProofVerified", + "anonymous": false + }, + { + "inputs": [], + "type": "event", + "name": "EIP712DomainChanged", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "version", + "type": "uint64", + "indexed": false + } + ], + "type": "event", + "name": "Initialized", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "previousOwner", + "type": "address", + "indexed": true + }, + { + "internalType": "address", + "name": "newOwner", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "OwnershipTransferred", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "registry", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "RegistrySet", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "Upgraded", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "workloadId", + "type": "bytes32", + "indexed": true + } + ], + "type": "event", + "name": "WorkloadAddedToPolicy", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "workloadId", + "type": "bytes32", + "indexed": true + } + ], + "type": "event", + "name": "WorkloadRemovedFromPolicy", + "anonymous": false + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "WorkloadId", + "name": "workloadId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "commitHash", + "type": "string" + }, + { + "internalType": "string[]", + "name": "sourceLocators", + "type": "string[]" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "addWorkloadToPolicy" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "blockContentHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function", + "name": "computeStructHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "domainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ] + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "structHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getHashedTypeDataV4", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "WorkloadId", + "name": "workloadId", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getWorkloadMetadata", + "outputs": [ + { + "internalType": "struct IBlockBuilderPolicy.WorkloadMetadata", + "name": "", + "type": "tuple", + "components": [ + { + "internalType": "string", + "name": "commitHash", + "type": "string" + }, + { + "internalType": "string[]", + "name": "sourceLocators", + "type": "string[]" + } + ] + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_initialOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "_registry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "initialize" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "teeAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "isAllowedPolicy", + "outputs": [ + { + "internalType": "bool", + "name": "allowed", + "type": "bool" + }, + { + "internalType": "WorkloadId", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "teeAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "permitNonce", + "type": "uint256" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ] + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "blockContentHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "eip712Sig", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "permitVerifyBlockBuilderProof" + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "registry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ] + }, + { + "inputs": [ + { + "internalType": "WorkloadId", + "name": "workloadId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "removeWorkloadFromPolicy" + }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "name": "renounceOwnership" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "transferOwnership" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function", + "name": "upgradeToAndCall" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "blockContentHash", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "verifyBlockBuilderProof" + }, + { + "inputs": [ + { + "internalType": "struct IFlashtestationRegistry.RegisteredTEE", + "name": "registration", + "type": "tuple", + "components": [ + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "rawQuote", + "type": "bytes" + }, + { + "internalType": "struct TD10ReportBody", + "name": "parsedReportBody", + "type": "tuple", + "components": [ + { + "internalType": "bytes16", + "name": "teeTcbSvn", + "type": "bytes16" + }, + { + "internalType": "bytes", + "name": "mrSeam", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "mrsignerSeam", + "type": "bytes" + }, + { + "internalType": "bytes8", + "name": "seamAttributes", + "type": "bytes8" + }, + { + "internalType": "bytes8", + "name": "tdAttributes", + "type": "bytes8" + }, + { + "internalType": "bytes8", + "name": "xFAM", + "type": "bytes8" + }, + { + "internalType": "bytes", + "name": "mrTd", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "mrConfigId", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "mrOwner", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "mrOwnerConfig", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "rtMr0", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "rtMr1", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "rtMr2", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "rtMr3", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "reportData", + "type": "bytes" + } + ] + }, + { + "internalType": "bytes", + "name": "extendedRegistrationData", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "quoteHash", + "type": "bytes32" + } + ] + } + ], + "stateMutability": "pure", + "type": "function", + "name": "workloadIdForTDRegistration", + "outputs": [ + { + "internalType": "WorkloadId", + "name": "", + "type": "bytes32" + } + ] + } + ], + "devdoc": { + "kind": "dev", + "methods": { + "addWorkloadToPolicy(bytes32,string,string[])": { + "details": "The commitHash solves the following problem; The only way for a smart contract like BlockBuilderPolicy to verify that a TEE (identified by its workloadId) is running a specific piece of code (for instance, op-rbuilder) is to reproducibly build that workload onchain. This is prohibitively expensive, so instead we rely on a permissioned multisig (the owner of this contract) to add a commit hash to the policy whenever it adds a new workloadId. We're already relying on the owner to verify that the workloadId is valid, so we can also assume the owner will not add a commit hash that is not associated with the workloadId. If the owner did act maliciously, this can easily be determined offchain by an honest actor building the TEE image from the given commit hash, deriving the image's workloadId, and then comparing it to the workloadId stored on the policy that is associated with the commit hash. If the workloadId is different, this can be used to prove that the owner acted maliciously. In the honest case, this Policy serves as a source of truth for which source code of build software (i.e. the commit hash) is used to build the TEE image identified by the workloadId.", + "params": { + "commitHash": "The 40-character hexadecimal commit hash of the git repository whose source code is used to build the TEE image identified by the workloadId", + "sourceLocators": "An array of URIs pointing to the source code", + "workloadId": "The workload identifier" + } + }, + "computeStructHash(uint8,bytes32,uint256)": { + "params": { + "blockContentHash": "The hash of the block content", + "nonce": "The nonce to use for the EIP-712 signature", + "version": "The version of the flashtestation's protocol" + }, + "returns": { + "_0": "The struct hash for the EIP-712 signature" + } + }, + "domainSeparator()": { + "details": "This is useful for when both onchain and offchain users want to compute the domain separator for the EIP-712 signature, and then use it to verify the signature", + "returns": { + "_0": "The domain separator for the EIP-712 signature" + } + }, + "eip712Domain()": { + "details": "returns the fields and values that describe the domain separator used by this contract for EIP-712 signature." + }, + "getHashedTypeDataV4(bytes32)": { + "params": { + "structHash": "The struct hash for the EIP-712 signature" + }, + "returns": { + "_0": "The digest for the EIP-712 signature" + } + }, + "getWorkloadMetadata(bytes32)": { + "details": "This is only updateable by governance (i.e. the owner) of the Policy contract Adding and removing a workload is O(1)", + "params": { + "workloadId": "The workload identifier to query" + }, + "returns": { + "_0": "The metadata associated with the workload" + } + }, + "initialize(address,address)": { + "params": { + "_initialOwner": "The address of the initial owner of the contract", + "_registry": "The address of the registry contract" + } + }, + "isAllowedPolicy(address)": { + "params": { + "teeAddress": "The TEE-controlled address" + }, + "returns": { + "_1": "The workloadId of the TEE that is using an approved workload in the policy, or 0 if the TEE is not using an approved workload in the policy", + "allowed": "True if the TEE is using an approved workload in the policy" + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)": { + "details": "This function is useful if you do not want to deal with the operational difficulties of keeping your TEE-controlled addresses funded, but note that because of the larger number of function arguments, will cost more gas than the non-EIP-712 verifyBlockBuilderProof function", + "params": { + "blockContentHash": "The hash of the block content", + "eip712Sig": "The EIP-712 signature of the verification message", + "nonce": "The nonce to use for the EIP-712 signature", + "version": "The version of the flashtestation's protocol used to generate the block builder proof" + } + }, + "proxiableUUID()": { + "details": "Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier." + }, + "removeWorkloadFromPolicy(bytes32)": { + "params": { + "workloadId": "The workload identifier" + } + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby disabling any functionality that is only available to the owner." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + }, + "upgradeToAndCall(address,bytes)": { + "custom:oz-upgrades-unsafe-allow-reachable": "delegatecall", + "details": "Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event." + }, + "verifyBlockBuilderProof(uint8,bytes32)": { + "details": "If you do not want to deal with the operational difficulties of keeping your TEE-controlled addresses funded, you can use the permitVerifyBlockBuilderProof function instead which costs more gas, but allows any EOA to submit a block builder proof on behalf of a TEE", + "params": { + "blockContentHash": "The hash of the block content", + "version": "The version of the flashtestation's protocol used to generate the block builder proof" + } + }, + "workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))": { + "details": "Think of the workload identifier as the version of the application for governance. The workloadId verifiably maps to a version of source code that builds the TEE VM image", + "params": { + "registration": "The registration data from a TEE device" + }, + "returns": { + "_0": "workloadId The computed workload identifier" + } + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH()": { + "notice": "EIP-712 Typehash, used in the permitVerifyBlockBuilderProof function" + }, + "addWorkloadToPolicy(bytes32,string,string[])": { + "notice": "Add a workload to a policy (governance only)Only the owner of this contract can add workloads to the policy and it is the responsibility of the owner to ensure that the workload is valid otherwise the address associated with this workload has full power to do anything who's authorization is based on this policy" + }, + "computeStructHash(uint8,bytes32,uint256)": { + "notice": "Computes the struct hash for the EIP-712 signature" + }, + "domainSeparator()": { + "notice": "Returns the domain separator for the EIP-712 signature" + }, + "getHashedTypeDataV4(bytes32)": { + "notice": "Computes the digest for the EIP-712 signature" + }, + "getWorkloadMetadata(bytes32)": { + "notice": "Mapping from workloadId to its metadata (commit hash and source locators)" + }, + "initialize(address,address)": { + "notice": "Initializer to set the FlashtestationRegistry contract which verifies TEE quotes and the initial owner of the contract" + }, + "isAllowedPolicy(address)": { + "notice": "Check if this TEE-controlled address has registered a valid TEE workload with the registry, and if the workload is approved under this policy" + }, + "nonces(address)": { + "notice": "Tracks nonces for EIP-712 signatures to prevent replay attacks" + }, + "permitVerifyBlockBuilderProof(uint8,bytes32,uint256,bytes)": { + "notice": "Verify a block builder proof with a Flashtestation Transaction using EIP-712 signaturesThis function allows any EOA to submit a block builder proof on behalf of a TEEThe TEE must sign a proper EIP-712-formatted message, and the signer must match a TEE-controlled address whose associated workload is approved under this policy" + }, + "registry()": { + "notice": "Address of the FlashtestationRegistry contract that verifies TEE quotes" + }, + "removeWorkloadFromPolicy(bytes32)": { + "notice": "Remove a workload from a policy (governance only)" + }, + "verifyBlockBuilderProof(uint8,bytes32)": { + "notice": "Verify a block builder proof with a Flashtestation TransactionThis function will only succeed if the caller is a registered TEE-controlled address from an attested TEE and the TEE is running an approved block builder workload (see `addWorkloadToPolicy`)The blockContentHash is a keccak256 hash of a subset of the block header, as specified by the version. See the [flashtestations spec](https://github.com/flashbots/rollup-boost/blob/main/specs/flashtestations.md) for more details" + }, + "workloadIdForTDRegistration((bool,bytes,(bytes16,bytes,bytes,bytes8,bytes8,bytes8,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes,bytes),bytes,bytes32))": { + "notice": "Application specific mapping of registration data to a workload identifier" + } + }, + "version": 1 + } + }, + "settings": { + "remappings": [ + "@automata-network/on-chain-pccs/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/", + "@sp1-contracts/=lib/automata-dcap-attestation/evm/lib/sp1-contracts/contracts/src/", + "automata-dcap-attestation/=lib/automata-dcap-attestation/evm/", + "automata-on-chain-pccs/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/", + "ds-test/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/forge-std/lib/ds-test/src/", + "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/", + "forge-std/=lib/forge-std/src/", + "halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/", + "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/", + "openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/", + "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/", + "risc0-ethereum/=lib/automata-dcap-attestation/evm/lib/risc0-ethereum/", + "risc0/=lib/automata-dcap-attestation/evm/lib/risc0-ethereum/contracts/src/", + "solady/=lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/", + "solmate/=lib/solmate/", + "sp1-contracts/=lib/automata-dcap-attestation/evm/lib/sp1-contracts/contracts/" + ], + "optimizer": { + "enabled": true, + "runs": 44444444 + }, + "metadata": { + "bytecodeHash": "none" + }, + "compilationTarget": { + "src/BlockBuilderPolicy.sol": "BlockBuilderPolicy" + }, + "evmVersion": "prague", + "libraries": {}, + "viaIR": true + }, + "sources": { + "lib/automata-dcap-attestation/evm/contracts/types/CommonStruct.sol": { + "keccak256": "0xc1968270cffd0b96268c9695d81647e707a6e1e7c1b851c1d6b1f9af6a88d4f2", + "urls": [ + "bzz-raw://d4e56c233fbb8e448961f5b847b92a8e725cc56ed9e0ad764d790f1cd4896245", + "dweb:/ipfs/QmVoUKR62HT6DKaR3rNnNr5w1CmDSbrDriX41ycTAzKodz" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/contracts/types/Constants.sol": { + "keccak256": "0xcfaac1552ee9277f6b0e4e38bdee7d2e07d1bdfe188c294c5c3f37f152b1d099", + "urls": [ + "bzz-raw://89a8a55be811abd8cc8af234d489c64a84f08023061ade4e67dbbaf11468cdc4", + "dweb:/ipfs/QmR1TWqBcAEFbtteg2xjVG4VJXeFVWoAQidR3jyWuaGpUf" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/contracts/types/V4Structs.sol": { + "keccak256": "0x39726e2cc0de02e8b34c0da2e6885fa9d18b7905970b9745e5310fb12cde9dd4", + "urls": [ + "bzz-raw://ddf5ab4f4188de35bb9c03137b207d56d69629283b9cb683eebdfafe7ff015ee", + "dweb:/ipfs/QmTmYBmBEUbxMESjeaF6cSPMSK5P8hv16tDXTk78LXiKpz" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/DateTimeLib.sol": { + "keccak256": "0x3945a985a34bc905beb149f7196f1dba021a60abc2f178ab2eb3a28ed4f94741", + "urls": [ + "bzz-raw://828a14ebcf4bd777c73ecbedd726819ac37c3b2e4fab7c8fe263f860db725f72", + "dweb:/ipfs/QmNW32zDLCVjbGecmZvxAaSdmDudHQSsdrFfpMYX6baGAv" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/JSONParserLib.sol": { + "keccak256": "0x8d4aeacf0459b28b40b63c9d331dc95cf7d751ca9961c83c89c1ad73b9b3cd81", + "urls": [ + "bzz-raw://42cc6de802e8c17668ae82518caa4c053e82f1884329d1d924fa7b9fccf5041f", + "dweb:/ipfs/QmPGLfqWXDCjjcZ2VEG8kRwasGFxR4u62RpLLBuLqXy9wP" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/lib/solady/src/utils/LibString.sol": { + "keccak256": "0x74ec81acbea6db4afe149ab51e26961bcb801af42f7af98242be71b866066200", + "urls": [ + "bzz-raw://16bb49824fcfa9667aeed0eb515fdefda1016225085cf78ed852078c67168229", + "dweb:/ipfs/QmZ59xrx5QLSx5N5CiTLrfwsPKR7kpK4RRpiEWSMEpvDzQ" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/helpers/FmspcTcbHelper.sol": { + "keccak256": "0x3041391dfb9e2dcb6ed27e9b89445a9206c5348da3f2753785c22c832878f226", + "urls": [ + "bzz-raw://883bd1cb7d425828b8b9c92052f16fbcc893e6cdd175e3616a99b857b723a808", + "dweb:/ipfs/QmZWqGm2D9AFrxzAsad7xkLSKuH1NQb7VEdFKwJbumj8EU" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/helpers/X509Helper.sol": { + "keccak256": "0x4b4a56e80792b64604975e52d1aad2e075bd49c94b36814aa6461fbd69b6a03e", + "urls": [ + "bzz-raw://7ea019efcc3904401a694b5704d7cb219b20f7cc11a5d4268b0015628fb71713", + "dweb:/ipfs/QmezJCskd9hVoERyUNk6Y9qK8oQxmen5HqasNgz8diyk6V" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/Asn1Decode.sol": { + "keccak256": "0xc7a6f959ce3d98244e28ff140c0f3eb5fae6349d93e250a49cbf9fdd0e7ac8e6", + "urls": [ + "bzz-raw://7c0760856218edc38ead344bd2587118a2a72a23d0d410e57c7e48251f313e3c", + "dweb:/ipfs/QmdNu9g5GWgEuS1ShgCqHckE1cSjfpt9LJb2t1A2CKAFfB" + ], + "license": "MIT" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/BytesUtils.sol": { + "keccak256": "0x9a9028cd3bc73fb8ff9404907c980df141fd06351f9ea79b32754d05cae25f85", + "urls": [ + "bzz-raw://eaf5b855f3013a40e5d6b62fd9a7b7077a06b1750fabc53b8514ba4cf0006bed", + "dweb:/ipfs/QmXKHL2zH51od64bYa8pifwEQF8UzccPBE2x7K5uvq4JeQ" + ], + "license": "BSD 2-Clause License" + }, + "lib/automata-dcap-attestation/evm/lib/automata-on-chain-pccs/src/utils/DateTimeUtils.sol": { + "keccak256": "0xee25670c66115185f0f0ab6070ade945faff8f75b502b70c2597a02868a02a4f", + "urls": [ + "bzz-raw://5c08c26dbd2e3196b3e16f7b8d2daa29c472997a845d5dc731f3bf17b7784d02", + "dweb:/ipfs/QmUMwg7SvYQpgwcnDkGM9vnau2SqoYzwsGcB8CM2MLZueP" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": { + "keccak256": "0xc163fcf9bb10138631a9ba5564df1fa25db9adff73bd9ee868a8ae1858fe093a", + "urls": [ + "bzz-raw://9706d43a0124053d9880f6e31a59f31bc0a6a3dc1acd66ce0a16e1111658c5f6", + "dweb:/ipfs/QmUFmfowzkRwGtDu36cXV9SPTBHJ3n7dG9xQiK5B28jTf2" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": { + "keccak256": "0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05", + "urls": [ + "bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08", + "dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": { + "keccak256": "0x574a7451e42724f7de29e2855c392a8a5020acd695169466a18459467d719d63", + "urls": [ + "bzz-raw://5bc189f63b639ee173dd7b6fecc39baf7113bf161776aea22b34c57fdd1872ec", + "dweb:/ipfs/QmZAf2VtjDLRULqjJkde6LNsxAg12tUqpPqgUQQZbAjgtZ" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol": { + "keccak256": "0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397", + "urls": [ + "bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9", + "dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardTransientUpgradeable.sol": { + "keccak256": "0x391a52a14dfcbe1a9ca16f1c052481de32242cf45714d92dab81be2a987e4bba", + "urls": [ + "bzz-raw://248b69f99e0452696ce5a2c90aac5602f496e2a697dacd5510d050f0dc833a3c", + "dweb:/ipfs/QmcYkMiFQhTs2AW5fmcV5a3XQAGdQBUz1Y2NQD4RvBrNTM" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol": { + "keccak256": "0x89374b2a634f0a9c08f5891b6ecce0179bc2e0577819c787ed3268ca428c2459", + "urls": [ + "bzz-raw://f13d2572e5bdd55e483dfac069aac47603644071616a41fce699e94368e38c13", + "dweb:/ipfs/QmfKeyNT6vyb99vJQatPZ88UyZgXNmAiHUXSWnaR1TPE11" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol": { + "keccak256": "0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5", + "urls": [ + "bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c", + "dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol": { + "keccak256": "0xfb223a85dd0b2175cfbbaa325a744e2cd74ecd17c3df2b77b0722f991d2725ee", + "urls": [ + "bzz-raw://84bf1dea0589ec49c8d15d559cc6d86ee493048a89b2d4adb60fbe705a3d89ae", + "dweb:/ipfs/Qmd56n556d529wk2pRMhYhm5nhMDhviwereodDikjs68w1" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol": { + "keccak256": "0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b", + "urls": [ + "bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422", + "dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol": { + "keccak256": "0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618", + "urls": [ + "bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a", + "dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol": { + "keccak256": "0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b", + "urls": [ + "bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d", + "dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol": { + "keccak256": "0x6d0ae6e206645341fd122d278c2cb643dea260c190531f2f3f6a0426e77b00c0", + "urls": [ + "bzz-raw://032d1201d839435be2c85b72e33206b3ea980c569d6ebf7fa57d811ab580a82f", + "dweb:/ipfs/QmeqQjAtMvdZT2tG7zm39itcRJkuwu8AEReK6WRnLJ18DD" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol": { + "keccak256": "0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123", + "urls": [ + "bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf", + "dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Panic.sol": { + "keccak256": "0xf7fe324703a64fc51702311dc51562d5cb1497734f074e4f483bfb6717572d7a", + "urls": [ + "bzz-raw://c6a5ff4f9fd8649b7ee20800b7fa387d3465bd77cf20c2d1068cd5c98e1ed57a", + "dweb:/ipfs/QmVSaVJf9FXFhdYEYeCEfjMVHrxDh5qL4CGkxdMWpQCrqG" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol": { + "keccak256": "0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97", + "urls": [ + "bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b", + "dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Strings.sol": { + "keccak256": "0xad148d59f05165f9217d0a9e1ac8f772abb02ea6aaad8a756315c532bf79f9f4", + "urls": [ + "bzz-raw://15e3599867c2182f5831e9268b274b2ef2047825837df6b4d81c9e89254b093e", + "dweb:/ipfs/QmZbL7XAYr5RmaNaooPgZRmcDXaudfsYQfYD9y5iAECvpS" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/TransientSlot.sol": { + "keccak256": "0xac673fa1e374d9e6107504af363333e3e5f6344d2e83faf57d9bfd41d77cc946", + "urls": [ + "bzz-raw://5982478dbbb218e9dd5a6e83f5c0e8d1654ddf20178484b43ef21dd2246809de", + "dweb:/ipfs/QmaB1hS68n2kG8vTbt7EPEzmrGhkUbfiFyykGGLsAr9X22" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol": { + "keccak256": "0x69f54c02b7d81d505910ec198c11ed4c6a728418a868b906b4a0cf29946fda84", + "urls": [ + "bzz-raw://8e25e4bdb7ae1f21d23bfee996e22736fc0ab44cfabedac82a757b1edc5623b9", + "dweb:/ipfs/QmQdWQvB6JCP9ZMbzi8EvQ1PTETqkcTWrbcVurS7DKpa5n" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol": { + "keccak256": "0x26670fef37d4adf55570ba78815eec5f31cb017e708f61886add4fc4da665631", + "urls": [ + "bzz-raw://b16d45febff462bafd8a5669f904796a835baf607df58a8461916d3bf4f08c59", + "dweb:/ipfs/QmU2eJFpjmT4vxeJWJyLeQb8Xht1kdB8Y6MKLDPFA9WPux" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/Math.sol": { + "keccak256": "0x1225214420c83ebcca88f2ae2b50f053aaa7df7bd684c3e878d334627f2edfc6", + "urls": [ + "bzz-raw://6c5fab4970634f9ab9a620983dc1c8a30153981a0b1a521666e269d0a11399d3", + "dweb:/ipfs/QmVRnBC575MESGkEHndjujtR7qub2FzU9RWy9eKLp4hPZB" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol": { + "keccak256": "0x195533c86d0ef72bcc06456a4f66a9b941f38eb403739b00f21fd7c1abd1ae54", + "urls": [ + "bzz-raw://b1d578337048cad08c1c03041cca5978eff5428aa130c781b271ad9e5566e1f8", + "dweb:/ipfs/QmPFKL2r9CBsMwmUqqdcFPfHZB2qcs9g1HDrPxzWSxomvy" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol": { + "keccak256": "0xb1970fac7b64e6c09611e6691791e848d5e3fe410fa5899e7df2e0afd77a99e3", + "urls": [ + "bzz-raw://db5fbb3dddd8b7047465b62575d96231ba8a2774d37fb4737fbf23340fabbb03", + "dweb:/ipfs/QmVUSvooZKEdEdap619tcJjTLcAuH6QBdZqAzWwnAXZAWJ" + ], + "license": "MIT" + }, + "src/BlockBuilderPolicy.sol": { + "keccak256": "0x7c3d3ba04441108b91651c8b9c4dee65b2c3bb7e71fc0a55f1d3bd08ad8635cb", + "urls": [ + "bzz-raw://48d897893c99de682f4a44c3158f7bdf489c5d8557800906142cc300e2318806", + "dweb:/ipfs/QmSjnpDXmwHcHei6dX6gQzMWUz3cZFF2JfKFH2yXnGQu3c" + ], + "license": "MIT" + }, + "src/FlashtestationRegistry.sol": { + "keccak256": "0x7409a55e63172d86293b538b0dc0c55ff69c00b541f9b7a26709c8309cb194c2", + "urls": [ + "bzz-raw://e35b2e23522c9dcd0bfa45e5ea70d623f7a9fb61a353e00492f64a140a31eeab", + "dweb:/ipfs/QmbaPWPRsrd42C79hgS4Nrx2mAba4rEzRqz8guFUAVM6js" + ], + "license": "MIT" + }, + "src/interfaces/IAttestation.sol": { + "keccak256": "0x2171e821adc778a1d451e06df44f8efd56648425489f1032c52bb90dd2adedb8", + "urls": [ + "bzz-raw://887140a9dfb7384d1dd7ae58001cc2e36fba193493ca89376b91be41b788b79a", + "dweb:/ipfs/QmVH8KdzFFDtLipA3WvojpRMc2uSmGjKnCAdgwkLHVq6Lf" + ], + "license": "MIT" + }, + "src/interfaces/IBlockBuilderPolicy.sol": { + "keccak256": "0x54a5fd427b7e2b926ff3f6eb361211f2a84a2c52fa8c641471e53b6bbe859d97", + "urls": [ + "bzz-raw://4a8ca0f86c046fa571befee3dc0c8a839ec83d932d999c09fa3a701c8e842901", + "dweb:/ipfs/QmP2pbZ1Xwhg2avcBxpsjL6vLGcS5vyoeqUDRq4xWNDKXw" + ], + "license": "MIT" + }, + "src/interfaces/IFlashtestationRegistry.sol": { + "keccak256": "0xe21137526212ed0946cd655935b1d1efb214520047dfb84053ecc1bc00095db8", + "urls": [ + "bzz-raw://589ad601f708e5e8b0c66760ea18b08fb028c61919f5d5222cf8916f1c283365", + "dweb:/ipfs/QmPGsofEZJigrAcSwawQ8EyUtyv15RmRZ36L2j97H1G6tK" + ], + "license": "MIT" + }, + "src/utils/QuoteParser.sol": { + "keccak256": "0xf8462661ddcf40037053c2f1527e8f56b12211ee2311fd6c3b8979ed392df702", + "urls": [ + "bzz-raw://20a30b5ea00b46fff0d3624d5a4a04cad774457775ea362ed0d17cf1f454deeb", + "dweb:/ipfs/QmbFB8yZdRj9j7id9ShHb7cxFFnKiaygLa6Abexa1b5tRG" + ], + "license": "MIT" + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 60637, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "approvedWorkloads", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_bytes32,t_struct(WorkloadMetadata)61869_storage)" + }, + { + "astId": 60640, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "registry", + "offset": 0, + "slot": "1", + "type": "t_address" + }, + { + "astId": 60645, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "nonces", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 60651, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "cachedWorkloads", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_struct(CachedWorkload)60611_storage)" + }, + { + "astId": 60656, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "__gap", + "offset": 0, + "slot": "4", + "type": "t_array(t_uint256)46_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_string_storage)dyn_storage": { + "encoding": "dynamic_array", + "label": "string[]", + "numberOfBytes": "32", + "base": "t_string_storage" + }, + "t_array(t_uint256)46_storage": { + "encoding": "inplace", + "label": "uint256[46]", + "numberOfBytes": "1472", + "base": "t_uint256" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_struct(CachedWorkload)60611_storage)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => struct CachedWorkload)", + "numberOfBytes": "32", + "value": "t_struct(CachedWorkload)60611_storage" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_bytes32,t_struct(WorkloadMetadata)61869_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata)", + "numberOfBytes": "32", + "value": "t_struct(WorkloadMetadata)61869_storage" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(CachedWorkload)60611_storage": { + "encoding": "inplace", + "label": "struct CachedWorkload", + "numberOfBytes": "64", + "members": [ + { + "astId": 60607, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "workloadId", + "offset": 0, + "slot": "0", + "type": "t_userDefinedValueType(WorkloadId)61861" + }, + { + "astId": 60610, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "quoteHash", + "offset": 0, + "slot": "1", + "type": "t_bytes32" + } + ] + }, + "t_struct(WorkloadMetadata)61869_storage": { + "encoding": "inplace", + "label": "struct IBlockBuilderPolicy.WorkloadMetadata", + "numberOfBytes": "64", + "members": [ + { + "astId": 61865, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "commitHash", + "offset": 0, + "slot": "0", + "type": "t_string_storage" + }, + { + "astId": 61868, + "contract": "src/BlockBuilderPolicy.sol:BlockBuilderPolicy", + "label": "sourceLocators", + "offset": 0, + "slot": "1", + "type": "t_array(t_string_storage)dyn_storage" + } + ] + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_userDefinedValueType(WorkloadId)61861": { + "encoding": "inplace", + "label": "WorkloadId", + "numberOfBytes": "32" + } + } + }, + "ast": { + "absolutePath": "src/BlockBuilderPolicy.sol", + "id": 61240, + "exportedSymbols": { + "BlockBuilderPolicy": [ + 61239 + ], + "CachedWorkload": [ + 60611 + ], + "ECDSA": [ + 52398 + ], + "EIP712Upgradeable": [ + 49049 + ], + "FlashtestationRegistry": [ + 61842 + ], + "IBlockBuilderPolicy": [ + 62049 + ], + "IFlashtestationRegistry": [ + 62267 + ], + "Initializable": [ + 48392 + ], + "OwnableUpgradeable": [ + 48124 + ], + "UUPSUpgradeable": [ + 48574 + ], + "WorkloadId": [ + 61861 + ] + }, + "nodeType": "SourceUnit", + "src": "32:13381:71", + "nodes": [ + { + "id": 60585, + "nodeType": "PragmaDirective", + "src": "32:23:71", + "nodes": [], + "literals": [ + "solidity", + "0.8", + ".28" + ] + }, + { + "id": 60587, + "nodeType": "ImportDirective", + "src": "57:96:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol", + "file": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 48393, + "symbolAliases": [ + { + "foreign": { + "id": 60586, + "name": "Initializable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48392, + "src": "65:13:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60589, + "nodeType": "ImportDirective", + "src": "154:100:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol", + "file": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 48575, + "symbolAliases": [ + { + "foreign": { + "id": 60588, + "name": "UUPSUpgradeable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48574, + "src": "162:15:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60591, + "nodeType": "ImportDirective", + "src": "255:101:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol", + "file": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 48125, + "symbolAliases": [ + { + "foreign": { + "id": 60590, + "name": "OwnableUpgradeable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48124, + "src": "263:18:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60593, + "nodeType": "ImportDirective", + "src": "357:111:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol", + "file": "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 49050, + "symbolAliases": [ + { + "foreign": { + "id": 60592, + "name": "EIP712Upgradeable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 49049, + "src": "365:17:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60595, + "nodeType": "ImportDirective", + "src": "469:75:71", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol", + "file": "@openzeppelin/contracts/utils/cryptography/ECDSA.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 52399, + "symbolAliases": [ + { + "foreign": { + "id": 60594, + "name": "ECDSA", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 52398, + "src": "477:5:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60597, + "nodeType": "ImportDirective", + "src": "545:68:71", + "nodes": [], + "absolutePath": "src/FlashtestationRegistry.sol", + "file": "./FlashtestationRegistry.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 61843, + "symbolAliases": [ + { + "foreign": { + "id": 60596, + "name": "FlashtestationRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61842, + "src": "553:22:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60599, + "nodeType": "ImportDirective", + "src": "614:81:71", + "nodes": [], + "absolutePath": "src/interfaces/IFlashtestationRegistry.sol", + "file": "./interfaces/IFlashtestationRegistry.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 62268, + "symbolAliases": [ + { + "foreign": { + "id": 60598, + "name": "IFlashtestationRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 62267, + "src": "622:23:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60602, + "nodeType": "ImportDirective", + "src": "696:85:71", + "nodes": [], + "absolutePath": "src/interfaces/IBlockBuilderPolicy.sol", + "file": "./interfaces/IBlockBuilderPolicy.sol", + "nameLocation": "-1:-1:-1", + "scope": 61240, + "sourceUnit": 62050, + "symbolAliases": [ + { + "foreign": { + "id": 60600, + "name": "IBlockBuilderPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 62049, + "src": "704:19:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + }, + { + "foreign": { + "id": 60601, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "725:10:71", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 60611, + "nodeType": "StructDefinition", + "src": "944:208:71", + "nodes": [], + "canonicalName": "CachedWorkload", + "documentation": { + "id": 60603, + "nodeType": "StructuredDocumentation", + "src": "783:160:71", + "text": " @notice Cached workload information for gas optimization\n @dev Stores computed workloadId and associated quoteHash to avoid expensive recomputation" + }, + "members": [ + { + "constant": false, + "id": 60607, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "1032:10:71", + "nodeType": "VariableDeclaration", + "scope": 60611, + "src": "1021:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60606, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60605, + "name": "WorkloadId", + "nameLocations": [ + "1021:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "1021:10:71" + }, + "referencedDeclaration": 61861, + "src": "1021:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60610, + "mutability": "mutable", + "name": "quoteHash", + "nameLocation": "1140:9:71", + "nodeType": "VariableDeclaration", + "scope": 60611, + "src": "1132:17:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60609, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1132:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "name": "CachedWorkload", + "nameLocation": "951:14:71", + "scope": 61240, + "visibility": "public" + }, + { + "id": 61239, + "nodeType": "ContractDefinition", + "src": "2021:11391:71", + "nodes": [ + { + "id": 60625, + "nodeType": "UsingForDirective", + "src": "2169:24:71", + "nodes": [], + "global": false, + "libraryName": { + "id": 60623, + "name": "ECDSA", + "nameLocations": [ + "2175:5:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 52398, + "src": "2175:5:71" + }, + "typeName": { + "id": 60624, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2185:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + }, + { + "id": 60631, + "nodeType": "VariableDeclaration", + "src": "2291:160:71", + "nodes": [], + "baseFunctions": [ + 62048 + ], + "constant": true, + "documentation": { + "id": 60626, + "nodeType": "StructuredDocumentation", + "src": "2251:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "73016923", + "mutability": "constant", + "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", + "nameLocation": "2315:35:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60627, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2291:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": { + "arguments": [ + { + "hexValue": "566572696679426c6f636b4275696c64657250726f6f662875696e74382076657273696f6e2c6279746573333220626c6f636b436f6e74656e74486173682c75696e74323536206e6f6e636529", + "id": 60629, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2371:79:71", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc", + "typeString": "literal_string \"VerifyBlockBuilderProof(uint8 version,bytes32 blockContentHash,uint256 nonce)\"" + }, + "value": "VerifyBlockBuilderProof(uint8 version,bytes32 blockContentHash,uint256 nonce)" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_93b3c192de39a93da71b94fb9fadb8e913f752a2e9ea950a33266a81fcbf2ffc", + "typeString": "literal_string \"VerifyBlockBuilderProof(uint8 version,bytes32 blockContentHash,uint256 nonce)\"" + } + ], + "id": 60628, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "2361:9:71", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 60630, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "2361:90:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "public" + }, + { + "id": 60637, + "nodeType": "VariableDeclaration", + "src": "2887:73:71", + "nodes": [], + "constant": false, + "documentation": { + "id": 60632, + "nodeType": "StructuredDocumentation", + "src": "2510:372:71", + "text": "@notice Mapping from workloadId to its metadata (commit hash and source locators)\n @dev This is only updateable by governance (i.e. the owner) of the Policy contract\n Adding and removing a workload is O(1).\n This means the critical `_cachedIsAllowedPolicy` function is O(1) since we can directly check if a workloadId exists\n in the mapping" + }, + "mutability": "mutable", + "name": "approvedWorkloads", + "nameLocation": "2943:17:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata)" + }, + "typeName": { + "id": 60636, + "keyName": "workloadId", + "keyNameLocation": "2903:10:71", + "keyType": { + "id": 60633, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2895:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "Mapping", + "src": "2887:47:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata)" + }, + "valueName": "", + "valueNameLocation": "-1:-1:-1", + "valueType": { + "id": 60635, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60634, + "name": "WorkloadMetadata", + "nameLocations": [ + "2917:16:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61869, + "src": "2917:16:71" + }, + "referencedDeclaration": 61869, + "src": "2917:16:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage_ptr", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata" + } + } + }, + "visibility": "private" + }, + { + "id": 60640, + "nodeType": "VariableDeclaration", + "src": "3007:23:71", + "nodes": [], + "baseFunctions": [ + 62034 + ], + "constant": false, + "documentation": { + "id": 60638, + "nodeType": "StructuredDocumentation", + "src": "2967:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "7b103999", + "mutability": "mutable", + "name": "registry", + "nameLocation": "3022:8:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60639, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3007:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "public" + }, + { + "id": 60645, + "nodeType": "VariableDeclaration", + "src": "3077:64:71", + "nodes": [], + "baseFunctions": [ + 62042 + ], + "constant": false, + "documentation": { + "id": 60641, + "nodeType": "StructuredDocumentation", + "src": "3037:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "7ecebe00", + "mutability": "mutable", + "name": "nonces", + "nameLocation": "3135:6:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "typeName": { + "id": 60644, + "keyName": "teeAddress", + "keyNameLocation": "3093:10:71", + "keyType": { + "id": 60642, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3085:7:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "3077:50:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "valueName": "permitNonce", + "valueNameLocation": "3115:11:71", + "valueType": { + "id": 60643, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3107:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + "visibility": "public" + }, + { + "id": 60651, + "nodeType": "VariableDeclaration", + "src": "3308:69:71", + "nodes": [], + "constant": false, + "documentation": { + "id": 60646, + "nodeType": "StructuredDocumentation", + "src": "3148:155:71", + "text": "@notice Cache of computed workloadIds to avoid expensive recomputation\n @dev Maps teeAddress to cached workload information for gas optimization" + }, + "mutability": "mutable", + "name": "cachedWorkloads", + "nameLocation": "3362:15:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_CachedWorkload_$60611_storage_$", + "typeString": "mapping(address => struct CachedWorkload)" + }, + "typeName": { + "id": 60650, + "keyName": "teeAddress", + "keyNameLocation": "3324:10:71", + "keyType": { + "id": 60647, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3316:7:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "3308:45:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_CachedWorkload_$60611_storage_$", + "typeString": "mapping(address => struct CachedWorkload)" + }, + "valueName": "", + "valueNameLocation": "-1:-1:-1", + "valueType": { + "id": 60649, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60648, + "name": "CachedWorkload", + "nameLocations": [ + "3338:14:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 60611, + "src": "3338:14:71" + }, + "referencedDeclaration": 60611, + "src": "3338:14:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage_ptr", + "typeString": "struct CachedWorkload" + } + } + }, + "visibility": "private" + }, + { + "id": 60656, + "nodeType": "VariableDeclaration", + "src": "3600:17:71", + "nodes": [], + "constant": false, + "documentation": { + "id": 60652, + "nodeType": "StructuredDocumentation", + "src": "3384:211:71", + "text": "@dev Storage gap to allow for future storage variable additions in upgrades\n @dev This reserves 46 storage slots (out of 50 total - 4 used for approvedWorkloads, registry, nonces, and cachedWorkloads)" + }, + "mutability": "mutable", + "name": "__gap", + "nameLocation": "3612:5:71", + "scope": 61239, + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$46_storage", + "typeString": "uint256[46]" + }, + "typeName": { + "baseType": { + "id": 60653, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3600:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 60655, + "length": { + "hexValue": "3436", + "id": 60654, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3608:2:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_46_by_1", + "typeString": "int_const 46" + }, + "value": "46" + }, + "nodeType": "ArrayTypeName", + "src": "3600:11:71", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$46_storage_ptr", + "typeString": "uint256[46]" + } + }, + "visibility": "internal" + }, + { + "id": 60699, + "nodeType": "FunctionDefinition", + "src": "3664:351:71", + "nodes": [], + "body": { + "id": 60698, + "nodeType": "Block", + "src": "3756:259:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "id": 60668, + "name": "_initialOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60659, + "src": "3781:13:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60667, + "name": "__Ownable_init", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 47984, + "src": "3766:14:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 60669, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3766:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60670, + "nodeType": "ExpressionStatement", + "src": "3766:29:71" + }, + { + "expression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 60671, + "name": "__UUPSUpgradeable_init", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48446, + "src": "3805:22:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$__$returns$__$", + "typeString": "function ()" + } + }, + "id": 60672, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3805:24:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60673, + "nodeType": "ExpressionStatement", + "src": "3805:24:71" + }, + { + "expression": { + "arguments": [ + { + "hexValue": "426c6f636b4275696c646572506f6c696379", + "id": 60675, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3853:20:71", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_cbeb49ca47be8803ce2a0d04c03e1532e2fe91bbb42a2a5d9922693dc31e4646", + "typeString": "literal_string \"BlockBuilderPolicy\"" + }, + "value": "BlockBuilderPolicy" + }, + { + "hexValue": "31", + "id": 60676, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3875:3:71", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", + "typeString": "literal_string \"1\"" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_cbeb49ca47be8803ce2a0d04c03e1532e2fe91bbb42a2a5d9922693dc31e4646", + "typeString": "literal_string \"BlockBuilderPolicy\"" + }, + { + "typeIdentifier": "t_stringliteral_c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", + "typeString": "literal_string \"1\"" + } + ], + "id": 60674, + "name": "__EIP712_init", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48762, + "src": "3839:13:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (string memory,string memory)" + } + }, + "id": 60677, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3839:40:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60678, + "nodeType": "ExpressionStatement", + "src": "3839:40:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 60685, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 60680, + "name": "_registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60661, + "src": "3897:9:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "arguments": [ + { + "hexValue": "30", + "id": 60683, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3918:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 60682, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3910:7:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 60681, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3910:7:71", + "typeDescriptions": {} + } + }, + "id": 60684, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3910:10:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "3897:23:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 60686, + "name": "InvalidRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61900, + "src": "3922:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 60687, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3922:17:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 60679, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "3889:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 60688, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3889:51:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60689, + "nodeType": "ExpressionStatement", + "src": "3889:51:71" + }, + { + "expression": { + "id": 60692, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "id": 60690, + "name": "registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60640, + "src": "3951:8:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 60691, + "name": "_registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60661, + "src": "3962:9:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "3951:20:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 60693, + "nodeType": "ExpressionStatement", + "src": "3951:20:71" + }, + { + "eventCall": { + "arguments": [ + { + "id": 60695, + "name": "_registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60661, + "src": "3998:9:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60694, + "name": "RegistrySet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61884, + "src": "3986:11:71", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 60696, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "3986:22:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60697, + "nodeType": "EmitStatement", + "src": "3981:27:71" + } + ] + }, + "baseFunctions": [ + 61932 + ], + "documentation": { + "id": 60657, + "nodeType": "StructuredDocumentation", + "src": "3624:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "485cc955", + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 60665, + "kind": "modifierInvocation", + "modifierName": { + "id": 60664, + "name": "initializer", + "nameLocations": [ + "3744:11:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48232, + "src": "3744:11:71" + }, + "nodeType": "ModifierInvocation", + "src": "3744:11:71" + } + ], + "name": "initialize", + "nameLocation": "3673:10:71", + "overrides": { + "id": 60663, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "3735:8:71" + }, + "parameters": { + "id": 60662, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60659, + "mutability": "mutable", + "name": "_initialOwner", + "nameLocation": "3692:13:71", + "nodeType": "VariableDeclaration", + "scope": 60699, + "src": "3684:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60658, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3684:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60661, + "mutability": "mutable", + "name": "_registry", + "nameLocation": "3715:9:71", + "nodeType": "VariableDeclaration", + "scope": 60699, + "src": "3707:17:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60660, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3707:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "3683:42:71" + }, + "returnParameters": { + "id": 60666, + "nodeType": "ParameterList", + "parameters": [], + "src": "3756:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 60709, + "nodeType": "FunctionDefinition", + "src": "4150:84:71", + "nodes": [], + "body": { + "id": 60708, + "nodeType": "Block", + "src": "4232:2:71", + "nodes": [], + "statements": [] + }, + "baseFunctions": [ + 48528 + ], + "documentation": { + "id": 60700, + "nodeType": "StructuredDocumentation", + "src": "4021:124:71", + "text": "@notice Restricts upgrades to owner only\n @param newImplementation The address of the new implementation contract" + }, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 60706, + "kind": "modifierInvocation", + "modifierName": { + "id": 60705, + "name": "onlyOwner", + "nameLocations": [ + "4222:9:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48019, + "src": "4222:9:71" + }, + "nodeType": "ModifierInvocation", + "src": "4222:9:71" + } + ], + "name": "_authorizeUpgrade", + "nameLocation": "4159:17:71", + "overrides": { + "id": 60704, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "4213:8:71" + }, + "parameters": { + "id": 60703, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60702, + "mutability": "mutable", + "name": "newImplementation", + "nameLocation": "4185:17:71", + "nodeType": "VariableDeclaration", + "scope": 60709, + "src": "4177:25:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60701, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4177:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "4176:27:71" + }, + "returnParameters": { + "id": 60707, + "nodeType": "ParameterList", + "parameters": [], + "src": "4232:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "internal" + }, + { + "id": 60726, + "nodeType": "FunctionDefinition", + "src": "4280:172:71", + "nodes": [], + "body": { + "id": 60725, + "nodeType": "Block", + "src": "4372:80:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "expression": { + "id": 60719, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "4407:3:71", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 60720, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "4411:6:71", + "memberName": "sender", + "nodeType": "MemberAccess", + "src": "4407:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "id": 60721, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60712, + "src": "4419:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 60722, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60714, + "src": "4428:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 60718, + "name": "_verifyBlockBuilderProof", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60833, + "src": "4382:24:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint8_$_t_bytes32_$returns$__$", + "typeString": "function (address,uint8,bytes32)" + } + }, + "id": 60723, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4382:63:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60724, + "nodeType": "ExpressionStatement", + "src": "4382:63:71" + } + ] + }, + "baseFunctions": [ + 61940 + ], + "documentation": { + "id": 60710, + "nodeType": "StructuredDocumentation", + "src": "4240:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "b33d59da", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "verifyBlockBuilderProof", + "nameLocation": "4289:23:71", + "overrides": { + "id": 60716, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "4363:8:71" + }, + "parameters": { + "id": 60715, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60712, + "mutability": "mutable", + "name": "version", + "nameLocation": "4319:7:71", + "nodeType": "VariableDeclaration", + "scope": 60726, + "src": "4313:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 60711, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "4313:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60714, + "mutability": "mutable", + "name": "blockContentHash", + "nameLocation": "4336:16:71", + "nodeType": "VariableDeclaration", + "scope": 60726, + "src": "4328:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60713, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "4328:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "4312:41:71" + }, + "returnParameters": { + "id": 60717, + "nodeType": "ParameterList", + "parameters": [], + "src": "4372:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 60784, + "nodeType": "FunctionDefinition", + "src": "4498:725:71", + "nodes": [], + "body": { + "id": 60783, + "nodeType": "Block", + "src": "4675:548:71", + "nodes": [], + "statements": [ + { + "assignments": [ + 60740 + ], + "declarations": [ + { + "constant": false, + "id": 60740, + "mutability": "mutable", + "name": "digest", + "nameLocation": "4743:6:71", + "nodeType": "VariableDeclaration", + "scope": 60783, + "src": "4735:14:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60739, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "4735:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 60748, + "initialValue": { + "arguments": [ + { + "arguments": [ + { + "id": 60743, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60729, + "src": "4790:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 60744, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60731, + "src": "4799:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 60745, + "name": "nonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60733, + "src": "4817:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 60742, + "name": "computeStructHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61228, + "src": "4772:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint8_$_t_bytes32_$_t_uint256_$returns$_t_bytes32_$", + "typeString": "function (uint8,bytes32,uint256) pure returns (bytes32)" + } + }, + "id": 60746, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4772:51:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 60741, + "name": "getHashedTypeDataV4", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61205, + "src": "4752:19:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_bytes32_$returns$_t_bytes32_$", + "typeString": "function (bytes32) view returns (bytes32)" + } + }, + "id": 60747, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4752:72:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4735:89:71" + }, + { + "assignments": [ + 60750 + ], + "declarations": [ + { + "constant": false, + "id": 60750, + "mutability": "mutable", + "name": "teeAddress", + "nameLocation": "4842:10:71", + "nodeType": "VariableDeclaration", + "scope": 60783, + "src": "4834:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60749, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4834:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "id": 60755, + "initialValue": { + "arguments": [ + { + "id": 60753, + "name": "eip712Sig", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60735, + "src": "4870:9:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_calldata_ptr", + "typeString": "bytes calldata" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_calldata_ptr", + "typeString": "bytes calldata" + } + ], + "expression": { + "id": 60751, + "name": "digest", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60740, + "src": "4855:6:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "id": 60752, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "4862:7:71", + "memberName": "recover", + "nodeType": "MemberAccess", + "referencedDeclaration": 52154, + "src": "4855:14:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_bytes32_$_t_bytes_memory_ptr_$returns$_t_address_$attached_to$_t_bytes32_$", + "typeString": "function (bytes32,bytes memory) pure returns (address)" + } + }, + "id": 60754, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4855:25:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4834:46:71" + }, + { + "assignments": [ + 60757 + ], + "declarations": [ + { + "constant": false, + "id": 60757, + "mutability": "mutable", + "name": "expectedNonce", + "nameLocation": "4927:13:71", + "nodeType": "VariableDeclaration", + "scope": 60783, + "src": "4919:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 60756, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4919:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "id": 60761, + "initialValue": { + "baseExpression": { + "id": 60758, + "name": "nonces", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60645, + "src": "4943:6:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 60760, + "indexExpression": { + "id": 60759, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60750, + "src": "4950:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "4943:18:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4919:42:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 60765, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 60763, + "name": "nonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60733, + "src": "4979:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "id": 60764, + "name": "expectedNonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60757, + "src": "4988:13:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "4979:22:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [ + { + "id": 60767, + "name": "expectedNonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60757, + "src": "5016:13:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "id": 60768, + "name": "nonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60733, + "src": "5031:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 60766, + "name": "InvalidNonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61918, + "src": "5003:12:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$_t_uint256_$_t_uint256_$returns$_t_error_$", + "typeString": "function (uint256,uint256) pure returns (error)" + } + }, + "id": 60769, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5003:34:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 60762, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "4971:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 60770, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "4971:67:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60771, + "nodeType": "ExpressionStatement", + "src": "4971:67:71" + }, + { + "expression": { + "id": 60775, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "5080:20:71", + "subExpression": { + "baseExpression": { + "id": 60772, + "name": "nonces", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60645, + "src": "5080:6:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 60774, + "indexExpression": { + "id": 60773, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60750, + "src": "5087:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "5080:18:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 60776, + "nodeType": "ExpressionStatement", + "src": "5080:20:71" + }, + { + "expression": { + "arguments": [ + { + "id": 60778, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60750, + "src": "5178:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "id": 60779, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60729, + "src": "5190:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 60780, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60731, + "src": "5199:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 60777, + "name": "_verifyBlockBuilderProof", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60833, + "src": "5153:24:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint8_$_t_bytes32_$returns$__$", + "typeString": "function (address,uint8,bytes32)" + } + }, + "id": 60781, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5153:63:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60782, + "nodeType": "ExpressionStatement", + "src": "5153:63:71" + } + ] + }, + "baseFunctions": [ + 61952 + ], + "documentation": { + "id": 60727, + "nodeType": "StructuredDocumentation", + "src": "4458:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "2dd8abfe", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "permitVerifyBlockBuilderProof", + "nameLocation": "4507:29:71", + "overrides": { + "id": 60737, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "4666:8:71" + }, + "parameters": { + "id": 60736, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60729, + "mutability": "mutable", + "name": "version", + "nameLocation": "4552:7:71", + "nodeType": "VariableDeclaration", + "scope": 60784, + "src": "4546:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 60728, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "4546:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60731, + "mutability": "mutable", + "name": "blockContentHash", + "nameLocation": "4577:16:71", + "nodeType": "VariableDeclaration", + "scope": 60784, + "src": "4569:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60730, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "4569:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60733, + "mutability": "mutable", + "name": "nonce", + "nameLocation": "4611:5:71", + "nodeType": "VariableDeclaration", + "scope": 60784, + "src": "4603:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 60732, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4603:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60735, + "mutability": "mutable", + "name": "eip712Sig", + "nameLocation": "4641:9:71", + "nodeType": "VariableDeclaration", + "scope": 60784, + "src": "4626:24:71", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_bytes_calldata_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 60734, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "4626:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "visibility": "internal" + } + ], + "src": "4536:120:71" + }, + "returnParameters": { + "id": 60738, + "nodeType": "ParameterList", + "parameters": [], + "src": "4675:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 60833, + "nodeType": "FunctionDefinition", + "src": "5648:1056:71", + "nodes": [], + "body": { + "id": 60832, + "nodeType": "Block", + "src": "5752:952:71", + "nodes": [], + "statements": [ + { + "assignments": [ + 60795, + 60798 + ], + "declarations": [ + { + "constant": false, + "id": 60795, + "mutability": "mutable", + "name": "allowed", + "nameLocation": "5866:7:71", + "nodeType": "VariableDeclaration", + "scope": 60832, + "src": "5861:12:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60794, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "5861:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60798, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "5886:10:71", + "nodeType": "VariableDeclaration", + "scope": 60832, + "src": "5875:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60797, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60796, + "name": "WorkloadId", + "nameLocations": [ + "5875:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "5875:10:71" + }, + "referencedDeclaration": 61861, + "src": "5875:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "id": 60802, + "initialValue": { + "arguments": [ + { + "id": 60800, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60787, + "src": "5923:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60799, + "name": "_cachedIsAllowedPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61013, + "src": "5900:22:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (address) returns (bool,WorkloadId)" + } + }, + "id": 60801, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5900:34:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5860:74:71" + }, + { + "expression": { + "arguments": [ + { + "id": 60804, + "name": "allowed", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60795, + "src": "5952:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [ + { + "id": 60806, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60787, + "src": "5986:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60805, + "name": "UnauthorizedBlockBuilder", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61911, + "src": "5961:24:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$_t_address_$returns$_t_error_$", + "typeString": "function (address) pure returns (error)" + } + }, + "id": 60807, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5961:36:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 60803, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5944:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 60808, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "5944:54:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60809, + "nodeType": "ExpressionStatement", + "src": "5944:54:71" + }, + { + "assignments": [ + 60811 + ], + "declarations": [ + { + "constant": false, + "id": 60811, + "mutability": "mutable", + "name": "workloadKey", + "nameLocation": "6472:11:71", + "nodeType": "VariableDeclaration", + "scope": 60832, + "src": "6464:19:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60810, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "6464:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 60816, + "initialValue": { + "arguments": [ + { + "id": 60814, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60798, + "src": "6504:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 60812, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "6486:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60813, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "6497:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "6486:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 60815, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "6486:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6464:51:71" + }, + { + "assignments": [ + 60818 + ], + "declarations": [ + { + "constant": false, + "id": 60818, + "mutability": "mutable", + "name": "commitHash", + "nameLocation": "6539:10:71", + "nodeType": "VariableDeclaration", + "scope": 60832, + "src": "6525:24:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 60817, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "6525:6:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + } + ], + "id": 60823, + "initialValue": { + "expression": { + "baseExpression": { + "id": 60819, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "6552:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 60821, + "indexExpression": { + "id": 60820, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60811, + "src": "6570:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "6552:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 60822, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "6583:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "6552:41:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6525:68:71" + }, + { + "eventCall": { + "arguments": [ + { + "id": 60825, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60787, + "src": "6634:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "id": 60826, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60811, + "src": "6646:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 60827, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60789, + "src": "6659:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 60828, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60791, + "src": "6668:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 60829, + "name": "commitHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60818, + "src": "6686:10:71", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + ], + "id": 60824, + "name": "BlockBuilderProofVerified", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61897, + "src": "6608:25:71", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_bytes32_$_t_uint8_$_t_bytes32_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (address,bytes32,uint8,bytes32,string memory)" + } + }, + "id": 60830, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "6608:89:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 60831, + "nodeType": "EmitStatement", + "src": "6603:94:71" + } + ] + }, + "documentation": { + "id": 60785, + "nodeType": "StructuredDocumentation", + "src": "5229:414:71", + "text": "@notice Internal function to verify a block builder proof\n @dev This function is internal because it is only used by the permitVerifyBlockBuilderProof function\n and it is not needed to be called by other contracts\n @param teeAddress The TEE-controlled address\n @param version The version of the flashtestation's protocol\n @param blockContentHash The hash of the block content" + }, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "_verifyBlockBuilderProof", + "nameLocation": "5657:24:71", + "parameters": { + "id": 60792, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60787, + "mutability": "mutable", + "name": "teeAddress", + "nameLocation": "5690:10:71", + "nodeType": "VariableDeclaration", + "scope": 60833, + "src": "5682:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60786, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5682:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60789, + "mutability": "mutable", + "name": "version", + "nameLocation": "5708:7:71", + "nodeType": "VariableDeclaration", + "scope": 60833, + "src": "5702:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 60788, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "5702:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60791, + "mutability": "mutable", + "name": "blockContentHash", + "nameLocation": "5725:16:71", + "nodeType": "VariableDeclaration", + "scope": 60833, + "src": "5717:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60790, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "5717:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "5681:61:71" + }, + "returnParameters": { + "id": 60793, + "nodeType": "ParameterList", + "parameters": [], + "src": "5752:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "internal" + }, + { + "id": 60903, + "nodeType": "FunctionDefinition", + "src": "6750:906:71", + "nodes": [], + "body": { + "id": 60902, + "nodeType": "Block", + "src": "6851:805:71", + "nodes": [], + "statements": [ + { + "assignments": [ + null, + 60849 + ], + "declarations": [ + null, + { + "constant": false, + "id": 60849, + "mutability": "mutable", + "name": "registration", + "nameLocation": "6971:12:71", + "nodeType": "VariableDeclaration", + "scope": 60902, + "src": "6926:57:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE" + }, + "typeName": { + "id": 60848, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60847, + "name": "IFlashtestationRegistry.RegisteredTEE", + "nameLocations": [ + "6926:23:71", + "6950:13:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 62069, + "src": "6926:37:71" + }, + "referencedDeclaration": 62069, + "src": "6926:37:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_storage_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE" + } + }, + "visibility": "internal" + } + ], + "id": 60856, + "initialValue": { + "arguments": [ + { + "id": 60854, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60836, + "src": "7048:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "arguments": [ + { + "id": 60851, + "name": "registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60640, + "src": "7022:8:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60850, + "name": "FlashtestationRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61842, + "src": "6999:22:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_FlashtestationRegistry_$61842_$", + "typeString": "type(contract FlashtestationRegistry)" + } + }, + "id": 60852, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "6999:32:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_FlashtestationRegistry_$61842", + "typeString": "contract FlashtestationRegistry" + } + }, + "id": 60853, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "7032:15:71", + "memberName": "getRegistration", + "nodeType": "MemberAccess", + "referencedDeclaration": 61657, + "src": "6999:48:71", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_bool_$_t_struct$_RegisteredTEE_$62069_memory_ptr_$", + "typeString": "function (address) view external returns (bool,struct IFlashtestationRegistry.RegisteredTEE memory)" + } + }, + "id": 60855, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "6999:60:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_struct$_RegisteredTEE_$62069_memory_ptr_$", + "typeString": "tuple(bool,struct IFlashtestationRegistry.RegisteredTEE memory)" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6923:136:71" + }, + { + "condition": { + "id": 60859, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "!", + "prefix": true, + "src": "7230:21:71", + "subExpression": { + "expression": { + "id": 60857, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60849, + "src": "7231:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 60858, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "7244:7:71", + "memberName": "isValid", + "nodeType": "MemberAccess", + "referencedDeclaration": 62059, + "src": "7231:20:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 60868, + "nodeType": "IfStatement", + "src": "7226:86:71", + "trueBody": { + "id": 60867, + "nodeType": "Block", + "src": "7253:59:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "66616c7365", + "id": 60860, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7275:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + { + "arguments": [ + { + "hexValue": "30", + "id": 60863, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7298:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "id": 60861, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "7282:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60862, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "7293:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "7282:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 60864, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7282:18:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60865, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "7274:27:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60844, + "id": 60866, + "nodeType": "Return", + "src": "7267:34:71" + } + ] + } + }, + { + "assignments": [ + 60871 + ], + "declarations": [ + { + "constant": false, + "id": 60871, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "7333:10:71", + "nodeType": "VariableDeclaration", + "scope": 60902, + "src": "7322:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60870, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60869, + "name": "WorkloadId", + "nameLocations": [ + "7322:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "7322:10:71" + }, + "referencedDeclaration": 61861, + "src": "7322:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "id": 60875, + "initialValue": { + "arguments": [ + { + "id": 60873, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60849, + "src": "7374:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + ], + "id": 60872, + "name": "workloadIdForTDRegistration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61059, + "src": "7346:27:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_struct$_RegisteredTEE_$62069_memory_ptr_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (struct IFlashtestationRegistry.RegisteredTEE memory) pure returns (WorkloadId)" + } + }, + "id": 60874, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7346:41:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "7322:65:71" + }, + { + "condition": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 60888, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "expression": { + "baseExpression": { + "id": 60878, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "7482:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 60883, + "indexExpression": { + "arguments": [ + { + "id": 60881, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60871, + "src": "7518:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 60879, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "7500:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60880, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "7511:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "7500:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 60882, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7500:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7482:48:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 60884, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "7531:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "7482:59:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + ], + "id": 60877, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "7476:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 60876, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "7476:5:71", + "typeDescriptions": {} + } + }, + "id": 60885, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7476:66:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes storage pointer" + } + }, + "id": 60886, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "7543:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "7476:73:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 60887, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7552:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "7476:77:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 60894, + "nodeType": "IfStatement", + "src": "7472:133:71", + "trueBody": { + "id": 60893, + "nodeType": "Block", + "src": "7555:50:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "74727565", + "id": 60889, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7577:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + { + "id": 60890, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60871, + "src": "7583:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60891, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "7576:18:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60844, + "id": 60892, + "nodeType": "Return", + "src": "7569:25:71" + } + ] + } + }, + { + "expression": { + "components": [ + { + "hexValue": "66616c7365", + "id": 60895, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7623:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + { + "arguments": [ + { + "hexValue": "30", + "id": 60898, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7646:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "id": 60896, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "7630:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60897, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "7641:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "7630:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 60899, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "7630:18:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60900, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "7622:27:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60844, + "id": 60901, + "nodeType": "Return", + "src": "7615:34:71" + } + ] + }, + "baseFunctions": [ + 61963 + ], + "documentation": { + "id": 60834, + "nodeType": "StructuredDocumentation", + "src": "6710:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "d2753561", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isAllowedPolicy", + "nameLocation": "6759:15:71", + "overrides": { + "id": 60838, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "6807:8:71" + }, + "parameters": { + "id": 60837, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60836, + "mutability": "mutable", + "name": "teeAddress", + "nameLocation": "6783:10:71", + "nodeType": "VariableDeclaration", + "scope": 60903, + "src": "6775:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60835, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "6775:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "6774:20:71" + }, + "returnParameters": { + "id": 60844, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60840, + "mutability": "mutable", + "name": "allowed", + "nameLocation": "6830:7:71", + "nodeType": "VariableDeclaration", + "scope": 60903, + "src": "6825:12:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60839, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "6825:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60843, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 60903, + "src": "6839:10:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60842, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60841, + "name": "WorkloadId", + "nameLocations": [ + "6839:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "6839:10:71" + }, + "referencedDeclaration": 61861, + "src": "6839:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "6824:26:71" + }, + "scope": 61239, + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "id": 61013, + "nodeType": "FunctionDefinition", + "src": "8892:1574:71", + "nodes": [], + "body": { + "id": 61012, + "nodeType": "Block", + "src": "8979:1487:71", + "nodes": [], + "statements": [ + { + "assignments": [ + 60915, + 60917 + ], + "declarations": [ + { + "constant": false, + "id": 60915, + "mutability": "mutable", + "name": "isValid", + "nameLocation": "9054:7:71", + "nodeType": "VariableDeclaration", + "scope": 61012, + "src": "9049:12:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60914, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "9049:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60917, + "mutability": "mutable", + "name": "quoteHash", + "nameLocation": "9071:9:71", + "nodeType": "VariableDeclaration", + "scope": 61012, + "src": "9063:17:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60916, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "9063:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 60924, + "initialValue": { + "arguments": [ + { + "id": 60922, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60906, + "src": "9139:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "arguments": [ + { + "id": 60919, + "name": "registry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60640, + "src": "9107:8:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60918, + "name": "FlashtestationRegistry", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61842, + "src": "9084:22:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_FlashtestationRegistry_$61842_$", + "typeString": "type(contract FlashtestationRegistry)" + } + }, + "id": 60920, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9084:32:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_FlashtestationRegistry_$61842", + "typeString": "contract FlashtestationRegistry" + } + }, + "id": 60921, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9117:21:71", + "memberName": "getRegistrationStatus", + "nodeType": "MemberAccess", + "referencedDeclaration": 61682, + "src": "9084:54:71", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_bool_$_t_bytes32_$", + "typeString": "function (address) view external returns (bool,bytes32)" + } + }, + "id": 60923, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9084:66:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_bytes32_$", + "typeString": "tuple(bool,bytes32)" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9048:102:71" + }, + { + "condition": { + "id": 60926, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "!", + "prefix": true, + "src": "9164:8:71", + "subExpression": { + "id": 60925, + "name": "isValid", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60915, + "src": "9165:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 60935, + "nodeType": "IfStatement", + "src": "9160:73:71", + "trueBody": { + "id": 60934, + "nodeType": "Block", + "src": "9174:59:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "66616c7365", + "id": 60927, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9196:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + { + "arguments": [ + { + "hexValue": "30", + "id": 60930, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9219:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "id": 60928, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "9203:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60929, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "9214:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "9203:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 60931, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9203:18:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60932, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "9195:27:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60913, + "id": 60933, + "nodeType": "Return", + "src": "9188:34:71" + } + ] + } + }, + { + "assignments": [ + 60938 + ], + "declarations": [ + { + "constant": false, + "id": 60938, + "mutability": "mutable", + "name": "cached", + "nameLocation": "9329:6:71", + "nodeType": "VariableDeclaration", + "scope": 61012, + "src": "9307:28:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload" + }, + "typeName": { + "id": 60937, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60936, + "name": "CachedWorkload", + "nameLocations": [ + "9307:14:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 60611, + "src": "9307:14:71" + }, + "referencedDeclaration": 60611, + "src": "9307:14:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage_ptr", + "typeString": "struct CachedWorkload" + } + }, + "visibility": "internal" + } + ], + "id": 60942, + "initialValue": { + "baseExpression": { + "id": 60939, + "name": "cachedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60651, + "src": "9338:15:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_CachedWorkload_$60611_storage_$", + "typeString": "mapping(address => struct CachedWorkload storage ref)" + } + }, + "id": 60941, + "indexExpression": { + "id": 60940, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60906, + "src": "9354:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "9338:27:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage", + "typeString": "struct CachedWorkload storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9307:58:71" + }, + { + "assignments": [ + 60944 + ], + "declarations": [ + { + "constant": false, + "id": 60944, + "mutability": "mutable", + "name": "cachedWorkloadId", + "nameLocation": "9467:16:71", + "nodeType": "VariableDeclaration", + "scope": 61012, + "src": "9459:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 60943, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "9459:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 60950, + "initialValue": { + "arguments": [ + { + "expression": { + "id": 60947, + "name": "cached", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60938, + "src": "9504:6:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload memory" + } + }, + "id": 60948, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9511:10:71", + "memberName": "workloadId", + "nodeType": "MemberAccess", + "referencedDeclaration": 60607, + "src": "9504:17:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 60945, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "9486:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60946, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "9497:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "9486:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 60949, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9486:36:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9459:63:71" + }, + { + "condition": { + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 60958, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "commonType": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "id": 60953, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 60951, + "name": "cachedWorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60944, + "src": "9536:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "hexValue": "30", + "id": 60952, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9556:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "9536:21:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "commonType": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "id": 60957, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "id": 60954, + "name": "cached", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60938, + "src": "9561:6:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload memory" + } + }, + "id": 60955, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9568:9:71", + "memberName": "quoteHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 60610, + "src": "9561:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "id": 60956, + "name": "quoteHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60917, + "src": "9581:9:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "src": "9561:29:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "9536:54:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "id": 61010, + "nodeType": "Block", + "src": "10031:429:71", + "statements": [ + { + "assignments": [ + 60986, + 60989 + ], + "declarations": [ + { + "constant": false, + "id": 60986, + "mutability": "mutable", + "name": "allowed", + "nameLocation": "10136:7:71", + "nodeType": "VariableDeclaration", + "scope": 61010, + "src": "10131:12:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60985, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10131:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60989, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "10156:10:71", + "nodeType": "VariableDeclaration", + "scope": 61010, + "src": "10145:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60988, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60987, + "name": "WorkloadId", + "nameLocations": [ + "10145:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "10145:10:71" + }, + "referencedDeclaration": 61861, + "src": "10145:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "id": 60993, + "initialValue": { + "arguments": [ + { + "id": 60991, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60906, + "src": "10186:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 60990, + "name": "isAllowedPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60903, + "src": "10170:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (address) view returns (bool,WorkloadId)" + } + }, + "id": 60992, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "10170:27:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "10130:67:71" + }, + { + "condition": { + "id": 60994, + "name": "allowed", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60986, + "src": "10216:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 61005, + "nodeType": "IfStatement", + "src": "10212:195:71", + "trueBody": { + "id": 61004, + "nodeType": "Block", + "src": "10225:182:71", + "statements": [ + { + "expression": { + "id": 61002, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "baseExpression": { + "id": 60995, + "name": "cachedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60651, + "src": "10300:15:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_CachedWorkload_$60611_storage_$", + "typeString": "mapping(address => struct CachedWorkload storage ref)" + } + }, + "id": 60997, + "indexExpression": { + "id": 60996, + "name": "teeAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60906, + "src": "10316:10:71", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "10300:27:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage", + "typeString": "struct CachedWorkload storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "arguments": [ + { + "id": 60999, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60989, + "src": "10358:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + { + "id": 61000, + "name": "quoteHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60917, + "src": "10381:9:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 60998, + "name": "CachedWorkload", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60611, + "src": "10330:14:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_struct$_CachedWorkload_$60611_storage_ptr_$", + "typeString": "type(struct CachedWorkload storage pointer)" + } + }, + "id": 61001, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "structConstructorCall", + "lValueRequested": false, + "nameLocations": [ + "10346:10:71", + "10370:9:71" + ], + "names": [ + "workloadId", + "quoteHash" + ], + "nodeType": "FunctionCall", + "src": "10330:62:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload memory" + } + }, + "src": "10300:92:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_storage", + "typeString": "struct CachedWorkload storage ref" + } + }, + "id": 61003, + "nodeType": "ExpressionStatement", + "src": "10300:92:71" + } + ] + } + }, + { + "expression": { + "components": [ + { + "id": 61006, + "name": "allowed", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60986, + "src": "10429:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "id": 61007, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60989, + "src": "10438:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 61008, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "10428:21:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60913, + "id": 61009, + "nodeType": "Return", + "src": "10421:28:71" + } + ] + }, + "id": 61011, + "nodeType": "IfStatement", + "src": "9532:928:71", + "trueBody": { + "id": 60984, + "nodeType": "Block", + "src": "9592:433:71", + "statements": [ + { + "condition": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 60968, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "expression": { + "baseExpression": { + "id": 60961, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "9715:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 60963, + "indexExpression": { + "id": 60962, + "name": "cachedWorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60944, + "src": "9733:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "9715:35:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 60964, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9751:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "9715:46:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + ], + "id": 60960, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "9709:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 60959, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "9709:5:71", + "typeDescriptions": {} + } + }, + "id": 60965, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9709:53:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes storage pointer" + } + }, + "id": 60966, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9763:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "9709:60:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 60967, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9772:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "9709:64:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "id": 60982, + "nodeType": "Block", + "src": "9846:169:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "66616c7365", + "id": 60975, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9974:5:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "false" + }, + { + "arguments": [ + { + "hexValue": "30", + "id": 60978, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9997:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "id": 60976, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "9981:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 60977, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "9992:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "9981:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 60979, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "9981:18:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60980, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "9973:27:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60913, + "id": 60981, + "nodeType": "Return", + "src": "9966:34:71" + } + ] + }, + "id": 60983, + "nodeType": "IfStatement", + "src": "9705:310:71", + "trueBody": { + "id": 60974, + "nodeType": "Block", + "src": "9775:65:71", + "statements": [ + { + "expression": { + "components": [ + { + "hexValue": "74727565", + "id": 60969, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9801:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + { + "expression": { + "id": 60970, + "name": "cached", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60938, + "src": "9807:6:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_CachedWorkload_$60611_memory_ptr", + "typeString": "struct CachedWorkload memory" + } + }, + "id": 60971, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "9814:10:71", + "memberName": "workloadId", + "nodeType": "MemberAccess", + "referencedDeclaration": 60607, + "src": "9807:17:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "id": 60972, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "9800:25:71", + "typeDescriptions": { + "typeIdentifier": "t_tuple$_t_bool_$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "tuple(bool,WorkloadId)" + } + }, + "functionReturnParameters": 60913, + "id": 60973, + "nodeType": "Return", + "src": "9793:32:71" + } + ] + } + } + ] + } + } + ] + }, + "documentation": { + "id": 60904, + "nodeType": "StructuredDocumentation", + "src": "7662:1225:71", + "text": "@notice isAllowedPolicy but with caching to reduce gas costs\n @dev This function is only used by the verifyBlockBuilderProof function, which needs to be as efficient as possible\n because it is called onchain for every flashblock. The workloadId is cached to avoid expensive recomputation\n @dev A careful reader will notice that this function does not delete stale cache entries. It overwrites them\n if the underlying TEE registration is still valid. But for stale cache entries in every other scenario, the\n cache entry persists indefinitely. This is because every other instance results in a return value of (false, 0)\n to the caller (which is always the verifyBlockBuilderProof function) and it immediately reverts. This is an unfortunate\n consequence of our need to make this function as gas-efficient as possible, otherwise we would try to cleanup\n stale cache entries\n @param teeAddress The TEE-controlled address\n @return True if the TEE is using an approved workload in the policy\n @return The workloadId of the TEE that is using an approved workload in the policy, or 0 if\n the TEE is not using an approved workload in the policy" + }, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "_cachedIsAllowedPolicy", + "nameLocation": "8901:22:71", + "parameters": { + "id": 60907, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60906, + "mutability": "mutable", + "name": "teeAddress", + "nameLocation": "8932:10:71", + "nodeType": "VariableDeclaration", + "scope": 61013, + "src": "8924:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 60905, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8924:7:71", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "8923:20:71" + }, + "returnParameters": { + "id": 60913, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 60909, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61013, + "src": "8961:4:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 60908, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "8961:4:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 60912, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61013, + "src": "8967:10:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 60911, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 60910, + "name": "WorkloadId", + "nameLocations": [ + "8967:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "8967:10:71" + }, + "referencedDeclaration": 61861, + "src": "8967:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "8960:18:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "private" + }, + { + "id": 61059, + "nodeType": "FunctionDefinition", + "src": "10512:815:71", + "nodes": [], + "body": { + "id": 61058, + "nodeType": "Block", + "src": "10686:641:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "arguments": [ + { + "arguments": [ + { + "expression": { + "expression": { + "id": 61030, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "10793:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61031, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10806:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "10793:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61032, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10823:4:71", + "memberName": "mrTd", + "nodeType": "MemberAccess", + "referencedDeclaration": 146, + "src": "10793:34:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61033, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "10849:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61034, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10862:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "10849:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61035, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10879:5:71", + "memberName": "rtMr0", + "nodeType": "MemberAccess", + "referencedDeclaration": 154, + "src": "10849:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61036, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "10906:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61037, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10919:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "10906:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61038, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10936:5:71", + "memberName": "rtMr1", + "nodeType": "MemberAccess", + "referencedDeclaration": 156, + "src": "10906:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61039, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "10963:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61040, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10976:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "10963:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61041, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10993:5:71", + "memberName": "rtMr2", + "nodeType": "MemberAccess", + "referencedDeclaration": 158, + "src": "10963:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61042, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "11020:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61043, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11033:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "11020:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61044, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11050:5:71", + "memberName": "rtMr3", + "nodeType": "MemberAccess", + "referencedDeclaration": 160, + "src": "11020:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61045, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "11118:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61046, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11131:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "11118:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61047, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11148:10:71", + "memberName": "mrConfigId", + "nodeType": "MemberAccess", + "referencedDeclaration": 148, + "src": "11118:40:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "expression": { + "expression": { + "id": 61048, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "11180:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61049, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11193:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "11180:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61050, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11210:4:71", + "memberName": "xFAM", + "nodeType": "MemberAccess", + "referencedDeclaration": 144, + "src": "11180:34:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes8", + "typeString": "bytes8" + } + }, + { + "expression": { + "expression": { + "id": 61051, + "name": "registration", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61017, + "src": "11236:12:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE memory" + } + }, + "id": 61052, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11249:16:71", + "memberName": "parsedReportBody", + "nodeType": "MemberAccess", + "referencedDeclaration": 62064, + "src": "11236:29:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_TD10ReportBody_$163_memory_ptr", + "typeString": "struct TD10ReportBody memory" + } + }, + "id": 61053, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11266:12:71", + "memberName": "tdAttributes", + "nodeType": "MemberAccess", + "referencedDeclaration": 142, + "src": "11236:42:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes8", + "typeString": "bytes8" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes8", + "typeString": "bytes8" + }, + { + "typeIdentifier": "t_bytes8", + "typeString": "bytes8" + } + ], + "expression": { + "id": 61028, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "10759:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 61027, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "10759:5:71", + "typeDescriptions": {} + } + }, + "id": 61029, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "10765:6:71", + "memberName": "concat", + "nodeType": "MemberAccess", + "src": "10759:12:71", + "typeDescriptions": { + "typeIdentifier": "t_function_bytesconcat_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 61054, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "10759:537:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 61026, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "10732:9:71", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 61055, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "10732:578:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "id": 61024, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "10703:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 61025, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "10714:4:71", + "memberName": "wrap", + "nodeType": "MemberAccess", + "src": "10703:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_wrap_pure$_t_bytes32_$returns$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "function (bytes32) pure returns (WorkloadId)" + } + }, + "id": 61056, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "10703:617:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "functionReturnParameters": 61023, + "id": 61057, + "nodeType": "Return", + "src": "10696:624:71" + } + ] + }, + "baseFunctions": [ + 61973 + ], + "documentation": { + "id": 61014, + "nodeType": "StructuredDocumentation", + "src": "10472:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "4d37fc7a", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "workloadIdForTDRegistration", + "nameLocation": "10521:27:71", + "overrides": { + "id": 61019, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "10644:8:71" + }, + "parameters": { + "id": 61018, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61017, + "mutability": "mutable", + "name": "registration", + "nameLocation": "10594:12:71", + "nodeType": "VariableDeclaration", + "scope": 61059, + "src": "10549:57:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_memory_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE" + }, + "typeName": { + "id": 61016, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61015, + "name": "IFlashtestationRegistry.RegisteredTEE", + "nameLocations": [ + "10549:23:71", + "10573:13:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 62069, + "src": "10549:37:71" + }, + "referencedDeclaration": 62069, + "src": "10549:37:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_RegisteredTEE_$62069_storage_ptr", + "typeString": "struct IFlashtestationRegistry.RegisteredTEE" + } + }, + "visibility": "internal" + } + ], + "src": "10548:59:71" + }, + "returnParameters": { + "id": 61023, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61022, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61059, + "src": "10670:10:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 61021, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61020, + "name": "WorkloadId", + "nameLocations": [ + "10670:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "10670:10:71" + }, + "referencedDeclaration": 61861, + "src": "10670:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "10669:12:71" + }, + "scope": 61239, + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "id": 61131, + "nodeType": "FunctionDefinition", + "src": "11373:730:71", + "nodes": [], + "body": { + "id": 61130, + "nodeType": "Block", + "src": "11543:560:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 61081, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "id": 61077, + "name": "commitHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61065, + "src": "11567:10:71", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + ], + "id": 61076, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "11561:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 61075, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "11561:5:71", + "typeDescriptions": {} + } + }, + "id": 61078, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11561:17:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_calldata_ptr", + "typeString": "bytes calldata" + } + }, + "id": 61079, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11579:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "11561:24:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 61080, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "11588:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "11561:28:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61082, + "name": "EmptyCommitHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61921, + "src": "11591:15:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 61083, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11591:17:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 61074, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "11553:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 61084, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11553:56:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61085, + "nodeType": "ExpressionStatement", + "src": "11553:56:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 61090, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "id": 61087, + "name": "sourceLocators", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61068, + "src": "11627:14:71", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_string_calldata_ptr_$dyn_calldata_ptr", + "typeString": "string calldata[] calldata" + } + }, + "id": 61088, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11642:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "11627:21:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 61089, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "11651:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "11627:25:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61091, + "name": "EmptySourceLocators", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61924, + "src": "11654:19:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 61092, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11654:21:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 61086, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "11619:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 61093, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11619:57:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61094, + "nodeType": "ExpressionStatement", + "src": "11619:57:71" + }, + { + "assignments": [ + 61096 + ], + "declarations": [ + { + "constant": false, + "id": 61096, + "mutability": "mutable", + "name": "workloadKey", + "nameLocation": "11695:11:71", + "nodeType": "VariableDeclaration", + "scope": 61130, + "src": "11687:19:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61095, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "11687:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 61101, + "initialValue": { + "arguments": [ + { + "id": 61099, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61063, + "src": "11727:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 61097, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "11709:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 61098, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "11720:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "11709:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 61100, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11709:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "11687:51:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 61112, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "expression": { + "baseExpression": { + "id": 61105, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "11807:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61107, + "indexExpression": { + "id": 61106, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61096, + "src": "11825:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "11807:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 61108, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11838:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "11807:41:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + ], + "id": 61104, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "11801:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 61103, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "11801:5:71", + "typeDescriptions": {} + } + }, + "id": 61109, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11801:48:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes storage pointer" + } + }, + "id": 61110, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "11850:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "11801:55:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "hexValue": "30", + "id": 61111, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "11860:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "11801:60:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61113, + "name": "WorkloadAlreadyInPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61903, + "src": "11863:23:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 61114, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11863:25:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 61102, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "11793:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 61115, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "11793:96:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61116, + "nodeType": "ExpressionStatement", + "src": "11793:96:71" + }, + { + "expression": { + "id": 61124, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "baseExpression": { + "id": 61117, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "11939:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61119, + "indexExpression": { + "id": 61118, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61096, + "src": "11957:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "11939:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "arguments": [ + { + "id": 61121, + "name": "commitHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61065, + "src": "12002:10:71", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + } + }, + { + "id": 61122, + "name": "sourceLocators", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61068, + "src": "12030:14:71", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_string_calldata_ptr_$dyn_calldata_ptr", + "typeString": "string calldata[] calldata" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string calldata" + }, + { + "typeIdentifier": "t_array$_t_string_calldata_ptr_$dyn_calldata_ptr", + "typeString": "string calldata[] calldata" + } + ], + "id": 61120, + "name": "WorkloadMetadata", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61869, + "src": "11972:16:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_struct$_WorkloadMetadata_$61869_storage_ptr_$", + "typeString": "type(struct IBlockBuilderPolicy.WorkloadMetadata storage pointer)" + } + }, + "id": 61123, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "structConstructorCall", + "lValueRequested": false, + "nameLocations": [ + "11990:10:71", + "12014:14:71" + ], + "names": [ + "commitHash", + "sourceLocators" + ], + "nodeType": "FunctionCall", + "src": "11972:74:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_memory_ptr", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata memory" + } + }, + "src": "11939:107:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 61125, + "nodeType": "ExpressionStatement", + "src": "11939:107:71" + }, + { + "eventCall": { + "arguments": [ + { + "id": 61127, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61096, + "src": "12084:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 61126, + "name": "WorkloadAddedToPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61874, + "src": "12062:21:71", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_bytes32_$returns$__$", + "typeString": "function (bytes32)" + } + }, + "id": 61128, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12062:34:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61129, + "nodeType": "EmitStatement", + "src": "12057:39:71" + } + ] + }, + "baseFunctions": [ + 61985 + ], + "documentation": { + "id": 61060, + "nodeType": "StructuredDocumentation", + "src": "11333:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "4f3a415a", + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 61072, + "kind": "modifierInvocation", + "modifierName": { + "id": 61071, + "name": "onlyOwner", + "nameLocations": [ + "11529:9:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48019, + "src": "11529:9:71" + }, + "nodeType": "ModifierInvocation", + "src": "11529:9:71" + } + ], + "name": "addWorkloadToPolicy", + "nameLocation": "11382:19:71", + "overrides": { + "id": 61070, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "11512:8:71" + }, + "parameters": { + "id": 61069, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61063, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "11413:10:71", + "nodeType": "VariableDeclaration", + "scope": 61131, + "src": "11402:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 61062, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61061, + "name": "WorkloadId", + "nameLocations": [ + "11402:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "11402:10:71" + }, + "referencedDeclaration": 61861, + "src": "11402:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61065, + "mutability": "mutable", + "name": "commitHash", + "nameLocation": "11441:10:71", + "nodeType": "VariableDeclaration", + "scope": 61131, + "src": "11425:26:71", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_string_calldata_ptr", + "typeString": "string" + }, + "typeName": { + "id": 61064, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "11425:6:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61068, + "mutability": "mutable", + "name": "sourceLocators", + "nameLocation": "11471:14:71", + "nodeType": "VariableDeclaration", + "scope": 61131, + "src": "11453:32:71", + "stateVariable": false, + "storageLocation": "calldata", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_string_calldata_ptr_$dyn_calldata_ptr", + "typeString": "string[]" + }, + "typeName": { + "baseType": { + "id": 61066, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "11453:6:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "id": 61067, + "nodeType": "ArrayTypeName", + "src": "11453:8:71", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_string_storage_$dyn_storage_ptr", + "typeString": "string[]" + } + }, + "visibility": "internal" + } + ], + "src": "11401:85:71" + }, + "returnParameters": { + "id": 61073, + "nodeType": "ParameterList", + "parameters": [], + "src": "11543:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 61173, + "nodeType": "FunctionDefinition", + "src": "12149:433:71", + "nodes": [], + "body": { + "id": 61172, + "nodeType": "Block", + "src": "12234:348:71", + "nodes": [], + "statements": [ + { + "assignments": [ + 61142 + ], + "declarations": [ + { + "constant": false, + "id": 61142, + "mutability": "mutable", + "name": "workloadKey", + "nameLocation": "12252:11:71", + "nodeType": "VariableDeclaration", + "scope": 61172, + "src": "12244:19:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61141, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12244:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 61147, + "initialValue": { + "arguments": [ + { + "id": 61145, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61135, + "src": "12284:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 61143, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "12266:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 61144, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "12277:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "12266:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 61146, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12266:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "12244:51:71" + }, + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 61158, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "expression": { + "arguments": [ + { + "expression": { + "baseExpression": { + "id": 61151, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "12356:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61153, + "indexExpression": { + "id": 61152, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61142, + "src": "12374:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "12356:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "id": 61154, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "12387:10:71", + "memberName": "commitHash", + "nodeType": "MemberAccess", + "referencedDeclaration": 61865, + "src": "12356:41:71", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + ], + "id": 61150, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "12350:5:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 61149, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "12350:5:71", + "typeDescriptions": {} + } + }, + "id": 61155, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12350:48:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes storage pointer" + } + }, + "id": 61156, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberLocation": "12399:6:71", + "memberName": "length", + "nodeType": "MemberAccess", + "src": "12350:55:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">", + "rightExpression": { + "hexValue": "30", + "id": 61157, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12408:1:71", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "12350:59:71", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61159, + "name": "WorkloadNotInPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61906, + "src": "12411:19:71", + "typeDescriptions": { + "typeIdentifier": "t_function_error_pure$__$returns$_t_error_$", + "typeString": "function () pure returns (error)" + } + }, + "id": 61160, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12411:21:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_error", + "typeString": "error" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_error", + "typeString": "error" + } + ], + "id": 61148, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "12342:7:71", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_error_$returns$__$", + "typeString": "function (bool,error) pure" + } + }, + "id": 61161, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12342:91:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61162, + "nodeType": "ExpressionStatement", + "src": "12342:91:71" + }, + { + "expression": { + "id": 61166, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "delete", + "prefix": true, + "src": "12484:37:71", + "subExpression": { + "baseExpression": { + "id": 61163, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "12491:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61165, + "indexExpression": { + "id": 61164, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61142, + "src": "12509:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "12491:30:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61167, + "nodeType": "ExpressionStatement", + "src": "12484:37:71" + }, + { + "eventCall": { + "arguments": [ + { + "id": 61169, + "name": "workloadKey", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61142, + "src": "12563:11:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 61168, + "name": "WorkloadRemovedFromPolicy", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61879, + "src": "12537:25:71", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_bytes32_$returns$__$", + "typeString": "function (bytes32)" + } + }, + "id": 61170, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12537:38:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 61171, + "nodeType": "EmitStatement", + "src": "12532:43:71" + } + ] + }, + "baseFunctions": [ + 61992 + ], + "documentation": { + "id": 61132, + "nodeType": "StructuredDocumentation", + "src": "12109:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "5c40e542", + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 61139, + "kind": "modifierInvocation", + "modifierName": { + "id": 61138, + "name": "onlyOwner", + "nameLocations": [ + "12224:9:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48019, + "src": "12224:9:71" + }, + "nodeType": "ModifierInvocation", + "src": "12224:9:71" + } + ], + "name": "removeWorkloadFromPolicy", + "nameLocation": "12158:24:71", + "overrides": { + "id": 61137, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "12215:8:71" + }, + "parameters": { + "id": 61136, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61135, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "12194:10:71", + "nodeType": "VariableDeclaration", + "scope": 61173, + "src": "12183:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 61134, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61133, + "name": "WorkloadId", + "nameLocations": [ + "12183:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "12183:10:71" + }, + "referencedDeclaration": 61861, + "src": "12183:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "12182:23:71" + }, + "returnParameters": { + "id": 61140, + "nodeType": "ParameterList", + "parameters": [], + "src": "12234:0:71" + }, + "scope": 61239, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "id": 61192, + "nodeType": "FunctionDefinition", + "src": "12628:181:71", + "nodes": [], + "body": { + "id": 61191, + "nodeType": "Block", + "src": "12737:72:71", + "nodes": [], + "statements": [ + { + "expression": { + "baseExpression": { + "id": 61184, + "name": "approvedWorkloads", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60637, + "src": "12754:17:71", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_bytes32_$_t_struct$_WorkloadMetadata_$61869_storage_$", + "typeString": "mapping(bytes32 => struct IBlockBuilderPolicy.WorkloadMetadata storage ref)" + } + }, + "id": 61189, + "indexExpression": { + "arguments": [ + { + "id": 61187, + "name": "workloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61177, + "src": "12790:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + ], + "expression": { + "id": 61185, + "name": "WorkloadId", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61861, + "src": "12772:10:71", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_userDefinedValueType$_WorkloadId_$61861_$", + "typeString": "type(WorkloadId)" + } + }, + "id": 61186, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "12783:6:71", + "memberName": "unwrap", + "nodeType": "MemberAccess", + "src": "12772:17:71", + "typeDescriptions": { + "typeIdentifier": "t_function_unwrap_pure$_t_userDefinedValueType$_WorkloadId_$61861_$returns$_t_bytes32_$", + "typeString": "function (WorkloadId) pure returns (bytes32)" + } + }, + "id": 61188, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12772:29:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "12754:48:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata storage ref" + } + }, + "functionReturnParameters": 61183, + "id": 61190, + "nodeType": "Return", + "src": "12747:55:71" + } + ] + }, + "baseFunctions": [ + 62002 + ], + "documentation": { + "id": 61174, + "nodeType": "StructuredDocumentation", + "src": "12588:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "abd45d21", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getWorkloadMetadata", + "nameLocation": "12637:19:71", + "overrides": { + "id": 61179, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "12694:8:71" + }, + "parameters": { + "id": 61178, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61177, + "mutability": "mutable", + "name": "workloadId", + "nameLocation": "12668:10:71", + "nodeType": "VariableDeclaration", + "scope": 61192, + "src": "12657:21:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + }, + "typeName": { + "id": 61176, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61175, + "name": "WorkloadId", + "nameLocations": [ + "12657:10:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61861, + "src": "12657:10:71" + }, + "referencedDeclaration": 61861, + "src": "12657:10:71", + "typeDescriptions": { + "typeIdentifier": "t_userDefinedValueType$_WorkloadId_$61861", + "typeString": "WorkloadId" + } + }, + "visibility": "internal" + } + ], + "src": "12656:23:71" + }, + "returnParameters": { + "id": 61183, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61182, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61192, + "src": "12712:23:71", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_memory_ptr", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata" + }, + "typeName": { + "id": 61181, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 61180, + "name": "WorkloadMetadata", + "nameLocations": [ + "12712:16:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 61869, + "src": "12712:16:71" + }, + "referencedDeclaration": 61869, + "src": "12712:16:71", + "typeDescriptions": { + "typeIdentifier": "t_struct$_WorkloadMetadata_$61869_storage_ptr", + "typeString": "struct IBlockBuilderPolicy.WorkloadMetadata" + } + }, + "visibility": "internal" + } + ], + "src": "12711:25:71" + }, + "scope": 61239, + "stateMutability": "view", + "virtual": false, + "visibility": "external" + }, + { + "id": 61205, + "nodeType": "FunctionDefinition", + "src": "12855:131:71", + "nodes": [], + "body": { + "id": 61204, + "nodeType": "Block", + "src": "12934:52:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "id": 61201, + "name": "structHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61195, + "src": "12968:10:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 61200, + "name": "_hashTypedDataV4", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48851, + "src": "12951:16:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_bytes32_$returns$_t_bytes32_$", + "typeString": "function (bytes32) view returns (bytes32)" + } + }, + "id": 61202, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "12951:28:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "functionReturnParameters": 61199, + "id": 61203, + "nodeType": "Return", + "src": "12944:35:71" + } + ] + }, + "baseFunctions": [ + 62010 + ], + "documentation": { + "id": 61193, + "nodeType": "StructuredDocumentation", + "src": "12815:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "6931164e", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getHashedTypeDataV4", + "nameLocation": "12864:19:71", + "parameters": { + "id": 61196, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61195, + "mutability": "mutable", + "name": "structHash", + "nameLocation": "12892:10:71", + "nodeType": "VariableDeclaration", + "scope": 61205, + "src": "12884:18:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61194, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12884:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "12883:20:71" + }, + "returnParameters": { + "id": 61199, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61198, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61205, + "src": "12925:7:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61197, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12925:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "12924:9:71" + }, + "scope": 61239, + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "id": 61228, + "nodeType": "FunctionDefinition", + "src": "13032:229:71", + "nodes": [], + "body": { + "id": 61227, + "nodeType": "Block", + "src": "13145:116:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "arguments": [ + { + "id": 61220, + "name": "VERIFY_BLOCK_BUILDER_PROOF_TYPEHASH", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 60631, + "src": "13183:35:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 61221, + "name": "version", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61208, + "src": "13220:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "id": 61222, + "name": "blockContentHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61210, + "src": "13229:16:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 61223, + "name": "nonce", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 61212, + "src": "13247:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "id": 61218, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "13172:3:71", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 61219, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberLocation": "13176:6:71", + "memberName": "encode", + "nodeType": "MemberAccess", + "src": "13172:10:71", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencode_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 61224, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "13172:81:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 61217, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "13162:9:71", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 61225, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "13162:92:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "functionReturnParameters": 61216, + "id": 61226, + "nodeType": "Return", + "src": "13155:99:71" + } + ] + }, + "baseFunctions": [ + 62022 + ], + "documentation": { + "id": 61206, + "nodeType": "StructuredDocumentation", + "src": "12992:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "7dec71a9", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "computeStructHash", + "nameLocation": "13041:17:71", + "parameters": { + "id": 61213, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61208, + "mutability": "mutable", + "name": "version", + "nameLocation": "13065:7:71", + "nodeType": "VariableDeclaration", + "scope": 61228, + "src": "13059:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 61207, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "13059:5:71", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61210, + "mutability": "mutable", + "name": "blockContentHash", + "nameLocation": "13082:16:71", + "nodeType": "VariableDeclaration", + "scope": 61228, + "src": "13074:24:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61209, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "13074:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61212, + "mutability": "mutable", + "name": "nonce", + "nameLocation": "13108:5:71", + "nodeType": "VariableDeclaration", + "scope": 61228, + "src": "13100:13:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 61211, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "13100:7:71", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "13058:56:71" + }, + "returnParameters": { + "id": 61216, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61215, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61228, + "src": "13136:7:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61214, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "13136:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "13135:9:71" + }, + "scope": 61239, + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "id": 61238, + "nodeType": "FunctionDefinition", + "src": "13307:103:71", + "nodes": [], + "body": { + "id": 61237, + "nodeType": "Block", + "src": "13366:44:71", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 61234, + "name": "_domainSeparatorV4", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 48812, + "src": "13383:18:71", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_bytes32_$", + "typeString": "function () view returns (bytes32)" + } + }, + "id": 61235, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "13383:20:71", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "functionReturnParameters": 61233, + "id": 61236, + "nodeType": "Return", + "src": "13376:27:71" + } + ] + }, + "baseFunctions": [ + 62028 + ], + "documentation": { + "id": 61229, + "nodeType": "StructuredDocumentation", + "src": "13267:35:71", + "text": "@inheritdoc IBlockBuilderPolicy" + }, + "functionSelector": "f698da25", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "domainSeparator", + "nameLocation": "13316:15:71", + "parameters": { + "id": 61230, + "nodeType": "ParameterList", + "parameters": [], + "src": "13331:2:71" + }, + "returnParameters": { + "id": 61233, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 61232, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 61238, + "src": "13357:7:71", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 61231, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "13357:7:71", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "src": "13356:9:71" + }, + "scope": 61239, + "stateMutability": "view", + "virtual": false, + "visibility": "external" + } + ], + "abstract": false, + "baseContracts": [ + { + "baseName": { + "id": 60613, + "name": "Initializable", + "nameLocations": [ + "2056:13:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48392, + "src": "2056:13:71" + }, + "id": 60614, + "nodeType": "InheritanceSpecifier", + "src": "2056:13:71" + }, + { + "baseName": { + "id": 60615, + "name": "UUPSUpgradeable", + "nameLocations": [ + "2075:15:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48574, + "src": "2075:15:71" + }, + "id": 60616, + "nodeType": "InheritanceSpecifier", + "src": "2075:15:71" + }, + { + "baseName": { + "id": 60617, + "name": "OwnableUpgradeable", + "nameLocations": [ + "2096:18:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 48124, + "src": "2096:18:71" + }, + "id": 60618, + "nodeType": "InheritanceSpecifier", + "src": "2096:18:71" + }, + { + "baseName": { + "id": 60619, + "name": "EIP712Upgradeable", + "nameLocations": [ + "2120:17:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 49049, + "src": "2120:17:71" + }, + "id": 60620, + "nodeType": "InheritanceSpecifier", + "src": "2120:17:71" + }, + { + "baseName": { + "id": 60621, + "name": "IBlockBuilderPolicy", + "nameLocations": [ + "2143:19:71" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 62049, + "src": "2143:19:71" + }, + "id": 60622, + "nodeType": "InheritanceSpecifier", + "src": "2143:19:71" + } + ], + "canonicalName": "BlockBuilderPolicy", + "contractDependencies": [], + "contractKind": "contract", + "documentation": { + "id": 60612, + "nodeType": "StructuredDocumentation", + "src": "1154:866:71", + "text": " @title BlockBuilderPolicy\n @notice A reference implementation of a policy contract for the FlashtestationRegistry\n @notice A Policy is a collection of related WorkloadIds. A Policy exists to specify which\n WorkloadIds are valid for a particular purpose, in this case for remote block building. It also\n exists to handle the problem that TEE workloads will need to change multiple times a year, either because\n of Intel DCAP Endorsement updates or updates to the TEE configuration (and thus its WorkloadId). Without\n Policies, consumer contracts that makes use of Flashtestations would need to be updated every time a TEE workload\n changes, which is a costly and error-prone process. Instead, consumer contracts need only check if a TEE address\n is allowed under any workload in a Policy, and the FlashtestationRegistry will handle the rest" + }, + "fullyImplemented": true, + "linearizedBaseContracts": [ + 61239, + 62049, + 49049, + 49243, + 48124, + 48620, + 48574, + 49253, + 48392 + ], + "name": "BlockBuilderPolicy", + "nameLocation": "2030:18:71", + "scope": 61240, + "usedErrors": [ + 47960, + 47965, + 48141, + 48144, + 48419, + 48424, + 49311, + 49324, + 49971, + 50264, + 52061, + 52066, + 52071, + 61900, + 61903, + 61906, + 61911, + 61918, + 61921, + 61924 + ], + "usedEvents": [ + 47971, + 48149, + 49205, + 49223, + 61874, + 61879, + 61884, + 61897 + ] + } + ], + "license": "MIT" + }, + "id": 71 +} \ No newline at end of file diff --git a/crates/builder/op-rbuilder/src/tests/framework/mod.rs b/crates/builder/op-rbuilder/src/tests/framework/mod.rs index 554c01cb..b259215b 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/mod.rs @@ -43,7 +43,7 @@ pub const TEE_DEBUG_ADDRESS: alloy_primitives::Address = alloy_primitives::address!("6Af149F267e1e62dFc431F2de6deeEC7224746f4"); pub const WORKLOAD_ID: B256 = - b256!("f724e7d117f5655cf33beefdfc7d31e930278fcb65cf6d1de632595e97ca82b2"); + b256!("952569f637f3f7e36cd8f5a7578ae4d03a1cb05ddaf33b35d3054464bb1c862e"); pub const SOURCE_LOCATORS: &[&str] = &[ "https://github.com/flashbots/flashbots-images/commit/53d431f58a0d1a76f6711518ef8d876ce8181fc2", From 06dd34dab4027dbceb32e237bff524954d6b8f49 Mon Sep 17 00:00:00 2001 From: shana Date: Mon, 24 Nov 2025 10:38:55 -0800 Subject: [PATCH 216/262] Add cumulative da of builder tx da size (#322) * Fix builder tx cumulative da * fix builder gas and da calculation * add test --- .../op-rbuilder/src/builders/builder_tx.rs | 1 + .../op-rbuilder/src/builders/context.rs | 2 + .../src/builders/flashblocks/payload.rs | 100 +++++++++------ .../src/builders/standard/payload.rs | 10 +- .../src/primitives/reth/execution.rs | 5 +- .../src/tests/data_availability.rs | 120 ++++++++++++++++-- 6 files changed, 189 insertions(+), 49 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index d8d99adb..f99bd79b 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -235,6 +235,7 @@ pub trait BuilderTransactions OpPayloadBuilderCtx { best_txs: &mut impl PayloadTxsBounds, block_gas_limit: u64, block_da_limit: Option, + block_da_footprint_limit: Option, ) -> Result, PayloadBuilderError> { let execute_txs_start_time = Instant::now(); let mut num_txs_considered = 0; @@ -470,6 +471,7 @@ impl OpPayloadBuilderCtx { block_da_limit, tx.gas_limit(), info.da_footprint_scalar, + block_da_footprint_limit, ) { // we can't fit this transaction into the block, so we need to mark it as // invalid which also removes all dependent transaction from diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index cb91116e..07f1a008 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -85,20 +85,30 @@ pub struct FlashblocksExtraCtx { 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 { - fn next(self, target_gas_for_batch: u64, target_da_for_batch: Option) -> Self { + 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 } } @@ -344,29 +354,18 @@ where ctx.metrics.sequencer_tx_gauge.set(sequencer_tx_time); // We add first builder tx right after deposits - let builder_txs = if ctx.attributes().no_tx_pool { - vec![] - } else { - match self.builder_tx.add_builder_txs( - &state_provider, - &mut info, - &ctx, - &mut state, - false, - ) { - Ok(builder_txs) => builder_txs, - Err(e) => { - error!(target: "payload_builder", "Error adding builder txs to fallback block: {}", e); - vec![] - } - } + if !ctx.attributes().no_tx_pool + && let Err(e) = + self.builder_tx + .add_builder_txs(&state_provider, &mut info, &ctx, &mut state, false) + { + error!( + target: "payload_builder", + "Error adding builder txs to fallback block: {}", + e + ); }; - // We subtract gas limit and da limit for builder transaction from the whole limit - let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); - let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); - info.cumulative_da_bytes_used += builder_tx_da_size; - let (payload, fb_payload) = build_block( &mut state, &ctx, @@ -439,7 +438,6 @@ where .first_flashblock_time_offset .record(first_flashblock_offset.as_millis() as f64); let gas_per_batch = ctx.block_gas_limit() / flashblocks_per_block; - let target_gas_for_batch = gas_per_batch; let da_per_batch = ctx .da_config .max_da_block_size() @@ -447,26 +445,26 @@ where // Check that builder tx won't affect fb limit too much if let Some(da_limit) = da_per_batch { // We error if we can't insert any tx aside from builder tx in flashblock - if da_limit / 2 < builder_tx_da_size { + if info.cumulative_da_bytes_used >= da_limit { error!( "Builder tx da size subtraction caused max_da_block_size to be 0. No transaction would be included." ); } } - let mut target_da_for_batch = da_per_batch; + let da_footprint_per_batch = info + .da_footprint_scalar + .map(|_| ctx.block_gas_limit() / flashblocks_per_block); - // Account for already included builder tx - if let Some(da_limit) = target_da_for_batch.as_mut() { - *da_limit = da_limit.saturating_sub(builder_tx_da_size); - } let extra_ctx = FlashblocksExtraCtx { flashblock_index: 1, target_flashblock_count: flashblocks_per_block, - target_gas_for_batch: target_gas_for_batch.saturating_sub(builder_tx_gas), - target_da_for_batch, + target_gas_for_batch: gas_per_batch, + target_da_for_batch: da_per_batch, gas_per_batch, da_per_batch, + da_footprint_per_batch, disable_state_root, + target_da_footprint_for_batch: da_footprint_per_batch, }; let mut fb_cancel = block_cancel.child_token(); @@ -614,6 +612,7 @@ where 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; info!( target: "payload_builder", @@ -624,6 +623,7 @@ where target_da = target_da_for_batch, da_used = info.cumulative_da_bytes_used, block_gas_used = ctx.block_gas_limit(), + target_da_footprint = target_da_footprint_for_batch, "Building flashblock", ); let flashblock_build_start_time = Instant::now(); @@ -640,9 +640,16 @@ where } }; - let builder_tx_gas = builder_txs.iter().fold(0, |acc, tx| acc + tx.gas_used); - let builder_tx_da_size: u64 = builder_txs.iter().fold(0, |acc, tx| acc + tx.da_size); - info.cumulative_da_bytes_used += builder_tx_da_size; + // only reserve builder tx gas / da size that has not been committed yet + // committed builder txs would have counted towards the gas / da used + let builder_tx_gas = builder_txs + .iter() + .filter(|tx| !tx.is_top_of_block) + .fold(0, |acc, tx| acc + tx.gas_used); + let builder_tx_da_size: u64 = builder_txs + .iter() + .filter(|tx| !tx.is_top_of_block) + .fold(0, |acc, tx| acc + tx.da_size); target_gas_for_batch = target_gas_for_batch.saturating_sub(builder_tx_gas); // saturating sub just in case, we will log an error if da_limit too small for builder_tx_da_size @@ -650,6 +657,13 @@ where *da_limit = da_limit.saturating_sub(builder_tx_da_size); } + if let (Some(footprint), Some(scalar)) = ( + target_da_footprint_for_batch.as_mut(), + info.da_footprint_scalar, + ) { + *footprint = footprint.saturating_sub(builder_tx_da_size.saturating_mul(scalar as u64)); + } + let best_txs_start_time = Instant::now(); best_txs.refresh_iterator( BestPayloadTransactions::new( @@ -673,6 +687,7 @@ where best_txs, target_gas_for_batch.min(ctx.block_gas_limit()), target_da_for_batch, + target_da_footprint_for_batch, ) .wrap_err("failed to execute best transactions")?; // Extract last transactions @@ -781,10 +796,19 @@ where let target_gas_for_batch = ctx.extra_ctx.target_gas_for_batch + ctx.extra_ctx.gas_per_batch; - let next_extra_ctx = ctx - .extra_ctx - .clone() - .next(target_gas_for_batch, target_da_for_batch); + + if let (Some(footprint), Some(da_footprint_limit)) = ( + target_da_footprint_for_batch.as_mut(), + ctx.extra_ctx.da_footprint_per_batch, + ) { + *footprint += da_footprint_limit; + } + + let next_extra_ctx = ctx.extra_ctx.clone().next( + target_gas_for_batch, + target_da_for_batch, + target_da_footprint_for_batch, + ); info!( target: "payload_builder", diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 4907c5c2..84bb84d0 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -375,7 +375,6 @@ impl OpBuilder<'_, Txs> { } // 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); - info.cumulative_da_bytes_used += builder_tx_da_size; let block_da_limit = ctx .da_config .max_da_block_size() @@ -386,6 +385,14 @@ impl OpBuilder<'_, Txs> { } 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(); @@ -405,6 +412,7 @@ impl OpBuilder<'_, Txs> { &mut best_txs, block_gas_limit, block_da_limit, + block_da_footprint, )? .is_some() { diff --git a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs index 9ce998d1..7865a1c8 100644 --- a/crates/builder/op-rbuilder/src/primitives/reth/execution.rs +++ b/crates/builder/op-rbuilder/src/primitives/reth/execution.rs @@ -65,6 +65,7 @@ impl ExecutionInfo { /// per tx. /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the /// maximum allowed DA limit per block. + #[allow(clippy::too_many_arguments)] pub fn is_tx_over_limits( &self, tx_da_size: u64, @@ -73,12 +74,12 @@ impl ExecutionInfo { block_data_limit: Option, tx_gas_limit: u64, da_footprint_gas_scalar: Option, + block_da_footprint_limit: Option, ) -> Result<(), TxnExecutionResult> { if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) { return Err(TxnExecutionResult::TransactionDALimitExceeded); } let total_da_bytes_used = self.cumulative_da_bytes_used.saturating_add(tx_da_size); - if block_data_limit.is_some_and(|da_limit| total_da_bytes_used > da_limit) { return Err(TxnExecutionResult::BlockDALimitExceeded( self.cumulative_da_bytes_used, @@ -91,7 +92,7 @@ impl ExecutionInfo { if let Some(da_footprint_gas_scalar) = da_footprint_gas_scalar { let tx_da_footprint = total_da_bytes_used.saturating_mul(da_footprint_gas_scalar as u64); - if tx_da_footprint > block_gas_limit { + if tx_da_footprint > block_da_footprint_limit.unwrap_or(block_gas_limit) { return Err(TxnExecutionResult::BlockDALimitExceeded( total_da_bytes_used, tx_da_size, diff --git a/crates/builder/op-rbuilder/src/tests/data_availability.rs b/crates/builder/op-rbuilder/src/tests/data_availability.rs index 0e856c9f..9621f398 100644 --- a/crates/builder/op-rbuilder/src/tests/data_availability.rs +++ b/crates/builder/op-rbuilder/src/tests/data_availability.rs @@ -1,4 +1,4 @@ -use crate::tests::{BlockTransactionsExt, ChainDriverExt, LocalInstance}; +use crate::tests::{BlockTransactionsExt, ChainDriverExt, LocalInstance, TransactionBuilderExt}; use alloy_provider::Provider; use macros::{if_flashblocks, if_standard, rb_test}; @@ -56,7 +56,7 @@ async fn block_size_limit(rbuilder: LocalInstance) -> eyre::Result<()> { } /// This test ensures that block will fill up to the limit. -/// Size of each transaction is 100000000 +/// Size of each transaction is 100 /// We will set limit to 3 txs and see that the builder will include 3 transactions. /// We should not forget about builder transaction so we will spawn only 2 regular txs. #[rb_test] @@ -64,7 +64,6 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; // Set block big enough so it could fit 3 transactions without tx size limit - // Deposit transactions also count towards DA and there is one deposit txn in this block too let call = driver .provider() .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100 * 4)) @@ -83,7 +82,12 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { .with_max_priority_fee_per_gas(50) .send() .await?; - let unfit_tx_3 = driver.create_transaction().send().await?; + let fit_tx_3 = driver + .create_transaction() + .with_max_priority_fee_per_gas(50) + .send() + .await?; + let unfit_tx_4 = driver.create_transaction().send().await?; let block = driver.build_new_block_with_current_timestamp(None).await?; @@ -91,6 +95,7 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { // Now the first 2 txs will fit into the block assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); assert!(block.includes(fit_tx_2.tx_hash()), "tx should be in block"); + assert!(block.includes(fit_tx_3.tx_hash()), "tx should be in block"); } if_flashblocks! { @@ -98,17 +103,116 @@ async fn block_fill(rbuilder: LocalInstance) -> eyre::Result<()> { // so we will include only one tx in the block because not all of them // will fit within DA quote / flashblocks count. assert!(block.includes(fit_tx_1.tx_hash()), "tx should be in block"); - assert!(!block.includes(fit_tx_2.tx_hash()), "tx should not be in block"); + assert!(block.includes(fit_tx_2.tx_hash()), "tx should be in block"); + assert!(!block.includes(fit_tx_3.tx_hash()), "tx should not be in block"); } assert!( - !block.includes(unfit_tx_3.tx_hash()), + !block.includes(unfit_tx_4.tx_hash()), "unfit tx should not be in block" ); assert!( - driver.latest_full().await?.transactions.len() == 4, - "builder + deposit + 2 valid txs should be in the block" + driver.latest_full().await?.transactions.len() == 5, + "builder + deposit + 3 valid txs should be in the block" ); Ok(()) } + +/// This test ensures that the DA footprint limit (Jovian) is respected and the block fills +/// to the DA footprint limit. The DA footprint is calculated as: +/// total_da_bytes_used * da_footprint_gas_scalar (stored in blob_gas_used). +/// This must not exceed the block gas limit, accounting for the builder transaction. +#[rb_test] +async fn da_footprint_fills_to_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // DA footprint scalar from JOVIAN_DATA is 400 + // Set a constrained gas limit so DA footprint becomes the limiting factor + // With gas limit = 200_000 and scalar = 400: + // Max DA bytes = 200_000 / 400 = 500 bytes + let gas_limit = 400_000u64; + let call = driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (gas_limit,)) + .await?; + assert!(call, "miner_setGasLimit should be executed successfully"); + + // Set DA size limit to be permissive (not the constraint) + let call = driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 100_000)) + .await?; + assert!(call, "miner_setMaxDASize should be executed successfully"); + + let mut tx_hashes = Vec::new(); + for _ in 0..10 { + // 400 * 100 = 400_000 total da footprint + let tx = driver + .create_transaction() + .random_valid_transfer() + .with_gas_limit(21000) + .send() + .await?; + tx_hashes.push(tx.tx_hash().clone()); + } + + let block = driver.build_new_block_with_current_timestamp(None).await?; + + // Verify that blob_gas_used (DA footprint) is set and respects limits + assert!( + block.header.blob_gas_used.is_some(), + "blob_gas_used should be set in Jovian" + ); + + let blob_gas = block.header.blob_gas_used.unwrap(); + + // The DA footprint must not exceed the block gas limit + assert!( + blob_gas == gas_limit, + "DA footprint (blob_gas_used={}) must not exceed block gas limit ({})", + blob_gas, + gas_limit + ); + + // Verify the block fills up to the DA footprint limit + // accounting for builder tx DA contribution + if_standard! { + for i in 0..8 { + assert!( + block.includes(&tx_hashes[i]), + "tx {} should be included in the block", + i + ); + } + + // Verify the last tx doesn't fit due to DA footprint limit + assert!( + !block.includes(&tx_hashes[9]), + "tx 9 should not fit in the block due to DA footprint limit" + ); + } + + if_flashblocks! { + for i in 0..7 { + assert!( + block.includes(&tx_hashes[i]), + "tx {} should be included in the block", + i + ); + } + + // Verify the last 2 tx doesn't fit due to DA footprint limit + assert!( + !block.includes(&tx_hashes[8]), + "tx 8 should not fit in the block due to DA footprint limit" + ); + + assert!( + !block.includes(&tx_hashes[9]), + "tx 9 should not fit in the block due to DA footprint limit" + ); + } + + Ok(()) +} From c92d1ffa59e1480b0186d0d387823e05f1dce49d Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Mon, 1 Dec 2025 10:36:43 +0400 Subject: [PATCH 217/262] Fix deps (#336) * Fix deps * fix version * fix * bump release --- crates/builder/op-rbuilder/src/builders/builder_tx.rs | 9 +++------ .../op-rbuilder/src/builders/flashblocks/payload.rs | 9 ++++----- crates/builder/op-rbuilder/src/builders/generator.rs | 5 +++-- .../builder/op-rbuilder/src/builders/standard/payload.rs | 9 ++++----- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/builder_tx.rs b/crates/builder/op-rbuilder/src/builders/builder_tx.rs index f99bd79b..590ae1b2 100644 --- a/crates/builder/op-rbuilder/src/builders/builder_tx.rs +++ b/crates/builder/op-rbuilder/src/builders/builder_tx.rs @@ -12,7 +12,7 @@ use op_alloy_consensus::OpTypedTransaction; use op_alloy_rpc_types::OpTransactionRequest; use op_revm::{OpHaltReason, OpTransactionError}; use reth_evm::{ - ConfigureEvm, Evm, EvmEnv, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, + ConfigureEvm, Evm, EvmError, InvalidTxError, eth::receipt_builder::ReceiptBuilderCtx, precompiles::PrecompilesMap, }; use reth_node_api::PayloadBuilderError; @@ -20,7 +20,7 @@ use reth_optimism_primitives::OpTransactionSigned; use reth_primitives::Recovered; use reth_provider::{ProviderError, StateProvider}; use reth_revm::{State, database::StateProviderDatabase}; -use reth_rpc_api::eth::{EthTxEnvError, TryIntoTxEnv}; +use reth_rpc_api::eth::{EthTxEnvError, transaction::TryIntoTxEnv}; use revm::{ DatabaseCommit, DatabaseRef, context::{ @@ -323,10 +323,7 @@ pub trait BuilderTransactions, evm: &mut OpEvm, ) -> Result, BuilderTransactionError> { - let tx_env = tx.try_into_tx_env(&EvmEnv { - cfg_env: evm.cfg().clone(), - block_env: evm.block().clone(), - })?; + let tx_env = tx.try_into_tx_env(evm.cfg(), evm.block())?; let to = tx_env.base.kind.into_to().unwrap_or_default(); let ResultAndState { result, state } = match evm.transact(tx_env) { diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 07f1a008..1d5d1785 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -18,10 +18,10 @@ use alloy_consensus::{ use alloy_eips::{Encodable2718, eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; use alloy_primitives::{Address, B256, U256, map::foldhash::HashMap}; use core::time::Duration; -use either::Either; use eyre::WrapErr as _; use reth::payload::PayloadBuilderAttributes; use reth_basic_payload_builder::BuildOutcome; +use reth_chain_state::ExecutedBlock; use reth_chainspec::EthChainSpec; use reth_evm::{ConfigureEvm, execute::BlockBuilder}; use reth_node_api::{Block, NodePrimitives, PayloadBuilderError}; @@ -30,7 +30,6 @@ use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes}; use reth_optimism_forks::OpHardforks; use reth_optimism_node::{OpBuiltPayload, OpPayloadBuilderAttributes}; use reth_optimism_primitives::{OpPrimitives, OpReceipt, OpTransactionSigned}; -use reth_payload_primitives::BuiltPayloadExecutedBlock; use reth_payload_util::BestPayloadTransactions; use reth_primitives_traits::RecoveredBlock; use reth_provider::{ @@ -1112,11 +1111,11 @@ where RecoveredBlock::new_unhashed(block.clone(), info.executed_senders.clone()); // create the executed block data - let executed = BuiltPayloadExecutedBlock { + let executed = ExecutedBlock { recovered_block: Arc::new(recovered_block), execution_output: Arc::new(execution_outcome), - hashed_state: Either::Left(Arc::new(hashed_state)), - trie_updates: Either::Left(Arc::new(trie_output)), + hashed_state: Arc::new(hashed_state), + trie_updates: Arc::new(trie_output), }; debug!(target: "payload_builder", message = "Executed block created"); diff --git a/crates/builder/op-rbuilder/src/builders/generator.rs b/crates/builder/op-rbuilder/src/builders/generator.rs index 041c274d..4f645cca 100644 --- a/crates/builder/op-rbuilder/src/builders/generator.rs +++ b/crates/builder/op-rbuilder/src/builders/generator.rs @@ -470,7 +470,8 @@ mod tests { use alloy_primitives::U256; use rand::rng; use reth::tasks::TokioTaskExecutor; - use reth_node_api::{BuiltPayloadExecutedBlock, NodePrimitives}; + use reth_chain_state::ExecutedBlock; + use reth_node_api::NodePrimitives; use reth_optimism_payload_builder::{OpPayloadPrimitives, payload::OpPayloadBuilderAttributes}; use reth_optimism_primitives::OpPrimitives; use reth_primitives::SealedBlock; @@ -589,7 +590,7 @@ mod tests { } /// Returns the entire execution data for the built block, if available. - fn executed_block(&self) -> Option> { + fn executed_block(&self) -> Option> { None } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 84bb84d0..f8bb1b58 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -14,6 +14,7 @@ use alloy_evm::Database; use alloy_primitives::U256; use reth::payload::PayloadBuilderAttributes; 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}; @@ -584,9 +585,7 @@ impl OpBuilder<'_, Txs> { info!(target: "payload_builder", id=%ctx.attributes().payload_id(), "sealed built block"); // create the executed block data - use either::Either; - use reth_payload_primitives::BuiltPayloadExecutedBlock; - let executed = BuiltPayloadExecutedBlock { + let executed = ExecutedBlock { recovered_block: Arc::new( RecoveredBlock::>::new_sealed( sealed_block.as_ref().clone(), @@ -594,8 +593,8 @@ impl OpBuilder<'_, Txs> { ), ), execution_output: Arc::new(execution_outcome), - hashed_state: Either::Left(Arc::new(hashed_state)), - trie_updates: Either::Left(Arc::new(trie_output)), + hashed_state: Arc::new(hashed_state), + trie_updates: Arc::new(trie_output), }; let no_tx_pool = ctx.attributes().no_tx_pool; From 67e8ce68d4528d09a998d9914bade571cab8bd19 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 17 Dec 2025 03:12:27 -0500 Subject: [PATCH 218/262] builder backrun --- crates/builder/op-rbuilder/src/args/op.rs | 4 + .../op-rbuilder/src/builders/context.rs | 49 ++- .../src/builders/flashblocks/ctx.rs | 5 + .../src/builders/flashblocks/payload.rs | 1 + .../builder/op-rbuilder/src/builders/mod.rs | 7 + .../src/builders/standard/payload.rs | 1 + crates/builder/op-rbuilder/src/bundles.rs | 286 ++++++++++++++++++ crates/builder/op-rbuilder/src/launcher.rs | 5 + crates/builder/op-rbuilder/src/lib.rs | 1 + crates/builder/op-rbuilder/src/metrics.rs | 6 + 10 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 crates/builder/op-rbuilder/src/bundles.rs diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index 4dc0663c..d3323451 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -58,6 +58,10 @@ pub struct OpRbuilderArgs { )] pub resource_metering_buffer_size: usize, + /// Buffer size for backrun bundles (LRU eviction when full) + #[arg(long = "builder.backrun-bundle-buffer-size", default_value = "10000")] + pub backrun_bundle_buffer_size: usize, + /// Path to builder playgorund to automatically start up the node connected to it #[arg( long = "builder.playground", diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 1e0f22da..802576b6 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -80,6 +80,8 @@ pub struct OpPayloadBuilderCtx { pub address_gas_limiter: AddressGasLimiter, /// Per transaction resource metering information pub resource_metering: ResourceMetering, + /// Backrun bundle store for storing backrun transactions + pub backrun_bundle_store: crate::bundles::BackrunBundleStore, } impl OpPayloadBuilderCtx { @@ -543,7 +545,8 @@ impl OpPayloadBuilderCtx { continue; } - if result.is_success() { + let is_success = result.is_success(); + if is_success { log_txn(TxnExecutionResult::Success); num_txs_simulated_success += 1; self.metrics.successful_tx_gas_used.record(gas_used as f64); @@ -600,6 +603,50 @@ impl OpPayloadBuilderCtx { // append sender and transaction to the respective lists info.executed_senders.push(tx.signer()); info.executed_transactions.push(tx.into_inner()); + + if is_success && let Some(backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) { + self.metrics.backrun_target_txs_found_total.increment(1); + + for stored_bundle in backrun_bundles { + for backrun_tx in stored_bundle.backrun_txs { + let ResultAndState { result, state } = match evm.transact(&backrun_tx) { + Ok(res) => res, + Err(err) => { + return Err(PayloadBuilderError::evm(err)); + } + }; + + let backrun_gas_used = result.gas_used(); + let is_backrun_success = result.is_success(); + + if !is_backrun_success { + continue; + } + + info.cumulative_gas_used += backrun_gas_used; + info.cumulative_da_bytes_used += backrun_tx.encoded_2718().len() as u64; + + let ctx = ReceiptBuilderCtx { + tx: backrun_tx.inner(), + evm: &evm, + result, + state: &state, + cumulative_gas_used: info.cumulative_gas_used, + }; + info.receipts.push(self.build_receipt(ctx, None)); + + evm.db_mut().commit(state); + + let miner_fee = backrun_tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(backrun_gas_used); + + info.executed_senders.push(backrun_tx.signer()); + info.executed_transactions.push(backrun_tx.into_inner()); + } + } + } } let payload_transaction_simulation_time = execute_txs_start_time.elapsed(); diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs index 28cbae76..47e9d5ee 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -1,5 +1,6 @@ use crate::{ builders::{BuilderConfig, OpPayloadBuilderCtx, flashblocks::FlashblocksConfig}, + bundles::BackrunBundleStore, gas_limiter::{AddressGasLimiter, args::GasLimiterArgs}, metrics::OpRBuilderMetrics, resource_metering::ResourceMetering, @@ -32,6 +33,8 @@ pub(super) struct OpPayloadSyncerCtx { metrics: Arc, /// Resource metering tracking resource_metering: ResourceMetering, + /// Backrun bundle store + backrun_bundle_store: BackrunBundleStore, } impl OpPayloadSyncerCtx { @@ -52,6 +55,7 @@ impl OpPayloadSyncerCtx { max_gas_per_txn: builder_config.max_gas_per_txn, metrics, resource_metering: builder_config.resource_metering, + backrun_bundle_store: builder_config.backrun_bundle_store, }) } @@ -85,6 +89,7 @@ impl OpPayloadSyncerCtx { max_gas_per_txn: self.max_gas_per_txn, address_gas_limiter: AddressGasLimiter::new(GasLimiterArgs::default()), resource_metering: self.resource_metering.clone(), + backrun_bundle_store: self.backrun_bundle_store.clone(), } } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 35e16834..6318b156 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -283,6 +283,7 @@ where max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), resource_metering: self.config.resource_metering.clone(), + backrun_bundle_store: self.config.backrun_bundle_store.clone(), }) } diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 48ce625b..f6087b0d 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -21,6 +21,7 @@ mod flashblocks; mod generator; mod standard; +use crate::bundles::BackrunBundleStore; use crate::resource_metering::ResourceMetering; pub use builder_tx::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, InvalidContractDataError, @@ -130,6 +131,9 @@ pub struct BuilderConfig { /// Resource metering context pub resource_metering: ResourceMetering, + + /// Backrun bundle store for storing backrun transactions + pub backrun_bundle_store: BackrunBundleStore, } impl core::fmt::Debug for BuilderConfig { @@ -152,6 +156,7 @@ impl core::fmt::Debug for BuilderConfig { .field("specific", &self.specific) .field("max_gas_per_txn", &self.max_gas_per_txn) .field("gas_limiter_config", &self.gas_limiter_config) + .field("backrun_bundle_store", &self.backrun_bundle_store) .finish() } } @@ -171,6 +176,7 @@ impl Default for BuilderConfig { max_gas_per_txn: None, gas_limiter_config: GasLimiterArgs::default(), resource_metering: ResourceMetering::default(), + backrun_bundle_store: BackrunBundleStore::default(), } } } @@ -197,6 +203,7 @@ where args.enable_resource_metering, args.resource_metering_buffer_size, ), + backrun_bundle_store: BackrunBundleStore::new(args.backrun_bundle_buffer_size), specific: S::try_from(args)?, }) } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index d9a74add..4e94a89f 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -252,6 +252,7 @@ where max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), resource_metering: self.config.resource_metering.clone(), + backrun_bundle_store: self.config.backrun_bundle_store.clone(), }; let builder = OpBuilder::new(best); diff --git a/crates/builder/op-rbuilder/src/bundles.rs b/crates/builder/op-rbuilder/src/bundles.rs new file mode 100644 index 00000000..aaad193d --- /dev/null +++ b/crates/builder/op-rbuilder/src/bundles.rs @@ -0,0 +1,286 @@ +use alloy_consensus::transaction::Recovered; +use alloy_primitives::TxHash; +use concurrent_queue::ConcurrentQueue; +use jsonrpsee::{ + core::{RpcResult, async_trait}, + proc_macros::rpc, +}; +use op_alloy_consensus::OpTxEnvelope; +use std::{fmt::Debug, sync::Arc}; +use tips_core::{Bundle, types::ParsedBundle}; +use tracing::{debug, warn}; +use uuid::Uuid; + +use crate::metrics::OpRBuilderMetrics; + +#[derive(Clone)] +pub struct StoredBackrunBundle { + pub bundle_id: Uuid, + pub backrun_txs: Vec>, +} + +struct BackrunData { + by_target_tx: dashmap::DashMap>, + lru: ConcurrentQueue, +} + +#[derive(Clone)] +pub struct BackrunBundleStore { + data: Arc, + metrics: OpRBuilderMetrics, +} + +impl Debug for BackrunBundleStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BackrunBundleStore") + .field("by_target_tx_count", &self.data.by_target_tx.len()) + .finish() + } +} + +impl BackrunBundleStore { + pub fn new(buffer_size: usize) -> Self { + Self { + data: Arc::new(BackrunData { + by_target_tx: dashmap::DashMap::new(), + lru: ConcurrentQueue::bounded(buffer_size), + }), + metrics: OpRBuilderMetrics::default(), + } + } + + pub fn insert(&self, bundle: ParsedBundle, bundle_id: Uuid) -> Result<(), String> { + if bundle.txs.len() < 2 { + return Err("Bundle must have at least 2 transactions (target + backrun)".to_string()); + } + + // Target tx is txs[0], backrun txs are txs[1..] + let target_tx_hash = bundle.txs[0].tx_hash(); + let backrun_txs: Vec> = bundle.txs[1..].to_vec(); + + if self.data.lru.is_full() + && let Ok(evicted_hash) = self.data.lru.pop() + { + self.data.by_target_tx.remove(&evicted_hash); + warn!( + target: "backrun_bundles", + evicted_target = ?evicted_hash, + "Evicted old backrun bundle" + ); + } + + let _ = self.data.lru.push(target_tx_hash); + + let stored_bundle = StoredBackrunBundle { + bundle_id, + backrun_txs: backrun_txs.clone(), + }; + + self.data + .by_target_tx + .entry(target_tx_hash) + .or_insert_with(Vec::new) + .push(stored_bundle); + + self.metrics + .backrun_bundles_in_store + .set(self.data.by_target_tx.len() as f64); + + Ok(()) + } + + pub fn get(&self, target_tx_hash: &TxHash) -> Option> { + self.data + .by_target_tx + .get(target_tx_hash) + .map(|entry| entry.clone()) + } + + pub fn remove(&self, target_tx_hash: &TxHash) { + if let Some((_, bundles)) = self.data.by_target_tx.remove(target_tx_hash) { + debug!( + target: "backrun_bundles", + target_tx = ?target_tx_hash, + bundle_count = bundles.len(), + "Removed backrun bundles" + ); + + self.metrics + .backrun_bundles_in_store + .set(self.data.by_target_tx.len() as f64); + } + } + + pub fn len(&self) -> usize { + self.data.by_target_tx.len() + } +} + +impl Default for BackrunBundleStore { + fn default() -> Self { + Self::new(10_000) + } +} + +#[cfg_attr(not(test), rpc(server, namespace = "base"))] +#[cfg_attr(test, rpc(server, client, namespace = "base"))] +pub trait BaseBundlesApiExt { + #[method(name = "sendBackrunBundle")] + async fn send_backrun_bundle(&self, bundle: Bundle, bundle_id: Uuid) -> RpcResult<()>; +} + +pub(crate) struct BundlesApiExt { + bundle_store: BackrunBundleStore, + metrics: OpRBuilderMetrics, +} + +impl BundlesApiExt { + pub(crate) fn new(bundle_store: BackrunBundleStore) -> Self { + Self { + bundle_store, + metrics: OpRBuilderMetrics::default(), + } + } +} + +#[async_trait] +impl BaseBundlesApiExtServer for BundlesApiExt { + async fn send_backrun_bundle(&self, bundle: Bundle, bundle_id: Uuid) -> RpcResult<()> { + self.metrics.backrun_bundles_received_total.increment(1); + + let parsed_bundle = ParsedBundle::try_from(bundle).map_err(|e| { + warn!(target: "backrun_bundles", error = %e, "Failed to parse bundle"); + jsonrpsee::types::ErrorObject::owned( + jsonrpsee::types::error::INVALID_PARAMS_CODE, + format!("Failed to parse bundle: {e}"), + None::<()>, + ) + })?; + + self.bundle_store + .insert(parsed_bundle, bundle_id) + .map_err(|e| { + warn!(target: "backrun_bundles", error = %e, "Failed to store bundle"); + jsonrpsee::types::ErrorObject::owned( + jsonrpsee::types::error::INTERNAL_ERROR_CODE, + format!("Failed to store bundle: {e}"), + None::<()>, + ) + })?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::SignableTransaction; + use alloy_primitives::{Address, Bytes, TxHash, U256}; + use alloy_provider::network::{TxSignerSync, eip2718::Encodable2718}; + use alloy_signer_local::PrivateKeySigner; + use op_alloy_consensus::OpTxEnvelope; + use op_alloy_rpc_types::OpTransactionRequest; + + fn create_transaction(from: PrivateKeySigner, nonce: u64, to: Address) -> OpTxEnvelope { + let mut txn = OpTransactionRequest::default() + .value(U256::from(10_000)) + .gas_limit(21_000) + .max_fee_per_gas(200) + .max_priority_fee_per_gas(100) + .from(from.address()) + .to(to) + .nonce(nonce) + .build_typed_tx() + .unwrap(); + + let sig = from.sign_transaction_sync(&mut txn).unwrap(); + OpTxEnvelope::Eip1559(txn.eip1559().cloned().unwrap().into_signed(sig).clone()) + } + + fn create_test_parsed_bundle(txs: Vec) -> ParsedBundle { + tips_core::Bundle { + txs, + block_number: 1, + ..Default::default() + } + .try_into() + .unwrap() + } + + #[test] + fn test_backrun_bundle_store() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + + // Create test transactions + let target_tx = create_transaction(alice.clone(), 0, bob.address()); + let backrun_tx1 = create_transaction(alice.clone(), 1, bob.address()); + let backrun_tx2 = create_transaction(alice.clone(), 2, bob.address()); + + let target_tx_hash = target_tx.tx_hash(); + + let store = BackrunBundleStore::new(100); + + // Test insert fails with only 1 tx (need target + at least 1 backrun) + let single_tx_bundle = create_test_parsed_bundle(vec![target_tx.encoded_2718().into()]); + assert!(store.insert(single_tx_bundle, Uuid::new_v4()).is_err()); + assert_eq!(store.len(), 0); + + // Test insert succeeds with 2+ txs + let valid_bundle = create_test_parsed_bundle(vec![ + target_tx.encoded_2718().into(), + backrun_tx1.encoded_2718().into(), + ]); + assert!(store.insert(valid_bundle, Uuid::new_v4()).is_ok()); + assert_eq!(store.len(), 1); + + // Test get returns the backrun txs (not the target) + let retrieved = store.get(&target_tx_hash).unwrap(); + assert_eq!(retrieved.len(), 1); // 1 bundle + assert_eq!(retrieved[0].backrun_txs.len(), 1); // 1 backrun tx in that bundle + assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx1.tx_hash()); + + // Test multiple backrun bundles for same target + let second_bundle = create_test_parsed_bundle(vec![ + target_tx.encoded_2718().into(), + backrun_tx2.encoded_2718().into(), + ]); + assert!(store.insert(second_bundle, Uuid::new_v4()).is_ok()); + assert_eq!(store.len(), 1); // Still 1 target, but 2 backrun bundles + + let retrieved = store.get(&target_tx_hash).unwrap(); + assert_eq!(retrieved.len(), 2); // Now 2 bundles for same target + + // Test remove + store.remove(&target_tx_hash); + assert_eq!(store.len(), 0); + assert!(store.get(&target_tx_hash).is_none()); + + // Test remove on non-existent key doesn't panic + store.remove(&TxHash::ZERO); + } + + #[test] + fn test_backrun_bundle_store_lru_eviction() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + + // Small buffer to test eviction + let store = BackrunBundleStore::new(2); + + // Insert 3 bundles, first should be evicted + for nonce in 0..3u64 { + let target = create_transaction(alice.clone(), nonce * 2, bob.address()); + let backrun = create_transaction(alice.clone(), nonce * 2 + 1, bob.address()); + let bundle = create_test_parsed_bundle(vec![ + target.encoded_2718().into(), + backrun.encoded_2718().into(), + ]); + let _ = store.insert(bundle, Uuid::new_v4()); + } + + // Only 2 should remain due to LRU eviction + assert_eq!(store.len(), 2); + } +} diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index 5d7c6f84..27fa04f3 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -4,6 +4,7 @@ use reth_optimism_rpc::OpEthApiBuilder; use crate::{ args::*, builders::{BuilderConfig, BuilderMode, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, + bundles::{BaseBundlesApiExtServer, BundlesApiExt}, metrics::{VERSION, record_flag_gauge_metrics}, monitor_tx_pool::monitor_tx_pool, primitives::reth::engine_api_builder::OpEngineApiBuilder, @@ -111,6 +112,7 @@ where let reverted_cache = Cache::builder().max_capacity(100).build(); let reverted_cache_copy = reverted_cache.clone(); let resource_metering = builder_config.resource_metering.clone(); + let backrun_bundle_store = builder_config.backrun_bundle_store.clone(); let mut addons: OpAddOns< _, @@ -167,8 +169,11 @@ where } let resource_metering_ext = ResourceMeteringExt::new(resource_metering); + let bundles_ext = BundlesApiExt::new(backrun_bundle_store); ctx.modules .add_or_replace_configured(resource_metering_ext.into_rpc())?; + ctx.modules + .add_or_replace_configured(bundles_ext.into_rpc())?; Ok(()) }) diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index f61c39b0..b30bd9f7 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,5 +1,6 @@ pub mod args; pub mod builders; +pub mod bundles; pub mod flashtestations; pub mod gas_limiter; pub mod launcher; diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 75ebb874..f2c9f5f0 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -167,6 +167,12 @@ pub struct OpRBuilderMetrics { pub metering_unknown_transaction: Counter, /// Count of the number of times we were unable to resolve metering information due to locking pub metering_locked_transaction: Counter, + /// Current number of backrun bundles in store + pub backrun_bundles_in_store: Gauge, + /// Number of target transactions found with backrun bundles + pub backrun_target_txs_found_total: Counter, + /// Number of backrun bundles received via RPC + pub backrun_bundles_received_total: Counter, } impl OpRBuilderMetrics { From 5b528924cb1a77560ab8718a38116ce639716c8a Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 17 Dec 2025 13:38:43 -0500 Subject: [PATCH 219/262] replacement --- .../op-rbuilder/src/builders/context.rs | 16 ++ crates/builder/op-rbuilder/src/bundles.rs | 183 +++++++++++------- crates/builder/op-rbuilder/src/metrics.rs | 4 + 3 files changed, 135 insertions(+), 68 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 802576b6..d8a40f35 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -608,6 +608,13 @@ impl OpPayloadBuilderCtx { self.metrics.backrun_target_txs_found_total.increment(1); for stored_bundle in backrun_bundles { + info!( + target: "payload_builder", + message = "Executing backrun bundles", + tx_hash = ?tx_hash, + bundle_id = ?stored_bundle.bundle_id, + ); + for backrun_tx in stored_bundle.backrun_txs { let ResultAndState { result, state } = match evm.transact(&backrun_tx) { Ok(res) => res, @@ -620,6 +627,15 @@ impl OpPayloadBuilderCtx { let is_backrun_success = result.is_success(); if !is_backrun_success { + self.metrics.backrun_txs_reverted_total.increment(1); + info!( + target: "payload_builder", + target_tx = ?tx_hash, + backrun_tx = ?backrun_tx.tx_hash(), + bundle_id = ?stored_bundle.bundle_id, + gas_used = backrun_gas_used, + "Backrun transaction reverted" + ); continue; } diff --git a/crates/builder/op-rbuilder/src/bundles.rs b/crates/builder/op-rbuilder/src/bundles.rs index aaad193d..36eb1b7b 100644 --- a/crates/builder/op-rbuilder/src/bundles.rs +++ b/crates/builder/op-rbuilder/src/bundles.rs @@ -1,14 +1,14 @@ use alloy_consensus::transaction::Recovered; -use alloy_primitives::TxHash; +use alloy_primitives::{Address, TxHash}; use concurrent_queue::ConcurrentQueue; use jsonrpsee::{ core::{RpcResult, async_trait}, proc_macros::rpc, }; use op_alloy_consensus::OpTxEnvelope; -use std::{fmt::Debug, sync::Arc}; -use tips_core::{Bundle, types::ParsedBundle}; -use tracing::{debug, warn}; +use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Instant}; +use tips_core::AcceptedBundle; +use tracing::{debug, info, warn}; use uuid::Uuid; use crate::metrics::OpRBuilderMetrics; @@ -16,11 +16,12 @@ use crate::metrics::OpRBuilderMetrics; #[derive(Clone)] pub struct StoredBackrunBundle { pub bundle_id: Uuid, + pub sender: Address, pub backrun_txs: Vec>, } struct BackrunData { - by_target_tx: dashmap::DashMap>, + by_target_tx: dashmap::DashMap>, lru: ConcurrentQueue, } @@ -49,14 +50,14 @@ impl BackrunBundleStore { } } - pub fn insert(&self, bundle: ParsedBundle, bundle_id: Uuid) -> Result<(), String> { + pub fn insert(&self, bundle: AcceptedBundle) -> Result<(), String> { if bundle.txs.len() < 2 { return Err("Bundle must have at least 2 transactions (target + backrun)".to_string()); } - // Target tx is txs[0], backrun txs are txs[1..] let target_tx_hash = bundle.txs[0].tx_hash(); let backrun_txs: Vec> = bundle.txs[1..].to_vec(); + let backrun_sender = backrun_txs[0].signer(); if self.data.lru.is_full() && let Ok(evicted_hash) = self.data.lru.pop() @@ -72,15 +73,25 @@ impl BackrunBundleStore { let _ = self.data.lru.push(target_tx_hash); let stored_bundle = StoredBackrunBundle { - bundle_id, + bundle_id: *bundle.uuid(), + sender: backrun_sender, backrun_txs: backrun_txs.clone(), }; - self.data - .by_target_tx - .entry(target_tx_hash) - .or_insert_with(Vec::new) - .push(stored_bundle); + let replaced = { + let mut entry = self.data.by_target_tx.entry(target_tx_hash).or_default(); + entry.insert(backrun_sender, stored_bundle).is_some() + }; // entry lock released here + + if replaced { + info!( + target: "backrun_bundles", + target_tx = ?target_tx_hash, + sender = ?backrun_sender, + bundle_id = ?bundle.uuid(), + "Replaced existing backrun bundle from same sender" + ); + } self.metrics .backrun_bundles_in_store @@ -93,7 +104,7 @@ impl BackrunBundleStore { self.data .by_target_tx .get(target_tx_hash) - .map(|entry| entry.clone()) + .map(|entry| entry.values().cloned().collect()) } pub fn remove(&self, target_tx_hash: &TxHash) { @@ -126,7 +137,7 @@ impl Default for BackrunBundleStore { #[cfg_attr(test, rpc(server, client, namespace = "base"))] pub trait BaseBundlesApiExt { #[method(name = "sendBackrunBundle")] - async fn send_backrun_bundle(&self, bundle: Bundle, bundle_id: Uuid) -> RpcResult<()>; + async fn send_backrun_bundle(&self, bundle: AcceptedBundle) -> RpcResult<()>; } pub(crate) struct BundlesApiExt { @@ -145,28 +156,21 @@ impl BundlesApiExt { #[async_trait] impl BaseBundlesApiExtServer for BundlesApiExt { - async fn send_backrun_bundle(&self, bundle: Bundle, bundle_id: Uuid) -> RpcResult<()> { + async fn send_backrun_bundle(&self, bundle: AcceptedBundle) -> RpcResult<()> { self.metrics.backrun_bundles_received_total.increment(1); - let parsed_bundle = ParsedBundle::try_from(bundle).map_err(|e| { - warn!(target: "backrun_bundles", error = %e, "Failed to parse bundle"); + let start = Instant::now(); + self.bundle_store.insert(bundle).map_err(|e| { + warn!(target: "backrun_bundles", error = %e, "Failed to store bundle"); jsonrpsee::types::ErrorObject::owned( - jsonrpsee::types::error::INVALID_PARAMS_CODE, - format!("Failed to parse bundle: {e}"), + jsonrpsee::types::error::INTERNAL_ERROR_CODE, + format!("Failed to store bundle: {e}"), None::<()>, ) })?; - - self.bundle_store - .insert(parsed_bundle, bundle_id) - .map_err(|e| { - warn!(target: "backrun_bundles", error = %e, "Failed to store bundle"); - jsonrpsee::types::ErrorObject::owned( - jsonrpsee::types::error::INTERNAL_ERROR_CODE, - format!("Failed to store bundle: {e}"), - None::<()>, - ) - })?; + self.metrics + .backrun_bundle_insert_duration + .record(start.elapsed().as_secs_f64()); Ok(()) } @@ -176,13 +180,18 @@ impl BaseBundlesApiExtServer for BundlesApiExt { mod tests { use super::*; use alloy_consensus::SignableTransaction; - use alloy_primitives::{Address, Bytes, TxHash, U256}; - use alloy_provider::network::{TxSignerSync, eip2718::Encodable2718}; + use alloy_primitives::{Address, TxHash, U256}; + use alloy_provider::network::TxSignerSync; use alloy_signer_local::PrivateKeySigner; use op_alloy_consensus::OpTxEnvelope; use op_alloy_rpc_types::OpTransactionRequest; + use tips_core::MeterBundleResponse; - fn create_transaction(from: PrivateKeySigner, nonce: u64, to: Address) -> OpTxEnvelope { + fn create_recovered_tx( + from: &PrivateKeySigner, + nonce: u64, + to: Address, + ) -> Recovered { let mut txn = OpTransactionRequest::default() .value(U256::from(10_000)) .gas_limit(21_000) @@ -195,17 +204,36 @@ mod tests { .unwrap(); let sig = from.sign_transaction_sync(&mut txn).unwrap(); - OpTxEnvelope::Eip1559(txn.eip1559().cloned().unwrap().into_signed(sig).clone()) + let envelope = + OpTxEnvelope::Eip1559(txn.eip1559().cloned().unwrap().into_signed(sig).clone()); + Recovered::new_unchecked(envelope, from.address()) } - fn create_test_parsed_bundle(txs: Vec) -> ParsedBundle { - tips_core::Bundle { + fn create_test_accepted_bundle(txs: Vec>) -> AcceptedBundle { + AcceptedBundle { + uuid: Uuid::new_v4(), txs, block_number: 1, - ..Default::default() + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, } - .try_into() - .unwrap() } #[test] @@ -213,44 +241,40 @@ mod tests { let alice = PrivateKeySigner::random(); let bob = PrivateKeySigner::random(); - // Create test transactions - let target_tx = create_transaction(alice.clone(), 0, bob.address()); - let backrun_tx1 = create_transaction(alice.clone(), 1, bob.address()); - let backrun_tx2 = create_transaction(alice.clone(), 2, bob.address()); + let target_tx = create_recovered_tx(&alice, 0, bob.address()); + let backrun_tx1 = create_recovered_tx(&alice, 1, bob.address()); + let backrun_tx2 = create_recovered_tx(&alice, 2, bob.address()); let target_tx_hash = target_tx.tx_hash(); let store = BackrunBundleStore::new(100); // Test insert fails with only 1 tx (need target + at least 1 backrun) - let single_tx_bundle = create_test_parsed_bundle(vec![target_tx.encoded_2718().into()]); - assert!(store.insert(single_tx_bundle, Uuid::new_v4()).is_err()); + let single_tx_bundle = create_test_accepted_bundle(vec![target_tx.clone()]); + assert!(store.insert(single_tx_bundle).is_err()); assert_eq!(store.len(), 0); // Test insert succeeds with 2+ txs - let valid_bundle = create_test_parsed_bundle(vec![ - target_tx.encoded_2718().into(), - backrun_tx1.encoded_2718().into(), - ]); - assert!(store.insert(valid_bundle, Uuid::new_v4()).is_ok()); + let valid_bundle = + create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx1.clone()]); + assert!(store.insert(valid_bundle).is_ok()); assert_eq!(store.len(), 1); // Test get returns the backrun txs (not the target) let retrieved = store.get(&target_tx_hash).unwrap(); - assert_eq!(retrieved.len(), 1); // 1 bundle - assert_eq!(retrieved[0].backrun_txs.len(), 1); // 1 backrun tx in that bundle + assert_eq!(retrieved.len(), 1); + assert_eq!(retrieved[0].backrun_txs.len(), 1); assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx1.tx_hash()); - // Test multiple backrun bundles for same target - let second_bundle = create_test_parsed_bundle(vec![ - target_tx.encoded_2718().into(), - backrun_tx2.encoded_2718().into(), - ]); - assert!(store.insert(second_bundle, Uuid::new_v4()).is_ok()); - assert_eq!(store.len(), 1); // Still 1 target, but 2 backrun bundles + // Test same sender replaces previous bundle (not accumulate) + let replacement_bundle = + create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx2.clone()]); + assert!(store.insert(replacement_bundle).is_ok()); + assert_eq!(store.len(), 1); let retrieved = store.get(&target_tx_hash).unwrap(); - assert_eq!(retrieved.len(), 2); // Now 2 bundles for same target + assert_eq!(retrieved.len(), 1); // Still 1 bundle (replaced, not accumulated) + assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx2.tx_hash()); // New tx // Test remove store.remove(&target_tx_hash); @@ -261,6 +285,32 @@ mod tests { store.remove(&TxHash::ZERO); } + #[test] + fn test_backrun_bundle_store_multiple_senders() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + let charlie = PrivateKeySigner::random(); + + let target_tx = create_recovered_tx(&alice, 0, bob.address()); + let alice_backrun = create_recovered_tx(&alice, 1, bob.address()); + let charlie_backrun = create_recovered_tx(&charlie, 0, bob.address()); + + let target_tx_hash = target_tx.tx_hash(); + let store = BackrunBundleStore::new(100); + + // Alice submits backrun + let alice_bundle = create_test_accepted_bundle(vec![target_tx.clone(), alice_backrun]); + store.insert(alice_bundle).unwrap(); + + // Charlie submits backrun for same target + let charlie_bundle = create_test_accepted_bundle(vec![target_tx.clone(), charlie_backrun]); + store.insert(charlie_bundle).unwrap(); + + // Both bundles should exist (different senders) + let retrieved = store.get(&target_tx_hash).unwrap(); + assert_eq!(retrieved.len(), 2); + } + #[test] fn test_backrun_bundle_store_lru_eviction() { let alice = PrivateKeySigner::random(); @@ -271,13 +321,10 @@ mod tests { // Insert 3 bundles, first should be evicted for nonce in 0..3u64 { - let target = create_transaction(alice.clone(), nonce * 2, bob.address()); - let backrun = create_transaction(alice.clone(), nonce * 2 + 1, bob.address()); - let bundle = create_test_parsed_bundle(vec![ - target.encoded_2718().into(), - backrun.encoded_2718().into(), - ]); - let _ = store.insert(bundle, Uuid::new_v4()); + let target = create_recovered_tx(&alice, nonce * 2, bob.address()); + let backrun = create_recovered_tx(&alice, nonce * 2 + 1, bob.address()); + let bundle = create_test_accepted_bundle(vec![target, backrun]); + let _ = store.insert(bundle); } // Only 2 should remain due to LRU eviction diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index f2c9f5f0..190a6c4f 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -173,6 +173,10 @@ pub struct OpRBuilderMetrics { pub backrun_target_txs_found_total: Counter, /// Number of backrun bundles received via RPC pub backrun_bundles_received_total: Counter, + /// Number of backrun transactions that reverted during execution + pub backrun_txs_reverted_total: Counter, + /// Latency of inserting a backrun bundle into the store + pub backrun_bundle_insert_duration: Histogram, } impl OpRBuilderMetrics { From 0410952bcaf18faf419e0360b113e9efba6e3f94 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 17 Dec 2025 14:03:04 -0500 Subject: [PATCH 220/262] fix fmt --- crates/builder/op-rbuilder/src/builders/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index f6087b0d..4da8be12 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -21,8 +21,7 @@ mod flashblocks; mod generator; mod standard; -use crate::bundles::BackrunBundleStore; -use crate::resource_metering::ResourceMetering; +use crate::{bundles::BackrunBundleStore, resource_metering::ResourceMetering}; pub use builder_tx::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, InvalidContractDataError, SimulationSuccessResult, get_balance, get_nonce, From e9a1bfe1a568e148ce88110e07132e5f03cdccb9 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 17 Dec 2025 15:11:23 -0500 Subject: [PATCH 221/262] add is empty --- crates/builder/op-rbuilder/src/bundles.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/bundles.rs b/crates/builder/op-rbuilder/src/bundles.rs index 36eb1b7b..39fb2a23 100644 --- a/crates/builder/op-rbuilder/src/bundles.rs +++ b/crates/builder/op-rbuilder/src/bundles.rs @@ -8,7 +8,7 @@ use jsonrpsee::{ use op_alloy_consensus::OpTxEnvelope; use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Instant}; use tips_core::AcceptedBundle; -use tracing::{debug, info, warn}; +use tracing::{debug, warn}; use uuid::Uuid; use crate::metrics::OpRBuilderMetrics; @@ -51,6 +51,8 @@ impl BackrunBundleStore { } pub fn insert(&self, bundle: AcceptedBundle) -> Result<(), String> { + warn!("Inserting backrun bundle for tx:"); + if bundle.txs.len() < 2 { return Err("Bundle must have at least 2 transactions (target + backrun)".to_string()); } @@ -83,8 +85,10 @@ impl BackrunBundleStore { entry.insert(backrun_sender, stored_bundle).is_some() }; // entry lock released here + warn!("Replaced: {}", replaced); + if replaced { - info!( + warn!( target: "backrun_bundles", target_tx = ?target_tx_hash, sender = ?backrun_sender, @@ -125,6 +129,10 @@ impl BackrunBundleStore { pub fn len(&self) -> usize { self.data.by_target_tx.len() } + + pub fn is_empty(&self) -> bool { + self.data.by_target_tx.is_empty() + } } impl Default for BackrunBundleStore { From f8ba64b6948dbbb7ad1923bf3e1797c6b4646118 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 17 Dec 2025 16:19:45 -0500 Subject: [PATCH 222/262] remove not needed logs --- crates/builder/op-rbuilder/src/bundles.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/builder/op-rbuilder/src/bundles.rs b/crates/builder/op-rbuilder/src/bundles.rs index 39fb2a23..1c917b8b 100644 --- a/crates/builder/op-rbuilder/src/bundles.rs +++ b/crates/builder/op-rbuilder/src/bundles.rs @@ -8,7 +8,7 @@ use jsonrpsee::{ use op_alloy_consensus::OpTxEnvelope; use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Instant}; use tips_core::AcceptedBundle; -use tracing::{debug, warn}; +use tracing::{debug, info, warn}; use uuid::Uuid; use crate::metrics::OpRBuilderMetrics; @@ -51,8 +51,6 @@ impl BackrunBundleStore { } pub fn insert(&self, bundle: AcceptedBundle) -> Result<(), String> { - warn!("Inserting backrun bundle for tx:"); - if bundle.txs.len() < 2 { return Err("Bundle must have at least 2 transactions (target + backrun)".to_string()); } @@ -83,12 +81,10 @@ impl BackrunBundleStore { let replaced = { let mut entry = self.data.by_target_tx.entry(target_tx_hash).or_default(); entry.insert(backrun_sender, stored_bundle).is_some() - }; // entry lock released here - - warn!("Replaced: {}", replaced); + }; if replaced { - warn!( + info!( target: "backrun_bundles", target_tx = ?target_tx_hash, sender = ?backrun_sender, From 00c81b0e0f6806da83fdb37864a16f988731c25c Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 18 Dec 2025 15:48:04 -0500 Subject: [PATCH 223/262] switch considering transaction to info --- crates/builder/op-rbuilder/src/builders/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index d8a40f35..58abcf01 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -435,7 +435,7 @@ impl OpPayloadBuilderCtx { is_bundle_tx && !reverted_hashes.unwrap().contains(&tx_hash); let log_txn = |result: TxnExecutionResult| { - debug!( + info!( target: "payload_builder", message = "Considering transaction", tx_hash = ?tx_hash, From ad787ecf174a571e518d40a3f80fc397b52909f8 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 19 Dec 2025 11:18:25 -0500 Subject: [PATCH 224/262] sort by priority fee; add backrun integration test --- .../op-rbuilder/src/builders/context.rs | 12 +- .../builder/op-rbuilder/src/tests/backrun.rs | 136 ++++++++++++++++++ .../src/tests/framework/instance.rs | 8 ++ crates/builder/op-rbuilder/src/tests/mod.rs | 3 + 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 crates/builder/op-rbuilder/src/tests/backrun.rs diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 58abcf01..c9aa8200 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -607,7 +607,14 @@ impl OpPayloadBuilderCtx { if is_success && let Some(backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) { self.metrics.backrun_target_txs_found_total.increment(1); - for stored_bundle in backrun_bundles { + for mut stored_bundle in backrun_bundles { + // Sort backrun txs by priority fee (highest first) + stored_bundle.backrun_txs.sort_unstable_by(|a, b| { + let a_tip = a.effective_tip_per_gas(base_fee).unwrap_or(0); + let b_tip = b.effective_tip_per_gas(base_fee).unwrap_or(0); + b_tip.cmp(&a_tip) + }); + info!( target: "payload_builder", message = "Executing backrun bundles", @@ -662,6 +669,9 @@ impl OpPayloadBuilderCtx { info.executed_transactions.push(backrun_tx.into_inner()); } } + + // Remove the target tx from the backrun bundle store as already executed + self.backrun_bundle_store.remove(&tx_hash); } } diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs new file mode 100644 index 00000000..241df02e --- /dev/null +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -0,0 +1,136 @@ +use crate::tests::{ChainDriverExt, LocalInstance, framework::ONE_ETH}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{TxHash, U256}; +use alloy_provider::Provider; +use macros::rb_test; +use tips_core::{AcceptedBundle, MeterBundleResponse}; +use uuid::Uuid; + +/// Tests that backrun bundles are executed correctly: +/// - Backrun txs are included in the block after their target tx +/// - Backrun txs within a bundle are sorted by priority fee (highest first) +/// - The final block maintains correct ordering +#[rb_test(flashblocks)] +async fn backrun_bundles_execution(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(3, ONE_ETH).await?; + + // 1. Build target tx first (we need Recovered for bundle) + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + // Send to mempool manually (send() doesn't return the Recovered tx) + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // 2. Create backrun transactions with different priority fees + // We intentionally create backrun_low first to verify sorting reorders them + let backrun_low = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(10) + .build() + .await; + let backrun_low_hash = backrun_low.tx_hash(); + + let backrun_high = driver + .create_transaction() + .with_signer(accounts[2]) + .with_max_priority_fee_per_gas(50) + .build() + .await; + let backrun_high_hash = backrun_high.tx_hash(); + + // 3. Insert backrun bundle into store + // Bundle format: [target_tx, backrun_txs...] + // We include backrun_low BEFORE backrun_high to verify sorting reorders them + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun_low, backrun_high], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .backrun_bundle_store() + .insert(bundle) + .expect("Failed to insert backrun bundle"); + + // 5. Build the block + driver.build_new_block().await?; + + // 6. Verify block contents + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + // Target tx should be in block + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx not included in block" + ); + + // Both backrun txs should be in block + assert!( + tx_hashes.contains(&backrun_low_hash), + "Backrun low priority tx not included in block" + ); + assert!( + tx_hashes.contains(&backrun_high_hash), + "Backrun high priority tx not included in block" + ); + + // 7. Verify ordering: target < backrun_high < backrun_low + // (high priority fee should come before low priority fee) + let target_pos = tx_hashes + .iter() + .position(|h| *h == target_tx_hash) + .expect("Target tx position not found"); + let high_pos = tx_hashes + .iter() + .position(|h| *h == backrun_high_hash) + .expect("Backrun high position not found"); + let low_pos = tx_hashes + .iter() + .position(|h| *h == backrun_low_hash) + .expect("Backrun low position not found"); + + assert!( + target_pos < high_pos, + "Target tx (pos {}) should come before high priority backrun (pos {})", + target_pos, + high_pos + ); + assert!( + high_pos < low_pos, + "High priority backrun (pos {}) should come before low priority backrun (pos {})", + high_pos, + low_pos + ); + + Ok(()) +} diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 8c718491..a7611b8e 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -1,6 +1,7 @@ use crate::{ args::OpRbuilderArgs, builders::{BuilderConfig, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, + bundles::BackrunBundleStore, primitives::reth::engine_api_builder::OpEngineApiBuilder, revert_protection::{EthApiExtServer, RevertProtectionExt}, tests::{ @@ -64,6 +65,7 @@ pub struct LocalInstance { _node_handle: Box, pool_observer: TransactionPoolObserver, attestation_server: Option, + backrun_bundle_store: BackrunBundleStore, } impl LocalInstance { @@ -112,6 +114,7 @@ impl LocalInstance { .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(); + let backrun_bundle_store = builder_config.backrun_bundle_store.clone(); let addons: OpAddOns< _, @@ -187,6 +190,7 @@ impl LocalInstance { task_manager: Some(task_manager), pool_observer: TransactionPoolObserver::new(pool_monitor, reverted_cache_clone), attestation_server, + backrun_bundle_store, }) } @@ -267,6 +271,10 @@ impl LocalInstance { &self.attestation_server } + pub fn backrun_bundle_store(&self) -> &BackrunBundleStore { + &self.backrun_bundle_store + } + pub async fn driver(&self) -> eyre::Result> { ChainDriver::::local(self).await } diff --git a/crates/builder/op-rbuilder/src/tests/mod.rs b/crates/builder/op-rbuilder/src/tests/mod.rs index fd202a89..87384e5c 100644 --- a/crates/builder/op-rbuilder/src/tests/mod.rs +++ b/crates/builder/op-rbuilder/src/tests/mod.rs @@ -17,6 +17,9 @@ mod miner_gas_limit; #[cfg(test)] mod gas_limiter; +#[cfg(test)] +mod backrun; + #[cfg(test)] mod ordering; From 8e22f949cb129ad992d8edd2c4f2dec3bd92c0ac Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 19 Dec 2025 11:34:26 -0500 Subject: [PATCH 225/262] support all or nothing bundles --- .../op-rbuilder/src/builders/context.rs | 48 ++++---- crates/builder/op-rbuilder/src/metrics.rs | 6 +- .../builder/op-rbuilder/src/tests/backrun.rs | 103 ++++++++++++++++++ 3 files changed, 136 insertions(+), 21 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index c9aa8200..d5c2c2e6 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -607,7 +607,15 @@ impl OpPayloadBuilderCtx { if is_success && let Some(backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) { self.metrics.backrun_target_txs_found_total.increment(1); - for mut stored_bundle in backrun_bundles { + 'bundle_loop: for mut stored_bundle in backrun_bundles { + info!( + target: "payload_builder", + message = "Executing backrun bundle", + tx_hash = ?tx_hash, + bundle_id = ?stored_bundle.bundle_id, + tx_count = stored_bundle.backrun_txs.len(), + ); + // Sort backrun txs by priority fee (highest first) stored_bundle.backrun_txs.sort_unstable_by(|a, b| { let a_tip = a.effective_tip_per_gas(base_fee).unwrap_or(0); @@ -615,37 +623,36 @@ impl OpPayloadBuilderCtx { b_tip.cmp(&a_tip) }); - info!( - target: "payload_builder", - message = "Executing backrun bundles", - tx_hash = ?tx_hash, - bundle_id = ?stored_bundle.bundle_id, - ); + // All-or-nothing: simulate all txs first, only commit if all succeed + let mut pending_results = Vec::with_capacity(stored_bundle.backrun_txs.len()); - for backrun_tx in stored_bundle.backrun_txs { - let ResultAndState { result, state } = match evm.transact(&backrun_tx) { + for backrun_tx in &stored_bundle.backrun_txs { + let ResultAndState { result, state } = match evm.transact(backrun_tx) { Ok(res) => res, Err(err) => { return Err(PayloadBuilderError::evm(err)); } }; - let backrun_gas_used = result.gas_used(); - let is_backrun_success = result.is_success(); - - if !is_backrun_success { - self.metrics.backrun_txs_reverted_total.increment(1); + if !result.is_success() { + self.metrics.backrun_bundles_reverted_total.increment(1); info!( target: "payload_builder", target_tx = ?tx_hash, - backrun_tx = ?backrun_tx.tx_hash(), + failed_tx = ?backrun_tx.tx_hash(), bundle_id = ?stored_bundle.bundle_id, - gas_used = backrun_gas_used, - "Backrun transaction reverted" + gas_used = result.gas_used(), + "Backrun bundle reverted (all-or-nothing)" ); - continue; + continue 'bundle_loop; } + pending_results.push((backrun_tx, result, state)); + } + + for (backrun_tx, result, state) in pending_results { + let backrun_gas_used = result.gas_used(); + info.cumulative_gas_used += backrun_gas_used; info.cumulative_da_bytes_used += backrun_tx.encoded_2718().len() as u64; @@ -666,8 +673,11 @@ impl OpPayloadBuilderCtx { info.total_fees += U256::from(miner_fee) * U256::from(backrun_gas_used); info.executed_senders.push(backrun_tx.signer()); - info.executed_transactions.push(backrun_tx.into_inner()); + info.executed_transactions + .push(backrun_tx.clone().into_inner()); } + + self.metrics.backrun_bundles_landed_total.increment(1); } // Remove the target tx from the backrun bundle store as already executed diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 190a6c4f..8e1be222 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -173,8 +173,10 @@ pub struct OpRBuilderMetrics { pub backrun_target_txs_found_total: Counter, /// Number of backrun bundles received via RPC pub backrun_bundles_received_total: Counter, - /// Number of backrun transactions that reverted during execution - pub backrun_txs_reverted_total: Counter, + /// Number of backrun bundles that reverted during execution (all-or-nothing) + pub backrun_bundles_reverted_total: Counter, + /// Number of backrun bundles successfully landed in a block + pub backrun_bundles_landed_total: Counter, /// Latency of inserting a backrun bundle into the store pub backrun_bundle_insert_duration: Histogram, } diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index 241df02e..704bed05 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -134,3 +134,106 @@ async fn backrun_bundles_execution(rbuilder: LocalInstance) -> eyre::Result<()> Ok(()) } + +/// Tests that backrun bundles are all-or-nothing: +/// - If any backrun tx in a bundle reverts, the entire bundle is excluded +/// - Even successful txs in the bundle are not included +#[rb_test(flashblocks)] +async fn backrun_bundle_all_or_nothing_revert(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(3, ONE_ETH).await?; + + // 1. Build target tx first (we need Recovered for bundle) + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + // Send to mempool manually (send() doesn't return the Recovered tx) + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // 2. Create backrun transactions: + // - backrun_ok: valid tx with HIGH priority (executes first, succeeds) + // - backrun_revert: tx that will REVERT with LOW priority (executes second, fails) + let backrun_ok = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) // High priority - executes first + .build() + .await; + let backrun_ok_hash = backrun_ok.tx_hash().clone(); + + let backrun_revert = driver + .create_transaction() + .with_signer(accounts[2]) + .with_max_priority_fee_per_gas(10) // Low priority - executes second + .with_revert() // This tx will revert + .build() + .await; + let backrun_revert_hash = backrun_revert.tx_hash().clone(); + + // 3. Insert backrun bundle into store + // Bundle format: [target_tx, backrun_txs...] + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun_ok, backrun_revert], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .backrun_bundle_store() + .insert(bundle) + .expect("Failed to insert backrun bundle"); + + // 4. Build the block + driver.build_new_block().await?; + + // 5. Verify block contents + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + // Target tx SHOULD be in block (it was in mempool independently) + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + // backrun_ok should NOT be in block (all-or-nothing: bundle failed) + assert!( + !tx_hashes.contains(&backrun_ok_hash), + "backrun_ok should NOT be in block (all-or-nothing revert)" + ); + + // backrun_revert should NOT be in block (it caused the revert) + assert!( + !tx_hashes.contains(&backrun_revert_hash), + "backrun_revert should NOT be in block" + ); + + Ok(()) +} From 6364ae8251bd23b5e8897b5a03ead3ceae987d14 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 19 Dec 2025 11:45:27 -0500 Subject: [PATCH 226/262] priority fee validation --- .../op-rbuilder/src/builders/context.rs | 24 ++++ crates/builder/op-rbuilder/src/metrics.rs | 4 + .../builder/op-rbuilder/src/tests/backrun.rs | 109 +++++++++++++++++- 3 files changed, 133 insertions(+), 4 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index d5c2c2e6..69ed01c6 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -606,6 +606,7 @@ impl OpPayloadBuilderCtx { if is_success && let Some(backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) { self.metrics.backrun_target_txs_found_total.increment(1); + let backrun_start_time = Instant::now(); 'bundle_loop: for mut stored_bundle in backrun_bundles { info!( @@ -623,6 +624,25 @@ impl OpPayloadBuilderCtx { b_tip.cmp(&a_tip) }); + // Validate: all backrun txs must have priority fee >= target tx's priority fee + if let Some(lowest_fee_tx) = stored_bundle.backrun_txs.last() { + let lowest_backrun_fee = + lowest_fee_tx.effective_tip_per_gas(base_fee).unwrap_or(0); + if lowest_backrun_fee < miner_fee { + self.metrics + .backrun_bundles_rejected_low_fee_total + .increment(1); + info!( + target: "payload_builder", + bundle_id = ?stored_bundle.bundle_id, + target_fee = miner_fee, + lowest_backrun_fee = lowest_backrun_fee, + "Backrun bundle rejected: priority fee below target tx" + ); + continue 'bundle_loop; + } + } + // All-or-nothing: simulate all txs first, only commit if all succeed let mut pending_results = Vec::with_capacity(stored_bundle.backrun_txs.len()); @@ -680,6 +700,10 @@ impl OpPayloadBuilderCtx { self.metrics.backrun_bundles_landed_total.increment(1); } + self.metrics + .backrun_bundle_execution_duration + .record(backrun_start_time.elapsed()); + // Remove the target tx from the backrun bundle store as already executed self.backrun_bundle_store.remove(&tx_hash); } diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 8e1be222..8d3554b6 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -175,10 +175,14 @@ pub struct OpRBuilderMetrics { pub backrun_bundles_received_total: Counter, /// Number of backrun bundles that reverted during execution (all-or-nothing) pub backrun_bundles_reverted_total: Counter, + /// Number of backrun bundles rejected due to priority fee below target tx + pub backrun_bundles_rejected_low_fee_total: Counter, /// Number of backrun bundles successfully landed in a block pub backrun_bundles_landed_total: Counter, /// Latency of inserting a backrun bundle into the store pub backrun_bundle_insert_duration: Histogram, + /// Duration of executing all backrun bundles for a target transaction + pub backrun_bundle_execution_duration: Histogram, } impl OpRBuilderMetrics { diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index 704bed05..d7e083eb 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -31,11 +31,12 @@ async fn backrun_bundles_execution(rbuilder: LocalInstance) -> eyre::Result<()> .await?; // 2. Create backrun transactions with different priority fees + // Both must have priority fee >= target's (20) to pass validation // We intentionally create backrun_low first to verify sorting reorders them let backrun_low = driver .create_transaction() .with_signer(accounts[1]) - .with_max_priority_fee_per_gas(10) + .with_max_priority_fee_per_gas(25) // >= target's 20 .build() .await; let backrun_low_hash = backrun_low.tx_hash(); @@ -43,7 +44,7 @@ async fn backrun_bundles_execution(rbuilder: LocalInstance) -> eyre::Result<()> let backrun_high = driver .create_transaction() .with_signer(accounts[2]) - .with_max_priority_fee_per_gas(50) + .with_max_priority_fee_per_gas(50) // >= target's 20 .build() .await; let backrun_high_hash = backrun_high.tx_hash(); @@ -160,7 +161,8 @@ async fn backrun_bundle_all_or_nothing_revert(rbuilder: LocalInstance) -> eyre:: // 2. Create backrun transactions: // - backrun_ok: valid tx with HIGH priority (executes first, succeeds) - // - backrun_revert: tx that will REVERT with LOW priority (executes second, fails) + // - backrun_revert: tx that will REVERT (executes second, fails) + // Both must have priority fee >= target's (20) to pass fee validation let backrun_ok = driver .create_transaction() .with_signer(accounts[1]) @@ -172,7 +174,7 @@ async fn backrun_bundle_all_or_nothing_revert(rbuilder: LocalInstance) -> eyre:: let backrun_revert = driver .create_transaction() .with_signer(accounts[2]) - .with_max_priority_fee_per_gas(10) // Low priority - executes second + .with_max_priority_fee_per_gas(25) // >= target's 20, but executes second (lower than 50) .with_revert() // This tx will revert .build() .await; @@ -237,3 +239,102 @@ async fn backrun_bundle_all_or_nothing_revert(rbuilder: LocalInstance) -> eyre:: Ok(()) } + +/// Tests that backrun bundles are rejected if any backrun tx has priority fee < target tx +#[rb_test(flashblocks)] +async fn backrun_bundle_rejected_low_priority_fee(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(3, ONE_ETH).await?; + + // 1. Build target tx with priority fee 20 + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + // Send to mempool manually + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // 2. Create backrun transactions: + // - backrun_ok: priority fee 50 (>= target's 20) ✓ + // - backrun_low_fee: priority fee 10 (< target's 20) ✗ + let backrun_ok = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) + .build() + .await; + let backrun_ok_hash = backrun_ok.tx_hash().clone(); + + let backrun_low_fee = driver + .create_transaction() + .with_signer(accounts[2]) + .with_max_priority_fee_per_gas(10) // < target's 20, should cause rejection + .build() + .await; + let backrun_low_fee_hash = backrun_low_fee.tx_hash().clone(); + + // 3. Insert backrun bundle into store + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun_ok, backrun_low_fee], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .backrun_bundle_store() + .insert(bundle) + .expect("Failed to insert backrun bundle"); + + // 4. Build the block + driver.build_new_block().await?; + + // 5. Verify block contents + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + // Target tx SHOULD be in block (it was in mempool independently) + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + // backrun_ok should NOT be in block (bundle rejected due to low fee tx) + assert!( + !tx_hashes.contains(&backrun_ok_hash), + "backrun_ok should NOT be in block (bundle rejected: low priority fee)" + ); + + // backrun_low_fee should NOT be in block (it caused the rejection) + assert!( + !tx_hashes.contains(&backrun_low_fee_hash), + "backrun_low_fee should NOT be in block" + ); + + Ok(()) +} From 1aac2787ee9f1bf692892eb63c8f46d6378c2ae4 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 19 Dec 2025 11:57:09 -0500 Subject: [PATCH 227/262] add bundle sorting --- .../op-rbuilder/src/builders/context.rs | 20 +- .../builder/op-rbuilder/src/tests/backrun.rs | 184 ++++++++++++++++++ 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 69ed01c6..05de8157 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -604,10 +604,26 @@ impl OpPayloadBuilderCtx { info.executed_senders.push(tx.signer()); info.executed_transactions.push(tx.into_inner()); - if is_success && let Some(backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) { + if is_success && let Some(mut backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) + { self.metrics.backrun_target_txs_found_total.increment(1); let backrun_start_time = Instant::now(); + // Sort bundles by total priority fee (descending) + backrun_bundles.sort_by(|a, b| { + let a_total: u128 = a + .backrun_txs + .iter() + .map(|tx| tx.effective_tip_per_gas(base_fee).unwrap_or(0)) + .sum(); + let b_total: u128 = b + .backrun_txs + .iter() + .map(|tx| tx.effective_tip_per_gas(base_fee).unwrap_or(0)) + .sum(); + b_total.cmp(&a_total) + }); + 'bundle_loop: for mut stored_bundle in backrun_bundles { info!( target: "payload_builder", @@ -617,7 +633,7 @@ impl OpPayloadBuilderCtx { tx_count = stored_bundle.backrun_txs.len(), ); - // Sort backrun txs by priority fee (highest first) + // Sort backrun txs by priority fee (descending) stored_bundle.backrun_txs.sort_unstable_by(|a, b| { let a_tip = a.effective_tip_per_gas(base_fee).unwrap_or(0); let b_tip = b.effective_tip_per_gas(base_fee).unwrap_or(0); diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index d7e083eb..514577a3 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -240,6 +240,190 @@ async fn backrun_bundle_all_or_nothing_revert(rbuilder: LocalInstance) -> eyre:: Ok(()) } +/// Tests that multiple backrun bundles for the same target tx are sorted by total priority fee +/// - Bundles with higher total priority fee are processed first +/// - Both bundles can land if they don't conflict +#[rb_test(flashblocks)] +async fn backrun_bundles_sorted_by_total_fee(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(5, ONE_ETH).await?; + + // 1. Build target tx with priority fee 20 + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + // Send to mempool manually + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // 2. Create Bundle A with HIGH total priority fee + // Two txs: 60 + 50 = 110 total + let bundle_a_tx1 = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(60) + .build() + .await; + let bundle_a_tx1_hash = bundle_a_tx1.tx_hash().clone(); + + let bundle_a_tx2 = driver + .create_transaction() + .with_signer(accounts[2]) + .with_max_priority_fee_per_gas(50) + .build() + .await; + let bundle_a_tx2_hash = bundle_a_tx2.tx_hash().clone(); + + // 3. Create Bundle B with LOW total priority fee + // Two txs: 30 + 25 = 55 total + let bundle_b_tx1 = driver + .create_transaction() + .with_signer(accounts[3]) + .with_max_priority_fee_per_gas(30) + .build() + .await; + let bundle_b_tx1_hash = bundle_b_tx1.tx_hash().clone(); + + let bundle_b_tx2 = driver + .create_transaction() + .with_signer(accounts[4]) + .with_max_priority_fee_per_gas(25) + .build() + .await; + let bundle_b_tx2_hash = bundle_b_tx2.tx_hash().clone(); + + // 4. Insert Bundle B FIRST (lower total fee), then Bundle A (higher total fee) + // This verifies that sorting reorders them correctly + let bundle_b = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx.clone(), bundle_b_tx1, bundle_b_tx2], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + let bundle_a = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, bundle_a_tx1, bundle_a_tx2], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + // Insert in "wrong" order - B first, then A + rbuilder + .backrun_bundle_store() + .insert(bundle_b) + .expect("Failed to insert bundle B"); + rbuilder + .backrun_bundle_store() + .insert(bundle_a) + .expect("Failed to insert bundle A"); + + // 5. Build the block + driver.build_new_block().await?; + + // 6. Verify block contents + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + // All txs should be in block + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx not included in block" + ); + assert!( + tx_hashes.contains(&bundle_a_tx1_hash), + "Bundle A tx1 not included in block" + ); + assert!( + tx_hashes.contains(&bundle_a_tx2_hash), + "Bundle A tx2 not included in block" + ); + assert!( + tx_hashes.contains(&bundle_b_tx1_hash), + "Bundle B tx1 not included in block" + ); + assert!( + tx_hashes.contains(&bundle_b_tx2_hash), + "Bundle B tx2 not included in block" + ); + + // 7. Verify ordering: Bundle A txs come BEFORE Bundle B txs + // (higher total fee bundle processed first) + let a_tx1_pos = tx_hashes + .iter() + .position(|h| *h == bundle_a_tx1_hash) + .expect("Bundle A tx1 position not found"); + let a_tx2_pos = tx_hashes + .iter() + .position(|h| *h == bundle_a_tx2_hash) + .expect("Bundle A tx2 position not found"); + let b_tx1_pos = tx_hashes + .iter() + .position(|h| *h == bundle_b_tx1_hash) + .expect("Bundle B tx1 position not found"); + let b_tx2_pos = tx_hashes + .iter() + .position(|h| *h == bundle_b_tx2_hash) + .expect("Bundle B tx2 position not found"); + + // Bundle A (higher total fee) should come before Bundle B + let bundle_a_last_pos = a_tx1_pos.max(a_tx2_pos); + let bundle_b_first_pos = b_tx1_pos.min(b_tx2_pos); + + assert!( + bundle_a_last_pos < bundle_b_first_pos, + "Bundle A (total fee 110) should be processed before Bundle B (total fee 55). \ + Bundle A last tx at pos {}, Bundle B first tx at pos {}", + bundle_a_last_pos, + bundle_b_first_pos + ); + + Ok(()) +} + /// Tests that backrun bundles are rejected if any backrun tx has priority fee < target tx #[rb_test(flashblocks)] async fn backrun_bundle_rejected_low_priority_fee(rbuilder: LocalInstance) -> eyre::Result<()> { From 62dbb4143643ba3d0030768fbc0de763ce3c14f4 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 19 Dec 2025 12:09:48 -0500 Subject: [PATCH 228/262] use bundle total fee for validation --- .../op-rbuilder/src/builders/context.rs | 68 ++++--- .../builder/op-rbuilder/src/tests/backrun.rs | 171 +++--------------- 2 files changed, 52 insertions(+), 187 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 05de8157..b3d16bd2 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -604,27 +604,25 @@ impl OpPayloadBuilderCtx { info.executed_senders.push(tx.signer()); info.executed_transactions.push(tx.into_inner()); - if is_success && let Some(mut backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) - { + if is_success && let Some(backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) { self.metrics.backrun_target_txs_found_total.increment(1); let backrun_start_time = Instant::now(); - // Sort bundles by total priority fee (descending) - backrun_bundles.sort_by(|a, b| { - let a_total: u128 = a - .backrun_txs - .iter() - .map(|tx| tx.effective_tip_per_gas(base_fee).unwrap_or(0)) - .sum(); - let b_total: u128 = b - .backrun_txs - .iter() - .map(|tx| tx.effective_tip_per_gas(base_fee).unwrap_or(0)) - .sum(); - b_total.cmp(&a_total) - }); - - 'bundle_loop: for mut stored_bundle in backrun_bundles { + // Pre-compute total fees and sort bundles (descending) + let mut bundles_with_fees: Vec<_> = backrun_bundles + .into_iter() + .map(|bundle| { + let total_fee: u128 = bundle + .backrun_txs + .iter() + .map(|tx| tx.effective_tip_per_gas(base_fee).unwrap_or(0)) + .sum(); + (bundle, total_fee) + }) + .collect(); + bundles_with_fees.sort_by(|a, b| b.1.cmp(&a.1)); + + 'bundle_loop: for (mut stored_bundle, total_bundle_fee) in bundles_with_fees { info!( target: "payload_builder", message = "Executing backrun bundle", @@ -633,6 +631,21 @@ impl OpPayloadBuilderCtx { tx_count = stored_bundle.backrun_txs.len(), ); + // Validate: total bundle priority fee must be >= target tx's priority fee + if total_bundle_fee < miner_fee { + self.metrics + .backrun_bundles_rejected_low_fee_total + .increment(1); + info!( + target: "payload_builder", + bundle_id = ?stored_bundle.bundle_id, + target_fee = miner_fee, + total_bundle_fee = total_bundle_fee, + "Backrun bundle rejected: total priority fee below target tx" + ); + continue 'bundle_loop; + } + // Sort backrun txs by priority fee (descending) stored_bundle.backrun_txs.sort_unstable_by(|a, b| { let a_tip = a.effective_tip_per_gas(base_fee).unwrap_or(0); @@ -640,25 +653,6 @@ impl OpPayloadBuilderCtx { b_tip.cmp(&a_tip) }); - // Validate: all backrun txs must have priority fee >= target tx's priority fee - if let Some(lowest_fee_tx) = stored_bundle.backrun_txs.last() { - let lowest_backrun_fee = - lowest_fee_tx.effective_tip_per_gas(base_fee).unwrap_or(0); - if lowest_backrun_fee < miner_fee { - self.metrics - .backrun_bundles_rejected_low_fee_total - .increment(1); - info!( - target: "payload_builder", - bundle_id = ?stored_bundle.bundle_id, - target_fee = miner_fee, - lowest_backrun_fee = lowest_backrun_fee, - "Backrun bundle rejected: priority fee below target tx" - ); - continue 'bundle_loop; - } - } - // All-or-nothing: simulate all txs first, only commit if all succeed let mut pending_results = Vec::with_capacity(stored_bundle.backrun_txs.len()); diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index 514577a3..6759c051 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -6,136 +6,6 @@ use macros::rb_test; use tips_core::{AcceptedBundle, MeterBundleResponse}; use uuid::Uuid; -/// Tests that backrun bundles are executed correctly: -/// - Backrun txs are included in the block after their target tx -/// - Backrun txs within a bundle are sorted by priority fee (highest first) -/// - The final block maintains correct ordering -#[rb_test(flashblocks)] -async fn backrun_bundles_execution(rbuilder: LocalInstance) -> eyre::Result<()> { - let driver = rbuilder.driver().await?; - let accounts = driver.fund_accounts(3, ONE_ETH).await?; - - // 1. Build target tx first (we need Recovered for bundle) - let target_tx = driver - .create_transaction() - .with_signer(accounts[0]) - .with_max_priority_fee_per_gas(20) - .build() - .await; - let target_tx_hash = target_tx.tx_hash().clone(); - - // Send to mempool manually (send() doesn't return the Recovered tx) - let provider = rbuilder.provider().await?; - let _ = provider - .send_raw_transaction(target_tx.encoded_2718().as_slice()) - .await?; - - // 2. Create backrun transactions with different priority fees - // Both must have priority fee >= target's (20) to pass validation - // We intentionally create backrun_low first to verify sorting reorders them - let backrun_low = driver - .create_transaction() - .with_signer(accounts[1]) - .with_max_priority_fee_per_gas(25) // >= target's 20 - .build() - .await; - let backrun_low_hash = backrun_low.tx_hash(); - - let backrun_high = driver - .create_transaction() - .with_signer(accounts[2]) - .with_max_priority_fee_per_gas(50) // >= target's 20 - .build() - .await; - let backrun_high_hash = backrun_high.tx_hash(); - - // 3. Insert backrun bundle into store - // Bundle format: [target_tx, backrun_txs...] - // We include backrun_low BEFORE backrun_high to verify sorting reorders them - let bundle = AcceptedBundle { - uuid: Uuid::new_v4(), - txs: vec![target_tx, backrun_low, backrun_high], - block_number: driver.latest().await?.header.number + 1, - flashblock_number_min: None, - flashblock_number_max: None, - min_timestamp: None, - max_timestamp: None, - reverting_tx_hashes: vec![], - replacement_uuid: None, - dropping_tx_hashes: vec![], - meter_bundle_response: MeterBundleResponse { - bundle_gas_price: U256::ZERO, - bundle_hash: TxHash::ZERO, - coinbase_diff: U256::ZERO, - eth_sent_to_coinbase: U256::ZERO, - gas_fees: U256::ZERO, - results: vec![], - state_block_number: 0, - state_flashblock_index: None, - total_gas_used: 0, - total_execution_time_us: 0, - }, - }; - - rbuilder - .backrun_bundle_store() - .insert(bundle) - .expect("Failed to insert backrun bundle"); - - // 5. Build the block - driver.build_new_block().await?; - - // 6. Verify block contents - let block = driver.latest_full().await?; - let tx_hashes: Vec<_> = block.transactions.hashes().collect(); - - // Target tx should be in block - assert!( - tx_hashes.contains(&target_tx_hash), - "Target tx not included in block" - ); - - // Both backrun txs should be in block - assert!( - tx_hashes.contains(&backrun_low_hash), - "Backrun low priority tx not included in block" - ); - assert!( - tx_hashes.contains(&backrun_high_hash), - "Backrun high priority tx not included in block" - ); - - // 7. Verify ordering: target < backrun_high < backrun_low - // (high priority fee should come before low priority fee) - let target_pos = tx_hashes - .iter() - .position(|h| *h == target_tx_hash) - .expect("Target tx position not found"); - let high_pos = tx_hashes - .iter() - .position(|h| *h == backrun_high_hash) - .expect("Backrun high position not found"); - let low_pos = tx_hashes - .iter() - .position(|h| *h == backrun_low_hash) - .expect("Backrun low position not found"); - - assert!( - target_pos < high_pos, - "Target tx (pos {}) should come before high priority backrun (pos {})", - target_pos, - high_pos - ); - assert!( - high_pos < low_pos, - "High priority backrun (pos {}) should come before low priority backrun (pos {})", - high_pos, - low_pos - ); - - Ok(()) -} - /// Tests that backrun bundles are all-or-nothing: /// - If any backrun tx in a bundle reverts, the entire bundle is excluded /// - Even successful txs in the bundle are not included @@ -424,17 +294,17 @@ async fn backrun_bundles_sorted_by_total_fee(rbuilder: LocalInstance) -> eyre::R Ok(()) } -/// Tests that backrun bundles are rejected if any backrun tx has priority fee < target tx +/// Tests that backrun bundles are rejected if total bundle priority fee < target tx priority fee #[rb_test(flashblocks)] -async fn backrun_bundle_rejected_low_priority_fee(rbuilder: LocalInstance) -> eyre::Result<()> { +async fn backrun_bundle_rejected_low_total_fee(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let accounts = driver.fund_accounts(3, ONE_ETH).await?; - // 1. Build target tx with priority fee 20 + // 1. Build target tx with HIGH priority fee (100) let target_tx = driver .create_transaction() .with_signer(accounts[0]) - .with_max_priority_fee_per_gas(20) + .with_max_priority_fee_per_gas(100) .build() .await; let target_tx_hash = target_tx.tx_hash().clone(); @@ -445,29 +315,30 @@ async fn backrun_bundle_rejected_low_priority_fee(rbuilder: LocalInstance) -> ey .send_raw_transaction(target_tx.encoded_2718().as_slice()) .await?; - // 2. Create backrun transactions: - // - backrun_ok: priority fee 50 (>= target's 20) ✓ - // - backrun_low_fee: priority fee 10 (< target's 20) ✗ - let backrun_ok = driver + // 2. Create backrun transactions with LOW total fee: + // - backrun_1: priority fee 30 + // - backrun_2: priority fee 20 + // - Total: 30 + 20 = 50 < target's 100 → bundle rejected + let backrun_1 = driver .create_transaction() .with_signer(accounts[1]) - .with_max_priority_fee_per_gas(50) + .with_max_priority_fee_per_gas(30) .build() .await; - let backrun_ok_hash = backrun_ok.tx_hash().clone(); + let backrun_1_hash = backrun_1.tx_hash().clone(); - let backrun_low_fee = driver + let backrun_2 = driver .create_transaction() .with_signer(accounts[2]) - .with_max_priority_fee_per_gas(10) // < target's 20, should cause rejection + .with_max_priority_fee_per_gas(20) .build() .await; - let backrun_low_fee_hash = backrun_low_fee.tx_hash().clone(); + let backrun_2_hash = backrun_2.tx_hash().clone(); // 3. Insert backrun bundle into store let bundle = AcceptedBundle { uuid: Uuid::new_v4(), - txs: vec![target_tx, backrun_ok, backrun_low_fee], + txs: vec![target_tx, backrun_1, backrun_2], block_number: driver.latest().await?.header.number + 1, flashblock_number_min: None, flashblock_number_max: None, @@ -508,16 +379,16 @@ async fn backrun_bundle_rejected_low_priority_fee(rbuilder: LocalInstance) -> ey "Target tx should be included in block" ); - // backrun_ok should NOT be in block (bundle rejected due to low fee tx) + // backrun_1 should NOT be in block (bundle rejected: total fee 50 < target fee 100) assert!( - !tx_hashes.contains(&backrun_ok_hash), - "backrun_ok should NOT be in block (bundle rejected: low priority fee)" + !tx_hashes.contains(&backrun_1_hash), + "backrun_1 should NOT be in block (bundle rejected: total fee below target)" ); - // backrun_low_fee should NOT be in block (it caused the rejection) + // backrun_2 should NOT be in block (bundle rejected) assert!( - !tx_hashes.contains(&backrun_low_fee_hash), - "backrun_low_fee should NOT be in block" + !tx_hashes.contains(&backrun_2_hash), + "backrun_2 should NOT be in block" ); Ok(()) From 9a2bc01544be0cb0a62b41979d5900f4d00abd88 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 13:06:04 -0500 Subject: [PATCH 229/262] refactor --- .../op-rbuilder/src/builders/context.rs | 70 +++++--- crates/builder/op-rbuilder/src/bundles.rs | 21 ++- crates/builder/op-rbuilder/src/metrics.rs | 2 + .../builder/op-rbuilder/src/tests/backrun.rs | 167 ++++++++++++++++++ 4 files changed, 227 insertions(+), 33 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index b3d16bd2..769128af 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -435,7 +435,7 @@ impl OpPayloadBuilderCtx { is_bundle_tx && !reverted_hashes.unwrap().contains(&tx_hash); let log_txn = |result: TxnExecutionResult| { - info!( + debug!( target: "payload_builder", message = "Considering transaction", tx_hash = ?tx_hash, @@ -608,22 +608,9 @@ impl OpPayloadBuilderCtx { self.metrics.backrun_target_txs_found_total.increment(1); let backrun_start_time = Instant::now(); - // Pre-compute total fees and sort bundles (descending) - let mut bundles_with_fees: Vec<_> = backrun_bundles - .into_iter() - .map(|bundle| { - let total_fee: u128 = bundle - .backrun_txs - .iter() - .map(|tx| tx.effective_tip_per_gas(base_fee).unwrap_or(0)) - .sum(); - (bundle, total_fee) - }) - .collect(); - bundles_with_fees.sort_by(|a, b| b.1.cmp(&a.1)); - - 'bundle_loop: for (mut stored_bundle, total_bundle_fee) in bundles_with_fees { - info!( + // Bundles are pre-sorted by total_priority_fee (descending) from the store + 'bundle_loop: for stored_bundle in backrun_bundles { + debug!( target: "payload_builder", message = "Executing backrun bundle", tx_hash = ?tx_hash, @@ -631,8 +618,12 @@ impl OpPayloadBuilderCtx { tx_count = stored_bundle.backrun_txs.len(), ); - // Validate: total bundle priority fee must be >= target tx's priority fee - if total_bundle_fee < miner_fee { + let total_effective_tip: u128 = stored_bundle + .backrun_txs + .iter() + .map(|tx| tx.effective_tip_per_gas(base_fee).unwrap_or(0)) + .sum(); + if total_effective_tip < miner_fee { self.metrics .backrun_bundles_rejected_low_fee_total .increment(1); @@ -640,18 +631,43 @@ impl OpPayloadBuilderCtx { target: "payload_builder", bundle_id = ?stored_bundle.bundle_id, target_fee = miner_fee, - total_bundle_fee = total_bundle_fee, - "Backrun bundle rejected: total priority fee below target tx" + total_effective_tip = total_effective_tip, + "Backrun bundle rejected: total effective tip below target tx" ); continue 'bundle_loop; } - // Sort backrun txs by priority fee (descending) - stored_bundle.backrun_txs.sort_unstable_by(|a, b| { - let a_tip = a.effective_tip_per_gas(base_fee).unwrap_or(0); - let b_tip = b.effective_tip_per_gas(base_fee).unwrap_or(0); - b_tip.cmp(&a_tip) - }); + let total_backrun_gas: u64 = stored_bundle + .backrun_txs + .iter() + .map(|tx| tx.gas_limit()) + .sum(); + let total_backrun_da_size: u64 = stored_bundle + .backrun_txs + .iter() + .map(|tx| tx.encoded_2718().len() as u64) + .sum(); + + if let Err(result) = info.is_tx_over_limits( + total_backrun_da_size, + block_gas_limit, + tx_da_limit, + block_da_limit, + total_backrun_gas, + info.da_footprint_scalar, + block_da_footprint_limit, + ) { + self.metrics + .backrun_bundles_rejected_over_limits_total + .increment(1); + info!( + target: "payload_builder", + bundle_id = ?stored_bundle.bundle_id, + result = ?result, + "Backrun bundle rejected: exceeds block limits" + ); + continue 'bundle_loop; + } // All-or-nothing: simulate all txs first, only commit if all succeed let mut pending_results = Vec::with_capacity(stored_bundle.backrun_txs.len()); diff --git a/crates/builder/op-rbuilder/src/bundles.rs b/crates/builder/op-rbuilder/src/bundles.rs index 1c917b8b..25c222df 100644 --- a/crates/builder/op-rbuilder/src/bundles.rs +++ b/crates/builder/op-rbuilder/src/bundles.rs @@ -1,4 +1,4 @@ -use alloy_consensus::transaction::Recovered; +use alloy_consensus::{Transaction, transaction::Recovered}; use alloy_primitives::{Address, TxHash}; use concurrent_queue::ConcurrentQueue; use jsonrpsee::{ @@ -18,6 +18,7 @@ pub struct StoredBackrunBundle { pub bundle_id: Uuid, pub sender: Address, pub backrun_txs: Vec>, + pub total_priority_fee: u128, } struct BackrunData { @@ -72,10 +73,16 @@ impl BackrunBundleStore { let _ = self.data.lru.push(target_tx_hash); + let total_priority_fee: u128 = backrun_txs + .iter() + .map(|tx| tx.max_priority_fee_per_gas().unwrap_or(0)) + .sum(); + let stored_bundle = StoredBackrunBundle { bundle_id: *bundle.uuid(), sender: backrun_sender, - backrun_txs: backrun_txs.clone(), + backrun_txs, + total_priority_fee, }; let replaced = { @@ -101,10 +108,12 @@ impl BackrunBundleStore { } pub fn get(&self, target_tx_hash: &TxHash) -> Option> { - self.data - .by_target_tx - .get(target_tx_hash) - .map(|entry| entry.values().cloned().collect()) + self.data.by_target_tx.get(target_tx_hash).map(|entry| { + let mut bundles: Vec<_> = entry.values().cloned().collect(); + // Sort bundles by total_priority_fee (descending) + bundles.sort_by(|a, b| b.total_priority_fee.cmp(&a.total_priority_fee)); + bundles + }) } pub fn remove(&self, target_tx_hash: &TxHash) { diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index 8d3554b6..a6166816 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -177,6 +177,8 @@ pub struct OpRBuilderMetrics { pub backrun_bundles_reverted_total: Counter, /// Number of backrun bundles rejected due to priority fee below target tx pub backrun_bundles_rejected_low_fee_total: Counter, + /// Number of backrun bundles rejected due to exceeding block limits + pub backrun_bundles_rejected_over_limits_total: Counter, /// Number of backrun bundles successfully landed in a block pub backrun_bundles_landed_total: Counter, /// Latency of inserting a backrun bundle into the store diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index 6759c051..0416d4ee 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -393,3 +393,170 @@ async fn backrun_bundle_rejected_low_total_fee(rbuilder: LocalInstance) -> eyre: Ok(()) } + +#[rb_test(flashblocks)] +async fn backrun_bundle_rejected_exceeds_gas_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(2, ONE_ETH).await?; + + // Set gas limit high enough for builder tx + target tx, but not backrun + // Flashblocks has additional overhead, so use higher limits + // Set limit to 500k, backrun requests 1M -> rejected + driver + .provider() + .raw_request::<(u64,), bool>("miner_setGasLimit".into(), (500_000,)) + .await?; + + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + let backrun = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) + .with_gas_limit(1_000_000) + .build() + .await; + let backrun_hash = backrun.tx_hash().clone(); + + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .backrun_bundle_store() + .insert(bundle) + .expect("Failed to insert backrun bundle"); + + driver.build_new_block().await?; + + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + assert!( + !tx_hashes.contains(&backrun_hash), + "Backrun should NOT be in block (exceeds gas limit)" + ); + + Ok(()) +} + +#[rb_test(flashblocks)] +async fn backrun_bundle_rejected_exceeds_da_limit(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(2, ONE_ETH).await?; + + // Set DA limit high enough for builder tx + target tx, but not backrun + // Flashblocks has additional overhead, so use higher limits + // Set block limit to 500 bytes, then create a backrun with large calldata + driver + .provider() + .raw_request::<(i32, i32), bool>("miner_setMaxDASize".into(), (0, 500)) + .await?; + + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // Create backrun with large calldata to exceed DA limit + let backrun = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) + .with_input(vec![0u8; 1000].into()) + .build() + .await; + let backrun_hash = backrun.tx_hash().clone(); + + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .backrun_bundle_store() + .insert(bundle) + .expect("Failed to insert backrun bundle"); + + driver.build_new_block().await?; + + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + assert!( + !tx_hashes.contains(&backrun_hash), + "Backrun should NOT be in block (exceeds DA limit)" + ); + + Ok(()) +} From bb3b5bf8b947521a20a37025e148e82b3b4de6d1 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 13:42:55 -0500 Subject: [PATCH 230/262] remove key by sender --- crates/builder/op-rbuilder/src/bundles.rs | 25 +++++++++++++++-------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/crates/builder/op-rbuilder/src/bundles.rs b/crates/builder/op-rbuilder/src/bundles.rs index 25c222df..fb8a652a 100644 --- a/crates/builder/op-rbuilder/src/bundles.rs +++ b/crates/builder/op-rbuilder/src/bundles.rs @@ -6,7 +6,7 @@ use jsonrpsee::{ proc_macros::rpc, }; use op_alloy_consensus::OpTxEnvelope; -use std::{collections::HashMap, fmt::Debug, sync::Arc, time::Instant}; +use std::{fmt::Debug, sync::Arc, time::Instant}; use tips_core::AcceptedBundle; use tracing::{debug, info, warn}; use uuid::Uuid; @@ -22,7 +22,7 @@ pub struct StoredBackrunBundle { } struct BackrunData { - by_target_tx: dashmap::DashMap>, + by_target_tx: dashmap::DashMap>, lru: ConcurrentQueue, } @@ -87,7 +87,16 @@ impl BackrunBundleStore { let replaced = { let mut entry = self.data.by_target_tx.entry(target_tx_hash).or_default(); - entry.insert(backrun_sender, stored_bundle).is_some() + let replaced = if let Some(pos) = entry.iter().position(|b| b.sender == backrun_sender) + { + entry[pos] = stored_bundle; + true + } else { + entry.push(stored_bundle); + false + }; + entry.sort_by(|a, b| b.total_priority_fee.cmp(&a.total_priority_fee)); + replaced }; if replaced { @@ -108,12 +117,10 @@ impl BackrunBundleStore { } pub fn get(&self, target_tx_hash: &TxHash) -> Option> { - self.data.by_target_tx.get(target_tx_hash).map(|entry| { - let mut bundles: Vec<_> = entry.values().cloned().collect(); - // Sort bundles by total_priority_fee (descending) - bundles.sort_by(|a, b| b.total_priority_fee.cmp(&a.total_priority_fee)); - bundles - }) + self.data + .by_target_tx + .get(target_tx_hash) + .map(|entry| entry.clone()) } pub fn remove(&self, target_tx_hash: &TxHash) { From 4458907725422c1031a714561eab46a91b207170 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 13:46:43 -0500 Subject: [PATCH 231/262] skip checking bundles if failed validation --- crates/builder/op-rbuilder/src/builders/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 769128af..1ada5463 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -634,7 +634,7 @@ impl OpPayloadBuilderCtx { total_effective_tip = total_effective_tip, "Backrun bundle rejected: total effective tip below target tx" ); - continue 'bundle_loop; + break 'bundle_loop; } let total_backrun_gas: u64 = stored_bundle From 222463d81ee940e72242f5ae14967b247e1528cf Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 14:08:25 -0500 Subject: [PATCH 232/262] merge resrouce metering and backrun bundle into one --- .../op-rbuilder/src/builders/context.rs | 16 +- .../src/builders/flashblocks/ctx.rs | 15 +- .../src/builders/flashblocks/payload.rs | 3 +- .../builder/op-rbuilder/src/builders/mod.rs | 20 +- .../src/builders/standard/payload.rs | 3 +- crates/builder/op-rbuilder/src/bundles.rs | 353 ------------ crates/builder/op-rbuilder/src/launcher.rs | 13 +- crates/builder/op-rbuilder/src/lib.rs | 3 +- .../op-rbuilder/src/resource_metering.rs | 218 ------- .../builder/op-rbuilder/src/tests/backrun.rs | 24 +- .../src/tests/framework/instance.rs | 12 +- .../builder/op-rbuilder/src/tx_data_store.rs | 533 ++++++++++++++++++ 12 files changed, 579 insertions(+), 634 deletions(-) delete mode 100644 crates/builder/op-rbuilder/src/bundles.rs delete mode 100644 crates/builder/op-rbuilder/src/resource_metering.rs create mode 100644 crates/builder/op-rbuilder/src/tx_data_store.rs diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 1ada5463..68003c5e 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -43,9 +43,9 @@ use crate::{ gas_limiter::AddressGasLimiter, metrics::OpRBuilderMetrics, primitives::reth::{ExecutionInfo, TxnExecutionResult}, - resource_metering::ResourceMetering, traits::PayloadTxsBounds, tx::MaybeRevertingTransaction, + tx_data_store::TxDataStore, tx_signer::Signer, }; @@ -78,10 +78,8 @@ pub struct OpPayloadBuilderCtx { pub max_gas_per_txn: Option, /// Rate limiting based on gas. This is an optional feature. pub address_gas_limiter: AddressGasLimiter, - /// Per transaction resource metering information - pub resource_metering: ResourceMetering, - /// Backrun bundle store for storing backrun transactions - pub backrun_bundle_store: crate::bundles::BackrunBundleStore, + /// Unified transaction data store (backrun bundles + resource metering) + pub tx_data_store: TxDataStore, } impl OpPayloadBuilderCtx { @@ -447,7 +445,7 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; - let _resource_usage = self.resource_metering.get(&tx_hash); + let _resource_usage = self.tx_data_store.get_metering(&tx_hash); // TODO: ideally we should get this from the txpool stream if let Some(conditional) = conditional @@ -604,7 +602,9 @@ impl OpPayloadBuilderCtx { info.executed_senders.push(tx.signer()); info.executed_transactions.push(tx.into_inner()); - if is_success && let Some(backrun_bundles) = self.backrun_bundle_store.get(&tx_hash) { + if is_success + && let Some(backrun_bundles) = self.tx_data_store.get_backrun_bundles(&tx_hash) + { self.metrics.backrun_target_txs_found_total.increment(1); let backrun_start_time = Instant::now(); @@ -731,7 +731,7 @@ impl OpPayloadBuilderCtx { .record(backrun_start_time.elapsed()); // Remove the target tx from the backrun bundle store as already executed - self.backrun_bundle_store.remove(&tx_hash); + self.tx_data_store.remove_backrun_bundles(&tx_hash); } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs index 47e9d5ee..5f7f46cb 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/ctx.rs @@ -1,10 +1,9 @@ use crate::{ builders::{BuilderConfig, OpPayloadBuilderCtx, flashblocks::FlashblocksConfig}, - bundles::BackrunBundleStore, gas_limiter::{AddressGasLimiter, args::GasLimiterArgs}, metrics::OpRBuilderMetrics, - resource_metering::ResourceMetering, traits::ClientBounds, + tx_data_store::TxDataStore, }; use op_revm::OpSpecId; use reth_basic_payload_builder::PayloadConfig; @@ -31,10 +30,8 @@ pub(super) struct OpPayloadSyncerCtx { max_gas_per_txn: Option, /// The metrics for the builder metrics: Arc, - /// Resource metering tracking - resource_metering: ResourceMetering, - /// Backrun bundle store - backrun_bundle_store: BackrunBundleStore, + /// Unified transaction data store (backrun bundles + resource metering) + tx_data_store: TxDataStore, } impl OpPayloadSyncerCtx { @@ -54,8 +51,7 @@ impl OpPayloadSyncerCtx { chain_spec, max_gas_per_txn: builder_config.max_gas_per_txn, metrics, - resource_metering: builder_config.resource_metering, - backrun_bundle_store: builder_config.backrun_bundle_store, + tx_data_store: builder_config.tx_data_store, }) } @@ -88,8 +84,7 @@ impl OpPayloadSyncerCtx { extra_ctx: (), max_gas_per_txn: self.max_gas_per_txn, address_gas_limiter: AddressGasLimiter::new(GasLimiterArgs::default()), - resource_metering: self.resource_metering.clone(), - backrun_bundle_store: self.backrun_bundle_store.clone(), + tx_data_store: self.tx_data_store.clone(), } } } diff --git a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs index 6318b156..9562f53d 100644 --- a/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/flashblocks/payload.rs @@ -282,8 +282,7 @@ where extra_ctx, max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), - resource_metering: self.config.resource_metering.clone(), - backrun_bundle_store: self.config.backrun_bundle_store.clone(), + tx_data_store: self.config.tx_data_store.clone(), }) } diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 4da8be12..85a5c098 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -21,7 +21,7 @@ mod flashblocks; mod generator; mod standard; -use crate::{bundles::BackrunBundleStore, resource_metering::ResourceMetering}; +use crate::tx_data_store::TxDataStore; pub use builder_tx::{ BuilderTransactionCtx, BuilderTransactionError, BuilderTransactions, InvalidContractDataError, SimulationSuccessResult, get_balance, get_nonce, @@ -128,11 +128,8 @@ pub struct BuilderConfig { /// Address gas limiter stuff pub gas_limiter_config: GasLimiterArgs, - /// Resource metering context - pub resource_metering: ResourceMetering, - - /// Backrun bundle store for storing backrun transactions - pub backrun_bundle_store: BackrunBundleStore, + /// Unified transaction data store (backrun bundles + resource metering) + pub tx_data_store: TxDataStore, } impl core::fmt::Debug for BuilderConfig { @@ -155,7 +152,7 @@ impl core::fmt::Debug for BuilderConfig { .field("specific", &self.specific) .field("max_gas_per_txn", &self.max_gas_per_txn) .field("gas_limiter_config", &self.gas_limiter_config) - .field("backrun_bundle_store", &self.backrun_bundle_store) + .field("tx_data_store", &self.tx_data_store) .finish() } } @@ -174,8 +171,7 @@ impl Default for BuilderConfig { sampling_ratio: 100, max_gas_per_txn: None, gas_limiter_config: GasLimiterArgs::default(), - resource_metering: ResourceMetering::default(), - backrun_bundle_store: BackrunBundleStore::default(), + tx_data_store: TxDataStore::default(), } } } @@ -198,11 +194,11 @@ where sampling_ratio: args.telemetry.sampling_ratio, max_gas_per_txn: args.max_gas_per_txn, gas_limiter_config: args.gas_limiter.clone(), - resource_metering: ResourceMetering::new( + tx_data_store: TxDataStore::new( args.enable_resource_metering, - args.resource_metering_buffer_size, + args.resource_metering_buffer_size + .max(args.backrun_bundle_buffer_size), ), - backrun_bundle_store: BackrunBundleStore::new(args.backrun_bundle_buffer_size), specific: S::try_from(args)?, }) } diff --git a/crates/builder/op-rbuilder/src/builders/standard/payload.rs b/crates/builder/op-rbuilder/src/builders/standard/payload.rs index 4e94a89f..ed81640e 100644 --- a/crates/builder/op-rbuilder/src/builders/standard/payload.rs +++ b/crates/builder/op-rbuilder/src/builders/standard/payload.rs @@ -251,8 +251,7 @@ where extra_ctx: Default::default(), max_gas_per_txn: self.config.max_gas_per_txn, address_gas_limiter: self.address_gas_limiter.clone(), - resource_metering: self.config.resource_metering.clone(), - backrun_bundle_store: self.config.backrun_bundle_store.clone(), + tx_data_store: self.config.tx_data_store.clone(), }; let builder = OpBuilder::new(best); diff --git a/crates/builder/op-rbuilder/src/bundles.rs b/crates/builder/op-rbuilder/src/bundles.rs deleted file mode 100644 index fb8a652a..00000000 --- a/crates/builder/op-rbuilder/src/bundles.rs +++ /dev/null @@ -1,353 +0,0 @@ -use alloy_consensus::{Transaction, transaction::Recovered}; -use alloy_primitives::{Address, TxHash}; -use concurrent_queue::ConcurrentQueue; -use jsonrpsee::{ - core::{RpcResult, async_trait}, - proc_macros::rpc, -}; -use op_alloy_consensus::OpTxEnvelope; -use std::{fmt::Debug, sync::Arc, time::Instant}; -use tips_core::AcceptedBundle; -use tracing::{debug, info, warn}; -use uuid::Uuid; - -use crate::metrics::OpRBuilderMetrics; - -#[derive(Clone)] -pub struct StoredBackrunBundle { - pub bundle_id: Uuid, - pub sender: Address, - pub backrun_txs: Vec>, - pub total_priority_fee: u128, -} - -struct BackrunData { - by_target_tx: dashmap::DashMap>, - lru: ConcurrentQueue, -} - -#[derive(Clone)] -pub struct BackrunBundleStore { - data: Arc, - metrics: OpRBuilderMetrics, -} - -impl Debug for BackrunBundleStore { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("BackrunBundleStore") - .field("by_target_tx_count", &self.data.by_target_tx.len()) - .finish() - } -} - -impl BackrunBundleStore { - pub fn new(buffer_size: usize) -> Self { - Self { - data: Arc::new(BackrunData { - by_target_tx: dashmap::DashMap::new(), - lru: ConcurrentQueue::bounded(buffer_size), - }), - metrics: OpRBuilderMetrics::default(), - } - } - - pub fn insert(&self, bundle: AcceptedBundle) -> Result<(), String> { - if bundle.txs.len() < 2 { - return Err("Bundle must have at least 2 transactions (target + backrun)".to_string()); - } - - let target_tx_hash = bundle.txs[0].tx_hash(); - let backrun_txs: Vec> = bundle.txs[1..].to_vec(); - let backrun_sender = backrun_txs[0].signer(); - - if self.data.lru.is_full() - && let Ok(evicted_hash) = self.data.lru.pop() - { - self.data.by_target_tx.remove(&evicted_hash); - warn!( - target: "backrun_bundles", - evicted_target = ?evicted_hash, - "Evicted old backrun bundle" - ); - } - - let _ = self.data.lru.push(target_tx_hash); - - let total_priority_fee: u128 = backrun_txs - .iter() - .map(|tx| tx.max_priority_fee_per_gas().unwrap_or(0)) - .sum(); - - let stored_bundle = StoredBackrunBundle { - bundle_id: *bundle.uuid(), - sender: backrun_sender, - backrun_txs, - total_priority_fee, - }; - - let replaced = { - let mut entry = self.data.by_target_tx.entry(target_tx_hash).or_default(); - let replaced = if let Some(pos) = entry.iter().position(|b| b.sender == backrun_sender) - { - entry[pos] = stored_bundle; - true - } else { - entry.push(stored_bundle); - false - }; - entry.sort_by(|a, b| b.total_priority_fee.cmp(&a.total_priority_fee)); - replaced - }; - - if replaced { - info!( - target: "backrun_bundles", - target_tx = ?target_tx_hash, - sender = ?backrun_sender, - bundle_id = ?bundle.uuid(), - "Replaced existing backrun bundle from same sender" - ); - } - - self.metrics - .backrun_bundles_in_store - .set(self.data.by_target_tx.len() as f64); - - Ok(()) - } - - pub fn get(&self, target_tx_hash: &TxHash) -> Option> { - self.data - .by_target_tx - .get(target_tx_hash) - .map(|entry| entry.clone()) - } - - pub fn remove(&self, target_tx_hash: &TxHash) { - if let Some((_, bundles)) = self.data.by_target_tx.remove(target_tx_hash) { - debug!( - target: "backrun_bundles", - target_tx = ?target_tx_hash, - bundle_count = bundles.len(), - "Removed backrun bundles" - ); - - self.metrics - .backrun_bundles_in_store - .set(self.data.by_target_tx.len() as f64); - } - } - - pub fn len(&self) -> usize { - self.data.by_target_tx.len() - } - - pub fn is_empty(&self) -> bool { - self.data.by_target_tx.is_empty() - } -} - -impl Default for BackrunBundleStore { - fn default() -> Self { - Self::new(10_000) - } -} - -#[cfg_attr(not(test), rpc(server, namespace = "base"))] -#[cfg_attr(test, rpc(server, client, namespace = "base"))] -pub trait BaseBundlesApiExt { - #[method(name = "sendBackrunBundle")] - async fn send_backrun_bundle(&self, bundle: AcceptedBundle) -> RpcResult<()>; -} - -pub(crate) struct BundlesApiExt { - bundle_store: BackrunBundleStore, - metrics: OpRBuilderMetrics, -} - -impl BundlesApiExt { - pub(crate) fn new(bundle_store: BackrunBundleStore) -> Self { - Self { - bundle_store, - metrics: OpRBuilderMetrics::default(), - } - } -} - -#[async_trait] -impl BaseBundlesApiExtServer for BundlesApiExt { - async fn send_backrun_bundle(&self, bundle: AcceptedBundle) -> RpcResult<()> { - self.metrics.backrun_bundles_received_total.increment(1); - - let start = Instant::now(); - self.bundle_store.insert(bundle).map_err(|e| { - warn!(target: "backrun_bundles", error = %e, "Failed to store bundle"); - jsonrpsee::types::ErrorObject::owned( - jsonrpsee::types::error::INTERNAL_ERROR_CODE, - format!("Failed to store bundle: {e}"), - None::<()>, - ) - })?; - self.metrics - .backrun_bundle_insert_duration - .record(start.elapsed().as_secs_f64()); - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_consensus::SignableTransaction; - use alloy_primitives::{Address, TxHash, U256}; - use alloy_provider::network::TxSignerSync; - use alloy_signer_local::PrivateKeySigner; - use op_alloy_consensus::OpTxEnvelope; - use op_alloy_rpc_types::OpTransactionRequest; - use tips_core::MeterBundleResponse; - - fn create_recovered_tx( - from: &PrivateKeySigner, - nonce: u64, - to: Address, - ) -> Recovered { - let mut txn = OpTransactionRequest::default() - .value(U256::from(10_000)) - .gas_limit(21_000) - .max_fee_per_gas(200) - .max_priority_fee_per_gas(100) - .from(from.address()) - .to(to) - .nonce(nonce) - .build_typed_tx() - .unwrap(); - - let sig = from.sign_transaction_sync(&mut txn).unwrap(); - let envelope = - OpTxEnvelope::Eip1559(txn.eip1559().cloned().unwrap().into_signed(sig).clone()); - Recovered::new_unchecked(envelope, from.address()) - } - - fn create_test_accepted_bundle(txs: Vec>) -> AcceptedBundle { - AcceptedBundle { - uuid: Uuid::new_v4(), - txs, - block_number: 1, - flashblock_number_min: None, - flashblock_number_max: None, - min_timestamp: None, - max_timestamp: None, - reverting_tx_hashes: vec![], - replacement_uuid: None, - dropping_tx_hashes: vec![], - meter_bundle_response: MeterBundleResponse { - bundle_gas_price: U256::ZERO, - bundle_hash: TxHash::ZERO, - coinbase_diff: U256::ZERO, - eth_sent_to_coinbase: U256::ZERO, - gas_fees: U256::ZERO, - results: vec![], - state_block_number: 0, - state_flashblock_index: None, - total_gas_used: 0, - total_execution_time_us: 0, - }, - } - } - - #[test] - fn test_backrun_bundle_store() { - let alice = PrivateKeySigner::random(); - let bob = PrivateKeySigner::random(); - - let target_tx = create_recovered_tx(&alice, 0, bob.address()); - let backrun_tx1 = create_recovered_tx(&alice, 1, bob.address()); - let backrun_tx2 = create_recovered_tx(&alice, 2, bob.address()); - - let target_tx_hash = target_tx.tx_hash(); - - let store = BackrunBundleStore::new(100); - - // Test insert fails with only 1 tx (need target + at least 1 backrun) - let single_tx_bundle = create_test_accepted_bundle(vec![target_tx.clone()]); - assert!(store.insert(single_tx_bundle).is_err()); - assert_eq!(store.len(), 0); - - // Test insert succeeds with 2+ txs - let valid_bundle = - create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx1.clone()]); - assert!(store.insert(valid_bundle).is_ok()); - assert_eq!(store.len(), 1); - - // Test get returns the backrun txs (not the target) - let retrieved = store.get(&target_tx_hash).unwrap(); - assert_eq!(retrieved.len(), 1); - assert_eq!(retrieved[0].backrun_txs.len(), 1); - assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx1.tx_hash()); - - // Test same sender replaces previous bundle (not accumulate) - let replacement_bundle = - create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx2.clone()]); - assert!(store.insert(replacement_bundle).is_ok()); - assert_eq!(store.len(), 1); - - let retrieved = store.get(&target_tx_hash).unwrap(); - assert_eq!(retrieved.len(), 1); // Still 1 bundle (replaced, not accumulated) - assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx2.tx_hash()); // New tx - - // Test remove - store.remove(&target_tx_hash); - assert_eq!(store.len(), 0); - assert!(store.get(&target_tx_hash).is_none()); - - // Test remove on non-existent key doesn't panic - store.remove(&TxHash::ZERO); - } - - #[test] - fn test_backrun_bundle_store_multiple_senders() { - let alice = PrivateKeySigner::random(); - let bob = PrivateKeySigner::random(); - let charlie = PrivateKeySigner::random(); - - let target_tx = create_recovered_tx(&alice, 0, bob.address()); - let alice_backrun = create_recovered_tx(&alice, 1, bob.address()); - let charlie_backrun = create_recovered_tx(&charlie, 0, bob.address()); - - let target_tx_hash = target_tx.tx_hash(); - let store = BackrunBundleStore::new(100); - - // Alice submits backrun - let alice_bundle = create_test_accepted_bundle(vec![target_tx.clone(), alice_backrun]); - store.insert(alice_bundle).unwrap(); - - // Charlie submits backrun for same target - let charlie_bundle = create_test_accepted_bundle(vec![target_tx.clone(), charlie_backrun]); - store.insert(charlie_bundle).unwrap(); - - // Both bundles should exist (different senders) - let retrieved = store.get(&target_tx_hash).unwrap(); - assert_eq!(retrieved.len(), 2); - } - - #[test] - fn test_backrun_bundle_store_lru_eviction() { - let alice = PrivateKeySigner::random(); - let bob = PrivateKeySigner::random(); - - // Small buffer to test eviction - let store = BackrunBundleStore::new(2); - - // Insert 3 bundles, first should be evicted - for nonce in 0..3u64 { - let target = create_recovered_tx(&alice, nonce * 2, bob.address()); - let backrun = create_recovered_tx(&alice, nonce * 2 + 1, bob.address()); - let bundle = create_test_accepted_bundle(vec![target, backrun]); - let _ = store.insert(bundle); - } - - // Only 2 should remain due to LRU eviction - assert_eq!(store.len(), 2); - } -} diff --git a/crates/builder/op-rbuilder/src/launcher.rs b/crates/builder/op-rbuilder/src/launcher.rs index 27fa04f3..45add569 100644 --- a/crates/builder/op-rbuilder/src/launcher.rs +++ b/crates/builder/op-rbuilder/src/launcher.rs @@ -4,13 +4,12 @@ use reth_optimism_rpc::OpEthApiBuilder; use crate::{ args::*, builders::{BuilderConfig, BuilderMode, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, - bundles::{BaseBundlesApiExtServer, BundlesApiExt}, metrics::{VERSION, record_flag_gauge_metrics}, monitor_tx_pool::monitor_tx_pool, primitives::reth::engine_api_builder::OpEngineApiBuilder, - resource_metering::{BaseApiExtServer, ResourceMeteringExt}, revert_protection::{EthApiExtServer, RevertProtectionExt}, tx::FBPooledTransaction, + tx_data_store::{BaseApiExtServer, TxDataStoreExt}, }; use core::fmt::Debug; use moka::future::Cache; @@ -111,8 +110,7 @@ where let op_node = OpNode::new(rollup_args.clone()); let reverted_cache = Cache::builder().max_capacity(100).build(); let reverted_cache_copy = reverted_cache.clone(); - let resource_metering = builder_config.resource_metering.clone(); - let backrun_bundle_store = builder_config.backrun_bundle_store.clone(); + let tx_data_store = builder_config.tx_data_store.clone(); let mut addons: OpAddOns< _, @@ -168,12 +166,9 @@ where .add_or_replace_configured(revert_protection_ext.into_rpc())?; } - let resource_metering_ext = ResourceMeteringExt::new(resource_metering); - let bundles_ext = BundlesApiExt::new(backrun_bundle_store); + let tx_data_store_ext = TxDataStoreExt::new(tx_data_store); ctx.modules - .add_or_replace_configured(resource_metering_ext.into_rpc())?; - ctx.modules - .add_or_replace_configured(bundles_ext.into_rpc())?; + .add_or_replace_configured(tx_data_store_ext.into_rpc())?; Ok(()) }) diff --git a/crates/builder/op-rbuilder/src/lib.rs b/crates/builder/op-rbuilder/src/lib.rs index b30bd9f7..20d2a647 100644 --- a/crates/builder/op-rbuilder/src/lib.rs +++ b/crates/builder/op-rbuilder/src/lib.rs @@ -1,6 +1,5 @@ pub mod args; pub mod builders; -pub mod bundles; pub mod flashtestations; pub mod gas_limiter; pub mod launcher; @@ -10,10 +9,10 @@ pub mod primitives; pub mod revert_protection; pub mod traits; pub mod tx; +pub mod tx_data_store; pub mod tx_signer; #[cfg(test)] pub mod mock_tx; -mod resource_metering; #[cfg(any(test, feature = "testing"))] pub mod tests; diff --git a/crates/builder/op-rbuilder/src/resource_metering.rs b/crates/builder/op-rbuilder/src/resource_metering.rs deleted file mode 100644 index 21d68f5e..00000000 --- a/crates/builder/op-rbuilder/src/resource_metering.rs +++ /dev/null @@ -1,218 +0,0 @@ -use crate::metrics::OpRBuilderMetrics; -use alloy_primitives::TxHash; -use concurrent_queue::{ConcurrentQueue, PopError}; -use dashmap::try_result::TryResult; -use jsonrpsee::{ - core::{RpcResult, async_trait}, - proc_macros::rpc, -}; -use std::{ - fmt::Debug, - sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - }, -}; -use tips_core::MeterBundleResponse; - -struct Data { - enabled: AtomicBool, - by_tx_hash: dashmap::DashMap, - lru: ConcurrentQueue, -} - -#[derive(Clone)] -pub struct ResourceMetering { - data: Arc, - metrics: OpRBuilderMetrics, -} - -impl Debug for ResourceMetering { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ResourceMetering") - .field("enabled", &self.data.enabled) - .field("by_tx_hash", &self.data.by_tx_hash.len()) - .finish() - } -} - -impl ResourceMetering { - pub(crate) fn insert(&self, tx: TxHash, metering_info: MeterBundleResponse) { - let to_remove = if self.data.lru.is_full() { - match self.data.lru.pop() { - Ok(tx_hash) => Some(tx_hash), - Err(PopError::Empty) => None, - Err(PopError::Closed) => None, - } - } else { - None - }; - - if let Some(tx_hash) = to_remove { - self.data.by_tx_hash.remove(&tx_hash); - } - - self.data.by_tx_hash.insert(tx, metering_info); - } - - pub(crate) fn clear(&self) { - self.data.by_tx_hash.clear(); - } - - pub(crate) fn set_enabled(&self, enabled: bool) { - self.data.enabled.store(enabled, Ordering::Relaxed); - } - - pub(crate) fn get(&self, tx: &TxHash) -> Option { - if !self.data.enabled.load(Ordering::Relaxed) { - return None; - } - - match self.data.by_tx_hash.try_get(tx) { - TryResult::Present(result) => { - self.metrics.metering_known_transaction.increment(1); - Some(result.clone()) - } - TryResult::Absent => { - self.metrics.metering_unknown_transaction.increment(1); - None - } - TryResult::Locked => { - self.metrics.metering_locked_transaction.increment(1); - None - } - } - } -} - -impl Default for ResourceMetering { - fn default() -> Self { - Self::new(false, 10_000) - } -} - -impl ResourceMetering { - pub fn new(enabled: bool, buffer_size: usize) -> Self { - Self { - data: Arc::new(Data { - by_tx_hash: dashmap::DashMap::new(), - enabled: AtomicBool::new(enabled), - lru: ConcurrentQueue::bounded(buffer_size), - }), - metrics: OpRBuilderMetrics::default(), - } - } -} - -// Namespace overrides for ingesting resource metering -#[cfg_attr(not(test), rpc(server, namespace = "base"))] -#[cfg_attr(test, rpc(server, client, namespace = "base"))] -pub trait BaseApiExt { - #[method(name = "setMeteringInformation")] - async fn set_metering_information( - &self, - tx_hash: TxHash, - meter: MeterBundleResponse, - ) -> RpcResult<()>; - - #[method(name = "setMeteringEnabled")] - async fn set_metering_enabled(&self, enabled: bool) -> RpcResult<()>; - - #[method(name = "clearMeteringInformation")] - async fn clear_metering_information(&self) -> RpcResult<()>; -} - -pub(crate) struct ResourceMeteringExt { - metering_info: ResourceMetering, -} - -impl ResourceMeteringExt { - pub(crate) fn new(metering_info: ResourceMetering) -> Self { - Self { metering_info } - } -} - -#[async_trait] -impl BaseApiExtServer for ResourceMeteringExt { - async fn set_metering_information( - &self, - tx_hash: TxHash, - metering: MeterBundleResponse, - ) -> RpcResult<()> { - self.metering_info.insert(tx_hash, metering); - Ok(()) - } - - async fn set_metering_enabled(&self, enabled: bool) -> RpcResult<()> { - self.metering_info.set_enabled(enabled); - Ok(()) - } - - async fn clear_metering_information(&self) -> RpcResult<()> { - self.metering_info.clear(); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{B256, TxHash, U256}; - use tips_core::MeterBundleResponse; - - fn create_test_metering(gas_used: u64) -> MeterBundleResponse { - MeterBundleResponse { - bundle_hash: B256::random(), - bundle_gas_price: U256::from(123), - coinbase_diff: U256::from(123), - eth_sent_to_coinbase: U256::from(123), - gas_fees: U256::from(123), - results: vec![], - state_block_number: 4, - state_flashblock_index: None, - total_gas_used: gas_used, - total_execution_time_us: 533, - } - } - - #[test] - fn test_basic_insert_get_and_enable_disable() { - let metering = ResourceMetering::default(); - let tx_hash = TxHash::random(); - let meter_data = create_test_metering(21000); - - metering.insert(tx_hash, meter_data); - assert!(metering.get(&tx_hash).is_none()); - - metering.set_enabled(true); - assert_eq!(metering.get(&tx_hash).unwrap().total_gas_used, 21000); - - metering.insert(tx_hash, create_test_metering(50000)); - assert_eq!(metering.get(&tx_hash).unwrap().total_gas_used, 50000); - - metering.set_enabled(false); - assert!(metering.get(&tx_hash).is_none()); - - metering.set_enabled(true); - assert!(metering.get(&TxHash::random()).is_none()); - } - - #[test] - fn test_clear() { - let metering = ResourceMetering::new(true, 100); - - let tx1 = TxHash::random(); - let tx2 = TxHash::random(); - - metering.insert(tx1, create_test_metering(1000)); - metering.insert(tx2, create_test_metering(2000)); - - assert!(metering.get(&tx1).is_some()); - assert!(metering.get(&tx2).is_some()); - - metering.clear(); - - assert!(metering.get(&tx1).is_none()); - assert!(metering.get(&tx2).is_none()); - } -} diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index 0416d4ee..a58e2bda 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -78,8 +78,8 @@ async fn backrun_bundle_all_or_nothing_revert(rbuilder: LocalInstance) -> eyre:: }; rbuilder - .backrun_bundle_store() - .insert(bundle) + .tx_data_store() + .insert_backrun_bundle(bundle) .expect("Failed to insert backrun bundle"); // 4. Build the block @@ -223,12 +223,12 @@ async fn backrun_bundles_sorted_by_total_fee(rbuilder: LocalInstance) -> eyre::R // Insert in "wrong" order - B first, then A rbuilder - .backrun_bundle_store() - .insert(bundle_b) + .tx_data_store() + .insert_backrun_bundle(bundle_b) .expect("Failed to insert bundle B"); rbuilder - .backrun_bundle_store() - .insert(bundle_a) + .tx_data_store() + .insert_backrun_bundle(bundle_a) .expect("Failed to insert bundle A"); // 5. Build the block @@ -362,8 +362,8 @@ async fn backrun_bundle_rejected_low_total_fee(rbuilder: LocalInstance) -> eyre: }; rbuilder - .backrun_bundle_store() - .insert(bundle) + .tx_data_store() + .insert_backrun_bundle(bundle) .expect("Failed to insert backrun bundle"); // 4. Build the block @@ -455,8 +455,8 @@ async fn backrun_bundle_rejected_exceeds_gas_limit(rbuilder: LocalInstance) -> e }; rbuilder - .backrun_bundle_store() - .insert(bundle) + .tx_data_store() + .insert_backrun_bundle(bundle) .expect("Failed to insert backrun bundle"); driver.build_new_block().await?; @@ -539,8 +539,8 @@ async fn backrun_bundle_rejected_exceeds_da_limit(rbuilder: LocalInstance) -> ey }; rbuilder - .backrun_bundle_store() - .insert(bundle) + .tx_data_store() + .insert_backrun_bundle(bundle) .expect("Failed to insert backrun bundle"); driver.build_new_block().await?; diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index a7611b8e..52016e6b 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -1,7 +1,6 @@ use crate::{ args::OpRbuilderArgs, builders::{BuilderConfig, FlashblocksBuilder, PayloadBuilder, StandardBuilder}, - bundles::BackrunBundleStore, primitives::reth::engine_api_builder::OpEngineApiBuilder, revert_protection::{EthApiExtServer, RevertProtectionExt}, tests::{ @@ -9,6 +8,7 @@ use crate::{ framework::driver::ChainDriver, get_available_port, }, tx::FBPooledTransaction, + tx_data_store::TxDataStore, tx_signer::Signer, }; use alloy_primitives::{Address, B256, Bytes, hex, keccak256}; @@ -65,7 +65,7 @@ pub struct LocalInstance { _node_handle: Box, pool_observer: TransactionPoolObserver, attestation_server: Option, - backrun_bundle_store: BackrunBundleStore, + tx_data_store: TxDataStore, } impl LocalInstance { @@ -114,7 +114,7 @@ impl LocalInstance { .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(); - let backrun_bundle_store = builder_config.backrun_bundle_store.clone(); + let tx_data_store = builder_config.tx_data_store.clone(); let addons: OpAddOns< _, @@ -190,7 +190,7 @@ impl LocalInstance { task_manager: Some(task_manager), pool_observer: TransactionPoolObserver::new(pool_monitor, reverted_cache_clone), attestation_server, - backrun_bundle_store, + tx_data_store, }) } @@ -271,8 +271,8 @@ impl LocalInstance { &self.attestation_server } - pub fn backrun_bundle_store(&self) -> &BackrunBundleStore { - &self.backrun_bundle_store + pub fn tx_data_store(&self) -> &TxDataStore { + &self.tx_data_store } pub async fn driver(&self) -> eyre::Result> { diff --git a/crates/builder/op-rbuilder/src/tx_data_store.rs b/crates/builder/op-rbuilder/src/tx_data_store.rs new file mode 100644 index 00000000..b11710ce --- /dev/null +++ b/crates/builder/op-rbuilder/src/tx_data_store.rs @@ -0,0 +1,533 @@ +use crate::metrics::OpRBuilderMetrics; +use alloy_consensus::{Transaction, transaction::Recovered}; +use alloy_primitives::{Address, TxHash}; +use concurrent_queue::ConcurrentQueue; +use dashmap::try_result::TryResult; +use jsonrpsee::{ + core::{RpcResult, async_trait}, + proc_macros::rpc, +}; +use op_alloy_consensus::OpTxEnvelope; +use std::{ + fmt::Debug, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::Instant, +}; +use tips_core::{AcceptedBundle, MeterBundleResponse}; +use tracing::{debug, info, warn}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct StoredBackrunBundle { + pub bundle_id: Uuid, + pub sender: Address, + pub backrun_txs: Vec>, + pub total_priority_fee: u128, +} + +#[derive(Clone, Default)] +pub struct TxData { + pub metering: Option, + pub backrun_bundles: Vec, +} + +struct StoreData { + by_tx_hash: dashmap::DashMap, + lru: ConcurrentQueue, + metering_enabled: AtomicBool, +} + +#[derive(Clone)] +pub struct TxDataStore { + data: Arc, + metrics: OpRBuilderMetrics, +} + +impl Debug for TxDataStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TxDataStore") + .field("entries", &self.data.by_tx_hash.len()) + .field( + "metering_enabled", + &self.data.metering_enabled.load(Ordering::Relaxed), + ) + .finish() + } +} + +impl TxDataStore { + pub fn new(enable_resource_metering: bool, buffer_size: usize) -> Self { + Self { + data: Arc::new(StoreData { + by_tx_hash: dashmap::DashMap::new(), + lru: ConcurrentQueue::bounded(buffer_size), + metering_enabled: AtomicBool::new(enable_resource_metering), + }), + metrics: OpRBuilderMetrics::default(), + } + } + + fn evict_if_needed(&self) { + if self.data.lru.is_full() { + if let Ok(evicted_hash) = self.data.lru.pop() { + self.data.by_tx_hash.remove(&evicted_hash); + debug!( + target: "tx_data_store", + evicted_tx = ?evicted_hash, + "Evicted old transaction data" + ); + } + } + } + + pub fn get(&self, tx_hash: &TxHash) -> Option { + self.data.by_tx_hash.get(tx_hash).map(|entry| entry.clone()) + } + + pub fn insert_backrun_bundle(&self, bundle: AcceptedBundle) -> Result<(), String> { + if bundle.txs.len() < 2 { + return Err("Bundle must have at least 2 transactions (target + backrun)".to_string()); + } + + let target_tx_hash = bundle.txs[0].tx_hash(); + let backrun_txs: Vec> = bundle.txs[1..].to_vec(); + let backrun_sender = backrun_txs[0].signer(); + + self.evict_if_needed(); + let _ = self.data.lru.push(target_tx_hash); + + let total_priority_fee: u128 = backrun_txs + .iter() + .map(|tx| tx.max_priority_fee_per_gas().unwrap_or(0)) + .sum(); + + let stored_bundle = StoredBackrunBundle { + bundle_id: *bundle.uuid(), + sender: backrun_sender, + backrun_txs, + total_priority_fee, + }; + + let replaced = { + let mut entry = self.data.by_tx_hash.entry(target_tx_hash).or_default(); + let replaced = if let Some(pos) = entry + .backrun_bundles + .iter() + .position(|b| b.sender == backrun_sender) + { + entry.backrun_bundles[pos] = stored_bundle; + true + } else { + entry.backrun_bundles.push(stored_bundle); + false + }; + entry + .backrun_bundles + .sort_by(|a, b| b.total_priority_fee.cmp(&a.total_priority_fee)); + replaced + }; + + if replaced { + info!( + target: "tx_data_store", + target_tx = ?target_tx_hash, + sender = ?backrun_sender, + bundle_id = ?bundle.uuid(), + "Replaced existing backrun bundle from same sender" + ); + } + + self.metrics + .backrun_bundles_in_store + .set(self.data.by_tx_hash.len() as f64); + + Ok(()) + } + + pub fn get_backrun_bundles(&self, target_tx_hash: &TxHash) -> Option> { + self.data.by_tx_hash.get(target_tx_hash).and_then(|entry| { + if entry.backrun_bundles.is_empty() { + None + } else { + Some(entry.backrun_bundles.clone()) + } + }) + } + + pub fn remove_backrun_bundles(&self, target_tx_hash: &TxHash) { + if let Some(mut entry) = self.data.by_tx_hash.get_mut(target_tx_hash) { + let bundle_count = entry.backrun_bundles.len(); + entry.backrun_bundles.clear(); + + if bundle_count > 0 { + debug!( + target: "tx_data_store", + target_tx = ?target_tx_hash, + bundle_count, + "Removed backrun bundles" + ); + } + + if entry.metering.is_none() && entry.backrun_bundles.is_empty() { + drop(entry); + self.data.by_tx_hash.remove(target_tx_hash); + } + } + + self.metrics + .backrun_bundles_in_store + .set(self.data.by_tx_hash.len() as f64); + } + + pub fn insert_metering(&self, tx_hash: TxHash, metering_info: MeterBundleResponse) { + self.evict_if_needed(); + let _ = self.data.lru.push(tx_hash); + + let mut entry = self.data.by_tx_hash.entry(tx_hash).or_default(); + entry.metering = Some(metering_info); + } + + pub fn get_metering(&self, tx_hash: &TxHash) -> Option { + if !self.data.metering_enabled.load(Ordering::Relaxed) { + return None; + } + + match self.data.by_tx_hash.try_get(tx_hash) { + TryResult::Present(entry) => { + if entry.metering.is_some() { + self.metrics.metering_known_transaction.increment(1); + } else { + self.metrics.metering_unknown_transaction.increment(1); + } + entry.metering.clone() + } + TryResult::Absent => { + self.metrics.metering_unknown_transaction.increment(1); + None + } + TryResult::Locked => { + self.metrics.metering_locked_transaction.increment(1); + None + } + } + } + + pub fn clear_metering(&self) { + for mut entry in self.data.by_tx_hash.iter_mut() { + entry.metering = None; + } + self.data + .by_tx_hash + .retain(|_, v| v.metering.is_some() || !v.backrun_bundles.is_empty()); + } + + pub fn set_metering_enabled(&self, enabled: bool) { + self.data.metering_enabled.store(enabled, Ordering::Relaxed); + } + + pub fn len(&self) -> usize { + self.data.by_tx_hash.len() + } + + pub fn is_empty(&self) -> bool { + self.data.by_tx_hash.is_empty() + } +} + +impl Default for TxDataStore { + fn default() -> Self { + Self::new(false, 10_000) + } +} + +#[cfg_attr(not(test), rpc(server, namespace = "base"))] +#[cfg_attr(test, rpc(server, client, namespace = "base"))] +pub trait BaseApiExt { + #[method(name = "sendBackrunBundle")] + async fn send_backrun_bundle(&self, bundle: AcceptedBundle) -> RpcResult<()>; + + #[method(name = "setMeteringInformation")] + async fn set_metering_information( + &self, + tx_hash: TxHash, + meter: MeterBundleResponse, + ) -> RpcResult<()>; + + #[method(name = "setMeteringEnabled")] + async fn set_metering_enabled(&self, enabled: bool) -> RpcResult<()>; + + #[method(name = "clearMeteringInformation")] + async fn clear_metering_information(&self) -> RpcResult<()>; +} + +pub struct TxDataStoreExt { + store: TxDataStore, + metrics: OpRBuilderMetrics, +} + +impl TxDataStoreExt { + pub fn new(store: TxDataStore) -> Self { + Self { + store, + metrics: OpRBuilderMetrics::default(), + } + } +} + +#[async_trait] +impl BaseApiExtServer for TxDataStoreExt { + async fn send_backrun_bundle(&self, bundle: AcceptedBundle) -> RpcResult<()> { + self.metrics.backrun_bundles_received_total.increment(1); + + let start = Instant::now(); + self.store.insert_backrun_bundle(bundle).map_err(|e| { + warn!(target: "tx_data_store", error = %e, "Failed to store bundle"); + jsonrpsee::types::ErrorObject::owned( + jsonrpsee::types::error::INTERNAL_ERROR_CODE, + format!("Failed to store bundle: {e}"), + None::<()>, + ) + })?; + self.metrics + .backrun_bundle_insert_duration + .record(start.elapsed().as_secs_f64()); + + Ok(()) + } + + async fn set_metering_information( + &self, + tx_hash: TxHash, + metering: MeterBundleResponse, + ) -> RpcResult<()> { + self.store.insert_metering(tx_hash, metering); + Ok(()) + } + + async fn set_metering_enabled(&self, enabled: bool) -> RpcResult<()> { + self.store.set_metering_enabled(enabled); + Ok(()) + } + + async fn clear_metering_information(&self) -> RpcResult<()> { + self.store.clear_metering(); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::SignableTransaction; + use alloy_primitives::{Address, B256, TxHash, U256}; + use alloy_provider::network::TxSignerSync; + use alloy_signer_local::PrivateKeySigner; + use op_alloy_consensus::OpTxEnvelope; + use op_alloy_rpc_types::OpTransactionRequest; + + fn create_recovered_tx( + from: &PrivateKeySigner, + nonce: u64, + to: Address, + ) -> Recovered { + let mut txn = OpTransactionRequest::default() + .value(U256::from(10_000)) + .gas_limit(21_000) + .max_fee_per_gas(200) + .max_priority_fee_per_gas(100) + .from(from.address()) + .to(to) + .nonce(nonce) + .build_typed_tx() + .unwrap(); + + let sig = from.sign_transaction_sync(&mut txn).unwrap(); + let envelope = + OpTxEnvelope::Eip1559(txn.eip1559().cloned().unwrap().into_signed(sig).clone()); + Recovered::new_unchecked(envelope, from.address()) + } + + fn create_test_accepted_bundle(txs: Vec>) -> AcceptedBundle { + AcceptedBundle { + uuid: Uuid::new_v4(), + txs, + block_number: 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + } + } + + fn create_test_metering(gas_used: u64) -> MeterBundleResponse { + MeterBundleResponse { + bundle_hash: B256::random(), + bundle_gas_price: U256::from(123), + coinbase_diff: U256::from(123), + eth_sent_to_coinbase: U256::from(123), + gas_fees: U256::from(123), + results: vec![], + state_block_number: 4, + state_flashblock_index: None, + total_gas_used: gas_used, + total_execution_time_us: 533, + } + } + + #[test] + fn test_unified_get() { + let store = TxDataStore::new(true, 100); + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + + let target_tx = create_recovered_tx(&alice, 0, bob.address()); + let backrun_tx = create_recovered_tx(&alice, 1, bob.address()); + let target_tx_hash = target_tx.tx_hash(); + + store.insert_metering(target_tx_hash, create_test_metering(21000)); + + let bundle = create_test_accepted_bundle(vec![target_tx, backrun_tx]); + store.insert_backrun_bundle(bundle).unwrap(); + + let result = store.get(&target_tx_hash).unwrap(); + assert!(result.metering.is_some()); + assert_eq!(result.metering.unwrap().total_gas_used, 21000); + assert_eq!(result.backrun_bundles.len(), 1); + } + + #[test] + fn test_backrun_bundle_store() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + + let target_tx = create_recovered_tx(&alice, 0, bob.address()); + let backrun_tx1 = create_recovered_tx(&alice, 1, bob.address()); + let backrun_tx2 = create_recovered_tx(&alice, 2, bob.address()); + + let target_tx_hash = target_tx.tx_hash(); + + let store = TxDataStore::new(false, 100); + + let single_tx_bundle = create_test_accepted_bundle(vec![target_tx.clone()]); + assert!(store.insert_backrun_bundle(single_tx_bundle).is_err()); + assert_eq!(store.len(), 0); + + let valid_bundle = + create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx1.clone()]); + assert!(store.insert_backrun_bundle(valid_bundle).is_ok()); + assert_eq!(store.len(), 1); + + let retrieved = store.get_backrun_bundles(&target_tx_hash).unwrap(); + assert_eq!(retrieved.len(), 1); + assert_eq!(retrieved[0].backrun_txs.len(), 1); + assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx1.tx_hash()); + + let replacement_bundle = + create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx2.clone()]); + assert!(store.insert_backrun_bundle(replacement_bundle).is_ok()); + assert_eq!(store.len(), 1); + + let retrieved = store.get_backrun_bundles(&target_tx_hash).unwrap(); + assert_eq!(retrieved.len(), 1); + assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx2.tx_hash()); + + store.remove_backrun_bundles(&target_tx_hash); + assert!(store.get_backrun_bundles(&target_tx_hash).is_none()); + } + + #[test] + fn test_backrun_bundle_multiple_senders() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + let charlie = PrivateKeySigner::random(); + + let target_tx = create_recovered_tx(&alice, 0, bob.address()); + let alice_backrun = create_recovered_tx(&alice, 1, bob.address()); + let charlie_backrun = create_recovered_tx(&charlie, 0, bob.address()); + + let target_tx_hash = target_tx.tx_hash(); + let store = TxDataStore::new(false, 100); + + let alice_bundle = create_test_accepted_bundle(vec![target_tx.clone(), alice_backrun]); + store.insert_backrun_bundle(alice_bundle).unwrap(); + + let charlie_bundle = create_test_accepted_bundle(vec![target_tx.clone(), charlie_backrun]); + store.insert_backrun_bundle(charlie_bundle).unwrap(); + + let retrieved = store.get_backrun_bundles(&target_tx_hash).unwrap(); + assert_eq!(retrieved.len(), 2); + } + + #[test] + fn test_lru_eviction() { + let alice = PrivateKeySigner::random(); + let bob = PrivateKeySigner::random(); + + let store = TxDataStore::new(false, 2); + + for nonce in 0..3u64 { + let target = create_recovered_tx(&alice, nonce * 2, bob.address()); + let backrun = create_recovered_tx(&alice, nonce * 2 + 1, bob.address()); + let bundle = create_test_accepted_bundle(vec![target, backrun]); + let _ = store.insert_backrun_bundle(bundle); + } + + assert_eq!(store.len(), 2); + } + + #[test] + fn test_metering_enable_disable() { + let store = TxDataStore::default(); + let tx_hash = TxHash::random(); + let meter_data = create_test_metering(21000); + + store.insert_metering(tx_hash, meter_data); + assert!(store.get_metering(&tx_hash).is_none()); + + store.set_metering_enabled(true); + assert_eq!(store.get_metering(&tx_hash).unwrap().total_gas_used, 21000); + + store.insert_metering(tx_hash, create_test_metering(50000)); + assert_eq!(store.get_metering(&tx_hash).unwrap().total_gas_used, 50000); + + store.set_metering_enabled(false); + assert!(store.get_metering(&tx_hash).is_none()); + } + + #[test] + fn test_clear_metering() { + let store = TxDataStore::new(true, 100); + + let tx1 = TxHash::random(); + let tx2 = TxHash::random(); + + store.insert_metering(tx1, create_test_metering(1000)); + store.insert_metering(tx2, create_test_metering(2000)); + + assert!(store.get_metering(&tx1).is_some()); + assert!(store.get_metering(&tx2).is_some()); + + store.clear_metering(); + + assert!(store.get_metering(&tx1).is_none()); + assert!(store.get_metering(&tx2).is_none()); + } +} From 28b331b731fb1c1872ee0797a9911896a6d12fb2 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 14:28:21 -0500 Subject: [PATCH 233/262] single buffer size; fix lint --- crates/builder/op-rbuilder/src/args/op.rs | 12 +++--------- crates/builder/op-rbuilder/src/builders/mod.rs | 3 +-- .../builder/op-rbuilder/src/tx_data_store.rs | 18 +++++++++--------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/crates/builder/op-rbuilder/src/args/op.rs b/crates/builder/op-rbuilder/src/args/op.rs index d3323451..4282d54d 100644 --- a/crates/builder/op-rbuilder/src/args/op.rs +++ b/crates/builder/op-rbuilder/src/args/op.rs @@ -51,16 +51,10 @@ pub struct OpRbuilderArgs { /// Whether to enable TIPS Resource Metering #[arg(long = "builder.enable-resource-metering", default_value = "false")] pub enable_resource_metering: bool, - /// Whether to enable TIPS Resource Metering - #[arg( - long = "builder.resource-metering-buffer-size", - default_value = "10000" - )] - pub resource_metering_buffer_size: usize, - /// Buffer size for backrun bundles (LRU eviction when full) - #[arg(long = "builder.backrun-bundle-buffer-size", default_value = "10000")] - pub backrun_bundle_buffer_size: usize, + /// Buffer size for tx data store (LRU eviction when full) + #[arg(long = "builder.tx-data-store-buffer-size", default_value = "10000")] + pub tx_data_store_buffer_size: usize, /// Path to builder playgorund to automatically start up the node connected to it #[arg( diff --git a/crates/builder/op-rbuilder/src/builders/mod.rs b/crates/builder/op-rbuilder/src/builders/mod.rs index 85a5c098..7950060a 100644 --- a/crates/builder/op-rbuilder/src/builders/mod.rs +++ b/crates/builder/op-rbuilder/src/builders/mod.rs @@ -196,8 +196,7 @@ where gas_limiter_config: args.gas_limiter.clone(), tx_data_store: TxDataStore::new( args.enable_resource_metering, - args.resource_metering_buffer_size - .max(args.backrun_bundle_buffer_size), + args.tx_data_store_buffer_size, ), specific: S::try_from(args)?, }) diff --git a/crates/builder/op-rbuilder/src/tx_data_store.rs b/crates/builder/op-rbuilder/src/tx_data_store.rs index b11710ce..82eebf29 100644 --- a/crates/builder/op-rbuilder/src/tx_data_store.rs +++ b/crates/builder/op-rbuilder/src/tx_data_store.rs @@ -71,15 +71,15 @@ impl TxDataStore { } fn evict_if_needed(&self) { - if self.data.lru.is_full() { - if let Ok(evicted_hash) = self.data.lru.pop() { - self.data.by_tx_hash.remove(&evicted_hash); - debug!( - target: "tx_data_store", - evicted_tx = ?evicted_hash, - "Evicted old transaction data" - ); - } + if self.data.lru.is_full() + && let Ok(evicted_hash) = self.data.lru.pop() + { + self.data.by_tx_hash.remove(&evicted_hash); + debug!( + target: "tx_data_store", + evicted_tx = ?evicted_hash, + "Evicted old transaction data" + ); } } From 18b1063a424d23e706a18e283fffec18f1895624 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 15:25:12 -0500 Subject: [PATCH 234/262] fix DA estimation; use one getter --- .../op-rbuilder/src/builders/context.rs | 18 ++- .../builder/op-rbuilder/src/tx_data_store.rs | 107 ++++++++---------- 2 files changed, 60 insertions(+), 65 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 68003c5e..f19cbec5 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -445,7 +445,8 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; - let _resource_usage = self.tx_data_store.get_metering(&tx_hash); + let tx_data = self.tx_data_store.get(&tx_hash); + let _resource_usage = tx_data.as_ref().and_then(|d| d.metering.clone()); // TODO: ideally we should get this from the txpool stream if let Some(conditional) = conditional @@ -603,8 +604,10 @@ impl OpPayloadBuilderCtx { info.executed_transactions.push(tx.into_inner()); if is_success - && let Some(backrun_bundles) = self.tx_data_store.get_backrun_bundles(&tx_hash) + && let Some(ref data) = tx_data + && !data.backrun_bundles.is_empty() { + let backrun_bundles = &data.backrun_bundles; self.metrics.backrun_target_txs_found_total.increment(1); let backrun_start_time = Instant::now(); @@ -645,7 +648,11 @@ impl OpPayloadBuilderCtx { let total_backrun_da_size: u64 = stored_bundle .backrun_txs .iter() - .map(|tx| tx.encoded_2718().len() as u64) + .map(|tx| { + op_alloy_flz::tx_estimated_size_fjord_bytes( + tx.encoded_2718().as_slice(), + ) + }) .sum(); if let Err(result) = info.is_tx_over_limits( @@ -700,7 +707,10 @@ impl OpPayloadBuilderCtx { let backrun_gas_used = result.gas_used(); info.cumulative_gas_used += backrun_gas_used; - info.cumulative_da_bytes_used += backrun_tx.encoded_2718().len() as u64; + info.cumulative_da_bytes_used += + op_alloy_flz::tx_estimated_size_fjord_bytes( + backrun_tx.encoded_2718().as_slice(), + ); let ctx = ReceiptBuilderCtx { tx: backrun_tx.inner(), diff --git a/crates/builder/op-rbuilder/src/tx_data_store.rs b/crates/builder/op-rbuilder/src/tx_data_store.rs index 82eebf29..2eddcc5d 100644 --- a/crates/builder/op-rbuilder/src/tx_data_store.rs +++ b/crates/builder/op-rbuilder/src/tx_data_store.rs @@ -2,7 +2,6 @@ use crate::metrics::OpRBuilderMetrics; use alloy_consensus::{Transaction, transaction::Recovered}; use alloy_primitives::{Address, TxHash}; use concurrent_queue::ConcurrentQueue; -use dashmap::try_result::TryResult; use jsonrpsee::{ core::{RpcResult, async_trait}, proc_macros::rpc, @@ -84,7 +83,26 @@ impl TxDataStore { } pub fn get(&self, tx_hash: &TxHash) -> Option { - self.data.by_tx_hash.get(tx_hash).map(|entry| entry.clone()) + let metering_enabled = self.data.metering_enabled.load(Ordering::Relaxed); + + let Some(entry) = self.data.by_tx_hash.get(tx_hash) else { + if metering_enabled { + self.metrics.metering_unknown_transaction.increment(1); + } + return None; + }; + + let data = entry.clone(); + + if metering_enabled { + if data.metering.is_some() { + self.metrics.metering_known_transaction.increment(1); + } else { + self.metrics.metering_unknown_transaction.increment(1); + } + } + + Some(data) } pub fn insert_backrun_bundle(&self, bundle: AcceptedBundle) -> Result<(), String> { @@ -147,16 +165,6 @@ impl TxDataStore { Ok(()) } - pub fn get_backrun_bundles(&self, target_tx_hash: &TxHash) -> Option> { - self.data.by_tx_hash.get(target_tx_hash).and_then(|entry| { - if entry.backrun_bundles.is_empty() { - None - } else { - Some(entry.backrun_bundles.clone()) - } - }) - } - pub fn remove_backrun_bundles(&self, target_tx_hash: &TxHash) { if let Some(mut entry) = self.data.by_tx_hash.get_mut(target_tx_hash) { let bundle_count = entry.backrun_bundles.len(); @@ -190,31 +198,6 @@ impl TxDataStore { entry.metering = Some(metering_info); } - pub fn get_metering(&self, tx_hash: &TxHash) -> Option { - if !self.data.metering_enabled.load(Ordering::Relaxed) { - return None; - } - - match self.data.by_tx_hash.try_get(tx_hash) { - TryResult::Present(entry) => { - if entry.metering.is_some() { - self.metrics.metering_known_transaction.increment(1); - } else { - self.metrics.metering_unknown_transaction.increment(1); - } - entry.metering.clone() - } - TryResult::Absent => { - self.metrics.metering_unknown_transaction.increment(1); - None - } - TryResult::Locked => { - self.metrics.metering_locked_transaction.increment(1); - None - } - } - } - pub fn clear_metering(&self) { for mut entry in self.data.by_tx_hash.iter_mut() { entry.metering = None; @@ -435,22 +418,28 @@ mod tests { assert!(store.insert_backrun_bundle(valid_bundle).is_ok()); assert_eq!(store.len(), 1); - let retrieved = store.get_backrun_bundles(&target_tx_hash).unwrap(); - assert_eq!(retrieved.len(), 1); - assert_eq!(retrieved[0].backrun_txs.len(), 1); - assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx1.tx_hash()); + let data = store.get(&target_tx_hash).unwrap(); + assert_eq!(data.backrun_bundles.len(), 1); + assert_eq!(data.backrun_bundles[0].backrun_txs.len(), 1); + assert_eq!( + data.backrun_bundles[0].backrun_txs[0].tx_hash(), + backrun_tx1.tx_hash() + ); let replacement_bundle = create_test_accepted_bundle(vec![target_tx.clone(), backrun_tx2.clone()]); assert!(store.insert_backrun_bundle(replacement_bundle).is_ok()); assert_eq!(store.len(), 1); - let retrieved = store.get_backrun_bundles(&target_tx_hash).unwrap(); - assert_eq!(retrieved.len(), 1); - assert_eq!(retrieved[0].backrun_txs[0].tx_hash(), backrun_tx2.tx_hash()); + let data = store.get(&target_tx_hash).unwrap(); + assert_eq!(data.backrun_bundles.len(), 1); + assert_eq!( + data.backrun_bundles[0].backrun_txs[0].tx_hash(), + backrun_tx2.tx_hash() + ); store.remove_backrun_bundles(&target_tx_hash); - assert!(store.get_backrun_bundles(&target_tx_hash).is_none()); + assert!(store.get(&target_tx_hash).is_none()); } #[test] @@ -472,8 +461,8 @@ mod tests { let charlie_bundle = create_test_accepted_bundle(vec![target_tx.clone(), charlie_backrun]); store.insert_backrun_bundle(charlie_bundle).unwrap(); - let retrieved = store.get_backrun_bundles(&target_tx_hash).unwrap(); - assert_eq!(retrieved.len(), 2); + let data = store.get(&target_tx_hash).unwrap(); + assert_eq!(data.backrun_bundles.len(), 2); } #[test] @@ -494,22 +483,18 @@ mod tests { } #[test] - fn test_metering_enable_disable() { - let store = TxDataStore::default(); + fn test_metering_insert_and_get() { + let store = TxDataStore::new(true, 100); let tx_hash = TxHash::random(); let meter_data = create_test_metering(21000); store.insert_metering(tx_hash, meter_data); - assert!(store.get_metering(&tx_hash).is_none()); - - store.set_metering_enabled(true); - assert_eq!(store.get_metering(&tx_hash).unwrap().total_gas_used, 21000); + let data = store.get(&tx_hash).unwrap(); + assert_eq!(data.metering.unwrap().total_gas_used, 21000); store.insert_metering(tx_hash, create_test_metering(50000)); - assert_eq!(store.get_metering(&tx_hash).unwrap().total_gas_used, 50000); - - store.set_metering_enabled(false); - assert!(store.get_metering(&tx_hash).is_none()); + let data = store.get(&tx_hash).unwrap(); + assert_eq!(data.metering.unwrap().total_gas_used, 50000); } #[test] @@ -522,12 +507,12 @@ mod tests { store.insert_metering(tx1, create_test_metering(1000)); store.insert_metering(tx2, create_test_metering(2000)); - assert!(store.get_metering(&tx1).is_some()); - assert!(store.get_metering(&tx2).is_some()); + assert!(store.get(&tx1).unwrap().metering.is_some()); + assert!(store.get(&tx2).unwrap().metering.is_some()); store.clear_metering(); - assert!(store.get_metering(&tx1).is_none()); - assert!(store.get_metering(&tx2).is_none()); + assert!(store.get(&tx1).is_none()); + assert!(store.get(&tx2).is_none()); } } From bf0ec448712c8fd628e964c773f816bed6adf022 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 15:41:45 -0500 Subject: [PATCH 235/262] optimize gettor --- .../op-rbuilder/src/builders/context.rs | 14 +++---- .../builder/op-rbuilder/src/tx_data_store.rs | 37 ++++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index f19cbec5..d764a118 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -45,7 +45,7 @@ use crate::{ primitives::reth::{ExecutionInfo, TxnExecutionResult}, traits::PayloadTxsBounds, tx::MaybeRevertingTransaction, - tx_data_store::TxDataStore, + tx_data_store::{TxData, TxDataStore}, tx_signer::Signer, }; @@ -445,8 +445,10 @@ impl OpPayloadBuilderCtx { num_txs_considered += 1; - let tx_data = self.tx_data_store.get(&tx_hash); - let _resource_usage = tx_data.as_ref().and_then(|d| d.metering.clone()); + let TxData { + metering: _resource_usage, + backrun_bundles, + } = self.tx_data_store.get(&tx_hash); // TODO: ideally we should get this from the txpool stream if let Some(conditional) = conditional @@ -603,11 +605,7 @@ impl OpPayloadBuilderCtx { info.executed_senders.push(tx.signer()); info.executed_transactions.push(tx.into_inner()); - if is_success - && let Some(ref data) = tx_data - && !data.backrun_bundles.is_empty() - { - let backrun_bundles = &data.backrun_bundles; + if is_success && !backrun_bundles.is_empty() { self.metrics.backrun_target_txs_found_total.increment(1); let backrun_start_time = Instant::now(); diff --git a/crates/builder/op-rbuilder/src/tx_data_store.rs b/crates/builder/op-rbuilder/src/tx_data_store.rs index 2eddcc5d..fff31973 100644 --- a/crates/builder/op-rbuilder/src/tx_data_store.rs +++ b/crates/builder/op-rbuilder/src/tx_data_store.rs @@ -82,14 +82,17 @@ impl TxDataStore { } } - pub fn get(&self, tx_hash: &TxHash) -> Option { + pub fn get(&self, tx_hash: &TxHash) -> TxData { let metering_enabled = self.data.metering_enabled.load(Ordering::Relaxed); let Some(entry) = self.data.by_tx_hash.get(tx_hash) else { if metering_enabled { self.metrics.metering_unknown_transaction.increment(1); } - return None; + return TxData { + metering: None, + backrun_bundles: vec![], + }; }; let data = entry.clone(); @@ -102,7 +105,7 @@ impl TxDataStore { } } - Some(data) + data } pub fn insert_backrun_bundle(&self, bundle: AcceptedBundle) -> Result<(), String> { @@ -390,9 +393,9 @@ mod tests { let bundle = create_test_accepted_bundle(vec![target_tx, backrun_tx]); store.insert_backrun_bundle(bundle).unwrap(); - let result = store.get(&target_tx_hash).unwrap(); + let result = store.get(&target_tx_hash); assert!(result.metering.is_some()); - assert_eq!(result.metering.unwrap().total_gas_used, 21000); + assert_eq!(result.metering.as_ref().unwrap().total_gas_used, 21000); assert_eq!(result.backrun_bundles.len(), 1); } @@ -418,7 +421,7 @@ mod tests { assert!(store.insert_backrun_bundle(valid_bundle).is_ok()); assert_eq!(store.len(), 1); - let data = store.get(&target_tx_hash).unwrap(); + let data = store.get(&target_tx_hash); assert_eq!(data.backrun_bundles.len(), 1); assert_eq!(data.backrun_bundles[0].backrun_txs.len(), 1); assert_eq!( @@ -431,7 +434,7 @@ mod tests { assert!(store.insert_backrun_bundle(replacement_bundle).is_ok()); assert_eq!(store.len(), 1); - let data = store.get(&target_tx_hash).unwrap(); + let data = store.get(&target_tx_hash); assert_eq!(data.backrun_bundles.len(), 1); assert_eq!( data.backrun_bundles[0].backrun_txs[0].tx_hash(), @@ -439,7 +442,7 @@ mod tests { ); store.remove_backrun_bundles(&target_tx_hash); - assert!(store.get(&target_tx_hash).is_none()); + assert!(store.get(&target_tx_hash).backrun_bundles.is_empty()); } #[test] @@ -461,7 +464,7 @@ mod tests { let charlie_bundle = create_test_accepted_bundle(vec![target_tx.clone(), charlie_backrun]); store.insert_backrun_bundle(charlie_bundle).unwrap(); - let data = store.get(&target_tx_hash).unwrap(); + let data = store.get(&target_tx_hash); assert_eq!(data.backrun_bundles.len(), 2); } @@ -489,12 +492,12 @@ mod tests { let meter_data = create_test_metering(21000); store.insert_metering(tx_hash, meter_data); - let data = store.get(&tx_hash).unwrap(); - assert_eq!(data.metering.unwrap().total_gas_used, 21000); + let data = store.get(&tx_hash); + assert_eq!(data.metering.as_ref().unwrap().total_gas_used, 21000); store.insert_metering(tx_hash, create_test_metering(50000)); - let data = store.get(&tx_hash).unwrap(); - assert_eq!(data.metering.unwrap().total_gas_used, 50000); + let data = store.get(&tx_hash); + assert_eq!(data.metering.as_ref().unwrap().total_gas_used, 50000); } #[test] @@ -507,12 +510,12 @@ mod tests { store.insert_metering(tx1, create_test_metering(1000)); store.insert_metering(tx2, create_test_metering(2000)); - assert!(store.get(&tx1).unwrap().metering.is_some()); - assert!(store.get(&tx2).unwrap().metering.is_some()); + assert!(store.get(&tx1).metering.is_some()); + assert!(store.get(&tx2).metering.is_some()); store.clear_metering(); - assert!(store.get(&tx1).is_none()); - assert!(store.get(&tx2).is_none()); + assert!(store.get(&tx1).metering.is_none()); + assert!(store.get(&tx2).metering.is_none()); } } From 47e6a2e475e521d837cde7dcf6430f8f6e026563 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 16:52:39 -0500 Subject: [PATCH 236/262] use FBPooledTransaction --- .../op-rbuilder/src/builders/context.rs | 27 +++++--------- .../builder/op-rbuilder/src/tx_data_store.rs | 37 ++++++++++++++----- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index d764a118..99236f80 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -646,11 +646,7 @@ impl OpPayloadBuilderCtx { let total_backrun_da_size: u64 = stored_bundle .backrun_txs .iter() - .map(|tx| { - op_alloy_flz::tx_estimated_size_fjord_bytes( - tx.encoded_2718().as_slice(), - ) - }) + .map(|tx| tx.estimated_da_size()) .sum(); if let Err(result) = info.is_tx_over_limits( @@ -678,7 +674,8 @@ impl OpPayloadBuilderCtx { let mut pending_results = Vec::with_capacity(stored_bundle.backrun_txs.len()); for backrun_tx in &stored_bundle.backrun_txs { - let ResultAndState { result, state } = match evm.transact(backrun_tx) { + let consensus_tx = backrun_tx.clone_into_consensus(); + let ResultAndState { result, state } = match evm.transact(&consensus_tx) { Ok(res) => res, Err(err) => { return Err(PayloadBuilderError::evm(err)); @@ -690,7 +687,7 @@ impl OpPayloadBuilderCtx { info!( target: "payload_builder", target_tx = ?tx_hash, - failed_tx = ?backrun_tx.tx_hash(), + failed_tx = ?backrun_tx.hash(), bundle_id = ?stored_bundle.bundle_id, gas_used = result.gas_used(), "Backrun bundle reverted (all-or-nothing)" @@ -698,20 +695,17 @@ impl OpPayloadBuilderCtx { continue 'bundle_loop; } - pending_results.push((backrun_tx, result, state)); + pending_results.push((backrun_tx.clone(), consensus_tx, result, state)); } - for (backrun_tx, result, state) in pending_results { + for (backrun_tx, consensus_tx, result, state) in pending_results { let backrun_gas_used = result.gas_used(); info.cumulative_gas_used += backrun_gas_used; - info.cumulative_da_bytes_used += - op_alloy_flz::tx_estimated_size_fjord_bytes( - backrun_tx.encoded_2718().as_slice(), - ); + info.cumulative_da_bytes_used += backrun_tx.estimated_da_size(); let ctx = ReceiptBuilderCtx { - tx: backrun_tx.inner(), + tx: consensus_tx.inner(), evm: &evm, result, state: &state, @@ -726,9 +720,8 @@ impl OpPayloadBuilderCtx { .expect("fee is always valid; execution succeeded"); info.total_fees += U256::from(miner_fee) * U256::from(backrun_gas_used); - info.executed_senders.push(backrun_tx.signer()); - info.executed_transactions - .push(backrun_tx.clone().into_inner()); + info.executed_senders.push(backrun_tx.sender()); + info.executed_transactions.push(consensus_tx.into_inner()); } self.metrics.backrun_bundles_landed_total.increment(1); diff --git a/crates/builder/op-rbuilder/src/tx_data_store.rs b/crates/builder/op-rbuilder/src/tx_data_store.rs index fff31973..eaff876d 100644 --- a/crates/builder/op-rbuilder/src/tx_data_store.rs +++ b/crates/builder/op-rbuilder/src/tx_data_store.rs @@ -1,12 +1,13 @@ -use crate::metrics::OpRBuilderMetrics; -use alloy_consensus::{Transaction, transaction::Recovered}; +use crate::{metrics::OpRBuilderMetrics, tx::FBPooledTransaction}; +use alloy_consensus::Transaction; use alloy_primitives::{Address, TxHash}; use concurrent_queue::ConcurrentQueue; use jsonrpsee::{ core::{RpcResult, async_trait}, proc_macros::rpc, }; -use op_alloy_consensus::OpTxEnvelope; +use reth_optimism_txpool::OpPooledTransaction; +use reth_transaction_pool::PoolTransaction; use std::{ fmt::Debug, sync::{ @@ -23,7 +24,7 @@ use uuid::Uuid; pub struct StoredBackrunBundle { pub bundle_id: Uuid, pub sender: Address, - pub backrun_txs: Vec>, + pub backrun_txs: Vec, pub total_priority_fee: u128, } @@ -114,8 +115,26 @@ impl TxDataStore { } let target_tx_hash = bundle.txs[0].tx_hash(); - let backrun_txs: Vec> = bundle.txs[1..].to_vec(); - let backrun_sender = backrun_txs[0].signer(); + + // Convert OpTxEnvelope transactions to FBPooledTransaction + let backrun_txs: Vec = bundle.txs[1..] + .iter() + .filter_map(|tx| { + let (envelope, signer) = tx.clone().into_parts(); + let pooled_envelope: op_alloy_consensus::OpPooledTransaction = + envelope.try_into().ok()?; + let recovered_pooled = + alloy_consensus::transaction::Recovered::new_unchecked(pooled_envelope, signer); + let pooled = OpPooledTransaction::from_pooled(recovered_pooled); + Some(FBPooledTransaction::from(pooled)) + }) + .collect(); + + if backrun_txs.is_empty() { + return Err("No valid poolable transactions in backrun bundle".to_string()); + } + + let backrun_sender = backrun_txs[0].sender(); self.evict_if_needed(); let _ = self.data.lru.push(target_tx_hash); @@ -307,7 +326,7 @@ impl BaseApiExtServer for TxDataStoreExt { #[cfg(test)] mod tests { use super::*; - use alloy_consensus::SignableTransaction; + use alloy_consensus::{SignableTransaction, transaction::Recovered}; use alloy_primitives::{Address, B256, TxHash, U256}; use alloy_provider::network::TxSignerSync; use alloy_signer_local::PrivateKeySigner; @@ -425,7 +444,7 @@ mod tests { assert_eq!(data.backrun_bundles.len(), 1); assert_eq!(data.backrun_bundles[0].backrun_txs.len(), 1); assert_eq!( - data.backrun_bundles[0].backrun_txs[0].tx_hash(), + *data.backrun_bundles[0].backrun_txs[0].hash(), backrun_tx1.tx_hash() ); @@ -437,7 +456,7 @@ mod tests { let data = store.get(&target_tx_hash); assert_eq!(data.backrun_bundles.len(), 1); assert_eq!( - data.backrun_bundles[0].backrun_txs[0].tx_hash(), + *data.backrun_bundles[0].backrun_txs[0].hash(), backrun_tx2.tx_hash() ); From 4be1d7664c3f0eeb831a7ab5764358f0ec727697 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 22 Dec 2025 21:00:24 -0500 Subject: [PATCH 237/262] info logs --- crates/builder/op-rbuilder/src/builders/context.rs | 4 ++-- crates/builder/op-rbuilder/src/tx_data_store.rs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 99236f80..17a4b149 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -433,7 +433,7 @@ impl OpPayloadBuilderCtx { is_bundle_tx && !reverted_hashes.unwrap().contains(&tx_hash); let log_txn = |result: TxnExecutionResult| { - debug!( + info!( target: "payload_builder", message = "Considering transaction", tx_hash = ?tx_hash, @@ -611,7 +611,7 @@ impl OpPayloadBuilderCtx { // Bundles are pre-sorted by total_priority_fee (descending) from the store 'bundle_loop: for stored_bundle in backrun_bundles { - debug!( + info!( target: "payload_builder", message = "Executing backrun bundle", tx_hash = ?tx_hash, diff --git a/crates/builder/op-rbuilder/src/tx_data_store.rs b/crates/builder/op-rbuilder/src/tx_data_store.rs index eaff876d..f6c6b6c0 100644 --- a/crates/builder/op-rbuilder/src/tx_data_store.rs +++ b/crates/builder/op-rbuilder/src/tx_data_store.rs @@ -180,6 +180,14 @@ impl TxDataStore { ); } + info!( + target: "tx_data_store", + target_tx = ?target_tx_hash, + sender = ?backrun_sender, + bundle_id = ?bundle.uuid(), + "Stored backrun bundle" + ); + self.metrics .backrun_bundles_in_store .set(self.data.by_tx_hash.len() as f64); @@ -193,7 +201,7 @@ impl TxDataStore { entry.backrun_bundles.clear(); if bundle_count > 0 { - debug!( + info!( target: "tx_data_store", target_tx = ?target_tx_hash, bundle_count, From 65b26c736f81c915f173c1b0c4ad38663ec0bb7d Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 6 Jan 2026 09:13:20 -0500 Subject: [PATCH 238/262] handle evm error gracefully --- .../op-rbuilder/src/builders/context.rs | 12 +- .../builder/op-rbuilder/src/tests/backrun.rs | 103 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 17a4b149..6c3f178a 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -678,7 +678,17 @@ impl OpPayloadBuilderCtx { let ResultAndState { result, state } = match evm.transact(&consensus_tx) { Ok(res) => res, Err(err) => { - return Err(PayloadBuilderError::evm(err)); + // Handle EVM errors gracefully - skip the bundle instead of failing the block + self.metrics.backrun_bundles_reverted_total.increment(1); + info!( + target: "payload_builder", + target_tx = ?tx_hash, + failed_tx = ?backrun_tx.hash(), + bundle_id = ?stored_bundle.bundle_id, + error = %err, + "Backrun bundle failed with EVM error" + ); + continue 'bundle_loop; } }; diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index a58e2bda..719c49dc 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -560,3 +560,106 @@ async fn backrun_bundle_rejected_exceeds_da_limit(rbuilder: LocalInstance) -> ey Ok(()) } + +/// Tests that backrun bundles with EVM errors (not reverts) are skipped gracefully +/// instead of failing the entire block build. +/// This covers errors like nonce-too-low which return Err from evm.transact() +/// rather than Ok with a failed result. +#[rb_test(flashblocks)] +async fn backrun_bundle_evm_error_skipped(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + let accounts = driver.fund_accounts(3, ONE_ETH).await?; + + // 1. Build target tx + let target_tx = driver + .create_transaction() + .with_signer(accounts[0]) + .with_max_priority_fee_per_gas(20) + .build() + .await; + let target_tx_hash = target_tx.tx_hash().clone(); + + let provider = rbuilder.provider().await?; + let _ = provider + .send_raw_transaction(target_tx.encoded_2718().as_slice()) + .await?; + + // 2. Create a backrun tx from accounts[1] with nonce 0 + let backrun_tx = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(50) + .with_nonce(0) + .build() + .await; + let backrun_tx_hash = backrun_tx.tx_hash().clone(); + + // 3. Also send a DIFFERENT tx from accounts[1] with nonce 0 to the mempool + // This tx will be included first, making the backrun's nonce stale + let conflicting_tx = driver + .create_transaction() + .with_signer(accounts[1]) + .with_max_priority_fee_per_gas(100) // Higher fee so it's picked first + .with_nonce(0) + .send() + .await?; + let conflicting_tx_hash = conflicting_tx.tx_hash().clone(); + + // 4. Insert backrun bundle targeting our target tx + let bundle = AcceptedBundle { + uuid: Uuid::new_v4(), + txs: vec![target_tx, backrun_tx], + block_number: driver.latest().await?.header.number + 1, + flashblock_number_min: None, + flashblock_number_max: None, + min_timestamp: None, + max_timestamp: None, + reverting_tx_hashes: vec![], + replacement_uuid: None, + dropping_tx_hashes: vec![], + meter_bundle_response: MeterBundleResponse { + bundle_gas_price: U256::ZERO, + bundle_hash: TxHash::ZERO, + coinbase_diff: U256::ZERO, + eth_sent_to_coinbase: U256::ZERO, + gas_fees: U256::ZERO, + results: vec![], + state_block_number: 0, + state_flashblock_index: None, + total_gas_used: 0, + total_execution_time_us: 0, + }, + }; + + rbuilder + .tx_data_store() + .insert_backrun_bundle(bundle) + .expect("Failed to insert backrun bundle"); + + // 5. Build the block - this should NOT fail even though backrun has invalid nonce + driver.build_new_block().await?; + + // 6. Verify block contents + let block = driver.latest_full().await?; + let tx_hashes: Vec<_> = block.transactions.hashes().collect(); + + // Target tx should be in block + assert!( + tx_hashes.contains(&target_tx_hash), + "Target tx should be included in block" + ); + + // Conflicting tx (from mempool) should be in block + assert!( + tx_hashes.contains(&conflicting_tx_hash), + "Conflicting tx should be included in block" + ); + + // Backrun tx should NOT be in block (nonce already used by conflicting tx) + assert!( + !tx_hashes.contains(&backrun_tx_hash), + "Backrun tx should NOT be in block (nonce-too-low EVM error)" + ); + + Ok(()) +} From 8b2d838615392d6048c84a8595f0dc4dd0d80f30 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 6 Jan 2026 09:24:39 -0500 Subject: [PATCH 239/262] use different metric --- .../op-rbuilder/src/builders/context.rs | 3 +-- crates/builder/op-rbuilder/src/metrics.rs | 2 ++ .../builder/op-rbuilder/src/tests/backrun.rs | 18 +++--------------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 6c3f178a..2efc91fd 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -678,8 +678,7 @@ impl OpPayloadBuilderCtx { let ResultAndState { result, state } = match evm.transact(&consensus_tx) { Ok(res) => res, Err(err) => { - // Handle EVM errors gracefully - skip the bundle instead of failing the block - self.metrics.backrun_bundles_reverted_total.increment(1); + self.metrics.backrun_bundles_evm_error_total.increment(1); info!( target: "payload_builder", target_tx = ?tx_hash, diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index a6166816..a6f151ad 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -175,6 +175,8 @@ pub struct OpRBuilderMetrics { pub backrun_bundles_received_total: Counter, /// Number of backrun bundles that reverted during execution (all-or-nothing) pub backrun_bundles_reverted_total: Counter, + /// Number of backrun bundles that failed with EVM errors (nonce too low, invalid tx, etc.) + pub backrun_bundles_evm_error_total: Counter, /// Number of backrun bundles rejected due to priority fee below target tx pub backrun_bundles_rejected_low_fee_total: Counter, /// Number of backrun bundles rejected due to exceeding block limits diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index 719c49dc..7c063e8a 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -561,16 +561,12 @@ async fn backrun_bundle_rejected_exceeds_da_limit(rbuilder: LocalInstance) -> ey Ok(()) } -/// Tests that backrun bundles with EVM errors (not reverts) are skipped gracefully -/// instead of failing the entire block build. -/// This covers errors like nonce-too-low which return Err from evm.transact() -/// rather than Ok with a failed result. +/// Tests that backrun bundles with EVM errors are skipped gracefully instead of failing block build #[rb_test(flashblocks)] async fn backrun_bundle_evm_error_skipped(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let accounts = driver.fund_accounts(3, ONE_ETH).await?; - // 1. Build target tx let target_tx = driver .create_transaction() .with_signer(accounts[0]) @@ -584,7 +580,6 @@ async fn backrun_bundle_evm_error_skipped(rbuilder: LocalInstance) -> eyre::Resu .send_raw_transaction(target_tx.encoded_2718().as_slice()) .await?; - // 2. Create a backrun tx from accounts[1] with nonce 0 let backrun_tx = driver .create_transaction() .with_signer(accounts[1]) @@ -594,18 +589,16 @@ async fn backrun_bundle_evm_error_skipped(rbuilder: LocalInstance) -> eyre::Resu .await; let backrun_tx_hash = backrun_tx.tx_hash().clone(); - // 3. Also send a DIFFERENT tx from accounts[1] with nonce 0 to the mempool - // This tx will be included first, making the backrun's nonce stale + // Send a conflicting tx with same nonce but higher fee - it will be included first let conflicting_tx = driver .create_transaction() .with_signer(accounts[1]) - .with_max_priority_fee_per_gas(100) // Higher fee so it's picked first + .with_max_priority_fee_per_gas(100) .with_nonce(0) .send() .await?; let conflicting_tx_hash = conflicting_tx.tx_hash().clone(); - // 4. Insert backrun bundle targeting our target tx let bundle = AcceptedBundle { uuid: Uuid::new_v4(), txs: vec![target_tx, backrun_tx], @@ -636,26 +629,21 @@ async fn backrun_bundle_evm_error_skipped(rbuilder: LocalInstance) -> eyre::Resu .insert_backrun_bundle(bundle) .expect("Failed to insert backrun bundle"); - // 5. Build the block - this should NOT fail even though backrun has invalid nonce driver.build_new_block().await?; - // 6. Verify block contents let block = driver.latest_full().await?; let tx_hashes: Vec<_> = block.transactions.hashes().collect(); - // Target tx should be in block assert!( tx_hashes.contains(&target_tx_hash), "Target tx should be included in block" ); - // Conflicting tx (from mempool) should be in block assert!( tx_hashes.contains(&conflicting_tx_hash), "Conflicting tx should be included in block" ); - // Backrun tx should NOT be in block (nonce already used by conflicting tx) assert!( !tx_hashes.contains(&backrun_tx_hash), "Backrun tx should NOT be in block (nonce-too-low EVM error)" From 62fe7b712815b813d3a9a851d6bc71e96c374f5d Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 6 Jan 2026 11:04:54 -0500 Subject: [PATCH 240/262] check invalid err --- .../op-rbuilder/src/builders/context.rs | 24 +++++++++++-------- crates/builder/op-rbuilder/src/metrics.rs | 6 +++-- .../builder/op-rbuilder/src/tests/backrun.rs | 4 ++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/builder/op-rbuilder/src/builders/context.rs b/crates/builder/op-rbuilder/src/builders/context.rs index 2efc91fd..dce507b2 100644 --- a/crates/builder/op-rbuilder/src/builders/context.rs +++ b/crates/builder/op-rbuilder/src/builders/context.rs @@ -678,16 +678,20 @@ impl OpPayloadBuilderCtx { let ResultAndState { result, state } = match evm.transact(&consensus_tx) { Ok(res) => res, Err(err) => { - self.metrics.backrun_bundles_evm_error_total.increment(1); - info!( - target: "payload_builder", - target_tx = ?tx_hash, - failed_tx = ?backrun_tx.hash(), - bundle_id = ?stored_bundle.bundle_id, - error = %err, - "Backrun bundle failed with EVM error" - ); - continue 'bundle_loop; + if err.as_invalid_tx_err().is_some() { + self.metrics.backrun_bundles_invalid_tx_total.increment(1); + info!( + target: "payload_builder", + target_tx = ?tx_hash, + failed_tx = ?backrun_tx.hash(), + bundle_id = ?stored_bundle.bundle_id, + error = %err, + "Backrun bundle failed with invalid tx error" + ); + continue 'bundle_loop; + } + self.metrics.backrun_bundles_fatal_error_total.increment(1); + return Err(PayloadBuilderError::evm(err)); } }; diff --git a/crates/builder/op-rbuilder/src/metrics.rs b/crates/builder/op-rbuilder/src/metrics.rs index a6f151ad..5d72d9ee 100644 --- a/crates/builder/op-rbuilder/src/metrics.rs +++ b/crates/builder/op-rbuilder/src/metrics.rs @@ -175,8 +175,10 @@ pub struct OpRBuilderMetrics { pub backrun_bundles_received_total: Counter, /// Number of backrun bundles that reverted during execution (all-or-nothing) pub backrun_bundles_reverted_total: Counter, - /// Number of backrun bundles that failed with EVM errors (nonce too low, invalid tx, etc.) - pub backrun_bundles_evm_error_total: Counter, + /// Number of backrun bundles skipped due to invalid tx errors (nonce too low, etc.) + pub backrun_bundles_invalid_tx_total: Counter, + /// Number of backrun bundles that caused fatal EVM errors + pub backrun_bundles_fatal_error_total: Counter, /// Number of backrun bundles rejected due to priority fee below target tx pub backrun_bundles_rejected_low_fee_total: Counter, /// Number of backrun bundles rejected due to exceeding block limits diff --git a/crates/builder/op-rbuilder/src/tests/backrun.rs b/crates/builder/op-rbuilder/src/tests/backrun.rs index 7c063e8a..2883fec4 100644 --- a/crates/builder/op-rbuilder/src/tests/backrun.rs +++ b/crates/builder/op-rbuilder/src/tests/backrun.rs @@ -561,9 +561,9 @@ async fn backrun_bundle_rejected_exceeds_da_limit(rbuilder: LocalInstance) -> ey Ok(()) } -/// Tests that backrun bundles with EVM errors are skipped gracefully instead of failing block build +/// Tests that backrun bundles with invalid tx errors (e.g. nonce too low) are skipped gracefully #[rb_test(flashblocks)] -async fn backrun_bundle_evm_error_skipped(rbuilder: LocalInstance) -> eyre::Result<()> { +async fn backrun_bundle_invalid_tx_skipped(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; let accounts = driver.fund_accounts(3, ONE_ETH).await?; From 779a91eb17c03fde556c0c50cb5dd5dfb932ceb9 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 12:54:32 -0600 Subject: [PATCH 241/262] chore: rename runner/primitives crates and move reth-rpc to shared --- Cargo.lock | 238 +++++++++--------- Cargo.toml | 16 +- Justfile | 2 +- bin/node/Cargo.toml | 8 +- bin/node/src/cli.rs | 4 +- bin/node/src/main.rs | 6 +- .../Cargo.toml | 2 +- .../README.md | 4 +- .../src/config.rs | 0 .../src/extension.rs | 0 .../src/lib.rs | 0 .../src/types.rs | 0 .../{runner => client-runner}/Cargo.toml | 8 +- .../{runner => client-runner}/README.md | 6 +- .../{runner => client-runner}/src/config.rs | 6 +- .../{runner => client-runner}/src/context.rs | 0 .../{runner => client-runner}/src/handle.rs | 0 .../{runner => client-runner}/src/lib.rs | 0 .../{runner => client-runner}/src/runner.rs | 2 +- crates/client/flashblocks/Cargo.toml | 6 +- .../flashblocks/benches/pending_state.rs | 4 +- crates/client/flashblocks/src/extension.rs | 2 +- .../client/flashblocks/src/rpc_extension.rs | 2 +- crates/client/flashblocks/tests/README.md | 2 +- .../client/flashblocks/tests/eip7702_tests.rs | 2 +- .../flashblocks/tests/eth_call_erc20.rs | 2 +- .../flashblocks/tests/flashblocks_rpc.rs | 2 +- crates/client/flashblocks/tests/state.rs | 4 +- crates/client/metering/Cargo.toml | 6 +- crates/client/metering/README.md | 2 +- crates/client/metering/src/extension.rs | 2 +- crates/client/metering/tests/meter.rs | 4 +- crates/client/metering/tests/meter_block.rs | 4 +- crates/client/metering/tests/meter_rpc.rs | 4 +- crates/client/test-utils/Cargo.toml | 4 +- crates/client/test-utils/README.md | 20 +- crates/client/test-utils/src/node.rs | 4 +- crates/client/txpool/Cargo.toml | 2 +- crates/client/txpool/README.md | 2 +- crates/client/txpool/src/extension.rs | 4 +- crates/client/txpool/src/rpc_extension.rs | 2 +- .../reth-rpc-types/Cargo.toml | 0 .../reth-rpc-types/README.md | 0 .../reth-rpc-types/src/lib.rs | 0 44 files changed, 190 insertions(+), 198 deletions(-) rename crates/client/{primitives => client-primitives}/Cargo.toml (92%) rename crates/client/{primitives => client-primitives}/README.md (92%) rename crates/client/{primitives => client-primitives}/src/config.rs (100%) rename crates/client/{primitives => client-primitives}/src/extension.rs (100%) rename crates/client/{primitives => client-primitives}/src/lib.rs (100%) rename crates/client/{primitives => client-primitives}/src/types.rs (100%) rename crates/client/{runner => client-runner}/Cargo.toml (81%) rename crates/client/{runner => client-runner}/README.md (92%) rename crates/client/{runner => client-runner}/src/config.rs (90%) rename crates/client/{runner => client-runner}/src/context.rs (100%) rename crates/client/{runner => client-runner}/src/handle.rs (100%) rename crates/client/{runner => client-runner}/src/lib.rs (100%) rename crates/client/{runner => client-runner}/src/runner.rs (97%) rename crates/{client => shared}/reth-rpc-types/Cargo.toml (100%) rename crates/{client => shared}/reth-rpc-types/README.md (100%) rename crates/{client => shared}/reth-rpc-types/src/lib.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 31d5857b..4510f5d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e46a465e50a339a817070ec23f06eb3fc9fbb8af71612868367b875a9d49e3" +checksum = "8e30ab0d3e3c32976f67fc1a96179989e45a69594af42003a6663332f9b0bb9d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -138,9 +138,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07001b1693af794c7526aab400b42e38075f986ef8fef78841e5ebc745473e56" +checksum = "c20736b1f9d927d875d8777ef0c2250d4c57ea828529a9dbfa2c628db57b911e" dependencies = [ "alloy-consensus", "alloy-eips", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef1b07c3ff5bf4fab5b8e6c46190cd40b2f2fd2cd72b5b02527a38125d0bff4" +checksum = "008aba161fce2a0d94956ae09d7d7a09f8fbdf18acbef921809ef126d6cdaf97" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707337efeb051ddbaece17a73eaec5150945a5a5541112f4146508248edc2e40" +checksum = "15b85157b7be31fc4adf6acfefcb0d4308cba5dbd7a8d8e62bcc02ff37d6131a" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -299,9 +299,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ba7afffa225272cf50c62ff04ac574adc7bfa73af2370db556340f26fcff5c" +checksum = "a838301c4e2546c96db1848f18ffe9f722f2fccd9715b83d4bf269a2cf00b5a1" dependencies = [ "alloy-eips", "alloy-primitives", @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48562f9b4c4e1514cab54af16feaffc18194a38216bbd0c23004ec4667ad696b" +checksum = "60f045b69b5e80b8944b25afe74ae6b974f3044d84b4a7a113da04745b2524cc" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -355,9 +355,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "364a5eaa598437d7a57bcbcb4b7fcb0518e192cf809a19b09b2b5cf73b9ba1cd" +checksum = "2b314ed5bdc7f449c53853125af2db5ac4d3954a9f4b205e7d694f02fc1932d1" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21af5255bd276e528ee625d97033884916e879a1c6edcd5b70a043bd440c0710" +checksum = "5e9762ac5cca67b0f6ab614f7f8314942eead1c8eeef61511ea43a6ff048dbe0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -455,9 +455,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc919fe241f9dd28c4c7f7dcff9e66e550c280bafe3545e1019622e1239db38" +checksum = "ea8f7ca47514e7f552aa9f3f141ab17351332c6637e3bf00462d8e7c5f10f51f" dependencies = [ "alloy-chains", "alloy-consensus", @@ -482,7 +482,7 @@ dependencies = [ "either", "futures", "futures-utils-wasm", - "lru 0.13.0", + "lru 0.16.3", "parking_lot", "pin-project", "reqwest", @@ -497,9 +497,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a0778833917a71a9e0065e0409bfc00cddef55ca962b3453472be38ebe7035" +checksum = "4082778c908aa801a1f9fdc85d758812842ab4b2aaba58e9dbe7626d708ab7e1" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -541,9 +541,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b587e63d8c4af437b0a7830dc12d24cb495e956cc8ecbf93e96d62c9cb55b13" +checksum = "26dd083153d2cb73cce1516f5a3f9c3af74764a2761d901581a355777468bd8f" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3000edc72a300048cf461df94bfa29fc5d7760ddd88ca7d56ea6fc8b28729" +checksum = "8c998214325cfee1fbe61e5abaed3a435f4ca746ac7399b46feb57c364552452" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -580,9 +580,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebb98103316e6f4a1ebc6e71328c2d18426cdd79fc999c44afd9f0f4e9f5edd6" +checksum = "730a38742dc0753f25b8ce7330c2fa88d79f165c5fc2f19f3d35291739c42e83" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -592,9 +592,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1207e852f30297d6918f91df3e76f758fa7b519ea1e49fbd7d961ce796663f9" +checksum = "a2b03d65fcf579fbf17d3aac32271f99e2b562be04097436cd6e766b3e06613b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -604,9 +604,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebc96cf29095c10a183fb7106a097fe12ca8dd46733895582da255407f54b29" +checksum = "4b4a6f49d161ef83354d5ba3c8bc83c8ee464cb90182b215551d5c4b846579be" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -615,9 +615,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cea7c1c22628b13b25d31fd63fa5dfa7fac0b0b78f1c89a5068102b653ff65c" +checksum = "3b6654644613f33fd2e6f333f4ce8ad0a26f036c0513699d7bc168bba18d412d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e1a6b13b6f95b80d3ff770998f81e61811264eb1d18b88dfa11c80180acdc1b" +checksum = "467025b916f32645f322a085d0017f2996d0200ac89dd82a4fc2bf0f17b9afa3" dependencies = [ "alloy-primitives", "derive_more", @@ -647,9 +647,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f35af673cc14e89813ab33671d79b6e73fe38788c5f3a8ec3a75476b58225f53" +checksum = "933aaaace9faa6d7efda89472add89a8bfd15270318c47a2be8bb76192c951e2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cc3f354a5079480acca0a6533d1d3838177a03ea494ef0ae8d1679efea88274" +checksum = "11920b16ab7c86052f990dcb4d25312fb2889faf506c4ee13dc946b450536989" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -689,9 +689,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10fbd905c35f780926ff0c4c2a74d3ce7d50576cb0e9997dc783ac99c6fd7afb" +checksum = "1826454c2890af6d642bf052909e0162ad7f261d172e56ef2e936d479960699c" dependencies = [ "alloy-consensus", "alloy-eips", @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d782d80221dfaa5a2f8a7bf277370bdec10e4e8119f5a60d2e2b1adb2e806ca" +checksum = "498375e6a13b6edd04422a13d2b1a6187183e5a3aa14c5907b4c566551248bab" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -718,9 +718,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3076c226bb4365f9c3ac0cd4082ba86208aaa1485cbf664383a90aba7c36b26" +checksum = "6d9123d321ecd70925646eb2c60b1d9b7a965f860fbd717643e2c20fcf85d48d" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -730,9 +730,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a438ce4cd49ec4bc213868c1fe94f2fe103d4c3f22f6a42073db974f9c0962da" +checksum = "d1a0d2d5c64881f3723232eaaf6c2d9f4f88b061c63e87194b2db785ff3aa31f" dependencies = [ "alloy-primitives", "arbitrary", @@ -742,9 +742,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389372d6ae4d62b88c8dca8238e4f7d0a7727b66029eb8a5516a908a03161450" +checksum = "5ea4ac9765e5a7582877ca53688e041fe184880fe75f16edf0945b24a319c710" dependencies = [ "alloy-primitives", "async-trait", @@ -757,9 +757,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69c260e78b9c104c444f8a202f283d5e8c6637e6fa52a83f649ad6aaa0b91fd0" +checksum = "3c9d85b9f7105ab5ce7dae7b0da33cd9d977601a48f759e1c82958978dd1a905" dependencies = [ "alloy-consensus", "alloy-network", @@ -849,9 +849,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f01c27edb3c0926919586a231d99e06284f9239da6044b5682033ef781e1cc62" +checksum = "4e72f5c4ba505ebead6a71144d72f21a70beadfb2d84e0a560a985491ecb71de" dependencies = [ "alloy-json-rpc", "auto_impl", @@ -872,9 +872,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc57657fd3249fc8324cbbc8edbb7d5114af5fbc7c6c32dff944d6b5922f400" +checksum = "400dc298aaabdbd48be05448c4a19eaa38416c446043f3e54561249149269c32" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -887,9 +887,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a5a36d4ca1261a29dd1d791cd89c21b71d7465211910e43b0862d1c067a211" +checksum = "ba22ff961cf99495ee4fdbaf4623f8d5483d408ca2c6e1b1a54ef438ca87f8dd" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -907,9 +907,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e81effa6a2db6b2152eefb244b4aa6334b1c42819d0eca8d5a91826ec7a9fdba" +checksum = "c38b4472f2bbd96a27f393de9e2f12adca0dc1075fb4d0f7c8f3557c5c600392" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -944,9 +944,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99dac443033e83b14f68fac56e8c27e76421f1253729574197ceccd06598f3ef" +checksum = "e2183706e24173309b0ab0e34d3e53cf3163b71a419803b2b3b0c1fb7ff7a941" dependencies = [ "darling 0.21.3", "proc-macro2", @@ -1380,13 +1380,12 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] @@ -1556,33 +1555,35 @@ dependencies = [ ] [[package]] -name = "base-flashtypes" +name = "base-client-primitives" version = "0.2.1" dependencies = [ - "alloy-primitives", - "alloy-rpc-types-engine", - "alloy-rpc-types-eth", - "alloy-serde", - "brotli", - "bytes", - "derive_more", - "rstest", - "serde", - "serde_json", + "eyre", + "reth", + "reth-db", + "reth-optimism-node", ] [[package]] -name = "base-primitives" +name = "base-client-runner" version = "0.2.1" dependencies = [ + "base-client-primitives", + "base-flashblocks", + "base-metering", + "base-txpool", + "derive_more", "eyre", + "futures-util", "reth", "reth-db", + "reth-optimism-chainspec", "reth-optimism-node", + "tracing", ] [[package]] -name = "base-reth-flashblocks" +name = "base-flashblocks" version = "0.2.1" dependencies = [ "alloy-consensus", @@ -1599,9 +1600,9 @@ dependencies = [ "alloy-sol-macro", "alloy-sol-types", "arc-swap", + "base-client-primitives", "base-flashtypes", - "base-primitives", - "base-reth-test-utils", + "base-test-utils", "criterion", "eyre", "futures-util", @@ -1648,7 +1649,23 @@ dependencies = [ ] [[package]] -name = "base-reth-metering" +name = "base-flashtypes" +version = "0.2.1" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "brotli", + "bytes", + "derive_more", + "rstest", + "serde", + "serde_json", +] + +[[package]] +name = "base-metering" version = "0.2.1" dependencies = [ "alloy-consensus", @@ -1657,8 +1674,8 @@ dependencies = [ "alloy-primitives", "alloy-rpc-client", "base-bundles", - "base-primitives", - "base-reth-test-utils", + "base-client-primitives", + "base-test-utils", "eyre", "jsonrpsee", "op-alloy-consensus", @@ -1685,10 +1702,10 @@ name = "base-reth-node" version = "0.2.1" dependencies = [ "base-cli-utils", - "base-primitives", - "base-reth-flashblocks", - "base-reth-metering", - "base-reth-runner", + "base-client-primitives", + "base-client-runner", + "base-flashblocks", + "base-metering", "base-txpool", "clap", "once_cell", @@ -1706,25 +1723,7 @@ dependencies = [ ] [[package]] -name = "base-reth-runner" -version = "0.2.1" -dependencies = [ - "base-primitives", - "base-reth-flashblocks", - "base-reth-metering", - "base-txpool", - "derive_more", - "eyre", - "futures-util", - "reth", - "reth-db", - "reth-optimism-chainspec", - "reth-optimism-node", - "tracing", -] - -[[package]] -name = "base-reth-test-utils" +name = "base-test-utils" version = "0.2.1" dependencies = [ "alloy-consensus", @@ -1740,8 +1739,8 @@ dependencies = [ "alloy-signer-local", "alloy-sol-macro", "alloy-sol-types", + "base-flashblocks", "base-flashtypes", - "base-reth-flashblocks", "chrono", "derive_more", "eyre", @@ -1778,7 +1777,7 @@ name = "base-txpool" version = "0.2.1" dependencies = [ "alloy-primitives", - "base-primitives", + "base-client-primitives", "chrono", "derive_more", "eyre", @@ -2556,9 +2555,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ "brotli", "compression-core", @@ -3045,15 +3044,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "data-encoding-macro" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -3061,9 +3060,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", "syn 2.0.114", @@ -5327,15 +5326,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "lru" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "lru" version = "0.16.3" diff --git a/Cargo.toml b/Cargo.toml index 71d3c104..908fea81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,17 +52,17 @@ codegen-units = 1 [workspace.dependencies] # Shared base-access-lists = { path = "crates/shared/access-lists" } -base-flashtypes = { path = "crates/shared/flashtypes" } +base-bundles = { path = "crates/shared/bundles" } base-cli-utils = { path = "crates/shared/cli-utils" } +base-flashtypes = { path = "crates/shared/flashtypes" } +base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } # Client -base-primitives = { path = "crates/client/primitives" } -base-bundles = { path = "crates/shared/bundles" } -base-reth-metering = { path = "crates/client/metering" } -base-reth-rpc-types = { path = "crates/client/reth-rpc-types" } +base-client-primitives = { path = "crates/client/client-primitives" } +base-metering = { path = "crates/client/metering" } base-txpool = { path = "crates/client/txpool" } -base-reth-runner = { path = "crates/client/runner" } -base-reth-test-utils = { path = "crates/client/test-utils" } -base-reth-flashblocks = { path = "crates/client/flashblocks" } +base-client-runner = { path = "crates/client/client-runner" } +base-test-utils = { path = "crates/client/test-utils" } +base-flashblocks = { path = "crates/client/flashblocks" } # reth reth = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } diff --git a/Justfile b/Justfile index 5ff85189..78bc8207 100644 --- a/Justfile +++ b/Justfile @@ -108,4 +108,4 @@ benches: # Runs flashblocks pending state benchmarks bench-flashblocks: - cargo bench -p base-reth-flashblocks --bench pending_state + cargo bench -p base-flashblocks --bench pending_state diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 3f22f493..2bbefffc 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,10 +15,10 @@ workspace = true [dependencies] # internal base-cli-utils.workspace = true -base-primitives.workspace = true -base-reth-flashblocks.workspace = true -base-reth-metering.workspace = true -base-reth-runner.workspace = true +base-client-primitives.workspace = true +base-flashblocks.workspace = true +base-metering.workspace = true +base-client-runner.workspace = true base-txpool.workspace = true # reth diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs index 08a0e1a0..a42d83c1 100644 --- a/bin/node/src/cli.rs +++ b/bin/node/src/cli.rs @@ -2,8 +2,8 @@ use std::sync::Arc; -use base_primitives::{FlashblocksConfig, TracingConfig}; -use base_reth_runner::{BaseNodeConfig, RunnerFlashblocksCell}; +use base_client_primitives::{FlashblocksConfig, TracingConfig}; +use base_client_runner::{BaseNodeConfig, RunnerFlashblocksCell}; use once_cell::sync::OnceCell; use reth_optimism_node::args::RollupArgs; diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index f7a247c9..0a3d5f48 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -5,9 +5,9 @@ pub mod cli; -use base_reth_flashblocks::{FlashblocksCanonExtension, FlashblocksRpcExtension}; -use base_reth_metering::MeteringRpcExtension; -use base_reth_runner::BaseNodeRunner; +use base_client_runner::BaseNodeRunner; +use base_flashblocks::{FlashblocksCanonExtension, FlashblocksRpcExtension}; +use base_metering::MeteringRpcExtension; use base_txpool::{TransactionStatusRpcExtension, TransactionTracingExtension}; #[global_allocator] diff --git a/crates/client/primitives/Cargo.toml b/crates/client/client-primitives/Cargo.toml similarity index 92% rename from crates/client/primitives/Cargo.toml rename to crates/client/client-primitives/Cargo.toml index ed12581e..1fc3f0cb 100644 --- a/crates/client/primitives/Cargo.toml +++ b/crates/client/client-primitives/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-primitives" +name = "base-client-primitives" description = "Primitive types and traits for Base node runner extensions" version.workspace = true edition.workspace = true diff --git a/crates/client/primitives/README.md b/crates/client/client-primitives/README.md similarity index 92% rename from crates/client/primitives/README.md rename to crates/client/client-primitives/README.md index aadf5ece..f9c8987f 100644 --- a/crates/client/primitives/README.md +++ b/crates/client/client-primitives/README.md @@ -26,7 +26,7 @@ base-primitives = { git = "https://github.com/base/node-reth" } Implement a custom node extension: ```rust,ignore -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; use eyre::Result; #[derive(Debug)] @@ -51,7 +51,7 @@ impl ConfigurableBaseNodeExtension for MyExtension { Use configuration types: ```rust,ignore -use base_primitives::{FlashblocksConfig, TracingConfig}; +use base_client_primitives::{FlashblocksConfig, TracingConfig}; let flashblocks_config = FlashblocksConfig { websocket_url: "ws://localhost:8545".to_string(), diff --git a/crates/client/primitives/src/config.rs b/crates/client/client-primitives/src/config.rs similarity index 100% rename from crates/client/primitives/src/config.rs rename to crates/client/client-primitives/src/config.rs diff --git a/crates/client/primitives/src/extension.rs b/crates/client/client-primitives/src/extension.rs similarity index 100% rename from crates/client/primitives/src/extension.rs rename to crates/client/client-primitives/src/extension.rs diff --git a/crates/client/primitives/src/lib.rs b/crates/client/client-primitives/src/lib.rs similarity index 100% rename from crates/client/primitives/src/lib.rs rename to crates/client/client-primitives/src/lib.rs diff --git a/crates/client/primitives/src/types.rs b/crates/client/client-primitives/src/types.rs similarity index 100% rename from crates/client/primitives/src/types.rs rename to crates/client/client-primitives/src/types.rs diff --git a/crates/client/runner/Cargo.toml b/crates/client/client-runner/Cargo.toml similarity index 81% rename from crates/client/runner/Cargo.toml rename to crates/client/client-runner/Cargo.toml index c367a166..ced3cfac 100644 --- a/crates/client/runner/Cargo.toml +++ b/crates/client/client-runner/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-reth-runner" +name = "base-client-runner" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -13,9 +13,9 @@ workspace = true [dependencies] # internal -base-primitives.workspace = true -base-reth-flashblocks.workspace = true -base-reth-metering.workspace = true +base-client-primitives.workspace = true +base-flashblocks.workspace = true +base-metering.workspace = true base-txpool.workspace = true # reth diff --git a/crates/client/runner/README.md b/crates/client/client-runner/README.md similarity index 92% rename from crates/client/runner/README.md rename to crates/client/client-runner/README.md index 2bd4f7fa..6888dfd3 100644 --- a/crates/client/runner/README.md +++ b/crates/client/client-runner/README.md @@ -1,4 +1,4 @@ -# `base-reth-runner` +# `base-client-runner` CI MIT License @@ -27,13 +27,13 @@ Add the dependency to your `Cargo.toml`: ```toml [dependencies] -base-reth-runner = { git = "https://github.com/base/node-reth" } +base-client-runner = { git = "https://github.com/base/node-reth" } ``` Build and run a Base node: ```rust,ignore -use base_reth_runner::{BaseNodeBuilder, BaseNodeConfig, FlashblocksConfig}; +use base_client_runner::{BaseNodeBuilder, BaseNodeConfig, FlashblocksConfig}; let config = BaseNodeConfig { flashblocks: FlashblocksConfig { diff --git a/crates/client/runner/src/config.rs b/crates/client/client-runner/src/config.rs similarity index 90% rename from crates/client/runner/src/config.rs rename to crates/client/client-runner/src/config.rs index 42f522ae..c6600ccc 100644 --- a/crates/client/runner/src/config.rs +++ b/crates/client/client-runner/src/config.rs @@ -1,8 +1,8 @@ //! Contains the Base node configuration structures. -use base_primitives::{FlashblocksConfig, OpProvider, TracingConfig}; -use base_reth_flashblocks::{FlashblocksCanonConfig, FlashblocksCell, FlashblocksState}; -use base_reth_metering::MeteringRpcConfig; +use base_client_primitives::{FlashblocksConfig, OpProvider, TracingConfig}; +use base_flashblocks::{FlashblocksCanonConfig, FlashblocksCell, FlashblocksState}; +use base_metering::MeteringRpcConfig; use base_txpool::{TransactionStatusRpcConfig, TransactionTracingConfig}; use reth_optimism_node::args::RollupArgs; diff --git a/crates/client/runner/src/context.rs b/crates/client/client-runner/src/context.rs similarity index 100% rename from crates/client/runner/src/context.rs rename to crates/client/client-runner/src/context.rs diff --git a/crates/client/runner/src/handle.rs b/crates/client/client-runner/src/handle.rs similarity index 100% rename from crates/client/runner/src/handle.rs rename to crates/client/client-runner/src/handle.rs diff --git a/crates/client/runner/src/lib.rs b/crates/client/client-runner/src/lib.rs similarity index 100% rename from crates/client/runner/src/lib.rs rename to crates/client/client-runner/src/lib.rs diff --git a/crates/client/runner/src/runner.rs b/crates/client/client-runner/src/runner.rs similarity index 97% rename from crates/client/runner/src/runner.rs rename to crates/client/client-runner/src/runner.rs index a7a9d35f..2500fcd8 100644 --- a/crates/client/runner/src/runner.rs +++ b/crates/client/client-runner/src/runner.rs @@ -1,6 +1,6 @@ //! Contains the [`BaseNodeRunner`], which is responsible for configuring and launching a Base node. -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension}; use eyre::Result; use reth::{ builder::{EngineNodeLauncher, Node, NodeHandleFor, TreeConfig}, diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index 82166911..d95e17ad 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-reth-flashblocks" +name = "base-flashblocks" description = "Flashblocks State Management" version.workspace = true edition.workspace = true @@ -14,7 +14,7 @@ workspace = true [dependencies] # workspace base-flashtypes.workspace = true -base-primitives.workspace = true +base-client-primitives.workspace = true # reth reth.workspace = true @@ -90,7 +90,7 @@ reth-transaction-pool = { workspace = true, features = ["test-utils"] } tokio-tungstenite.workspace = true tracing-subscriber.workspace = true reth-e2e-test-utils.workspace = true -base-reth-test-utils.workspace = true +base-test-utils.workspace = true serde_json.workspace = true futures-util.workspace = true criterion = { version = "0.5", features = ["async_tokio"] } diff --git a/crates/client/flashblocks/benches/pending_state.rs b/crates/client/flashblocks/benches/pending_state.rs index 7b201576..a38df250 100644 --- a/crates/client/flashblocks/benches/pending_state.rs +++ b/crates/client/flashblocks/benches/pending_state.rs @@ -8,11 +8,11 @@ use std::{ use alloy_eips::{BlockHashOrNumber, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, bytes, hex::FromHex}; use alloy_rpc_types_engine::PayloadId; +use base_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; -use base_reth_test_utils::{LocalNodeProvider, TestAccounts, TestHarness}; +use base_test_utils::{LocalNodeProvider, TestAccounts, TestHarness}; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; use reth::{ chainspec::{ChainSpecProvider, EthChainSpec}, diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index 0f1ea1d3..0ba8c3bd 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use base_primitives::{ +use base_client_primitives::{ BaseNodeExtension, ConfigurableBaseNodeExtension, FlashblocksConfig, OpBuilder, OpProvider, }; use futures_util::TryStreamExt; diff --git a/crates/client/flashblocks/src/rpc_extension.rs b/crates/client/flashblocks/src/rpc_extension.rs index e1163e11..fd5fcde7 100644 --- a/crates/client/flashblocks/src/rpc_extension.rs +++ b/crates/client/flashblocks/src/rpc_extension.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use base_primitives::{ +use base_client_primitives::{ BaseNodeExtension, ConfigurableBaseNodeExtension, FlashblocksConfig, OpBuilder, OpProvider, }; use tracing::info; diff --git a/crates/client/flashblocks/tests/README.md b/crates/client/flashblocks/tests/README.md index d7152a24..3a822c9a 100644 --- a/crates/client/flashblocks/tests/README.md +++ b/crates/client/flashblocks/tests/README.md @@ -1,6 +1,6 @@ ## Flashblocks RPC Integration Tests -The suites under this directory exercise `base-reth-flashblocks-rpc` the same way external +The suites under this directory exercise `base-flashblocks-rpc` the same way external consumers do — by linking against the published library instead of the crate's `cfg(test)` build. Running them from `tests/` ensures: diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index e4ab15c9..1bfc1c55 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -11,7 +11,7 @@ use alloy_sol_types::SolCall; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_test_utils::{ +use base_test_utils::{ Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync, }; use eyre::Result; diff --git a/crates/client/flashblocks/tests/eth_call_erc20.rs b/crates/client/flashblocks/tests/eth_call_erc20.rs index de846fc4..2c62d223 100644 --- a/crates/client/flashblocks/tests/eth_call_erc20.rs +++ b/crates/client/flashblocks/tests/eth_call_erc20.rs @@ -19,7 +19,7 @@ use alloy_sol_types::{SolConstructor, SolValue}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_test_utils::{ +use base_test_utils::{ FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, }; use eyre::Result; diff --git a/crates/client/flashblocks/tests/flashblocks_rpc.rs b/crates/client/flashblocks/tests/flashblocks_rpc.rs index fc8fa418..d589a568 100644 --- a/crates/client/flashblocks/tests/flashblocks_rpc.rs +++ b/crates/client/flashblocks/tests/flashblocks_rpc.rs @@ -14,7 +14,7 @@ use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_test_utils::{DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; +use base_test_utils::{DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; diff --git a/crates/client/flashblocks/tests/state.rs b/crates/client/flashblocks/tests/state.rs index 8f998125..6102952c 100644 --- a/crates/client/flashblocks/tests/state.rs +++ b/crates/client/flashblocks/tests/state.rs @@ -8,11 +8,11 @@ use alloy_primitives::{ Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::foldhash::HashMap, }; use alloy_rpc_types_engine::PayloadId; +use base_flashblocks::{FlashblocksAPI, FlashblocksState, PendingBlocksAPI}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_reth_flashblocks::{FlashblocksAPI, FlashblocksState, PendingBlocksAPI}; -use base_reth_test_utils::{ +use base_test_utils::{ FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, TestAccounts, }; diff --git a/crates/client/metering/Cargo.toml b/crates/client/metering/Cargo.toml index 0c975fca..8bbbed3a 100644 --- a/crates/client/metering/Cargo.toml +++ b/crates/client/metering/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-reth-metering" +name = "base-metering" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -14,7 +14,7 @@ workspace = true [dependencies] # workspace base-bundles.workspace = true -base-primitives.workspace = true +base-client-primitives.workspace = true # reth reth.workspace = true @@ -39,7 +39,7 @@ eyre.workspace = true serde.workspace = true [dev-dependencies] -base-reth-test-utils.workspace = true +base-test-utils.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-optimism-node.workspace = true diff --git a/crates/client/metering/README.md b/crates/client/metering/README.md index e79d4da8..3d1c679a 100644 --- a/crates/client/metering/README.md +++ b/crates/client/metering/README.md @@ -1,4 +1,4 @@ -# base-reth-metering +# base-metering Metering RPC for Base node. Provides RPC methods for measuring transaction and block execution timing. diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 578523d1..17771a08 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`MeteringRpcExtension`] which wires up the metering RPC surface //! on the Base node builder. -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; use tracing::info; use crate::{MeteringApiImpl, MeteringApiServer}; diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index 371be97a..e014691b 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -7,8 +7,8 @@ use alloy_eips::Encodable2718; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; -use base_reth_metering::meter_bundle; -use base_reth_test_utils::create_provider_factory; +use base_metering::meter_bundle; +use base_test_utils::create_provider_factory; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; use rand::{SeedableRng, rngs::StdRng}; diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 42af07b3..0ad45a0d 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use alloy_consensus::{BlockHeader, Header, crypto::secp256k1::public_key_to_address}; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; -use base_reth_metering::meter_block; -use base_reth_test_utils::create_provider_factory; +use base_metering::meter_block; +use base_test_utils::create_provider_factory; use eyre::Context; use rand::{SeedableRng, rngs::StdRng}; use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index 5ffe598c..dd99a9dc 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -6,8 +6,8 @@ use alloy_eips::Encodable2718; use alloy_primitives::{Bytes, U256, address, b256, bytes}; use alloy_rpc_client::RpcClient; use base_bundles::{Bundle, MeterBundleResponse}; -use base_reth_metering::{MeteringApiImpl, MeteringApiServer}; -use base_reth_test_utils::{init_silenced_tracing, load_genesis}; +use base_metering::{MeteringApiImpl, MeteringApiServer}; +use base_test_utils::{init_silenced_tracing, load_genesis}; use op_alloy_consensus::OpTxEnvelope; use reth::{ args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml index d66222f9..64f3b529 100644 --- a/crates/client/test-utils/Cargo.toml +++ b/crates/client/test-utils/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "base-reth-test-utils" +name = "base-test-utils" version.workspace = true edition.workspace = true rust-version.workspace = true @@ -14,7 +14,7 @@ workspace = true [dependencies] # Project base-flashtypes.workspace = true -base-reth-flashblocks.workspace = true +base-flashblocks.workspace = true # reth reth.workspace = true diff --git a/crates/client/test-utils/README.md b/crates/client/test-utils/README.md index 2b735f2c..1a425a2a 100644 --- a/crates/client/test-utils/README.md +++ b/crates/client/test-utils/README.md @@ -15,7 +15,7 @@ This crate provides reusable testing utilities for integration tests across the ## Quick Start ```rust,ignore -use base_reth_test_utils::TestHarness; +use base_test_utils::TestHarness; #[tokio::test] async fn test_example() -> eyre::Result<()> { @@ -68,7 +68,7 @@ The framework follows a three-layer architecture: The main entry point for integration tests that only need canonical chain control. Combines node, engine, and accounts into a single interface. ```rust,ignore -use base_reth_test_utils::TestHarness; +use base_test_utils::TestHarness; use alloy_primitives::Bytes; #[tokio::test] @@ -117,7 +117,7 @@ async fn test_harness() -> eyre::Result<()> { In-process Optimism node with Base Sepolia configuration. ```rust,ignore -use base_reth_test_utils::{LocalNode, default_launcher}; +use base_test_utils::{LocalNode, default_launcher}; #[tokio::test] async fn test_node() -> eyre::Result<()> { @@ -140,7 +140,7 @@ async fn test_node() -> eyre::Result<()> { For flashblocks-enabled nodes, use `FlashblocksLocalNode`: ```rust,ignore -use base_reth_test_utils::FlashblocksLocalNode; +use base_test_utils::FlashblocksLocalNode; let node = FlashblocksLocalNode::new().await?; let pending_state = node.flashblocks_state(); @@ -154,7 +154,7 @@ node.send_flashblock(flashblock).await?; Type-safe Engine API client wrapping raw CL operations. ```rust,ignore -use base_reth_test_utils::EngineApi; +use base_test_utils::EngineApi; use alloy_primitives::B256; use op_alloy_rpc_types_engine::OpPayloadAttributes; @@ -179,7 +179,7 @@ let status = engine.new_payload(payload, vec![], parent_root, requests).await?; Hardcoded test accounts with deterministic addresses (Anvil-compatible). ```rust,ignore -use base_reth_test_utils::{TestAccounts, TestHarness}; +use base_test_utils::{TestAccounts, TestHarness}; let accounts = TestAccounts::new(); @@ -210,7 +210,7 @@ Each account includes: Use `FlashblocksHarness` when you need `send_flashblock` and access to the in-memory pending state. ```rust,ignore -use base_reth_test_utils::FlashblocksHarness; +use base_test_utils::FlashblocksHarness; #[tokio::test] async fn test_flashblocks() -> eyre::Result<()> { @@ -234,7 +234,7 @@ Test flashblocks delivery without WebSocket connections by constructing payloads Key constants are exported from the crate root: ```rust,ignore -use base_reth_test_utils::{ +use base_test_utils::{ BASE_CHAIN_ID, // Chain ID for Base Sepolia (84532) BLOCK_TIME_SECONDS, // Base L2 block time (2 seconds) GAS_LIMIT, // Default gas limit (200M) @@ -273,13 +273,13 @@ Add to `dev-dependencies`: ```toml [dev-dependencies] -base-reth-test-utils.workspace = true +base-test-utils.workspace = true ``` Import in tests: ```rust,ignore -use base_reth_test_utils::TestHarness; +use base_test_utils::TestHarness; #[tokio::test] async fn my_test() -> eyre::Result<()> { diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index b943abe2..d62c1fa8 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -10,11 +10,11 @@ use std::{ use alloy_genesis::Genesis; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; -use base_flashtypes::Flashblock; -use base_reth_flashblocks::{ +use base_flashblocks::{ EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksReceiver, FlashblocksState, }; +use base_flashtypes::Flashblock; use eyre::Result; use futures_util::Future; use once_cell::sync::OnceCell; diff --git a/crates/client/txpool/Cargo.toml b/crates/client/txpool/Cargo.toml index 5fe839b5..3d7806c6 100644 --- a/crates/client/txpool/Cargo.toml +++ b/crates/client/txpool/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] # workspace -base-primitives.workspace = true +base-client-primitives.workspace = true # reth reth.workspace = true diff --git a/crates/client/txpool/README.md b/crates/client/txpool/README.md index 3dbc4aa0..d115bb60 100644 --- a/crates/client/txpool/README.md +++ b/crates/client/txpool/README.md @@ -27,7 +27,7 @@ cargo run -p node --release -- \ From code, wire the ExEx into the node builder: ```rust,ignore -use base_reth_runner::{TracingConfig, extensions::TransactionTracingExtension}; +use base_client_runner::{TracingConfig, extensions::TransactionTracingExtension}; let tracing = TracingConfig { enabled: true, logs_enabled: true }; let builder = TransactionTracingExtension::new(tracing).apply(builder); diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index f8bdce28..e9dab016 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,7 +1,9 @@ //! Contains the [`TransactionTracingExtension`] which wires up the `tracex` //! execution extension on the Base node builder. -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, TracingConfig}; +use base_client_primitives::{ + BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, TracingConfig, +}; use crate::tracex_exex; diff --git a/crates/client/txpool/src/rpc_extension.rs b/crates/client/txpool/src/rpc_extension.rs index 3c3270e2..05276225 100644 --- a/crates/client/txpool/src/rpc_extension.rs +++ b/crates/client/txpool/src/rpc_extension.rs @@ -1,7 +1,7 @@ //! Contains the [`TransactionStatusRpcExtension`] which wires up the transaction status //! RPC surface on the Base node builder. -use base_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer}; diff --git a/crates/client/reth-rpc-types/Cargo.toml b/crates/shared/reth-rpc-types/Cargo.toml similarity index 100% rename from crates/client/reth-rpc-types/Cargo.toml rename to crates/shared/reth-rpc-types/Cargo.toml diff --git a/crates/client/reth-rpc-types/README.md b/crates/shared/reth-rpc-types/README.md similarity index 100% rename from crates/client/reth-rpc-types/README.md rename to crates/shared/reth-rpc-types/README.md diff --git a/crates/client/reth-rpc-types/src/lib.rs b/crates/shared/reth-rpc-types/src/lib.rs similarity index 100% rename from crates/client/reth-rpc-types/src/lib.rs rename to crates/shared/reth-rpc-types/src/lib.rs From 2b4da5c45c72db58fe3e0a5096032cf9df5d999c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 13:09:39 -0600 Subject: [PATCH 242/262] chore: unify extensions to be one per crate --- Cargo.lock | 1 - bin/node/Cargo.toml | 1 - bin/node/src/cli.rs | 3 +- bin/node/src/main.rs | 18 ++-- crates/client/client-primitives/README.md | 28 ++---- crates/client/client-primitives/src/config.rs | 19 ---- crates/client/client-primitives/src/lib.rs | 3 - crates/client/client-runner/README.md | 37 ++++---- crates/client/client-runner/src/config.rs | 26 +++--- crates/client/flashblocks/src/extension.rs | 90 +++++++++++++++---- crates/client/flashblocks/src/lib.rs | 7 +- .../client/flashblocks/src/rpc_extension.rs | 89 ------------------ crates/client/metering/src/extension.rs | 18 ++-- crates/client/metering/src/lib.rs | 2 +- crates/client/txpool/README.md | 7 +- crates/client/txpool/src/extension.rs | 66 +++++++++----- crates/client/txpool/src/lib.rs | 5 +- crates/client/txpool/src/rpc_extension.rs | 50 ----------- 18 files changed, 179 insertions(+), 291 deletions(-) delete mode 100644 crates/client/client-primitives/src/config.rs delete mode 100644 crates/client/flashblocks/src/rpc_extension.rs delete mode 100644 crates/client/txpool/src/rpc_extension.rs diff --git a/Cargo.lock b/Cargo.lock index 4510f5d6..da70177d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1702,7 +1702,6 @@ name = "base-reth-node" version = "0.2.1" dependencies = [ "base-cli-utils", - "base-client-primitives", "base-client-runner", "base-flashblocks", "base-metering", diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 2bbefffc..afb492c9 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,7 +15,6 @@ workspace = true [dependencies] # internal base-cli-utils.workspace = true -base-client-primitives.workspace = true base-flashblocks.workspace = true base-metering.workspace = true base-client-runner.workspace = true diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs index a42d83c1..7bcd3a34 100644 --- a/bin/node/src/cli.rs +++ b/bin/node/src/cli.rs @@ -2,8 +2,9 @@ use std::sync::Arc; -use base_client_primitives::{FlashblocksConfig, TracingConfig}; use base_client_runner::{BaseNodeConfig, RunnerFlashblocksCell}; +use base_flashblocks::FlashblocksConfig; +use base_txpool::TracingConfig; use once_cell::sync::OnceCell; use reth_optimism_node::args::RollupArgs; diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 0a3d5f48..c729c98a 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -6,9 +6,9 @@ pub mod cli; use base_client_runner::BaseNodeRunner; -use base_flashblocks::{FlashblocksCanonExtension, FlashblocksRpcExtension}; -use base_metering::MeteringRpcExtension; -use base_txpool::{TransactionStatusRpcExtension, TransactionTracingExtension}; +use base_flashblocks::FlashblocksExtension; +use base_metering::MeteringExtension; +use base_txpool::TxPoolExtension; #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); @@ -26,14 +26,10 @@ fn main() { cli.run(|builder, args| async move { let mut runner = BaseNodeRunner::new(args); - // ExEx extensions - runner.install_ext::()?; - runner.install_ext::()?; - - // RPC extensions (FlashblocksRpcExtension must be last - uses replace_configured) - runner.install_ext::()?; - runner.install_ext::()?; - runner.install_ext::()?; + // Feature extensions (FlashblocksExtension must be last - uses replace_configured) + runner.install_ext::()?; + runner.install_ext::()?; + runner.install_ext::()?; let handle = runner.run(builder); handle.await diff --git a/crates/client/client-primitives/README.md b/crates/client/client-primitives/README.md index f9c8987f..225e06b4 100644 --- a/crates/client/client-primitives/README.md +++ b/crates/client/client-primitives/README.md @@ -1,26 +1,28 @@ -# `base-primitives` +# `base-client-primitives` CI MIT License -Primitive types and traits for Base node runner extensions. Provides configuration types, extension traits, and type aliases for building modular node extensions. +Primitive types and traits for Base node runner extensions. Provides extension traits and type aliases for building modular node extensions. ## Overview -- **`FlashblocksConfig`**: Configuration for flashblocks streaming, including websocket endpoint and pending block depth. -- **`TracingConfig`**: Configuration for the transaction tracing ExEx, with toggles for enabling tracing and logs. - **`BaseNodeExtension`**: Trait for node builder extensions that can apply additional wiring to the builder. - **`ConfigurableBaseNodeExtension`**: Trait for extensions that can be constructed from a configuration type. - **`OpBuilder`**: Type alias for the OP node builder with launch context. - **`OpProvider`**: Type alias for the blockchain provider instance. +Configuration types are located in their respective feature crates: +- **`FlashblocksConfig`**: in `base-flashblocks` crate +- **`TracingConfig`**: in `base-txpool` crate + ## Usage Add the dependency to your `Cargo.toml`: ```toml [dependencies] -base-primitives = { git = "https://github.com/base/node-reth" } +base-client-primitives = { git = "https://github.com/base/node-reth" } ``` Implement a custom node extension: @@ -47,19 +49,3 @@ impl ConfigurableBaseNodeExtension for MyExtension { } } ``` - -Use configuration types: - -```rust,ignore -use base_client_primitives::{FlashblocksConfig, TracingConfig}; - -let flashblocks_config = FlashblocksConfig { - websocket_url: "ws://localhost:8545".to_string(), - max_pending_blocks_depth: 10, -}; - -let tracing_config = TracingConfig { - enabled: true, - logs_enabled: false, -}; -``` diff --git a/crates/client/client-primitives/src/config.rs b/crates/client/client-primitives/src/config.rs deleted file mode 100644 index 67a102d5..00000000 --- a/crates/client/client-primitives/src/config.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Configuration types for extensions. - -/// Flashblocks-specific configuration knobs. -#[derive(Debug, Clone)] -pub struct FlashblocksConfig { - /// The websocket endpoint that streams flashblock updates. - pub websocket_url: String, - /// Maximum number of pending flashblocks to retain in memory. - pub max_pending_blocks_depth: u64, -} - -/// Transaction tracing toggles. -#[derive(Debug, Clone, Copy)] -pub struct TracingConfig { - /// Enables the transaction tracing ExEx. - pub enabled: bool, - /// Emits `info`-level logs for the tracing ExEx when enabled. - pub logs_enabled: bool, -} diff --git a/crates/client/client-primitives/src/lib.rs b/crates/client/client-primitives/src/lib.rs index 7fcd87c3..8306c677 100644 --- a/crates/client/client-primitives/src/lib.rs +++ b/crates/client/client-primitives/src/lib.rs @@ -3,9 +3,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -mod config; -pub use config::{FlashblocksConfig, TracingConfig}; - mod extension; pub use extension::{BaseNodeExtension, ConfigurableBaseNodeExtension}; diff --git a/crates/client/client-runner/README.md b/crates/client/client-runner/README.md index 6888dfd3..816845c1 100644 --- a/crates/client/client-runner/README.md +++ b/crates/client/client-runner/README.md @@ -11,15 +11,14 @@ Base-specific node launcher that wires together the Optimism node components, ex - **`BaseNodeRunner`**: Runs the Base node with all configured extensions. - **`BaseNodeHandle`**: Handle to the running node, providing access to providers and state. - **`BaseNodeConfig`**: Configuration options for the Base node. -- **`FlashblocksConfig`**: Configuration for flashblocks WebSocket subscription. -- **`TracingConfig`**: Configuration for transaction tracing extension. ## Extensions -- **`BaseNodeExtension`**: Core extension trait for adding functionality to the node. -- **`BaseRpcExtension`**: Extension for adding custom RPC methods. -- **`FlashblocksCanonExtension`**: Extension for canonical block reconciliation with flashblocks. -- **`TransactionTracingExtension`**: Extension for transaction lifecycle tracing. +Each feature crate provides a single `BaseNodeExtension` that combines all functionality for that feature: + +- **`FlashblocksExtension`** (from `base-flashblocks`): Combines canon ExEx and RPC for flashblocks. +- **`TxPoolExtension`** (from `base-txpool`): Combines transaction tracing ExEx and status RPC. +- **`MeteringExtension`** (from `base-metering`): Provides metering RPC. ## Usage @@ -33,21 +32,17 @@ base-client-runner = { git = "https://github.com/base/node-reth" } Build and run a Base node: ```rust,ignore -use base_client_runner::{BaseNodeBuilder, BaseNodeConfig, FlashblocksConfig}; - -let config = BaseNodeConfig { - flashblocks: FlashblocksConfig { - enabled: true, - url: "wss://flashblocks.base.org".to_string(), - }, - ..Default::default() -}; - -let node = BaseNodeBuilder::new(config) - .with_flashblocks() - .with_transaction_tracing() - .build() - .await?; +use base_client_runner::BaseNodeRunner; +use base_flashblocks::FlashblocksExtension; +use base_metering::MeteringExtension; +use base_txpool::TxPoolExtension; + +let mut runner = BaseNodeRunner::new(args); +runner.install_ext::()?; +runner.install_ext::()?; +runner.install_ext::()?; + +let handle = runner.run(builder); ``` ## License diff --git a/crates/client/client-runner/src/config.rs b/crates/client/client-runner/src/config.rs index c6600ccc..e7a59520 100644 --- a/crates/client/client-runner/src/config.rs +++ b/crates/client/client-runner/src/config.rs @@ -1,9 +1,11 @@ //! Contains the Base node configuration structures. -use base_client_primitives::{FlashblocksConfig, OpProvider, TracingConfig}; -use base_flashblocks::{FlashblocksCanonConfig, FlashblocksCell, FlashblocksState}; -use base_metering::MeteringRpcConfig; -use base_txpool::{TransactionStatusRpcConfig, TransactionTracingConfig}; +use base_client_primitives::OpProvider; +use base_flashblocks::{ + FlashblocksCell, FlashblocksConfig, FlashblocksExtensionConfig, FlashblocksState, +}; +use base_metering::MeteringExtensionConfig; +use base_txpool::{TracingConfig, TxPoolExtensionConfig}; use reth_optimism_node::args::RollupArgs; /// Concrete type alias for the flashblocks cell used in the runner. @@ -34,7 +36,7 @@ impl BaseNodeConfig { // Implement configuration traits for BaseNodeConfig so it can be used // with ConfigurableBaseNodeExtension -impl FlashblocksCanonConfig for BaseNodeConfig { +impl FlashblocksExtensionConfig for BaseNodeConfig { fn flashblocks_cell(&self) -> &FlashblocksCell> { &self.flashblocks_cell } @@ -44,20 +46,18 @@ impl FlashblocksCanonConfig for BaseNodeConfig { } } -impl TransactionTracingConfig for BaseNodeConfig { +impl TxPoolExtensionConfig for BaseNodeConfig { fn tracing(&self) -> &TracingConfig { &self.tracing } -} -impl MeteringRpcConfig for BaseNodeConfig { - fn metering_enabled(&self) -> bool { - self.metering_enabled + fn sequencer_rpc(&self) -> Option<&str> { + self.rollup_args.sequencer.as_deref() } } -impl TransactionStatusRpcConfig for BaseNodeConfig { - fn sequencer_rpc(&self) -> Option<&str> { - self.rollup_args.sequencer.as_deref() +impl MeteringExtensionConfig for BaseNodeConfig { + fn metering_enabled(&self) -> bool { + self.metering_enabled } } diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index 0ba8c3bd..36a493f5 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -1,31 +1,45 @@ -//! Contains the [`FlashblocksCanonExtension`] which wires up the `flashblocks-canon` -//! execution extension on the Base node builder. +//! Contains the [`FlashblocksExtension`] which wires up the flashblocks feature +//! (both the canon ExEx and RPC surface) on the Base node builder. use std::sync::Arc; use base_client_primitives::{ - BaseNodeExtension, ConfigurableBaseNodeExtension, FlashblocksConfig, OpBuilder, OpProvider, + BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, OpProvider, }; use futures_util::TryStreamExt; use once_cell::sync::OnceCell; use reth_exex::ExExEvent; +use tracing::info; +use url::Url; -use crate::FlashblocksState; +use crate::{ + EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksState, + FlashblocksSubscriber, +}; /// The flashblocks cell holds a shared state reference. pub type FlashblocksCell = Arc>>; -/// Helper struct that wires the Flashblocks canon ExEx into the node builder. +/// Flashblocks-specific configuration knobs. +#[derive(Debug, Clone)] +pub struct FlashblocksConfig { + /// The websocket endpoint that streams flashblock updates. + pub websocket_url: String, + /// Maximum number of pending flashblocks to retain in memory. + pub max_pending_blocks_depth: u64, +} + +/// Helper struct that wires the Flashblocks feature (canon ExEx and RPC) into the node builder. #[derive(Debug, Clone)] -pub struct FlashblocksCanonExtension { +pub struct FlashblocksExtension { /// Shared Flashblocks state cache. pub cell: FlashblocksCell>, /// Optional Flashblocks configuration. pub config: Option, } -impl FlashblocksCanonExtension { - /// Create a new Flashblocks canon extension helper. +impl FlashblocksExtension { + /// Create a new Flashblocks extension helper. pub const fn new( cell: FlashblocksCell>, config: Option, @@ -34,23 +48,27 @@ impl FlashblocksCanonExtension { } } -impl BaseNodeExtension for FlashblocksCanonExtension { +impl BaseNodeExtension for FlashblocksExtension { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let flashblocks = self.config; - let flashblocks_enabled = flashblocks.is_some(); + let Some(cfg) = self.config else { + info!(message = "flashblocks integration is disabled"); + return builder; + }; + let flashblocks_cell = self.cell; + let cfg_for_rpc = cfg.clone(); + let flashblocks_cell_for_rpc = flashblocks_cell.clone(); - builder.install_exex_if(flashblocks_enabled, "flashblocks-canon", move |mut ctx| { + // Install the canon ExEx + let builder = builder.install_exex("flashblocks-canon", move |mut ctx| { let flashblocks_cell = flashblocks_cell.clone(); async move { - let fb_config = - flashblocks.as_ref().expect("flashblocks config checked above").clone(); let fb = flashblocks_cell .get_or_init(|| { Arc::new(FlashblocksState::new( ctx.provider().clone(), - fb_config.max_pending_blocks_depth, + cfg.max_pending_blocks_depth, )) }) .clone(); @@ -69,21 +87,55 @@ impl BaseNodeExtension for FlashblocksCanonExtension { Ok(()) }) } + }); + + // Extend with RPC modules + builder.extend_rpc_modules(move |ctx| { + info!(message = "Starting Flashblocks RPC"); + + let ws_url = Url::parse(cfg_for_rpc.websocket_url.as_str())?; + let fb = flashblocks_cell_for_rpc + .get_or_init(|| { + Arc::new(FlashblocksState::new( + ctx.provider().clone(), + cfg_for_rpc.max_pending_blocks_depth, + )) + }) + .clone(); + fb.start(); + + let mut flashblocks_client = FlashblocksSubscriber::new(fb.clone(), ws_url); + flashblocks_client.start(); + + let api_ext = EthApiExt::new( + ctx.registry.eth_api().clone(), + ctx.registry.eth_handlers().filter.clone(), + fb.clone(), + ); + ctx.modules.replace_configured(api_ext.into_rpc())?; + + // Register the eth_subscribe subscription endpoint for flashblocks + // Uses replace_configured since eth_subscribe already exists from reth's standard module + // Pass eth_api to enable proxying standard subscription types to reth's implementation + let eth_pubsub = EthPubSub::new(ctx.registry.eth_api().clone(), fb); + ctx.modules.replace_configured(eth_pubsub.into_rpc())?; + + Ok(()) }) } } -/// Configuration trait for [`FlashblocksCanonExtension`]. +/// Configuration trait for [`FlashblocksExtension`]. /// -/// Types implementing this trait can be used to construct a [`FlashblocksCanonExtension`]. -pub trait FlashblocksCanonConfig { +/// Types implementing this trait can be used to construct a [`FlashblocksExtension`]. +pub trait FlashblocksExtensionConfig { /// Returns the shared flashblocks cell. fn flashblocks_cell(&self) -> &FlashblocksCell>; /// Returns the flashblocks configuration if enabled. fn flashblocks(&self) -> Option<&FlashblocksConfig>; } -impl ConfigurableBaseNodeExtension for FlashblocksCanonExtension { +impl ConfigurableBaseNodeExtension for FlashblocksExtension { fn build(config: &C) -> eyre::Result { Ok(Self::new(config.flashblocks_cell().clone(), config.flashblocks().cloned())) } diff --git a/crates/client/flashblocks/src/lib.rs b/crates/client/flashblocks/src/lib.rs index 1f2f1ced..d1969f72 100644 --- a/crates/client/flashblocks/src/lib.rs +++ b/crates/client/flashblocks/src/lib.rs @@ -45,7 +45,6 @@ pub use rpc::{ }; mod extension; -pub use extension::{FlashblocksCanonConfig, FlashblocksCanonExtension, FlashblocksCell}; - -mod rpc_extension; -pub use rpc_extension::FlashblocksRpcExtension; +pub use extension::{ + FlashblocksCell, FlashblocksConfig, FlashblocksExtension, FlashblocksExtensionConfig, +}; diff --git a/crates/client/flashblocks/src/rpc_extension.rs b/crates/client/flashblocks/src/rpc_extension.rs deleted file mode 100644 index fd5fcde7..00000000 --- a/crates/client/flashblocks/src/rpc_extension.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Contains the [`FlashblocksRpcExtension`] which wires up the flashblocks RPC surface -//! on the Base node builder. - -use std::sync::Arc; - -use base_client_primitives::{ - BaseNodeExtension, ConfigurableBaseNodeExtension, FlashblocksConfig, OpBuilder, OpProvider, -}; -use tracing::info; -use url::Url; - -use crate::{ - EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksCell, - FlashblocksState, FlashblocksSubscriber, -}; - -/// Helper struct that wires the flashblocks RPC into the node builder. -#[derive(Debug, Clone)] -pub struct FlashblocksRpcExtension { - /// Shared Flashblocks state cache. - pub cell: FlashblocksCell>, - /// Optional Flashblocks configuration. - pub config: Option, -} - -impl FlashblocksRpcExtension { - /// Creates a new flashblocks RPC extension. - pub const fn new( - cell: FlashblocksCell>, - config: Option, - ) -> Self { - Self { cell, config } - } -} - -impl BaseNodeExtension for FlashblocksRpcExtension { - /// Applies the extension to the supplied builder. - fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let Some(cfg) = self.config else { - info!(message = "flashblocks RPC integration is disabled"); - return builder; - }; - - let flashblocks_cell = self.cell; - - builder.extend_rpc_modules(move |ctx| { - info!(message = "Starting Flashblocks RPC"); - - let ws_url = Url::parse(cfg.websocket_url.as_str())?; - let fb = flashblocks_cell - .get_or_init(|| { - Arc::new(FlashblocksState::new( - ctx.provider().clone(), - cfg.max_pending_blocks_depth, - )) - }) - .clone(); - fb.start(); - - let mut flashblocks_client = FlashblocksSubscriber::new(fb.clone(), ws_url); - flashblocks_client.start(); - - let api_ext = EthApiExt::new( - ctx.registry.eth_api().clone(), - ctx.registry.eth_handlers().filter.clone(), - fb.clone(), - ); - ctx.modules.replace_configured(api_ext.into_rpc())?; - - // Register the eth_subscribe subscription endpoint for flashblocks - // Uses replace_configured since eth_subscribe already exists from reth's standard module - // Pass eth_api to enable proxying standard subscription types to reth's implementation - let eth_pubsub = EthPubSub::new(ctx.registry.eth_api().clone(), fb); - ctx.modules.replace_configured(eth_pubsub.into_rpc())?; - - Ok(()) - }) - } -} - -// Reuse FlashblocksCanonConfig since the methods are identical. -// Any type implementing FlashblocksCanonConfig can construct a FlashblocksRpcExtension. -impl ConfigurableBaseNodeExtension - for FlashblocksRpcExtension -{ - fn build(config: &C) -> eyre::Result { - Ok(Self::new(config.flashblocks_cell().clone(), config.flashblocks().cloned())) - } -} diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 17771a08..59c1c99e 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,4 +1,4 @@ -//! Contains the [`MeteringRpcExtension`] which wires up the metering RPC surface +//! Contains the [`MeteringExtension`] which wires up the metering RPC surface //! on the Base node builder. use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; @@ -8,19 +8,19 @@ use crate::{MeteringApiImpl, MeteringApiServer}; /// Helper struct that wires the metering RPC into the node builder. #[derive(Debug, Clone, Copy)] -pub struct MeteringRpcExtension { +pub struct MeteringExtension { /// Whether metering is enabled. pub enabled: bool, } -impl MeteringRpcExtension { - /// Creates a new metering RPC extension. +impl MeteringExtension { + /// Creates a new metering extension. pub const fn new(enabled: bool) -> Self { Self { enabled } } } -impl BaseNodeExtension for MeteringRpcExtension { +impl BaseNodeExtension for MeteringExtension { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder { if !self.enabled { @@ -36,15 +36,15 @@ impl BaseNodeExtension for MeteringRpcExtension { } } -/// Configuration trait for [`MeteringRpcExtension`]. +/// Configuration trait for [`MeteringExtension`]. /// -/// Types implementing this trait can be used to construct a [`MeteringRpcExtension`]. -pub trait MeteringRpcConfig { +/// Types implementing this trait can be used to construct a [`MeteringExtension`]. +pub trait MeteringExtensionConfig { /// Returns whether metering is enabled. fn metering_enabled(&self) -> bool; } -impl ConfigurableBaseNodeExtension for MeteringRpcExtension { +impl ConfigurableBaseNodeExtension for MeteringExtension { fn build(config: &C) -> eyre::Result { Ok(Self::new(config.metering_enabled())) } diff --git a/crates/client/metering/src/lib.rs b/crates/client/metering/src/lib.rs index 16a09587..1c23bd30 100644 --- a/crates/client/metering/src/lib.rs +++ b/crates/client/metering/src/lib.rs @@ -7,7 +7,7 @@ mod block; pub use block::meter_block; mod extension; -pub use extension::{MeteringRpcConfig, MeteringRpcExtension}; +pub use extension::{MeteringExtension, MeteringExtensionConfig}; mod meter; pub use meter::meter_bundle; diff --git a/crates/client/txpool/README.md b/crates/client/txpool/README.md index d115bb60..11ed50f5 100644 --- a/crates/client/txpool/README.md +++ b/crates/client/txpool/README.md @@ -24,13 +24,14 @@ cargo run -p node --release -- \ --enable-transaction-tracing-logs # optional: emit per-tx lifecycle logs ``` -From code, wire the ExEx into the node builder: +From code, wire the extension into the node builder: ```rust,ignore -use base_client_runner::{TracingConfig, extensions::TransactionTracingExtension}; +use base_txpool::{TracingConfig, TxPoolExtension}; let tracing = TracingConfig { enabled: true, logs_enabled: true }; -let builder = TransactionTracingExtension::new(tracing).apply(builder); +let ext = TxPoolExtension::new(tracing, None); +let builder = Box::new(ext).apply(builder); ``` ## Metrics diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index e9dab016..21628819 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,46 +1,70 @@ -//! Contains the [`TransactionTracingExtension`] which wires up the `tracex` -//! execution extension on the Base node builder. +//! Contains the [`TxPoolExtension`] which wires up the transaction pool features +//! (tracing ExEx and status RPC) on the Base node builder. -use base_client_primitives::{ - BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, TracingConfig, -}; +use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use tracing::info; -use crate::tracex_exex; +use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; -/// Helper struct that wires the transaction tracing ExEx into the node builder. +/// Transaction tracing toggles. #[derive(Debug, Clone, Copy)] -pub struct TransactionTracingExtension { +pub struct TracingConfig { + /// Enables the transaction tracing ExEx. + pub enabled: bool, + /// Emits `info`-level logs for the tracing ExEx when enabled. + pub logs_enabled: bool, +} + +/// Helper struct that wires the transaction pool features into the node builder. +#[derive(Debug, Clone)] +pub struct TxPoolExtension { /// Transaction tracing configuration flags. - pub config: TracingConfig, + pub tracing: TracingConfig, + /// Sequencer RPC endpoint for transaction status proxying. + pub sequencer_rpc: Option, } -impl TransactionTracingExtension { - /// Creates a new transaction tracing extension helper. - pub const fn new(config: TracingConfig) -> Self { - Self { config } +impl TxPoolExtension { + /// Creates a new transaction pool extension helper. + pub const fn new(tracing: TracingConfig, sequencer_rpc: Option) -> Self { + Self { tracing, sequencer_rpc } } } -impl BaseNodeExtension for TransactionTracingExtension { +impl BaseNodeExtension for TxPoolExtension { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let tracing = self.config; - builder.install_exex_if(tracing.enabled, "tracex", move |ctx| async move { + let tracing = self.tracing; + let sequencer_rpc = self.sequencer_rpc; + + // Install the tracing ExEx if enabled + let builder = builder.install_exex_if(tracing.enabled, "tracex", move |ctx| async move { Ok(tracex_exex(ctx, tracing.logs_enabled)) + }); + + // Extend with RPC modules + builder.extend_rpc_modules(move |ctx| { + info!(message = "Starting Transaction Status RPC"); + let proxy_api = TransactionStatusApiImpl::new(sequencer_rpc, ctx.pool().clone()) + .expect("Failed to create transaction status proxy"); + ctx.modules.merge_configured(proxy_api.into_rpc())?; + Ok(()) }) } } -/// Configuration trait for [`TransactionTracingExtension`]. +/// Configuration trait for [`TxPoolExtension`]. /// -/// Types implementing this trait can be used to construct a [`TransactionTracingExtension`]. -pub trait TransactionTracingConfig { +/// Types implementing this trait can be used to construct a [`TxPoolExtension`]. +pub trait TxPoolExtensionConfig { /// Returns the tracing configuration. fn tracing(&self) -> &TracingConfig; + /// Returns the sequencer RPC URL if configured. + fn sequencer_rpc(&self) -> Option<&str>; } -impl ConfigurableBaseNodeExtension for TransactionTracingExtension { +impl ConfigurableBaseNodeExtension for TxPoolExtension { fn build(config: &C) -> eyre::Result { - Ok(Self::new(*config.tracing())) + Ok(Self::new(*config.tracing(), config.sequencer_rpc().map(String::from))) } } diff --git a/crates/client/txpool/src/lib.rs b/crates/client/txpool/src/lib.rs index ab3577d3..66e9ecd1 100644 --- a/crates/client/txpool/src/lib.rs +++ b/crates/client/txpool/src/lib.rs @@ -14,11 +14,8 @@ pub use rpc::{ Status, TransactionStatusApiImpl, TransactionStatusApiServer, TransactionStatusResponse, }; -mod rpc_extension; -pub use rpc_extension::{TransactionStatusRpcConfig, TransactionStatusRpcExtension}; - mod tracker; pub use tracker::Tracker; mod extension; -pub use extension::{TransactionTracingConfig, TransactionTracingExtension}; +pub use extension::{TracingConfig, TxPoolExtension, TxPoolExtensionConfig}; diff --git a/crates/client/txpool/src/rpc_extension.rs b/crates/client/txpool/src/rpc_extension.rs deleted file mode 100644 index 05276225..00000000 --- a/crates/client/txpool/src/rpc_extension.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Contains the [`TransactionStatusRpcExtension`] which wires up the transaction status -//! RPC surface on the Base node builder. - -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; - -use crate::{TransactionStatusApiImpl, TransactionStatusApiServer}; - -/// Helper struct that wires the transaction status RPC into the node builder. -#[derive(Debug, Clone)] -pub struct TransactionStatusRpcExtension { - /// Sequencer RPC endpoint for transaction status proxying. - pub sequencer_rpc: Option, -} - -impl TransactionStatusRpcExtension { - /// Creates a new transaction status RPC extension. - pub const fn new(sequencer_rpc: Option) -> Self { - Self { sequencer_rpc } - } -} - -impl BaseNodeExtension for TransactionStatusRpcExtension { - /// Applies the extension to the supplied builder. - fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let sequencer_rpc = self.sequencer_rpc; - - builder.extend_rpc_modules(move |ctx| { - let proxy_api = TransactionStatusApiImpl::new(sequencer_rpc, ctx.pool().clone()) - .expect("Failed to create transaction status proxy"); - ctx.modules.merge_configured(proxy_api.into_rpc())?; - Ok(()) - }) - } -} - -/// Configuration trait for [`TransactionStatusRpcExtension`]. -/// -/// Types implementing this trait can be used to construct a [`TransactionStatusRpcExtension`]. -pub trait TransactionStatusRpcConfig { - /// Returns the sequencer RPC URL if configured. - fn sequencer_rpc(&self) -> Option<&str>; -} - -impl ConfigurableBaseNodeExtension - for TransactionStatusRpcExtension -{ - fn build(config: &C) -> eyre::Result { - Ok(Self::new(config.sequencer_rpc().map(String::from))) - } -} From 3108574dd0b02d26bac6bb8cf06a10e337526c4e Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 13:31:33 -0600 Subject: [PATCH 243/262] chore: migrate generic node setup to client-primitives, remove client-runner --- Cargo.lock | 16 +---- Cargo.toml | 1 - bin/node/Cargo.toml | 2 +- bin/node/src/cli.rs | 33 ++++------ bin/node/src/main.rs | 26 ++++++-- crates/client/client-primitives/Cargo.toml | 4 ++ .../client/client-primitives/src/extension.rs | 10 +-- .../src/handle.rs | 2 + crates/client/client-primitives/src/lib.rs | 10 ++- .../src/runner.rs | 42 +++++-------- crates/client/client-primitives/src/types.rs | 6 +- crates/client/client-runner/Cargo.toml | 31 --------- crates/client/client-runner/README.md | 50 --------------- crates/client/client-runner/src/config.rs | 63 ------------------- crates/client/client-runner/src/context.rs | 10 --- crates/client/client-runner/src/lib.rs | 16 ----- crates/client/flashblocks/Cargo.toml | 1 - crates/client/flashblocks/src/extension.rs | 20 +----- crates/client/flashblocks/src/lib.rs | 4 +- crates/client/metering/src/extension.rs | 16 +---- crates/client/metering/src/lib.rs | 2 +- crates/client/txpool/src/extension.rs | 18 +----- crates/client/txpool/src/lib.rs | 2 +- 23 files changed, 76 insertions(+), 309 deletions(-) rename crates/client/{client-runner => client-primitives}/src/handle.rs (96%) rename crates/client/{client-runner => client-primitives}/src/runner.rs (61%) delete mode 100644 crates/client/client-runner/Cargo.toml delete mode 100644 crates/client/client-runner/README.md delete mode 100644 crates/client/client-runner/src/config.rs delete mode 100644 crates/client/client-runner/src/context.rs delete mode 100644 crates/client/client-runner/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index da70177d..8e355c27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1558,20 +1558,6 @@ dependencies = [ name = "base-client-primitives" version = "0.2.1" dependencies = [ - "eyre", - "reth", - "reth-db", - "reth-optimism-node", -] - -[[package]] -name = "base-client-runner" -version = "0.2.1" -dependencies = [ - "base-client-primitives", - "base-flashblocks", - "base-metering", - "base-txpool", "derive_more", "eyre", "futures-util", @@ -1702,7 +1688,7 @@ name = "base-reth-node" version = "0.2.1" dependencies = [ "base-cli-utils", - "base-client-runner", + "base-client-primitives", "base-flashblocks", "base-metering", "base-txpool", diff --git a/Cargo.toml b/Cargo.toml index 908fea81..507d310c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,6 @@ base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } base-client-primitives = { path = "crates/client/client-primitives" } base-metering = { path = "crates/client/metering" } base-txpool = { path = "crates/client/txpool" } -base-client-runner = { path = "crates/client/client-runner" } base-test-utils = { path = "crates/client/test-utils" } base-flashblocks = { path = "crates/client/flashblocks" } diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index afb492c9..0b1be210 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,9 +15,9 @@ workspace = true [dependencies] # internal base-cli-utils.workspace = true +base-client-primitives.workspace = true base-flashblocks.workspace = true base-metering.workspace = true -base-client-runner.workspace = true base-txpool.workspace = true # reth diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs index 7bcd3a34..b4385ca1 100644 --- a/bin/node/src/cli.rs +++ b/bin/node/src/cli.rs @@ -1,11 +1,7 @@ //! Contains the CLI arguments -use std::sync::Arc; - -use base_client_runner::{BaseNodeConfig, RunnerFlashblocksCell}; use base_flashblocks::FlashblocksConfig; use base_txpool::TracingConfig; -use once_cell::sync::OnceCell; use reth_optimism_node::args::RollupArgs; /// CLI Arguments @@ -50,25 +46,20 @@ impl Args { pub const fn flashblocks_enabled(&self) -> bool { self.websocket_url.is_some() } -} -impl From for BaseNodeConfig { - fn from(args: Args) -> Self { - let flashblocks_cell: RunnerFlashblocksCell = Arc::new(OnceCell::new()); - let flashblocks = args.websocket_url.map(|websocket_url| FlashblocksConfig { - websocket_url, - max_pending_blocks_depth: args.max_pending_blocks_depth, - }); + /// Build FlashblocksConfig from CLI args. + pub fn flashblocks_config(&self) -> Option { + self.websocket_url.as_ref().map(|url| FlashblocksConfig { + websocket_url: url.clone(), + max_pending_blocks_depth: self.max_pending_blocks_depth, + }) + } - Self { - rollup_args: args.rollup_args, - flashblocks, - tracing: TracingConfig { - enabled: args.enable_transaction_tracing, - logs_enabled: args.enable_transaction_tracing_logs, - }, - metering_enabled: args.enable_metering, - flashblocks_cell, + /// Build TracingConfig from CLI args. + pub const fn tracing_config(&self) -> TracingConfig { + TracingConfig { + enabled: self.enable_transaction_tracing, + logs_enabled: self.enable_transaction_tracing_logs, } } } diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index c729c98a..e6791368 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -5,10 +5,13 @@ pub mod cli; -use base_client_runner::BaseNodeRunner; -use base_flashblocks::FlashblocksExtension; +use std::sync::Arc; + +use base_client_primitives::{BaseNodeRunner, OpProvider}; +use base_flashblocks::{FlashblocksCell, FlashblocksExtension, FlashblocksState}; use base_metering::MeteringExtension; use base_txpool::TxPoolExtension; +use once_cell::sync::OnceCell; #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); @@ -24,12 +27,23 @@ fn main() { // Step 3: Hand the parsed CLI to the node runner so it can build and launch the Base node. cli.run(|builder, args| async move { - let mut runner = BaseNodeRunner::new(args); + // Create shared flashblocks cell + let flashblocks_cell: FlashblocksCell> = + Arc::new(OnceCell::new()); + + // Extract values needed for extensions before moving rollup_args + let sequencer_rpc = args.rollup_args.sequencer.clone(); + let tracing_config = args.tracing_config(); + let metering_enabled = args.enable_metering; + let flashblocks_config = args.flashblocks_config(); + + let mut runner = BaseNodeRunner::new(args.rollup_args); // Feature extensions (FlashblocksExtension must be last - uses replace_configured) - runner.install_ext::()?; - runner.install_ext::()?; - runner.install_ext::()?; + runner.install_ext(Box::new(TxPoolExtension::new(tracing_config, sequencer_rpc))); + runner.install_ext(Box::new(MeteringExtension::new(metering_enabled))); + runner + .install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, flashblocks_config))); let handle = runner.run(builder); handle.await diff --git a/crates/client/client-primitives/Cargo.toml b/crates/client/client-primitives/Cargo.toml index 1fc3f0cb..38130118 100644 --- a/crates/client/client-primitives/Cargo.toml +++ b/crates/client/client-primitives/Cargo.toml @@ -16,6 +16,10 @@ workspace = true reth.workspace = true reth-db.workspace = true reth-optimism-node.workspace = true +reth-optimism-chainspec.workspace = true # misc eyre.workspace = true +futures-util.workspace = true +tracing.workspace = true +derive_more = { workspace = true, features = ["debug"] } diff --git a/crates/client/client-primitives/src/extension.rs b/crates/client/client-primitives/src/extension.rs index 605978bf..5e8e809e 100644 --- a/crates/client/client-primitives/src/extension.rs +++ b/crates/client/client-primitives/src/extension.rs @@ -1,9 +1,7 @@ -//! Traits describing configurable node builder extensions. +//! Traits describing node builder extensions. use std::fmt::Debug; -use eyre::Result; - use crate::OpBuilder; /// A node builder extension that can apply additional wiring to the builder. @@ -11,9 +9,3 @@ pub trait BaseNodeExtension: Send + Sync + Debug { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder; } - -/// An extension that can be constructed from a configuration type. -pub trait ConfigurableBaseNodeExtension: BaseNodeExtension + Sized + 'static { - /// Builds the extension from the node config. - fn build(config: &C) -> Result; -} diff --git a/crates/client/client-runner/src/handle.rs b/crates/client/client-primitives/src/handle.rs similarity index 96% rename from crates/client/client-runner/src/handle.rs rename to crates/client/client-primitives/src/handle.rs index 0332ba2c..9ffc1ca1 100644 --- a/crates/client/client-runner/src/handle.rs +++ b/crates/client/client-primitives/src/handle.rs @@ -1,3 +1,5 @@ +//! Contains the [`BaseNodeHandle`], an awaitable handle to a launched Base node. + use std::{ future::Future, pin::Pin, diff --git a/crates/client/client-primitives/src/lib.rs b/crates/client/client-primitives/src/lib.rs index 8306c677..2dc062de 100644 --- a/crates/client/client-primitives/src/lib.rs +++ b/crates/client/client-primitives/src/lib.rs @@ -4,7 +4,13 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod extension; -pub use extension::{BaseNodeExtension, ConfigurableBaseNodeExtension}; +pub use extension::BaseNodeExtension; + +mod handle; +pub use handle::BaseNodeHandle; + +mod runner; +pub use runner::BaseNodeRunner; mod types; -pub use types::{OpBuilder, OpProvider}; +pub use types::{BaseNodeBuilder, OpBuilder, OpProvider}; diff --git a/crates/client/client-runner/src/runner.rs b/crates/client/client-primitives/src/runner.rs similarity index 61% rename from crates/client/client-runner/src/runner.rs rename to crates/client/client-primitives/src/runner.rs index 2500fcd8..7d712d82 100644 --- a/crates/client/client-runner/src/runner.rs +++ b/crates/client/client-primitives/src/runner.rs @@ -1,60 +1,50 @@ //! Contains the [`BaseNodeRunner`], which is responsible for configuring and launching a Base node. -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension}; use eyre::Result; use reth::{ builder::{EngineNodeLauncher, Node, NodeHandleFor, TreeConfig}, providers::providers::BlockchainProvider, }; -use reth_optimism_node::OpNode; +use reth_optimism_node::{OpNode, args::RollupArgs}; use tracing::info; -use crate::{BaseNodeBuilder, BaseNodeConfig, BaseNodeHandle}; +use crate::{BaseNodeBuilder, BaseNodeExtension, BaseNodeHandle}; /// Wraps the Base node configuration and orchestrates builder wiring. #[derive(Debug)] pub struct BaseNodeRunner { - /// Contains the configuration for the Base node. - config: BaseNodeConfig, + /// Rollup-specific arguments forwarded to the Optimism node implementation. + rollup_args: RollupArgs, /// Registered builder extensions. extensions: Vec>, } impl BaseNodeRunner { - /// Creates a new launcher using the provided configuration. - pub fn new(config: impl Into) -> Self { - Self { config: config.into(), extensions: Vec::new() } + /// Creates a new launcher using the provided rollup arguments. + pub fn new(rollup_args: RollupArgs) -> Self { + Self { rollup_args, extensions: Vec::new() } } - /// Returns the underlying configuration, primarily for testing. - pub const fn config(&self) -> &BaseNodeConfig { - &self.config + /// Registers a new builder extension. + pub fn install_ext(&mut self, extension: Box) { + self.extensions.push(extension); } - /// Registers a new builder extension constructed from the node configuration. - pub fn install_ext(&mut self) -> Result<()> - where - E: ConfigurableBaseNodeExtension, - { - let extension = E::build(&self.config)?; - self.extensions.push(Box::new(extension)); - Ok(()) - } - - /// Applies all Base-specific wiring to the supplied builder, launches the node, and returns a handle that can be awaited. + /// Applies all Base-specific wiring to the supplied builder, launches the node, and returns a + /// handle that can be awaited. pub fn run(self, builder: BaseNodeBuilder) -> BaseNodeHandle { - let Self { config, extensions } = self; - BaseNodeHandle::new(Self::launch_node(config, extensions, builder)) + let Self { rollup_args, extensions } = self; + BaseNodeHandle::new(Self::launch_node(rollup_args, extensions, builder)) } async fn launch_node( - config: BaseNodeConfig, + rollup_args: RollupArgs, extensions: Vec>, builder: BaseNodeBuilder, ) -> Result> { info!(target: "base-runner", "starting custom Base node"); - let op_node = OpNode::new(config.rollup_args.clone()); + let op_node = OpNode::new(rollup_args); let builder = builder .with_types_and_provider::>() diff --git a/crates/client/client-primitives/src/types.rs b/crates/client/client-primitives/src/types.rs index fee2d759..b25f0c46 100644 --- a/crates/client/client-primitives/src/types.rs +++ b/crates/client/client-primitives/src/types.rs @@ -4,10 +4,11 @@ use std::sync::Arc; use reth::{ api::{FullNodeTypesAdapter, NodeTypesWithDBAdapter}, - builder::{Node, NodeBuilderWithComponents, WithLaunchContext}, + builder::{Node, NodeBuilder, NodeBuilderWithComponents, WithLaunchContext}, providers::providers::BlockchainProvider, }; use reth_db::DatabaseEnv; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::OpNode; type OpNodeTypes = FullNodeTypesAdapter, OpProvider>; @@ -20,3 +21,6 @@ pub type OpProvider = BlockchainProvider>; + +/// Convenience alias for the Base node builder type. +pub type BaseNodeBuilder = WithLaunchContext, OpChainSpec>>; diff --git a/crates/client/client-runner/Cargo.toml b/crates/client/client-runner/Cargo.toml deleted file mode 100644 index ced3cfac..00000000 --- a/crates/client/client-runner/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "base-client-runner" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -description = "Base-specific node launcher wiring" - -[lints] -workspace = true - -[dependencies] -# internal -base-client-primitives.workspace = true -base-flashblocks.workspace = true -base-metering.workspace = true -base-txpool.workspace = true - -# reth -reth.workspace = true -reth-db.workspace = true -reth-optimism-node.workspace = true -reth-optimism-chainspec.workspace = true - -# misc -eyre.workspace = true -futures-util.workspace = true -tracing.workspace = true -derive_more = { workspace = true, features = ["debug"] } diff --git a/crates/client/client-runner/README.md b/crates/client/client-runner/README.md deleted file mode 100644 index 816845c1..00000000 --- a/crates/client/client-runner/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# `base-client-runner` - -CI -MIT License - -Base-specific node launcher that wires together the Optimism node components, execution extensions, and RPC add-ons for the Base node binary. Exposes the types that the CLI uses to build a node and pass them to Optimism's `Cli` runner. - -## Overview - -- **`BaseNodeBuilder`**: Builder for constructing a Base node with custom extensions and configuration. -- **`BaseNodeRunner`**: Runs the Base node with all configured extensions. -- **`BaseNodeHandle`**: Handle to the running node, providing access to providers and state. -- **`BaseNodeConfig`**: Configuration options for the Base node. - -## Extensions - -Each feature crate provides a single `BaseNodeExtension` that combines all functionality for that feature: - -- **`FlashblocksExtension`** (from `base-flashblocks`): Combines canon ExEx and RPC for flashblocks. -- **`TxPoolExtension`** (from `base-txpool`): Combines transaction tracing ExEx and status RPC. -- **`MeteringExtension`** (from `base-metering`): Provides metering RPC. - -## Usage - -Add the dependency to your `Cargo.toml`: - -```toml -[dependencies] -base-client-runner = { git = "https://github.com/base/node-reth" } -``` - -Build and run a Base node: - -```rust,ignore -use base_client_runner::BaseNodeRunner; -use base_flashblocks::FlashblocksExtension; -use base_metering::MeteringExtension; -use base_txpool::TxPoolExtension; - -let mut runner = BaseNodeRunner::new(args); -runner.install_ext::()?; -runner.install_ext::()?; -runner.install_ext::()?; - -let handle = runner.run(builder); -``` - -## License - -Licensed under the [MIT License](https://github.com/base/node-reth/blob/main/LICENSE). diff --git a/crates/client/client-runner/src/config.rs b/crates/client/client-runner/src/config.rs deleted file mode 100644 index e7a59520..00000000 --- a/crates/client/client-runner/src/config.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Contains the Base node configuration structures. - -use base_client_primitives::OpProvider; -use base_flashblocks::{ - FlashblocksCell, FlashblocksConfig, FlashblocksExtensionConfig, FlashblocksState, -}; -use base_metering::MeteringExtensionConfig; -use base_txpool::{TracingConfig, TxPoolExtensionConfig}; -use reth_optimism_node::args::RollupArgs; - -/// Concrete type alias for the flashblocks cell used in the runner. -pub type RunnerFlashblocksCell = FlashblocksCell>; - -/// Captures the pieces of CLI configuration that the node logic cares about. -#[derive(Debug, Clone)] -pub struct BaseNodeConfig { - /// Rollup-specific arguments forwarded to the Optimism node implementation. - pub rollup_args: RollupArgs, - /// Optional flashblocks configuration if the websocket URL was provided. - pub flashblocks: Option, - /// Execution extension tracing toggles. - pub tracing: TracingConfig, - /// Indicates whether the metering RPC surface should be installed. - pub metering_enabled: bool, - /// Shared Flashblocks state cache. - pub flashblocks_cell: RunnerFlashblocksCell, -} - -impl BaseNodeConfig { - /// Returns `true` if flashblocks support should be wired up. - pub const fn flashblocks_enabled(&self) -> bool { - self.flashblocks.is_some() - } -} - -// Implement configuration traits for BaseNodeConfig so it can be used -// with ConfigurableBaseNodeExtension - -impl FlashblocksExtensionConfig for BaseNodeConfig { - fn flashblocks_cell(&self) -> &FlashblocksCell> { - &self.flashblocks_cell - } - - fn flashblocks(&self) -> Option<&FlashblocksConfig> { - self.flashblocks.as_ref() - } -} - -impl TxPoolExtensionConfig for BaseNodeConfig { - fn tracing(&self) -> &TracingConfig { - &self.tracing - } - - fn sequencer_rpc(&self) -> Option<&str> { - self.rollup_args.sequencer.as_deref() - } -} - -impl MeteringExtensionConfig for BaseNodeConfig { - fn metering_enabled(&self) -> bool { - self.metering_enabled - } -} diff --git a/crates/client/client-runner/src/context.rs b/crates/client/client-runner/src/context.rs deleted file mode 100644 index cf28cbb6..00000000 --- a/crates/client/client-runner/src/context.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Contains a type alias for the base node builder used in the runner. - -use std::sync::Arc; - -use reth::builder::{NodeBuilder, WithLaunchContext}; -use reth_db::DatabaseEnv; -use reth_optimism_chainspec::OpChainSpec; - -/// Convenience alias for the Base node builder type. -pub type BaseNodeBuilder = WithLaunchContext, OpChainSpec>>; diff --git a/crates/client/client-runner/src/lib.rs b/crates/client/client-runner/src/lib.rs deleted file mode 100644 index 1e902da1..00000000 --- a/crates/client/client-runner/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![doc = include_str!("../README.md")] -#![doc(issue_tracker_base_url = "https://github.com/base/node-reth/issues/")] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] - -mod context; -pub use context::BaseNodeBuilder; - -mod handle; -pub use handle::BaseNodeHandle; - -mod runner; -pub use runner::BaseNodeRunner; - -mod config; -pub use config::{BaseNodeConfig, RunnerFlashblocksCell}; diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index d95e17ad..6b1ace83 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -58,7 +58,6 @@ jsonrpsee-types.workspace = true serde.workspace = true # misc -eyre.workspace = true once_cell.workspace = true url.workspace = true thiserror.workspace = true diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index 36a493f5..ce4a53dd 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -3,9 +3,7 @@ use std::sync::Arc; -use base_client_primitives::{ - BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder, OpProvider, -}; +use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; use futures_util::TryStreamExt; use once_cell::sync::OnceCell; use reth_exex::ExExEvent; @@ -124,19 +122,3 @@ impl BaseNodeExtension for FlashblocksExtension { }) } } - -/// Configuration trait for [`FlashblocksExtension`]. -/// -/// Types implementing this trait can be used to construct a [`FlashblocksExtension`]. -pub trait FlashblocksExtensionConfig { - /// Returns the shared flashblocks cell. - fn flashblocks_cell(&self) -> &FlashblocksCell>; - /// Returns the flashblocks configuration if enabled. - fn flashblocks(&self) -> Option<&FlashblocksConfig>; -} - -impl ConfigurableBaseNodeExtension for FlashblocksExtension { - fn build(config: &C) -> eyre::Result { - Ok(Self::new(config.flashblocks_cell().clone(), config.flashblocks().cloned())) - } -} diff --git a/crates/client/flashblocks/src/lib.rs b/crates/client/flashblocks/src/lib.rs index d1969f72..dbed7230 100644 --- a/crates/client/flashblocks/src/lib.rs +++ b/crates/client/flashblocks/src/lib.rs @@ -45,6 +45,4 @@ pub use rpc::{ }; mod extension; -pub use extension::{ - FlashblocksCell, FlashblocksConfig, FlashblocksExtension, FlashblocksExtensionConfig, -}; +pub use extension::{FlashblocksCell, FlashblocksConfig, FlashblocksExtension}; diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 59c1c99e..77b0fba2 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`MeteringExtension`] which wires up the metering RPC surface //! on the Base node builder. -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, OpBuilder}; use tracing::info; use crate::{MeteringApiImpl, MeteringApiServer}; @@ -35,17 +35,3 @@ impl BaseNodeExtension for MeteringExtension { }) } } - -/// Configuration trait for [`MeteringExtension`]. -/// -/// Types implementing this trait can be used to construct a [`MeteringExtension`]. -pub trait MeteringExtensionConfig { - /// Returns whether metering is enabled. - fn metering_enabled(&self) -> bool; -} - -impl ConfigurableBaseNodeExtension for MeteringExtension { - fn build(config: &C) -> eyre::Result { - Ok(Self::new(config.metering_enabled())) - } -} diff --git a/crates/client/metering/src/lib.rs b/crates/client/metering/src/lib.rs index 1c23bd30..41883a8d 100644 --- a/crates/client/metering/src/lib.rs +++ b/crates/client/metering/src/lib.rs @@ -7,7 +7,7 @@ mod block; pub use block::meter_block; mod extension; -pub use extension::{MeteringExtension, MeteringExtensionConfig}; +pub use extension::MeteringExtension; mod meter; pub use meter::meter_bundle; diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index 21628819..e7687ce1 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`TxPoolExtension`] which wires up the transaction pool features //! (tracing ExEx and status RPC) on the Base node builder. -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_primitives::{BaseNodeExtension, OpBuilder}; use tracing::info; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; @@ -52,19 +52,3 @@ impl BaseNodeExtension for TxPoolExtension { }) } } - -/// Configuration trait for [`TxPoolExtension`]. -/// -/// Types implementing this trait can be used to construct a [`TxPoolExtension`]. -pub trait TxPoolExtensionConfig { - /// Returns the tracing configuration. - fn tracing(&self) -> &TracingConfig; - /// Returns the sequencer RPC URL if configured. - fn sequencer_rpc(&self) -> Option<&str>; -} - -impl ConfigurableBaseNodeExtension for TxPoolExtension { - fn build(config: &C) -> eyre::Result { - Ok(Self::new(*config.tracing(), config.sequencer_rpc().map(String::from))) - } -} diff --git a/crates/client/txpool/src/lib.rs b/crates/client/txpool/src/lib.rs index 66e9ecd1..96f35667 100644 --- a/crates/client/txpool/src/lib.rs +++ b/crates/client/txpool/src/lib.rs @@ -18,4 +18,4 @@ mod tracker; pub use tracker::Tracker; mod extension; -pub use extension::{TracingConfig, TxPoolExtension, TxPoolExtensionConfig}; +pub use extension::{TracingConfig, TxPoolExtension}; From b55a54eac99498e1defbe7282642a875cd53b99b Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 14:04:49 -0600 Subject: [PATCH 244/262] chore: unify test account logic --- .../flashblocks/benches/pending_state.rs | 14 +- .../client/flashblocks/tests/eip7702_tests.rs | 50 ++--- .../flashblocks/tests/eth_call_erc20.rs | 21 +- .../flashblocks/tests/flashblocks_rpc.rs | 41 ++-- crates/client/flashblocks/tests/state.rs | 199 ++++++++---------- crates/client/metering/tests/meter.rs | 84 +++----- crates/client/metering/tests/meter_block.rs | 66 ++---- crates/client/test-utils/src/accounts.rs | 153 +++++--------- crates/client/test-utils/src/harness.rs | 18 +- crates/client/test-utils/src/lib.rs | 2 +- 10 files changed, 244 insertions(+), 404 deletions(-) diff --git a/crates/client/flashblocks/benches/pending_state.rs b/crates/client/flashblocks/benches/pending_state.rs index a38df250..9f2aacfe 100644 --- a/crates/client/flashblocks/benches/pending_state.rs +++ b/crates/client/flashblocks/benches/pending_state.rs @@ -12,7 +12,7 @@ use base_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{LocalNodeProvider, TestAccounts, TestHarness}; +use base_test_utils::{Account, LocalNodeProvider, TestHarness}; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; use reth::{ chainspec::{ChainSpecProvider, EthChainSpec}, @@ -56,7 +56,7 @@ impl BenchSetup { let flashblocks = tx_counts .iter() .map(|count| { - let txs = sample_transactions(&provider, harness.accounts(), *count); + let txs = sample_transactions(&provider, *count); let blocks = build_flashblocks(&canonical_block, &txs); (format!("pending_state_{}_txs", count), blocks) }) @@ -260,12 +260,8 @@ fn transaction_flashblock( } } -fn sample_transactions( - provider: &LocalNodeProvider, - accounts: &TestAccounts, - count: usize, -) -> Vec { - let signer = B256::from_hex(accounts.alice.private_key).expect("valid private key hex"); +fn sample_transactions(provider: &LocalNodeProvider, count: usize) -> Vec { + let signer = B256::from_hex(Account::Alice.private_key()).expect("valid private key hex"); let chain_id = provider.chain_spec().chain_id(); (0..count as u64) @@ -273,7 +269,7 @@ fn sample_transactions( let txn = TransactionBuilder::default() .signer(signer) .chain_id(chain_id) - .to(accounts.bob.address) + .to(Account::Bob.address()) .nonce(nonce) .value(1_000_000_000u128) .gas_limit(TX_GAS_USED) diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index 1bfc1c55..435cc71b 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -31,7 +31,7 @@ struct TestSetup { impl TestSetup { async fn new() -> Result { let harness = FlashblocksHarness::new().await?; - let deployer = &harness.accounts().deployer; + let deployer = Account::Deployer; // Deploy Minimal7702Account contract let deploy_data = Minimal7702Account::BYTECODE.to_vec(); @@ -52,14 +52,6 @@ impl TestSetup { fn chain_id(&self) -> u64 { 84532 // Base Sepolia chain ID (matches test harness) } - - fn alice(&self) -> &Account { - &self.harness.accounts().alice - } - - fn bob(&self) -> &Account { - &self.harness.accounts().bob - } } /// Build an EIP-7702 authorization for delegating to a contract @@ -67,7 +59,7 @@ fn build_authorization( chain_id: u64, contract_address: Address, nonce: u64, - account: &Account, + account: Account, ) -> alloy_eips::eip7702::SignedAuthorization { let auth = Authorization { chain_id: U256::from(chain_id), address: contract_address, nonce }; @@ -83,7 +75,7 @@ fn build_eip7702_tx( value: U256, input: Bytes, authorization_list: Vec, - account: &Account, + account: Account, ) -> Bytes { let tx = TxEip7702 { chain_id, @@ -111,7 +103,7 @@ fn build_eip1559_tx( to: Address, value: U256, input: Bytes, - account: &Account, + account: Account, ) -> Bytes { let tx = TxEip1559 { chain_id, @@ -187,7 +179,7 @@ async fn test_eip7702_delegation_in_pending_flashblock() -> Result<()> { setup.send_flashblock(base_payload).await?; // Create authorization for Alice to delegate to the Minimal7702Account contract - let auth = build_authorization(chain_id, setup.account_contract_address, 0, setup.alice()); + let auth = build_authorization(chain_id, setup.account_contract_address, 0, Account::Alice); // Build EIP-7702 transaction with authorization // This delegates Alice's EOA to execute code from Minimal7702Account @@ -195,11 +187,11 @@ async fn test_eip7702_delegation_in_pending_flashblock() -> Result<()> { let eip7702_tx = build_eip7702_tx( chain_id, 0, - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), vec![auth], - setup.alice(), + Account::Alice, ); let tx_hash = alloy_primitives::keccak256(&eip7702_tx); @@ -230,29 +222,29 @@ async fn test_eip7702_multiple_delegations_same_flashblock() -> Result<()> { // Create authorizations for both Alice and Bob let auth_alice = - build_authorization(chain_id, setup.account_contract_address, 0, setup.alice()); - let auth_bob = build_authorization(chain_id, setup.account_contract_address, 0, setup.bob()); + build_authorization(chain_id, setup.account_contract_address, 0, Account::Alice); + let auth_bob = build_authorization(chain_id, setup.account_contract_address, 0, Account::Bob); // Build EIP-7702 transactions let increment_call = Minimal7702Account::incrementCall {}; let tx_alice = build_eip7702_tx( chain_id, 0, - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), vec![auth_alice], - setup.alice(), + Account::Alice, ); let tx_bob = build_eip7702_tx( chain_id, 0, - setup.bob().address, + Account::Bob.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), vec![auth_bob], - setup.bob(), + Account::Bob, ); let tx_hash_alice = alloy_primitives::keccak256(&tx_alice); @@ -303,16 +295,16 @@ async fn test_eip7702_pending_receipt() -> Result<()> { setup.send_flashblock(base_payload).await?; // Create and send EIP-7702 transaction - let auth = build_authorization(chain_id, setup.account_contract_address, 0, setup.alice()); + let auth = build_authorization(chain_id, setup.account_contract_address, 0, Account::Alice); let increment_call = Minimal7702Account::incrementCall {}; let eip7702_tx = build_eip7702_tx( chain_id, 0, - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), vec![auth], - setup.alice(), + Account::Alice, ); let tx_hash = alloy_primitives::keccak256(&eip7702_tx); @@ -346,15 +338,15 @@ async fn test_eip7702_delegation_then_execution() -> Result<()> { let delegation_gas = 30000; let delegation_cumulative = BASE_CUMULATIVE_GAS + delegation_gas; - let auth = build_authorization(chain_id, setup.account_contract_address, 0, setup.alice()); + let auth = build_authorization(chain_id, setup.account_contract_address, 0, Account::Alice); let delegation_tx = build_eip7702_tx( chain_id, 0, - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::new(), // Empty input - just setting up delegation vec![auth], - setup.alice(), + Account::Alice, ); let delegation_hash = alloy_primitives::keccak256(&delegation_tx); @@ -372,10 +364,10 @@ async fn test_eip7702_delegation_then_execution() -> Result<()> { let execution_tx = build_eip1559_tx( chain_id, 1, // incremented nonce - setup.alice().address, + Account::Alice.address(), U256::ZERO, Bytes::from(increment_call.abi_encode()), - setup.alice(), + Account::Alice, ); let execution_hash = alloy_primitives::keccak256(&execution_tx); diff --git a/crates/client/flashblocks/tests/eth_call_erc20.rs b/crates/client/flashblocks/tests/eth_call_erc20.rs index 2c62d223..cb833701 100644 --- a/crates/client/flashblocks/tests/eth_call_erc20.rs +++ b/crates/client/flashblocks/tests/eth_call_erc20.rs @@ -20,7 +20,7 @@ use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_test_utils::{ - FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, + Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, }; use eyre::Result; struct Erc20TestSetup { @@ -34,7 +34,7 @@ struct Erc20TestSetup { impl Erc20TestSetup { async fn new(with_proxy: bool) -> Result { let harness = FlashblocksHarness::new().await?; - let deployer = &harness.accounts().deployer; + let deployer = Account::Deployer; // Deploy MockERC20 from solmate with constructor args (name, symbol, decimals) let token_constructor = MockERC20::constructorCall { @@ -52,7 +52,7 @@ impl Erc20TestSetup { // Constructor: (implementation, initialOwner, data) let proxy_constructor = TransparentUpgradeableProxy::constructorCall { _logic: token_address, - initialOwner: deployer.address, + initialOwner: deployer.address(), _data: Bytes::new(), }; let proxy_deploy_data = @@ -212,14 +212,13 @@ async fn test_proxy_erc20_deployment() -> Result<()> { async fn test_erc20_mint() -> Result<()> { let setup = Erc20TestSetup::new(false).await?; let provider = setup.harness.provider(); - let accounts = setup.harness.accounts(); // Deploy contracts first setup.send_base_and_deploy().await?; // Check initial balance is zero let token = MockERC20::MockERC20Instance::new(setup.token_address, provider.clone()); - let balance_call = token.balanceOf(accounts.alice.address).into_transaction_request(); + let balance_call = token.balanceOf(Account::Alice.address()).into_transaction_request(); let result = provider.call(balance_call.clone()).block(BlockNumberOrTag::Pending.into()).await?; let initial_balance = U256::abi_decode(&result)?; @@ -228,8 +227,8 @@ async fn test_erc20_mint() -> Result<()> { // Create mint transaction let mint_amount = U256::from(1000u64); let mint_tx_request = - token.mint(accounts.alice.address, mint_amount).into_transaction_request(); - let (mint_tx, _) = accounts.deployer.sign_txn_request(mint_tx_request.nonce(1))?; + token.mint(Account::Alice.address(), mint_amount).into_transaction_request(); + let (mint_tx, _) = Account::Deployer.sign_txn_request(mint_tx_request.nonce(1))?; // Send mint flashblock let mint_payload = setup.create_mint_payload(mint_tx); @@ -248,7 +247,6 @@ async fn test_erc20_mint() -> Result<()> { async fn test_proxy_erc20_mint() -> Result<()> { let setup = Erc20TestSetup::new(true).await?; let provider = setup.harness.provider(); - let accounts = setup.harness.accounts(); // Deploy contracts first setup.send_base_and_deploy().await?; @@ -256,7 +254,8 @@ async fn test_proxy_erc20_mint() -> Result<()> { // Check initial balance is zero through proxy let proxy_address = setup.proxy_address.unwrap(); let token_via_proxy = MockERC20::MockERC20Instance::new(proxy_address, provider.clone()); - let balance_call = token_via_proxy.balanceOf(accounts.alice.address).into_transaction_request(); + let balance_call = + token_via_proxy.balanceOf(Account::Alice.address()).into_transaction_request(); let result = provider.call(balance_call.clone()).block(BlockNumberOrTag::Pending.into()).await?; let initial_balance = U256::abi_decode(&result)?; @@ -265,8 +264,8 @@ async fn test_proxy_erc20_mint() -> Result<()> { // Create mint transaction through proxy let mint_amount = U256::from(5000u64); let mint_tx_request = - token_via_proxy.mint(accounts.alice.address, mint_amount).into_transaction_request(); - let (mint_tx, _) = accounts.deployer.sign_txn_request(mint_tx_request.nonce(2))?; + token_via_proxy.mint(Account::Alice.address(), mint_amount).into_transaction_request(); + let (mint_tx, _) = Account::Deployer.sign_txn_request(mint_tx_request.nonce(2))?; // Send mint flashblock (note: interaction_address returns proxy) let mint_payload = setup.create_mint_payload(mint_tx); diff --git a/crates/client/flashblocks/tests/flashblocks_rpc.rs b/crates/client/flashblocks/tests/flashblocks_rpc.rs index d589a568..3544af1e 100644 --- a/crates/client/flashblocks/tests/flashblocks_rpc.rs +++ b/crates/client/flashblocks/tests/flashblocks_rpc.rs @@ -14,7 +14,7 @@ use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; +use base_test_utils::{Account, DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; @@ -173,9 +173,9 @@ impl TestSetup { let harness = FlashblocksHarness::new().await?; let provider = harness.provider(); - let deployer = &harness.accounts().deployer; - let alice = &harness.accounts().alice; - let bob = &harness.accounts().bob; + let deployer = Account::Deployer; + let alice = Account::Alice; + let bob = Account::Bob; // DoubleCounter deployment at nonce 0 let (counter_deployment_tx, counter_address, _) = deployer @@ -193,11 +193,11 @@ impl TestSetup { let (eth_transfer_tx, eth_transfer_hash) = alice .sign_txn_request( OpTransactionRequest::default() - .from(alice.address) + .from(alice.address()) .transaction_type(TransactionType::Eip1559.into()) .gas_limit(100_000) .nonce(0) - .to(bob.address) + .to(bob.address()) .value(U256::from_str("999999999000000000000000").unwrap()) .into(), ) @@ -205,14 +205,14 @@ impl TestSetup { // Log-emitting contracts: // Deploy LogEmitterB at deployer nonce 3 - let log_emitter_b_address = deployer.address.create(3); + let log_emitter_b_address = deployer.address().create(3); let log_emitter_b_bytecode = wrap_in_init_code(LOG_EMITTER_B_RUNTIME); let (log_emitter_b_deployment_tx, _, _) = deployer .create_deployment_tx(log_emitter_b_bytecode, 3) .expect("should be able to sign LogEmitterB deployment txn"); // Deploy LogEmitterA at deployer nonce 4 (knows LogEmitterB's address) - let log_emitter_a_address = deployer.address.create(4); + let log_emitter_a_address = deployer.address().create(4); let log_emitter_a_runtime = log_emitter_a_runtime(log_emitter_b_address); let log_emitter_a_bytecode = wrap_in_init_code(&log_emitter_a_runtime); let (log_emitter_a_deployment_tx, _, _) = deployer @@ -223,7 +223,7 @@ impl TestSetup { let (log_trigger_tx, log_trigger_hash) = deployer .sign_txn_request( OpTransactionRequest::default() - .from(deployer.address) + .from(deployer.address()) .transaction_type(TransactionType::Eip1559.into()) .gas_limit(100_000) .nonce(5) @@ -236,7 +236,7 @@ impl TestSetup { let (balance_transfer_tx, _) = alice .sign_txn_request( OpTransactionRequest::default() - .from(alice.address) + .from(alice.address()) .transaction_type(TransactionType::Eip1559.into()) .gas_limit(21_000) .nonce(1) @@ -471,11 +471,8 @@ async fn test_get_transaction_by_hash_pending() -> Result<()> { .await? .expect("tx2 expected"); assert_eq!(tx2.tx_hash(), setup.txn_details.alice_eth_transfer_hash); - assert_eq!(tx2.from(), setup.harness.accounts().alice.address); - assert_eq!( - tx2.inner.inner.as_eip1559().unwrap().to().unwrap(), - setup.harness.accounts().bob.address - ); + assert_eq!(tx2.from(), Account::Alice.address()); + assert_eq!(tx2.inner.inner.as_eip1559().unwrap().to().unwrap(), Account::Bob.address()); Ok(()) } @@ -508,8 +505,8 @@ async fn test_get_transaction_count() -> Result<()> { let setup = TestSetup::new().await?; let provider = setup.harness.provider(); - let deployer_addr = setup.harness.accounts().deployer.address; - let alice_addr = setup.harness.accounts().alice.address; + let deployer_addr = Account::Deployer.address(); + let alice_addr = Account::Alice.address(); assert_eq!(provider.get_transaction_count(DEPOSIT_SENDER).pending().await?, 0); assert_eq!(provider.get_transaction_count(deployer_addr).pending().await?, 0); @@ -532,15 +529,13 @@ async fn test_eth_call() -> Result<()> { let setup = TestSetup::new().await?; let provider = setup.harness.provider(); - let accounts = setup.harness.accounts(); - // Initially, the big spend will succeed because we haven't sent the test payloads yet let big_spend = OpTransactionRequest::default() - .from(accounts.alice.address) + .from(Account::Alice.address()) .transaction_type(0) .gas_limit(200000) .nonce(0) - .to(setup.harness.accounts().bob.address) + .to(Account::Bob.address()) .value(U256::from(9999999999849942300000u128)); let res = provider.call(big_spend.clone()).block(BlockNumberOrTag::Pending.into()).await; @@ -577,11 +572,11 @@ async fn test_eth_estimate_gas() -> Result<()> { // We ensure that eth_estimate_gas will succeed because we are on plain state let send_estimate_gas = OpTransactionRequest::default() - .from(setup.harness.accounts().alice.address) + .from(Account::Alice.address()) .transaction_type(0) .gas_limit(200000) .nonce(0) - .to(setup.harness.accounts().bob.address) + .to(Account::Bob.address()) .value(U256::from(9999999999849942300000u128)) .input(TransactionInput::new(bytes!("0x"))); diff --git a/crates/client/flashblocks/tests/state.rs b/crates/client/flashblocks/tests/state.rs index 6102952c..25068b0e 100644 --- a/crates/client/flashblocks/tests/state.rs +++ b/crates/client/flashblocks/tests/state.rs @@ -4,17 +4,15 @@ use std::{sync::Arc, time::Duration}; use alloy_consensus::{Receipt, Transaction}; use alloy_eips::{BlockHashOrNumber, Encodable2718}; -use alloy_primitives::{ - Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::foldhash::HashMap, -}; +use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::HashMap}; use alloy_rpc_types_engine::PayloadId; use base_flashblocks::{FlashblocksAPI, FlashblocksState, PendingBlocksAPI}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_test_utils::{ - FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, - TestAccounts, + Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, + LocalNodeProvider, }; use op_alloy_consensus::OpDepositReceipt; use op_alloy_network::BlockResponse; @@ -24,26 +22,17 @@ use reth::{ transaction_pool::test_utils::TransactionBuilder, }; use reth_optimism_primitives::{OpBlock, OpReceipt, OpTransactionSigned}; -use reth_primitives_traits::{Account, Block as BlockT, RecoveredBlock}; +use reth_primitives_traits::{Account as RethAccount, Block as BlockT, RecoveredBlock}; use reth_provider::{ChainSpecProvider, StateProviderFactory}; use tokio::time::sleep; // The amount of time to wait (in milliseconds) after sending a new flashblock or canonical block // so it can be processed by the state processor const SLEEP_TIME: u64 = 10; -#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)] -enum User { - Alice, - Bob, - Charlie, -} - struct TestHarness { node: FlashblocksHarness, flashblocks: Arc>, provider: LocalNodeProvider, - user_to_address: HashMap, - user_to_private_key: HashMap, } impl TestHarness { @@ -64,67 +53,43 @@ impl TestHarness { .expect("able to recover block"); flashblocks.on_canonical_block_received(genesis_block); - let accounts: TestAccounts = node.accounts().clone(); - - let mut user_to_address = HashMap::default(); - user_to_address.insert(User::Alice, accounts.alice.address); - user_to_address.insert(User::Bob, accounts.bob.address); - user_to_address.insert(User::Charlie, accounts.charlie.address); - - let mut user_to_private_key = HashMap::default(); - user_to_private_key - .insert(User::Alice, Self::decode_private_key(accounts.alice.private_key)); - user_to_private_key.insert(User::Bob, Self::decode_private_key(accounts.bob.private_key)); - user_to_private_key - .insert(User::Charlie, Self::decode_private_key(accounts.charlie.private_key)); - - Self { node, flashblocks, provider, user_to_address, user_to_private_key } - } - - fn decode_private_key(key: &str) -> B256 { - B256::from_hex(key).expect("valid hex-encoded key") - } - - fn address(&self, u: User) -> Address { - assert!(self.user_to_address.contains_key(&u)); - self.user_to_address[&u] + Self { node, flashblocks, provider } } - fn signer(&self, u: User) -> B256 { - assert!(self.user_to_private_key.contains_key(&u)); - self.user_to_private_key[&u] + fn decode_private_key(account: Account) -> B256 { + B256::from_hex(account.private_key()).expect("valid hex-encoded key") } - fn canonical_account(&self, u: User) -> Account { + fn canonical_account(&self, account: Account) -> RethAccount { self.provider - .basic_account(&self.address(u)) + .basic_account(&account.address()) .expect("can lookup account state") .expect("should be existing account state") } - fn canonical_balance(&self, u: User) -> U256 { - self.canonical_account(u).balance + fn canonical_balance(&self, account: Account) -> U256 { + self.canonical_account(account).balance } - fn expected_pending_balance(&self, u: User, delta: u128) -> U256 { - self.canonical_balance(u) + U256::from(delta) + fn expected_pending_balance(&self, account: Account, delta: u128) -> U256 { + self.canonical_balance(account) + U256::from(delta) } - fn account_state(&self, u: User) -> Account { - let basic_account = self.canonical_account(u); + fn account_state(&self, account: Account) -> RethAccount { + let basic_account = self.canonical_account(account); let nonce = self .flashblocks .get_pending_blocks() - .get_transaction_count(self.address(u)) + .get_transaction_count(account.address()) .to::(); let balance = self .flashblocks .get_pending_blocks() - .get_balance(self.address(u)) + .get_balance(account.address()) .unwrap_or(basic_account.balance); - Account { + RethAccount { nonce: nonce + basic_account.nonce, balance, bytecode_hash: basic_account.bytecode_hash, @@ -133,14 +98,14 @@ impl TestHarness { fn build_transaction_to_send_eth( &self, - from: User, - to: User, + from: Account, + to: Account, amount: u128, ) -> OpTransactionSigned { let txn = TransactionBuilder::default() - .signer(self.signer(from)) + .signer(Self::decode_private_key(from)) .chain_id(self.provider.chain_spec().chain_id()) - .to(self.address(to)) + .to(to.address()) .nonce(self.account_state(from).nonce) .value(amount) .gas_limit(21_000) @@ -156,15 +121,15 @@ impl TestHarness { fn build_transaction_to_send_eth_with_nonce( &self, - from: User, - to: User, + from: Account, + to: Account, amount: u128, nonce: u64, ) -> OpTransactionSigned { let txn = TransactionBuilder::default() - .signer(self.signer(from)) + .signer(Self::decode_private_key(from)) .chain_id(self.provider.chain_spec().chain_id()) - .to(self.address(to)) + .to(to.address()) .nonce(nonce) .value(amount) .gas_limit(21_000) @@ -351,14 +316,14 @@ async fn test_state_overrides_persisted_across_flashblocks() { .get_pending_blocks() .get_state_overrides() .unwrap() - .contains_key(&test.address(User::Alice)) + .contains_key(&Account::Alice.address()) ); test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -376,14 +341,14 @@ async fn test_state_overrides_persisted_across_flashblocks() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); test.send_flashblock(FlashblockBuilder::new(&test, 2).build()).await; @@ -394,14 +359,14 @@ async fn test_state_overrides_persisted_across_flashblocks() { .get_state_overrides() .expect("should be set from txn execution in flashblock index 1"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); } @@ -429,14 +394,14 @@ async fn test_state_overrides_persisted_across_blocks() { .get_pending_blocks() .get_state_overrides() .unwrap() - .contains_key(&test.address(User::Alice)) + .contains_key(&Account::Alice.address()) ); test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -454,14 +419,14 @@ async fn test_state_overrides_persisted_across_blocks() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); test.send_flashblock( @@ -496,15 +461,15 @@ async fn test_state_overrides_persisted_across_blocks() { .get_pending_blocks() .get_state_overrides() .unwrap() - .contains_key(&test.address(User::Alice)) + .contains_key(&Account::Alice.address()) ); test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_canonical_block_number(initial_block_number) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -517,14 +482,14 @@ async fn test_state_overrides_persisted_across_blocks() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 200_000) + test.expected_pending_balance(Account::Bob, 200_000) ); } @@ -549,14 +514,14 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { .get_pending_blocks() .get_state_overrides() .unwrap() - .contains_key(&test.address(User::Alice)) + .contains_key(&Account::Alice.address()) ); test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -573,14 +538,14 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); test.send_flashblock(FlashblockBuilder::new_base(&test).with_canonical_block_number(1).build()) @@ -589,8 +554,8 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { FlashblockBuilder::new(&test, 1) .with_canonical_block_number(1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(), @@ -607,19 +572,19 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 200_000) + test.expected_pending_balance(Account::Bob, 200_000) ); test.new_canonical_block(vec![test.build_transaction_to_send_eth_with_nonce( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100, 0, )]) @@ -636,14 +601,14 @@ async fn test_only_current_pending_state_cleared_upon_canonical_block_reorg() { .get_state_overrides() .expect("should be set from txn execution"); - assert!(overrides.get(&test.address(User::Alice)).is_some()); + assert!(overrides.get(&Account::Alice.address()).is_some()); assert_eq!( overrides - .get(&test.address(User::Bob)) + .get(&Account::Bob.address()) .expect("should be set as txn receiver") .balance .expect("should be changed due to receiving funds"), - test.expected_pending_balance(User::Bob, 100_000) + test.expected_pending_balance(Account::Bob, 100_000) ); } @@ -660,8 +625,8 @@ async fn test_nonce_uses_pending_canon_block_instead_of_latest() { test.send_flashblock( FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100, )]) .build(), @@ -669,25 +634,25 @@ async fn test_nonce_uses_pending_canon_block_instead_of_latest() { .await; let pending_nonce = - test.provider.basic_account(&test.address(User::Alice)).unwrap().unwrap().nonce + test.provider.basic_account(&Account::Alice.address()).unwrap().unwrap().nonce + test .flashblocks .get_pending_blocks() - .get_transaction_count(test.address(User::Alice)) + .get_transaction_count(Account::Alice.address()) .to::(); assert_eq!(pending_nonce, 1); test.new_canonical_block_without_processing(vec![ - test.build_transaction_to_send_eth_with_nonce(User::Alice, User::Bob, 100, 0), + test.build_transaction_to_send_eth_with_nonce(Account::Alice, Account::Bob, 100, 0), ]) .await; let pending_nonce = - test.provider.basic_account(&test.address(User::Alice)).unwrap().unwrap().nonce + test.provider.basic_account(&Account::Alice.address()).unwrap().unwrap().nonce + test .flashblocks .get_pending_blocks() - .get_transaction_count(test.address(User::Alice)) + .get_transaction_count(Account::Alice.address()) .to::(); // This is 2, because canon block has reached the underlying chain @@ -702,12 +667,12 @@ async fn test_nonce_uses_pending_canon_block_instead_of_latest() { let canon_block = test.flashblocks.get_pending_blocks().get_canonical_block_number(); let canon_state_provider = test.provider.state_by_block_number_or_tag(canon_block).unwrap(); let canon_nonce = - canon_state_provider.account_nonce(&test.address(User::Alice)).unwrap().unwrap(); + canon_state_provider.account_nonce(&Account::Alice.address()).unwrap().unwrap(); let pending_nonce = canon_nonce + test .flashblocks .get_pending_blocks() - .get_transaction_count(test.address(User::Alice)) + .get_transaction_count(Account::Alice.address()) .to::(); assert_eq!(pending_nonce, 1); } @@ -798,8 +763,8 @@ async fn test_non_sequential_payload_clears_pending_state() { test.send_flashblock( FlashblockBuilder::new(&test, 3) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100, )]) .build(), @@ -817,8 +782,8 @@ async fn test_duplicate_flashblock_ignored() { let fb = FlashblockBuilder::new(&test, 1) .with_transactions(vec![test.build_transaction_to_send_eth( - User::Alice, - User::Bob, + Account::Alice, + Account::Bob, 100_000, )]) .build(); @@ -841,8 +806,12 @@ async fn test_progress_canonical_blocks_without_flashblocks() { assert_eq!(genesis_block.transaction_count(), 0); assert!(test.flashblocks.get_pending_blocks().get_block(true).is_none()); - test.new_canonical_block(vec![test.build_transaction_to_send_eth(User::Alice, User::Bob, 100)]) - .await; + test.new_canonical_block(vec![test.build_transaction_to_send_eth( + Account::Alice, + Account::Bob, + 100, + )]) + .await; let block_one = test.node.latest_block(); assert_eq!(block_one.number, 1); @@ -850,8 +819,8 @@ async fn test_progress_canonical_blocks_without_flashblocks() { assert!(test.flashblocks.get_pending_blocks().get_block(true).is_none()); test.new_canonical_block(vec![ - test.build_transaction_to_send_eth(User::Bob, User::Charlie, 100), - test.build_transaction_to_send_eth(User::Charlie, User::Alice, 1000), + test.build_transaction_to_send_eth(Account::Bob, Account::Charlie, 100), + test.build_transaction_to_send_eth(Account::Charlie, Account::Alice, 1000), ]) .await; diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index e014691b..4226b710 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -2,16 +2,14 @@ use std::sync::Arc; -use alloy_consensus::crypto::secp256k1::public_key_to_address; use alloy_eips::Encodable2718; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, Bytes, U256, keccak256}; +use alloy_primitives::{Address, B256, Bytes, U256, hex::FromHex, keccak256}; use base_bundles::{Bundle, ParsedBundle}; use base_metering::meter_bundle; -use base_test_utils::create_provider_factory; +use base_test_utils::{Account, create_provider_factory}; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; -use rand::{SeedableRng, rngs::StdRng}; use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; use reth_db::{DatabaseEnv, test_utils::TempDatabase}; use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; @@ -19,77 +17,45 @@ use reth_optimism_node::OpNode; use reth_optimism_primitives::OpTransactionSigned; use reth_primitives_traits::SealedHeader; use reth_provider::{HeaderProvider, StateProviderFactory, providers::BlockchainProvider}; -use reth_testing_utils::generators::generate_keys; use reth_transaction_pool::test_utils::TransactionBuilder; type NodeTypes = NodeTypesWithDBAdapter>>; -#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)] -enum User { - Alice, - Bob, -} - #[derive(Debug, Clone)] struct TestHarness { provider: BlockchainProvider, header: SealedHeader, chain_spec: Arc, - user_to_address: std::collections::HashMap, - user_to_private_key: std::collections::HashMap, } impl TestHarness { - fn address(&self, u: User) -> Address { - self.user_to_address[&u] - } - - fn signer(&self, u: User) -> B256 { - self.user_to_private_key[&u] + fn signer(&self, account: Account) -> B256 { + B256::from_hex(account.private_key()).expect("valid private key hex") } } -fn create_chain_spec( - seed: u64, -) -> ( - Arc, - std::collections::HashMap, - std::collections::HashMap, -) { - let keys = generate_keys(&mut StdRng::seed_from_u64(seed), 2); - - let mut addresses = std::collections::HashMap::new(); - let mut private_keys = std::collections::HashMap::new(); - - let alice_key = keys[0]; - let alice_address = public_key_to_address(alice_key.public_key()); - let alice_secret = B256::from(alice_key.secret_bytes()); - addresses.insert(User::Alice, alice_address); - private_keys.insert(User::Alice, alice_secret); - - let bob_key = keys[1]; - let bob_address = public_key_to_address(bob_key.public_key()); - let bob_secret = B256::from(bob_key.secret_bytes()); - addresses.insert(User::Bob, bob_address); - private_keys.insert(User::Bob, bob_secret); - +fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis .clone() - .extend_accounts(vec![ - (alice_address, GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64))), - (bob_address, GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64))), - ]) + .extend_accounts( + Account::all() + .into_iter() + .map(|a| { + ( + a.address(), + GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), + ) + }) + .collect::>(), + ) .with_gas_limit(100_000_000); - let spec = - Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()); - - (spec, addresses, private_keys) + Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) } fn setup_harness() -> eyre::Result { - let (chain_spec, user_to_address, user_to_private_key) = create_chain_spec(1337); + let chain_spec = create_chain_spec(); let factory = create_provider_factory::(chain_spec.clone()); reth_db_common::init::init_genesis(&factory).context("initializing genesis state")?; @@ -100,7 +66,7 @@ fn setup_harness() -> eyre::Result { .context("fetching genesis header")? .expect("genesis header exists"); - Ok(TestHarness { provider, header, chain_spec, user_to_address, user_to_private_key }) + Ok(TestHarness { provider, header, chain_spec }) } fn envelope_from_signed(tx: &OpTransactionSigned) -> eyre::Result { @@ -155,7 +121,7 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { let to = Address::random(); let signed_tx = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to) @@ -185,7 +151,7 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { let result = &results[0]; assert!(total_execution_time > 0); - assert_eq!(result.from_address, harness.address(User::Alice)); + assert_eq!(result.from_address, Account::Alice.address()); assert_eq!(result.to_address, Some(to)); assert_eq!(result.tx_hash, tx_hash); assert_eq!(result.gas_price, U256::from(10)); @@ -213,7 +179,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create first transaction let signed_tx_1 = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_1) @@ -229,7 +195,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create second transaction let signed_tx_2 = TransactionBuilder::default() - .signer(harness.signer(User::Bob)) + .signer(harness.signer(Account::Bob)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_2) @@ -263,7 +229,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Check first transaction let result_1 = &results[0]; - assert_eq!(result_1.from_address, harness.address(User::Alice)); + assert_eq!(result_1.from_address, Account::Alice.address()); assert_eq!(result_1.to_address, Some(to_1)); assert_eq!(result_1.tx_hash, tx_hash_1); assert_eq!(result_1.gas_price, U256::from(10)); @@ -272,7 +238,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Check second transaction let result_2 = &results[1]; - assert_eq!(result_2.from_address, harness.address(User::Bob)); + assert_eq!(result_2.from_address, Account::Bob.address()); assert_eq!(result_2.to_address, Some(to_2)); assert_eq!(result_2.tx_hash, tx_hash_2); assert_eq!(result_2.gas_price, U256::from(15)); diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 0ad45a0d..22bc93a4 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -2,13 +2,12 @@ use std::sync::Arc; -use alloy_consensus::{BlockHeader, Header, crypto::secp256k1::public_key_to_address}; +use alloy_consensus::{BlockHeader, Header}; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, B256, U256, hex::FromHex}; use base_metering::meter_block; -use base_test_utils::create_provider_factory; +use base_test_utils::{Account, create_provider_factory}; use eyre::Context; -use rand::{SeedableRng, rngs::StdRng}; use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; use reth_db::{DatabaseEnv, test_utils::TempDatabase}; use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; @@ -16,17 +15,10 @@ use reth_optimism_node::OpNode; use reth_optimism_primitives::{OpBlock, OpBlockBody, OpTransactionSigned}; use reth_primitives_traits::Block as BlockT; use reth_provider::{HeaderProvider, providers::BlockchainProvider}; -use reth_testing_utils::generators::generate_keys; use reth_transaction_pool::test_utils::TransactionBuilder; type NodeTypes = NodeTypesWithDBAdapter>>; -#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)] -enum User { - Alice, - Bob, -} - #[derive(Debug, Clone)] struct TestHarness { provider: BlockchainProvider, @@ -34,47 +26,36 @@ struct TestHarness { genesis_header_number: u64, genesis_header_timestamp: u64, chain_spec: Arc, - user_to_private_key: std::collections::HashMap, } impl TestHarness { - fn signer(&self, u: User) -> B256 { - self.user_to_private_key[&u] + fn signer(&self, account: Account) -> B256 { + B256::from_hex(account.private_key()).expect("valid private key hex") } } -fn create_chain_spec(seed: u64) -> (Arc, std::collections::HashMap) { - let keys = generate_keys(&mut StdRng::seed_from_u64(seed), 2); - - let mut private_keys = std::collections::HashMap::new(); - - let alice_key = keys[0]; - let alice_address = public_key_to_address(alice_key.public_key()); - let alice_secret = B256::from(alice_key.secret_bytes()); - private_keys.insert(User::Alice, alice_secret); - - let bob_key = keys[1]; - let bob_address = public_key_to_address(bob_key.public_key()); - let bob_secret = B256::from(bob_key.secret_bytes()); - private_keys.insert(User::Bob, bob_secret); - +fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis .clone() - .extend_accounts(vec![ - (alice_address, GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64))), - (bob_address, GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64))), - ]) + .extend_accounts( + Account::all() + .into_iter() + .map(|a| { + ( + a.address(), + GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), + ) + }) + .collect::>(), + ) .with_gas_limit(100_000_000); - let spec = - Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()); - - (spec, private_keys) + Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) } fn setup_harness() -> eyre::Result { - let (chain_spec, user_to_private_key) = create_chain_spec(1337); + let chain_spec = create_chain_spec(); let factory = create_provider_factory::(chain_spec.clone()); reth_db_common::init::init_genesis(&factory).context("initializing genesis state")?; @@ -91,7 +72,6 @@ fn setup_harness() -> eyre::Result { genesis_header_number: header.number(), genesis_header_timestamp: header.timestamp(), chain_spec, - user_to_private_key, }) } @@ -144,7 +124,7 @@ fn meter_block_single_transaction() -> eyre::Result<()> { let to = Address::random(); let signed_tx = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to) @@ -191,7 +171,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create first transaction from Alice let signed_tx_1 = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_1) @@ -208,7 +188,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create second transaction from Bob let signed_tx_2 = TransactionBuilder::default() - .signer(harness.signer(User::Bob)) + .signer(harness.signer(Account::Bob)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_2) @@ -268,7 +248,7 @@ fn meter_block_timing_consistency() -> eyre::Result<()> { // Create a block with one transaction let signed_tx = TransactionBuilder::default() - .signer(harness.signer(User::Alice)) + .signer(harness.signer(Account::Alice)) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(Address::random()) diff --git a/crates/client/test-utils/src/accounts.rs b/crates/client/test-utils/src/accounts.rs index 331bbded..08151545 100644 --- a/crates/client/test-utils/src/accounts.rs +++ b/crates/client/test-utils/src/accounts.rs @@ -12,29 +12,66 @@ use reth::{revm::context::TransactionType, rpc::compat::SignTxRequestError}; use crate::BASE_CHAIN_ID; -/// Hardcoded test account with a fixed private key -#[derive(Debug, Clone)] -pub struct Account { - /// Account name for easy identification - pub name: &'static str, - /// Ethereum address - pub address: Address, - /// Private key (hex string without 0x prefix) - pub private_key: &'static str, +/// Hardcoded test accounts using Anvil's deterministic keys. +/// Derived from the test mnemonic: "test test test test test test test test test test test junk" +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Account { + /// Alice (Anvil account #0) + Alice, + /// Bob (Anvil account #1) + Bob, + /// Charlie (Anvil account #2) + Charlie, + /// Deployer (Anvil account #3) + Deployer, } impl Account { + /// Returns the Ethereum address for this account. + pub const fn address(&self) -> Address { + match self { + Self::Alice => address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), + Self::Bob => address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"), + Self::Charlie => address!("3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), + Self::Deployer => address!("90F79bf6EB2c4f870365E785982E1f101E93b906"), + } + } + + /// Returns the private key (hex string without 0x prefix). + pub const fn private_key(&self) -> &'static str { + match self { + Self::Alice => "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + Self::Bob => "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + Self::Charlie => "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + Self::Deployer => "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + } + } + + /// Returns all available test accounts. + pub const fn all() -> [Self; 4] { + [Self::Alice, Self::Bob, Self::Charlie, Self::Deployer] + } + + /// Constructs and returns a PrivateKeySigner for this account. + pub fn signer(&self) -> PrivateKeySigner { + let key_bytes = + hex::decode(self.private_key()).expect("should be able to decode private key"); + let key_fixed: FixedBytes<32> = FixedBytes::from_slice(&key_bytes); + PrivateKeySigner::from_bytes(&key_fixed) + .expect("should be able to build the PrivateKeySigner") + } + /// Constructs a signed CREATE transaction with a given nonce and - /// returns the signed bytes, contract address, and transaction hash + /// returns the signed bytes, contract address, and transaction hash. pub fn create_deployment_tx( &self, bytecode: Bytes, nonce: u64, ) -> Result<(Bytes, Address, TxHash)> { let tx_request = OpTransactionRequest::default() - .from(self.address) + .from(self.address()) .transaction_type(TransactionType::Eip1559.into()) - .with_gas_limit(3_000_000) // Increased for larger contracts like ERC-20 + .with_gas_limit(3_000_000) .with_max_fee_per_gas(1_000_000_000) .with_max_priority_fee_per_gas(0) .with_chain_id(BASE_CHAIN_ID) @@ -48,14 +85,14 @@ impl Account { let signed_tx = tx.into_signed(signature); let signed_tx_bytes = signed_tx.encoded_2718().into(); - let contract_address = self.address.create(signed_tx.nonce()); + let contract_address = self.address().create(signed_tx.nonce()); Ok((signed_tx_bytes, contract_address, signed_tx.hash().clone())) } - /// Sign a TransactionRequest and return the signed bytes + /// Sign a TransactionRequest and return the signed bytes. pub fn sign_txn_request(&self, tx_request: OpTransactionRequest) -> Result<(Bytes, TxHash)> { let tx_request = tx_request - .from(self.address) + .from(self.address()) .transaction_type(TransactionType::Eip1559.into()) .with_gas_limit(500_000) .with_chain_id(BASE_CHAIN_ID) @@ -71,90 +108,4 @@ impl Account { let tx_hash = signed_tx.hash(); Ok((signed_tx_bytes, tx_hash.clone())) } - - /// Constructs and returns a PrivateKeySigner for the TestAccount - pub fn signer(&self) -> PrivateKeySigner { - let key_bytes = - hex::decode(self.private_key).expect("should be able to decode private key"); - let key_fixed: FixedBytes<32> = FixedBytes::from_slice(&key_bytes); - PrivateKeySigner::from_bytes(&key_fixed) - .expect("should be able to build the PrivateKeySigner") - .into() - } -} - -/// Handy alias used throughout tests to refer to the deterministic `Account`. -pub type TestAccount = Account; - -/// Collection of all test accounts -#[derive(Debug, Clone)] -pub struct TestAccounts { - /// Alice (Anvil account #0) with a large starting balance. - pub alice: TestAccount, - /// Bob (Anvil account #1) handy for bilateral tests. - pub bob: TestAccount, - /// Charlie (Anvil account #2) used when three participants are required. - pub charlie: TestAccount, - /// Deterministic account intended for contract deployments. - pub deployer: TestAccount, -} - -impl TestAccounts { - /// Create a new instance with all test accounts - pub fn new() -> Self { - Self { alice: ALICE, bob: BOB, charlie: CHARLIE, deployer: DEPLOYER } - } - - /// Get all accounts as a vector - pub fn all(&self) -> Vec<&TestAccount> { - vec![&self.alice, &self.bob, &self.charlie, &self.deployer] - } - - /// Get account by name - pub fn get(&self, name: &str) -> Option<&TestAccount> { - match name { - "alice" => Some(&self.alice), - "bob" => Some(&self.bob), - "charlie" => Some(&self.charlie), - "deployer" => Some(&self.deployer), - _ => None, - } - } -} - -impl Default for TestAccounts { - fn default() -> Self { - Self::new() - } } - -// Hardcoded test accounts using Anvil's deterministic keys -// These are derived from the test mnemonic: "test test test test test test test test test test test junk" - -/// Alice - First test account (Anvil account #0) -pub const ALICE: TestAccount = TestAccount { - name: "Alice", - address: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - private_key: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", -}; - -/// Bob - Second test account (Anvil account #1) -pub const BOB: TestAccount = TestAccount { - name: "Bob", - address: address!("70997970C51812dc3A010C7d01b50e0d17dc79C8"), - private_key: "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", -}; - -/// Charlie - Third test account (Anvil account #2) -pub const CHARLIE: TestAccount = TestAccount { - name: "Charlie", - address: address!("3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), - private_key: "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", -}; - -/// Deployer - Account for deploying smart contracts (Anvil account #3) -pub const DEPLOYER: TestAccount = TestAccount { - name: "Deployer", - address: address!("90F79bf6EB2c4f870365E785982E1f101E93b906"), - private_key: "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", -}; diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index 7a1a7e3c..e30c2560 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -23,7 +23,7 @@ use tokio::time::sleep; use crate::{ BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, - NODE_STARTUP_DELAY_MS, TestAccounts, + NODE_STARTUP_DELAY_MS, engine::{EngineApi, IpcEngine}, node::{LocalNode, LocalNodeProvider, OpAddOns, OpBuilder, default_launcher}, tracing::init_silenced_tracing, @@ -34,7 +34,6 @@ use crate::{ pub struct TestHarness { node: LocalNode, engine: EngineApi, - accounts: TestAccounts, } impl TestHarness { @@ -57,11 +56,10 @@ impl TestHarness { /// Build a harness from an already-running [`LocalNode`]. pub(crate) async fn from_node(node: LocalNode) -> Result { let engine = node.engine_api()?; - let accounts = TestAccounts::new(); sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; - Ok(Self { node, engine, accounts }) + Ok(Self { node, engine }) } /// Return an Optimism JSON-RPC provider connected to the harness node. @@ -69,11 +67,6 @@ impl TestHarness { self.node.provider().expect("provider should always be available after node initialization") } - /// Access the deterministic test accounts backing the harness. - pub fn accounts(&self) -> &TestAccounts { - &self.accounts - } - /// Access the low-level blockchain provider for direct database queries. pub fn blockchain_provider(&self) -> LocalNodeProvider { self.node.blockchain_provider() @@ -195,18 +188,17 @@ mod tests { use alloy_provider::Provider; use super::*; + use crate::Account; + #[tokio::test] async fn test_harness_setup() -> Result<()> { let harness = TestHarness::new().await?; - assert_eq!(harness.accounts().alice.name, "Alice"); - assert_eq!(harness.accounts().bob.name, "Bob"); - let provider = harness.provider(); let chain_id = provider.get_chain_id().await?; assert_eq!(chain_id, crate::BASE_CHAIN_ID); - let alice_balance = provider.get_balance(harness.accounts().alice.address).await?; + let alice_balance = provider.get_balance(Account::Alice.address()).await?; assert!(alice_balance > U256::ZERO); let block_number = provider.get_block_number().await?; diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index df8fc534..cb81a004 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod accounts; -pub use accounts::{ALICE, Account, BOB, CHARLIE, DEPLOYER, TestAccount, TestAccounts}; +pub use accounts::Account; mod constants; pub use constants::{ From 3f5fef50ee4a2e146efa66a6009372f2f1628d60 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 15:46:11 -0600 Subject: [PATCH 245/262] chore: removed dead code --- crates/client/test-utils/src/constants.rs | 39 +++----------- .../test-utils/src/flashblocks_harness.rs | 51 ++----------------- crates/client/test-utils/src/node.rs | 27 +--------- 3 files changed, 15 insertions(+), 102 deletions(-) diff --git a/crates/client/test-utils/src/constants.rs b/crates/client/test-utils/src/constants.rs index c2855e65..673e878b 100644 --- a/crates/client/test-utils/src/constants.rs +++ b/crates/client/test-utils/src/constants.rs @@ -1,63 +1,40 @@ //! Shared constants used across integration tests. -//! -//! This module centralizes configuration values and magic constants to avoid -//! duplication and make them easy to discover. use alloy_primitives::{B256, Bytes, b256, bytes}; -// Re-export NamedChain for convenient access to chain IDs. pub use reth::chainspec::NamedChain; -// ============================================================================= // Chain Configuration -// ============================================================================= -/// Chain ID for the Base Sepolia environment spun up by the harness. -/// -/// This is equivalent to `NamedChain::BaseSepolia as u64`. +/// Chain ID used for test networks (Base Sepolia). pub const BASE_CHAIN_ID: u64 = NamedChain::BaseSepolia as u64; -// ============================================================================= // Block Building -// ============================================================================= -/// Time between blocks in seconds. +/// Block time in seconds for test node configuration. pub const BLOCK_TIME_SECONDS: u64 = 2; - -/// Gas limit for blocks built by the harness. +/// Gas limit for test blocks. pub const GAS_LIMIT: u64 = 200_000_000; -// ============================================================================= // Timing / Delays -// ============================================================================= -/// Delay after node startup before the harness is ready. +/// Delay in milliseconds to wait for node startup. pub const NODE_STARTUP_DELAY_MS: u64 = 500; - -/// Delay between requesting and fetching a payload during block building. +/// Delay in milliseconds to wait for block building. pub const BLOCK_BUILD_DELAY_MS: u64 = 100; -// ============================================================================= // Engine API -// ============================================================================= -/// Default JWT secret for Engine API authentication in tests. -/// -/// This is an all-zeros secret used only for local testing. +/// All-zeros secret for local testing only. pub const DEFAULT_JWT_SECRET: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; -// ============================================================================= // L1 Block Info (OP Stack) -// ============================================================================= -/// Pre-captured L1 block info deposit transaction required by OP Stack. -/// -/// Every OP Stack block must start with an L1 block info deposit. This is a -/// sample transaction suitable for Base Sepolia tests. +/// Sample L1 block info deposit transaction for Base Sepolia tests. pub const L1_BLOCK_INFO_DEPOSIT_TX: Bytes = bytes!( "0x7ef90104a06c0c775b6b492bab9d7e81abdf27f77cafb698551226455a82f559e0f93fea3794deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b0098999be000008dd00101c1200000000000000020000000068869d6300000000015f277f000000000000000000000000000000000000000000000000000000000d42ac290000000000000000000000000000000000000000000000000000000000000001abf52777e63959936b1bf633a2a643f0da38d63deffe49452fed1bf8a44975d50000000000000000000000005050f69a9786f081509234f1a7f4684b5e5b76c9000000000000000000000000" ); -/// Hash of the L1 block info deposit transaction. +/// Hash of the sample L1 block info deposit transaction. pub const L1_BLOCK_INFO_DEPOSIT_TX_HASH: B256 = b256!("0xba56c8b0deb460ff070f8fca8e2ee01e51a3db27841cc862fdd94cc1a47662b6"); diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs index a1acaa2c..24467635 100644 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ b/crates/client/test-utils/src/flashblocks_harness.rs @@ -5,18 +5,11 @@ use std::sync::Arc; use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; -use futures_util::Future; -use reth::builder::NodeHandle; -use reth_e2e_test_utils::Adapter; -use reth_optimism_node::OpNode; use crate::{ harness::TestHarness, init_silenced_tracing, - node::{ - FlashblocksLocalNode, FlashblocksParts, LocalFlashblocksState, OpAddOns, OpBuilder, - default_launcher, - }, + node::{FlashblocksLocalNode, FlashblocksParts, LocalFlashblocksState}, }; /// Helper that exposes [`TestHarness`] conveniences plus Flashblocks helpers. @@ -30,33 +23,15 @@ pub struct FlashblocksHarness { impl FlashblocksHarness { /// Launch a flashblocks-enabled harness with the default launcher. pub async fn new() -> Result { - Self::with_launcher(default_launcher).await - } - - /// Launch the harness configured for manual canonical progression. - pub async fn manual_canonical() -> Result { - Self::manual_canonical_with_launcher(default_launcher).await - } - - /// Launch the harness using a custom node launcher. - pub async fn with_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { init_silenced_tracing(); - let flash_node = FlashblocksLocalNode::with_launcher(launcher).await?; + let flash_node = FlashblocksLocalNode::new().await?; Self::from_flashblocks_node(flash_node).await } - /// Launch the harness with a custom launcher while disabling automatic canonical processing. - pub async fn manual_canonical_with_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { + /// Launch the harness configured for manual canonical progression. + pub async fn manual_canonical() -> Result { init_silenced_tracing(); - let flash_node = FlashblocksLocalNode::with_manual_canonical_launcher(launcher).await?; + let flash_node = FlashblocksLocalNode::manual_canonical().await?; Self::from_flashblocks_node(flash_node).await } @@ -70,22 +45,6 @@ impl FlashblocksHarness { self.parts.send(flashblock).await } - /// Send a batch of flashblocks sequentially, awaiting each confirmation. - pub async fn send_flashblocks(&self, flashblocks: I) -> Result<()> - where - I: IntoIterator, - { - for flashblock in flashblocks { - self.send_flashblock(flashblock).await?; - } - Ok(()) - } - - /// Consume the flashblocks harness and return the underlying [`TestHarness`]. - pub fn into_inner(self) -> TestHarness { - self.inner - } - async fn from_flashblocks_node(flash_node: FlashblocksLocalNode) -> Result { let (node, parts) = flash_node.into_parts(); let inner = TestHarness::from_node(node).await?; diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index d62c1fa8..9a17df5a 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -405,31 +405,13 @@ impl fmt::Debug for FlashblocksLocalNode { impl FlashblocksLocalNode { /// Launch a flashblocks-enabled node using the default launcher. pub async fn new() -> Result { - Self::with_launcher(default_launcher).await + Self::with_launcher_inner(default_launcher, true).await } /// Builds a flashblocks-enabled node with canonical block streaming disabled so tests can call /// `FlashblocksState::on_canonical_block_received` at precise points. pub async fn manual_canonical() -> Result { - Self::with_manual_canonical_launcher(default_launcher).await - } - - /// Launch a flashblocks-enabled node with a custom launcher and canonical processing enabled. - pub async fn with_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - Self::with_launcher_inner(launcher, true).await - } - - /// Same as [`Self::with_launcher`] but leaves canonical processing to the caller. - pub async fn with_manual_canonical_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - Self::with_launcher_inner(launcher, false).await + Self::with_launcher_inner(default_launcher, false).await } async fn with_launcher_inner(launcher: L, process_canonical: bool) -> Result @@ -459,9 +441,4 @@ impl FlashblocksLocalNode { pub fn into_parts(self) -> (LocalNode, FlashblocksParts) { (self.node, self.parts) } - - /// Borrow the underlying [`LocalNode`]. - pub fn as_node(&self) -> &LocalNode { - &self.node - } } From 01f9d253bbc2f8f416a90aeabca257256d304b0b Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 17:11:06 -0600 Subject: [PATCH 246/262] chore: migrated test harness to support extensions --- Cargo.lock | 5 +- crates/client/flashblocks/Cargo.toml | 1 - crates/client/metering/Cargo.toml | 4 + crates/client/test-utils/Cargo.toml | 7 +- .../test-utils/src/flashblocks_harness.rs | 38 ++-- crates/client/test-utils/src/harness.rs | 69 ++++--- crates/client/test-utils/src/lib.rs | 8 +- crates/client/test-utils/src/node.rs | 190 +++++++++--------- 8 files changed, 178 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e355c27..a91e9337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,7 +1605,6 @@ dependencies = [ "reth", "reth-db", "reth-db-common", - "reth-e2e-test-utils", "reth-evm", "reth-exex", "reth-optimism-chainspec", @@ -1669,6 +1668,7 @@ dependencies = [ "reth", "reth-db", "reth-db-common", + "reth-e2e-test-utils", "reth-evm", "reth-optimism-chainspec", "reth-optimism-evm", @@ -1724,12 +1724,12 @@ dependencies = [ "alloy-signer-local", "alloy-sol-macro", "alloy-sol-types", + "base-client-primitives", "base-flashblocks", "base-flashtypes", "chrono", "derive_more", "eyre", - "futures-util", "jsonrpsee", "once_cell", "op-alloy-network", @@ -1737,7 +1737,6 @@ dependencies = [ "op-alloy-rpc-types-engine", "reth", "reth-db", - "reth-e2e-test-utils", "reth-exex", "reth-ipc", "reth-node-core", diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index 6b1ace83..f1b2376c 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -88,7 +88,6 @@ reth-optimism-node.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } tokio-tungstenite.workspace = true tracing-subscriber.workspace = true -reth-e2e-test-utils.workspace = true base-test-utils.workspace = true serde_json.workspace = true futures-util.workspace = true diff --git a/crates/client/metering/Cargo.toml b/crates/client/metering/Cargo.toml index 8bbbed3a..fd13c915 100644 --- a/crates/client/metering/Cargo.toml +++ b/crates/client/metering/Cargo.toml @@ -11,6 +11,9 @@ description = "Metering RPC for Base node" [lints] workspace = true +[package.metadata.cargo-udeps.ignore] +development = ["reth-e2e-test-utils"] + [dependencies] # workspace base-bundles.workspace = true @@ -40,6 +43,7 @@ serde.workspace = true [dev-dependencies] base-test-utils.workspace = true +reth-e2e-test-utils.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-optimism-node.workspace = true diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml index 64f3b529..39ae07f2 100644 --- a/crates/client/test-utils/Cargo.toml +++ b/crates/client/test-utils/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # Project +base-client-primitives.workspace = true base-flashtypes.workspace = true base-flashblocks.workspace = true @@ -24,8 +25,7 @@ reth-optimism-primitives.workspace = true reth-optimism-rpc = { workspace = true, features = ["client"] } reth-provider.workspace = true reth-primitives-traits.workspace = true -reth-db.workspace = true -reth-e2e-test-utils.workspace = true +reth-db = { workspace = true, features = ["test-utils"] } reth-node-core.workspace = true reth-exex.workspace = true reth-tracing.workspace = true @@ -56,9 +56,6 @@ op-alloy-network.workspace = true tokio.workspace = true tokio-stream.workspace = true -# async -futures-util.workspace = true - # rpc jsonrpsee.workspace = true diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs index 24467635..f856ae5f 100644 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ b/crates/client/test-utils/src/flashblocks_harness.rs @@ -1,15 +1,17 @@ //! Flashblocks-aware wrapper around [`TestHarness`] that wires in the custom RPC modules. -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; +use tokio::time::sleep; use crate::{ + NODE_STARTUP_DELAY_MS, harness::TestHarness, init_silenced_tracing, - node::{FlashblocksLocalNode, FlashblocksParts, LocalFlashblocksState}, + node::{FlashblocksParts, FlashblocksTestExtension, LocalFlashblocksState, LocalNode}, }; /// Helper that exposes [`TestHarness`] conveniences plus Flashblocks helpers. @@ -21,18 +23,14 @@ pub struct FlashblocksHarness { } impl FlashblocksHarness { - /// Launch a flashblocks-enabled harness with the default launcher. + /// Launch a flashblocks-enabled harness with automatic canonical processing. pub async fn new() -> Result { - init_silenced_tracing(); - let flash_node = FlashblocksLocalNode::new().await?; - Self::from_flashblocks_node(flash_node).await + Self::with_options(true).await } /// Launch the harness configured for manual canonical progression. pub async fn manual_canonical() -> Result { - init_silenced_tracing(); - let flash_node = FlashblocksLocalNode::manual_canonical().await?; - Self::from_flashblocks_node(flash_node).await + Self::with_options(false).await } /// Get a handle to the in-memory Flashblocks state backing the harness. @@ -45,9 +43,25 @@ impl FlashblocksHarness { self.parts.send(flashblock).await } - async fn from_flashblocks_node(flash_node: FlashblocksLocalNode) -> Result { - let (node, parts) = flash_node.into_parts(); - let inner = TestHarness::from_node(node).await?; + async fn with_options(process_canonical: bool) -> Result { + init_silenced_tracing(); + + // Create the extension and keep a reference to get parts after launch + let extension = FlashblocksTestExtension::new(process_canonical); + let parts_source = extension.clone(); + + // Launch the node with the flashblocks extension + let node = LocalNode::new(vec![Box::new(extension)]).await?; + let engine = node.engine_api()?; + + sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; + + // Get the parts from the extension after node launch + let parts = parts_source.parts()?; + + // Create harness by building it directly (avoiding TestHarnessBuilder since we already have node) + let inner = TestHarness::from_parts(node, engine); + Ok(Self { inner, parts }) } } diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index e30c2560..36187975 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -7,16 +7,11 @@ use alloy_primitives::{B64, B256, Bytes}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_types::BlockNumberOrTag; use alloy_rpc_types_engine::PayloadAttributes; +use base_client_primitives::BaseNodeExtension; use eyre::{Result, eyre}; -use futures_util::Future; use op_alloy_network::Optimism; use op_alloy_rpc_types_engine::OpPayloadAttributes; -use reth::{ - builder::NodeHandle, - providers::{BlockNumReader, BlockReader, ChainSpecProvider}, -}; -use reth_e2e_test_utils::Adapter; -use reth_optimism_node::OpNode; +use reth::providers::{BlockNumReader, BlockReader, ChainSpecProvider}; use reth_optimism_primitives::OpBlock; use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::time::sleep; @@ -25,10 +20,40 @@ use crate::{ BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, NODE_STARTUP_DELAY_MS, engine::{EngineApi, IpcEngine}, - node::{LocalNode, LocalNodeProvider, OpAddOns, OpBuilder, default_launcher}, + node::{LocalNode, LocalNodeProvider}, tracing::init_silenced_tracing, }; +/// Builder for configuring and launching a test harness. +#[derive(Debug, Default)] +pub struct TestHarnessBuilder { + extensions: Vec>, +} + +impl TestHarnessBuilder { + /// Create a new builder with no extensions. + pub fn new() -> Self { + Self::default() + } + + /// Add an extension to be applied during node launch. + pub fn with_extension(mut self, ext: impl BaseNodeExtension + 'static) -> Self { + self.extensions.push(Box::new(ext)); + self + } + + /// Build and launch the test harness. + pub async fn build(self) -> Result { + init_silenced_tracing(); + let node = LocalNode::new(self.extensions).await?; + let engine = node.engine_api()?; + + sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; + + Ok(TestHarness { node, engine }) + } +} + /// High-level façade that bundles a local node, engine API client, and common helpers. #[derive(Debug)] pub struct TestHarness { @@ -37,29 +62,21 @@ pub struct TestHarness { } impl TestHarness { - /// Launch a new harness using the default launcher configuration. + /// Launch a new harness using the default configuration (no extensions). pub async fn new() -> Result { - Self::with_launcher(default_launcher).await + TestHarnessBuilder::new().build().await } - /// Launch the harness with a custom node launcher (e.g. to tweak components). - pub async fn with_launcher(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - init_silenced_tracing(); - let node = LocalNode::new(launcher).await?; - Self::from_node(node).await + /// Create a builder for configuring the test harness with extensions. + pub fn builder() -> TestHarnessBuilder { + TestHarnessBuilder::new() } - /// Build a harness from an already-running [`LocalNode`]. - pub(crate) async fn from_node(node: LocalNode) -> Result { - let engine = node.engine_api()?; - - sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; - - Ok(Self { node, engine }) + /// Create a harness from pre-built parts. + /// + /// This is useful when you need to capture extension state before building the harness. + pub(crate) fn from_parts(node: LocalNode, engine: EngineApi) -> Self { + Self { node, engine } } /// Return an Optimism JSON-RPC provider connected to the harness node. diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index cb81a004..dd8a5e09 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -25,12 +25,14 @@ mod flashblocks_harness; pub use flashblocks_harness::FlashblocksHarness; mod harness; -pub use harness::TestHarness; +pub use harness::{TestHarness, TestHarnessBuilder}; mod node; +// Re-export BaseNodeExtension for extension authors +pub use base_client_primitives::BaseNodeExtension; pub use node::{ - FlashblocksLocalNode, FlashblocksParts, LocalFlashblocksState, LocalNode, LocalNodeProvider, - OpAddOns, OpBuilder, OpComponentsBuilder, OpTypes, default_launcher, + FlashblocksLocalNode, FlashblocksParts, FlashblocksTestExtension, LocalFlashblocksState, + LocalNode, LocalNodeProvider, }; mod tracing; diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index 9a17df5a..b6a6c38e 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -4,36 +4,32 @@ use std::{ any::Any, fmt, net::SocketAddr, + path::PathBuf, sync::{Arc, Mutex}, }; use alloy_genesis::Genesis; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; +use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; use base_flashblocks::{ EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksReceiver, FlashblocksState, }; use base_flashtypes::Flashblock; use eyre::Result; -use futures_util::Future; use once_cell::sync::OnceCell; use op_alloy_network::Optimism; use reth::{ - api::{FullNodeTypesAdapter, NodeTypesWithDBAdapter}, args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, - builder::{ - Node, NodeBuilder, NodeBuilderWithComponents, NodeConfig, NodeHandle, WithLaunchContext, - }, + builder::{EngineNodeLauncher, Node, NodeBuilder, NodeConfig, NodeHandle, TreeConfig}, core::exit::NodeExitFuture, + providers::providers::BlockchainProvider, tasks::TaskManager, }; use reth_db::{ - ClientVersion, DatabaseEnv, init_db, - mdbx::DatabaseArguments, - test_utils::{ERROR_DB_CREATION, TempDatabase, tempdir_path}, + ClientVersion, DatabaseEnv, init_db, mdbx::DatabaseArguments, test_utils::tempdir_path, }; -use reth_e2e_test_utils::{Adapter, TmpDB}; use reth_exex::ExExEvent; use reth_node_core::{ args::DatadirArgs, @@ -41,14 +37,14 @@ use reth_node_core::{ }; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::{OpNode, args::RollupArgs}; -use reth_provider::{CanonStateSubscriptions, providers::BlockchainProvider}; +use reth_provider::CanonStateSubscriptions; use tokio::sync::{mpsc, oneshot}; use tokio_stream::StreamExt; use crate::engine::EngineApi; /// Convenience alias for the local blockchain provider type. -pub type LocalNodeProvider = BlockchainProvider>; +pub type LocalNodeProvider = OpProvider; /// Convenience alias for the Flashblocks state backing the local node. pub type LocalFlashblocksState = FlashblocksState; @@ -61,6 +57,14 @@ pub struct LocalNode { _node_exit_future: NodeExitFuture, _node: Box, _task_manager: TaskManager, + _db_path: PathBuf, +} + +impl Drop for LocalNode { + fn drop(&mut self) { + // Clean up the temporary database directory + let _ = std::fs::remove_dir_all(&self._db_path); + } } impl fmt::Debug for LocalNode { @@ -101,12 +105,16 @@ impl FlashblocksParts { } } -#[derive(Clone)] -struct FlashblocksNodeExtensions { - inner: Arc, +/// Test extension for flashblocks functionality. +/// +/// This extension wires up the flashblocks ExEx and RPC modules for testing, +/// with optional control over canonical block processing. +#[derive(Clone, Debug)] +pub struct FlashblocksTestExtension { + inner: Arc, } -struct FlashblocksNodeExtensionsInner { +struct FlashblocksTestExtensionInner { sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, #[allow(clippy::type_complexity)] receiver: Arc)>>>>, @@ -114,10 +122,22 @@ struct FlashblocksNodeExtensionsInner { process_canonical: bool, } -impl FlashblocksNodeExtensions { - fn new(process_canonical: bool) -> Self { +impl fmt::Debug for FlashblocksTestExtensionInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlashblocksTestExtensionInner") + .field("process_canonical", &self.process_canonical) + .finish_non_exhaustive() + } +} + +impl FlashblocksTestExtension { + /// Create a new flashblocks test extension. + /// + /// If `process_canonical` is true, canonical blocks are automatically processed. + /// Set to false for tests that need manual control over canonical block timing. + pub fn new(process_canonical: bool) -> Self { let (sender, receiver) = mpsc::channel::<(Flashblock, oneshot::Sender<()>)>(100); - let inner = FlashblocksNodeExtensionsInner { + let inner = FlashblocksTestExtensionInner { sender, receiver: Arc::new(Mutex::new(Some(receiver))), fb_cell: Arc::new(OnceCell::new()), @@ -126,7 +146,17 @@ impl FlashblocksNodeExtensions { Self { inner: Arc::new(inner) } } - fn apply(&self, builder: OpBuilder) -> OpBuilder { + /// Get the flashblocks parts after the node has been launched. + pub fn parts(&self) -> Result { + let state = self.inner.fb_cell.get().ok_or_else(|| { + eyre::eyre!("FlashblocksState should be initialized during node launch") + })?; + Ok(FlashblocksParts { sender: self.inner.sender.clone(), state: state.clone() }) + } +} + +impl BaseNodeExtension for FlashblocksTestExtension { + fn apply(self: Box, builder: OpBuilder) -> OpBuilder { let fb_cell = self.inner.fb_cell.clone(); let receiver = self.inner.receiver.clone(); let process_canonical = self.inner.process_canonical; @@ -136,7 +166,6 @@ impl FlashblocksNodeExtensions { builder .install_exex("flashblocks-canon", move |mut ctx| { let fb_cell = fb_cell_for_exex.clone(); - let process_canonical = process_canonical; async move { let provider = ctx.provider().clone(); let fb = init_flashblocks_state(&fb_cell, &provider); @@ -202,53 +231,12 @@ impl FlashblocksNodeExtensions { Ok(()) }) } - - fn wrap_launcher(&self, launcher: L) -> impl FnOnce(OpBuilder) -> LRet - where - L: FnOnce(OpBuilder) -> LRet, - { - let extensions = self.clone(); - move |builder| { - let builder = extensions.apply(builder); - launcher(builder) - } - } - - fn parts(&self) -> Result { - let state = self.inner.fb_cell.get().ok_or_else(|| { - eyre::eyre!("FlashblocksState should be initialized during node launch") - })?; - Ok(FlashblocksParts { sender: self.inner.sender.clone(), state: state.clone() }) - } -} - -/// Optimism node types used for the local harness. -pub type OpTypes = - FullNodeTypesAdapter>>; -/// Builder that wires up the concrete node components. -pub type OpComponentsBuilder = >::ComponentsBuilder; -/// Additional services attached to the node builder. -pub type OpAddOns = >::AddOns; -/// Launcher builder used by the harness to customize node startup. -pub type OpBuilder = - WithLaunchContext>; - -/// Default launcher that is reused across the harness and integration tests. -pub async fn default_launcher( - builder: OpBuilder, -) -> eyre::Result, OpAddOns>> { - let launcher = builder.engine_api_launcher(); - builder.launch_with(launcher).await } impl LocalNode { - /// Launch a new local node using the provided launcher function. - pub async fn new(launcher: L) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - build_node(launcher).await + /// Launch a new local node with the provided extensions. + pub async fn new(extensions: Vec>) -> Result { + build_node(extensions).await } /// Creates a test database with a smaller map size to reduce memory usage. @@ -258,7 +246,9 @@ impl LocalNode { /// `ENOMEM` errors when running parallel tests with `cargo test`, as the /// default 8 TB size can cause memory exhaustion when multiple test processes /// run concurrently. - fn create_test_database() -> Result>> { + /// + /// Returns the database wrapped in Arc and the path for cleanup. + fn create_test_database() -> Result<(Arc, PathBuf)> { let default_size = 100 * 1024 * 1024; // 100 MB Self::create_test_database_with_size(default_size) } @@ -268,13 +258,13 @@ impl LocalNode { /// # Arguments /// /// * `max_size` - Maximum map size in bytes. - fn create_test_database_with_size(max_size: usize) -> Result>> { + fn create_test_database_with_size(max_size: usize) -> Result<(Arc, PathBuf)> { let path = tempdir_path(); - let emsg = format!("{ERROR_DB_CREATION}: {path:?}"); + let emsg = format!("Failed to create test database at {path:?}"); let args = DatabaseArguments::new(ClientVersion::default()).with_geometry_max_size(Some(max_size)); let db = init_db(&path, args).expect(&emsg); - Ok(Arc::new(TempDatabase::new(db, path))) + Ok((Arc::new(db), path)) } /// Create an HTTP provider pointed at the node's public RPC endpoint. @@ -300,11 +290,7 @@ impl LocalNode { } } -async fn build_node(launcher: L) -> Result -where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, -{ +async fn build_node(extensions: Vec>) -> Result { let tasks = TaskManager::current(); let exec = tasks.executor(); @@ -327,10 +313,9 @@ where RpcServerArgs::default().with_unused_ports().with_http().with_auth_ipc().with_ws(); rpc_args.auth_ipc_path = unique_ipc_path; - let node = OpNode::new(RollupArgs::default()); + let op_node = OpNode::new(RollupArgs::default()); - let temp_db = LocalNode::create_test_database()?; - let db_path = temp_db.path().to_path_buf(); + let (db, db_path) = LocalNode::create_test_database()?; let mut node_config = NodeConfig::new(chain_spec.clone()) .with_network(network_config) @@ -342,14 +327,35 @@ where node_config.with_datadir_args(DatadirArgs { datadir: datadir_path, ..Default::default() }); let builder = NodeBuilder::new(node_config.clone()) - .with_database(temp_db) + .with_database(db) .with_launch_context(exec.clone()) .with_types_and_provider::>() - .with_components(node.components_builder()) - .with_add_ons(node.add_ons()); + .with_components(op_node.components()) + .with_add_ons(op_node.add_ons()) + .on_component_initialized(move |_ctx| Ok(())); + + // Apply all extensions + let builder = + extensions.into_iter().fold(builder, |builder, extension| extension.apply(builder)); + + // Launch with EngineNodeLauncher + let NodeHandle { node: node_handle, node_exit_future } = builder + .launch_with_fn(|builder| { + let engine_tree_config = TreeConfig::default() + .with_persistence_threshold(builder.config().engine.persistence_threshold) + .with_memory_block_buffer_target( + builder.config().engine.memory_block_buffer_target, + ); + + let launcher = EngineNodeLauncher::new( + builder.task_executor().clone(), + builder.config().datadir(), + engine_tree_config, + ); - let NodeHandle { node: node_handle, node_exit_future } = - builder.launch_with_fn(launcher).await?; + builder.launch_with(launcher) + }) + .await?; let http_api_addr = node_handle .rpc_server_handle() @@ -372,6 +378,7 @@ where _node_exit_future: node_exit_future, _node: Box::new(node_handle), _task_manager: tasks, + _db_path: db_path, }) } @@ -403,27 +410,22 @@ impl fmt::Debug for FlashblocksLocalNode { } impl FlashblocksLocalNode { - /// Launch a flashblocks-enabled node using the default launcher. + /// Launch a flashblocks-enabled node using the default configuration. pub async fn new() -> Result { - Self::with_launcher_inner(default_launcher, true).await + Self::with_options(true).await } /// Builds a flashblocks-enabled node with canonical block streaming disabled so tests can call /// `FlashblocksState::on_canonical_block_received` at precise points. pub async fn manual_canonical() -> Result { - Self::with_launcher_inner(default_launcher, false).await + Self::with_options(false).await } - async fn with_launcher_inner(launcher: L, process_canonical: bool) -> Result - where - L: FnOnce(OpBuilder) -> LRet, - LRet: Future, OpAddOns>>>, - { - let extensions = FlashblocksNodeExtensions::new(process_canonical); - let wrapped_launcher = extensions.wrap_launcher(launcher); - let node = LocalNode::new(wrapped_launcher).await?; - - let parts = extensions.parts()?; + async fn with_options(process_canonical: bool) -> Result { + let extension = FlashblocksTestExtension::new(process_canonical); + let parts_source = extension.clone(); + let node = LocalNode::new(vec![Box::new(extension)]).await?; + let parts = parts_source.parts()?; Ok(Self { node, parts }) } From 30fc46f38e0bdf5a49f39113af9015c9c0d2eae6 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 17:23:59 -0600 Subject: [PATCH 247/262] chore: update metering rpc tests to use the node harness --- Cargo.lock | 94 --------------- crates/client/flashblocks/Cargo.toml | 1 - crates/client/metering/Cargo.toml | 5 - crates/client/metering/tests/meter_rpc.rs | 134 +++++----------------- crates/client/test-utils/src/accounts.rs | 9 +- crates/client/test-utils/src/harness.rs | 7 ++ 6 files changed, 42 insertions(+), 208 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a91e9337..5fda0a2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1618,7 +1618,6 @@ dependencies = [ "reth-rpc", "reth-rpc-convert", "reth-rpc-eth-api", - "reth-testing-utils", "reth-tracing", "reth-transaction-pool", "rstest", @@ -1668,7 +1667,6 @@ dependencies = [ "reth", "reth-db", "reth-db-common", - "reth-e2e-test-utils", "reth-evm", "reth-optimism-chainspec", "reth-optimism-evm", @@ -1676,7 +1674,6 @@ dependencies = [ "reth-optimism-primitives", "reth-primitives-traits", "reth-provider", - "reth-testing-utils", "reth-transaction-pool", "serde", "tokio", @@ -7545,16 +7542,12 @@ dependencies = [ "rayon", "reth-config", "reth-consensus", - "reth-ethereum-primitives", "reth-metrics", "reth-network-p2p", "reth-network-peers", "reth-primitives-traits", - "reth-provider", "reth-storage-api", "reth-tasks", - "reth-testing-utils", - "tempfile", "thiserror 2.0.17", "tokio", "tokio-stream", @@ -7562,64 +7555,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "reth-e2e-test-utils" -version = "1.9.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-network", - "alloy-primitives", - "alloy-provider", - "alloy-rlp", - "alloy-rpc-types-engine", - "alloy-rpc-types-eth", - "alloy-signer", - "alloy-signer-local", - "derive_more", - "eyre", - "futures-util", - "jsonrpsee", - "reth-chainspec", - "reth-cli-commands", - "reth-config", - "reth-consensus", - "reth-db", - "reth-db-common", - "reth-engine-local", - "reth-engine-primitives", - "reth-ethereum-primitives", - "reth-network-api", - "reth-network-p2p", - "reth-network-peers", - "reth-node-api", - "reth-node-builder", - "reth-node-core", - "reth-node-ethereum", - "reth-payload-builder", - "reth-payload-builder-primitives", - "reth-payload-primitives", - "reth-primitives", - "reth-primitives-traits", - "reth-provider", - "reth-rpc-api", - "reth-rpc-builder", - "reth-rpc-eth-api", - "reth-rpc-server-types", - "reth-stages-types", - "reth-tasks", - "reth-tokio-util", - "reth-tracing", - "revm", - "serde_json", - "tempfile", - "tokio", - "tokio-stream", - "tracing", - "url", -] - [[package]] name = "reth-ecies" version = "1.9.3" @@ -7742,7 +7677,6 @@ dependencies = [ "parking_lot", "rayon", "reth-chain-state", - "reth-chainspec", "reth-consensus", "reth-db", "reth-engine-primitives", @@ -7757,13 +7691,9 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-prune", - "reth-prune-types", "reth-revm", - "reth-stages", "reth-stages-api", - "reth-static-file", "reth-tasks", - "reth-tracing", "reth-trie", "reth-trie-parallel", "reth-trie-sparse", @@ -8382,7 +8312,6 @@ dependencies = [ "auto_impl", "derive_more", "futures", - "parking_lot", "reth-consensus", "reth-eth-wire-types", "reth-ethereum-primitives", @@ -9241,7 +9170,6 @@ dependencies = [ "reth-db", "reth-db-api", "reth-errors", - "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-execution-types", "reth-fs-util", @@ -9257,9 +9185,7 @@ dependencies = [ "reth-trie", "reth-trie-db", "revm-database", - "revm-state", "strum 0.27.2", - "tokio", "tracing", ] @@ -9703,7 +9629,6 @@ dependencies = [ "num-traits", "rayon", "reqwest", - "reth-chainspec", "reth-codecs", "reth-config", "reth-consensus", @@ -9712,7 +9637,6 @@ dependencies = [ "reth-era", "reth-era-downloader", "reth-era-utils", - "reth-ethereum-primitives", "reth-etl", "reth-evm", "reth-execution-types", @@ -9727,10 +9651,8 @@ dependencies = [ "reth-stages-api", "reth-static-file-types", "reth-storage-errors", - "reth-testing-utils", "reth-trie", "reth-trie-db", - "tempfile", "thiserror 2.0.17", "tokio", "tracing", @@ -9866,22 +9788,6 @@ dependencies = [ "tracing-futures", ] -[[package]] -name = "reth-testing-utils" -version = "1.9.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-genesis", - "alloy-primitives", - "rand 0.8.5", - "rand 0.9.2", - "reth-ethereum-primitives", - "reth-primitives-traits", - "secp256k1 0.30.0", -] - [[package]] name = "reth-tokio-util" version = "1.9.3" diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index f1b2376c..41ebacb5 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -82,7 +82,6 @@ alloy-sol-macro = { workspace = true, features = ["json"] } alloy-sol-types.workspace = true alloy-contract.workspace = true op-alloy-consensus.workspace = true -reth-testing-utils.workspace = true reth-tracing.workspace = true reth-optimism-node.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } diff --git a/crates/client/metering/Cargo.toml b/crates/client/metering/Cargo.toml index fd13c915..0a370995 100644 --- a/crates/client/metering/Cargo.toml +++ b/crates/client/metering/Cargo.toml @@ -11,9 +11,6 @@ description = "Metering RPC for Base node" [lints] workspace = true -[package.metadata.cargo-udeps.ignore] -development = ["reth-e2e-test-utils"] - [dependencies] # workspace base-bundles.workspace = true @@ -43,11 +40,9 @@ serde.workspace = true [dev-dependencies] base-test-utils.workspace = true -reth-e2e-test-utils.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-optimism-node.workspace = true -reth-testing-utils.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } alloy-genesis.workspace = true alloy-rpc-client.workspace = true diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index dd99a9dc..f82d8faf 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -1,34 +1,16 @@ //! Integration tests covering the Metering RPC surface area. -use std::{any::Any, net::SocketAddr, sync::Arc}; - use alloy_eips::Encodable2718; -use alloy_primitives::{Bytes, U256, address, b256, bytes}; +use alloy_primitives::{Bytes, U256, address, bytes}; use alloy_rpc_client::RpcClient; use base_bundles::{Bundle, MeterBundleResponse}; -use base_metering::{MeteringApiImpl, MeteringApiServer}; -use base_test_utils::{init_silenced_tracing, load_genesis}; +use base_metering::MeteringExtension; +use base_test_utils::{Account, TestHarness}; use op_alloy_consensus::OpTxEnvelope; -use reth::{ - args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, - builder::{Node, NodeBuilder, NodeConfig, NodeHandle}, - chainspec::Chain, - core::exit::NodeExitFuture, - tasks::TaskManager, -}; -use reth_optimism_chainspec::OpChainSpecBuilder; -use reth_optimism_node::{OpNode, args::RollupArgs}; use reth_optimism_primitives::OpTransactionSigned; -use reth_provider::providers::BlockchainProvider; use reth_transaction_pool::test_utils::TransactionBuilder; -struct NodeContext { - http_api_addr: SocketAddr, - _node_exit_future: NodeExitFuture, - _node: Box, -} - -// Helper function to create a Bundle with default fields +/// Helper function to create a Bundle with default fields. fn create_bundle(txs: Vec, block_number: u64, min_timestamp: Option) -> Bundle { Bundle { txs, @@ -43,66 +25,18 @@ fn create_bundle(txs: Vec, block_number: u64, min_timestamp: Option) } } -impl NodeContext { - async fn rpc_client(&self) -> eyre::Result { - let url = format!("http://{}", self.http_api_addr); - let client = RpcClient::new_http(url.parse()?); - Ok(client) - } -} - -async fn setup_node() -> eyre::Result { - init_silenced_tracing(); - let tasks = TaskManager::current(); - let exec = tasks.executor(); - const BASE_SEPOLIA_CHAIN_ID: u64 = 84532; - - let genesis = load_genesis(); - let chain_spec = Arc::new( - OpChainSpecBuilder::base_mainnet() - .genesis(genesis) - .ecotone_activated() - .chain(Chain::from(BASE_SEPOLIA_CHAIN_ID)) - .build(), - ); +/// Set up a test harness with the metering extension and return an RPC client. +async fn setup() -> eyre::Result<(TestHarness, RpcClient)> { + let harness = + TestHarness::builder().with_extension(MeteringExtension::new(true)).build().await?; - let network_config = NetworkArgs { - discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, - ..NetworkArgs::default() - }; - - let node_config = NodeConfig::new(chain_spec.clone()) - .with_network(network_config.clone()) - .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()) - .with_unused_ports(); - - let node = OpNode::new(RollupArgs::default()); - - let NodeHandle { node, node_exit_future } = NodeBuilder::new(node_config.clone()) - .testing_node(exec.clone()) - .with_types_and_provider::>() - .with_components(node.components_builder()) - .with_add_ons(node.add_ons()) - .extend_rpc_modules(move |ctx| { - let metering_api = MeteringApiImpl::new(ctx.provider().clone()); - ctx.modules.merge_configured(metering_api.into_rpc())?; - Ok(()) - }) - .launch() - .await?; - - let http_api_addr = node - .rpc_server_handle() - .http_local_addr() - .ok_or_else(|| eyre::eyre!("Failed to get http api address"))?; - - Ok(NodeContext { http_api_addr, _node_exit_future: node_exit_future, _node: Box::new(node) }) + let client = harness.rpc_client()?; + Ok((harness, client)) } #[tokio::test] async fn test_meter_bundle_empty() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; let bundle = create_bundle(vec![], 0, None); @@ -118,14 +52,10 @@ async fn test_meter_bundle_empty() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; - // Use a funded account from genesis.json - // Account: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 - // Private key from common test accounts (Hardhat account #0) - let sender_address = address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"); - let sender_secret = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + let sender_address = Account::Alice.address(); + let sender_secret = Account::Alice.signer_b256(); // Build a transaction let tx = TransactionBuilder::default() @@ -166,13 +96,10 @@ async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; - // Use funded accounts from genesis.json - // Hardhat account #0 and #1 - let address1 = address!("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"); - let secret1 = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + let address1 = Account::Alice.address(); + let secret1 = Account::Alice.signer_b256(); let tx1_inner = TransactionBuilder::default() .signer(secret1) @@ -191,8 +118,8 @@ async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { let tx1_bytes = Bytes::from(tx1_envelope.encoded_2718()); // Second transaction from second account - let address2 = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); - let secret2 = b256!("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); + let address2 = Account::Bob.address(); + let secret2 = Account::Bob.signer_b256(); let tx2_inner = TransactionBuilder::default() .signer(secret2) @@ -235,8 +162,7 @@ async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_invalid_transaction() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; let bundle = create_bundle( vec![bytes!("0xdeadbeef")], // Invalid transaction data @@ -254,8 +180,7 @@ async fn test_meter_bundle_invalid_transaction() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_uses_latest_block() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; // Metering always uses the latest block state, regardless of bundle.block_number let bundle = create_bundle(vec![], 0, None); @@ -270,8 +195,7 @@ async fn test_meter_bundle_uses_latest_block() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_ignores_bundle_block_number() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; // Even if bundle.block_number is different, it should use the latest block // In this test, we specify block_number=0 in the bundle @@ -293,8 +217,7 @@ async fn test_meter_bundle_ignores_bundle_block_number() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_custom_timestamp() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; // Test that bundle.min_timestamp is used for simulation. // The timestamp affects block.timestamp in the EVM during simulation but is not @@ -313,8 +236,7 @@ async fn test_meter_bundle_custom_timestamp() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_arbitrary_block_number() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; // Since we now ignore bundle.block_number and always use the latest block, // any block_number value should work (it's only used for bundle validity in TIPS) @@ -330,12 +252,10 @@ async fn test_meter_bundle_arbitrary_block_number() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_gas_calculations() -> eyre::Result<()> { - let node = setup_node().await?; - let client = node.rpc_client().await?; + let (_harness, client) = setup().await?; - // Use two funded accounts from genesis.json with different gas prices - let secret1 = b256!("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); - let secret2 = b256!("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); + let secret1 = Account::Alice.signer_b256(); + let secret2 = Account::Bob.signer_b256(); // First transaction with 3 gwei gas price let tx1_inner = TransactionBuilder::default() diff --git a/crates/client/test-utils/src/accounts.rs b/crates/client/test-utils/src/accounts.rs index 08151545..651fd77b 100644 --- a/crates/client/test-utils/src/accounts.rs +++ b/crates/client/test-utils/src/accounts.rs @@ -2,7 +2,7 @@ use alloy_consensus::{SignableTransaction, Transaction}; use alloy_eips::eip2718::Encodable2718; -use alloy_primitives::{Address, Bytes, FixedBytes, TxHash, address, hex}; +use alloy_primitives::{Address, B256, Bytes, FixedBytes, TxHash, address, hex}; use alloy_signer::SignerSync; use alloy_signer_local::PrivateKeySigner; use eyre::Result; @@ -61,6 +61,13 @@ impl Account { .expect("should be able to build the PrivateKeySigner") } + /// Returns the private key as a B256 for use with TransactionBuilder. + pub fn signer_b256(&self) -> B256 { + let key_bytes = + hex::decode(self.private_key()).expect("should be able to decode private key"); + B256::from_slice(&key_bytes) + } + /// Constructs a signed CREATE transaction with a given nonce and /// returns the signed bytes, contract address, and transaction hash. pub fn create_deployment_tx( diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index 36187975..b0196e8d 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -5,6 +5,7 @@ use std::time::Duration; use alloy_eips::{BlockHashOrNumber, eip7685::Requests}; use alloy_primitives::{B64, B256, Bytes}; use alloy_provider::{Provider, RootProvider}; +use alloy_rpc_client::RpcClient; use alloy_rpc_types::BlockNumberOrTag; use alloy_rpc_types_engine::PayloadAttributes; use base_client_primitives::BaseNodeExtension; @@ -99,6 +100,12 @@ impl TestHarness { format!("ws://{}", self.node.ws_api_addr) } + /// Return a JSON-RPC client connected to the harness node. + pub fn rpc_client(&self) -> Result { + let url = self.rpc_url().parse()?; + Ok(RpcClient::new_http(url)) + } + /// Build a block using the provided transactions and push it through the engine. pub async fn build_block_from_transactions(&self, mut transactions: Vec) -> Result<()> { // Ensure the block always starts with the required L1 block info deposit. From 985b5821c60a8a0bab4dfc3854692bb242e0fc4e Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 18:47:21 -0600 Subject: [PATCH 248/262] chore: code cleanup - Remove file structure sections from READMEs - Move build_node function body into LocalNode::new - Replace local TestHarness::signer methods with Account::signer_b256() --- README.md | 18 -- bin/node/src/main.rs | 2 - crates/client/metering/tests/meter.rs | 14 +- crates/client/metering/tests/meter_block.rs | 16 +- crates/client/test-utils/README.md | 21 -- crates/client/test-utils/src/node.rs | 209 +++++++++----------- 6 files changed, 102 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index b057bd79..73d5c1fa 100644 --- a/README.md +++ b/README.md @@ -44,24 +44,6 @@ Base Reth Node is a Reth-based Ethereum node implementation, specifically tailor > [node-reth image](https://github.com/base/node/pkgs/container/node-reth). This image bundles vanilla Reth and Base Reth and can be toggled with > `NODE_TYPE=base` or `NODE_TYPE=vanilla` -## Repository Structure - -``` -. -├── Cargo.toml # Rust workspace and package definitions -├── Cargo.lock # Dependency lockfile -├── Dockerfile # For building the Docker image -├── LICENSE # MIT License -├── README.md # This file -├── crates/ -│ ├── client/ # Node client crates (cli, rpc, flashblocks, txpool, etc.) -│ └── shared/ # Shared library crates (access-lists, flashtypes) -├── justfile # Command runner for development tasks -└── .github/ - └── workflows/ - └── ci.yml # GitHub Actions CI configuration -``` - ## Prerequisites - **Rust:** Version 1.85 or later (as specified in `Cargo.toml`). You can install Rust using [rustup](https://rustup.rs/). diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index e6791368..9863940d 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -27,11 +27,9 @@ fn main() { // Step 3: Hand the parsed CLI to the node runner so it can build and launch the Base node. cli.run(|builder, args| async move { - // Create shared flashblocks cell let flashblocks_cell: FlashblocksCell> = Arc::new(OnceCell::new()); - // Extract values needed for extensions before moving rollup_args let sequencer_rpc = args.rollup_args.sequencer.clone(); let tracing_config = args.tracing_config(); let metering_enabled = args.enable_metering; diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index 4226b710..aa5ce150 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use alloy_eips::Encodable2718; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, Bytes, U256, hex::FromHex, keccak256}; +use alloy_primitives::{Address, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; use base_metering::meter_bundle; use base_test_utils::{Account, create_provider_factory}; @@ -28,12 +28,6 @@ struct TestHarness { chain_spec: Arc, } -impl TestHarness { - fn signer(&self, account: Account) -> B256 { - B256::from_hex(account.private_key()).expect("valid private key hex") - } -} - fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis @@ -121,7 +115,7 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { let to = Address::random(); let signed_tx = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to) @@ -179,7 +173,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create first transaction let signed_tx_1 = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_1) @@ -195,7 +189,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create second transaction let signed_tx_2 = TransactionBuilder::default() - .signer(harness.signer(Account::Bob)) + .signer(Account::Bob.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_2) diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 22bc93a4..258e9e8c 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use alloy_consensus::{BlockHeader, Header}; use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, U256, hex::FromHex}; +use alloy_primitives::{Address, B256, U256}; use base_metering::meter_block; use base_test_utils::{Account, create_provider_factory}; use eyre::Context; @@ -28,12 +28,6 @@ struct TestHarness { chain_spec: Arc, } -impl TestHarness { - fn signer(&self, account: Account) -> B256 { - B256::from_hex(account.private_key()).expect("valid private key hex") - } -} - fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis @@ -124,7 +118,7 @@ fn meter_block_single_transaction() -> eyre::Result<()> { let to = Address::random(); let signed_tx = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to) @@ -171,7 +165,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create first transaction from Alice let signed_tx_1 = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_1) @@ -188,7 +182,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create second transaction from Bob let signed_tx_2 = TransactionBuilder::default() - .signer(harness.signer(Account::Bob)) + .signer(Account::Bob.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(to_2) @@ -248,7 +242,7 @@ fn meter_block_timing_consistency() -> eyre::Result<()> { // Create a block with one transaction let signed_tx = TransactionBuilder::default() - .signer(harness.signer(Account::Alice)) + .signer(Account::Alice.signer_b256()) .chain_id(harness.chain_spec.chain_id()) .nonce(0) .to(Address::random()) diff --git a/crates/client/test-utils/README.md b/crates/client/test-utils/README.md index 1a425a2a..5bb44b1c 100644 --- a/crates/client/test-utils/README.md +++ b/crates/client/test-utils/README.md @@ -246,27 +246,6 @@ use base_test_utils::{ }; ``` -## File Structure - -```text -test-utils/ -├── src/ -│ ├── lib.rs # Public API and re-exports -│ ├── accounts.rs # Test account definitions -│ ├── constants.rs # Shared constants (chain ID, timing, etc.) -│ ├── contracts.rs # Solidity contract bindings -│ ├── engine.rs # EngineApi (CL wrapper) -│ ├── fixtures.rs # Genesis loading, provider factories -│ ├── flashblocks_harness.rs # FlashblocksHarness + helpers -│ ├── harness.rs # TestHarness (orchestration) -│ ├── node.rs # LocalNode (EL wrapper) -│ └── tracing.rs # Tracing initialization helpers -├── assets/ -│ └── genesis.json # Base Sepolia genesis -├── contracts/ # Solidity sources + compiled artifacts -└── Cargo.toml -``` - ## Usage in Other Crates Add to `dev-dependencies`: diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index b6a6c38e..5f95da0f 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -236,34 +236,103 @@ impl BaseNodeExtension for FlashblocksTestExtension { impl LocalNode { /// Launch a new local node with the provided extensions. pub async fn new(extensions: Vec>) -> Result { - build_node(extensions).await - } + let tasks = TaskManager::current(); + let exec = tasks.executor(); - /// Creates a test database with a smaller map size to reduce memory usage. - /// - /// Unlike `NodeBuilder::testing_node()` which hardcodes an 8 TB map size, - /// this method configures the database with a 100 MB map size. This prevents - /// `ENOMEM` errors when running parallel tests with `cargo test`, as the - /// default 8 TB size can cause memory exhaustion when multiple test processes - /// run concurrently. - /// - /// Returns the database wrapped in Arc and the path for cleanup. - fn create_test_database() -> Result<(Arc, PathBuf)> { - let default_size = 100 * 1024 * 1024; // 100 MB - Self::create_test_database_with_size(default_size) + let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + + let network_config = NetworkArgs { + discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, + ..NetworkArgs::default() + }; + + let unique_ipc_path = format!( + "/tmp/reth_engine_api_{}_{}_{:?}.ipc", + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(), + std::process::id(), + std::thread::current().id() + ); + + let mut rpc_args = + RpcServerArgs::default().with_unused_ports().with_http().with_auth_ipc().with_ws(); + rpc_args.auth_ipc_path = unique_ipc_path; + + let op_node = OpNode::new(RollupArgs::default()); + + let (db, db_path) = Self::create_test_database()?; + + let mut node_config = NodeConfig::new(chain_spec.clone()) + .with_network(network_config) + .with_rpc(rpc_args) + .with_unused_ports(); + + let datadir_path = MaybePlatformPath::::from(db_path.clone()); + node_config = node_config + .with_datadir_args(DatadirArgs { datadir: datadir_path, ..Default::default() }); + + let builder = NodeBuilder::new(node_config.clone()) + .with_database(db) + .with_launch_context(exec.clone()) + .with_types_and_provider::>() + .with_components(op_node.components()) + .with_add_ons(op_node.add_ons()) + .on_component_initialized(move |_ctx| Ok(())); + + // Apply all extensions + let builder = + extensions.into_iter().fold(builder, |builder, extension| extension.apply(builder)); + + // Launch with EngineNodeLauncher + let NodeHandle { node: node_handle, node_exit_future } = builder + .launch_with_fn(|builder| { + let engine_tree_config = TreeConfig::default() + .with_persistence_threshold(builder.config().engine.persistence_threshold) + .with_memory_block_buffer_target( + builder.config().engine.memory_block_buffer_target, + ); + + let launcher = EngineNodeLauncher::new( + builder.task_executor().clone(), + builder.config().datadir(), + engine_tree_config, + ); + + builder.launch_with(launcher) + }) + .await?; + + let http_api_addr = node_handle + .rpc_server_handle() + .http_local_addr() + .ok_or_else(|| eyre::eyre!("HTTP RPC server failed to bind to address"))?; + + let ws_api_addr = node_handle + .rpc_server_handle() + .ws_local_addr() + .ok_or_else(|| eyre::eyre!("Failed to get websocket api address"))?; + + let engine_ipc_path = node_config.rpc.auth_ipc_path; + let provider = node_handle.provider().clone(); + + Ok(Self { + http_api_addr, + ws_api_addr, + engine_ipc_path, + provider, + _node_exit_future: node_exit_future, + _node: Box::new(node_handle), + _task_manager: tasks, + _db_path: db_path, + }) } - /// Creates a test database with a configurable map size to reduce memory usage. - /// - /// # Arguments - /// - /// * `max_size` - Maximum map size in bytes. - fn create_test_database_with_size(max_size: usize) -> Result<(Arc, PathBuf)> { + /// Creates a test database with a 100 MB map size (vs reth's default 8 TB). + fn create_test_database() -> Result<(Arc, PathBuf)> { let path = tempdir_path(); - let emsg = format!("Failed to create test database at {path:?}"); - let args = - DatabaseArguments::new(ClientVersion::default()).with_geometry_max_size(Some(max_size)); - let db = init_db(&path, args).expect(&emsg); + let args = DatabaseArguments::new(ClientVersion::default()) + .with_geometry_max_size(Some(100 * 1024 * 1024)); + let db = init_db(&path, args).expect("Failed to create test database"); Ok((Arc::new(db), path)) } @@ -290,98 +359,6 @@ impl LocalNode { } } -async fn build_node(extensions: Vec>) -> Result { - let tasks = TaskManager::current(); - let exec = tasks.executor(); - - let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; - let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); - - let network_config = NetworkArgs { - discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, - ..NetworkArgs::default() - }; - - let unique_ipc_path = format!( - "/tmp/reth_engine_api_{}_{}_{:?}.ipc", - std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos(), - std::process::id(), - std::thread::current().id() - ); - - let mut rpc_args = - RpcServerArgs::default().with_unused_ports().with_http().with_auth_ipc().with_ws(); - rpc_args.auth_ipc_path = unique_ipc_path; - - let op_node = OpNode::new(RollupArgs::default()); - - let (db, db_path) = LocalNode::create_test_database()?; - - let mut node_config = NodeConfig::new(chain_spec.clone()) - .with_network(network_config) - .with_rpc(rpc_args) - .with_unused_ports(); - - let datadir_path = MaybePlatformPath::::from(db_path.clone()); - node_config = - node_config.with_datadir_args(DatadirArgs { datadir: datadir_path, ..Default::default() }); - - let builder = NodeBuilder::new(node_config.clone()) - .with_database(db) - .with_launch_context(exec.clone()) - .with_types_and_provider::>() - .with_components(op_node.components()) - .with_add_ons(op_node.add_ons()) - .on_component_initialized(move |_ctx| Ok(())); - - // Apply all extensions - let builder = - extensions.into_iter().fold(builder, |builder, extension| extension.apply(builder)); - - // Launch with EngineNodeLauncher - let NodeHandle { node: node_handle, node_exit_future } = builder - .launch_with_fn(|builder| { - let engine_tree_config = TreeConfig::default() - .with_persistence_threshold(builder.config().engine.persistence_threshold) - .with_memory_block_buffer_target( - builder.config().engine.memory_block_buffer_target, - ); - - let launcher = EngineNodeLauncher::new( - builder.task_executor().clone(), - builder.config().datadir(), - engine_tree_config, - ); - - builder.launch_with(launcher) - }) - .await?; - - let http_api_addr = node_handle - .rpc_server_handle() - .http_local_addr() - .ok_or_else(|| eyre::eyre!("HTTP RPC server failed to bind to address"))?; - - let ws_api_addr = node_handle - .rpc_server_handle() - .ws_local_addr() - .ok_or_else(|| eyre::eyre!("Failed to get websocket api address"))?; - - let engine_ipc_path = node_config.rpc.auth_ipc_path; - let provider = node_handle.provider().clone(); - - Ok(LocalNode { - http_api_addr, - ws_api_addr, - engine_ipc_path, - provider, - _node_exit_future: node_exit_future, - _node: Box::new(node_handle), - _task_manager: tasks, - _db_path: db_path, - }) -} - fn init_flashblocks_state( cell: &Arc>>, provider: &LocalNodeProvider, From 1882a183b841e4942ab3991429b21b800c6ccae9 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 19:13:07 -0600 Subject: [PATCH 249/262] chore: migrate metering non-rpc tests --- crates/client/metering/tests/meter.rs | 98 ++++++------- crates/client/metering/tests/meter_block.rs | 136 ++++++++---------- .../test-utils/src/flashblocks_harness.rs | 8 +- crates/client/test-utils/src/harness.rs | 30 +++- crates/client/test-utils/src/node.rs | 16 ++- 5 files changed, 148 insertions(+), 140 deletions(-) diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index aa5ce150..56b10f6f 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -6,28 +6,16 @@ use alloy_eips::Encodable2718; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; -use base_metering::meter_bundle; -use base_test_utils::{Account, create_provider_factory}; +use base_metering::{MeteringExtension, meter_bundle}; +use base_test_utils::{Account, TestHarness}; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; -use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; -use reth_db::{DatabaseEnv, test_utils::TempDatabase}; +use reth::chainspec::EthChainSpec; use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; -use reth_optimism_node::OpNode; use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives_traits::SealedHeader; -use reth_provider::{HeaderProvider, StateProviderFactory, providers::BlockchainProvider}; +use reth_provider::{HeaderProvider, StateProviderFactory}; use reth_transaction_pool::test_utils::TransactionBuilder; -type NodeTypes = NodeTypesWithDBAdapter>>; - -#[derive(Debug, Clone)] -struct TestHarness { - provider: BlockchainProvider, - header: SealedHeader, - chain_spec: Arc, -} - fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis @@ -48,19 +36,13 @@ fn create_chain_spec() -> Arc { Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) } -fn setup_harness() -> eyre::Result { +async fn setup() -> eyre::Result { let chain_spec = create_chain_spec(); - let factory = create_provider_factory::(chain_spec.clone()); - - reth_db_common::init::init_genesis(&factory).context("initializing genesis state")?; - - let provider = BlockchainProvider::new(factory.clone()).context("creating provider")?; - let header = provider - .sealed_header(0) - .context("fetching genesis header")? - .expect("genesis header exists"); - - Ok(TestHarness { provider, header, chain_spec }) + TestHarness::builder() + .with_chain_spec(chain_spec) + .with_extension(MeteringExtension::new(true)) + .build() + .await } fn envelope_from_signed(tx: &OpTransactionSigned) -> eyre::Result { @@ -85,19 +67,21 @@ fn create_parsed_bundle(envelopes: Vec) -> eyre::Result eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_bundle_empty_transactions() -> eyre::Result<()> { + let harness = setup().await?; + + let provider = harness.blockchain_provider(); + let header = provider.sealed_header(0)?.expect("genesis header exists"); + let chain_spec = harness.chain_spec(); - let state_provider = harness - .provider - .state_by_block_hash(harness.header.hash()) - .context("getting state provider")?; + let state_provider = + provider.state_by_block_hash(header.hash()).context("getting state provider")?; let parsed_bundle = create_parsed_bundle(Vec::new())?; let (results, total_gas_used, total_gas_fees, bundle_hash, total_execution_time) = - meter_bundle(state_provider, harness.chain_spec.clone(), parsed_bundle, &harness.header)?; + meter_bundle(state_provider, chain_spec, parsed_bundle, &header)?; assert!(results.is_empty()); assert_eq!(total_gas_used, 0); @@ -109,14 +93,18 @@ fn meter_bundle_empty_transactions() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_bundle_single_transaction() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_bundle_single_transaction() -> eyre::Result<()> { + let harness = setup().await?; + + let provider = harness.blockchain_provider(); + let header = provider.sealed_header(0)?.expect("genesis header exists"); + let chain_spec = harness.chain_spec(); let to = Address::random(); let signed_tx = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to) .value(1_000) @@ -131,15 +119,13 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { let envelope = envelope_from_signed(&tx)?; let tx_hash = envelope.tx_hash(); - let state_provider = harness - .provider - .state_by_block_hash(harness.header.hash()) - .context("getting state provider")?; + let state_provider = + provider.state_by_block_hash(header.hash()).context("getting state provider")?; let parsed_bundle = create_parsed_bundle(vec![envelope.clone()])?; let (results, total_gas_used, total_gas_fees, bundle_hash, total_execution_time) = - meter_bundle(state_provider, harness.chain_spec.clone(), parsed_bundle, &harness.header)?; + meter_bundle(state_provider, chain_spec, parsed_bundle, &header)?; assert_eq!(results.len(), 1); let result = &results[0]; @@ -164,9 +150,13 @@ fn meter_bundle_single_transaction() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_bundle_multiple_transactions() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_bundle_multiple_transactions() -> eyre::Result<()> { + let harness = setup().await?; + + let provider = harness.blockchain_provider(); + let header = provider.sealed_header(0)?.expect("genesis header exists"); + let chain_spec = harness.chain_spec(); let to_1 = Address::random(); let to_2 = Address::random(); @@ -174,7 +164,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create first transaction let signed_tx_1 = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to_1) .value(1_000) @@ -190,7 +180,7 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { // Create second transaction let signed_tx_2 = TransactionBuilder::default() .signer(Account::Bob.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to_2) .value(2_000) @@ -208,15 +198,13 @@ fn meter_bundle_multiple_transactions() -> eyre::Result<()> { let tx_hash_1 = envelope_1.tx_hash(); let tx_hash_2 = envelope_2.tx_hash(); - let state_provider = harness - .provider - .state_by_block_hash(harness.header.hash()) - .context("getting state provider")?; + let state_provider = + provider.state_by_block_hash(header.hash()).context("getting state provider")?; let parsed_bundle = create_parsed_bundle(vec![envelope_1.clone(), envelope_2.clone()])?; let (results, total_gas_used, total_gas_fees, bundle_hash, total_execution_time) = - meter_bundle(state_provider, harness.chain_spec.clone(), parsed_bundle, &harness.header)?; + meter_bundle(state_provider, chain_spec, parsed_bundle, &header)?; assert_eq!(results.len(), 2); assert!(total_execution_time > 0); diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 258e9e8c..2a081289 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -5,29 +5,15 @@ use std::sync::Arc; use alloy_consensus::{BlockHeader, Header}; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; -use base_metering::meter_block; -use base_test_utils::{Account, create_provider_factory}; -use eyre::Context; -use reth::{api::NodeTypesWithDBAdapter, chainspec::EthChainSpec}; -use reth_db::{DatabaseEnv, test_utils::TempDatabase}; +use base_metering::{MeteringExtension, meter_block}; +use base_test_utils::{Account, TestHarness}; +use reth::chainspec::EthChainSpec; use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; -use reth_optimism_node::OpNode; use reth_optimism_primitives::{OpBlock, OpBlockBody, OpTransactionSigned}; use reth_primitives_traits::Block as BlockT; -use reth_provider::{HeaderProvider, providers::BlockchainProvider}; +use reth_provider::HeaderProvider; use reth_transaction_pool::test_utils::TransactionBuilder; -type NodeTypes = NodeTypesWithDBAdapter>>; - -#[derive(Debug, Clone)] -struct TestHarness { - provider: BlockchainProvider, - genesis_header_hash: B256, - genesis_header_number: u64, - genesis_header_timestamp: u64, - chain_spec: Arc, -} - fn create_chain_spec() -> Arc { let genesis = BASE_MAINNET .genesis @@ -48,35 +34,26 @@ fn create_chain_spec() -> Arc { Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) } -fn setup_harness() -> eyre::Result { +async fn setup() -> eyre::Result { let chain_spec = create_chain_spec(); - let factory = create_provider_factory::(chain_spec.clone()); - - reth_db_common::init::init_genesis(&factory).context("initializing genesis state")?; - - let provider = BlockchainProvider::new(factory.clone()).context("creating provider")?; - let header = provider - .sealed_header(0) - .context("fetching genesis header")? - .expect("genesis header exists"); - - Ok(TestHarness { - provider, - genesis_header_hash: header.hash(), - genesis_header_number: header.number(), - genesis_header_timestamp: header.timestamp(), - chain_spec, - }) + TestHarness::builder() + .with_chain_spec(chain_spec) + .with_extension(MeteringExtension::new(true)) + .build() + .await } fn create_block_with_transactions( harness: &TestHarness, transactions: Vec, -) -> OpBlock { +) -> eyre::Result { + let provider = harness.blockchain_provider(); + let genesis = provider.sealed_header(0)?.expect("genesis header exists"); + let header = Header { - parent_hash: harness.genesis_header_hash, - number: harness.genesis_header_number + 1, - timestamp: harness.genesis_header_timestamp + 2, + parent_hash: genesis.hash(), + number: genesis.number() + 1, + timestamp: genesis.timestamp() + 2, gas_limit: 30_000_000, beneficiary: Address::random(), base_fee_per_gas: Some(1), @@ -87,16 +64,16 @@ fn create_block_with_transactions( let body = OpBlockBody { transactions, ommers: vec![], withdrawals: None }; - OpBlock::new(header, body) + Ok(OpBlock::new(header, body)) } -#[test] -fn meter_block_empty_transactions() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_empty_transactions() -> eyre::Result<()> { + let harness = setup().await?; - let block = create_block_with_transactions(&harness, vec![]); + let block = create_block_with_transactions(&harness, vec![])?; - let response = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block)?; + let response = meter_block(harness.blockchain_provider(), harness.chain_spec(), &block)?; assert_eq!(response.block_hash, block.header().hash_slow()); assert_eq!(response.block_number, block.header().number()); @@ -112,14 +89,15 @@ fn meter_block_empty_transactions() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_block_single_transaction() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_single_transaction() -> eyre::Result<()> { + let harness = setup().await?; + let chain_spec = harness.chain_spec(); let to = Address::random(); let signed_tx = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to) .value(1_000) @@ -132,9 +110,9 @@ fn meter_block_single_transaction() -> eyre::Result<()> { OpTransactionSigned::Eip1559(signed_tx.as_eip1559().expect("eip1559 transaction").clone()); let tx_hash = tx.tx_hash(); - let block = create_block_with_transactions(&harness, vec![tx]); + let block = create_block_with_transactions(&harness, vec![tx])?; - let response = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block)?; + let response = meter_block(harness.blockchain_provider(), chain_spec, &block)?; assert_eq!(response.block_hash, block.header().hash_slow()); assert_eq!(response.block_number, block.header().number()); @@ -156,9 +134,10 @@ fn meter_block_single_transaction() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_block_multiple_transactions() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_multiple_transactions() -> eyre::Result<()> { + let harness = setup().await?; + let chain_spec = harness.chain_spec(); let to_1 = Address::random(); let to_2 = Address::random(); @@ -166,7 +145,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create first transaction from Alice let signed_tx_1 = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to_1) .value(1_000) @@ -183,7 +162,7 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { // Create second transaction from Bob let signed_tx_2 = TransactionBuilder::default() .signer(Account::Bob.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(to_2) .value(2_000) @@ -197,9 +176,9 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { ); let tx_hash_2 = tx_2.tx_hash(); - let block = create_block_with_transactions(&harness, vec![tx_1, tx_2]); + let block = create_block_with_transactions(&harness, vec![tx_1, tx_2])?; - let response = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block)?; + let response = meter_block(harness.blockchain_provider(), chain_spec, &block)?; assert_eq!(response.block_hash, block.header().hash_slow()); assert_eq!(response.block_number, block.header().number()); @@ -236,14 +215,15 @@ fn meter_block_multiple_transactions() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_block_timing_consistency() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_timing_consistency() -> eyre::Result<()> { + let harness = setup().await?; + let chain_spec = harness.chain_spec(); // Create a block with one transaction let signed_tx = TransactionBuilder::default() .signer(Account::Alice.signer_b256()) - .chain_id(harness.chain_spec.chain_id()) + .chain_id(chain_spec.chain_id()) .nonce(0) .to(Address::random()) .value(1_000) @@ -255,9 +235,9 @@ fn meter_block_timing_consistency() -> eyre::Result<()> { let tx = OpTransactionSigned::Eip1559(signed_tx.as_eip1559().expect("eip1559 transaction").clone()); - let block = create_block_with_transactions(&harness, vec![tx]); + let block = create_block_with_transactions(&harness, vec![tx])?; - let response = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block)?; + let response = meter_block(harness.blockchain_provider(), chain_spec, &block)?; // Verify timing invariants assert!(response.signer_recovery_time_us > 0, "signer recovery time must be positive"); @@ -276,16 +256,19 @@ fn meter_block_timing_consistency() -> eyre::Result<()> { // Error Path Tests // ============================================================================ -#[test] -fn meter_block_parent_header_not_found() -> eyre::Result<()> { - let harness = setup_harness()?; +#[tokio::test] +async fn meter_block_parent_header_not_found() -> eyre::Result<()> { + let harness = setup().await?; + let chain_spec = harness.chain_spec(); + let provider = harness.blockchain_provider(); + let genesis = provider.sealed_header(0)?.expect("genesis header exists"); // Create a block that references a non-existent parent let fake_parent_hash = B256::random(); let header = Header { parent_hash: fake_parent_hash, // This parent doesn't exist number: 999, - timestamp: harness.genesis_header_timestamp + 2, + timestamp: genesis.timestamp() + 2, gas_limit: 30_000_000, beneficiary: Address::random(), base_fee_per_gas: Some(1), @@ -296,7 +279,7 @@ fn meter_block_parent_header_not_found() -> eyre::Result<()> { let body = OpBlockBody { transactions: vec![], ommers: vec![], withdrawals: None }; let block = OpBlock::new(header, body); - let result = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block); + let result = meter_block(provider, chain_spec, &block); assert!(result.is_err(), "should fail when parent header is not found"); let err = result.unwrap_err(); @@ -310,16 +293,17 @@ fn meter_block_parent_header_not_found() -> eyre::Result<()> { Ok(()) } -#[test] -fn meter_block_invalid_transaction_signature() -> eyre::Result<()> { +#[tokio::test] +async fn meter_block_invalid_transaction_signature() -> eyre::Result<()> { use alloy_consensus::TxEip1559; use alloy_primitives::Signature; - let harness = setup_harness()?; + let harness = setup().await?; + let chain_spec = harness.chain_spec(); // Create a transaction with an invalid signature let tx = TxEip1559 { - chain_id: harness.chain_spec.chain_id(), + chain_id: chain_spec.chain_id(), nonce: 0, gas_limit: 21_000, max_fee_per_gas: 10, @@ -337,9 +321,9 @@ fn meter_block_invalid_transaction_signature() -> eyre::Result<()> { let signed_tx = alloy_consensus::Signed::new_unchecked(tx, invalid_signature, B256::random()); let op_tx = OpTransactionSigned::Eip1559(signed_tx); - let block = create_block_with_transactions(&harness, vec![op_tx]); + let block = create_block_with_transactions(&harness, vec![op_tx])?; - let result = meter_block(harness.provider.clone(), harness.chain_spec.clone(), &block); + let result = meter_block(harness.blockchain_provider(), chain_spec, &block); assert!(result.is_err(), "should fail when transaction has invalid signature"); let err = result.unwrap_err(); diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs index f856ae5f..18f1219c 100644 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ b/crates/client/test-utils/src/flashblocks_harness.rs @@ -2,9 +2,11 @@ use std::{sync::Arc, time::Duration}; +use alloy_genesis::Genesis; use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; +use reth_optimism_chainspec::OpChainSpec; use tokio::time::sleep; use crate::{ @@ -46,12 +48,16 @@ impl FlashblocksHarness { async fn with_options(process_canonical: bool) -> Result { init_silenced_tracing(); + // Load default chain spec + let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + // Create the extension and keep a reference to get parts after launch let extension = FlashblocksTestExtension::new(process_canonical); let parts_source = extension.clone(); // Launch the node with the flashblocks extension - let node = LocalNode::new(vec![Box::new(extension)]).await?; + let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; let engine = node.engine_api()?; sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index b0196e8d..f18684ba 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -1,8 +1,9 @@ //! Unified test harness combining node and engine helpers, plus optional flashblocks adapter. -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use alloy_eips::{BlockHashOrNumber, eip7685::Requests}; +use alloy_genesis::Genesis; use alloy_primitives::{B64, B256, Bytes}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_client::RpcClient; @@ -13,6 +14,7 @@ use eyre::{Result, eyre}; use op_alloy_network::Optimism; use op_alloy_rpc_types_engine::OpPayloadAttributes; use reth::providers::{BlockNumReader, BlockReader, ChainSpecProvider}; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_primitives::OpBlock; use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::time::sleep; @@ -29,6 +31,7 @@ use crate::{ #[derive(Debug, Default)] pub struct TestHarnessBuilder { extensions: Vec>, + chain_spec: Option>, } impl TestHarnessBuilder { @@ -43,10 +46,28 @@ impl TestHarnessBuilder { self } + /// Set a custom chain spec for the test harness. + /// + /// If not provided, the default genesis from `assets/genesis.json` is used. + pub fn with_chain_spec(mut self, chain_spec: Arc) -> Self { + self.chain_spec = Some(chain_spec); + self + } + /// Build and launch the test harness. pub async fn build(self) -> Result { init_silenced_tracing(); - let node = LocalNode::new(self.extensions).await?; + + let chain_spec = match self.chain_spec { + Some(spec) => spec, + None => { + let genesis: Genesis = + serde_json::from_str(include_str!("../assets/genesis.json"))?; + Arc::new(OpChainSpec::from_genesis(genesis)) + } + }; + + let node = LocalNode::new(self.extensions, chain_spec).await?; let engine = node.engine_api()?; sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; @@ -204,6 +225,11 @@ impl TestHarness { .expect("canonical block exists"); BlockT::try_into_recovered(block).expect("able to recover canonical block") } + + /// Return the chain specification used by the harness. + pub fn chain_spec(&self) -> Arc { + self.node.blockchain_provider().chain_spec() + } } #[cfg(test)] diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index 5f95da0f..ea14563b 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -234,14 +234,14 @@ impl BaseNodeExtension for FlashblocksTestExtension { } impl LocalNode { - /// Launch a new local node with the provided extensions. - pub async fn new(extensions: Vec>) -> Result { + /// Launch a new local node with the provided extensions and chain spec. + pub async fn new( + extensions: Vec>, + chain_spec: Arc, + ) -> Result { let tasks = TaskManager::current(); let exec = tasks.executor(); - let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; - let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); - let network_config = NetworkArgs { discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() }, ..NetworkArgs::default() @@ -399,9 +399,13 @@ impl FlashblocksLocalNode { } async fn with_options(process_canonical: bool) -> Result { + // Load default chain spec + let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + let extension = FlashblocksTestExtension::new(process_canonical); let parts_source = extension.clone(); - let node = LocalNode::new(vec![Box::new(extension)]).await?; + let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; let parts = parts_source.parts()?; Ok(Self { node, parts }) } From 96f607467741e2aebf97748efd3b285afecbcdfb Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 21:04:17 -0600 Subject: [PATCH 250/262] chore: chain spec loaders --- Cargo.lock | 3 +- crates/client/metering/tests/meter.rs | 28 ++--------------- crates/client/metering/tests/meter_block.rs | 30 ++----------------- crates/client/test-utils/src/fixtures.rs | 26 +++++++++++++++- crates/client/test-utils/src/lib.rs | 2 +- crates/shared/access-lists/Cargo.toml | 3 +- .../shared/access-lists/tests/builder/main.rs | 9 ++---- 7 files changed, 36 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fda0a2c..eac1cf5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,14 +1515,13 @@ dependencies = [ "alloy-rlp", "alloy-sol-macro", "alloy-sol-types", + "base-test-utils", "eyre", "op-revm", "reth-evm", - "reth-optimism-chainspec", "reth-optimism-evm", "revm", "serde", - "serde_json", "tracing", ] diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index 56b10f6f..de6875fd 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -1,43 +1,19 @@ //! Integration tests covering the Metering logic surface area. -use std::sync::Arc; - use alloy_eips::Encodable2718; -use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; use base_metering::{MeteringExtension, meter_bundle}; -use base_test_utils::{Account, TestHarness}; +use base_test_utils::{Account, TestHarness, load_chain_spec}; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; use reth::chainspec::EthChainSpec; -use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; use reth_optimism_primitives::OpTransactionSigned; use reth_provider::{HeaderProvider, StateProviderFactory}; use reth_transaction_pool::test_utils::TransactionBuilder; -fn create_chain_spec() -> Arc { - let genesis = BASE_MAINNET - .genesis - .clone() - .extend_accounts( - Account::all() - .into_iter() - .map(|a| { - ( - a.address(), - GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), - ) - }) - .collect::>(), - ) - .with_gas_limit(100_000_000); - - Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) -} - async fn setup() -> eyre::Result { - let chain_spec = create_chain_spec(); + let chain_spec = load_chain_spec(); TestHarness::builder() .with_chain_spec(chain_spec) .with_extension(MeteringExtension::new(true)) diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 2a081289..1f3bbeb1 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -1,41 +1,17 @@ //! Integration tests for block metering functionality. -use std::sync::Arc; - use alloy_consensus::{BlockHeader, Header}; -use alloy_genesis::GenesisAccount; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, B256}; use base_metering::{MeteringExtension, meter_block}; -use base_test_utils::{Account, TestHarness}; +use base_test_utils::{Account, TestHarness, load_chain_spec}; use reth::chainspec::EthChainSpec; -use reth_optimism_chainspec::{BASE_MAINNET, OpChainSpec, OpChainSpecBuilder}; use reth_optimism_primitives::{OpBlock, OpBlockBody, OpTransactionSigned}; use reth_primitives_traits::Block as BlockT; use reth_provider::HeaderProvider; use reth_transaction_pool::test_utils::TransactionBuilder; -fn create_chain_spec() -> Arc { - let genesis = BASE_MAINNET - .genesis - .clone() - .extend_accounts( - Account::all() - .into_iter() - .map(|a| { - ( - a.address(), - GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), - ) - }) - .collect::>(), - ) - .with_gas_limit(100_000_000); - - Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).isthmus_activated().build()) -} - async fn setup() -> eyre::Result { - let chain_spec = create_chain_spec(); + let chain_spec = load_chain_spec(); TestHarness::builder() .with_chain_spec(chain_spec) .with_extension(MeteringExtension::new(true)) diff --git a/crates/client/test-utils/src/fixtures.rs b/crates/client/test-utils/src/fixtures.rs index 382a01b5..6e6ab7c1 100644 --- a/crates/client/test-utils/src/fixtures.rs +++ b/crates/client/test-utils/src/fixtures.rs @@ -2,20 +2,44 @@ use std::sync::Arc; -use alloy_genesis::Genesis; +use alloy_genesis::{Genesis, GenesisAccount}; +use alloy_primitives::U256; use reth::api::{NodeTypes, NodeTypesWithDBAdapter}; use reth_db::{ ClientVersion, DatabaseEnv, init_db, mdbx::{DatabaseArguments, KILOBYTE, MEGABYTE, MaxReadTransactionDuration}, test_utils::{ERROR_DB_CREATION, TempDatabase, create_test_static_files_dir, tempdir_path}, }; +use reth_optimism_chainspec::OpChainSpec; use reth_provider::{ProviderFactory, providers::StaticFileProvider}; +use crate::Account; + /// Loads the shared test genesis configuration. pub fn load_genesis() -> Genesis { serde_json::from_str(include_str!("../assets/genesis.json")).unwrap() } +/// Creates a test chain spec with pre-funded test accounts. +/// Uses the shared genesis.json (Base Sepolia) as the base configuration. +pub fn load_chain_spec() -> Arc { + let genesis = load_genesis() + .extend_accounts( + Account::all() + .into_iter() + .map(|a| { + ( + a.address(), + GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), + ) + }) + .collect::>(), + ) + .with_gas_limit(100_000_000); + + Arc::new(OpChainSpec::from_genesis(genesis)) +} + /// Creates a provider factory for tests with the given chain spec. pub fn create_provider_factory( chain_spec: Arc, diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index dd8a5e09..7d361e30 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -19,7 +19,7 @@ mod engine; pub use engine::{EngineAddress, EngineApi, EngineProtocol, HttpEngine, IpcEngine}; mod fixtures; -pub use fixtures::{create_provider_factory, load_genesis}; +pub use fixtures::{create_provider_factory, load_chain_spec, load_genesis}; mod flashblocks_harness; pub use flashblocks_harness::FlashblocksHarness; diff --git a/crates/shared/access-lists/Cargo.toml b/crates/shared/access-lists/Cargo.toml index 2a0828d6..ab4bd3cd 100644 --- a/crates/shared/access-lists/Cargo.toml +++ b/crates/shared/access-lists/Cargo.toml @@ -22,14 +22,13 @@ serde.workspace = true [dev-dependencies] op-revm.workspace = true eyre.workspace = true -reth-optimism-chainspec.workspace = true reth-optimism-evm.workspace = true alloy-consensus.workspace = true alloy-contract.workspace = true alloy-sol-macro = { workspace = true, features = ["json"] } alloy-sol-types.workspace = true reth-evm.workspace = true -serde_json.workspace = true +base-test-utils.workspace = true [[test]] name = "builder" diff --git a/crates/shared/access-lists/tests/builder/main.rs b/crates/shared/access-lists/tests/builder/main.rs index 04d94e6d..405b6048 100644 --- a/crates/shared/access-lists/tests/builder/main.rs +++ b/crates/shared/access-lists/tests/builder/main.rs @@ -1,6 +1,6 @@ //! Tests for ensuring the access list is built properly -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; use alloy_consensus::Header; pub use alloy_primitives::{Address, B256, TxKind, U256}; @@ -8,10 +8,10 @@ use alloy_sol_macro::sol; pub use alloy_sol_types::SolCall; use base_access_lists::FBALBuilderDb; pub use base_access_lists::FlashblockAccessList; +use base_test_utils::load_chain_spec; pub use eyre::Result; pub use op_revm::OpTransaction; use reth_evm::{ConfigureEvm, Evm}; -use reth_optimism_chainspec::OpChainSpec; use reth_optimism_evm::OpEvmConfig; use revm::{DatabaseCommit, context::result::ResultAndState, database::InMemoryDB}; pub use revm::{ @@ -93,10 +93,7 @@ pub fn execute_txns_build_access_list( acc_overrides: Option>, storage_overrides: Option>>, ) -> Result { - let chain_spec = Arc::new(OpChainSpec::from_genesis( - serde_json::from_str(include_str!("../../../../client/test-utils/assets/genesis.json")) - .unwrap(), - )); + let chain_spec = load_chain_spec(); let evm_config = OpEvmConfig::optimism(chain_spec.clone()); let header = Header { base_fee_per_gas: Some(0), ..chain_spec.genesis_header().clone() }; From 07827bfc9a2e720a1182a90cb21b36fbc66b5200 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 21:43:57 -0600 Subject: [PATCH 251/262] chore: remove genesis file + build it in rust --- .../client/flashblocks/tests/eip7702_tests.rs | 12 +- crates/client/metering/tests/meter_rpc.rs | 16 +-- crates/client/test-utils/assets/genesis.json | 107 ------------------ crates/client/test-utils/src/fixtures.rs | 95 ++++++++++++++-- .../test-utils/src/flashblocks_harness.rs | 5 +- crates/client/test-utils/src/harness.rs | 11 +- crates/client/test-utils/src/lib.rs | 2 +- crates/client/test-utils/src/node.rs | 5 +- .../shared/access-lists/tests/builder/main.rs | 6 +- 9 files changed, 114 insertions(+), 145 deletions(-) delete mode 100644 crates/client/test-utils/assets/genesis.json diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index 435cc71b..4ddd8965 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -48,10 +48,6 @@ impl TestSetup { fn provider(&self) -> alloy_provider::RootProvider { self.harness.provider() } - - fn chain_id(&self) -> u64 { - 84532 // Base Sepolia chain ID (matches test harness) - } } /// Build an EIP-7702 authorization for delegating to a contract @@ -172,7 +168,7 @@ fn create_eip7702_flashblock(eip7702_tx: Bytes, cumulative_gas: u64) -> Flashblo #[tokio::test] async fn test_eip7702_delegation_in_pending_flashblock() -> Result<()> { let setup = TestSetup::new().await?; - let chain_id = setup.chain_id(); + let chain_id = setup.harness.chain_id(); // Send base flashblock with contract deployment let base_payload = create_base_flashblock(&setup); @@ -214,7 +210,7 @@ async fn test_eip7702_delegation_in_pending_flashblock() -> Result<()> { #[tokio::test] async fn test_eip7702_multiple_delegations_same_flashblock() -> Result<()> { let setup = TestSetup::new().await?; - let chain_id = setup.chain_id(); + let chain_id = setup.harness.chain_id(); // Send base flashblock with contract deployment let base_payload = create_base_flashblock(&setup); @@ -288,7 +284,7 @@ async fn test_eip7702_multiple_delegations_same_flashblock() -> Result<()> { #[tokio::test] async fn test_eip7702_pending_receipt() -> Result<()> { let setup = TestSetup::new().await?; - let chain_id = setup.chain_id(); + let chain_id = setup.harness.chain_id(); // Send base flashblock with contract deployment let base_payload = create_base_flashblock(&setup); @@ -327,7 +323,7 @@ async fn test_eip7702_pending_receipt() -> Result<()> { #[tokio::test] async fn test_eip7702_delegation_then_execution() -> Result<()> { let setup = TestSetup::new().await?; - let chain_id = setup.chain_id(); + let chain_id = setup.harness.chain_id(); // Send base flashblock with contract deployment let base_payload = create_base_flashblock(&setup); diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index f82d8faf..6739f1a0 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -52,7 +52,7 @@ async fn test_meter_bundle_empty() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { - let (_harness, client) = setup().await?; + let (harness, client) = setup().await?; let sender_address = Account::Alice.address(); let sender_secret = Account::Alice.signer_b256(); @@ -60,7 +60,7 @@ async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { // Build a transaction let tx = TransactionBuilder::default() .signer(sender_secret) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x1111111111111111111111111111111111111111")) .value(1000) @@ -96,14 +96,14 @@ async fn test_meter_bundle_single_transaction() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { - let (_harness, client) = setup().await?; + let (harness, client) = setup().await?; let address1 = Account::Alice.address(); let secret1 = Account::Alice.signer_b256(); let tx1_inner = TransactionBuilder::default() .signer(secret1) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x1111111111111111111111111111111111111111")) .value(1000) @@ -123,7 +123,7 @@ async fn test_meter_bundle_multiple_transactions() -> eyre::Result<()> { let tx2_inner = TransactionBuilder::default() .signer(secret2) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x2222222222222222222222222222222222222222")) .value(2000) @@ -252,7 +252,7 @@ async fn test_meter_bundle_arbitrary_block_number() -> eyre::Result<()> { #[tokio::test] async fn test_meter_bundle_gas_calculations() -> eyre::Result<()> { - let (_harness, client) = setup().await?; + let (harness, client) = setup().await?; let secret1 = Account::Alice.signer_b256(); let secret2 = Account::Bob.signer_b256(); @@ -260,7 +260,7 @@ async fn test_meter_bundle_gas_calculations() -> eyre::Result<()> { // First transaction with 3 gwei gas price let tx1_inner = TransactionBuilder::default() .signer(secret1) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x1111111111111111111111111111111111111111")) .value(1000) @@ -277,7 +277,7 @@ async fn test_meter_bundle_gas_calculations() -> eyre::Result<()> { // Second transaction with 7 gwei gas price let tx2_inner = TransactionBuilder::default() .signer(secret2) - .chain_id(84532) + .chain_id(harness.chain_id()) .nonce(0) .to(address!("0x2222222222222222222222222222222222222222")) .value(2000) diff --git a/crates/client/test-utils/assets/genesis.json b/crates/client/test-utils/assets/genesis.json deleted file mode 100644 index dde20c5e..00000000 --- a/crates/client/test-utils/assets/genesis.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "config": { - "chainId": 84532, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "berlinBlock": 0, - "londonBlock": 0, - "arrowGlacierBlock": 0, - "grayGlacierBlock": 0, - "mergeNetsplitBlock": 0, - "bedrockBlock": 0, - "regolithTime": 0, - "canyonTime": 0, - "ecotoneTime": 0, - "fjordTime": 0, - "graniteTime": 0, - "isthmusTime": 0, - "jovianTime": 0, - "pragueTime": 0, - "terminalTotalDifficulty": 0, - "terminalTotalDifficultyPassed": true, - "optimism": { - "eip1559Elasticity": 6, - "eip1559Denominator": 50 - } - }, - "nonce": "0x0", - "timestamp": "0x0", - "extraData": "0x00", - "gasLimit": "0x1c9c380", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "alloc": { - "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x1cbd3b2770909d4e10f157cabc84c7264073c9ec": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x2546bcd3c84621e976d8185a91a922ae77ecec30": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x71be63f3384f5fb98995898a86b02fb2426c5788": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x976ea74026e726554db657fa54763abd0c3a0aa9": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { - "balance": "0xd3c21bcecceda1000000" - }, - "0x9c41de96b2088cdc640c6182dfcf5491dc574a57": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xbcd4042de499d14e55001ccbb24a551f3b954096": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xbda5747bfd65f08deb54cb465eb87d40e51b197e": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xcd3b766ccdd6ae721141f452c550ca635964ce71": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xdd2fd4581271e230360230f9337d5c0430bf44c0": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { - "balance": "0xd3c21bcecceda1000000" - }, - "0xfabb0ac9d68b0b445fb7357272ff202c5651694a": { - "balance": "0xd3c21bcecceda1000000" - } - }, - "number": "0x0" -} diff --git a/crates/client/test-utils/src/fixtures.rs b/crates/client/test-utils/src/fixtures.rs index 6e6ab7c1..e707b125 100644 --- a/crates/client/test-utils/src/fixtures.rs +++ b/crates/client/test-utils/src/fixtures.rs @@ -1,9 +1,9 @@ //! Shared fixtures and test data reused by integration tests across the Base codebase. -use std::sync::Arc; +use std::{collections::BTreeMap, sync::Arc}; -use alloy_genesis::{Genesis, GenesisAccount}; -use alloy_primitives::U256; +use alloy_genesis::{ChainConfig, Genesis, GenesisAccount}; +use alloy_primitives::{Address, B256, Bytes, U256, utils::parse_ether}; use reth::api::{NodeTypes, NodeTypesWithDBAdapter}; use reth_db::{ ClientVersion, DatabaseEnv, init_db, @@ -15,15 +15,94 @@ use reth_provider::{ProviderFactory, providers::StaticFileProvider}; use crate::Account; -/// Loads the shared test genesis configuration. -pub fn load_genesis() -> Genesis { - serde_json::from_str(include_str!("../assets/genesis.json")).unwrap() +/// Builds a test genesis configuration programmatically. +/// +/// Creates a Base Sepolia-like genesis with: +/// - All EVM and OP hardforks enabled from genesis +/// - Optimism EIP-1559 settings (elasticity=6, denominator=50) +/// - Pre-funded test accounts from the `Account` enum +pub fn build_test_genesis() -> Genesis { + // OP EIP-1559 base fee parameters + const EIP1559_ELASTICITY: u64 = 6; + const EIP1559_DENOMINATOR: u64 = 50; + + // Test account balance: 1 million ETH + let test_account_balance: U256 = parse_ether("1000000").expect("valid ether amount"); + + // Build chain config with all hardforks enabled at genesis + let config = ChainConfig { + chain_id: crate::BASE_CHAIN_ID, + // Block-based EVM hardforks (all at block 0) + homestead_block: Some(0), + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + muir_glacier_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + arrow_glacier_block: Some(0), + gray_glacier_block: Some(0), + merge_netsplit_block: Some(0), + // Time-based hardforks + shanghai_time: Some(0), + cancun_time: Some(0), + prague_time: Some(0), + // Post-merge settings + terminal_total_difficulty: Some(U256::ZERO), + terminal_total_difficulty_passed: true, + // OP-specific hardforks and settings via extra_fields + extra_fields: [ + ("bedrockBlock", serde_json::json!(0)), + ("regolithTime", serde_json::json!(0)), + ("canyonTime", serde_json::json!(0)), + ("ecotoneTime", serde_json::json!(0)), + ("fjordTime", serde_json::json!(0)), + ("graniteTime", serde_json::json!(0)), + ("isthmusTime", serde_json::json!(0)), + ("jovianTime", serde_json::json!(0)), + ( + "optimism", + serde_json::json!({ + "eip1559Elasticity": EIP1559_ELASTICITY, + "eip1559Denominator": EIP1559_DENOMINATOR + }), + ), + ] + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(), + ..Default::default() + }; + + // Pre-fund all test accounts + let alloc: BTreeMap = Account::all() + .into_iter() + .map(|account| { + (account.address(), GenesisAccount::default().with_balance(test_account_balance)) + }) + .collect(); + + Genesis { + config, + alloc, + gas_limit: 100_000_000, + difficulty: U256::ZERO, + nonce: 0, + timestamp: 0, + extra_data: Bytes::from_static(&[0x00]), + mix_hash: B256::ZERO, + coinbase: Address::ZERO, + ..Default::default() + } } /// Creates a test chain spec with pre-funded test accounts. -/// Uses the shared genesis.json (Base Sepolia) as the base configuration. pub fn load_chain_spec() -> Arc { - let genesis = load_genesis() + let genesis = build_test_genesis() .extend_accounts( Account::all() .into_iter() diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs index 18f1219c..d56df358 100644 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ b/crates/client/test-utils/src/flashblocks_harness.rs @@ -2,7 +2,6 @@ use std::{sync::Arc, time::Duration}; -use alloy_genesis::Genesis; use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; @@ -48,8 +47,8 @@ impl FlashblocksHarness { async fn with_options(process_canonical: bool) -> Result { init_silenced_tracing(); - // Load default chain spec - let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + // Build default chain spec programmatically + let genesis = crate::build_test_genesis(); let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); // Create the extension and keep a reference to get parts after launch diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index f18684ba..9e5643d9 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -3,7 +3,6 @@ use std::{sync::Arc, time::Duration}; use alloy_eips::{BlockHashOrNumber, eip7685::Requests}; -use alloy_genesis::Genesis; use alloy_primitives::{B64, B256, Bytes}; use alloy_provider::{Provider, RootProvider}; use alloy_rpc_client::RpcClient; @@ -48,7 +47,7 @@ impl TestHarnessBuilder { /// Set a custom chain spec for the test harness. /// - /// If not provided, the default genesis from `assets/genesis.json` is used. + /// If not provided, the default genesis is built programmatically. pub fn with_chain_spec(mut self, chain_spec: Arc) -> Self { self.chain_spec = Some(chain_spec); self @@ -61,8 +60,7 @@ impl TestHarnessBuilder { let chain_spec = match self.chain_spec { Some(spec) => spec, None => { - let genesis: Genesis = - serde_json::from_str(include_str!("../assets/genesis.json"))?; + let genesis = crate::build_test_genesis(); Arc::new(OpChainSpec::from_genesis(genesis)) } }; @@ -230,6 +228,11 @@ impl TestHarness { pub fn chain_spec(&self) -> Arc { self.node.blockchain_provider().chain_spec() } + + /// Return the chain ID used by the harness. + pub fn chain_id(&self) -> u64 { + self.chain_spec().chain().id() + } } #[cfg(test)] diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index 7d361e30..3a1a1d86 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -19,7 +19,7 @@ mod engine; pub use engine::{EngineAddress, EngineApi, EngineProtocol, HttpEngine, IpcEngine}; mod fixtures; -pub use fixtures::{create_provider_factory, load_chain_spec, load_genesis}; +pub use fixtures::{build_test_genesis, create_provider_factory, load_chain_spec}; mod flashblocks_harness; pub use flashblocks_harness::FlashblocksHarness; diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index ea14563b..c06bc86e 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -8,7 +8,6 @@ use std::{ sync::{Arc, Mutex}, }; -use alloy_genesis::Genesis; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; @@ -399,8 +398,8 @@ impl FlashblocksLocalNode { } async fn with_options(process_canonical: bool) -> Result { - // Load default chain spec - let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json"))?; + // Build default chain spec programmatically + let genesis = crate::build_test_genesis(); let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); let extension = FlashblocksTestExtension::new(process_canonical); diff --git a/crates/shared/access-lists/tests/builder/main.rs b/crates/shared/access-lists/tests/builder/main.rs index 405b6048..5ab64d47 100644 --- a/crates/shared/access-lists/tests/builder/main.rs +++ b/crates/shared/access-lists/tests/builder/main.rs @@ -8,7 +8,7 @@ use alloy_sol_macro::sol; pub use alloy_sol_types::SolCall; use base_access_lists::FBALBuilderDb; pub use base_access_lists::FlashblockAccessList; -use base_test_utils::load_chain_spec; +use base_test_utils::{BASE_CHAIN_ID, load_chain_spec}; pub use eyre::Result; pub use op_revm::OpTransaction; use reth_evm::{ConfigureEvm, Evm}; @@ -80,8 +80,8 @@ sol!( ) ); -/// Chain ID for Base Sepolia -pub const BASE_SEPOLIA_CHAIN_ID: u64 = 84532; +/// Chain ID for Base Sepolia (re-export from test-utils for convenience) +pub const BASE_SEPOLIA_CHAIN_ID: u64 = BASE_CHAIN_ID; /// Executes a list of transactions and builds a FlashblockAccessList tracking all /// account and storage changes across all transactions. From bb07cd06541de37a4959340dad0bbce19a4b8c02 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 22:05:23 -0600 Subject: [PATCH 252/262] chore: setup shared primitives and move accounts/contracts there under test utils --- .gitignore | 1 - .gitmodules | 12 +-- Cargo.lock | 31 ++++-- Cargo.toml | 2 + Justfile | 2 +- crates/client/test-utils/Cargo.toml | 10 +- crates/client/test-utils/src/constants.rs | 5 - crates/client/test-utils/src/fixtures.rs | 94 +----------------- crates/client/test-utils/src/harness.rs | 2 +- crates/client/test-utils/src/lib.rs | 15 +-- crates/shared/access-lists/Cargo.toml | 4 +- .../access-lists/tests/builder/deployment.rs | 8 +- .../shared/access-lists/tests/builder/main.rs | 68 ++----------- .../access-lists/tests/builder/storage.rs | 12 +-- .../access-lists/tests/builder/transfers.rs | 11 +-- crates/shared/primitives/Cargo.toml | 46 +++++++++ .../primitives}/contracts/.gitignore | 0 .../primitives}/contracts/README.md | 0 .../primitives}/contracts/foundry.lock | 0 .../primitives}/contracts/foundry.toml | 0 .../primitives}/contracts/lib/forge-std | 0 .../contracts/lib/openzeppelin-contracts | 0 .../primitives}/contracts/lib/solmate | 0 .../script/DeployDoubleCounter.s.sol | 0 .../contracts/script/DeployERC20.s.sol | 0 .../primitives}/contracts/src/AccessList.sol | 0 .../contracts/src/ContractFactory.sol | 0 .../contracts/src/DoubleCounter.sol | 0 .../contracts/src/Minimal7702Account.sol | 0 .../primitives}/contracts/src/Proxy.sol | 0 crates/shared/primitives/src/lib.rs | 9 ++ .../primitives/src/test_utils}/accounts.rs | 28 +++--- .../primitives/src/test_utils}/contracts.rs | 41 +++++++- .../primitives/src/test_utils/genesis.rs | 96 +++++++++++++++++++ .../shared/primitives/src/test_utils/mod.rs | 13 +++ lychee.toml | 2 +- 36 files changed, 286 insertions(+), 226 deletions(-) create mode 100644 crates/shared/primitives/Cargo.toml rename crates/{client/test-utils => shared/primitives}/contracts/.gitignore (100%) rename crates/{client/test-utils => shared/primitives}/contracts/README.md (100%) rename crates/{client/test-utils => shared/primitives}/contracts/foundry.lock (100%) rename crates/{client/test-utils => shared/primitives}/contracts/foundry.toml (100%) rename crates/{client/test-utils => shared/primitives}/contracts/lib/forge-std (100%) rename crates/{client/test-utils => shared/primitives}/contracts/lib/openzeppelin-contracts (100%) rename crates/{client/test-utils => shared/primitives}/contracts/lib/solmate (100%) rename crates/{client/test-utils => shared/primitives}/contracts/script/DeployDoubleCounter.s.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/script/DeployERC20.s.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/AccessList.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/ContractFactory.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/DoubleCounter.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/Minimal7702Account.sol (100%) rename crates/{client/test-utils => shared/primitives}/contracts/src/Proxy.sol (100%) create mode 100644 crates/shared/primitives/src/lib.rs rename crates/{client/test-utils/src => shared/primitives/src/test_utils}/accounts.rs (85%) rename crates/{client/test-utils/src => shared/primitives/src/test_utils}/contracts.rs (53%) create mode 100644 crates/shared/primitives/src/test_utils/genesis.rs create mode 100644 crates/shared/primitives/src/test_utils/mod.rs diff --git a/.gitignore b/.gitignore index 959253b2..036d404a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ target/ .idea/ -integration_logs/ .DS_Store diff --git a/.gitmodules b/.gitmodules index f9644fe7..4508bf7f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ -[submodule "crates/client/test-utils/contracts/lib/forge-std"] - path = crates/client/test-utils/contracts/lib/forge-std +[submodule "crates/shared/primitives/contracts/lib/forge-std"] + path = crates/shared/primitives/contracts/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "crates/client/test-utils/contracts/lib/solmate"] - path = crates/client/test-utils/contracts/lib/solmate +[submodule "crates/shared/primitives/contracts/lib/solmate"] + path = crates/shared/primitives/contracts/lib/solmate url = https://github.com/transmissions11/solmate -[submodule "crates/client/test-utils/contracts/lib/openzeppelin-contracts"] - path = crates/client/test-utils/contracts/lib/openzeppelin-contracts +[submodule "crates/shared/primitives/contracts/lib/openzeppelin-contracts"] + path = crates/shared/primitives/contracts/lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/Cargo.lock b/Cargo.lock index eac1cf5e..705714ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1513,12 +1513,12 @@ dependencies = [ "alloy-eip7928", "alloy-primitives", "alloy-rlp", - "alloy-sol-macro", "alloy-sol-types", - "base-test-utils", + "base-primitives", "eyre", "op-revm", "reth-evm", + "reth-optimism-chainspec", "reth-optimism-evm", "revm", "serde", @@ -1679,6 +1679,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "base-primitives" +version = "0.2.1" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "alloy-sol-macro", + "alloy-sol-types", + "eyre", + "op-alloy-network", + "op-alloy-rpc-types", + "serde_json", +] + [[package]] name = "base-reth-node" version = "0.2.1" @@ -1707,8 +1726,6 @@ dependencies = [ name = "base-test-utils" version = "0.2.1" dependencies = [ - "alloy-consensus", - "alloy-contract", "alloy-eips", "alloy-genesis", "alloy-primitives", @@ -1717,19 +1734,16 @@ dependencies = [ "alloy-rpc-types", "alloy-rpc-types-engine", "alloy-signer", - "alloy-signer-local", - "alloy-sol-macro", - "alloy-sol-types", "base-client-primitives", "base-flashblocks", "base-flashtypes", + "base-primitives", "chrono", "derive_more", "eyre", "jsonrpsee", "once_cell", "op-alloy-network", - "op-alloy-rpc-types", "op-alloy-rpc-types-engine", "reth", "reth-db", @@ -1744,7 +1758,6 @@ dependencies = [ "reth-provider", "reth-rpc-layer", "reth-tracing", - "serde_json", "tokio", "tokio-stream", "tower", diff --git a/Cargo.toml b/Cargo.toml index 507d310c..7908db43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ base-access-lists = { path = "crates/shared/access-lists" } base-bundles = { path = "crates/shared/bundles" } base-cli-utils = { path = "crates/shared/cli-utils" } base-flashtypes = { path = "crates/shared/flashtypes" } +base-primitives = { path = "crates/shared/primitives" } base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } # Client base-client-primitives = { path = "crates/client/client-primitives" } @@ -104,6 +105,7 @@ alloy-eips = "1.0.41" alloy-serde = "1.0.41" alloy-eip7928 = "0.3.0" alloy-genesis = "1.0.41" +alloy-signer = "1.0.41" alloy-signer-local = "1.0.41" alloy-hardforks = "0.4.4" alloy-provider = "1.0.41" diff --git a/Justfile b/Justfile index 78bc8207..fe1ccf12 100644 --- a/Justfile +++ b/Justfile @@ -83,7 +83,7 @@ build-node: # Build the contracts used for tests build-contracts: - cd crates/client/test-utils/contracts && forge build + cd crates/shared/primitives/contracts && forge build # Cleans the workspace clean: diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml index 39ae07f2..39fa52d3 100644 --- a/crates/client/test-utils/Cargo.toml +++ b/crates/client/test-utils/Cargo.toml @@ -16,6 +16,7 @@ workspace = true base-client-primitives.workspace = true base-flashtypes.workspace = true base-flashblocks.workspace = true +base-primitives = { workspace = true, features = ["test-utils"] } # reth reth.workspace = true @@ -35,20 +36,14 @@ reth-ipc.workspace = true # alloy alloy-primitives.workspace = true alloy-genesis.workspace = true -alloy-sol-macro = { workspace = true, features = ["json"] } -alloy-sol-types.workspace = true -alloy-contract.workspace = true alloy-eips.workspace = true alloy-rpc-types.workspace = true alloy-rpc-types-engine.workspace = true -alloy-consensus.workspace = true alloy-provider.workspace = true alloy-rpc-client.workspace = true -alloy-signer = "1.0" -alloy-signer-local = "1.1.0" +alloy-signer.workspace = true # op-alloy -op-alloy-rpc-types.workspace = true op-alloy-rpc-types-engine.workspace = true op-alloy-network.workspace = true @@ -62,7 +57,6 @@ jsonrpsee.workspace = true # misc derive_more = { workspace = true, features = ["deref"] } tracing-subscriber.workspace = true -serde_json.workspace = true eyre.workspace = true once_cell.workspace = true url.workspace = true diff --git a/crates/client/test-utils/src/constants.rs b/crates/client/test-utils/src/constants.rs index 673e878b..5572e4fc 100644 --- a/crates/client/test-utils/src/constants.rs +++ b/crates/client/test-utils/src/constants.rs @@ -3,11 +3,6 @@ use alloy_primitives::{B256, Bytes, b256, bytes}; pub use reth::chainspec::NamedChain; -// Chain Configuration - -/// Chain ID used for test networks (Base Sepolia). -pub const BASE_CHAIN_ID: u64 = NamedChain::BaseSepolia as u64; - // Block Building /// Block time in seconds for test node configuration. diff --git a/crates/client/test-utils/src/fixtures.rs b/crates/client/test-utils/src/fixtures.rs index e707b125..14d67ab3 100644 --- a/crates/client/test-utils/src/fixtures.rs +++ b/crates/client/test-utils/src/fixtures.rs @@ -1,9 +1,10 @@ //! Shared fixtures and test data reused by integration tests across the Base codebase. -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; -use alloy_genesis::{ChainConfig, Genesis, GenesisAccount}; -use alloy_primitives::{Address, B256, Bytes, U256, utils::parse_ether}; +use alloy_genesis::GenesisAccount; +use alloy_primitives::U256; +use base_primitives::{Account, build_test_genesis}; use reth::api::{NodeTypes, NodeTypesWithDBAdapter}; use reth_db::{ ClientVersion, DatabaseEnv, init_db, @@ -13,93 +14,6 @@ use reth_db::{ use reth_optimism_chainspec::OpChainSpec; use reth_provider::{ProviderFactory, providers::StaticFileProvider}; -use crate::Account; - -/// Builds a test genesis configuration programmatically. -/// -/// Creates a Base Sepolia-like genesis with: -/// - All EVM and OP hardforks enabled from genesis -/// - Optimism EIP-1559 settings (elasticity=6, denominator=50) -/// - Pre-funded test accounts from the `Account` enum -pub fn build_test_genesis() -> Genesis { - // OP EIP-1559 base fee parameters - const EIP1559_ELASTICITY: u64 = 6; - const EIP1559_DENOMINATOR: u64 = 50; - - // Test account balance: 1 million ETH - let test_account_balance: U256 = parse_ether("1000000").expect("valid ether amount"); - - // Build chain config with all hardforks enabled at genesis - let config = ChainConfig { - chain_id: crate::BASE_CHAIN_ID, - // Block-based EVM hardforks (all at block 0) - homestead_block: Some(0), - eip150_block: Some(0), - eip155_block: Some(0), - eip158_block: Some(0), - byzantium_block: Some(0), - constantinople_block: Some(0), - petersburg_block: Some(0), - istanbul_block: Some(0), - muir_glacier_block: Some(0), - berlin_block: Some(0), - london_block: Some(0), - arrow_glacier_block: Some(0), - gray_glacier_block: Some(0), - merge_netsplit_block: Some(0), - // Time-based hardforks - shanghai_time: Some(0), - cancun_time: Some(0), - prague_time: Some(0), - // Post-merge settings - terminal_total_difficulty: Some(U256::ZERO), - terminal_total_difficulty_passed: true, - // OP-specific hardforks and settings via extra_fields - extra_fields: [ - ("bedrockBlock", serde_json::json!(0)), - ("regolithTime", serde_json::json!(0)), - ("canyonTime", serde_json::json!(0)), - ("ecotoneTime", serde_json::json!(0)), - ("fjordTime", serde_json::json!(0)), - ("graniteTime", serde_json::json!(0)), - ("isthmusTime", serde_json::json!(0)), - ("jovianTime", serde_json::json!(0)), - ( - "optimism", - serde_json::json!({ - "eip1559Elasticity": EIP1559_ELASTICITY, - "eip1559Denominator": EIP1559_DENOMINATOR - }), - ), - ] - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - .collect(), - ..Default::default() - }; - - // Pre-fund all test accounts - let alloc: BTreeMap = Account::all() - .into_iter() - .map(|account| { - (account.address(), GenesisAccount::default().with_balance(test_account_balance)) - }) - .collect(); - - Genesis { - config, - alloc, - gas_limit: 100_000_000, - difficulty: U256::ZERO, - nonce: 0, - timestamp: 0, - extra_data: Bytes::from_static(&[0x00]), - mix_hash: B256::ZERO, - coinbase: Address::ZERO, - ..Default::default() - } -} - /// Creates a test chain spec with pre-funded test accounts. pub fn load_chain_spec() -> Arc { let genesis = build_test_genesis() diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index 9e5643d9..92dcf47d 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -249,7 +249,7 @@ mod tests { let provider = harness.provider(); let chain_id = provider.get_chain_id().await?; - assert_eq!(chain_id, crate::BASE_CHAIN_ID); + assert_eq!(chain_id, crate::DEVNET_CHAIN_ID); let alice_balance = provider.get_balance(Account::Alice.address()).await?; assert!(alice_balance > U256::ZERO); diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index 3a1a1d86..1ab87bfc 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -3,23 +3,24 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -mod accounts; -pub use accounts::Account; +// Re-export from base-primitives for backwards compatibility +pub use base_primitives::{ + AccessListContract, Account, ContractFactory, DEVNET_CHAIN_ID, DoubleCounter, Logic, Logic2, + Minimal7702Account, MockERC20, Proxy, SimpleStorage, TransparentUpgradeableProxy, + build_test_genesis, +}; mod constants; pub use constants::{ - BASE_CHAIN_ID, BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, DEFAULT_JWT_SECRET, GAS_LIMIT, + BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, DEFAULT_JWT_SECRET, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, NODE_STARTUP_DELAY_MS, NamedChain, }; -mod contracts; -pub use contracts::{DoubleCounter, Minimal7702Account, MockERC20, TransparentUpgradeableProxy}; - mod engine; pub use engine::{EngineAddress, EngineApi, EngineProtocol, HttpEngine, IpcEngine}; mod fixtures; -pub use fixtures::{build_test_genesis, create_provider_factory, load_chain_spec}; +pub use fixtures::{create_provider_factory, load_chain_spec}; mod flashblocks_harness; pub use flashblocks_harness::FlashblocksHarness; diff --git a/crates/shared/access-lists/Cargo.toml b/crates/shared/access-lists/Cargo.toml index ab4bd3cd..a392db97 100644 --- a/crates/shared/access-lists/Cargo.toml +++ b/crates/shared/access-lists/Cargo.toml @@ -23,12 +23,12 @@ serde.workspace = true op-revm.workspace = true eyre.workspace = true reth-optimism-evm.workspace = true +reth-optimism-chainspec.workspace = true alloy-consensus.workspace = true alloy-contract.workspace = true -alloy-sol-macro = { workspace = true, features = ["json"] } alloy-sol-types.workspace = true reth-evm.workspace = true -base-test-utils.workspace = true +base-primitives = { workspace = true, features = ["test-utils"] } [[test]] name = "builder" diff --git a/crates/shared/access-lists/tests/builder/deployment.rs b/crates/shared/access-lists/tests/builder/deployment.rs index ea25add6..b4f0b923 100644 --- a/crates/shared/access-lists/tests/builder/deployment.rs +++ b/crates/shared/access-lists/tests/builder/deployment.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use super::{ - AccountInfo, B256, BASE_SEPOLIA_CHAIN_ID, Bytecode, ContractFactory, IntoAddress, ONE_ETHER, + AccountInfo, B256, Bytecode, ContractFactory, DEVNET_CHAIN_ID, IntoAddress, ONE_ETHER, OpTransaction, SimpleStorage, SolCall, TxEnv, TxKind, U256, execute_txns_build_access_list, }; @@ -30,7 +30,7 @@ fn test_create_deployment_tracked() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(factory)) .data( ContractFactory::deployWithCreateCall { @@ -96,7 +96,7 @@ fn test_create2_deployment_tracked() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(factory)) .data( ContractFactory::deployWithCreate2Call { @@ -160,7 +160,7 @@ fn test_create_and_immediate_call() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(factory)) .data( ContractFactory::deployAndCallCall { diff --git a/crates/shared/access-lists/tests/builder/main.rs b/crates/shared/access-lists/tests/builder/main.rs index 5ab64d47..fbcc9b21 100644 --- a/crates/shared/access-lists/tests/builder/main.rs +++ b/crates/shared/access-lists/tests/builder/main.rs @@ -1,17 +1,19 @@ //! Tests for ensuring the access list is built properly -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use alloy_consensus::Header; pub use alloy_primitives::{Address, B256, TxKind, U256}; -use alloy_sol_macro::sol; pub use alloy_sol_types::SolCall; use base_access_lists::FBALBuilderDb; pub use base_access_lists::FlashblockAccessList; -use base_test_utils::{BASE_CHAIN_ID, load_chain_spec}; +use base_primitives::{ + AccessListContract, ContractFactory, DEVNET_CHAIN_ID, SimpleStorage, build_test_genesis, +}; pub use eyre::Result; pub use op_revm::OpTransaction; use reth_evm::{ConfigureEvm, Evm}; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_evm::OpEvmConfig; use revm::{DatabaseCommit, context::result::ResultAndState, database::InMemoryDB}; pub use revm::{ @@ -26,62 +28,10 @@ mod deployment; mod storage; mod transfers; -sol!( - #[sol(rpc)] - AccessListContract, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/AccessList.sol/AccessList.json" - ) -); - -sol!( - #[sol(rpc)] - ContractFactory, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/ContractFactory.sol/ContractFactory.json" - ) -); - -sol!( - #[sol(rpc)] - SimpleStorage, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/ContractFactory.sol/SimpleStorage.json" - ) -); - -sol!( - #[sol(rpc)] - Proxy, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/Proxy.sol/Proxy.json" - ) -); - -sol!( - #[sol(rpc)] - Logic, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/Proxy.sol/Logic.json" - ) -); - -sol!( - #[sol(rpc)] - Logic2, - concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../client/test-utils/contracts/out/Proxy.sol/Logic2.json" - ) -); - -/// Chain ID for Base Sepolia (re-export from test-utils for convenience) -pub const BASE_SEPOLIA_CHAIN_ID: u64 = BASE_CHAIN_ID; +/// Loads the test chain spec from the genesis configuration. +fn load_chain_spec() -> Arc { + Arc::new(OpChainSpec::from_genesis(build_test_genesis())) +} /// Executes a list of transactions and builds a FlashblockAccessList tracking all /// account and storage changes across all transactions. diff --git a/crates/shared/access-lists/tests/builder/storage.rs b/crates/shared/access-lists/tests/builder/storage.rs index 98ba221b..8f40bb11 100644 --- a/crates/shared/access-lists/tests/builder/storage.rs +++ b/crates/shared/access-lists/tests/builder/storage.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use super::{ - AccessListContract, AccountInfo, BASE_SEPOLIA_CHAIN_ID, Bytecode, IntoAddress, ONE_ETHER, + AccessListContract, AccountInfo, Bytecode, DEVNET_CHAIN_ID, IntoAddress, ONE_ETHER, OpTransaction, SolCall, TxEnv, TxKind, U256, execute_txns_build_access_list, }; @@ -24,7 +24,7 @@ fn test_sload_zero_value() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data(AccessListContract::valueCall {}.abi_encode().into()) .gas_price(0) @@ -67,7 +67,7 @@ fn test_update_one_value() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data( AccessListContract::updateValueCall { newValue: U256::from(42) } @@ -87,7 +87,7 @@ fn test_update_one_value() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data(AccessListContract::valueCall {}.abi_encode().into()) .nonce(1) @@ -147,7 +147,7 @@ fn test_multi_sload_same_slot() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data(AccessListContract::getABCall {}.abi_encode().into()) .nonce(0) @@ -193,7 +193,7 @@ fn test_multi_sstore() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(contract)) .data( AccessListContract::insertMultipleCall { diff --git a/crates/shared/access-lists/tests/builder/transfers.rs b/crates/shared/access-lists/tests/builder/transfers.rs index 87157f5f..68306b10 100644 --- a/crates/shared/access-lists/tests/builder/transfers.rs +++ b/crates/shared/access-lists/tests/builder/transfers.rs @@ -3,15 +3,14 @@ use std::collections::HashMap; use super::{ - AccountInfo, BASE_SEPOLIA_CHAIN_ID, IntoAddress, ONE_ETHER, OpTransaction, TxEnv, TxKind, U256, + AccountInfo, DEVNET_CHAIN_ID, IntoAddress, ONE_ETHER, OpTransaction, TxEnv, TxKind, U256, execute_txns_build_access_list, }; #[test] /// Tests that the system precompiles get included in the access list fn test_precompiles() { - let base_tx = - TxEnv::builder().chain_id(Some(BASE_SEPOLIA_CHAIN_ID)).gas_limit(50_000).gas_price(0); + let base_tx = TxEnv::builder().chain_id(Some(DEVNET_CHAIN_ID)).gas_limit(50_000).gas_price(0); let tx = OpTransaction::builder().base(base_tx).build_fill(); let access_list = execute_txns_build_access_list(vec![tx], None, None) .expect("access list build should succeed"); @@ -32,7 +31,7 @@ fn test_single_transfer() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(recipient)) .value(U256::from(1_000_000)) .gas_price(0) @@ -73,7 +72,7 @@ fn test_gas_included_in_balance_change() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .kind(TxKind::Call(recipient)) .value(U256::from(1_000_000)) .gas_price(1000) @@ -118,7 +117,7 @@ fn test_multiple_transfers() { .base( TxEnv::builder() .caller(sender) - .chain_id(Some(BASE_SEPOLIA_CHAIN_ID)) + .chain_id(Some(DEVNET_CHAIN_ID)) .nonce(i) .kind(TxKind::Call(recipient)) .value(U256::from(1_000_000)) diff --git a/crates/shared/primitives/Cargo.toml b/crates/shared/primitives/Cargo.toml new file mode 100644 index 00000000..2a38b5a7 --- /dev/null +++ b/crates/shared/primitives/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "base-primitives" +description = "Shared primitives and test utilities for node-reth" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +default = [] +test-utils = [ + "dep:alloy-consensus", + "dep:alloy-contract", + "dep:alloy-eips", + "dep:alloy-genesis", + "dep:alloy-signer", + "dep:alloy-signer-local", + "dep:alloy-sol-macro", + "dep:alloy-sol-types", + "dep:eyre", + "dep:op-alloy-network", + "dep:op-alloy-rpc-types", + "dep:serde_json", +] + +[dependencies] +alloy-primitives = { workspace = true, features = ["serde"] } + +# test-utils (optional) +alloy-consensus = { workspace = true, features = ["std"], optional = true } +alloy-contract = { workspace = true, optional = true } +alloy-eips = { workspace = true, optional = true } +alloy-genesis = { workspace = true, optional = true } +alloy-signer = { workspace = true, optional = true } +alloy-signer-local = { workspace = true, optional = true } +alloy-sol-macro = { workspace = true, features = ["json"], optional = true } +alloy-sol-types = { workspace = true, optional = true } +eyre = { workspace = true, optional = true } +op-alloy-network = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } diff --git a/crates/client/test-utils/contracts/.gitignore b/crates/shared/primitives/contracts/.gitignore similarity index 100% rename from crates/client/test-utils/contracts/.gitignore rename to crates/shared/primitives/contracts/.gitignore diff --git a/crates/client/test-utils/contracts/README.md b/crates/shared/primitives/contracts/README.md similarity index 100% rename from crates/client/test-utils/contracts/README.md rename to crates/shared/primitives/contracts/README.md diff --git a/crates/client/test-utils/contracts/foundry.lock b/crates/shared/primitives/contracts/foundry.lock similarity index 100% rename from crates/client/test-utils/contracts/foundry.lock rename to crates/shared/primitives/contracts/foundry.lock diff --git a/crates/client/test-utils/contracts/foundry.toml b/crates/shared/primitives/contracts/foundry.toml similarity index 100% rename from crates/client/test-utils/contracts/foundry.toml rename to crates/shared/primitives/contracts/foundry.toml diff --git a/crates/client/test-utils/contracts/lib/forge-std b/crates/shared/primitives/contracts/lib/forge-std similarity index 100% rename from crates/client/test-utils/contracts/lib/forge-std rename to crates/shared/primitives/contracts/lib/forge-std diff --git a/crates/client/test-utils/contracts/lib/openzeppelin-contracts b/crates/shared/primitives/contracts/lib/openzeppelin-contracts similarity index 100% rename from crates/client/test-utils/contracts/lib/openzeppelin-contracts rename to crates/shared/primitives/contracts/lib/openzeppelin-contracts diff --git a/crates/client/test-utils/contracts/lib/solmate b/crates/shared/primitives/contracts/lib/solmate similarity index 100% rename from crates/client/test-utils/contracts/lib/solmate rename to crates/shared/primitives/contracts/lib/solmate diff --git a/crates/client/test-utils/contracts/script/DeployDoubleCounter.s.sol b/crates/shared/primitives/contracts/script/DeployDoubleCounter.s.sol similarity index 100% rename from crates/client/test-utils/contracts/script/DeployDoubleCounter.s.sol rename to crates/shared/primitives/contracts/script/DeployDoubleCounter.s.sol diff --git a/crates/client/test-utils/contracts/script/DeployERC20.s.sol b/crates/shared/primitives/contracts/script/DeployERC20.s.sol similarity index 100% rename from crates/client/test-utils/contracts/script/DeployERC20.s.sol rename to crates/shared/primitives/contracts/script/DeployERC20.s.sol diff --git a/crates/client/test-utils/contracts/src/AccessList.sol b/crates/shared/primitives/contracts/src/AccessList.sol similarity index 100% rename from crates/client/test-utils/contracts/src/AccessList.sol rename to crates/shared/primitives/contracts/src/AccessList.sol diff --git a/crates/client/test-utils/contracts/src/ContractFactory.sol b/crates/shared/primitives/contracts/src/ContractFactory.sol similarity index 100% rename from crates/client/test-utils/contracts/src/ContractFactory.sol rename to crates/shared/primitives/contracts/src/ContractFactory.sol diff --git a/crates/client/test-utils/contracts/src/DoubleCounter.sol b/crates/shared/primitives/contracts/src/DoubleCounter.sol similarity index 100% rename from crates/client/test-utils/contracts/src/DoubleCounter.sol rename to crates/shared/primitives/contracts/src/DoubleCounter.sol diff --git a/crates/client/test-utils/contracts/src/Minimal7702Account.sol b/crates/shared/primitives/contracts/src/Minimal7702Account.sol similarity index 100% rename from crates/client/test-utils/contracts/src/Minimal7702Account.sol rename to crates/shared/primitives/contracts/src/Minimal7702Account.sol diff --git a/crates/client/test-utils/contracts/src/Proxy.sol b/crates/shared/primitives/contracts/src/Proxy.sol similarity index 100% rename from crates/client/test-utils/contracts/src/Proxy.sol rename to crates/shared/primitives/contracts/src/Proxy.sol diff --git a/crates/shared/primitives/src/lib.rs b/crates/shared/primitives/src/lib.rs new file mode 100644 index 00000000..3c53919f --- /dev/null +++ b/crates/shared/primitives/src/lib.rs @@ -0,0 +1,9 @@ +//! Shared primitives and test utilities for node-reth crates. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +#[cfg(any(test, feature = "test-utils"))] +pub mod test_utils; + +#[cfg(any(test, feature = "test-utils"))] +pub use test_utils::*; diff --git a/crates/client/test-utils/src/accounts.rs b/crates/shared/primitives/src/test_utils/accounts.rs similarity index 85% rename from crates/client/test-utils/src/accounts.rs rename to crates/shared/primitives/src/test_utils/accounts.rs index 651fd77b..a67024e7 100644 --- a/crates/client/test-utils/src/accounts.rs +++ b/crates/shared/primitives/src/test_utils/accounts.rs @@ -5,12 +5,14 @@ use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{Address, B256, Bytes, FixedBytes, TxHash, address, hex}; use alloy_signer::SignerSync; use alloy_signer_local::PrivateKeySigner; -use eyre::Result; +use eyre::{Result, eyre}; use op_alloy_network::TransactionBuilder; use op_alloy_rpc_types::OpTransactionRequest; -use reth::{revm::context::TransactionType, rpc::compat::SignTxRequestError}; -use crate::BASE_CHAIN_ID; +use super::DEVNET_CHAIN_ID; + +/// EIP-1559 transaction type constant. +const EIP1559_TX_TYPE: u8 = 2; /// Hardcoded test accounts using Anvil's deterministic keys. /// Derived from the test mnemonic: "test test test test test test test test test test test junk" @@ -77,42 +79,38 @@ impl Account { ) -> Result<(Bytes, Address, TxHash)> { let tx_request = OpTransactionRequest::default() .from(self.address()) - .transaction_type(TransactionType::Eip1559.into()) + .transaction_type(EIP1559_TX_TYPE) .with_gas_limit(3_000_000) .with_max_fee_per_gas(1_000_000_000) .with_max_priority_fee_per_gas(0) - .with_chain_id(BASE_CHAIN_ID) + .with_chain_id(DEVNET_CHAIN_ID) .with_deploy_code(bytecode) .with_nonce(nonce); - let tx = tx_request - .build_typed_tx() - .map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + let tx = tx_request.build_typed_tx().map_err(|_| eyre!("invalid transaction request"))?; let signature = self.signer().sign_hash_sync(&tx.signature_hash())?; let signed_tx = tx.into_signed(signature); let signed_tx_bytes = signed_tx.encoded_2718().into(); let contract_address = self.address().create(signed_tx.nonce()); - Ok((signed_tx_bytes, contract_address, signed_tx.hash().clone())) + Ok((signed_tx_bytes, contract_address, *signed_tx.hash())) } /// Sign a TransactionRequest and return the signed bytes. pub fn sign_txn_request(&self, tx_request: OpTransactionRequest) -> Result<(Bytes, TxHash)> { let tx_request = tx_request .from(self.address()) - .transaction_type(TransactionType::Eip1559.into()) + .transaction_type(EIP1559_TX_TYPE) .with_gas_limit(500_000) - .with_chain_id(BASE_CHAIN_ID) + .with_chain_id(DEVNET_CHAIN_ID) .with_max_fee_per_gas(1_000_000_000) .with_max_priority_fee_per_gas(0); - let tx = tx_request - .build_typed_tx() - .map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; + let tx = tx_request.build_typed_tx().map_err(|_| eyre!("invalid transaction request"))?; let signature = self.signer().sign_hash_sync(&tx.signature_hash())?; let signed_tx = tx.into_signed(signature); let signed_tx_bytes = signed_tx.encoded_2718().into(); let tx_hash = signed_tx.hash(); - Ok((signed_tx_bytes, tx_hash.clone())) + Ok((signed_tx_bytes, *tx_hash)) } } diff --git a/crates/client/test-utils/src/contracts.rs b/crates/shared/primitives/src/test_utils/contracts.rs similarity index 53% rename from crates/client/test-utils/src/contracts.rs rename to crates/shared/primitives/src/test_utils/contracts.rs index c0bc4332..fe4214e6 100644 --- a/crates/client/test-utils/src/contracts.rs +++ b/crates/shared/primitives/src/test_utils/contracts.rs @@ -2,11 +2,6 @@ //! //! This module provides pre-compiled contract bindings that can be used //! across different test crates without needing relative path references. -//! -//! Contract sources: -//! - `DoubleCounter`: Custom test contract (src/DoubleCounter.sol) -//! - `MockERC20`: Solmate's MockERC20 (lib/solmate) -//! - `TransparentUpgradeableProxy`: OpenZeppelin's TransparentUpgradeableProxy (lib/openzeppelin-contracts) use alloy_sol_macro::sol; @@ -39,3 +34,39 @@ sol!( "/contracts/out/Minimal7702Account.sol/Minimal7702Account.json" ) ); + +sol!( + #[sol(rpc)] + AccessListContract, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/AccessList.sol/AccessList.json") +); + +sol!( + #[sol(rpc)] + ContractFactory, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/ContractFactory.sol/ContractFactory.json") +); + +sol!( + #[sol(rpc)] + SimpleStorage, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/ContractFactory.sol/SimpleStorage.json") +); + +sol!( + #[sol(rpc)] + Proxy, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/Proxy.sol/Proxy.json") +); + +sol!( + #[sol(rpc)] + Logic, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/Proxy.sol/Logic.json") +); + +sol!( + #[sol(rpc)] + Logic2, + concat!(env!("CARGO_MANIFEST_DIR"), "/contracts/out/Proxy.sol/Logic2.json") +); diff --git a/crates/shared/primitives/src/test_utils/genesis.rs b/crates/shared/primitives/src/test_utils/genesis.rs new file mode 100644 index 00000000..cd4f5902 --- /dev/null +++ b/crates/shared/primitives/src/test_utils/genesis.rs @@ -0,0 +1,96 @@ +//! Genesis configuration utilities for testing. + +use std::collections::BTreeMap; + +use alloy_genesis::{ChainConfig, Genesis, GenesisAccount}; +use alloy_primitives::{Address, B256, Bytes, U256, utils::parse_ether}; + +use super::Account; + +/// Chain ID for devnet test network. +pub const DEVNET_CHAIN_ID: u64 = 84538453; + +/// Builds a test genesis configuration programmatically. +/// +/// Creates a Base Sepolia-like genesis with: +/// - All EVM and OP hardforks enabled from genesis +/// - Optimism EIP-1559 settings (elasticity=6, denominator=50) +/// - Pre-funded test accounts from the `Account` enum +pub fn build_test_genesis() -> Genesis { + // OP EIP-1559 base fee parameters + const EIP1559_ELASTICITY: u64 = 6; + const EIP1559_DENOMINATOR: u64 = 50; + + // Test account balance: 1 million ETH + let test_account_balance: U256 = parse_ether("1000000").expect("valid ether amount"); + + // Build chain config with all hardforks enabled at genesis + let config = ChainConfig { + chain_id: DEVNET_CHAIN_ID, + // Block-based EVM hardforks (all at block 0) + homestead_block: Some(0), + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + muir_glacier_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + arrow_glacier_block: Some(0), + gray_glacier_block: Some(0), + merge_netsplit_block: Some(0), + // Time-based hardforks + shanghai_time: Some(0), + cancun_time: Some(0), + prague_time: Some(0), + // Post-merge settings + terminal_total_difficulty: Some(U256::ZERO), + terminal_total_difficulty_passed: true, + // OP-specific hardforks and settings via extra_fields + extra_fields: [ + ("bedrockBlock", serde_json::json!(0)), + ("regolithTime", serde_json::json!(0)), + ("canyonTime", serde_json::json!(0)), + ("ecotoneTime", serde_json::json!(0)), + ("fjordTime", serde_json::json!(0)), + ("graniteTime", serde_json::json!(0)), + ("isthmusTime", serde_json::json!(0)), + ("jovianTime", serde_json::json!(0)), + ( + "optimism", + serde_json::json!({ + "eip1559Elasticity": EIP1559_ELASTICITY, + "eip1559Denominator": EIP1559_DENOMINATOR + }), + ), + ] + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(), + ..Default::default() + }; + + // Pre-fund all test accounts + let alloc: BTreeMap = Account::all() + .into_iter() + .map(|account| { + (account.address(), GenesisAccount::default().with_balance(test_account_balance)) + }) + .collect(); + + Genesis { + config, + alloc, + gas_limit: 100_000_000, + difficulty: U256::ZERO, + nonce: 0, + timestamp: 0, + extra_data: Bytes::from_static(&[0x00]), + mix_hash: B256::ZERO, + coinbase: Address::ZERO, + ..Default::default() + } +} diff --git a/crates/shared/primitives/src/test_utils/mod.rs b/crates/shared/primitives/src/test_utils/mod.rs new file mode 100644 index 00000000..8437cffd --- /dev/null +++ b/crates/shared/primitives/src/test_utils/mod.rs @@ -0,0 +1,13 @@ +//! Test utilities including accounts, genesis configuration, and contract bindings. + +mod accounts; +pub use accounts::Account; + +mod genesis; +pub use genesis::{DEVNET_CHAIN_ID, build_test_genesis}; + +mod contracts; +pub use contracts::{ + AccessListContract, ContractFactory, DoubleCounter, Logic, Logic2, Minimal7702Account, + MockERC20, Proxy, SimpleStorage, TransparentUpgradeableProxy, +}; diff --git a/lychee.toml b/lychee.toml index 885df032..2109d843 100644 --- a/lychee.toml +++ b/lychee.toml @@ -3,7 +3,7 @@ no_progress = false exclude_all_private = false accept = [200, 403] # 403 status code is often returned by private repos instead of 404 exclude_path = [ - "client/test-utils/contracts/lib" + "shared/primitives/contracts/lib" ] exclude = [ 'foo.bar', From 571c1db97c19ae652d5933f1bb1544951c80cd09 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 22:33:14 -0600 Subject: [PATCH 253/262] chore: move FB test harness to flashblocks crate --- Cargo.lock | 8 +- crates/client/flashblocks/Cargo.toml | 10 +- crates/client/flashblocks/src/lib.rs | 3 + crates/client/flashblocks/src/test_harness.rs | 318 ++++++++++++++++++ .../client/flashblocks/tests/eip7702_tests.rs | 5 +- .../flashblocks/tests/eth_call_erc20.rs | 5 +- .../flashblocks/tests/flashblocks_rpc.rs | 3 +- crates/client/flashblocks/tests/state.rs | 7 +- crates/client/test-utils/Cargo.toml | 6 - .../test-utils/src/flashblocks_harness.rs | 72 ---- crates/client/test-utils/src/harness.rs | 2 +- crates/client/test-utils/src/lib.rs | 8 +- crates/client/test-utils/src/node.rs | 245 +------------- 13 files changed, 346 insertions(+), 346 deletions(-) create mode 100644 crates/client/flashblocks/src/test_harness.rs delete mode 100644 crates/client/test-utils/src/flashblocks_harness.rs diff --git a/Cargo.lock b/Cargo.lock index 705714ea..c60d104e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1586,9 +1586,11 @@ dependencies = [ "alloy-sol-types", "arc-swap", "base-client-primitives", + "base-flashblocks", "base-flashtypes", "base-test-utils", "criterion", + "derive_more", "eyre", "futures-util", "jsonrpsee", @@ -1735,19 +1737,14 @@ dependencies = [ "alloy-rpc-types-engine", "alloy-signer", "base-client-primitives", - "base-flashblocks", - "base-flashtypes", "base-primitives", "chrono", - "derive_more", "eyre", "jsonrpsee", - "once_cell", "op-alloy-network", "op-alloy-rpc-types-engine", "reth", "reth-db", - "reth-exex", "reth-ipc", "reth-node-core", "reth-optimism-chainspec", @@ -1759,7 +1756,6 @@ dependencies = [ "reth-rpc-layer", "reth-tracing", "tokio", - "tokio-stream", "tower", "tracing-subscriber 0.3.22", "url", diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index 41ebacb5..01813dc6 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -11,6 +11,9 @@ repository.workspace = true [lints] workspace = true +[features] +test-utils = [ "base-test-utils", "derive_more", "eyre" ] + [dependencies] # workspace base-flashtypes.workspace = true @@ -67,7 +70,13 @@ arc-swap.workspace = true metrics-derive.workspace = true rayon.workspace = true +# Optional test-utils dependencies +base-test-utils = { workspace = true, optional = true } +derive_more = { workspace = true, features = ["deref"], optional = true } +eyre = { workspace = true, optional = true } + [dev-dependencies] +base-flashblocks = { path = ".", features = ["test-utils"] } rstest.workspace = true rand.workspace = true eyre.workspace = true @@ -87,7 +96,6 @@ reth-optimism-node.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } tokio-tungstenite.workspace = true tracing-subscriber.workspace = true -base-test-utils.workspace = true serde_json.workspace = true futures-util.workspace = true criterion = { version = "0.5", features = ["async_tokio"] } diff --git a/crates/client/flashblocks/src/lib.rs b/crates/client/flashblocks/src/lib.rs index dbed7230..06f1f2b0 100644 --- a/crates/client/flashblocks/src/lib.rs +++ b/crates/client/flashblocks/src/lib.rs @@ -46,3 +46,6 @@ pub use rpc::{ mod extension; pub use extension::{FlashblocksCell, FlashblocksConfig, FlashblocksExtension}; + +#[cfg(any(test, feature = "test-utils"))] +pub mod test_harness; diff --git a/crates/client/flashblocks/src/test_harness.rs b/crates/client/flashblocks/src/test_harness.rs new file mode 100644 index 00000000..a5a96704 --- /dev/null +++ b/crates/client/flashblocks/src/test_harness.rs @@ -0,0 +1,318 @@ +//! Flashblocks test harness module. +//! +//! Provides test utilities for flashblocks including: +//! - [`FlashblocksHarness`] - High-level test harness wrapping [`TestHarness`] +//! - [`FlashblocksParts`] - Components for interacting with flashblocks worker tasks +//! - [`FlashblocksTestExtension`] - Node extension for wiring up flashblocks in tests +//! - [`FlashblocksLocalNode`] - Local node wrapper with flashblocks helpers + +use std::{ + fmt, + sync::{Arc, Mutex}, + time::Duration, +}; + +use base_client_primitives::BaseNodeExtension; +use base_flashtypes::Flashblock; +use base_test_utils::{ + LocalNode, LocalNodeProvider, NODE_STARTUP_DELAY_MS, TestHarness, build_test_genesis, + init_silenced_tracing, +}; +use derive_more::Deref; +use eyre::Result; +use once_cell::sync::OnceCell; +use reth::providers::CanonStateSubscriptions; +use reth_optimism_chainspec::OpChainSpec; +use tokio::sync::{mpsc, oneshot}; +use tokio_stream::StreamExt; + +use crate::{ + EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksReceiver, + FlashblocksState, +}; + +/// Convenience alias for the Flashblocks state backing the local node. +pub type LocalFlashblocksState = FlashblocksState; + +/// Components that allow tests to interact with the Flashblocks worker tasks. +#[derive(Clone)] +pub struct FlashblocksParts { + sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, + state: Arc, +} + +impl fmt::Debug for FlashblocksParts { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlashblocksParts").finish_non_exhaustive() + } +} + +impl FlashblocksParts { + /// Clone the shared [`FlashblocksState`] handle. + pub fn state(&self) -> Arc { + self.state.clone() + } + + /// Send a flashblock to the background processor and wait until it is handled. + pub async fn send(&self, flashblock: Flashblock) -> Result<()> { + let (tx, rx) = oneshot::channel(); + self.sender.send((flashblock, tx)).await.map_err(|err| eyre::eyre!(err))?; + rx.await.map_err(|err| eyre::eyre!(err))?; + Ok(()) + } +} + +/// Test extension for flashblocks functionality. +/// +/// This extension wires up the flashblocks ExEx and RPC modules for testing, +/// with optional control over canonical block processing. +#[derive(Clone, Debug)] +pub struct FlashblocksTestExtension { + inner: Arc, +} + +struct FlashblocksTestExtensionInner { + sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, + #[allow(clippy::type_complexity)] + receiver: Arc)>>>>, + fb_cell: Arc>>, + process_canonical: bool, +} + +impl fmt::Debug for FlashblocksTestExtensionInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlashblocksTestExtensionInner") + .field("process_canonical", &self.process_canonical) + .finish_non_exhaustive() + } +} + +impl FlashblocksTestExtension { + /// Create a new flashblocks test extension. + /// + /// If `process_canonical` is true, canonical blocks are automatically processed. + /// Set to false for tests that need manual control over canonical block timing. + pub fn new(process_canonical: bool) -> Self { + let (sender, receiver) = mpsc::channel::<(Flashblock, oneshot::Sender<()>)>(100); + let inner = FlashblocksTestExtensionInner { + sender, + receiver: Arc::new(Mutex::new(Some(receiver))), + fb_cell: Arc::new(OnceCell::new()), + process_canonical, + }; + Self { inner: Arc::new(inner) } + } + + /// Get the flashblocks parts after the node has been launched. + pub fn parts(&self) -> Result { + let state = self.inner.fb_cell.get().ok_or_else(|| { + eyre::eyre!("FlashblocksState should be initialized during node launch") + })?; + Ok(FlashblocksParts { sender: self.inner.sender.clone(), state: state.clone() }) + } +} + +impl BaseNodeExtension for FlashblocksTestExtension { + fn apply( + self: Box, + builder: base_client_primitives::OpBuilder, + ) -> base_client_primitives::OpBuilder { + let fb_cell = self.inner.fb_cell.clone(); + let receiver = self.inner.receiver.clone(); + let process_canonical = self.inner.process_canonical; + + let fb_cell_for_exex = fb_cell.clone(); + + builder + .install_exex("flashblocks-canon", move |mut ctx| { + let fb_cell = fb_cell_for_exex.clone(); + async move { + let provider = ctx.provider().clone(); + let fb = init_flashblocks_state(&fb_cell, &provider); + Ok(async move { + use reth_exex::ExExEvent; + while let Some(note) = ctx.notifications.try_next().await? { + if let Some(committed) = note.committed_chain() { + let hash = committed.tip().num_hash(); + if process_canonical { + // Many suites drive canonical updates manually to reproduce race conditions, so + // allowing this to be disabled keeps canonical replay deterministic. + let chain = Arc::unwrap_or_clone(committed); + for (_, block) in chain.into_blocks() { + fb.on_canonical_block_received(block); + } + } + let _ = ctx.events.send(ExExEvent::FinishedHeight(hash)); + } + } + Ok(()) + }) + } + }) + .extend_rpc_modules(move |ctx| { + let fb_cell = fb_cell.clone(); + let provider = ctx.provider().clone(); + let fb = init_flashblocks_state(&fb_cell, &provider); + + let mut canon_stream = tokio_stream::wrappers::BroadcastStream::new( + ctx.provider().subscribe_to_canonical_state(), + ); + tokio::spawn(async move { + use tokio_stream::StreamExt; + while let Some(Ok(notification)) = canon_stream.next().await { + provider.canonical_in_memory_state().notify_canon_state(notification); + } + }); + let api_ext = EthApiExt::new( + ctx.registry.eth_api().clone(), + ctx.registry.eth_handlers().filter.clone(), + fb.clone(), + ); + ctx.modules.replace_configured(api_ext.into_rpc())?; + + // Register eth_subscribe subscription endpoint for flashblocks + // Uses replace_configured since eth_subscribe already exists from reth's standard module + // Pass eth_api to enable proxying standard subscription types to reth's implementation + let eth_pubsub = EthPubSub::new(ctx.registry.eth_api().clone(), fb.clone()); + ctx.modules.replace_configured(eth_pubsub.into_rpc())?; + + let fb_for_task = fb.clone(); + let mut receiver = receiver + .lock() + .expect("flashblock receiver mutex poisoned") + .take() + .expect("flashblock receiver should only be initialized once"); + tokio::spawn(async move { + while let Some((payload, tx)) = receiver.recv().await { + fb_for_task.on_flashblock_received(payload); + let _ = tx.send(()); + } + }); + + Ok(()) + }) + } +} + +fn init_flashblocks_state( + cell: &Arc>>, + provider: &LocalNodeProvider, +) -> Arc { + cell.get_or_init(|| { + let fb = Arc::new(FlashblocksState::new(provider.clone(), 5)); + fb.start(); + fb + }) + .clone() +} + +/// Local node wrapper that exposes helpers specific to Flashblocks tests. +pub struct FlashblocksLocalNode { + node: LocalNode, + parts: FlashblocksParts, +} + +impl fmt::Debug for FlashblocksLocalNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlashblocksLocalNode") + .field("node", &self.node) + .field("parts", &self.parts) + .finish() + } +} + +impl FlashblocksLocalNode { + /// Launch a flashblocks-enabled node using the default configuration. + pub async fn new() -> Result { + Self::with_options(true).await + } + + /// Builds a flashblocks-enabled node with canonical block streaming disabled so tests can call + /// `FlashblocksState::on_canonical_block_received` at precise points. + pub async fn manual_canonical() -> Result { + Self::with_options(false).await + } + + async fn with_options(process_canonical: bool) -> Result { + // Build default chain spec programmatically + let genesis = build_test_genesis(); + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + + let extension = FlashblocksTestExtension::new(process_canonical); + let parts_source = extension.clone(); + let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; + let parts = parts_source.parts()?; + Ok(Self { node, parts }) + } + + /// Access the shared Flashblocks state for assertions or manual driving. + pub fn flashblocks_state(&self) -> Arc { + self.parts.state() + } + + /// Send a flashblock through the background processor and await completion. + pub async fn send_flashblock(&self, flashblock: Flashblock) -> Result<()> { + self.parts.send(flashblock).await + } + + /// Split the wrapper into the underlying node plus flashblocks parts. + pub fn into_parts(self) -> (LocalNode, FlashblocksParts) { + (self.node, self.parts) + } +} + +/// Helper that exposes [`TestHarness`] conveniences plus Flashblocks helpers. +#[derive(Debug, Deref)] +pub struct FlashblocksHarness { + #[deref] + inner: TestHarness, + parts: FlashblocksParts, +} + +impl FlashblocksHarness { + /// Launch a flashblocks-enabled harness with automatic canonical processing. + pub async fn new() -> Result { + Self::with_options(true).await + } + + /// Launch the harness configured for manual canonical progression. + pub async fn manual_canonical() -> Result { + Self::with_options(false).await + } + + /// Get a handle to the in-memory Flashblocks state backing the harness. + pub fn flashblocks_state(&self) -> Arc { + self.parts.state() + } + + /// Send a single flashblock through the harness. + pub async fn send_flashblock(&self, flashblock: Flashblock) -> Result<()> { + self.parts.send(flashblock).await + } + + async fn with_options(process_canonical: bool) -> Result { + init_silenced_tracing(); + + // Build default chain spec programmatically + let genesis = build_test_genesis(); + let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); + + // Create the extension and keep a reference to get parts after launch + let extension = FlashblocksTestExtension::new(process_canonical); + let parts_source = extension.clone(); + + // Launch the node with the flashblocks extension + let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; + let engine = node.engine_api()?; + + tokio::time::sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; + + // Get the parts from the extension after node launch + let parts = parts_source.parts()?; + + // Create harness by building it directly (avoiding TestHarnessBuilder since we already have node) + let inner = TestHarness::from_parts(node, engine); + + Ok(Self { inner, parts }) + } +} diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index 4ddd8965..ab463d1f 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -8,12 +8,11 @@ use alloy_eips::{eip2718::Encodable2718, eip7702::Authorization}; use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_sol_types::SolCall; +use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{ - Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync, -}; +use base_test_utils::{Account, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync}; use eyre::Result; use op_alloy_network::ReceiptResponse; diff --git a/crates/client/flashblocks/tests/eth_call_erc20.rs b/crates/client/flashblocks/tests/eth_call_erc20.rs index cb833701..55b4317b 100644 --- a/crates/client/flashblocks/tests/eth_call_erc20.rs +++ b/crates/client/flashblocks/tests/eth_call_erc20.rs @@ -16,12 +16,11 @@ use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_rpc_types_engine::PayloadId; use alloy_sol_types::{SolConstructor, SolValue}; +use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{ - Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, -}; +use base_test_utils::{Account, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy}; use eyre::Result; struct Erc20TestSetup { harness: FlashblocksHarness, diff --git a/crates/client/flashblocks/tests/flashblocks_rpc.rs b/crates/client/flashblocks/tests/flashblocks_rpc.rs index 3544af1e..e7d6fc57 100644 --- a/crates/client/flashblocks/tests/flashblocks_rpc.rs +++ b/crates/client/flashblocks/tests/flashblocks_rpc.rs @@ -11,10 +11,11 @@ use alloy_rpc_client::RpcClient; use alloy_rpc_types::simulate::{SimBlock, SimulatePayload}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; +use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, DoubleCounter, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX}; +use base_test_utils::{Account, DoubleCounter, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; diff --git a/crates/client/flashblocks/tests/state.rs b/crates/client/flashblocks/tests/state.rs index 25068b0e..d6321351 100644 --- a/crates/client/flashblocks/tests/state.rs +++ b/crates/client/flashblocks/tests/state.rs @@ -6,13 +6,14 @@ use alloy_consensus::{Receipt, Transaction}; use alloy_eips::{BlockHashOrNumber, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::HashMap}; use alloy_rpc_types_engine::PayloadId; -use base_flashblocks::{FlashblocksAPI, FlashblocksState, PendingBlocksAPI}; +use base_flashblocks::{ + FlashblocksAPI, FlashblocksState, PendingBlocksAPI, test_harness::FlashblocksHarness, +}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; use base_test_utils::{ - Account, FlashblocksHarness, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, - LocalNodeProvider, + Account, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, }; use op_alloy_consensus::OpDepositReceipt; use op_alloy_network::BlockResponse; diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml index 39fa52d3..3bd718e5 100644 --- a/crates/client/test-utils/Cargo.toml +++ b/crates/client/test-utils/Cargo.toml @@ -14,8 +14,6 @@ workspace = true [dependencies] # Project base-client-primitives.workspace = true -base-flashtypes.workspace = true -base-flashblocks.workspace = true base-primitives = { workspace = true, features = ["test-utils"] } # reth @@ -28,7 +26,6 @@ reth-provider.workspace = true reth-primitives-traits.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-node-core.workspace = true -reth-exex.workspace = true reth-tracing.workspace = true reth-rpc-layer.workspace = true reth-ipc.workspace = true @@ -49,16 +46,13 @@ op-alloy-network.workspace = true # tokio tokio.workspace = true -tokio-stream.workspace = true # rpc jsonrpsee.workspace = true # misc -derive_more = { workspace = true, features = ["deref"] } tracing-subscriber.workspace = true eyre.workspace = true -once_cell.workspace = true url.workspace = true chrono.workspace = true diff --git a/crates/client/test-utils/src/flashblocks_harness.rs b/crates/client/test-utils/src/flashblocks_harness.rs deleted file mode 100644 index d56df358..00000000 --- a/crates/client/test-utils/src/flashblocks_harness.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Flashblocks-aware wrapper around [`TestHarness`] that wires in the custom RPC modules. - -use std::{sync::Arc, time::Duration}; - -use base_flashtypes::Flashblock; -use derive_more::Deref; -use eyre::Result; -use reth_optimism_chainspec::OpChainSpec; -use tokio::time::sleep; - -use crate::{ - NODE_STARTUP_DELAY_MS, - harness::TestHarness, - init_silenced_tracing, - node::{FlashblocksParts, FlashblocksTestExtension, LocalFlashblocksState, LocalNode}, -}; - -/// Helper that exposes [`TestHarness`] conveniences plus Flashblocks helpers. -#[derive(Debug, Deref)] -pub struct FlashblocksHarness { - #[deref] - inner: TestHarness, - parts: FlashblocksParts, -} - -impl FlashblocksHarness { - /// Launch a flashblocks-enabled harness with automatic canonical processing. - pub async fn new() -> Result { - Self::with_options(true).await - } - - /// Launch the harness configured for manual canonical progression. - pub async fn manual_canonical() -> Result { - Self::with_options(false).await - } - - /// Get a handle to the in-memory Flashblocks state backing the harness. - pub fn flashblocks_state(&self) -> Arc { - self.parts.state() - } - - /// Send a single flashblock through the harness. - pub async fn send_flashblock(&self, flashblock: Flashblock) -> Result<()> { - self.parts.send(flashblock).await - } - - async fn with_options(process_canonical: bool) -> Result { - init_silenced_tracing(); - - // Build default chain spec programmatically - let genesis = crate::build_test_genesis(); - let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); - - // Create the extension and keep a reference to get parts after launch - let extension = FlashblocksTestExtension::new(process_canonical); - let parts_source = extension.clone(); - - // Launch the node with the flashblocks extension - let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; - let engine = node.engine_api()?; - - sleep(Duration::from_millis(NODE_STARTUP_DELAY_MS)).await; - - // Get the parts from the extension after node launch - let parts = parts_source.parts()?; - - // Create harness by building it directly (avoiding TestHarnessBuilder since we already have node) - let inner = TestHarness::from_parts(node, engine); - - Ok(Self { inner, parts }) - } -} diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/test-utils/src/harness.rs index 92dcf47d..3dad8758 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/test-utils/src/harness.rs @@ -95,7 +95,7 @@ impl TestHarness { /// Create a harness from pre-built parts. /// /// This is useful when you need to capture extension state before building the harness. - pub(crate) fn from_parts(node: LocalNode, engine: EngineApi) -> Self { + pub fn from_parts(node: LocalNode, engine: EngineApi) -> Self { Self { node, engine } } diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/test-utils/src/lib.rs index 1ab87bfc..18ef3dea 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/test-utils/src/lib.rs @@ -22,19 +22,13 @@ pub use engine::{EngineAddress, EngineApi, EngineProtocol, HttpEngine, IpcEngine mod fixtures; pub use fixtures::{create_provider_factory, load_chain_spec}; -mod flashblocks_harness; -pub use flashblocks_harness::FlashblocksHarness; - mod harness; pub use harness::{TestHarness, TestHarnessBuilder}; mod node; // Re-export BaseNodeExtension for extension authors pub use base_client_primitives::BaseNodeExtension; -pub use node::{ - FlashblocksLocalNode, FlashblocksParts, FlashblocksTestExtension, LocalFlashblocksState, - LocalNode, LocalNodeProvider, -}; +pub use node::{LocalNode, LocalNodeProvider}; mod tracing; // Re-export signer traits for use in tests diff --git a/crates/client/test-utils/src/node.rs b/crates/client/test-utils/src/node.rs index c06bc86e..9c3c64f5 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/test-utils/src/node.rs @@ -1,23 +1,11 @@ //! Local node setup with Base Sepolia chainspec -use std::{ - any::Any, - fmt, - net::SocketAddr, - path::PathBuf, - sync::{Arc, Mutex}, -}; +use std::{any::Any, fmt, net::SocketAddr, path::PathBuf, sync::Arc}; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; -use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; -use base_flashblocks::{ - EthApiExt, EthApiOverrideServer, EthPubSub, EthPubSubApiServer, FlashblocksReceiver, - FlashblocksState, -}; -use base_flashtypes::Flashblock; +use base_client_primitives::{BaseNodeExtension, OpProvider}; use eyre::Result; -use once_cell::sync::OnceCell; use op_alloy_network::Optimism; use reth::{ args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, @@ -29,23 +17,17 @@ use reth::{ use reth_db::{ ClientVersion, DatabaseEnv, init_db, mdbx::DatabaseArguments, test_utils::tempdir_path, }; -use reth_exex::ExExEvent; use reth_node_core::{ args::DatadirArgs, dirs::{DataDirPath, MaybePlatformPath}, }; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::{OpNode, args::RollupArgs}; -use reth_provider::CanonStateSubscriptions; -use tokio::sync::{mpsc, oneshot}; -use tokio_stream::StreamExt; use crate::engine::EngineApi; /// Convenience alias for the local blockchain provider type. pub type LocalNodeProvider = OpProvider; -/// Convenience alias for the Flashblocks state backing the local node. -pub type LocalFlashblocksState = FlashblocksState; /// Handle to a launched local node along with the resources required to keep it alive. pub struct LocalNode { @@ -76,162 +58,6 @@ impl fmt::Debug for LocalNode { } } -/// Components that allow tests to interact with the Flashblocks worker tasks. -#[derive(Clone)] -pub struct FlashblocksParts { - sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, - state: Arc, -} - -impl fmt::Debug for FlashblocksParts { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlashblocksParts").finish_non_exhaustive() - } -} - -impl FlashblocksParts { - /// Clone the shared [`FlashblocksState`] handle. - pub fn state(&self) -> Arc { - self.state.clone() - } - - /// Send a flashblock to the background processor and wait until it is handled. - pub async fn send(&self, flashblock: Flashblock) -> Result<()> { - let (tx, rx) = oneshot::channel(); - self.sender.send((flashblock, tx)).await.map_err(|err| eyre::eyre!(err))?; - rx.await.map_err(|err| eyre::eyre!(err))?; - Ok(()) - } -} - -/// Test extension for flashblocks functionality. -/// -/// This extension wires up the flashblocks ExEx and RPC modules for testing, -/// with optional control over canonical block processing. -#[derive(Clone, Debug)] -pub struct FlashblocksTestExtension { - inner: Arc, -} - -struct FlashblocksTestExtensionInner { - sender: mpsc::Sender<(Flashblock, oneshot::Sender<()>)>, - #[allow(clippy::type_complexity)] - receiver: Arc)>>>>, - fb_cell: Arc>>, - process_canonical: bool, -} - -impl fmt::Debug for FlashblocksTestExtensionInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlashblocksTestExtensionInner") - .field("process_canonical", &self.process_canonical) - .finish_non_exhaustive() - } -} - -impl FlashblocksTestExtension { - /// Create a new flashblocks test extension. - /// - /// If `process_canonical` is true, canonical blocks are automatically processed. - /// Set to false for tests that need manual control over canonical block timing. - pub fn new(process_canonical: bool) -> Self { - let (sender, receiver) = mpsc::channel::<(Flashblock, oneshot::Sender<()>)>(100); - let inner = FlashblocksTestExtensionInner { - sender, - receiver: Arc::new(Mutex::new(Some(receiver))), - fb_cell: Arc::new(OnceCell::new()), - process_canonical, - }; - Self { inner: Arc::new(inner) } - } - - /// Get the flashblocks parts after the node has been launched. - pub fn parts(&self) -> Result { - let state = self.inner.fb_cell.get().ok_or_else(|| { - eyre::eyre!("FlashblocksState should be initialized during node launch") - })?; - Ok(FlashblocksParts { sender: self.inner.sender.clone(), state: state.clone() }) - } -} - -impl BaseNodeExtension for FlashblocksTestExtension { - fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let fb_cell = self.inner.fb_cell.clone(); - let receiver = self.inner.receiver.clone(); - let process_canonical = self.inner.process_canonical; - - let fb_cell_for_exex = fb_cell.clone(); - - builder - .install_exex("flashblocks-canon", move |mut ctx| { - let fb_cell = fb_cell_for_exex.clone(); - async move { - let provider = ctx.provider().clone(); - let fb = init_flashblocks_state(&fb_cell, &provider); - Ok(async move { - while let Some(note) = ctx.notifications.try_next().await? { - if let Some(committed) = note.committed_chain() { - let hash = committed.tip().num_hash(); - if process_canonical { - // Many suites drive canonical updates manually to reproduce race conditions, so - // allowing this to be disabled keeps canonical replay deterministic. - let chain = Arc::unwrap_or_clone(committed); - for (_, block) in chain.into_blocks() { - fb.on_canonical_block_received(block); - } - } - let _ = ctx.events.send(ExExEvent::FinishedHeight(hash)); - } - } - Ok(()) - }) - } - }) - .extend_rpc_modules(move |ctx| { - let fb_cell = fb_cell.clone(); - let provider = ctx.provider().clone(); - let fb = init_flashblocks_state(&fb_cell, &provider); - - let mut canon_stream = tokio_stream::wrappers::BroadcastStream::new( - ctx.provider().subscribe_to_canonical_state(), - ); - tokio::spawn(async move { - use tokio_stream::StreamExt; - while let Some(Ok(notification)) = canon_stream.next().await { - provider.canonical_in_memory_state().notify_canon_state(notification); - } - }); - let api_ext = EthApiExt::new( - ctx.registry.eth_api().clone(), - ctx.registry.eth_handlers().filter.clone(), - fb.clone(), - ); - ctx.modules.replace_configured(api_ext.into_rpc())?; - - // Register eth_subscribe subscription endpoint for flashblocks - // Uses replace_configured since eth_subscribe already exists from reth's standard module - // Pass eth_api to enable proxying standard subscription types to reth's implementation - let eth_pubsub = EthPubSub::new(ctx.registry.eth_api().clone(), fb.clone()); - ctx.modules.replace_configured(eth_pubsub.into_rpc())?; - - let fb_for_task = fb.clone(); - let mut receiver = receiver - .lock() - .expect("flashblock receiver mutex poisoned") - .take() - .expect("flashblock receiver should only be initialized once"); - tokio::spawn(async move { - while let Some((payload, tx)) = receiver.recv().await { - fb_for_task.on_flashblock_received(payload); - let _ = tx.send(()); - } - }); - - Ok(()) - }) - } -} - impl LocalNode { /// Launch a new local node with the provided extensions and chain spec. pub async fn new( @@ -357,70 +183,3 @@ impl LocalNode { format!("ws://{}", self.ws_api_addr) } } - -fn init_flashblocks_state( - cell: &Arc>>, - provider: &LocalNodeProvider, -) -> Arc { - cell.get_or_init(|| { - let fb = Arc::new(FlashblocksState::new(provider.clone(), 5)); - fb.start(); - fb - }) - .clone() -} - -/// Local node wrapper that exposes helpers specific to Flashblocks tests. -pub struct FlashblocksLocalNode { - node: LocalNode, - parts: FlashblocksParts, -} - -impl fmt::Debug for FlashblocksLocalNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlashblocksLocalNode") - .field("node", &self.node) - .field("parts", &self.parts) - .finish() - } -} - -impl FlashblocksLocalNode { - /// Launch a flashblocks-enabled node using the default configuration. - pub async fn new() -> Result { - Self::with_options(true).await - } - - /// Builds a flashblocks-enabled node with canonical block streaming disabled so tests can call - /// `FlashblocksState::on_canonical_block_received` at precise points. - pub async fn manual_canonical() -> Result { - Self::with_options(false).await - } - - async fn with_options(process_canonical: bool) -> Result { - // Build default chain spec programmatically - let genesis = crate::build_test_genesis(); - let chain_spec = Arc::new(OpChainSpec::from_genesis(genesis)); - - let extension = FlashblocksTestExtension::new(process_canonical); - let parts_source = extension.clone(); - let node = LocalNode::new(vec![Box::new(extension)], chain_spec).await?; - let parts = parts_source.parts()?; - Ok(Self { node, parts }) - } - - /// Access the shared Flashblocks state for assertions or manual driving. - pub fn flashblocks_state(&self) -> Arc { - self.parts.state() - } - - /// Send a flashblock through the background processor and await completion. - pub async fn send_flashblock(&self, flashblock: Flashblock) -> Result<()> { - self.parts.send(flashblock).await - } - - /// Split the wrapper into the underlying node plus flashblocks parts. - pub fn into_parts(self) -> (LocalNode, FlashblocksParts) { - (self.node, self.parts) - } -} From 3ceb99508aab938a4c6a7ab1e4474ca3fa7d64b6 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 22:50:32 -0600 Subject: [PATCH 254/262] chore: delete test-utils, rename client-primites to node --- Cargo.lock | 75 ++--- Cargo.toml | 3 +- bin/node/Cargo.toml | 2 +- bin/node/src/main.rs | 2 +- crates/client/client-primitives/Cargo.toml | 25 -- crates/client/flashblocks/Cargo.toml | 11 +- .../flashblocks/benches/pending_state.rs | 2 +- crates/client/flashblocks/src/extension.rs | 2 +- crates/client/flashblocks/src/test_harness.rs | 17 +- .../client/flashblocks/tests/eip7702_tests.rs | 4 +- .../flashblocks/tests/eth_call_erc20.rs | 4 +- .../flashblocks/tests/flashblocks_rpc.rs | 2 +- crates/client/flashblocks/tests/state.rs | 6 +- crates/client/metering/Cargo.toml | 4 +- crates/client/metering/src/extension.rs | 2 +- crates/client/metering/tests/meter.rs | 2 +- crates/client/metering/tests/meter_block.rs | 2 +- crates/client/metering/tests/meter_rpc.rs | 2 +- crates/client/node/Cargo.toml | 94 ++++++ .../{client-primitives => node}/README.md | 2 +- .../src/extension.rs | 0 .../{client-primitives => node}/src/handle.rs | 0 .../{client-primitives => node}/src/lib.rs | 6 + .../{client-primitives => node}/src/runner.rs | 0 .../src => node/src/test_utils}/constants.rs | 0 .../src => node/src/test_utils}/engine.rs | 2 +- .../src => node/src/test_utils}/fixtures.rs | 0 .../src => node/src/test_utils}/harness.rs | 20 +- .../src/lib.rs => node/src/test_utils/mod.rs} | 13 +- .../src => node/src/test_utils}/node.rs | 7 +- .../src => node/src/test_utils}/tracing.rs | 0 .../{client-primitives => node}/src/types.rs | 0 crates/client/test-utils/Cargo.toml | 62 ---- crates/client/test-utils/README.md | 307 ------------------ crates/client/txpool/Cargo.toml | 2 +- crates/client/txpool/src/extension.rs | 2 +- 36 files changed, 193 insertions(+), 491 deletions(-) delete mode 100644 crates/client/client-primitives/Cargo.toml create mode 100644 crates/client/node/Cargo.toml rename crates/client/{client-primitives => node}/README.md (94%) rename crates/client/{client-primitives => node}/src/extension.rs (100%) rename crates/client/{client-primitives => node}/src/handle.rs (100%) rename crates/client/{client-primitives => node}/src/lib.rs (72%) rename crates/client/{client-primitives => node}/src/runner.rs (100%) rename crates/client/{test-utils/src => node/src/test_utils}/constants.rs (100%) rename crates/client/{test-utils/src => node/src/test_utils}/engine.rs (99%) rename crates/client/{test-utils/src => node/src/test_utils}/fixtures.rs (100%) rename crates/client/{test-utils/src => node/src/test_utils}/harness.rs (95%) rename crates/client/{test-utils/src/lib.rs => node/src/test_utils/mod.rs} (71%) rename crates/client/{test-utils/src => node/src/test_utils}/node.rs (96%) rename crates/client/{test-utils/src => node/src/test_utils}/tracing.rs (100%) rename crates/client/{client-primitives => node}/src/types.rs (100%) delete mode 100644 crates/client/test-utils/Cargo.toml delete mode 100644 crates/client/test-utils/README.md diff --git a/Cargo.lock b/Cargo.lock index c60d104e..7fbebbda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1554,17 +1554,42 @@ dependencies = [ ] [[package]] -name = "base-client-primitives" +name = "base-client-node" version = "0.2.1" dependencies = [ + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-engine", + "alloy-signer", + "base-primitives", + "chrono", "derive_more", "eyre", "futures-util", + "jsonrpsee", + "op-alloy-network", + "op-alloy-rpc-types-engine", "reth", "reth-db", + "reth-ipc", + "reth-node-core", "reth-optimism-chainspec", "reth-optimism-node", + "reth-optimism-primitives", + "reth-optimism-rpc", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-layer", + "reth-tracing", + "tokio", + "tower", "tracing", + "tracing-subscriber 0.3.22", + "url", ] [[package]] @@ -1585,10 +1610,9 @@ dependencies = [ "alloy-sol-macro", "alloy-sol-types", "arc-swap", - "base-client-primitives", + "base-client-node", "base-flashblocks", "base-flashtypes", - "base-test-utils", "criterion", "derive_more", "eyre", @@ -1659,8 +1683,7 @@ dependencies = [ "alloy-primitives", "alloy-rpc-client", "base-bundles", - "base-client-primitives", - "base-test-utils", + "base-client-node", "eyre", "jsonrpsee", "op-alloy-consensus", @@ -1705,7 +1728,7 @@ name = "base-reth-node" version = "0.2.1" dependencies = [ "base-cli-utils", - "base-client-primitives", + "base-client-node", "base-flashblocks", "base-metering", "base-txpool", @@ -1724,49 +1747,12 @@ dependencies = [ "reth-rpc-eth-types", ] -[[package]] -name = "base-test-utils" -version = "0.2.1" -dependencies = [ - "alloy-eips", - "alloy-genesis", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-client", - "alloy-rpc-types", - "alloy-rpc-types-engine", - "alloy-signer", - "base-client-primitives", - "base-primitives", - "chrono", - "eyre", - "jsonrpsee", - "op-alloy-network", - "op-alloy-rpc-types-engine", - "reth", - "reth-db", - "reth-ipc", - "reth-node-core", - "reth-optimism-chainspec", - "reth-optimism-node", - "reth-optimism-primitives", - "reth-optimism-rpc", - "reth-primitives-traits", - "reth-provider", - "reth-rpc-layer", - "reth-tracing", - "tokio", - "tower", - "tracing-subscriber 0.3.22", - "url", -] - [[package]] name = "base-txpool" version = "0.2.1" dependencies = [ "alloy-primitives", - "base-client-primitives", + "base-client-node", "chrono", "derive_more", "eyre", @@ -7922,6 +7908,7 @@ dependencies = [ "alloy-eip2124", "alloy-hardforks", "alloy-primitives", + "arbitrary", "auto_impl", "once_cell", "rustc-hash", diff --git a/Cargo.toml b/Cargo.toml index 7908db43..ad905eda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,10 +58,9 @@ base-flashtypes = { path = "crates/shared/flashtypes" } base-primitives = { path = "crates/shared/primitives" } base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" } # Client -base-client-primitives = { path = "crates/client/client-primitives" } +base-client-node = { path = "crates/client/node" } base-metering = { path = "crates/client/metering" } base-txpool = { path = "crates/client/txpool" } -base-test-utils = { path = "crates/client/test-utils" } base-flashblocks = { path = "crates/client/flashblocks" } # reth diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 0b1be210..08e3c1a9 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -15,7 +15,7 @@ workspace = true [dependencies] # internal base-cli-utils.workspace = true -base-client-primitives.workspace = true +base-client-node.workspace = true base-flashblocks.workspace = true base-metering.workspace = true base-txpool.workspace = true diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 9863940d..21ab8b92 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -7,7 +7,7 @@ pub mod cli; use std::sync::Arc; -use base_client_primitives::{BaseNodeRunner, OpProvider}; +use base_client_node::{BaseNodeRunner, OpProvider}; use base_flashblocks::{FlashblocksCell, FlashblocksExtension, FlashblocksState}; use base_metering::MeteringExtension; use base_txpool::TxPoolExtension; diff --git a/crates/client/client-primitives/Cargo.toml b/crates/client/client-primitives/Cargo.toml deleted file mode 100644 index 38130118..00000000 --- a/crates/client/client-primitives/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "base-client-primitives" -description = "Primitive types and traits for Base node runner extensions" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[lints] -workspace = true - -[dependencies] -# reth -reth.workspace = true -reth-db.workspace = true -reth-optimism-node.workspace = true -reth-optimism-chainspec.workspace = true - -# misc -eyre.workspace = true -futures-util.workspace = true -tracing.workspace = true -derive_more = { workspace = true, features = ["debug"] } diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index 01813dc6..a8b15150 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -12,12 +12,18 @@ repository.workspace = true workspace = true [features] -test-utils = [ "base-test-utils", "derive_more", "eyre" ] +test-utils = [ + "base-client-node/test-utils", + "derive_more", + "eyre", + "reth-evm/test-utils", + "reth-primitives/test-utils", +] [dependencies] # workspace base-flashtypes.workspace = true -base-client-primitives.workspace = true +base-client-node.workspace = true # reth reth.workspace = true @@ -71,7 +77,6 @@ metrics-derive.workspace = true rayon.workspace = true # Optional test-utils dependencies -base-test-utils = { workspace = true, optional = true } derive_more = { workspace = true, features = ["deref"], optional = true } eyre = { workspace = true, optional = true } diff --git a/crates/client/flashblocks/benches/pending_state.rs b/crates/client/flashblocks/benches/pending_state.rs index 9f2aacfe..182b190a 100644 --- a/crates/client/flashblocks/benches/pending_state.rs +++ b/crates/client/flashblocks/benches/pending_state.rs @@ -8,11 +8,11 @@ use std::{ use alloy_eips::{BlockHashOrNumber, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, bytes, hex::FromHex}; use alloy_rpc_types_engine::PayloadId; +use base_client_node::test_utils::{Account, LocalNodeProvider, TestHarness}; use base_flashblocks::{FlashblocksAPI, FlashblocksReceiver, FlashblocksState}; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, LocalNodeProvider, TestHarness}; use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main}; use reth::{ chainspec::{ChainSpecProvider, EthChainSpec}, diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index ce4a53dd..ab72aaa7 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use base_client_primitives::{BaseNodeExtension, OpBuilder, OpProvider}; +use base_client_node::{BaseNodeExtension, OpBuilder, OpProvider}; use futures_util::TryStreamExt; use once_cell::sync::OnceCell; use reth_exex::ExExEvent; diff --git a/crates/client/flashblocks/src/test_harness.rs b/crates/client/flashblocks/src/test_harness.rs index a5a96704..cb9e1e62 100644 --- a/crates/client/flashblocks/src/test_harness.rs +++ b/crates/client/flashblocks/src/test_harness.rs @@ -12,12 +12,14 @@ use std::{ time::Duration, }; -use base_client_primitives::BaseNodeExtension; -use base_flashtypes::Flashblock; -use base_test_utils::{ - LocalNode, LocalNodeProvider, NODE_STARTUP_DELAY_MS, TestHarness, build_test_genesis, - init_silenced_tracing, +use base_client_node::{ + BaseNodeExtension, + test_utils::{ + LocalNode, LocalNodeProvider, NODE_STARTUP_DELAY_MS, TestHarness, build_test_genesis, + init_silenced_tracing, + }, }; +use base_flashtypes::Flashblock; use derive_more::Deref; use eyre::Result; use once_cell::sync::OnceCell; @@ -113,10 +115,7 @@ impl FlashblocksTestExtension { } impl BaseNodeExtension for FlashblocksTestExtension { - fn apply( - self: Box, - builder: base_client_primitives::OpBuilder, - ) -> base_client_primitives::OpBuilder { + fn apply(self: Box, builder: base_client_node::OpBuilder) -> base_client_node::OpBuilder { let fb_cell = self.inner.fb_cell.clone(); let receiver = self.inner.receiver.clone(); let process_canonical = self.inner.process_canonical; diff --git a/crates/client/flashblocks/tests/eip7702_tests.rs b/crates/client/flashblocks/tests/eip7702_tests.rs index ab463d1f..b1e17f46 100644 --- a/crates/client/flashblocks/tests/eip7702_tests.rs +++ b/crates/client/flashblocks/tests/eip7702_tests.rs @@ -8,11 +8,13 @@ use alloy_eips::{eip2718::Encodable2718, eip7702::Authorization}; use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_sol_types::SolCall; +use base_client_node::test_utils::{ + Account, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync, +}; use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, L1_BLOCK_INFO_DEPOSIT_TX, Minimal7702Account, SignerSync}; use eyre::Result; use op_alloy_network::ReceiptResponse; diff --git a/crates/client/flashblocks/tests/eth_call_erc20.rs b/crates/client/flashblocks/tests/eth_call_erc20.rs index 55b4317b..531ddd0b 100644 --- a/crates/client/flashblocks/tests/eth_call_erc20.rs +++ b/crates/client/flashblocks/tests/eth_call_erc20.rs @@ -16,11 +16,13 @@ use alloy_primitives::{Address, B256, Bytes, U256}; use alloy_provider::Provider; use alloy_rpc_types_engine::PayloadId; use alloy_sol_types::{SolConstructor, SolValue}; +use base_client_node::test_utils::{ + Account, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy, +}; use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, L1_BLOCK_INFO_DEPOSIT_TX, MockERC20, TransparentUpgradeableProxy}; use eyre::Result; struct Erc20TestSetup { harness: FlashblocksHarness, diff --git a/crates/client/flashblocks/tests/flashblocks_rpc.rs b/crates/client/flashblocks/tests/flashblocks_rpc.rs index e7d6fc57..3b46f047 100644 --- a/crates/client/flashblocks/tests/flashblocks_rpc.rs +++ b/crates/client/flashblocks/tests/flashblocks_rpc.rs @@ -11,11 +11,11 @@ use alloy_rpc_client::RpcClient; use alloy_rpc_types::simulate::{SimBlock, SimulatePayload}; use alloy_rpc_types_engine::PayloadId; use alloy_rpc_types_eth::{TransactionInput, error::EthRpcErrorCode}; +use base_client_node::test_utils::{Account, DoubleCounter, L1_BLOCK_INFO_DEPOSIT_TX}; use base_flashblocks::test_harness::FlashblocksHarness; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{Account, DoubleCounter, L1_BLOCK_INFO_DEPOSIT_TX}; use eyre::Result; use futures_util::{SinkExt, StreamExt}; use op_alloy_network::{Optimism, ReceiptResponse, TransactionResponse}; diff --git a/crates/client/flashblocks/tests/state.rs b/crates/client/flashblocks/tests/state.rs index d6321351..a0a85b80 100644 --- a/crates/client/flashblocks/tests/state.rs +++ b/crates/client/flashblocks/tests/state.rs @@ -6,15 +6,15 @@ use alloy_consensus::{Receipt, Transaction}; use alloy_eips::{BlockHashOrNumber, Encodable2718}; use alloy_primitives::{Address, B256, BlockNumber, Bytes, U256, hex::FromHex, map::HashMap}; use alloy_rpc_types_engine::PayloadId; +use base_client_node::test_utils::{ + Account, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, +}; use base_flashblocks::{ FlashblocksAPI, FlashblocksState, PendingBlocksAPI, test_harness::FlashblocksHarness, }; use base_flashtypes::{ ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, Flashblock, Metadata, }; -use base_test_utils::{ - Account, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, LocalNodeProvider, -}; use op_alloy_consensus::OpDepositReceipt; use op_alloy_network::BlockResponse; use reth::{ diff --git a/crates/client/metering/Cargo.toml b/crates/client/metering/Cargo.toml index 0a370995..c45ee302 100644 --- a/crates/client/metering/Cargo.toml +++ b/crates/client/metering/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] # workspace base-bundles.workspace = true -base-client-primitives.workspace = true +base-client-node.workspace = true # reth reth.workspace = true @@ -39,7 +39,7 @@ eyre.workspace = true serde.workspace = true [dev-dependencies] -base-test-utils.workspace = true +base-client-node = { workspace = true, features = ["test-utils"] } reth-db = { workspace = true, features = ["test-utils"] } reth-db-common.workspace = true reth-optimism-node.workspace = true diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 77b0fba2..69bdc7cd 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`MeteringExtension`] which wires up the metering RPC surface //! on the Base node builder. -use base_client_primitives::{BaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, OpBuilder}; use tracing::info; use crate::{MeteringApiImpl, MeteringApiServer}; diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index de6875fd..8b541804 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -3,8 +3,8 @@ use alloy_eips::Encodable2718; use alloy_primitives::{Address, Bytes, U256, keccak256}; use base_bundles::{Bundle, ParsedBundle}; +use base_client_node::test_utils::{Account, TestHarness, load_chain_spec}; use base_metering::{MeteringExtension, meter_bundle}; -use base_test_utils::{Account, TestHarness, load_chain_spec}; use eyre::Context; use op_alloy_consensus::OpTxEnvelope; use reth::chainspec::EthChainSpec; diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index 1f3bbeb1..c8078b60 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -2,8 +2,8 @@ use alloy_consensus::{BlockHeader, Header}; use alloy_primitives::{Address, B256}; +use base_client_node::test_utils::{Account, TestHarness, load_chain_spec}; use base_metering::{MeteringExtension, meter_block}; -use base_test_utils::{Account, TestHarness, load_chain_spec}; use reth::chainspec::EthChainSpec; use reth_optimism_primitives::{OpBlock, OpBlockBody, OpTransactionSigned}; use reth_primitives_traits::Block as BlockT; diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index 6739f1a0..db619d30 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -4,8 +4,8 @@ use alloy_eips::Encodable2718; use alloy_primitives::{Bytes, U256, address, bytes}; use alloy_rpc_client::RpcClient; use base_bundles::{Bundle, MeterBundleResponse}; +use base_client_node::test_utils::{Account, TestHarness}; use base_metering::MeteringExtension; -use base_test_utils::{Account, TestHarness}; use op_alloy_consensus::OpTxEnvelope; use reth_optimism_primitives::OpTransactionSigned; use reth_transaction_pool::test_utils::TransactionBuilder; diff --git a/crates/client/node/Cargo.toml b/crates/client/node/Cargo.toml new file mode 100644 index 00000000..bfe488e6 --- /dev/null +++ b/crates/client/node/Cargo.toml @@ -0,0 +1,94 @@ +[package] +name = "base-client-node" +description = "Primitive types and traits for Base node runner extensions" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +test-utils = [ + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-rpc-types-engine", + "alloy-signer", + "base-primitives/test-utils", + "chrono", + "jsonrpsee", + "op-alloy-network", + "op-alloy-rpc-types-engine", + "reth-db/test-utils", + "reth-ipc", + "reth-node-core", + "reth-optimism-rpc", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-layer", + "reth-tracing", + "tokio", + "tower", + "tracing-subscriber", + "url", + "reth-optimism-node/test-utils", + "reth-primitives-traits?/test-utils", + "reth-provider?/test-utils" +] + +[dependencies] +# Project +base-primitives = { workspace = true, optional = true } + +# reth +reth.workspace = true +reth-db.workspace = true +reth-optimism-node.workspace = true +reth-optimism-chainspec.workspace = true +reth-ipc = { workspace = true, optional = true } +reth-tracing = { workspace = true, optional = true } +reth-rpc-layer = { workspace = true, optional = true } +reth-provider = { workspace = true, optional = true } +reth-node-core = { workspace = true, optional = true } +reth-primitives-traits = { workspace = true, optional = true } +reth-optimism-rpc = { workspace = true, features = ["client"], optional = true } +reth-optimism-primitives.workspace = true + +# alloy +alloy-primitives = { workspace = true, optional = true } +alloy-genesis = { workspace = true, optional = true } +alloy-eips = { workspace = true, optional = true } +alloy-rpc-types = { workspace = true, optional = true } +alloy-rpc-types-engine = { workspace = true, optional = true } +alloy-provider = { workspace = true, optional = true } +alloy-rpc-client = { workspace = true, optional = true } +alloy-signer = { workspace = true, optional = true } + +# op-alloy +op-alloy-rpc-types-engine = { workspace = true, optional = true } +op-alloy-network = { workspace = true, optional = true } + +# tokio +tokio = { workspace = true, optional = true } + +# rpc +jsonrpsee = { workspace = true, optional = true } + +# misc +eyre.workspace = true +futures-util.workspace = true +tracing.workspace = true +derive_more = { workspace = true, features = ["debug"] } +tracing-subscriber = { workspace = true, optional = true } +url = { workspace = true, optional = true } +chrono = { workspace = true, optional = true } + +# tower for middleware +tower = { version = "0.5", optional = true } diff --git a/crates/client/client-primitives/README.md b/crates/client/node/README.md similarity index 94% rename from crates/client/client-primitives/README.md rename to crates/client/node/README.md index 225e06b4..7b10150a 100644 --- a/crates/client/client-primitives/README.md +++ b/crates/client/node/README.md @@ -28,7 +28,7 @@ base-client-primitives = { git = "https://github.com/base/node-reth" } Implement a custom node extension: ```rust,ignore -use base_client_primitives::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, ConfigurableBaseNodeExtension, OpBuilder}; use eyre::Result; #[derive(Debug)] diff --git a/crates/client/client-primitives/src/extension.rs b/crates/client/node/src/extension.rs similarity index 100% rename from crates/client/client-primitives/src/extension.rs rename to crates/client/node/src/extension.rs diff --git a/crates/client/client-primitives/src/handle.rs b/crates/client/node/src/handle.rs similarity index 100% rename from crates/client/client-primitives/src/handle.rs rename to crates/client/node/src/handle.rs diff --git a/crates/client/client-primitives/src/lib.rs b/crates/client/node/src/lib.rs similarity index 72% rename from crates/client/client-primitives/src/lib.rs rename to crates/client/node/src/lib.rs index 2dc062de..25f03b3d 100644 --- a/crates/client/client-primitives/src/lib.rs +++ b/crates/client/node/src/lib.rs @@ -3,6 +3,9 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] +// Silence false positive - reth_optimism_primitives is used in test_utils module +use reth_optimism_primitives as _; + mod extension; pub use extension::BaseNodeExtension; @@ -14,3 +17,6 @@ pub use runner::BaseNodeRunner; mod types; pub use types::{BaseNodeBuilder, OpBuilder, OpProvider}; + +#[cfg(feature = "test-utils")] +pub mod test_utils; diff --git a/crates/client/client-primitives/src/runner.rs b/crates/client/node/src/runner.rs similarity index 100% rename from crates/client/client-primitives/src/runner.rs rename to crates/client/node/src/runner.rs diff --git a/crates/client/test-utils/src/constants.rs b/crates/client/node/src/test_utils/constants.rs similarity index 100% rename from crates/client/test-utils/src/constants.rs rename to crates/client/node/src/test_utils/constants.rs diff --git a/crates/client/test-utils/src/engine.rs b/crates/client/node/src/test_utils/engine.rs similarity index 99% rename from crates/client/test-utils/src/engine.rs rename to crates/client/node/src/test_utils/engine.rs index 8e8de2a2..76532653 100644 --- a/crates/client/test-utils/src/engine.rs +++ b/crates/client/node/src/test_utils/engine.rs @@ -21,7 +21,7 @@ use reth_rpc_layer::{AuthClientLayer, JwtSecret}; use reth_tracing::tracing::debug; use url::Url; -use crate::DEFAULT_JWT_SECRET; +use crate::test_utils::DEFAULT_JWT_SECRET; /// Describes how to reach the Engine API endpoint. #[derive(Clone, Debug)] diff --git a/crates/client/test-utils/src/fixtures.rs b/crates/client/node/src/test_utils/fixtures.rs similarity index 100% rename from crates/client/test-utils/src/fixtures.rs rename to crates/client/node/src/test_utils/fixtures.rs diff --git a/crates/client/test-utils/src/harness.rs b/crates/client/node/src/test_utils/harness.rs similarity index 95% rename from crates/client/test-utils/src/harness.rs rename to crates/client/node/src/test_utils/harness.rs index 3dad8758..16aed723 100644 --- a/crates/client/test-utils/src/harness.rs +++ b/crates/client/node/src/test_utils/harness.rs @@ -8,7 +8,6 @@ use alloy_provider::{Provider, RootProvider}; use alloy_rpc_client::RpcClient; use alloy_rpc_types::BlockNumberOrTag; use alloy_rpc_types_engine::PayloadAttributes; -use base_client_primitives::BaseNodeExtension; use eyre::{Result, eyre}; use op_alloy_network::Optimism; use op_alloy_rpc_types_engine::OpPayloadAttributes; @@ -19,11 +18,14 @@ use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::time::sleep; use crate::{ - BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, - NODE_STARTUP_DELAY_MS, - engine::{EngineApi, IpcEngine}, - node::{LocalNode, LocalNodeProvider}, - tracing::init_silenced_tracing, + BaseNodeExtension, + test_utils::{ + BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, + NODE_STARTUP_DELAY_MS, + engine::{EngineApi, IpcEngine}, + node::{LocalNode, LocalNodeProvider}, + tracing::init_silenced_tracing, + }, }; /// Builder for configuring and launching a test harness. @@ -60,7 +62,7 @@ impl TestHarnessBuilder { let chain_spec = match self.chain_spec { Some(spec) => spec, None => { - let genesis = crate::build_test_genesis(); + let genesis = crate::test_utils::build_test_genesis(); Arc::new(OpChainSpec::from_genesis(genesis)) } }; @@ -241,7 +243,7 @@ mod tests { use alloy_provider::Provider; use super::*; - use crate::Account; + use crate::test_utils::Account; #[tokio::test] async fn test_harness_setup() -> Result<()> { @@ -249,7 +251,7 @@ mod tests { let provider = harness.provider(); let chain_id = provider.get_chain_id().await?; - assert_eq!(chain_id, crate::DEVNET_CHAIN_ID); + assert_eq!(chain_id, crate::test_utils::DEVNET_CHAIN_ID); let alice_balance = provider.get_balance(Account::Alice.address()).await?; assert!(alice_balance > U256::ZERO); diff --git a/crates/client/test-utils/src/lib.rs b/crates/client/node/src/test_utils/mod.rs similarity index 71% rename from crates/client/test-utils/src/lib.rs rename to crates/client/node/src/test_utils/mod.rs index 18ef3dea..7976c184 100644 --- a/crates/client/test-utils/src/lib.rs +++ b/crates/client/node/src/test_utils/mod.rs @@ -1,7 +1,10 @@ -#![doc = include_str!("../README.md")] -#![doc(issue_tracker_base_url = "https://github.com/base/node-reth/issues/")] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] +//! Test utilities for integration testing. +//! +//! This module provides testing infrastructure including: +//! - [`TestHarness`] and [`TestHarnessBuilder`] - Unified test harness for node and engine. +//! - [`LocalNode`] and [`LocalNodeProvider`] - Local node setup. +//! - [`EngineApi`] with [`HttpEngine`] and [`IpcEngine`] - Engine API client. +//! - Test constants and fixtures. // Re-export from base-primitives for backwards compatibility pub use base_primitives::{ @@ -26,8 +29,6 @@ mod harness; pub use harness::{TestHarness, TestHarnessBuilder}; mod node; -// Re-export BaseNodeExtension for extension authors -pub use base_client_primitives::BaseNodeExtension; pub use node::{LocalNode, LocalNodeProvider}; mod tracing; diff --git a/crates/client/test-utils/src/node.rs b/crates/client/node/src/test_utils/node.rs similarity index 96% rename from crates/client/test-utils/src/node.rs rename to crates/client/node/src/test_utils/node.rs index 9c3c64f5..d8ce15c9 100644 --- a/crates/client/test-utils/src/node.rs +++ b/crates/client/node/src/test_utils/node.rs @@ -4,7 +4,6 @@ use std::{any::Any, fmt, net::SocketAddr, path::PathBuf, sync::Arc}; use alloy_provider::RootProvider; use alloy_rpc_client::RpcClient; -use base_client_primitives::{BaseNodeExtension, OpProvider}; use eyre::Result; use op_alloy_network::Optimism; use reth::{ @@ -24,7 +23,7 @@ use reth_node_core::{ use reth_optimism_chainspec::OpChainSpec; use reth_optimism_node::{OpNode, args::RollupArgs}; -use crate::engine::EngineApi; +use crate::{BaseNodeExtension, OpProvider, test_utils::engine::EngineApi}; /// Convenience alias for the local blockchain provider type. pub type LocalNodeProvider = OpProvider; @@ -169,8 +168,8 @@ impl LocalNode { } /// Build an Engine API client that talks to the node's IPC endpoint. - pub fn engine_api(&self) -> Result> { - EngineApi::::new(self.engine_ipc_path.clone()) + pub fn engine_api(&self) -> Result> { + EngineApi::::new(self.engine_ipc_path.clone()) } /// Clone the underlying blockchain provider so callers can inspect chain state. diff --git a/crates/client/test-utils/src/tracing.rs b/crates/client/node/src/test_utils/tracing.rs similarity index 100% rename from crates/client/test-utils/src/tracing.rs rename to crates/client/node/src/test_utils/tracing.rs diff --git a/crates/client/client-primitives/src/types.rs b/crates/client/node/src/types.rs similarity index 100% rename from crates/client/client-primitives/src/types.rs rename to crates/client/node/src/types.rs diff --git a/crates/client/test-utils/Cargo.toml b/crates/client/test-utils/Cargo.toml deleted file mode 100644 index 3bd718e5..00000000 --- a/crates/client/test-utils/Cargo.toml +++ /dev/null @@ -1,62 +0,0 @@ -[package] -name = "base-test-utils" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -description = "Common integration test utilities for node-reth crates" - -[lints] -workspace = true - -[dependencies] -# Project -base-client-primitives.workspace = true -base-primitives = { workspace = true, features = ["test-utils"] } - -# reth -reth.workspace = true -reth-optimism-node.workspace = true -reth-optimism-chainspec.workspace = true -reth-optimism-primitives.workspace = true -reth-optimism-rpc = { workspace = true, features = ["client"] } -reth-provider.workspace = true -reth-primitives-traits.workspace = true -reth-db = { workspace = true, features = ["test-utils"] } -reth-node-core.workspace = true -reth-tracing.workspace = true -reth-rpc-layer.workspace = true -reth-ipc.workspace = true - -# alloy -alloy-primitives.workspace = true -alloy-genesis.workspace = true -alloy-eips.workspace = true -alloy-rpc-types.workspace = true -alloy-rpc-types-engine.workspace = true -alloy-provider.workspace = true -alloy-rpc-client.workspace = true -alloy-signer.workspace = true - -# op-alloy -op-alloy-rpc-types-engine.workspace = true -op-alloy-network.workspace = true - -# tokio -tokio.workspace = true - -# rpc -jsonrpsee.workspace = true - -# misc -tracing-subscriber.workspace = true -eyre.workspace = true -url.workspace = true -chrono.workspace = true - -# tower for middleware -tower = "0.5" - -[dev-dependencies] diff --git a/crates/client/test-utils/README.md b/crates/client/test-utils/README.md deleted file mode 100644 index 5bb44b1c..00000000 --- a/crates/client/test-utils/README.md +++ /dev/null @@ -1,307 +0,0 @@ -# Test Utils - -A comprehensive integration test framework for node-reth crates. - -## Overview - -This crate provides reusable testing utilities for integration tests across the node-reth workspace. It includes: - -- **LocalNode**: Isolated in-process node with Base Sepolia chainspec -- **TestHarness**: Unified orchestration layer combining node, Engine API, and flashblocks -- **EngineApi**: Type-safe Engine API client for CL operations -- **Test Accounts**: Pre-funded hardcoded accounts (Alice, Bob, Charlie, Deployer) -- **Flashblocks Support**: Testing pending state with flashblocks delivery - -## Quick Start - -```rust,ignore -use base_test_utils::TestHarness; - -#[tokio::test] -async fn test_example() -> eyre::Result<()> { - let harness = TestHarness::new().await?; - - // Advance the chain - harness.advance_chain(5).await?; - - // Access accounts - let alice = &harness.accounts().alice; - - // Get balance via provider - let balance = harness.provider().get_balance(alice.address).await?; - - Ok(()) -} -``` - -## Architecture - -The framework follows a three-layer architecture: - -```text -┌─────────────────────────────────────┐ -│ TestHarness │ ← Orchestration layer (tests use this) -│ - Coordinates node + engine │ -│ - Builds blocks from transactions │ -│ - Manages test accounts │ -│ - Manages flashblocks │ -└─────────────────────────────────────┘ - │ │ - ┌──────┘ └──────┐ - ▼ ▼ -┌─────────┐ ┌──────────┐ -│LocalNode│ │EngineApi │ ← Raw API wrappers -│ (EL) │ │ (CL) │ -└─────────┘ └──────────┘ -``` - -### Component Responsibilities - -- **LocalNode** (EL wrapper): In-process Optimism node with HTTP RPC + Engine API IPC -- **EngineApi** (CL wrapper): Raw Engine API calls (forkchoice, payloads) -- **TestHarness**: Orchestrates block building by fetching latest block headers and calling Engine API - -## Components - -### 1. TestHarness - -The main entry point for integration tests that only need canonical chain control. Combines node, engine, and accounts into a single interface. - -```rust,ignore -use base_test_utils::TestHarness; -use alloy_primitives::Bytes; - -#[tokio::test] -async fn test_harness() -> eyre::Result<()> { - let harness = TestHarness::new().await?; - - // Access provider - let provider = harness.provider(); - let chain_id = provider.get_chain_id().await?; - - // Access accounts - let alice = &harness.accounts().alice; - let bob = &harness.accounts().bob; - - // Build empty blocks - harness.advance_chain(10).await?; - - // Build block with transactions - let txs: Vec = vec![/* signed transaction bytes */]; - harness.build_block_from_transactions(txs).await?; - - Ok(()) -} -``` - -> Need pending-state testing? Use `FlashblocksHarness` (see Flashblocks section below) to gain `send_flashblock` helpers. - -**Key Methods:** -- `new()` - Create new harness with node, engine, and accounts -- `provider()` - Get Alloy RootProvider for RPC calls -- `accounts()` - Access test accounts -- `advance_chain(n)` - Build N empty blocks -- `build_block_from_transactions(txs)` - Build block with specific transactions (auto-prepends the L1 block info deposit) - -**Block Building Process:** -1. Fetches latest block header from provider (no local state tracking) -2. Calculates next timestamp (parent + 2 seconds for Base) -3. Calls `engine.update_forkchoice()` with payload attributes -4. Waits for block construction -5. Calls `engine.get_payload()` to retrieve built payload -6. Calls `engine.new_payload()` to validate and submit -7. Calls `engine.update_forkchoice()` again to finalize - -### 2. LocalNode - -In-process Optimism node with Base Sepolia configuration. - -```rust,ignore -use base_test_utils::{LocalNode, default_launcher}; - -#[tokio::test] -async fn test_node() -> eyre::Result<()> { - let node = LocalNode::new(default_launcher).await?; - - let provider = node.provider()?; - let engine = node.engine_api()?; - - Ok(()) -} -``` - -**Features (base):** -- Base Sepolia chain configuration -- Disabled P2P discovery (isolated testing) -- Random unused ports (parallel test safety) -- HTTP RPC server at `node.http_api_addr` -- Engine API IPC at `node.engine_ipc_path` - -For flashblocks-enabled nodes, use `FlashblocksLocalNode`: - -```rust,ignore -use base_test_utils::FlashblocksLocalNode; - -let node = FlashblocksLocalNode::new().await?; -let pending_state = node.flashblocks_state(); -node.send_flashblock(flashblock).await?; -``` - -**Note:** Most tests should use `TestHarness` instead of `LocalNode` directly. - -### 3. EngineApi - -Type-safe Engine API client wrapping raw CL operations. - -```rust,ignore -use base_test_utils::EngineApi; -use alloy_primitives::B256; -use op_alloy_rpc_types_engine::OpPayloadAttributes; - -// Usually accessed via TestHarness, but can be used directly -let engine = node.engine_api()?; - -// Raw Engine API calls -let fcu = engine.update_forkchoice(current_head, new_head, Some(attrs)).await?; -let payload = engine.get_payload(payload_id).await?; -let status = engine.new_payload(payload, vec![], parent_root, requests).await?; -``` - -**Methods:** -- `get_payload(payload_id)` - Retrieve built payload by ID -- `new_payload(payload, hashes, root, requests)` - Submit new payload -- `update_forkchoice(current, new, attrs)` - Update forkchoice state - -**Note:** EngineApi is stateless. Block building logic lives in `TestHarness`. - -### 4. Test Accounts - -Hardcoded test accounts with deterministic addresses (Anvil-compatible). - -```rust,ignore -use base_test_utils::{TestAccounts, TestHarness}; - -let accounts = TestAccounts::new(); - -let alice = &accounts.alice; -let bob = &accounts.bob; -let charlie = &accounts.charlie; -let deployer = &accounts.deployer; - -// Access via harness -let harness = TestHarness::new().await?; -let alice = &harness.accounts().alice; -``` - -**Account Details:** -- **Alice**: `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` - 10,000 ETH -- **Bob**: `0x70997970C51812dc3A010C7d01b50e0d17dc79C8` - 10,000 ETH -- **Charlie**: `0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC` - 10,000 ETH -- **Deployer**: `0x90F79bf6EB2c4f870365E785982E1f101E93b906` - 10,000 ETH - -Each account includes: -- `name` - Account identifier -- `address` - Ethereum address -- `private_key` - Private key (hex string) -- `initial_balance_eth` - Starting balance in ETH - -### 5. Flashblocks Support - -Use `FlashblocksHarness` when you need `send_flashblock` and access to the in-memory pending state. - -```rust,ignore -use base_test_utils::FlashblocksHarness; - -#[tokio::test] -async fn test_flashblocks() -> eyre::Result<()> { - let harness = FlashblocksHarness::new().await?; - - harness.send_flashblock(flashblock).await?; - - let pending = harness.flashblocks_state(); - // assertions... - - Ok(()) -} -``` - -`FlashblocksHarness` derefs to the base `TestHarness`, so you can keep using methods like `provider()`, `build_block_from_transactions`, etc. - -Test flashblocks delivery without WebSocket connections by constructing payloads and sending them through `FlashblocksHarness` (or the lower-level `FlashblocksLocalNode`). - -## Configuration Constants - -Key constants are exported from the crate root: - -```rust,ignore -use base_test_utils::{ - BASE_CHAIN_ID, // Chain ID for Base Sepolia (84532) - BLOCK_TIME_SECONDS, // Base L2 block time (2 seconds) - GAS_LIMIT, // Default gas limit (200M) - NODE_STARTUP_DELAY_MS, // IPC endpoint initialization (500ms) - BLOCK_BUILD_DELAY_MS, // Payload construction wait (100ms) - L1_BLOCK_INFO_DEPOSIT_TX, // Pre-captured L1 block info deposit - L1_BLOCK_INFO_DEPOSIT_TX_HASH, - DEFAULT_JWT_SECRET, // All-zeros JWT for local testing -}; -``` - -## Usage in Other Crates - -Add to `dev-dependencies`: - -```toml -[dev-dependencies] -base-test-utils.workspace = true -``` - -Import in tests: - -```rust,ignore -use base_test_utils::TestHarness; - -#[tokio::test] -async fn my_test() -> eyre::Result<()> { - let harness = TestHarness::new().await?; - // Your test logic - - Ok(()) -} -``` - -## Design Principles - -1. **Separation of Concerns**: LocalNode (EL), EngineApi (CL), TestHarness (orchestration) -2. **Stateless Components**: No local state tracking; always fetch from provider -3. **Type Safety**: Use reth's `OpEngineApiClient` trait instead of raw RPC strings -4. **Parallel Testing**: Random ports + isolated nodes enable concurrent tests -5. **Anvil Compatibility**: Same mnemonic as Anvil for tooling compatibility - -## Testing - -Run the test suite: - -```bash -cargo test -p base-reth-test-utils -``` - -Run specific test: - -```bash -cargo test -p base-reth-test-utils test_harness_setup -``` - -## Future Enhancements - -- Transaction builders for common operations -- Smart contract deployment helpers (Foundry integration planned) -- Snapshot/restore functionality -- Multi-node network simulation -- Performance benchmarking utilities -- Helper builder for Flashblocks - -## References - -Inspired by: -- [op-rbuilder test framework](https://github.com/flashbots/op-rbuilder/tree/main/crates/op-rbuilder/src/tests/framework) -- [reth e2e-test-utils](https://github.com/paradigmxyz/reth/tree/main/crates/e2e-test-utils) diff --git a/crates/client/txpool/Cargo.toml b/crates/client/txpool/Cargo.toml index 3d7806c6..8a031068 100644 --- a/crates/client/txpool/Cargo.toml +++ b/crates/client/txpool/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] # workspace -base-client-primitives.workspace = true +base-client-node.workspace = true # reth reth.workspace = true diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index e7687ce1..92f366c2 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`TxPoolExtension`] which wires up the transaction pool features //! (tracing ExEx and status RPC) on the Base node builder. -use base_client_primitives::{BaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, OpBuilder}; use tracing::info; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; From 20a3495c56906be0be4cbe77f3caac856164dd1d Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 22:59:22 -0600 Subject: [PATCH 255/262] chore: add GHA/script to check that shared crates dont depend on client/ --- .github/workflows/ci.yml | 15 +++++++++++++++ Justfile | 4 ++++ scripts/check-crate-deps.sh | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100755 scripts/check-crate-deps.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0bd83c3..96180292 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -159,3 +159,18 @@ jobs: with: version: stable - run: just check-udeps + + crate-deps: + name: Crate Dependencies + runs-on: ubuntu-latest + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable + - name: Install just + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 + - run: just check-crate-deps diff --git a/Justfile b/Justfile index fe1ccf12..b231c87e 100644 --- a/Justfile +++ b/Justfile @@ -94,6 +94,10 @@ check-udeps: build-contracts @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: + ./scripts/check-crate-deps.sh + # Watches tests watch-test: build-contracts cargo watch -x test diff --git a/scripts/check-crate-deps.sh b/scripts/check-crate-deps.sh new file mode 100755 index 00000000..f8b478aa --- /dev/null +++ b/scripts/check-crate-deps.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Use cargo metadata to get structured dependency information +# --no-deps: only workspace members, not transitive deps +# --format-version 1: stable JSON format + +echo "Checking that shared crates don't depend on client crates..." + +# Find all violations using jq: +# 1. Select packages in crates/shared/ +# 2. For each, find dependencies with paths pointing to crates/client/ +# 3. Output violations as "shared_crate -> client_crate" +VIOLATIONS=$(cargo metadata --format-version 1 --no-deps | jq -r ' + [.packages[] + | select(.manifest_path | contains("/crates/shared/")) + | . as $pkg + | .dependencies[] + | select(.path) + | select(.path | contains("/crates/client/")) + | "\($pkg.name) -> \(.name)" + ] + | .[] +') + +if [ -n "$VIOLATIONS" ]; then + echo "ERROR: Found dependency boundary violations:" + echo "$VIOLATIONS" | while read -r violation; do + echo " - $violation" + done + echo "" + echo "Shared crates (crates/shared/) must not depend on client crates (crates/client/)" + exit 1 +fi + +echo "All shared crates have valid dependencies (no client crate dependencies)" From 391034c7d1a6def10790dfe371523d3ca4c0053e Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sat, 10 Jan 2026 23:10:54 -0600 Subject: [PATCH 256/262] chore: format features and update lockfile --- Cargo.lock | 95 +++++++++++++++++++++++++++++++++++ crates/client/node/Cargo.toml | 6 +-- 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fbebbda..ae7bfb46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7536,12 +7536,16 @@ dependencies = [ "rayon", "reth-config", "reth-consensus", + "reth-ethereum-primitives", "reth-metrics", "reth-network-p2p", "reth-network-peers", "reth-primitives-traits", + "reth-provider", "reth-storage-api", "reth-tasks", + "reth-testing-utils", + "tempfile", "thiserror 2.0.17", "tokio", "tokio-stream", @@ -7549,6 +7553,64 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-e2e-test-utils" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-signer", + "alloy-signer-local", + "derive_more", + "eyre", + "futures-util", + "jsonrpsee", + "reth-chainspec", + "reth-cli-commands", + "reth-config", + "reth-consensus", + "reth-db", + "reth-db-common", + "reth-engine-local", + "reth-engine-primitives", + "reth-ethereum-primitives", + "reth-network-api", + "reth-network-p2p", + "reth-network-peers", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-rpc-api", + "reth-rpc-builder", + "reth-rpc-eth-api", + "reth-rpc-server-types", + "reth-stages-types", + "reth-tasks", + "reth-tokio-util", + "reth-tracing", + "revm", + "serde_json", + "tempfile", + "tokio", + "tokio-stream", + "tracing", + "url", +] + [[package]] name = "reth-ecies" version = "1.9.3" @@ -7671,6 +7733,7 @@ dependencies = [ "parking_lot", "rayon", "reth-chain-state", + "reth-chainspec", "reth-consensus", "reth-db", "reth-engine-primitives", @@ -7685,9 +7748,13 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-prune", + "reth-prune-types", "reth-revm", + "reth-stages", "reth-stages-api", + "reth-static-file", "reth-tasks", + "reth-tracing", "reth-trie", "reth-trie-parallel", "reth-trie-sparse", @@ -8307,6 +8374,7 @@ dependencies = [ "auto_impl", "derive_more", "futures", + "parking_lot", "reth-consensus", "reth-eth-wire-types", "reth-ethereum-primitives", @@ -8816,6 +8884,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", + "alloy-genesis", "alloy-primitives", "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -8826,6 +8895,7 @@ dependencies = [ "op-revm", "reth-chainspec", "reth-consensus", + "reth-e2e-test-utils", "reth-engine-local", "reth-evm", "reth-network", @@ -8847,11 +8917,13 @@ dependencies = [ "reth-rpc-api", "reth-rpc-engine-api", "reth-rpc-server-types", + "reth-tasks", "reth-tracing", "reth-transaction-pool", "reth-trie-common", "revm", "serde", + "serde_json", "tokio", "url", ] @@ -9165,6 +9237,7 @@ dependencies = [ "reth-db", "reth-db-api", "reth-errors", + "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-execution-types", "reth-fs-util", @@ -9180,7 +9253,9 @@ dependencies = [ "reth-trie", "reth-trie-db", "revm-database", + "revm-state", "strum 0.27.2", + "tokio", "tracing", ] @@ -9624,6 +9699,7 @@ dependencies = [ "num-traits", "rayon", "reqwest", + "reth-chainspec", "reth-codecs", "reth-config", "reth-consensus", @@ -9632,6 +9708,7 @@ dependencies = [ "reth-era", "reth-era-downloader", "reth-era-utils", + "reth-ethereum-primitives", "reth-etl", "reth-evm", "reth-execution-types", @@ -9646,8 +9723,10 @@ dependencies = [ "reth-stages-api", "reth-static-file-types", "reth-storage-errors", + "reth-testing-utils", "reth-trie", "reth-trie-db", + "tempfile", "thiserror 2.0.17", "tokio", "tracing", @@ -9783,6 +9862,22 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "reth-testing-utils" +version = "1.9.3" +source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "rand 0.8.5", + "rand 0.9.2", + "reth-ethereum-primitives", + "reth-primitives-traits", + "secp256k1 0.30.0", +] + [[package]] name = "reth-tokio-util" version = "1.9.3" diff --git a/crates/client/node/Cargo.toml b/crates/client/node/Cargo.toml index bfe488e6..90b60bc6 100644 --- a/crates/client/node/Cargo.toml +++ b/crates/client/node/Cargo.toml @@ -29,18 +29,18 @@ test-utils = [ "reth-db/test-utils", "reth-ipc", "reth-node-core", + "reth-optimism-node/test-utils", "reth-optimism-rpc", "reth-primitives-traits", + "reth-primitives-traits?/test-utils", "reth-provider", + "reth-provider?/test-utils", "reth-rpc-layer", "reth-tracing", "tokio", "tower", "tracing-subscriber", "url", - "reth-optimism-node/test-utils", - "reth-primitives-traits?/test-utils", - "reth-provider?/test-utils" ] [dependencies] From 49f6d1c8da79f9cb90608be5912437394ab24f4c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 16:11:09 -0600 Subject: [PATCH 257/262] review feedback --- bin/node/src/cli.rs | 25 ++++++---- bin/node/src/main.rs | 7 ++- crates/client/flashblocks/Cargo.toml | 4 +- crates/client/node/Cargo.toml | 50 ++++++++++--------- crates/client/node/README.md | 6 +-- crates/client/node/src/lib.rs | 3 -- .../client/node/src/test_utils/constants.rs | 8 ++- crates/client/node/src/test_utils/engine.rs | 4 +- crates/client/node/src/test_utils/fixtures.rs | 14 +++--- crates/client/node/src/test_utils/mod.rs | 7 +-- crates/client/txpool/README.md | 12 +++-- crates/client/txpool/src/extension.rs | 35 +++++++------ crates/client/txpool/src/lib.rs | 2 +- crates/shared/primitives/README.md | 26 ++++++++++ crates/shared/primitives/src/lib.rs | 5 +- .../primitives/src/test_utils/genesis.rs | 5 +- .../shared/primitives/src/test_utils/mod.rs | 2 +- 17 files changed, 130 insertions(+), 85 deletions(-) create mode 100644 crates/shared/primitives/README.md diff --git a/bin/node/src/cli.rs b/bin/node/src/cli.rs index b4385ca1..36aaf70c 100644 --- a/bin/node/src/cli.rs +++ b/bin/node/src/cli.rs @@ -1,7 +1,7 @@ //! Contains the CLI arguments use base_flashblocks::FlashblocksConfig; -use base_txpool::TracingConfig; +use base_txpool::TxpoolConfig; use reth_optimism_node::args::RollupArgs; /// CLI Arguments @@ -46,20 +46,23 @@ impl Args { pub const fn flashblocks_enabled(&self) -> bool { self.websocket_url.is_some() } +} - /// Build FlashblocksConfig from CLI args. - pub fn flashblocks_config(&self) -> Option { - self.websocket_url.as_ref().map(|url| FlashblocksConfig { - websocket_url: url.clone(), - max_pending_blocks_depth: self.max_pending_blocks_depth, +impl From for Option { + fn from(args: Args) -> Self { + args.websocket_url.map(|url| FlashblocksConfig { + websocket_url: url, + max_pending_blocks_depth: args.max_pending_blocks_depth, }) } +} - /// Build TracingConfig from CLI args. - pub const fn tracing_config(&self) -> TracingConfig { - TracingConfig { - enabled: self.enable_transaction_tracing, - logs_enabled: self.enable_transaction_tracing_logs, +impl From for TxpoolConfig { + fn from(args: Args) -> Self { + Self { + tracing_enabled: args.enable_transaction_tracing, + tracing_logs_enabled: args.enable_transaction_tracing_logs, + sequencer_rpc: args.rollup_args.sequencer, } } } diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 21ab8b92..d84e25b1 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -30,15 +30,14 @@ fn main() { let flashblocks_cell: FlashblocksCell> = Arc::new(OnceCell::new()); - let sequencer_rpc = args.rollup_args.sequencer.clone(); - let tracing_config = args.tracing_config(); + let txpool_config = args.clone().into(); let metering_enabled = args.enable_metering; - let flashblocks_config = args.flashblocks_config(); + let flashblocks_config = args.clone().into(); let mut runner = BaseNodeRunner::new(args.rollup_args); // Feature extensions (FlashblocksExtension must be last - uses replace_configured) - runner.install_ext(Box::new(TxPoolExtension::new(tracing_config, sequencer_rpc))); + runner.install_ext(Box::new(TxPoolExtension::new(txpool_config))); runner.install_ext(Box::new(MeteringExtension::new(metering_enabled))); runner .install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, flashblocks_config))); diff --git a/crates/client/flashblocks/Cargo.toml b/crates/client/flashblocks/Cargo.toml index a8b15150..7a0604c4 100644 --- a/crates/client/flashblocks/Cargo.toml +++ b/crates/client/flashblocks/Cargo.toml @@ -14,8 +14,8 @@ workspace = true [features] test-utils = [ "base-client-node/test-utils", - "derive_more", - "eyre", + "dep:derive_more", + "dep:eyre", "reth-evm/test-utils", "reth-primitives/test-utils", ] diff --git a/crates/client/node/Cargo.toml b/crates/client/node/Cargo.toml index 90b60bc6..5ac4c765 100644 --- a/crates/client/node/Cargo.toml +++ b/crates/client/node/Cargo.toml @@ -13,34 +13,36 @@ workspace = true [features] test-utils = [ - "alloy-eips", - "alloy-genesis", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-client", - "alloy-rpc-types", - "alloy-rpc-types-engine", - "alloy-signer", "base-primitives/test-utils", - "chrono", - "jsonrpsee", - "op-alloy-network", - "op-alloy-rpc-types-engine", + "dep:alloy-eips", + "dep:alloy-genesis", + "dep:alloy-primitives", + "dep:alloy-provider", + "dep:alloy-rpc-client", + "dep:alloy-rpc-types", + "dep:alloy-rpc-types-engine", + "dep:alloy-signer", + "dep:base-primitives", + "dep:chrono", + "dep:jsonrpsee", + "dep:op-alloy-network", + "dep:op-alloy-rpc-types-engine", + "dep:reth-ipc", + "dep:reth-node-core", + "dep:reth-optimism-primitives", + "dep:reth-optimism-rpc", + "dep:reth-primitives-traits", + "dep:reth-provider", + "dep:reth-rpc-layer", + "dep:reth-tracing", + "dep:tokio", + "dep:tower", + "dep:tracing-subscriber", + "dep:url", "reth-db/test-utils", - "reth-ipc", - "reth-node-core", "reth-optimism-node/test-utils", - "reth-optimism-rpc", - "reth-primitives-traits", "reth-primitives-traits?/test-utils", - "reth-provider", "reth-provider?/test-utils", - "reth-rpc-layer", - "reth-tracing", - "tokio", - "tower", - "tracing-subscriber", - "url", ] [dependencies] @@ -59,7 +61,7 @@ reth-provider = { workspace = true, optional = true } reth-node-core = { workspace = true, optional = true } reth-primitives-traits = { workspace = true, optional = true } reth-optimism-rpc = { workspace = true, features = ["client"], optional = true } -reth-optimism-primitives.workspace = true +reth-optimism-primitives = { workspace = true, optional = true } # alloy alloy-primitives = { workspace = true, optional = true } diff --git a/crates/client/node/README.md b/crates/client/node/README.md index 7b10150a..b8fa338e 100644 --- a/crates/client/node/README.md +++ b/crates/client/node/README.md @@ -1,4 +1,4 @@ -# `base-client-primitives` +# `base-client-node` CI MIT License @@ -14,7 +14,7 @@ Primitive types and traits for Base node runner extensions. Provides extension t Configuration types are located in their respective feature crates: - **`FlashblocksConfig`**: in `base-flashblocks` crate -- **`TracingConfig`**: in `base-txpool` crate +- **`TxpoolConfig`**: in `base-txpool` crate ## Usage @@ -22,7 +22,7 @@ Add the dependency to your `Cargo.toml`: ```toml [dependencies] -base-client-primitives = { git = "https://github.com/base/node-reth" } +base-client-node = { git = "https://github.com/base/node-reth" } ``` Implement a custom node extension: diff --git a/crates/client/node/src/lib.rs b/crates/client/node/src/lib.rs index 25f03b3d..607989cf 100644 --- a/crates/client/node/src/lib.rs +++ b/crates/client/node/src/lib.rs @@ -3,9 +3,6 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -// Silence false positive - reth_optimism_primitives is used in test_utils module -use reth_optimism_primitives as _; - mod extension; pub use extension::BaseNodeExtension; diff --git a/crates/client/node/src/test_utils/constants.rs b/crates/client/node/src/test_utils/constants.rs index 5572e4fc..7ceb6c7f 100644 --- a/crates/client/node/src/test_utils/constants.rs +++ b/crates/client/node/src/test_utils/constants.rs @@ -10,6 +10,11 @@ pub const BLOCK_TIME_SECONDS: u64 = 2; /// Gas limit for test blocks. pub const GAS_LIMIT: u64 = 200_000_000; +// Test Account Balances + +/// Balance in ETH for test accounts in fixtures. +pub const TEST_ACCOUNT_BALANCE_ETH: u64 = 100; + // Timing / Delays /// Delay in milliseconds to wait for node startup. @@ -20,8 +25,7 @@ pub const BLOCK_BUILD_DELAY_MS: u64 = 100; // Engine API /// All-zeros secret for local testing only. -pub const DEFAULT_JWT_SECRET: &str = - "0x0000000000000000000000000000000000000000000000000000000000000000"; +pub const DEFAULT_JWT_SECRET: B256 = B256::ZERO; // L1 Block Info (OP Stack) diff --git a/crates/client/node/src/test_utils/engine.rs b/crates/client/node/src/test_utils/engine.rs index 76532653..53ba6711 100644 --- a/crates/client/node/src/test_utils/engine.rs +++ b/crates/client/node/src/test_utils/engine.rs @@ -97,7 +97,7 @@ impl EngineApi { /// Build a new HTTP-backed Engine API client from the provided URL. pub fn new(engine_url: String) -> Result { let url: Url = engine_url.parse()?; - let jwt_secret: JwtSecret = DEFAULT_JWT_SECRET.parse()?; + let jwt_secret = JwtSecret::from_hex(DEFAULT_JWT_SECRET.to_string())?; Ok(Self { address: EngineAddress::Http(url), jwt_secret, _phantom: PhantomData }) } @@ -106,7 +106,7 @@ impl EngineApi { impl EngineApi { /// Build a new IPC-backed Engine API client using the IPC socket path. pub fn new(path: String) -> Result { - let jwt_secret: JwtSecret = DEFAULT_JWT_SECRET.parse()?; + let jwt_secret = JwtSecret::from_hex(DEFAULT_JWT_SECRET.to_string())?; Ok(Self { address: EngineAddress::Ipc(path), jwt_secret, _phantom: PhantomData }) } diff --git a/crates/client/node/src/test_utils/fixtures.rs b/crates/client/node/src/test_utils/fixtures.rs index 14d67ab3..eff8a258 100644 --- a/crates/client/node/src/test_utils/fixtures.rs +++ b/crates/client/node/src/test_utils/fixtures.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use alloy_genesis::GenesisAccount; -use alloy_primitives::U256; +use alloy_primitives::{U256, utils::Unit}; use base_primitives::{Account, build_test_genesis}; use reth::api::{NodeTypes, NodeTypesWithDBAdapter}; use reth_db::{ @@ -14,21 +14,23 @@ use reth_db::{ use reth_optimism_chainspec::OpChainSpec; use reth_provider::{ProviderFactory, providers::StaticFileProvider}; +use crate::test_utils::{GENESIS_GAS_LIMIT, TEST_ACCOUNT_BALANCE_ETH}; + /// Creates a test chain spec with pre-funded test accounts. pub fn load_chain_spec() -> Arc { + let test_account_balance: U256 = + Unit::ETHER.wei().saturating_mul(U256::from(TEST_ACCOUNT_BALANCE_ETH)); + let genesis = build_test_genesis() .extend_accounts( Account::all() .into_iter() .map(|a| { - ( - a.address(), - GenesisAccount::default().with_balance(U256::from(1_000_000_000_u64)), - ) + (a.address(), GenesisAccount::default().with_balance(test_account_balance)) }) .collect::>(), ) - .with_gas_limit(100_000_000); + .with_gas_limit(GENESIS_GAS_LIMIT); Arc::new(OpChainSpec::from_genesis(genesis)) } diff --git a/crates/client/node/src/test_utils/mod.rs b/crates/client/node/src/test_utils/mod.rs index 7976c184..5aff3eb1 100644 --- a/crates/client/node/src/test_utils/mod.rs +++ b/crates/client/node/src/test_utils/mod.rs @@ -8,15 +8,16 @@ // Re-export from base-primitives for backwards compatibility pub use base_primitives::{ - AccessListContract, Account, ContractFactory, DEVNET_CHAIN_ID, DoubleCounter, Logic, Logic2, - Minimal7702Account, MockERC20, Proxy, SimpleStorage, TransparentUpgradeableProxy, - build_test_genesis, + AccessListContract, Account, ContractFactory, DEVNET_CHAIN_ID, DoubleCounter, + GENESIS_GAS_LIMIT, Logic, Logic2, Minimal7702Account, MockERC20, Proxy, SimpleStorage, + TransparentUpgradeableProxy, build_test_genesis, }; mod constants; pub use constants::{ BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, DEFAULT_JWT_SECRET, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, L1_BLOCK_INFO_DEPOSIT_TX_HASH, NODE_STARTUP_DELAY_MS, NamedChain, + TEST_ACCOUNT_BALANCE_ETH, }; mod engine; diff --git a/crates/client/txpool/README.md b/crates/client/txpool/README.md index 11ed50f5..2ae97087 100644 --- a/crates/client/txpool/README.md +++ b/crates/client/txpool/README.md @@ -27,10 +27,14 @@ cargo run -p node --release -- \ From code, wire the extension into the node builder: ```rust,ignore -use base_txpool::{TracingConfig, TxPoolExtension}; - -let tracing = TracingConfig { enabled: true, logs_enabled: true }; -let ext = TxPoolExtension::new(tracing, None); +use base_txpool::{TxpoolConfig, TxPoolExtension}; + +let config = TxpoolConfig { + tracing_enabled: true, + tracing_logs_enabled: true, + sequencer_rpc: None, +}; +let ext = TxPoolExtension::new(config); let builder = Box::new(ext).apply(builder); ``` diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index 92f366c2..d5e9a95b 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -6,43 +6,46 @@ use tracing::info; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; -/// Transaction tracing toggles. -#[derive(Debug, Clone, Copy)] -pub struct TracingConfig { +/// Transaction pool configuration. +#[derive(Debug, Clone)] +pub struct TxpoolConfig { /// Enables the transaction tracing ExEx. - pub enabled: bool, + pub tracing_enabled: bool, /// Emits `info`-level logs for the tracing ExEx when enabled. - pub logs_enabled: bool, + pub tracing_logs_enabled: bool, + /// Sequencer RPC endpoint for transaction status proxying. + pub sequencer_rpc: Option, } /// Helper struct that wires the transaction pool features into the node builder. #[derive(Debug, Clone)] pub struct TxPoolExtension { - /// Transaction tracing configuration flags. - pub tracing: TracingConfig, - /// Sequencer RPC endpoint for transaction status proxying. - pub sequencer_rpc: Option, + /// Transaction pool configuration. + config: TxpoolConfig, } impl TxPoolExtension { /// Creates a new transaction pool extension helper. - pub const fn new(tracing: TracingConfig, sequencer_rpc: Option) -> Self { - Self { tracing, sequencer_rpc } + #[allow(clippy::missing_const_for_fn)] + pub fn new(config: TxpoolConfig) -> Self { + Self { config } } } impl BaseNodeExtension for TxPoolExtension { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder { - let tracing = self.tracing; - let sequencer_rpc = self.sequencer_rpc; + let config = self.config; // Install the tracing ExEx if enabled - let builder = builder.install_exex_if(tracing.enabled, "tracex", move |ctx| async move { - Ok(tracex_exex(ctx, tracing.logs_enabled)) - }); + let logs_enabled = config.tracing_logs_enabled; + let builder = + builder.install_exex_if(config.tracing_enabled, "tracex", move |ctx| async move { + Ok(tracex_exex(ctx, logs_enabled)) + }); // Extend with RPC modules + let sequencer_rpc = config.sequencer_rpc; builder.extend_rpc_modules(move |ctx| { info!(message = "Starting Transaction Status RPC"); let proxy_api = TransactionStatusApiImpl::new(sequencer_rpc, ctx.pool().clone()) diff --git a/crates/client/txpool/src/lib.rs b/crates/client/txpool/src/lib.rs index 96f35667..3757caa4 100644 --- a/crates/client/txpool/src/lib.rs +++ b/crates/client/txpool/src/lib.rs @@ -18,4 +18,4 @@ mod tracker; pub use tracker::Tracker; mod extension; -pub use extension::{TracingConfig, TxPoolExtension}; +pub use extension::{TxPoolExtension, TxpoolConfig}; diff --git a/crates/shared/primitives/README.md b/crates/shared/primitives/README.md new file mode 100644 index 00000000..d6e37ce3 --- /dev/null +++ b/crates/shared/primitives/README.md @@ -0,0 +1,26 @@ +# `base-primitives` + +CI +MIT License + +Shared primitives and test utilities for node-reth crates. + +## Usage + +Add the dependency to your `Cargo.toml`: + +```toml +[dependencies] +base-primitives = { git = "https://github.com/base/node-reth" } +``` + +For test utilities: + +```toml +[dev-dependencies] +base-primitives = { git = "https://github.com/base/node-reth", features = ["test-utils"] } +``` + +## License + +Licensed under the [MIT License](https://github.com/base/node-reth/blob/main/LICENSE). diff --git a/crates/shared/primitives/src/lib.rs b/crates/shared/primitives/src/lib.rs index 3c53919f..273a68db 100644 --- a/crates/shared/primitives/src/lib.rs +++ b/crates/shared/primitives/src/lib.rs @@ -1,5 +1,6 @@ -//! Shared primitives and test utilities for node-reth crates. - +#![doc = include_str!("../README.md")] +#![doc(issue_tracker_base_url = "https://github.com/base/node-reth/issues/")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #[cfg(any(test, feature = "test-utils"))] diff --git a/crates/shared/primitives/src/test_utils/genesis.rs b/crates/shared/primitives/src/test_utils/genesis.rs index cd4f5902..df558287 100644 --- a/crates/shared/primitives/src/test_utils/genesis.rs +++ b/crates/shared/primitives/src/test_utils/genesis.rs @@ -10,6 +10,9 @@ use super::Account; /// Chain ID for devnet test network. pub const DEVNET_CHAIN_ID: u64 = 84538453; +/// Gas limit for genesis block configuration. +pub const GENESIS_GAS_LIMIT: u64 = 100_000_000; + /// Builds a test genesis configuration programmatically. /// /// Creates a Base Sepolia-like genesis with: @@ -84,7 +87,7 @@ pub fn build_test_genesis() -> Genesis { Genesis { config, alloc, - gas_limit: 100_000_000, + gas_limit: GENESIS_GAS_LIMIT, difficulty: U256::ZERO, nonce: 0, timestamp: 0, diff --git a/crates/shared/primitives/src/test_utils/mod.rs b/crates/shared/primitives/src/test_utils/mod.rs index 8437cffd..8626c87e 100644 --- a/crates/shared/primitives/src/test_utils/mod.rs +++ b/crates/shared/primitives/src/test_utils/mod.rs @@ -4,7 +4,7 @@ mod accounts; pub use accounts::Account; mod genesis; -pub use genesis::{DEVNET_CHAIN_ID, build_test_genesis}; +pub use genesis::{DEVNET_CHAIN_ID, GENESIS_GAS_LIMIT, build_test_genesis}; mod contracts; pub use contracts::{ From b439aa8683023adbc5db30860319b38d27a4473c Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 16:15:38 -0600 Subject: [PATCH 258/262] chore: inline config extraction in extension constructors Move config extraction directly into extension constructor calls to reduce intermediate variables in main.rs. --- bin/node/src/main.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index d84e25b1..40ddc98f 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -30,17 +30,12 @@ fn main() { let flashblocks_cell: FlashblocksCell> = Arc::new(OnceCell::new()); - let txpool_config = args.clone().into(); - let metering_enabled = args.enable_metering; - let flashblocks_config = args.clone().into(); - - let mut runner = BaseNodeRunner::new(args.rollup_args); + let mut runner = BaseNodeRunner::new(args.rollup_args.clone()); // Feature extensions (FlashblocksExtension must be last - uses replace_configured) - runner.install_ext(Box::new(TxPoolExtension::new(txpool_config))); - runner.install_ext(Box::new(MeteringExtension::new(metering_enabled))); - runner - .install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, flashblocks_config))); + runner.install_ext(Box::new(TxPoolExtension::new(args.clone().into()))); + runner.install_ext(Box::new(MeteringExtension::new(args.enable_metering))); + runner.install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, args.into()))); let handle = runner.run(builder); handle.await From 7aa0f89c7aa09d56ebef2debb2848f6b78214c06 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 16:42:06 -0600 Subject: [PATCH 259/262] migrate back to generic install_ext --- Cargo.lock | 1 - bin/node/Cargo.toml | 1 - bin/node/src/main.rs | 16 +++++----------- crates/client/flashblocks/src/extension.rs | 14 +++++++++++--- crates/client/metering/src/extension.rs | 10 +++++++++- crates/client/metering/tests/meter.rs | 2 +- crates/client/metering/tests/meter_block.rs | 2 +- crates/client/metering/tests/meter_rpc.rs | 3 +-- crates/client/node/src/extension.rs | 13 ++++++++++++- crates/client/node/src/lib.rs | 2 +- crates/client/node/src/runner.rs | 6 +++--- crates/client/node/src/test_utils/harness.rs | 12 ++++++++++-- crates/client/txpool/src/extension.rs | 13 ++++++++++--- 13 files changed, 64 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae7bfb46..1b53991d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1733,7 +1733,6 @@ dependencies = [ "base-metering", "base-txpool", "clap", - "once_cell", "reth-cli-util", "reth-optimism-cli", "reth-optimism-node", diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 08e3c1a9..43f2ce23 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -27,7 +27,6 @@ reth-cli-util.workspace = true # misc clap.workspace = true -once_cell.workspace = true [features] default = [] diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index 40ddc98f..10b591ee 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -5,13 +5,10 @@ pub mod cli; -use std::sync::Arc; - -use base_client_node::{BaseNodeRunner, OpProvider}; -use base_flashblocks::{FlashblocksCell, FlashblocksExtension, FlashblocksState}; +use base_client_node::BaseNodeRunner; +use base_flashblocks::FlashblocksExtension; use base_metering::MeteringExtension; use base_txpool::TxPoolExtension; -use once_cell::sync::OnceCell; #[global_allocator] static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); @@ -27,15 +24,12 @@ fn main() { // Step 3: Hand the parsed CLI to the node runner so it can build and launch the Base node. cli.run(|builder, args| async move { - let flashblocks_cell: FlashblocksCell> = - Arc::new(OnceCell::new()); - let mut runner = BaseNodeRunner::new(args.rollup_args.clone()); // Feature extensions (FlashblocksExtension must be last - uses replace_configured) - runner.install_ext(Box::new(TxPoolExtension::new(args.clone().into()))); - runner.install_ext(Box::new(MeteringExtension::new(args.enable_metering))); - runner.install_ext(Box::new(FlashblocksExtension::new(flashblocks_cell, args.into()))); + runner.install_ext::(args.clone().into()); + runner.install_ext::(args.enable_metering); + runner.install_ext::(args.into()); let handle = runner.run(builder); handle.await diff --git a/crates/client/flashblocks/src/extension.rs b/crates/client/flashblocks/src/extension.rs index ab72aaa7..a9f68d40 100644 --- a/crates/client/flashblocks/src/extension.rs +++ b/crates/client/flashblocks/src/extension.rs @@ -3,7 +3,7 @@ use std::sync::Arc; -use base_client_node::{BaseNodeExtension, OpBuilder, OpProvider}; +use base_client_node::{BaseNodeExtension, FromExtensionConfig, OpBuilder, OpProvider}; use futures_util::TryStreamExt; use once_cell::sync::OnceCell; use reth_exex::ExExEvent; @@ -31,9 +31,9 @@ pub struct FlashblocksConfig { #[derive(Debug, Clone)] pub struct FlashblocksExtension { /// Shared Flashblocks state cache. - pub cell: FlashblocksCell>, + cell: FlashblocksCell>, /// Optional Flashblocks configuration. - pub config: Option, + config: Option, } impl FlashblocksExtension { @@ -122,3 +122,11 @@ impl BaseNodeExtension for FlashblocksExtension { }) } } + +impl FromExtensionConfig for FlashblocksExtension { + type Config = Option; + + fn from_config(config: Self::Config) -> Self { + Self::new(Arc::new(OnceCell::new()), config) + } +} diff --git a/crates/client/metering/src/extension.rs b/crates/client/metering/src/extension.rs index 69bdc7cd..c089954c 100644 --- a/crates/client/metering/src/extension.rs +++ b/crates/client/metering/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`MeteringExtension`] which wires up the metering RPC surface //! on the Base node builder. -use base_client_node::{BaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, FromExtensionConfig, OpBuilder}; use tracing::info; use crate::{MeteringApiImpl, MeteringApiServer}; @@ -35,3 +35,11 @@ impl BaseNodeExtension for MeteringExtension { }) } } + +impl FromExtensionConfig for MeteringExtension { + type Config = bool; + + fn from_config(enabled: Self::Config) -> Self { + Self::new(enabled) + } +} diff --git a/crates/client/metering/tests/meter.rs b/crates/client/metering/tests/meter.rs index 8b541804..3ab38191 100644 --- a/crates/client/metering/tests/meter.rs +++ b/crates/client/metering/tests/meter.rs @@ -16,7 +16,7 @@ async fn setup() -> eyre::Result { let chain_spec = load_chain_spec(); TestHarness::builder() .with_chain_spec(chain_spec) - .with_extension(MeteringExtension::new(true)) + .with_ext::(true) .build() .await } diff --git a/crates/client/metering/tests/meter_block.rs b/crates/client/metering/tests/meter_block.rs index c8078b60..704e3b77 100644 --- a/crates/client/metering/tests/meter_block.rs +++ b/crates/client/metering/tests/meter_block.rs @@ -14,7 +14,7 @@ async fn setup() -> eyre::Result { let chain_spec = load_chain_spec(); TestHarness::builder() .with_chain_spec(chain_spec) - .with_extension(MeteringExtension::new(true)) + .with_ext::(true) .build() .await } diff --git a/crates/client/metering/tests/meter_rpc.rs b/crates/client/metering/tests/meter_rpc.rs index db619d30..3fc528eb 100644 --- a/crates/client/metering/tests/meter_rpc.rs +++ b/crates/client/metering/tests/meter_rpc.rs @@ -27,8 +27,7 @@ fn create_bundle(txs: Vec, block_number: u64, min_timestamp: Option) /// Set up a test harness with the metering extension and return an RPC client. async fn setup() -> eyre::Result<(TestHarness, RpcClient)> { - let harness = - TestHarness::builder().with_extension(MeteringExtension::new(true)).build().await?; + let harness = TestHarness::builder().with_ext::(true).build().await?; let client = harness.rpc_client()?; Ok((harness, client)) diff --git a/crates/client/node/src/extension.rs b/crates/client/node/src/extension.rs index 5e8e809e..beec0801 100644 --- a/crates/client/node/src/extension.rs +++ b/crates/client/node/src/extension.rs @@ -4,8 +4,19 @@ use std::fmt::Debug; use crate::OpBuilder; -/// A node builder extension that can apply additional wiring to the builder. +/// Customizes the node builder before launch. +/// +/// Register extensions via [`BaseNodeRunner::install_ext`]. pub trait BaseNodeExtension: Send + Sync + Debug { /// Applies the extension to the supplied builder. fn apply(self: Box, builder: OpBuilder) -> OpBuilder; } + +/// An extension that can be built from a config. +pub trait FromExtensionConfig: BaseNodeExtension + Sized { + /// Configuration type used to construct this extension. + type Config; + + /// Creates a new extension from the provided configuration. + fn from_config(config: Self::Config) -> Self; +} diff --git a/crates/client/node/src/lib.rs b/crates/client/node/src/lib.rs index 607989cf..eeeb27f4 100644 --- a/crates/client/node/src/lib.rs +++ b/crates/client/node/src/lib.rs @@ -4,7 +4,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] mod extension; -pub use extension::BaseNodeExtension; +pub use extension::{BaseNodeExtension, FromExtensionConfig}; mod handle; pub use handle::BaseNodeHandle; diff --git a/crates/client/node/src/runner.rs b/crates/client/node/src/runner.rs index 7d712d82..dcca0009 100644 --- a/crates/client/node/src/runner.rs +++ b/crates/client/node/src/runner.rs @@ -8,7 +8,7 @@ use reth::{ use reth_optimism_node::{OpNode, args::RollupArgs}; use tracing::info; -use crate::{BaseNodeBuilder, BaseNodeExtension, BaseNodeHandle}; +use crate::{BaseNodeBuilder, BaseNodeExtension, BaseNodeHandle, FromExtensionConfig}; /// Wraps the Base node configuration and orchestrates builder wiring. #[derive(Debug)] @@ -26,8 +26,8 @@ impl BaseNodeRunner { } /// Registers a new builder extension. - pub fn install_ext(&mut self, extension: Box) { - self.extensions.push(extension); + pub fn install_ext(&mut self, config: T::Config) { + self.extensions.push(Box::new(T::from_config(config))); } /// Applies all Base-specific wiring to the supplied builder, launches the node, and returns a diff --git a/crates/client/node/src/test_utils/harness.rs b/crates/client/node/src/test_utils/harness.rs index 16aed723..76ddc3f3 100644 --- a/crates/client/node/src/test_utils/harness.rs +++ b/crates/client/node/src/test_utils/harness.rs @@ -18,7 +18,7 @@ use reth_primitives_traits::{Block as BlockT, RecoveredBlock}; use tokio::time::sleep; use crate::{ - BaseNodeExtension, + BaseNodeExtension, FromExtensionConfig, test_utils::{ BLOCK_BUILD_DELAY_MS, BLOCK_TIME_SECONDS, GAS_LIMIT, L1_BLOCK_INFO_DEPOSIT_TX, NODE_STARTUP_DELAY_MS, @@ -41,7 +41,15 @@ impl TestHarnessBuilder { Self::default() } - /// Add an extension to be applied during node launch. + /// Add an extension to be applied during node launch using its config type. + pub fn with_ext(mut self, config: T::Config) -> Self { + self.extensions.push(Box::new(T::from_config(config))); + self + } + + /// Add a pre-constructed extension to be applied during node launch. + /// + /// Prefer [`with_ext`](Self::with_ext) for simpler configuration. pub fn with_extension(mut self, ext: impl BaseNodeExtension + 'static) -> Self { self.extensions.push(Box::new(ext)); self diff --git a/crates/client/txpool/src/extension.rs b/crates/client/txpool/src/extension.rs index d5e9a95b..3800db71 100644 --- a/crates/client/txpool/src/extension.rs +++ b/crates/client/txpool/src/extension.rs @@ -1,7 +1,7 @@ //! Contains the [`TxPoolExtension`] which wires up the transaction pool features //! (tracing ExEx and status RPC) on the Base node builder. -use base_client_node::{BaseNodeExtension, OpBuilder}; +use base_client_node::{BaseNodeExtension, FromExtensionConfig, OpBuilder}; use tracing::info; use crate::{TransactionStatusApiImpl, TransactionStatusApiServer, tracex_exex}; @@ -26,8 +26,7 @@ pub struct TxPoolExtension { impl TxPoolExtension { /// Creates a new transaction pool extension helper. - #[allow(clippy::missing_const_for_fn)] - pub fn new(config: TxpoolConfig) -> Self { + pub const fn new(config: TxpoolConfig) -> Self { Self { config } } } @@ -55,3 +54,11 @@ impl BaseNodeExtension for TxPoolExtension { }) } } + +impl FromExtensionConfig for TxPoolExtension { + type Config = TxpoolConfig; + + fn from_config(config: Self::Config) -> Self { + Self::new(config) + } +} From d0e7185aa2de1bcd038e91117f5e4c44c1d9aedd Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 18:28:22 -0600 Subject: [PATCH 260/262] minimal changes to integrate project --- .github/workflows/builder.yml | 141 + Cargo.lock | 3591 ++++++++++++++++-- Cargo.toml | 118 +- Justfile | 56 +- crates/builder/op-rbuilder/Cargo.toml | 54 +- crates/builder/p2p/Cargo.toml | 8 +- crates/builder/tdx-quote-provider/Cargo.toml | 8 +- crates/builder/tdx-quote-provider/README.md | 4 +- rustfmt.toml | 5 + scripts/check-crate-deps.sh | 29 +- 10 files changed, 3591 insertions(+), 423 deletions(-) create mode 100644 .github/workflows/builder.yml diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml new file mode 100644 index 00000000..97c47c2e --- /dev/null +++ b/.github/workflows/builder.yml @@ -0,0 +1,141 @@ +name: Builder CI + +on: + push: + pull_request: + merge_group: + +env: + CARGO_TERM_COLOR: always + # Zero out OTEL env vars to prevent local environment interference in tests + OTEL_EXPORTER_OTLP_ENDPOINT: "" + OTEL_EXPORTER_OTLP_HEADERS: "" + OTEL_SDK_DISABLED: "true" + +permissions: + contents: read + +jobs: + build-builder: + name: Build Builder Crates + runs-on: ubuntu-24.04 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable + with: + components: clippy + + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + cache-on-failure: true + add-rust-environment-hash-key: "false" + key: builder-stable-${{ hashFiles('Cargo.lock') }} + + - name: Install just + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 + + - name: Build builder crates + run: just build-builder + + test-builder: + name: Test Builder Crates + runs-on: ubuntu-24.04 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable + + - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + cache-on-failure: true + add-rust-environment-hash-key: "false" + key: builder-stable-${{ hashFiles('Cargo.lock') }} + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@50d5a8956f2e319df19e6b57539d7e2acb9f8c1e # v1.5.0 + with: + version: stable + + - name: Install just + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 + + - name: Run builder tests + run: just test-builder + env: + TESTS_TEMP_DIR: ${{ github.workspace }} + + clippy-builder: + name: Clippy Builder Crates + runs-on: ubuntu-24.04 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + + - uses: dtolnay/rust-toolchain@0c3131df9e5407c0c36352032d04af846dbe0fb7 # nightly + with: + components: clippy + + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + cache-on-failure: true + add-rust-environment-hash-key: "false" + key: builder-nightly-${{ hashFiles('Cargo.lock') }} + + - name: Install just + uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 + + - name: Run clippy on builder crates + run: just check-clippy-builder diff --git a/Cargo.lock b/Cargo.lock index 1b53991d..84506a53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,13 +95,36 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c97aa0031055a663e364890f2bc15879d6ec38dae9fbeece68fcc82d9cdb81" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-trie", +] + [[package]] name = "alloy-chains" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd208e8a87fbc2ca1a3822dd1ea03b0a7a4a841e6fa70db2c236dd30ae2e7018" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "num_enum", "serde", @@ -115,7 +138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e30ab0d3e3c32976f67fc1a96179989e45a69594af42003a6663332f9b0bb9d" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", "alloy-trie", @@ -144,7 +167,7 @@ checksum = "c20736b1f9d927d875d8777ef0c2250d4c57ea828529a9dbfa2c628db57b911e" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", "arbitrary", @@ -159,13 +182,13 @@ checksum = "008aba161fce2a0d94956ae09d7d7a09f8fbdf18acbef921809ef126d6cdaf97" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-json-abi", + "alloy-json-abi 1.5.2", "alloy-network", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-types-eth", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "alloy-transport", "futures", "futures-util", @@ -173,16 +196,29 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "alloy-core" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4087016b0896051dd3d03e0bedda2f4d4d1689af8addc8450288c63a9e5f68" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi 1.5.2", + "alloy-primitives 1.5.2", + "alloy-rlp", + "alloy-sol-types 1.5.2", +] + [[package]] name = "alloy-dyn-abi" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "369f5707b958927176265e8a58627fc6195e5dfa5c55689396e68b241b3a72e6" dependencies = [ - "alloy-json-abi", - "alloy-primitives", - "alloy-sol-type-parser", - "alloy-sol-types", + "alloy-json-abi 1.5.2", + "alloy-primitives 1.5.2", + "alloy-sol-type-parser 1.5.2", + "alloy-sol-types 1.5.2", "derive_more", "itoa", "serde", @@ -196,7 +232,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "crc", @@ -211,7 +247,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "borsh", @@ -225,7 +261,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "borsh", @@ -242,7 +278,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6adac476434bf024279164dcdca299309f0c7d1e3557024eb7a83f8d9d01c6b5" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "borsh", "serde", @@ -257,7 +293,7 @@ dependencies = [ "alloy-eip2124", "alloy-eip2930", "alloy-eip7702", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", "arbitrary", @@ -284,10 +320,10 @@ dependencies = [ "alloy-eips", "alloy-hardforks", "alloy-op-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "auto_impl", "derive_more", "op-alloy-consensus", @@ -304,7 +340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a838301c4e2546c96db1848f18ffe9f722f2fccd9715b83d4bf269a2cf00b5a1" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-serde", "alloy-trie", "borsh", @@ -320,20 +356,32 @@ checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" dependencies = [ "alloy-chains", "alloy-eip2124", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "dyn-clone", "serde", ] +[[package]] +name = "alloy-json-abi" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4584e3641181ff073e9d5bec5b3b8f78f9749d9fb108a1cfbc4399a4a139c72a" +dependencies = [ + "alloy-primitives 0.8.26", + "alloy-sol-type-parser 0.8.26", + "serde", + "serde_json", +] + [[package]] name = "alloy-json-abi" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e3cf01219c966f95a460c95f1d4c30e12f6c18150c21a30b768af2a2a29142" dependencies = [ - "alloy-primitives", - "alloy-sol-type-parser", + "alloy-primitives 1.5.2", + "alloy-sol-type-parser 1.5.2", "serde", "serde_json", ] @@ -344,8 +392,8 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60f045b69b5e80b8944b25afe74ae6b974f3044d84b4a7a113da04745b2524cc" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-primitives 1.5.2", + "alloy-sol-types 1.5.2", "http", "serde", "serde_json", @@ -364,12 +412,12 @@ dependencies = [ "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-any", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "async-trait", "auto_impl", "derive_more", @@ -387,7 +435,7 @@ checksum = "5e9762ac5cca67b0f6ab614f7f8314942eead1c8eeef61511ea43a6ff048dbe0" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-serde", "serde", ] @@ -402,7 +450,7 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-op-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "op-alloy-consensus", "op-revm", @@ -418,10 +466,37 @@ checksum = "6472c610150c4c4c15be9e1b964c9b78068f933bda25fb9cdf09b9ac2bb66f36" dependencies = [ "alloy-chains", "alloy-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", ] +[[package]] +name = "alloy-primitives" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777d58b30eb9a4db0e5f59bc30e8c2caef877fee7dc8734cf242a51a60f22e05" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash 0.1.5", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.8.5", + "ruint", + "rustc-hash 2.1.1", + "serde", + "sha3", + "tiny-keccak", +] + [[package]] name = "alloy-primitives" version = "1.5.2" @@ -447,7 +522,7 @@ dependencies = [ "rand 0.9.2", "rapidhash", "ruint", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "sha3", "tiny-keccak", @@ -465,12 +540,14 @@ dependencies = [ "alloy-json-rpc", "alloy-network", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-pubsub", "alloy-rpc-client", + "alloy-rpc-types-engine", "alloy-rpc-types-eth", + "alloy-rpc-types-txpool", "alloy-signer", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "alloy-transport", "alloy-transport-http", "alloy-transport-ipc", @@ -502,7 +579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4082778c908aa801a1f9fdc85d758812842ab4b2aaba58e9dbe7626d708ab7e1" dependencies = [ "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-transport", "auto_impl", "bimap", @@ -512,7 +589,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", "wasmtimer", ] @@ -546,7 +623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26dd083153d2cb73cce1516f5a3f9c3af74764a2761d901581a355777468bd8f" dependencies = [ "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-pubsub", "alloy-transport", "alloy-transport-http", @@ -559,7 +636,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", "url", "wasmtimer", @@ -571,7 +648,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c998214325cfee1fbe61e5abaed3a435f4ca746ac7399b46feb57c364552452" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-serde", @@ -585,7 +662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "730a38742dc0753f25b8ce7330c2fa88d79f165c5fc2f19f3d35291739c42e83" dependencies = [ "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "serde", "serde_json", ] @@ -596,7 +673,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2b03d65fcf579fbf17d3aac32271f99e2b562be04097436cd6e766b3e06613b" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -620,7 +697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b6654644613f33fd2e6f333f4ce8ad0a26f036c0513699d7bc168bba18d412d" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "derive_more", "ethereum_ssz", @@ -639,7 +716,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467025b916f32645f322a085d0017f2996d0200ac89dd82a4fc2bf0f17b9afa3" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "derive_more", "serde", "serde_with", @@ -653,7 +730,7 @@ checksum = "933aaaace9faa6d7efda89472add89a8bfd15270318c47a2be8bb76192c951e2" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", "derive_more", @@ -675,10 +752,10 @@ dependencies = [ "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-serde", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "arbitrary", "itertools 0.14.0", "serde", @@ -695,7 +772,7 @@ checksum = "1826454c2890af6d642bf052909e0162ad7f261d172e56ef2e936d479960699c" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -708,7 +785,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498375e6a13b6edd04422a13d2b1a6187183e5a3aa14c5907b4c566551248bab" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -722,7 +799,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d9123d321ecd70925646eb2c60b1d9b7a965f860fbd717643e2c20fcf85d48d" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -734,7 +811,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1a0d2d5c64881f3723232eaaf6c2d9f4f88b061c63e87194b2db785ff3aa31f" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "serde", "serde_json", @@ -746,7 +823,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ea4ac9765e5a7582877ca53688e041fe184880fe75f16edf0945b24a319c710" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "async-trait", "auto_impl", "either", @@ -763,7 +840,7 @@ checksum = "3c9d85b9f7105ab5ce7dae7b0da33cd9d977601a48f759e1c82958978dd1a905" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-signer", "async-trait", "coins-bip32", @@ -774,18 +851,50 @@ dependencies = [ "zeroize", ] +[[package]] +name = "alloy-sol-macro" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68b32b6fa0d09bb74b4cefe35ccc8269d711c26629bc7cd98a47eeb12fe353f" +dependencies = [ + "alloy-sol-macro-expander 0.8.26", + "alloy-sol-macro-input 0.8.26", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "alloy-sol-macro" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09eb18ce0df92b4277291bbaa0ed70545d78b02948df756bbd3d6214bf39a218" dependencies = [ - "alloy-sol-macro-expander", - "alloy-sol-macro-input", + "alloy-sol-macro-expander 1.5.2", + "alloy-sol-macro-input 1.5.2", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2afe6879ac373e58fd53581636f2cce843998ae0b058ebe1e4f649195e2bd23c" +dependencies = [ + "alloy-sol-macro-input 0.8.26", + "const-hex", + "heck", + "indexmap 2.13.0", "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.114", + "syn-solidity 0.8.26", + "tiny-keccak", ] [[package]] @@ -794,8 +903,8 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95d9fa2daf21f59aa546d549943f10b5cce1ae59986774019fbedae834ffe01b" dependencies = [ - "alloy-json-abi", - "alloy-sol-macro-input", + "alloy-json-abi 1.5.2", + "alloy-sol-macro-input 1.5.2", "const-hex", "heck", "indexmap 2.13.0", @@ -803,17 +912,33 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "syn-solidity", + "syn-solidity 1.5.2", "tiny-keccak", ] +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ba01aee235a8c699d07e5be97ba215607564e71be72f433665329bec307d28" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.114", + "syn-solidity 0.8.26", +] + [[package]] name = "alloy-sol-macro-input" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9396007fe69c26ee118a19f4dee1f5d1d6be186ea75b3881adf16d87f8444686" dependencies = [ - "alloy-json-abi", + "alloy-json-abi 1.5.2", "const-hex", "dunce", "heck", @@ -822,7 +947,17 @@ dependencies = [ "quote", "serde_json", "syn 2.0.114", - "syn-solidity", + "syn-solidity 1.5.2", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c13fc168b97411e04465f03e632f31ef94cad1c7c8951bf799237fd7870d535" +dependencies = [ + "serde", + "winnow", ] [[package]] @@ -835,15 +970,28 @@ dependencies = [ "winnow", ] +[[package]] +name = "alloy-sol-types" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e960c4b52508ef2ae1e37cae5058e905e9ae099b107900067a503f8c454036f" +dependencies = [ + "alloy-json-abi 0.8.26", + "alloy-primitives 0.8.26", + "alloy-sol-macro 0.8.26", + "const-hex", + "serde", +] + [[package]] name = "alloy-sol-types" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09aeea64f09a7483bdcd4193634c7e5cf9fd7775ee767585270cd8ce2d69dc95" dependencies = [ - "alloy-json-abi", - "alloy-primitives", - "alloy-sol-macro", + "alloy-json-abi 1.5.2", + "alloy-primitives 1.5.2", + "alloy-sol-macro 1.5.2", "serde", ] @@ -864,7 +1012,7 @@ dependencies = [ "serde_json", "thiserror 2.0.17", "tokio", - "tower", + "tower 0.5.2", "tracing", "url", "wasmtimer", @@ -878,10 +1026,13 @@ checksum = "400dc298aaabdbd48be05448c4a19eaa38416c446043f3e54561249149269c32" dependencies = [ "alloy-json-rpc", "alloy-transport", + "opentelemetry 0.31.0", + "opentelemetry-http 0.31.0", "reqwest", "serde_json", - "tower", + "tower 0.5.2", "tracing", + "tracing-opentelemetry 0.32.0", "url", ] @@ -928,7 +1079,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428aa0f0e0658ff091f8f667c406e034b431cb10abd39de4f507520968acc499" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "arrayvec", @@ -1362,6 +1513,84 @@ dependencies = [ "serde", ] +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive 0.4.0", + "asn1-rs-impl 0.1.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl 0.2.0", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.17", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure 0.13.2", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "asn1_der" version = "0.7.6" @@ -1390,6 +1619,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.3", + "slab", + "windows-sys 0.61.2", +] + [[package]] name = "async-lock" version = "3.4.2" @@ -1455,12 +1702,37 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64 0.22.1", + "http", + "log", + "url", +] + [[package]] name = "aurora-engine-modexp" version = "1.2.0" @@ -1489,48 +1761,181 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "az" -version = "1.2.1" +name = "aws-lc-rs" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] [[package]] -name = "backon" -version = "1.6.0" +name = "aws-lc-sys" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ - "fastrand", - "tokio", + "bindgen 0.69.5", + "cc", + "cmake", + "dunce", + "fs_extra", ] [[package]] -name = "base-access-lists" -version = "0.2.1" +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ - "alloy-consensus", - "alloy-contract", - "alloy-eip7928", - "alloy-primitives", - "alloy-rlp", - "alloy-sol-types", - "base-primitives", - "eyre", - "op-revm", - "reth-evm", - "reth-optimism-chainspec", - "reth-optimism-evm", - "revm", + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", "serde", - "tracing", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", ] [[package]] -name = "base-bundles" -version = "0.2.1" +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core 0.5.6", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom 0.2.16", + "instant", + "rand 0.8.5", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "tokio", +] + +[[package]] +name = "base-access-lists" +version = "0.2.1" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eip7928", + "alloy-primitives 1.5.2", + "alloy-rlp", + "alloy-sol-types 1.5.2", + "base-primitives", + "eyre", + "op-revm", + "reth-evm", + "reth-optimism-chainspec", + "reth-optimism-evm", + "revm", + "serde", + "tracing", +] + +[[package]] +name = "base-bundles" +version = "0.2.1" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-serde", "alloy-signer-local", @@ -1559,7 +1964,7 @@ version = "0.2.1" dependencies = [ "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-client", "alloy-rpc-types", @@ -1570,7 +1975,7 @@ dependencies = [ "derive_more", "eyre", "futures-util", - "jsonrpsee", + "jsonrpsee 0.26.0", "op-alloy-network", "op-alloy-rpc-types-engine", "reth", @@ -1586,7 +1991,7 @@ dependencies = [ "reth-rpc-layer", "reth-tracing", "tokio", - "tower", + "tower 0.5.2", "tracing", "tracing-subscriber 0.3.22", "url", @@ -1601,14 +2006,14 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-op-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-client", "alloy-rpc-types", "alloy-rpc-types-engine", "alloy-rpc-types-eth", - "alloy-sol-macro", - "alloy-sol-types", + "alloy-sol-macro 1.5.2", + "alloy-sol-types 1.5.2", "arc-swap", "base-client-node", "base-flashblocks", @@ -1617,8 +2022,8 @@ dependencies = [ "derive_more", "eyre", "futures-util", - "jsonrpsee", - "jsonrpsee-types", + "jsonrpsee 0.26.0", + "jsonrpsee-types 0.26.0", "metrics", "metrics-derive", "once_cell", @@ -1661,7 +2066,7 @@ dependencies = [ name = "base-flashtypes" version = "0.2.1" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-serde", @@ -1680,12 +2085,12 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-client", "base-bundles", "base-client-node", "eyre", - "jsonrpsee", + "jsonrpsee 0.26.0", "op-alloy-consensus", "rand 0.9.2", "reth", @@ -1712,11 +2117,11 @@ dependencies = [ "alloy-contract", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-signer", "alloy-signer-local", - "alloy-sol-macro", - "alloy-sol-types", + "alloy-sol-macro 1.5.2", + "alloy-sol-types 1.5.2", "eyre", "op-alloy-network", "op-alloy-rpc-types", @@ -1750,14 +2155,14 @@ dependencies = [ name = "base-txpool" version = "0.2.1" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "base-client-node", "chrono", "derive_more", "eyre", "futures", "httpmock", - "jsonrpsee", + "jsonrpsee 0.26.0", "lru 0.16.3", "metrics", "reth", @@ -1804,6 +2209,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-url" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5b428e9fb429c6fda7316e9b006f993e6b4c33005e4659339fb5214479dddec" +dependencies = [ + "base64 0.22.1", +] + [[package]] name = "base64ct" version = "1.8.2" @@ -1831,6 +2245,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.10.5", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.114", + "which", +] + [[package]] name = "bindgen" version = "0.71.1" @@ -1844,7 +2281,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.1", "shlex", "syn 2.0.114", ] @@ -1862,7 +2299,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.1", "shlex", "syn 2.0.114", ] @@ -1898,6 +2335,32 @@ dependencies = [ "hex-conservative", ] +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitfield" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ba6517c6b0f2bf08be60e187ab64b038438f22dd755614d8fe4d4098c46419" +dependencies = [ + "bitfield-macros", +] + +[[package]] +name = "bitfield-macros" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -1926,6 +2389,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1968,7 +2440,7 @@ dependencies = [ "boa_string", "indexmap 2.13.0", "num-bigint", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2009,7 +2481,7 @@ dependencies = [ "portable-atomic", "rand 0.9.2", "regress", - "rustc-hash", + "rustc-hash 2.1.1", "ryu-js", "serde", "serde_json", @@ -2047,7 +2519,7 @@ dependencies = [ "indexmap 2.13.0", "once_cell", "phf", - "rustc-hash", + "rustc-hash 2.1.1", "static_assertions", ] @@ -2062,7 +2534,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -2080,7 +2552,7 @@ dependencies = [ "num-bigint", "num-traits", "regress", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2092,11 +2564,61 @@ dependencies = [ "fast-float2", "itoa", "paste", - "rustc-hash", + "rustc-hash 2.1.1", "ryu-js", "static_assertions", ] +[[package]] +name = "bollard" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http", + "http-body-util", + "hyper", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 2.0.17", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.47.1-rc.27.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" +dependencies = [ + "serde", + "serde_repr", + "serde_with", +] + [[package]] name = "borsh" version = "1.6.0" @@ -2295,6 +2817,34 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cbindgen" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "befbfd072a8e81c02f8c507aefce431fe5e7d051f83d48a23ffc9b9fe5a11799" +dependencies = [ + "clap", + "heck", + "indexmap 2.13.0", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.114", + "tempfile", + "toml 0.9.11+spec-1.1.0", +] + +[[package]] +name = "cbor4ii" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "472931dd4dfcc785075b09be910147f9c6258883fc4591d0dac6116392b2daa6" +dependencies = [ + "serde", +] + [[package]] name = "cc" version = "1.2.15" @@ -2333,6 +2883,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.42" @@ -2344,7 +2918,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2382,6 +2956,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -2435,6 +3010,34 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "coco-provider" +version = "0.3.0" +source = "git+https://github.com/automata-network/coco-provider-sdk#3a832b8cf5e88ef71649ab56e4efd67067b26b7c" +dependencies = [ + "bincode", + "bitfield 0.19.4", + "cbindgen", + "iocuddle", + "libc", + "log", + "rand 0.8.5", + "serde", + "serde-big-array", + "sysinfo 0.35.2", + "tss-esapi", + "uuid", +] + [[package]] name = "coins-bip32" version = "0.12.0" @@ -2848,6 +3451,22 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + [[package]] name = "ctr" version = "0.9.2" @@ -3043,7 +3662,24 @@ dependencies = [ ] [[package]] -name = "debug-helper" +name = "dcap-rs" +version = "0.1.0" +source = "git+https://github.com/automata-network/dcap-rs.git?rev=d847b8f75a493640c4881bdf67775250b6baefab#d847b8f75a493640c4881bdf67775250b6baefab" +dependencies = [ + "alloy-sol-types 0.8.26", + "chrono", + "hex", + "p256", + "serde", + "serde_json", + "sha2", + "sha3", + "time", + "x509-parser 0.15.1", +] + +[[package]] +name = "debug-helper" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" @@ -3066,9 +3702,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs 0.5.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.5" @@ -3285,6 +3950,17 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "docker_credential" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d89dfcba45b4afad7450a99b39e751590463e45c04728cf555d36bb66940de8" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "doctest-file" version = "1.0.0" @@ -3300,6 +3976,33 @@ dependencies = [ "litrs", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtor" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + [[package]] name = "dunce" version = "1.0.5" @@ -3405,6 +4108,7 @@ dependencies = [ "ff", "generic-array", "group", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -3413,6 +4117,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enr" version = "0.13.0" @@ -3465,6 +4184,26 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "equator" version = "0.4.2" @@ -3510,6 +4249,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "etcetera" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.59.0", +] + [[package]] name = "ethereum_hashing" version = "0.7.0" @@ -3527,7 +4277,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "hex", "serde", "serde_derive", @@ -3540,7 +4290,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "ethereum_serde_utils", "itertools 0.13.0", "serde", @@ -3744,6 +4494,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -3774,6 +4530,16 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-bounded" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" +dependencies = [ + "futures-timer", + "futures-util", +] + [[package]] name = "futures-buffered" version = "0.2.12" @@ -3827,6 +4593,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -3859,6 +4626,17 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls", + "rustls-pki-types", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -3916,7 +4694,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link", + "windows-link 0.2.1", "windows-result 0.4.1", ] @@ -4120,6 +4898,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.1.5", + "serde", ] [[package]] @@ -4224,6 +5003,7 @@ dependencies = [ "rand 0.9.2", "ring", "serde", + "socket2 0.5.10", "thiserror 2.0.17", "tinyvec", "tokio", @@ -4271,6 +5051,21 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + [[package]] name = "http" version = "1.4.0" @@ -4401,6 +5196,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + [[package]] name = "hyper-rustls" version = "0.27.7" @@ -4468,9 +5278,27 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration", "tokio", + "tower-layer", "tower-service", "tracing", + "windows-registry", +] + +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", ] [[package]] @@ -4613,6 +5441,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "if-addrs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "if-addrs" version = "0.14.0" @@ -4623,6 +5461,50 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "if-watch" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf9d64cfcf380606e64f9a0bcf493616b65331199f984151a6fa11a7b3cde38" +dependencies = [ + "async-io", + "core-foundation 0.9.4", + "fnv", + "futures", + "if-addrs 0.10.2", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration", + "tokio", + "windows 0.53.0", +] + +[[package]] +name = "igd-next" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.9.2", + "tokio", + "url", + "xmltree", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -4744,6 +5626,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "interprocess" version = "2.2.3" @@ -4768,6 +5659,12 @@ dependencies = [ "memoffset", ] +[[package]] +name = "iocuddle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8972d5be69940353d5347a1344cb375d9b457d6809b428b05bb1ca2fb9ce007" + [[package]] name = "ipconfig" version = "0.3.2" @@ -4888,6 +5785,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonrpsee" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "jsonrpsee-core 0.25.1", + "jsonrpsee-http-client 0.25.1", + "jsonrpsee-proc-macros 0.25.1", + "jsonrpsee-server 0.25.1", + "jsonrpsee-types 0.25.1", + "tokio", + "tracing", +] + [[package]] name = "jsonrpsee" version = "0.26.0" @@ -4895,11 +5806,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-http-client", - "jsonrpsee-proc-macros", - "jsonrpsee-server", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-http-client 0.26.0", + "jsonrpsee-proc-macros 0.26.0", + "jsonrpsee-server 0.26.0", + "jsonrpsee-types 0.26.0", "jsonrpsee-wasm-client", "jsonrpsee-ws-client", "tokio", @@ -4917,7 +5828,7 @@ dependencies = [ "futures-util", "gloo-net", "http", - "jsonrpsee-core", + "jsonrpsee-core 0.26.0", "pin-project", "rustls", "rustls-pki-types", @@ -4931,6 +5842,30 @@ dependencies = [ "url", ] +[[package]] +name = "jsonrpsee-core" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "jsonrpsee-types 0.25.1", + "parking_lot", + "pin-project", + "rand 0.9.2", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tower 0.5.2", + "tracing", +] + [[package]] name = "jsonrpsee-core" version = "0.26.0" @@ -4944,21 +5879,43 @@ dependencies = [ "http", "http-body", "http-body-util", - "jsonrpsee-types", + "jsonrpsee-types 0.26.0", "parking_lot", "pin-project", "rand 0.9.2", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "thiserror 2.0.17", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", "wasm-bindgen-futures", ] +[[package]] +name = "jsonrpsee-http-client" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "base64 0.22.1", + "http-body", + "hyper", + "hyper-rustls", + "hyper-util", + "jsonrpsee-core 0.25.1", + "jsonrpsee-types 0.25.1", + "rustls", + "rustls-platform-verifier", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tower 0.5.2", + "url", +] + [[package]] name = "jsonrpsee-http-client" version = "0.26.0" @@ -4970,18 +5927,30 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "rustls", "rustls-platform-verifier", "serde", "serde_json", "thiserror 2.0.17", "tokio", - "tower", + "tower 0.5.2", "url", ] +[[package]] +name = "jsonrpsee-proc-macros" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "jsonrpsee-proc-macros" version = "0.26.0" @@ -4997,9 +5966,8 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" dependencies = [ "futures-util", "http", @@ -5007,8 +5975,8 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.25.1", + "jsonrpsee-types 0.25.1", "pin-project", "route-recognizer", "serde", @@ -5018,15 +5986,53 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.5.2", "tracing", ] [[package]] -name = "jsonrpsee-types" +name = "jsonrpsee-server" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" +checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f" +dependencies = [ + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "pin-project", + "route-recognizer", + "serde", + "serde_json", + "soketto", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-util", + "tower 0.5.2", + "tracing", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.25.1" +source = "git+https://github.com/paritytech/jsonrpsee?rev=f04afa740e55db60dce20d9839758792f035ffff#f04afa740e55db60dce20d9839758792f035ffff" +dependencies = [ + "http", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "jsonrpsee-types" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" dependencies = [ "http", "serde", @@ -5041,9 +6047,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7902885de4779f711a95d82c8da2d7e5f9f3a7c7cfa44d51c067fd1c29d72a3c" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", - "tower", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "tower 0.5.2", ] [[package]] @@ -5054,9 +6060,9 @@ checksum = "9b6fceceeb05301cc4c065ab3bd2fa990d41ff4eb44e4ca1b30fa99c057c3e79" dependencies = [ "http", "jsonrpsee-client-transport", - "jsonrpsee-core", - "jsonrpsee-types", - "tower", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "tower 0.5.2", "url", ] @@ -5135,6 +6141,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.180" @@ -5160,7 +6172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5169,6 +6181,149 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libp2p" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce71348bf5838e46449ae240631117b487073d5f347c06d434caddcb91dceb5a" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom 0.2.16", + "libp2p-allow-block-list", + "libp2p-autonat", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dns", + "libp2p-identify", + "libp2p-identity", + "libp2p-mdns", + "libp2p-metrics", + "libp2p-noise", + "libp2p-ping", + "libp2p-quic", + "libp2p-request-response", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-upnp", + "libp2p-yamux", + "multiaddr", + "pin-project", + "rw-stream-sink", + "thiserror 2.0.17", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16ccf824ee859ca83df301e1c0205270206223fd4b1f2e512a693e1912a8f4a" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-autonat" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fab5e25c49a7d48dac83d95d8f3bac0a290d8a5df717012f6e34ce9886396c0b" +dependencies = [ + "async-trait", + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-request-response", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "rand_core 0.6.4", + "thiserror 2.0.17", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18b8b607cf3bfa2f8c57db9c7d8569a315d5cc0a282e6bfd5ebfc0a9840b2a0" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-core" +version = "0.43.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "thiserror 2.0.17", + "tracing", + "unsigned-varint 0.8.0", + "web-time", +] + +[[package]] +name = "libp2p-dns" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b770c1c8476736ca98c578cba4b505104ff8e842c2876b528925f9766379f9a" +dependencies = [ + "async-trait", + "futures", + "hickory-resolver", + "libp2p-core", + "libp2p-identity", + "parking_lot", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-identify" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ab792a8b68fdef443a62155b01970c81c3aadab5e659621b063ef252a8e65e8" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror 2.0.17", + "tracing", +] + [[package]] name = "libp2p-identity" version = "0.2.13" @@ -5182,12 +6337,241 @@ dependencies = [ "k256", "multihash", "quick-protobuf", + "rand 0.8.5", "sha2", "thiserror 2.0.17", "tracing", "zeroize", ] +[[package]] +name = "libp2p-mdns" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66872d0f1ffcded2788683f76931be1c52e27f343edb93bc6d0bcd8887be443" +dependencies = [ + "futures", + "hickory-proto", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-metrics" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805a555148522cb3414493a5153451910cb1a146c53ffbf4385708349baf62b7" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-identify", + "libp2p-identity", + "libp2p-ping", + "libp2p-swarm", + "pin-project", + "prometheus-client", + "web-time", +] + +[[package]] +name = "libp2p-noise" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc73eacbe6462a0eb92a6527cac6e63f02026e5407f8831bde8293f19217bfbf" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "libp2p-identity", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "snow", + "static_assertions", + "thiserror 2.0.17", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-ping" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74bb7fcdfd9fead4144a3859da0b49576f171a8c8c7c0bfc7c541921d25e60d3" +dependencies = [ + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-quic" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc448b2de9f4745784e3751fe8bc6c473d01b8317edd5ababcb0dec803d843f" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "quinn", + "rand 0.8.5", + "ring", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-request-response" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9f1cca83488b90102abac7b67d5c36fc65bc02ed47620228af7ed002e6a1478" +dependencies = [ + "async-trait", + "cbor4ii", + "futures", + "futures-bounded", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-stream" +version = "0.4.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6bd8025c80205ec2810cfb28b02f362ab48a01bee32c50ab5f12761e033464" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "tracing", +] + +[[package]] +name = "libp2p-swarm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa762e5215919a34e31c35d4b18bf2e18566ecab7f8a3d39535f4a3068f8b62" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "lru 0.12.5", + "multistream-select", + "rand 0.8.5", + "smallvec", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd297cf53f0cb3dee4d2620bb319ae47ef27c702684309f682bdb7e55a18ae9c" +dependencies = [ + "heck", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "libp2p-tcp" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4e030c52c46c8d01559b2b8ca9b7c4185f10576016853129ca1fe5cd1a644" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-tls" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring", + "rustls", + "rustls-webpki", + "thiserror 2.0.17", + "x509-parser 0.17.0", + "yasna", +] + +[[package]] +name = "libp2p-upnp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4757e65fe69399c1a243bbb90ec1ae5a2114b907467bf09f3575e899815bb8d3" +dependencies = [ + "futures", + "futures-timer", + "igd-next", + "libp2p-core", + "libp2p-swarm", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-yamux" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8" +dependencies = [ + "either", + "futures", + "libp2p-core", + "thiserror 2.0.17", + "tracing", + "yamux 0.12.1", + "yamux 0.13.8", +] + [[package]] name = "libproc" version = "0.14.11" @@ -5360,6 +6744,16 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "macros" +version = "0.1.0" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "match-lookup" version = "0.1.1" @@ -5380,6 +6774,28 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "mbox" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d142aeadbc4e8c679fc6d93fbe7efe1c021fa7d80629e615915b519e3bc6de" +dependencies = [ + "libc", + "stable_deref_trait", +] + [[package]] name = "memchr" version = "2.7.6" @@ -5433,11 +6849,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" dependencies = [ "base64 0.22.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", "indexmap 2.13.0", + "ipnet", "metrics", - "metrics-util", + "metrics-util 0.19.1", "quanta", "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "indexmap 2.13.0", + "ipnet", + "metrics", + "metrics-util 0.20.1", + "quanta", + "thiserror 2.0.17", + "tokio", + "tracing", ] [[package]] @@ -5462,9 +6906,29 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" dependencies = [ + "aho-corasick", "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.15.5", + "indexmap 2.13.0", + "metrics", + "ordered-float", + "quanta", + "radix_trie", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + +[[package]] +name = "metrics-util" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfb1365fea27e6dd9dc1dbc19f570198bc86914533ad639dae939635f096be4" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.16.1", "metrics", "quanta", "rand 0.9.2", @@ -5558,10 +7022,13 @@ version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" dependencies = [ + "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", + "event-listener", + "futures-util", "parking_lot", "portable-atomic", "smallvec", @@ -5590,49 +7057,162 @@ dependencies = [ "percent-encoding", "serde", "static_assertions", - "unsigned-varint", + "unsigned-varint 0.8.0", "url", ] [[package]] -name = "multibase" -version = "0.9.2" +name = "multibase" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +dependencies = [ + "base-x", + "base256emoji", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe 0.1.6", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror 1.0.69", +] + +[[package]] +name = "netlink-proto" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.17", +] + +[[package]] +name = "netlink-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" dependencies = [ - "base-x", - "base256emoji", - "data-encoding", - "data-encoding-macro", + "bytes", + "futures", + "libc", + "log", + "tokio", ] [[package]] -name = "multihash" -version = "0.19.3" +name = "nibble_vec" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ - "core2", - "unsigned-varint", + "smallvec", ] [[package]] -name = "native-tls" -version = "0.2.14" +name = "nix" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ + "bitflags 1.3.2", + "cfg-if", "libc", - "log", - "openssl", - "openssl-probe 0.1.6", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -5725,6 +7305,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -5822,6 +7413,52 @@ dependencies = [ "smallvec", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "oid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" +dependencies = [ + "serde", +] + +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs 0.5.2", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs 0.7.1", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -5853,7 +7490,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", @@ -5878,7 +7515,7 @@ checksum = "f63f27e65be273ec8fcb0b6af0fd850b550979465ab93423705ceb3dfddbd2ab" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-types-eth", "alloy-signer", @@ -5892,8 +7529,8 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ef9114426b16172254555aad34a8ea96c01895e40da92f5d12ea680a1baeaa7" dependencies = [ - "alloy-primitives", - "jsonrpsee", + "alloy-primitives 1.5.2", + "jsonrpsee 0.26.0", ] [[package]] @@ -5905,7 +7542,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network-primitives", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-serde", "derive_more", @@ -5923,7 +7560,7 @@ checksum = "d8f24b8cb66e4b33e6c9e508bf46b8ecafc92eadd0b93fedd306c0accb477657" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-engine", "alloy-serde", @@ -5936,6 +7573,141 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "op-rbuilder" +version = "0.2.1" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eips", + "alloy-evm", + "alloy-json-rpc", + "alloy-network", + "alloy-op-evm", + "alloy-primitives 1.5.2", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types-beacon", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer-local", + "alloy-sol-types 1.5.2", + "alloy-transport", + "alloy-transport-http", + "anyhow", + "async-trait", + "chrono", + "clap", + "clap_builder", + "concurrent-queue", + "ctor", + "dashmap 6.1.0", + "derive_more", + "dirs-next", + "either", + "eyre", + "futures", + "futures-util", + "hex", + "http", + "http-body-util", + "hyper", + "hyper-util", + "jsonrpsee 0.26.0", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", + "k256", + "macros", + "metrics", + "moka", + "nanoid", + "op-alloy-consensus", + "op-alloy-flz", + "op-alloy-network", + "op-alloy-rpc-types", + "op-alloy-rpc-types-engine", + "op-revm", + "opentelemetry 0.31.0", + "p2p", + "parking_lot", + "rand 0.9.2", + "reqwest", + "reth", + "reth-basic-payload-builder", + "reth-chain-state", + "reth-chainspec", + "reth-cli", + "reth-cli-commands", + "reth-cli-util", + "reth-db", + "reth-evm", + "reth-execution-types", + "reth-exex", + "reth-ipc", + "reth-metrics", + "reth-network-peers", + "reth-node-api", + "reth-node-builder", + "reth-node-core", + "reth-node-ethereum", + "reth-optimism-chainspec", + "reth-optimism-cli", + "reth-optimism-consensus", + "reth-optimism-evm", + "reth-optimism-forks", + "reth-optimism-node", + "reth-optimism-payload-builder", + "reth-optimism-primitives", + "reth-optimism-rpc", + "reth-optimism-txpool", + "reth-payload-builder", + "reth-payload-builder-primitives", + "reth-payload-primitives", + "reth-payload-util", + "reth-primitives", + "reth-primitives-traits", + "reth-provider", + "reth-revm", + "reth-rpc-api", + "reth-rpc-engine-api", + "reth-rpc-eth-types", + "reth-rpc-layer", + "reth-storage-api", + "reth-tasks", + "reth-testing-utils", + "reth-tracing-otlp", + "reth-transaction-pool", + "reth-trie", + "revm", + "rlimit", + "rollup-boost", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "serde_yaml", + "sha3", + "shellexpand", + "tar", + "tempfile", + "testcontainers", + "thiserror 2.0.17", + "tikv-jemallocator", + "time", + "tips-core", + "tokio", + "tokio-tungstenite 0.26.2", + "tokio-util", + "tower 0.5.2", + "tracing", + "tracing-subscriber 0.3.22", + "url", + "uuid", + "vergen", + "vergen-git2", +] + [[package]] name = "op-revm" version = "12.0.2" @@ -6003,6 +7775,20 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236e667b670a5cdf90c258f5a55794ec5ac5027e960c224bff8367a59e1e6426" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.17", + "tracing", +] + [[package]] name = "opentelemetry" version = "0.31.0" @@ -6017,6 +7803,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "opentelemetry-http" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8863faf2910030d139fb48715ad5ff2f35029fc5f244f6d5f689ddcf4d26253" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry 0.28.0", + "reqwest", + "tracing", +] + [[package]] name = "opentelemetry-http" version = "0.31.0" @@ -6026,8 +7826,30 @@ dependencies = [ "async-trait", "bytes", "http", - "opentelemetry", + "opentelemetry 0.31.0", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bef114c6d41bea83d6dc60eb41720eedd0261a67af57b66dd2b84ac46c01d91" +dependencies = [ + "async-trait", + "futures-core", + "http", + "opentelemetry 0.28.0", + "opentelemetry-http 0.28.0", + "opentelemetry-proto 0.28.0", + "opentelemetry_sdk 0.28.0", + "prost 0.13.5", "reqwest", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tonic 0.12.3", + "tracing", ] [[package]] @@ -6037,28 +7859,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" dependencies = [ "http", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", + "opentelemetry 0.31.0", + "opentelemetry-http 0.31.0", + "opentelemetry-proto 0.31.0", + "opentelemetry_sdk 0.31.0", + "prost 0.14.1", "reqwest", "thiserror 2.0.17", "tokio", - "tonic", + "tonic 0.14.2", "tracing", ] +[[package]] +name = "opentelemetry-proto" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8870d3024727e99212eb3bb1762ec16e255e3e6f58eeb3dc8db1aa226746d" +dependencies = [ + "base64 0.22.1", + "hex", + "opentelemetry 0.28.0", + "opentelemetry_sdk 0.28.0", + "prost 0.13.5", + "serde", + "tonic 0.12.3", +] + [[package]] name = "opentelemetry-proto" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "prost", - "tonic", + "opentelemetry 0.31.0", + "opentelemetry_sdk 0.31.0", + "prost 0.14.1", + "tonic 0.14.2", "tonic-prost", ] @@ -6068,6 +7905,27 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" +[[package]] +name = "opentelemetry_sdk" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84dfad6042089c7fc1f6118b7040dc2eb4ab520abbf410b79dc481032af39570" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "opentelemetry 0.28.0", + "percent-encoding", + "rand 0.8.5", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "opentelemetry_sdk" version = "0.31.0" @@ -6077,7 +7935,7 @@ dependencies = [ "futures-channel", "futures-executor", "futures-util", - "opentelemetry", + "opentelemetry 0.31.0", "percent-encoding", "rand 0.9.2", "thiserror 2.0.17", @@ -6089,6 +7947,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + [[package]] name = "p256" version = "0.13.2" @@ -6101,6 +7968,25 @@ dependencies = [ "sha2", ] +[[package]] +name = "p2p" +version = "0.2.1" +dependencies = [ + "derive_more", + "eyre", + "futures", + "futures-util", + "hex", + "libp2p", + "libp2p-stream", + "multiaddr", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "page_size" version = "0.6.0" @@ -6167,7 +8053,32 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.114", ] [[package]] @@ -6205,6 +8116,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -6274,6 +8194,41 @@ dependencies = [ "siphasher", ] +[[package]] +name = "picky-asn1" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295eea0f33c16be21e2a98b908fdd4d73c04dd48c8480991b76dbcf0cb58b212" +dependencies = [ + "oid", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-der" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df7873a9e36d42dadb393bea5e211fe83d793c172afad5fb4ec846ec582793f" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5f20f71a68499ff32310f418a6fad8816eac1a2859ed3f0c5c741389dd6208" +dependencies = [ + "base64 0.21.7", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -6359,6 +8314,31 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" @@ -6411,6 +8391,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -6517,6 +8507,29 @@ dependencies = [ "hex", ] +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf41c1a7c32ed72abe5082fb19505b969095c12da9f5732a4bc9878757fd087c" +dependencies = [ + "dtoa", + "itoa", + "parking_lot", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "proptest" version = "1.9.0" @@ -6568,6 +8581,16 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive 0.13.5", +] + [[package]] name = "prost" version = "0.14.1" @@ -6575,7 +8598,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.14.1", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] @@ -6632,6 +8668,19 @@ dependencies = [ "byteorder", ] +[[package]] +name = "quick-protobuf-codec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror 1.0.69", + "unsigned-varint 0.8.0", +] + [[package]] name = "quinn" version = "0.11.9" @@ -6640,10 +8689,11 @@ checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", + "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.1", "rustls", "socket2 0.6.1", "thiserror 2.0.17", @@ -6663,7 +8713,7 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash", + "rustc-hash 2.1.1", "rustls", "rustls-pki-types", "slab", @@ -6708,6 +8758,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -6848,12 +8908,34 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "yasna", +] + [[package]] name = "recvmsg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -6967,9 +9049,11 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -6979,6 +9063,7 @@ dependencies = [ "hyper-util", "js-sys", "log", + "mime", "native-tls", "percent-encoding", "pin-project-lite", @@ -6994,7 +9079,7 @@ dependencies = [ "tokio-native-tls", "tokio-rustls", "tokio-util", - "tower", + "tower 0.5.2", "tower-http", "tower-service", "url", @@ -7064,7 +9149,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "futures-core", "futures-util", "metrics", @@ -7088,7 +9173,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-signer", "alloy-signer-local", "derive_more", @@ -7122,7 +9207,7 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-trie", "auto_impl", "derive_more", @@ -7154,7 +9239,7 @@ dependencies = [ "alloy-chains", "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "backon", "clap", @@ -7216,7 +9301,7 @@ dependencies = [ "tar", "tokio", "tokio-stream", - "toml", + "toml 0.8.23", "tracing", "zstd", ] @@ -7237,7 +9322,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "cfg-if", "eyre", "libc", @@ -7257,7 +9342,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-trie", "arbitrary", "bytes", @@ -7290,7 +9375,7 @@ dependencies = [ "reth-prune-types", "reth-stages-types", "serde", - "toml", + "toml 0.8.23", "url", ] @@ -7300,7 +9385,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "reth-execution-types", "reth-primitives-traits", @@ -7327,7 +9412,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-types-engine", "alloy-transport", @@ -7350,7 +9435,7 @@ name = "reth-db" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "derive_more", "eyre", "metrics", @@ -7364,9 +9449,9 @@ dependencies = [ "reth-static-file-types", "reth-storage-errors", "reth-tracing", - "rustc-hash", + "rustc-hash 2.1.1", "strum 0.27.2", - "sysinfo", + "sysinfo 0.33.1", "tempfile", "thiserror 2.0.17", ] @@ -7378,7 +9463,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "bytes", "derive_more", @@ -7406,7 +9491,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "boyer-moore-magiclen", "eyre", "reth-chainspec", @@ -7435,7 +9520,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "bytes", "modular-bitfield", @@ -7449,7 +9534,7 @@ name = "reth-discv4" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "discv5", "enr", @@ -7474,7 +9559,7 @@ name = "reth-discv5" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "derive_more", "discv5", @@ -7498,7 +9583,7 @@ name = "reth-dns-discovery" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "data-encoding", "enr", "hickory-resolver", @@ -7524,7 +9609,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "async-compression", "futures", @@ -7560,7 +9645,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rlp", "alloy-rpc-types-engine", @@ -7570,7 +9655,7 @@ dependencies = [ "derive_more", "eyre", "futures-util", - "jsonrpsee", + "jsonrpsee 0.26.0", "reth-chainspec", "reth-cli-commands", "reth-config", @@ -7616,7 +9701,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "aes", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "block-padding", "byteorder", @@ -7647,7 +9732,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "eyre", "futures-util", @@ -7672,7 +9757,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "auto_impl", "futures", @@ -7720,7 +9805,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-engine", "crossbeam-channel", @@ -7802,7 +9887,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "ethereum_ssz", "ethereum_ssz_derive", @@ -7816,7 +9901,7 @@ name = "reth-era-downloader" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "bytes", "eyre", "futures-util", @@ -7832,7 +9917,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "eyre", "futures-util", "reth-db-api", @@ -7865,7 +9950,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-chains", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "bytes", "derive_more", @@ -7896,7 +9981,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "bytes", "derive_more", @@ -7939,7 +10024,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-chainspec", "reth-consensus", "reth-consensus-common", @@ -7954,7 +10039,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-engine", "reth-engine-primitives", @@ -7973,11 +10058,11 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-eip2124", "alloy-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "auto_impl", "once_cell", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -7987,7 +10072,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-engine", "reth-basic-payload-builder", @@ -8016,7 +10101,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", @@ -8047,7 +10132,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "derive_more", "futures-util", @@ -8070,7 +10155,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "reth-chainspec", "reth-ethereum-forks", @@ -8088,7 +10173,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "nybbles", "reth-storage-errors", @@ -8103,7 +10188,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "derive_more", "reth-ethereum-primitives", "reth-primitives-traits", @@ -8120,7 +10205,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "eyre", "futures", "itertools 0.14.0", @@ -8157,7 +10242,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-chain-state", "reth-execution-types", "reth-primitives-traits", @@ -8181,12 +10266,12 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-debug", "eyre", "futures", - "jsonrpsee", + "jsonrpsee 0.26.0", "pretty_assertions", "reth-engine-primitives", "reth-evm", @@ -8212,14 +10297,14 @@ dependencies = [ "futures", "futures-util", "interprocess", - "jsonrpsee", + "jsonrpsee 0.26.0", "pin-project", "serde_json", "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.5.2", "tracing", ] @@ -8265,7 +10350,7 @@ name = "reth-net-banlist" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", ] [[package]] @@ -8274,7 +10359,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "futures-util", - "if-addrs", + "if-addrs 0.14.0", "reqwest", "serde_with", "thiserror 2.0.17", @@ -8289,7 +10374,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "aquamarine", "auto_impl", @@ -8325,7 +10410,7 @@ dependencies = [ "reth-tasks", "reth-tokio-util", "reth-transaction-pool", - "rustc-hash", + "rustc-hash 2.1.1", "schnellru", "secp256k1 0.30.0", "serde", @@ -8343,7 +10428,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-admin", "alloy-rpc-types-eth", "auto_impl", @@ -8369,7 +10454,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "auto_impl", "derive_more", "futures", @@ -8390,7 +10475,7 @@ name = "reth-network-peers" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "enr", "secp256k1 0.30.0", @@ -8462,7 +10547,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-provider", "alloy-rpc-types", "alloy-rpc-types-engine", @@ -8470,7 +10555,7 @@ dependencies = [ "eyre", "fdlimit", "futures", - "jsonrpsee", + "jsonrpsee 0.26.0", "rayon", "reth-basic-payload-builder", "reth-chain-state", @@ -8530,7 +10615,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "clap", "derive_more", @@ -8569,7 +10654,7 @@ dependencies = [ "shellexpand", "strum 0.27.2", "thiserror 2.0.17", - "toml", + "toml 0.8.23", "tracing", "url", "vergen", @@ -8620,7 +10705,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "chrono", "futures-util", "reth-chain-state", @@ -8645,7 +10730,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "derive_more", "futures", @@ -8669,18 +10754,18 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "eyre", "http", - "jsonrpsee-server", + "jsonrpsee-server 0.26.0", "metrics", - "metrics-exporter-prometheus", + "metrics-exporter-prometheus 0.16.2", "metrics-process", - "metrics-util", + "metrics-util 0.19.1", "procfs 0.17.0", "reqwest", "reth-metrics", "reth-tasks", "tikv-jemalloc-ctl", "tokio", - "tower", + "tower 0.5.2", "tracing", ] @@ -8706,7 +10791,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "derive_more", "miniz_oxide", "op-alloy-consensus", @@ -8731,7 +10816,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "clap", "derive_more", @@ -8781,7 +10866,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-trie", "reth-chainspec", "reth-consensus", @@ -8808,7 +10893,7 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-op-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "op-alloy-consensus", "op-alloy-rpc-types-engine", "op-revm", @@ -8834,7 +10919,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-serde", "brotli", @@ -8872,7 +10957,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-op-hardforks", - "alloy-primitives", + "alloy-primitives 1.5.2", "once_cell", "reth-ethereum-forks", ] @@ -8884,7 +10969,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "clap", @@ -8935,7 +11020,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-debug", "alloy-rpc-types-engine", @@ -8974,7 +11059,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "arbitrary", "bytes", @@ -8995,7 +11080,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-client", "alloy-rpc-types-debug", "alloy-rpc-types-engine", @@ -9006,9 +11091,9 @@ dependencies = [ "derive_more", "eyre", "futures", - "jsonrpsee", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee 0.26.0", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "metrics", "op-alloy-consensus", "op-alloy-network", @@ -9044,7 +11129,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", ] @@ -9066,7 +11151,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-client", "alloy-rpc-types-eth", "alloy-serde", @@ -9100,7 +11185,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types", "futures-util", "metrics", @@ -9133,7 +11218,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "auto_impl", "either", @@ -9153,7 +11238,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-transaction-pool", ] @@ -9189,7 +11274,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-trie", @@ -9221,7 +11306,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "dashmap 6.1.0", "eyre", @@ -9265,7 +11350,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "itertools 0.14.0", "metrics", "rayon", @@ -9279,7 +11364,7 @@ dependencies = [ "reth-prune-types", "reth-static-file-types", "reth-tokio-util", - "rustc-hash", + "rustc-hash 2.1.1", "thiserror 2.0.17", "tokio", "tracing", @@ -9290,7 +11375,7 @@ name = "reth-prune-types" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "derive_more", "modular-bitfield", @@ -9306,7 +11391,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "futures", "reth-eth-wire", @@ -9325,7 +11410,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "eyre", "futures", "parking_lot", @@ -9351,7 +11436,7 @@ name = "reth-revm" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-primitives-traits", "reth-storage-api", "reth-storage-errors", @@ -9370,7 +11455,7 @@ dependencies = [ "alloy-evm", "alloy-genesis", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-client", "alloy-rpc-types", @@ -9393,8 +11478,8 @@ dependencies = [ "http-body", "hyper", "itertools 0.14.0", - "jsonrpsee", - "jsonrpsee-types", + "jsonrpsee 0.26.0", + "jsonrpsee-types 0.26.0", "jsonwebtoken", "parking_lot", "pin-project", @@ -9433,7 +11518,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", "tracing-futures", ] @@ -9446,7 +11531,7 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-json-rpc", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types", "alloy-rpc-types-admin", "alloy-rpc-types-anvil", @@ -9458,7 +11543,7 @@ dependencies = [ "alloy-rpc-types-trace", "alloy-rpc-types-txpool", "alloy-serde", - "jsonrpsee", + "jsonrpsee 0.26.0", "reth-chain-state", "reth-engine-primitives", "reth-network-peers", @@ -9475,7 +11560,7 @@ dependencies = [ "alloy-provider", "dyn-clone", "http", - "jsonrpsee", + "jsonrpsee 0.26.0", "metrics", "pin-project", "reth-chain-state", @@ -9500,7 +11585,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-util", - "tower", + "tower 0.5.2", "tower-http", "tracing", ] @@ -9513,12 +11598,12 @@ dependencies = [ "alloy-consensus", "alloy-json-rpc", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-signer", "auto_impl", "dyn-clone", - "jsonrpsee-types", + "jsonrpsee-types 0.26.0", "op-alloy-consensus", "op-alloy-network", "op-alloy-rpc-types", @@ -9538,11 +11623,11 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "async-trait", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "metrics", "parking_lot", "reth-chainspec", @@ -9573,7 +11658,7 @@ dependencies = [ "alloy-evm", "alloy-json-rpc", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-rpc-types-mev", @@ -9582,8 +11667,8 @@ dependencies = [ "auto_impl", "dyn-clone", "futures", - "jsonrpsee", - "jsonrpsee-types", + "jsonrpsee 0.26.0", + "jsonrpsee-types 0.26.0", "parking_lot", "reth-chain-state", "reth-chainspec", @@ -9615,16 +11700,16 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-network", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-client", "alloy-rpc-types-eth", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "alloy-transport", "derive_more", "futures", "itertools 0.14.0", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "metrics", "rand 0.9.2", "reqwest", @@ -9660,9 +11745,9 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-rpc-types-engine", "http", - "jsonrpsee-http-client", + "jsonrpsee-http-client 0.26.0", "pin-project", - "tower", + "tower 0.5.2", "tower-http", "tracing", ] @@ -9673,10 +11758,10 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", - "jsonrpsee-core", - "jsonrpsee-types", + "jsonrpsee-core 0.26.0", + "jsonrpsee-types 0.26.0", "reth-errors", "reth-network-api", "serde", @@ -9690,7 +11775,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "bincode", "eyre", "futures-util", @@ -9737,7 +11822,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "aquamarine", "auto_impl", "futures-util", @@ -9763,7 +11848,7 @@ name = "reth-stages-types" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "arbitrary", "bytes", "modular-bitfield", @@ -9777,7 +11862,7 @@ name = "reth-static-file" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "parking_lot", "rayon", "reth-codecs", @@ -9797,7 +11882,7 @@ name = "reth-static-file-types" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "clap", "derive_more", "serde", @@ -9811,7 +11896,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-engine", "auto_impl", "reth-chainspec", @@ -9833,7 +11918,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "derive_more", "reth-primitives-traits", @@ -9869,7 +11954,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", - "alloy-primitives", + "alloy-primitives 1.5.2", "rand 0.8.5", "rand 0.9.2", "reth-ethereum-primitives", @@ -9911,12 +11996,12 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "clap", "eyre", - "opentelemetry", - "opentelemetry-otlp", + "opentelemetry 0.31.0", + "opentelemetry-otlp 0.31.0", "opentelemetry-semantic-conventions", - "opentelemetry_sdk", + "opentelemetry_sdk 0.31.0", "tracing", - "tracing-opentelemetry", + "tracing-opentelemetry 0.32.0", "tracing-subscriber 0.3.22", "url", ] @@ -9928,7 +12013,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "aquamarine", "auto_impl", @@ -9951,7 +12036,7 @@ dependencies = [ "reth-tasks", "revm-interpreter", "revm-primitives", - "rustc-hash", + "rustc-hash 2.1.1", "schnellru", "serde", "serde_json", @@ -9969,7 +12054,7 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-trie", "auto_impl", @@ -9993,7 +12078,7 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-consensus", - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", @@ -10019,7 +12104,7 @@ name = "reth-trie-db" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "reth-db-api", "reth-execution-errors", "reth-primitives-traits", @@ -10032,7 +12117,7 @@ name = "reth-trie-parallel" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "crossbeam-channel", "dashmap 6.1.0", @@ -10057,7 +12142,7 @@ name = "reth-trie-sparse" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-trie", "auto_impl", @@ -10076,7 +12161,7 @@ name = "reth-trie-sparse-parallel" version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rlp", "alloy-trie", "metrics", @@ -10231,10 +12316,10 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21caa99f22184a6818946362778cccd3ff02f743c1e085bee87700671570ecb7" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "alloy-rpc-types-eth", "alloy-rpc-types-trace", - "alloy-sol-types", + "alloy-sol-types 1.5.2", "anstyle", "boa_engine", "boa_gc", @@ -10289,7 +12374,7 @@ version = "21.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29e161db429d465c09ba9cbff0df49e31049fe6b549e28eb0b7bd642fcbd4412" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "num_enum", "once_cell", "serde", @@ -10403,6 +12488,60 @@ dependencies = [ "chrono", ] +[[package]] +name = "rollup-boost" +version = "0.1.0" +source = "git+https://github.com/flashbots/rollup-boost?tag=v0.7.11#196237bab2a02298de994b439e0455abb1ac512f" +dependencies = [ + "alloy-primitives 1.5.2", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "backoff", + "bytes", + "clap", + "dashmap 6.1.0", + "dotenvy", + "eyre", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "jsonrpsee 0.25.1", + "lru 0.16.3", + "metrics", + "metrics-derive", + "metrics-exporter-prometheus 0.16.2", + "metrics-util 0.19.1", + "moka", + "op-alloy-rpc-types-engine", + "opentelemetry 0.28.0", + "opentelemetry-otlp 0.28.0", + "opentelemetry_sdk 0.28.0", + "parking_lot", + "paste", + "reth-optimism-payload-builder", + "rustls", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.17", + "tokio", + "tokio-tungstenite 0.26.2", + "tokio-util", + "tower 0.5.2", + "tower-http", + "tracing", + "tracing-opentelemetry 0.29.0", + "tracing-subscriber 0.3.22", + "url", + "uuid", + "vergen", + "vergen-git2", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -10438,6 +12577,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rtnetlink" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" +dependencies = [ + "futures", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-packet-utils", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "rug" version = "1.28.0" @@ -10485,6 +12642,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -10527,6 +12690,15 @@ dependencies = [ "semver 1.0.27", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.38.44" @@ -10555,10 +12727,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -10580,6 +12753,15 @@ dependencies = [ "security-framework 3.5.1", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.13.2" @@ -10619,10 +12801,11 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -10646,6 +12829,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + [[package]] name = "ryu" version = "1.0.22" @@ -10880,6 +13074,25 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -10914,6 +13127,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_regex" version = "1.1.0" @@ -10924,6 +13148,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -10933,6 +13168,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -10976,6 +13220,19 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serdect" version = "0.2.0" @@ -10997,6 +13254,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -11175,6 +13438,23 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek", + "rand_core 0.6.4", + "ring", + "rustc_version 0.4.1", + "sha2", + "subtle", +] + [[package]] name = "socket2" version = "0.5.10" @@ -11251,6 +13531,29 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.114", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "strum" version = "0.26.3" @@ -11322,6 +13625,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4e6eed052a117409a1a744c8bda9c3ea6934597cf7419f791cb7d590871c4c" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "syn-solidity" version = "1.5.2" @@ -11343,6 +13658,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -11367,6 +13694,41 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "sysinfo" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.61.3", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tabwriter" version = "1.4.1" @@ -11416,6 +13778,55 @@ dependencies = [ "num-traits", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tdx" +version = "0.2.0" +source = "git+https://github.com/automata-network/tdx-attestation-sdk.git?branch=main#0c75c913a8a00728efa17e068e319ea742eba85f" +dependencies = [ + "alloy", + "anyhow", + "base64-url", + "cbindgen", + "chrono", + "clap", + "coco-provider", + "dcap-rs", + "hex", + "rand 0.8.5", + "serde", + "tokio", + "ureq", + "x509-parser 0.15.1", +] + +[[package]] +name = "tdx-quote-provider" +version = "0.1.0" +dependencies = [ + "axum 0.8.8", + "clap", + "dotenvy", + "eyre", + "hex", + "metrics", + "metrics-derive", + "metrics-exporter-prometheus 0.17.2", + "reqwest", + "serde", + "serde_json", + "tdx", + "thiserror 2.0.17", + "tokio", + "tracing", + "tracing-subscriber 0.3.22", +] + [[package]] name = "tempfile" version = "3.24.0" @@ -11429,6 +13840,35 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "testcontainers" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bb7577dca13ad86a78e8271ef5d322f37229ec83b8d98da6d996c588a1ddb1" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes", + "docker_credential", + "either", + "etcetera", + "futures", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "url", +] + [[package]] name = "thin-vec" version = "0.2.14" @@ -11603,6 +14043,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tips-core" +version = "0.1.0" +source = "git+https://github.com/base/tips?rev=c08eaa4fe10c26de8911609b41ddab4918698325#c08eaa4fe10c26de8911609b41ddab4918698325" +dependencies = [ + "alloy-consensus", + "alloy-primitives 1.5.2", + "alloy-provider", + "alloy-serde", + "op-alloy-consensus", + "op-alloy-flz", + "serde", + "tracing", + "tracing-subscriber 0.3.22", + "uuid", +] + [[package]] name = "tokio" version = "1.49.0" @@ -11663,6 +14120,21 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + [[package]] name = "tokio-tungstenite" version = "0.26.2" @@ -11671,10 +14143,12 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", + "native-tls", "rustls", "rustls-native-certs", "rustls-pki-types", "tokio", + "tokio-native-tls", "tokio-rustls", "tungstenite 0.26.2", "webpki-roots 0.26.11", @@ -11716,11 +14190,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_edit 0.22.27", ] +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -11747,7 +14236,7 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.13.0", "serde", - "serde_spanned", + "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", "winnow", @@ -11774,12 +14263,48 @@ dependencies = [ "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.7.9", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.5", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tonic" version = "0.14.2" @@ -11800,7 +14325,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -11813,8 +14338,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" dependencies = [ "bytes", - "prost", - "tonic", + "prost 0.14.1", + "tonic 0.14.2", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", ] [[package]] @@ -11861,7 +14406,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -11969,6 +14514,24 @@ dependencies = [ "tracing-subscriber 0.3.22", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721f2d2569dce9f3dfbbddee5906941e953bfcdf736a62da3377f5751650cc36" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry 0.28.0", + "opentelemetry_sdk 0.28.0", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber 0.3.22", + "web-time", +] + [[package]] name = "tracing-opentelemetry" version = "0.32.0" @@ -11976,8 +14539,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6e5658463dd88089aba75c7791e1d3120633b1bfde22478b28f625a9bb1b8e" dependencies = [ "js-sys", - "opentelemetry", - "opentelemetry_sdk", + "opentelemetry 0.31.0", + "opentelemetry_sdk 0.31.0", "rustversion", "smallvec", "thiserror 2.0.17", @@ -12034,7 +14597,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee44f4cef85f88b4dea21c0b1f58320bdf35715cf56d840969487cff00613321" dependencies = [ - "alloy-primitives", + "alloy-primitives 1.5.2", "ethereum_hashing", "ethereum_ssz", "smallvec", @@ -12075,6 +14638,39 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tss-esapi" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ea9ccde878b029392ac97b5be1f470173d06ea41d18ad0bb3c92794c16a0f2" +dependencies = [ + "bitfield 0.14.0", + "enumflags2", + "getrandom 0.2.16", + "hostname-validator", + "log", + "mbox", + "num-derive", + "num-traits", + "oid", + "picky-asn1", + "picky-asn1-x509", + "regex", + "serde", + "tss-esapi-sys", + "zeroize", +] + +[[package]] +name = "tss-esapi-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535cd192581c2ec4d5f82e670b1d3fbba6a23ccce8c85de387642051d7cad5b5" +dependencies = [ + "pkg-config", + "target-lexicon", +] + [[package]] name = "tungstenite" version = "0.26.2" @@ -12086,6 +14682,7 @@ dependencies = [ "http", "httparse", "log", + "native-tls", "rand 0.9.2", "rustls", "rustls-pki-types", @@ -12211,6 +14808,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" + [[package]] name = "unsigned-varint" version = "0.8.0" @@ -12223,6 +14832,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + [[package]] name = "url" version = "2.5.8" @@ -12269,6 +14896,7 @@ dependencies = [ "getrandom 0.3.4", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] @@ -12526,6 +15154,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "widestring" version = "1.2.1" @@ -12563,6 +15203,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" +dependencies = [ + "windows-core 0.53.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.57.0" @@ -12573,16 +15223,38 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections", + "windows-collections 0.3.2", "windows-core 0.62.2", - "windows-future", - "windows-numerics", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -12594,6 +15266,16 @@ dependencies = [ "windows-core 0.62.2", ] +[[package]] +name = "windows-core" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.57.0" @@ -12606,6 +15288,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -12614,9 +15309,20 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement 0.60.2", "windows-interface 0.59.3", - "windows-link", + "windows-link 0.2.1", "windows-result 0.4.1", - "windows-strings", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", ] [[package]] @@ -12626,8 +15332,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core 0.62.2", - "windows-link", - "windows-threading", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -12674,12 +15380,28 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + [[package]] name = "windows-numerics" version = "0.3.1" @@ -12687,7 +15409,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ "windows-core 0.62.2", - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -12699,13 +15432,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", ] [[package]] @@ -12714,7 +15465,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -12768,7 +15519,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -12823,7 +15574,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -12834,13 +15585,22 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -13088,6 +15848,52 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs 0.5.2", + "data-encoding", + "der-parser 8.2.0", + "lazy_static", + "nom", + "oid-registry 0.6.1", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs 0.7.1", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", + "rusticata-macros", + "thiserror 2.0.17", + "time", +] + [[package]] name = "xattr" version = "1.6.1" @@ -13098,18 +15904,73 @@ dependencies = [ "rustix 1.1.3", ] +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + [[package]] name = "xsum" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0637d3a5566a82fa5214bae89087bc8c9fb94cd8e8a3c07feb691bb8d9c632db" +[[package]] +name = "yamux" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "yamux" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deab71f2e20691b4728b349c6cee8fc7223880fa67b6b4f92225ec32225447e5" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.9.2", + "static_assertions", + "web-time", +] + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.1" @@ -13130,7 +15991,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -13171,7 +16032,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "synstructure", + "synstructure 0.13.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ad905eda..0f804acd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,11 @@ rust-version = "1.88" license = "MIT" homepage = "https://github.com/base/node-reth" repository = "https://github.com/base/node-reth" +exclude = [".github/"] [workspace] resolver = "2" -members = ["bin/*", "crates/client/*", "crates/shared/*"] +members = ["bin/*", "crates/client/*", "crates/shared/*", "crates/builder/*"] default-members = ["bin/node"] [workspace.metadata.cargo-udeps.ignore] @@ -69,6 +70,8 @@ reth-ipc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-exex = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-tracing = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-cli-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-provider = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-db-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } @@ -78,8 +81,9 @@ reth-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" reth-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-rpc-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", features = ["client"] } reth-optimism-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-testing-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-node = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } @@ -89,13 +93,48 @@ reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9. reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } reth-optimism-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } -reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", features = [ - "op", -] } +reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3", features = ["op"] } +reth-chain-state = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-trie = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-trie-parallel = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-execution-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-tasks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-metrics = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-trie-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-execution-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-revm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-payload-builder-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-payload-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-network-peers = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-tracing-otlp = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-consensus = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-forks = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } +reth-optimism-txpool = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" } # revm -revm = { version = "31.0.2", default-features = false } +revm = { version = "31.0.2", features = ["std", "secp256k1", "optional_balance_check"], default-features = false } revm-bytecode = { version = "7.1.1", default-features = false } +revm-inspectors = { version = "0.32.0", default-features = false } +revm-database = { version = "9.0.5", default-features = false } +revm-state = { version = "8.1.1", default-features = false } +revm-primitives = { version = "21.0.2", default-features = false } +revm-interpreter = { version = "29.0.1", default-features = false } +revm-inspector = { version = "12.0.0", default-features = false } +revm-context = { version = "11.0.2", default-features = false } +revm-context-interface = { version = "12.0.1", default-features = false } +revm-database-interface = { version = "8.0.5", default-features = false } # alloy alloy-rlp = "0.3.10" @@ -107,25 +146,39 @@ alloy-genesis = "1.0.41" alloy-signer = "1.0.41" alloy-signer-local = "1.0.41" alloy-hardforks = "0.4.4" -alloy-provider = "1.0.41" +alloy-provider = { version = "1.0.41", features = ["ipc", "pubsub", "txpool-api", "engine-api"] } alloy-contract = "1.0.41" -alloy-sol-types = "1.4.1" +alloy-sol-types = { version = "1.4.1", features = ["json"] } alloy-sol-macro = "1.4.1" -alloy-primitives = "1.4.1" -alloy-consensus = "1.0.41" +alloy-primitives = { version = "1.4.1", default-features = false, features = ["map-foldhash"] } +alloy-consensus = { version = "1.0.41", features = ["kzg"] } alloy-rpc-types = "1.0.41" alloy-rpc-client = "1.0.41" alloy-rpc-types-eth = "1.0.41" -alloy-rpc-types-engine = "1.0.41" +alloy-rpc-types-engine = { version = "1.0.41", features = ["ssz"] } +alloy-chains = "0.2.5" +alloy-evm = { version = "0.23.0", default-features = false } +alloy-pubsub = "1.0.41" +alloy-json-rpc = "1.0.41" +alloy-transport-http = "1.0.41" +alloy-network = "1.0.41" +alloy-network-primitives = "1.0.41" +alloy-transport = "1.0.41" +alloy-node-bindings = "1.0.41" +alloy-rpc-types-beacon = { version = "1.0.41", features = ["ssz"] } # op-alloy -op-alloy-flz = "0.13.1" -op-alloy-network = "0.22.0" -op-alloy-rpc-types = "0.22.0" -op-alloy-consensus = "0.22.0" -op-alloy-rpc-jsonrpsee = "0.22.0" -op-alloy-rpc-types-engine = "0.22.0" +op-alloy-flz = { version = "0.13.1", default-features = false } +op-alloy-network = { version = "0.22.0", default-features = false } +op-alloy-rpc-types = { version = "0.22.0", default-features = false } +op-alloy-consensus = { version = "0.22.0", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.22.0", default-features = false } +op-alloy-rpc-types-engine = { version = "0.22.0", default-features = false } alloy-op-evm = { version = "0.23.3", default-features = false } +alloy-op-hardforks = "0.4.4" + +# rollup-boost +rollup-boost = { git = "https://github.com/flashbots/rollup-boost", tag = "v0.7.11" } # op-revm op-revm = { version = "12.0.2", default-features = false } @@ -149,9 +202,10 @@ url = "2.5.7" lru = "0.16.2" rand = "0.9.2" uuid = "1.19.0" -time = "0.3.44" +time = { version = "0.3.44", features = ["macros", "formatting", "parsing"] } rayon = "1.11" -clap = "4.5.53" +clap = { version = "4.5.53", features = ["derive", "env", "string"] } +clap_builder = "4.5.19" eyre = "0.6.12" bytes = "1.11.0" brotli = "8.0.2" @@ -170,3 +224,29 @@ serde_json = "1.0.145" metrics-derive = "0.1.0" tracing-subscriber = "0.3.22" thiserror = "2.0" +async-trait = "0.1.83" +parking_lot = "0.12.3" +auto_impl = "1.2.0" +serde_with = "3.8.1" +secp256k1 = "0.30" +either = { version = "1.15.0", default-features = false } +tokio-util = { version = "0.7.4", features = ["codec"] } +warp = "0.3.7" +flate2 = "1.0.37" +prometheus = "0.13.4" +ctor = "0.2" +dashmap = "6.1" +hex = "0.4" +lazy_static = "1.4.0" +tikv-jemallocator = "0.6" +ahash = "0.8.6" +vergen = "9.0.4" +vergen-git2 = "1.0.5" +opentelemetry = { version = "0.31", features = ["trace"] } +jsonrpsee-core = "0.26.0" +ethereum_ssz = "0.9.0" +ethereum_ssz_derive = "0.9.0" + +# base +concurrent-queue = "2.5.0" +tips-core = { git = "https://github.com/base/tips", rev = "c08eaa4fe10c26de8911609b41ddab4918698325", default-features = false } diff --git a/Justfile b/Justfile index b231c87e..4118cd4e 100644 --- a/Justfile +++ b/Justfile @@ -39,7 +39,7 @@ zepter-fix: @command -v zepter >/dev/null 2>&1 || cargo install zepter zepter format features --fix -# Runs tests across workspace with all features enabled +# Runs tests across workspace with all features enabled (excludes builder crates) test: build-contracts @command -v cargo-nextest >/dev/null 2>&1 || cargo install cargo-nextest RUSTFLAGS="-D warnings" cargo nextest run --workspace --all-features @@ -48,16 +48,16 @@ test: build-contracts hack: cargo hack check --feature-powerset --no-dev-deps -# Checks formatting +# Checks formatting (builder crates excluded via rustfmt.toml) check-format: cargo +nightly fmt --all -- --check -# Fixes any formatting issues +# Fixes any formatting issues (builder crates excluded via rustfmt.toml) format-fix: - cargo fix --allow-dirty --allow-staged + cargo fix --allow-dirty --allow-staged --workspace cargo +nightly fmt --all -# Checks clippy +# Checks clippy (excludes builder crates) check-clippy: cargo clippy --all-targets -- -D warnings @@ -89,7 +89,7 @@ build-contracts: clean: cargo clean -# Checks if there are any unused dependencies +# 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 @@ -113,3 +113,47 @@ benches: # Runs flashblocks pending state benchmarks bench-flashblocks: cargo bench -p base-flashblocks --bench pending_state + +# ============================================ +# Builder Crate Targets +# ============================================ + +# Builds builder crates +build-builder: + cargo build -p op-rbuilder -p p2p -p tdx-quote-provider + +# Builds op-rbuilder binary +build-op-rbuilder: + cargo build -p op-rbuilder --bin op-rbuilder + +# Builds tester binary (requires testing feature) +build-tester: + cargo build -p op-rbuilder --bin tester --features "testing" + +# Builds tdx-quote-provider binary +build-tdx-quote-provider: + cargo build -p tdx-quote-provider --bin tdx-quote-provider + +# Runs tests for builder crates (with OTEL env vars disabled) +test-builder: + OTEL_EXPORTER_OTLP_ENDPOINT="" OTEL_EXPORTER_OTLP_HEADERS="" OTEL_SDK_DISABLED="true" \ + cargo test -p op-rbuilder -p p2p -p tdx-quote-provider --verbose + +# Runs clippy on builder crates (using nightly, matching op-rbuilder's original) +check-clippy-builder: + cargo +nightly clippy -p op-rbuilder -p p2p -p tdx-quote-provider --all-features -- -D warnings + +# Fixes formatting for builder crates +format-builder: + cargo +nightly fmt -p op-rbuilder -p p2p -p tdx-quote-provider + +# Full builder CI check +ci-builder: build-builder test-builder check-clippy-builder + +# Builds builder release binary +build-builder-release: + cargo build --release -p op-rbuilder --bin op-rbuilder + +# Builds builder with maxperf profile +build-builder-maxperf: + cargo build --profile maxperf -p op-rbuilder --bin op-rbuilder --features jemalloc diff --git a/crates/builder/op-rbuilder/Cargo.toml b/crates/builder/op-rbuilder/Cargo.toml index a0f5c8d2..517cb67f 100644 --- a/crates/builder/op-rbuilder/Cargo.toml +++ b/crates/builder/op-rbuilder/Cargo.toml @@ -8,8 +8,12 @@ homepage.workspace = true repository.workspace = true default-run = "op-rbuilder" -[lints] -workspace = true +[lints.rust] +unreachable_pub = "deny" +dead_code = "allow" + +[lints.clippy] +unused_async = "warn" [dependencies] p2p = { path = "../p2p" } @@ -171,41 +175,41 @@ hyper-util = { version = "0.1.11" } http-body-util = { version = "0.1.3" } [features] -default = ["jemalloc"] +default = [ "jemalloc" ] jemalloc = [ - "dep:tikv-jemallocator", - "reth-cli-util/jemalloc", - "reth-optimism-cli/jemalloc", + "dep:tikv-jemallocator", + "reth-cli-util/jemalloc", + "reth-optimism-cli/jemalloc", ] jemalloc-prof = [ - "jemalloc", - "tikv-jemallocator?/profiling", - "reth/jemalloc-prof", - "reth-cli-util/jemalloc-prof", + "jemalloc", + "reth-cli-util/jemalloc-prof", + "reth/jemalloc-prof", + "tikv-jemallocator?/profiling", ] -min-error-logs = ["tracing/release_max_level_error"] -min-warn-logs = ["tracing/release_max_level_warn"] -min-info-logs = ["tracing/release_max_level_info"] -min-debug-logs = ["tracing/release_max_level_debug"] -min-trace-logs = ["tracing/release_max_level_trace"] +min-error-logs = [ "tracing/release_max_level_error" ] +min-warn-logs = [ "tracing/release_max_level_warn" ] +min-info-logs = [ "tracing/release_max_level_info" ] +min-debug-logs = [ "tracing/release_max_level_debug" ] +min-trace-logs = [ "tracing/release_max_level_trace" ] testing = [ - "nanoid", - "reth-ipc", - "reth-node-builder/test-utils", - "ctor", - "macros", - "rlimit", - "hyper", - "hyper-util", - "http-body-util", + "ctor", + "http-body-util", + "hyper", + "hyper-util", + "macros", + "nanoid", + "reth-ipc", + "reth-node-builder/test-utils", + "rlimit", ] interop = [] -telemetry = ["reth-tracing-otlp", "opentelemetry"] +telemetry = [ "opentelemetry", "reth-tracing-otlp" ] custom-engine-api = [] diff --git a/crates/builder/p2p/Cargo.toml b/crates/builder/p2p/Cargo.toml index 5f512c4a..33d57c31 100644 --- a/crates/builder/p2p/Cargo.toml +++ b/crates/builder/p2p/Cargo.toml @@ -24,5 +24,9 @@ tokio = { workspace = true, features = [ "macros" ] } tokio-util = { workspace = true, features = [ "compat", "codec" ] } tracing = { workspace = true } -[lints] -workspace = true +[lints.rust] +unreachable_pub = "deny" +dead_code = "allow" + +[lints.clippy] +unused_async = "warn" diff --git a/crates/builder/tdx-quote-provider/Cargo.toml b/crates/builder/tdx-quote-provider/Cargo.toml index 5616cba8..f672de56 100644 --- a/crates/builder/tdx-quote-provider/Cargo.toml +++ b/crates/builder/tdx-quote-provider/Cargo.toml @@ -29,6 +29,12 @@ metrics-exporter-prometheus = { version = "0.17.0", features = [ tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } tdx = { git = "https://github.com/automata-network/tdx-attestation-sdk.git", features = ["configfs"], branch = "main"} +[lints.rust] +dead_code = "allow" + +[lints.clippy] +unused_async = "warn" + [dev-dependencies] reqwest.workspace = true @@ -38,4 +44,4 @@ path = "src/main.rs" [lib] name = "tdx_quote_provider" -path = "src/lib.rs" \ No newline at end of file +path = "src/lib.rs" diff --git a/crates/builder/tdx-quote-provider/README.md b/crates/builder/tdx-quote-provider/README.md index 205bdd67..99b24cd0 100644 --- a/crates/builder/tdx-quote-provider/README.md +++ b/crates/builder/tdx-quote-provider/README.md @@ -89,8 +89,6 @@ To build a docker image: docker build -f crates/tdx-quote-provider/Dockerfile -t tdx-quote-provider . ``` -Builds of the websocket proxy are provided on [Dockerhub](https://hub.docker.com/r/flashbots/tdx-quote-provider/tags). - You can see a full list of parameters by running: `docker run flashbots/tdx-quote-provider:latest --help` @@ -99,4 +97,4 @@ Example: ```bash docker run flashbots/tdx-quote-provider:latest -``` \ No newline at end of file +``` diff --git a/rustfmt.toml b/rustfmt.toml index 6e800413..b7910780 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -4,3 +4,8 @@ group_imports = "StdExternalCrate" use_small_heuristics = "Max" trailing_comma = "Vertical" use_field_init_shorthand = true + +# Ignore builder crates - they have their own formatting style +ignore = [ + "crates/builder/**", +] diff --git a/scripts/check-crate-deps.sh b/scripts/check-crate-deps.sh index f8b478aa..6ae94711 100755 --- a/scripts/check-crate-deps.sh +++ b/scripts/check-crate-deps.sh @@ -24,7 +24,7 @@ VIOLATIONS=$(cargo metadata --format-version 1 --no-deps | jq -r ' ') if [ -n "$VIOLATIONS" ]; then - echo "ERROR: Found dependency boundary violations:" + echo "ERROR: Found shared -> client dependency violations:" echo "$VIOLATIONS" | while read -r violation; do echo " - $violation" done @@ -33,4 +33,29 @@ if [ -n "$VIOLATIONS" ]; then exit 1 fi -echo "All shared crates have valid dependencies (no client crate dependencies)" +echo "Checking that shared crates don't depend on builder crates..." + +# Check for shared -> builder violations +BUILDER_VIOLATIONS=$(cargo metadata --format-version 1 --no-deps | jq -r ' + [.packages[] + | select(.manifest_path | contains("/crates/shared/")) + | . as $pkg + | .dependencies[] + | select(.path) + | select(.path | contains("/crates/builder/")) + | "\($pkg.name) -> \(.name)" + ] + | .[] +') + +if [ -n "$BUILDER_VIOLATIONS" ]; then + echo "ERROR: Found shared -> builder dependency violations:" + echo "$BUILDER_VIOLATIONS" | while read -r violation; do + echo " - $violation" + done + echo "" + echo "Shared crates (crates/shared/) must not depend on builder crates (crates/builder/)" + exit 1 +fi + +echo "All crate dependencies are valid" From 2f7eac2374896c04138f5acaca736b759e68d48e Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 18:49:31 -0600 Subject: [PATCH 261/262] fix running tests locally (we have oltp env vars on macs --- .../src/tests/framework/instance.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/builder/op-rbuilder/src/tests/framework/instance.rs b/crates/builder/op-rbuilder/src/tests/framework/instance.rs index 52016e6b..9458aea7 100644 --- a/crates/builder/op-rbuilder/src/tests/framework/instance.rs +++ b/crates/builder/op-rbuilder/src/tests/framework/instance.rs @@ -54,6 +54,25 @@ use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; use tokio_tungstenite::{connect_async, tungstenite::Message}; use tokio_util::sync::CancellationToken; +/// Clears OTEL-related environment variables that can interfere with CLI argument parsing. +/// This is necessary because clap reads env vars for args with `env = "..."` attributes, +/// and external OTEL env vars (e.g., `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`) may contain +/// values that are incompatible with the CLI's expected values. +fn clear_otel_env_vars() { + for key in [ + "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_HEADERS", + "OTEL_EXPORTER_OTLP_PROTOCOL", + "OTEL_LOGS_EXPORTER", + "OTEL_METRICS_EXPORTER", + "OTEL_TRACES_EXPORTER", + "OTEL_SDK_DISABLED", + ] { + // SAFETY: We're in a test environment where env var mutation is acceptable + unsafe { std::env::remove_var(key) }; + } +} + /// Represents a type that emulates a local in-process instance of the OP builder node. /// This node uses IPC as the communication channel for the RPC server Engine API. pub struct LocalInstance { @@ -197,6 +216,7 @@ 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!() @@ -207,6 +227,7 @@ impl LocalInstance { /// 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!() From 8391c043cf27f3a201cbd1a422fc69097e819d04 Mon Sep 17 00:00:00 2001 From: Danyal Prout Date: Sun, 11 Jan 2026 19:02:05 -0600 Subject: [PATCH 262/262] fix: add native dependencies to udeps CI job The tss-esapi-sys crate requires libtss2-dev to build, which was missing from the udeps job causing CI failures. --- .github/workflows/ci.yml | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96180292..8a3b224d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,19 @@ jobs: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 with: @@ -44,6 +57,19 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 @@ -96,6 +122,19 @@ jobs: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + - uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable with: components: clippy @@ -142,6 +181,19 @@ jobs: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install native dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + libsqlite3-dev \ + clang \ + libclang-dev \ + llvm \ + build-essential \ + pkg-config \ + libtss2-dev + - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - uses: dtolnay/rust-toolchain@0c3131df9e5407c0c36352032d04af846dbe0fb7 # nightly - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2