diff --git a/crates/node/src/config.rs b/crates/node/src/config.rs index e12a5fe..1de4a5f 100644 --- a/crates/node/src/config.rs +++ b/crates/node/src/config.rs @@ -119,6 +119,7 @@ impl EvolvePayloadBuilderConfig { } } } + Ok(config) } @@ -400,10 +401,7 @@ mod tests { mint_admin: Some(address!("00000000000000000000000000000000000000aa")), base_fee_redirect_activation_height: Some(0), mint_precompile_activation_height: Some(0), - contract_size_limit: None, - contract_size_limit_activation_height: None, - deploy_allowlist: Vec::new(), - deploy_allowlist_activation_height: None, + ..Default::default() }; assert!(config_with_sink.validate().is_ok()); } @@ -468,14 +466,9 @@ mod tests { allowlist.push(addr); } let config = EvolvePayloadBuilderConfig { - base_fee_sink: None, - mint_admin: None, - base_fee_redirect_activation_height: None, - mint_precompile_activation_height: None, - contract_size_limit: None, - contract_size_limit_activation_height: None, deploy_allowlist: allowlist, deploy_allowlist_activation_height: Some(0), + ..Default::default() }; assert!(matches!( @@ -489,13 +482,8 @@ mod tests { let sink = address!("0000000000000000000000000000000000000003"); let mut config = EvolvePayloadBuilderConfig { base_fee_sink: Some(sink), - mint_admin: None, base_fee_redirect_activation_height: Some(5), - mint_precompile_activation_height: None, - contract_size_limit: None, - contract_size_limit_activation_height: None, - deploy_allowlist: Vec::new(), - deploy_allowlist_activation_height: None, + ..Default::default() }; assert_eq!(config.base_fee_sink_for_block(4), None); diff --git a/crates/node/src/payload_service.rs b/crates/node/src/payload_service.rs index 09e6186..bb0cb2f 100644 --- a/crates/node/src/payload_service.rs +++ b/crates/node/src/payload_service.rs @@ -63,6 +63,7 @@ where pub(crate) evolve_builder: Arc>, pub(crate) config: EvolvePayloadBuilderConfig, pub(crate) pool: Pool, + pub(crate) dev_mode: bool, } impl PayloadBuilderBuilder for EvolvePayloadBuilderBuilder @@ -117,6 +118,7 @@ where evolve_builder, config, pool, + dev_mode: ctx.is_dev(), }) } } @@ -180,9 +182,9 @@ where } } - // Use transactions from Engine API attributes if provided, otherwise pull from the pool - // (e.g. in --dev mode where LocalMiner sends empty attributes). - let transactions = if attributes.transactions.is_empty() { + // In dev mode, pull pending transactions from the txpool. + // In production, transactions come exclusively from Engine API attributes. + let transactions = if self.dev_mode { let pool_txs: Vec = self .pool .pending_transactions() @@ -192,7 +194,7 @@ where if !pool_txs.is_empty() { info!( pool_tx_count = pool_txs.len(), - "pulling transactions from pool" + "pulling transactions from pool (dev mode)" ); } pool_txs @@ -382,6 +384,7 @@ mod tests { evolve_builder, config, pool: NoopTransactionPool::::new(), + dev_mode: false, }; let rpc_attrs = RpcPayloadAttributes { @@ -472,6 +475,7 @@ mod tests { evolve_builder, config, pool: NoopTransactionPool::::new(), + dev_mode: false, }; let rpc_attrs = RpcPayloadAttributes { diff --git a/crates/tests/src/e2e_tests.rs b/crates/tests/src/e2e_tests.rs index 18ed5b5..eee0bb2 100644 --- a/crates/tests/src/e2e_tests.rs +++ b/crates/tests/src/e2e_tests.rs @@ -273,7 +273,7 @@ async fn test_e2e_base_fee_sink_receives_base_fee() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -408,7 +408,7 @@ async fn test_e2e_sponsored_evnode_transaction() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -614,7 +614,7 @@ async fn test_e2e_invalid_sponsor_signature_skipped() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -739,7 +739,7 @@ async fn test_e2e_empty_calls_skipped() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -852,7 +852,7 @@ async fn test_e2e_sponsor_insufficient_max_fee_skipped() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -1000,7 +1000,7 @@ async fn test_e2e_nonce_bumped_on_create_batch_failure() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -1239,7 +1239,7 @@ async fn test_e2e_mint_and_burn_to_new_wallet() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec.clone()) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -1661,7 +1661,7 @@ async fn test_e2e_mint_precompile_via_contract() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec.clone()) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -1892,7 +1892,7 @@ async fn test_e2e_deploy_allowlist_blocks_unauthorized_deploys() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -2023,3 +2023,70 @@ async fn test_e2e_deploy_allowlist_blocks_unauthorized_deploys() -> Result<()> { Ok(()) } + +/// Tests that dev mode correctly enables the txpool fallback. +/// +/// When running with `--dev` flag, the payload builder pulls pending +/// transactions from the txpool instead of relying on Engine API attributes. +/// This validates the full flow: +/// `--dev` flag → `ctx.is_dev()` → `dev_mode` on payload builder → txpool +#[tokio::test(flavor = "multi_thread")] +async fn test_e2e_dev_mode_txpool_fallback() -> Result<()> { + reth_tracing::init_test_tracing(); + + let chain_spec = create_test_chain_spec(); + let chain_id = chain_spec.chain().id(); + + let mut setup = Setup::::default() + .with_chain_spec(chain_spec) + .with_network(NetworkSetup::single_node()) + .with_dev_mode(true) + .with_tree_config(e2e_test_tree_config()); + + let mut env = Environment::::default(); + setup.apply::(&mut env).await?; + + let parent_block = env.node_clients[0] + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .expect("parent block should exist"); + let mut parent_hash = parent_block.header.hash; + let mut parent_timestamp = parent_block.header.inner.timestamp; + let mut parent_number = parent_block.header.inner.number; + + // Create a signed transaction and send it to the txpool + let wallets = Wallet::new(1).with_chain_id(chain_id).wallet_gen(); + let sender = wallets.into_iter().next().unwrap(); + let raw_tx = TransactionTestContext::transfer_tx_bytes(chain_id, sender).await; + + EthApiClient::::send_raw_transaction( + &env.node_clients[0].rpc, + raw_tx, + ) + .await?; + + // Build a block with empty transactions via Engine API. + // In dev mode, the payload builder pulls from the txpool. + let payload_envelope = build_block_with_transactions( + &mut env, + &mut parent_hash, + &mut parent_number, + &mut parent_timestamp, + None, + vec![], + Address::random(), + ) + .await?; + + let block_txs = &payload_envelope + .execution_payload + .payload_inner + .payload_inner + .transactions; + assert!( + !block_txs.is_empty(), + "dev mode should pull transaction from txpool when attributes are empty" + ); + + Ok(()) +} diff --git a/crates/tests/src/test_deploy_allowlist.rs b/crates/tests/src/test_deploy_allowlist.rs index 8a7b026..b36dab9 100644 --- a/crates/tests/src/test_deploy_allowlist.rs +++ b/crates/tests/src/test_deploy_allowlist.rs @@ -78,7 +78,7 @@ async fn test_e2e_deploy_allowlist_permits_create2_via_factory() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); diff --git a/crates/tests/src/test_evolve_engine_api.rs b/crates/tests/src/test_evolve_engine_api.rs index f171ca7..d062c5e 100644 --- a/crates/tests/src/test_evolve_engine_api.rs +++ b/crates/tests/src/test_evolve_engine_api.rs @@ -75,7 +75,7 @@ async fn test_e2e_engine_api_fork_choice_with_transactions() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default(); @@ -250,7 +250,7 @@ async fn test_e2e_engine_api_gas_limit_handling() -> Result<()> { let mut setup = Setup::::default() .with_chain_spec(chain_spec) .with_network(NetworkSetup::single_node()) - .with_dev_mode(true) + .with_dev_mode(false) .with_tree_config(e2e_test_tree_config()); let mut env = Environment::::default();