diff --git a/Cargo.lock b/Cargo.lock index d9dc6c20d..e952ed55f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5056,6 +5056,16 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "macros" +version = "0.1.0" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "matchers" version = "0.1.0" @@ -5693,6 +5703,7 @@ dependencies = [ "jsonrpsee 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpsee-core 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpsee-types 0.25.1 (registry+https://github.com/rust-lang/crates.io-index)", + "macros", "metrics", "moka", "nanoid", diff --git a/crates/op-rbuilder/Cargo.toml b/crates/op-rbuilder/Cargo.toml index 5e8287065..b39ead6a2 100644 --- a/crates/op-rbuilder/Cargo.toml +++ b/crates/op-rbuilder/Cargo.toml @@ -122,6 +122,7 @@ 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 } +macros = { path = "src/tests/framework/macros", optional = true } [target.'cfg(unix)'.dependencies] tikv-jemallocator = { version = "0.6", optional = true } @@ -135,7 +136,7 @@ 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 } @@ -172,6 +173,7 @@ testing = [ "reth-node-builder/test-utils", "bollard", "ctor", + "macros", ] diff --git a/crates/op-rbuilder/src/tests/vanilla/data_availability.rs b/crates/op-rbuilder/src/tests/data_availability.rs similarity index 77% rename from crates/op-rbuilder/src/tests/vanilla/data_availability.rs rename to crates/op-rbuilder/src/tests/data_availability.rs index 745f10b6e..ec37ed10e 100644 --- a/crates/op-rbuilder/src/tests/vanilla/data_availability.rs +++ b/crates/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/op-rbuilder/src/tests/flashblocks/smoke.rs b/crates/op-rbuilder/src/tests/flashblocks.rs similarity index 79% rename from crates/op-rbuilder/src/tests/flashblocks/smoke.rs rename to crates/op-rbuilder/src/tests/flashblocks.rs index 5a98b5d0d..5ee8a1de5 100644 --- a/crates/op-rbuilder/src/tests/flashblocks/smoke.rs +++ b/crates/op-rbuilder/src/tests/flashblocks.rs @@ -1,31 +1,22 @@ 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}; use tokio_util::sync::CancellationToken; use crate::{ - args::{FlashblocksArgs, OpRbuilderArgs}, - builders::FlashblocksBuilder, + args::OpRbuilderArgs, 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, - }, - ..Default::default() - }) - .await?; - +#[rb_test(flashblocks, args = OpRbuilderArgs { + chain_block_time: 2000, + ..Default::default() +})] +async fn smoke(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; driver.fund_default_accounts().await?; diff --git a/crates/op-rbuilder/src/tests/flashblocks/mod.rs b/crates/op-rbuilder/src/tests/flashblocks/mod.rs deleted file mode 100644 index 6ca676337..000000000 --- a/crates/op-rbuilder/src/tests/flashblocks/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![cfg(test)] - -mod smoke; diff --git a/crates/op-rbuilder/src/tests/framework/macros/Cargo.toml b/crates/op-rbuilder/src/tests/framework/macros/Cargo.toml new file mode 100644 index 000000000..c131b63bf --- /dev/null +++ b/crates/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/op-rbuilder/src/tests/framework/macros/src/lib.rs b/crates/op-rbuilder/src/tests/framework/macros/src/lib.rs new file mode 100644 index 000000000..5e9858f8d --- /dev/null +++ b/crates/op-rbuilder/src/tests/framework/macros/src/lib.rs @@ -0,0 +1,293 @@ +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 '{}' expression. Use either '{} = expression' or 'args/config' parameters, not both.", + variant, variant + ), + )); + } + } + + // 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/op-rbuilder/src/tests/mod.rs b/crates/op-rbuilder/src/tests/mod.rs index e3f96187c..ecdda8bb2 100644 --- a/crates/op-rbuilder/src/tests/mod.rs +++ b/crates/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/op-rbuilder/src/tests/vanilla/ordering.rs b/crates/op-rbuilder/src/tests/ordering.rs similarity index 84% rename from crates/op-rbuilder/src/tests/vanilla/ordering.rs rename to crates/op-rbuilder/src/tests/ordering.rs index 74fe89677..11cc496c7 100644 --- a/crates/op-rbuilder/src/tests/vanilla/ordering.rs +++ b/crates/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/op-rbuilder/src/tests/vanilla/revert.rs b/crates/op-rbuilder/src/tests/revert.rs similarity index 84% rename from crates/op-rbuilder/src/tests/vanilla/revert.rs rename to crates/op-rbuilder/src/tests/revert.rs index d137c1cc4..97da3cb45 100644 --- a/crates/op-rbuilder/src/tests/vanilla/revert.rs +++ b/crates/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 @@ -220,14 +216,11 @@ async fn bundle_min_block_number() -> 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 @@ -309,14 +302,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 { @@ -341,14 +331,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/op-rbuilder/src/tests/vanilla/smoke.rs b/crates/op-rbuilder/src/tests/smoke.rs similarity index 75% rename from crates/op-rbuilder/src/tests/vanilla/smoke.rs rename to crates/op-rbuilder/src/tests/smoke.rs index 8fd537e25..4fa000a34 100644 --- a/crates/op-rbuilder/src/tests/vanilla/smoke.rs +++ b/crates/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 thransaction 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/op-rbuilder/src/tests/vanilla/txpool.rs b/crates/op-rbuilder/src/tests/txpool.rs similarity index 75% rename from crates/op-rbuilder/src/tests/vanilla/txpool.rs rename to crates/op-rbuilder/src/tests/txpool.rs index be8aae121..16f1e29b9 100644 --- a/crates/op-rbuilder/src/tests/vanilla/txpool.rs +++ b/crates/op-rbuilder/src/tests/txpool.rs @@ -1,26 +1,22 @@ -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() + }, +)] +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/op-rbuilder/src/tests/vanilla/mod.rs b/crates/op-rbuilder/src/tests/vanilla/mod.rs deleted file mode 100644 index 5eb1364e1..000000000 --- a/crates/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;