Skip to content

fix: restrict txpool fallback to dev mode only#155

Open
randygrok wants to merge 6 commits intomainfrom
randygrok/fix-txpool-dev-only
Open

fix: restrict txpool fallback to dev mode only#155
randygrok wants to merge 6 commits intomainfrom
randygrok/fix-txpool-dev-only

Conversation

@randygrok
Copy link
Contributor

@randygrok randygrok commented Mar 4, 2026

The payload builder was pulling transactions from the txpool whenever Engine API attributes had none, which could happen in production and break consensus (non-deterministic block contents).

This change:

  • Adds a dev_mode boolean to EvolvePayloadBuilderConfig
  • Sets it to true when running with --dev flag (detected via ctx.is_dev())
  • Guards the txpool fallback behind this flag

In production (without --dev), the payload builder always uses only the transactions from Engine API attributes, preventing any non-deterministic behavior.

Summary by CodeRabbit

  • New Features

    • Added an explicit development-mode flag influencing payload construction.
  • Improvements

    • In dev mode, pending transactions are sourced from the local transaction pool when the Engine returns none.
    • Log messages updated to indicate dev-mode transaction sourcing.
  • Tests

    • Added an end-to-end test verifying dev-mode txpool fallback.
    • Multiple tests adjusted to disable dev mode and test setups simplified.

The payload builder was pulling transactions from the txpool whenever
Engine API attributes had none, which could happen in production and
break consensus (non-deterministic block contents).

This change:
- Adds a dev_mode boolean to EvolvePayloadBuilderConfig
- Sets it to true when running with --dev flag (detected via ctx.is_dev())
- Guards the txpool fallback behind this flag

In production (without --dev), the payload builder always uses only the
transactions from Engine API attributes, preventing any non-deterministic
behavior.
@randygrok randygrok requested a review from a team as a code owner March 4, 2026 14:32
@claude
Copy link

claude bot commented Mar 4, 2026

Claude finished @randygrok's task —— View job


Code Review for txpool fallback restriction

Todo List:

  • Read and understand the changed files
  • Analyze code quality and implementation
  • Check for potential bugs and security issues
  • Review test coverage
  • Provide comprehensive feedback

📋 Summary

This PR successfully addresses a critical consensus safety issue by restricting txpool fallback behavior to development mode only. The implementation adds a dev_mode boolean flag that prevents non-deterministic block construction in production environments.

✅ Strengths

1. Architecture & Design

  • Clear separation of concerns: The dev_mode flag provides a clean boundary between development and production behavior
  • Safe defaults: Production mode defaults to only using Engine API transactions, ensuring deterministic consensus
  • Proper initialization: The flag is correctly initialized from ctx.is_dev() in payload_service.rs:121

2. Implementation Quality

  • Minimal surface area: Changes are focused and contained to the essential logic
  • Good logging: Added informative log message at payload_service.rs:195-198 to indicate dev mode transaction sourcing
  • Consistent patterns: Follows existing codebase conventions for configuration and builder patterns

3. Testing

  • E2E coverage: Added test_e2e_dev_mode_txpool_fallback validates the complete flow from CLI flag to payload building
  • Real integration: Test exercises actual Engine API interactions rather than mocks

⚠️ Issues Requiring Attention

1. Critical: Missing Non-Dev Mode Test

The test suite only validates dev mode behavior but lacks a companion test ensuring production mode never falls back to txpool. This is a critical gap for a consensus-safety feature.

Recommendation: Add test_e2e_non_dev_mode_no_txpool_fallback with identical setup but with_dev_mode(false) and assert empty transactions.

2. Code Quality: Missing dev_mode Field Declaration

Looking at config.rs, the EvolvePayloadBuilderConfig struct is missing the dev_mode field declaration. The field is used in payload_service.rs:66 but not defined in the config struct.

Need to verify: Is dev_mode supposed to be part of the config struct or only the payload builder?

3. Test Assertion Weakness

The dev mode test at e2e_tests.rs:2086-2089 only checks !block_txs.is_empty() but doesn't verify the specific transaction from the txpool was included.

Recommendation: Preserve raw_tx and assert its presence in the built payload to prove txpool fallback worked correctly.

4. Documentation Gap

The dev_mode field lacks documentation explaining its purpose and impact on consensus safety.

🔍 Detailed Code Analysis

crates/node/src/payload_service.rs

  • Line 66: dev_mode: bool field added appropriately
  • Lines 187-203: Transaction selection logic is clean and well-commented
  • Line 121: Correct initialization from build context
  • Issue: Lines 387-388 unit tests both set dev_mode: false - missing dev mode coverage

crates/node/src/config.rs

  • Structure looks good overall
  • Issue: No dev_mode field visible in EvolvePayloadBuilderConfig - needs clarification

crates/tests/src/e2e_tests.rs

  • Lines 2034-2092: Good comprehensive test structure
  • Issue: Missing non-dev companion test
  • Issue: Weak assertion on line 2086-2089

🛡️ Security Analysis

Positive: This change improves security by preventing non-deterministic behavior in production
No security regressions identified
Proper access controls: Dev mode only accessible via explicit CLI flag

🚀 Performance Considerations

Minimal impact: Single boolean check with no performance overhead
No additional allocations or expensive operations introduced

📝 Final Recommendations

High Priority

  1. Add non-dev mode test to ensure production safety guarantees
  2. Clarify dev_mode field location in config vs. builder struct
  3. Fix formatting issues (run just fmt as indicated by CI)

Medium Priority

  1. Strengthen test assertions to verify specific transaction inclusion
  2. Add unit tests for payload service dev mode branching logic
  3. Add documentation for the dev_mode field

Low Priority

  1. Consider edge cases: What happens with mixed dev/prod node environments?

🎯 Overall Assessment

This is a well-implemented, critical safety fix that properly addresses the consensus issue. The core logic is sound and the approach is correct. The main concerns are around test coverage completeness and ensuring the safety guarantees are thoroughly validated.

Recommendation: Approve after addressing the missing non-dev test - this is essential for verifying the production safety guarantee that motivated this change.


@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a dev_mode flag to the payload builder and changes payload construction so that when dev_mode is true and Engine API attributes contain no transactions, the builder pulls pending transactions from the txpool; several tests were adjusted and a new e2e test validating txpool fallback was added (duplicate present).

Changes

Cohort / File(s) Summary
Configuration / Tests
crates/node/src/config.rs
Simplified test struct initializations by using ..Default::default() to reduce verbosity; whitespace-only adjustments.
Payload Builder / Service
crates/node/src/payload_service.rs
Adds pub(crate) dev_mode: bool to EvolveEnginePayloadBuilder, sets it from ctx.is_dev(), and changes tx sourcing: when Engine attributes are empty and dev_mode is true, pull pending txs from the txpool; updates log text and test initialization.
End-to-end Tests
crates/tests/src/e2e_tests.rs
Replaced several tests' setups to disable dev mode; added test_e2e_dev_mode_txpool_fallback to assert txpool fallback in dev mode (duplicate block present in diff).
Other Tests
crates/tests/src/test_deploy_allowlist.rs, crates/tests/src/test_evolve_engine_api.rs
Switched .with_dev_mode(true) to .with_dev_mode(false) in multiple tests to run in non-dev mode.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant PayloadBuilder
    participant EngineAPI_Attrs
    participant TxPool

    Caller->>PayloadBuilder: build_payload(ctx)
    PayloadBuilder->>EngineAPI_Attrs: fetch attributes (may include txs)
    alt attributes contain transactions
        EngineAPI_Attrs-->>PayloadBuilder: return attributes.transactions
    else attributes empty
        PayloadBuilder->>PayloadBuilder: check dev_mode flag
        alt dev_mode == true
            PayloadBuilder->>TxPool: query pending transactions
            TxPool-->>PayloadBuilder: return pool transactions
        else dev_mode == false
            EngineAPI_Attrs-->>PayloadBuilder: return empty tx list
        end
    end
    PayloadBuilder-->>Caller: return built payload
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • damiannolan
  • chatton

Poem

🐇
I sniff the quiet attrs and twitch my nose,
From txpool burrows tiny transactions rose.
I gather, hop, and stitch them into play —
A dev-mode patch, and blocks skip on their way.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is missing several required template sections including Type of Change checkboxes, Related Issues, Checklist items, Testing details, and Additional Notes. Fill in the missing template sections: mark the bug fix checkbox, link related issues, complete the self-review checklist, document testing methodology, and add any additional context.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: restricting txpool fallback to dev mode only, which is the core purpose of this bug fix.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch randygrok/fix-txpool-dev-only

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

The payload builder was pulling transactions from the txpool whenever
Engine API attributes had none, which could break consensus by producing
non-deterministic block contents in production.

Guard the txpool fallback behind ctx.is_dev() so it only activates
when the node is running with --dev flag.
@randygrok randygrok force-pushed the randygrok/fix-txpool-dev-only branch from 1844841 to 75746fa Compare March 4, 2026 14:37
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/node/src/payload_service.rs (1)

184-203: ⚠️ Potential issue | 🟠 Major

Add explicit behavior tests for tx source selection in dev vs non-dev mode.

This branch is consensus-sensitive but current coverage in this file only validates tracing spans. Please add tests that assert: non-dev never pulls pool txs, dev pulls pool txs only when attributes are empty, and provided attribute txs always take precedence.

Based on learnings: Applies to crates/tests/**/*.rs : Write integration tests in crates/tests/ covering Engine API interactions, payload building, state execution, and Evolve-specific scenarios.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/node/src/payload_service.rs` around lines 184 - 203, Add integration
tests under crates/tests that exercise the payload selection logic in
payload_service.rs: create tests that call the payload-building path (the method
that uses self.config.dev_mode and attributes.transactions) with a mocked pool
(pool.pending_transactions returning TransactionSigned items) and varying Engine
API attributes to assert three cases: (1) non-dev mode never pulls pool txs even
if attributes.transactions is empty, (2) dev mode pulls pool txs only when
attributes.transactions is empty, and (3) when attributes.transactions is
non-empty those provided txs always take precedence over
pool.pending_transactions. Use the same types referenced in the file
(attributes.transactions, self.config.dev_mode, pool.pending_transactions,
TransactionSigned) and implement setup/teardown to control dev_mode and pool
contents so the assertions verify the chosen transactions match the expected
source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/node/src/config.rs`:
- Around line 125-127: CI is failing because rustfmt formatting in
crates/node/src/config.rs is out of date; run the workspace formatter (just fmt)
to apply rustfmt to this file (and the workspace), review the resulting changes,
and commit the formatted file so the lint passes; ensure you run `just fmt`
locally and include the updated crates/node/src/config.rs in your commit.

---

Outside diff comments:
In `@crates/node/src/payload_service.rs`:
- Around line 184-203: Add integration tests under crates/tests that exercise
the payload selection logic in payload_service.rs: create tests that call the
payload-building path (the method that uses self.config.dev_mode and
attributes.transactions) with a mocked pool (pool.pending_transactions returning
TransactionSigned items) and varying Engine API attributes to assert three
cases: (1) non-dev mode never pulls pool txs even if attributes.transactions is
empty, (2) dev mode pulls pool txs only when attributes.transactions is empty,
and (3) when attributes.transactions is non-empty those provided txs always take
precedence over pool.pending_transactions. Use the same types referenced in the
file (attributes.transactions, self.config.dev_mode, pool.pending_transactions,
TransactionSigned) and implement setup/teardown to control dev_mode and pool
contents so the assertions verify the chosen transactions match the expected
source.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ce714976-d8fe-4f2e-b4b5-abd420cda51d

📥 Commits

Reviewing files that changed from the base of the PR and between 9e38126 and 1844841.

📒 Files selected for processing (2)
  • crates/node/src/config.rs
  • crates/node/src/payload_service.rs

Comment on lines +125 to +127

}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Formatting check is currently blocking CI.

rustfmt is failing in lint; please run formatter before merge.

As per coding guidelines: Use just fmt for code formatting in Rust workspace.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/node/src/config.rs` around lines 125 - 127, CI is failing because
rustfmt formatting in crates/node/src/config.rs is out of date; run the
workspace formatter (just fmt) to apply rustfmt to this file (and the
workspace), review the resulting changes, and commit the formatted file so the
lint passes; ensure you run `just fmt` locally and include the updated
crates/node/src/config.rs in your commit.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
crates/node/src/config.rs (1)

121-123: ⚠️ Potential issue | 🟡 Minor

Run workspace formatting to unblock CI lint.

cargo fmt --all --check is failing, and this region is part of the reported formatting drift. Please run just fmt and commit the formatted output.

As per coding guidelines: Use just fmt for code formatting in Rust workspace.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/node/src/config.rs` around lines 121 - 123, Formatting drift detected
in config.rs causing CI lint to fail; run the workspace formatter and commit the
results. Execute the workspace format command (just fmt), verify cargo fmt --all
--check passes, and commit the updated formatting for crates/node/src/config.rs
(and any other modified files) so CI lint is unblocked.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/node/src/config.rs`:
- Around line 121-123: Formatting drift detected in config.rs causing CI lint to
fail; run the workspace formatter and commit the results. Execute the workspace
format command (just fmt), verify cargo fmt --all --check passes, and commit the
updated formatting for crates/node/src/config.rs (and any other modified files)
so CI lint is unblocked.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9d190401-3eb3-4671-b012-9853b4dc8148

📥 Commits

Reviewing files that changed from the base of the PR and between 1844841 and 75746fa.

📒 Files selected for processing (2)
  • crates/node/src/config.rs
  • crates/node/src/payload_service.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/node/src/payload_service.rs

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/tests/src/e2e_tests.rs (1)

2057-2089: Tighten assertion to prove the fallback used the intended txpool transaction.

The check on Line 2086 only asserts non-empty output. Strengthen it by preserving raw_tx and asserting the built payload contains that exact transaction (or its hash).

Proposed assertion hardening
-    let raw_tx = TransactionTestContext::transfer_tx_bytes(chain_id, sender).await;
+    let raw_tx = TransactionTestContext::transfer_tx_bytes(chain_id, sender).await;
+    let expected_tx = raw_tx.clone();

     EthApiClient::<TransactionRequest, Transaction, Block, Receipt, Header, Bytes>::send_raw_transaction(
         &env.node_clients[0].rpc,
         raw_tx,
     )
     .await?;
@@
     assert!(
         !block_txs.is_empty(),
         "dev mode should pull transaction from txpool when attributes are empty"
     );
+    assert!(
+        block_txs.iter().any(|tx| tx == &expected_tx),
+        "expected block to include the tx submitted to txpool"
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/tests/src/e2e_tests.rs` around lines 2057 - 2089, The test currently
only checks that
payload_envelope.execution_payload.payload_inner.payload_inner.transactions
(block_txs) is non-empty; instead preserve raw_tx and assert that the payload
contains that exact transaction (or its hash) to prove the txpool fallback was
used. Locate the raw_tx variable produced by
TransactionTestContext::transfer_tx_bytes and after
build_block_with_transactions completes, either (a) compare the raw_tx byte
sequence against one of the entries in block_txs or (b) compute the transaction
hash from raw_tx and assert that an entry in block_txs matches that hash; add a
clear assert that fails if the exact raw_tx (or its hash) is not found. Use the
existing symbols raw_tx, block_txs, and build_block_with_transactions to find
where to add the check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/tests/src/e2e_tests.rs`:
- Around line 2034-2092: Add a non-dev counterpart of
test_e2e_dev_mode_txpool_fallback that uses the same setup but sets
with_dev_mode(false) (e.g., name it test_e2e_non_dev_txpool_no_fallback), send a
signed transaction into the txpool exactly as in
test_e2e_dev_mode_txpool_fallback, call build_block_with_transactions the same
way, then assert that the resulting payload's transactions
(payload_envelope.execution_payload.payload_inner.payload_inner.transactions)
are empty to verify txpool fallback is disabled in production mode; reuse
Setup::<EvolveEngineTypes>, Wallet::new(...).with_chain_id(...).wallet_gen(),
EthApiClient::send_raw_transaction and build_block_with_transactions to mirror
the dev test flow.

---

Nitpick comments:
In `@crates/tests/src/e2e_tests.rs`:
- Around line 2057-2089: The test currently only checks that
payload_envelope.execution_payload.payload_inner.payload_inner.transactions
(block_txs) is non-empty; instead preserve raw_tx and assert that the payload
contains that exact transaction (or its hash) to prove the txpool fallback was
used. Locate the raw_tx variable produced by
TransactionTestContext::transfer_tx_bytes and after
build_block_with_transactions completes, either (a) compare the raw_tx byte
sequence against one of the entries in block_txs or (b) compute the transaction
hash from raw_tx and assert that an entry in block_txs matches that hash; add a
clear assert that fails if the exact raw_tx (or its hash) is not found. Use the
existing symbols raw_tx, block_txs, and build_block_with_transactions to find
where to add the check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71dff13a-86e4-4182-aba8-945b36343028

📥 Commits

Reviewing files that changed from the base of the PR and between 75746fa and 263e476.

📒 Files selected for processing (2)
  • crates/node/src/config.rs
  • crates/tests/src/e2e_tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/node/src/config.rs

Comment on lines +2034 to +2092
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::<EvolveEngineTypes>::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::<EvolveEngineTypes>::default();
setup.apply::<EvolveNode>(&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::<TransactionRequest, Transaction, Block, Receipt, Header, Bytes>::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(())
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add a non-dev regression test for the safety-critical branch.

This test validates the dev-mode path, but it does not verify the production behavior (with_dev_mode(false)) where txpool fallback must stay disabled. Without that companion assertion, the core safety guarantee from this PR can regress silently. See Line 2043 and Line 2068-Line 2089.

Proposed test addition
+#[tokio::test(flavor = "multi_thread")]
+async fn test_e2e_non_dev_mode_does_not_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::<EvolveEngineTypes>::default()
+        .with_chain_spec(chain_spec)
+        .with_network(NetworkSetup::single_node())
+        .with_dev_mode(false)
+        .with_tree_config(e2e_test_tree_config());
+
+    let mut env = Environment::<EvolveEngineTypes>::default();
+    setup.apply::<EvolveNode>(&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;
+
+    let wallets = Wallet::new(1).with_chain_id(chain_id).wallet_gen();
+    let sender = wallets.into_iter().next().expect("wallet exists");
+    let raw_tx = TransactionTestContext::transfer_tx_bytes(chain_id, sender).await;
+    EthApiClient::<TransactionRequest, Transaction, Block, Receipt, Header, Bytes>::send_raw_transaction(
+        &env.node_clients[0].rpc,
+        raw_tx,
+    )
+    .await?;
+
+    let payload_envelope = build_block_with_transactions(
+        &mut env,
+        &mut parent_hash,
+        &mut parent_number,
+        &mut parent_timestamp,
+        None,
+        vec![],
+        Address::ZERO,
+    )
+    .await?;
+
+    let block_txs = &payload_envelope
+        .execution_payload
+        .payload_inner
+        .payload_inner
+        .transactions;
+    assert!(
+        block_txs.is_empty(),
+        "non-dev mode must not pull from txpool when attributes are empty"
+    );
+
+    Ok(())
+}

Based on learnings: Transactions should bypass the mempool and be submitted directly via Engine API payload attributes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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::<EvolveEngineTypes>::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::<EvolveEngineTypes>::default();
setup.apply::<EvolveNode>(&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::<TransactionRequest, Transaction, Block, Receipt, Header, Bytes>::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(())
}
async fn test_e2e_non_dev_mode_does_not_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::<EvolveEngineTypes>::default()
.with_chain_spec(chain_spec)
.with_network(NetworkSetup::single_node())
.with_dev_mode(false)
.with_tree_config(e2e_test_tree_config());
let mut env = Environment::<EvolveEngineTypes>::default();
setup.apply::<EvolveNode>(&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;
let wallets = Wallet::new(1).with_chain_id(chain_id).wallet_gen();
let sender = wallets.into_iter().next().expect("wallet exists");
let raw_tx = TransactionTestContext::transfer_tx_bytes(chain_id, sender).await;
EthApiClient::<TransactionRequest, Transaction, Block, Receipt, Header, Bytes>::send_raw_transaction(
&env.node_clients[0].rpc,
raw_tx,
)
.await?;
let payload_envelope = build_block_with_transactions(
&mut env,
&mut parent_hash,
&mut parent_number,
&mut parent_timestamp,
None,
vec![],
Address::ZERO,
)
.await?;
let block_txs = &payload_envelope
.execution_payload
.payload_inner
.payload_inner
.transactions;
assert!(
block_txs.is_empty(),
"non-dev mode must not pull from txpool when attributes are empty"
);
Ok(())
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/tests/src/e2e_tests.rs` around lines 2034 - 2092, Add a non-dev
counterpart of test_e2e_dev_mode_txpool_fallback that uses the same setup but
sets with_dev_mode(false) (e.g., name it test_e2e_non_dev_txpool_no_fallback),
send a signed transaction into the txpool exactly as in
test_e2e_dev_mode_txpool_fallback, call build_block_with_transactions the same
way, then assert that the resulting payload's transactions
(payload_envelope.execution_payload.payload_inner.payload_inner.transactions)
are empty to verify txpool fallback is disabled in production mode; reuse
Setup::<EvolveEngineTypes>, Wallet::new(...).with_chain_id(...).wallet_gen(),
EthApiClient::send_raw_transaction and build_block_with_transactions to mirror
the dev test flow.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/node/src/payload_service.rs`:
- Around line 387-388: Add a unit test in crates/node/src/payload_service.rs
that exercises the new branch in try_build by constructing the relevant struct
with dev_mode: true and injecting a mocked pool that returns a known transaction
source, then call try_build and assert the transaction sourcing came from the
pool; also add a complementary test with dev_mode: false that injects mocked
Engine API attributes and assert try_build used those attributes. Target the
symbols try_build and the struct/constructor that accepts dev_mode so the test
explicitly toggles dev_mode, injects test doubles for the pool and Engine API,
and asserts the correct branch behavior (pool when dev_mode is true, Engine API
attributes when false).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c95232bb-e076-4c0f-9851-8a8aecafaf10

📥 Commits

Reviewing files that changed from the base of the PR and between 263e476 and b89ab07.

📒 Files selected for processing (2)
  • crates/node/src/payload_service.rs
  • crates/tests/src/e2e_tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/tests/src/e2e_tests.rs

Comment on lines +387 to 388
dev_mode: false,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add a unit test that exercises dev_mode: true transaction sourcing.

Both updated unit tests pin dev_mode: false; the new branch in try_build (Line 187) is not unit-covered here. Please add a focused unit test asserting dev mode pulls from pool and production mode uses Engine API attributes.

As per coding guidelines: "Write unit tests for individual components in Rust workspace."

Also applies to: 478-479

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/node/src/payload_service.rs` around lines 387 - 388, Add a unit test
in crates/node/src/payload_service.rs that exercises the new branch in try_build
by constructing the relevant struct with dev_mode: true and injecting a mocked
pool that returns a known transaction source, then call try_build and assert the
transaction sourcing came from the pool; also add a complementary test with
dev_mode: false that injects mocked Engine API attributes and assert try_build
used those attributes. Target the symbols try_build and the struct/constructor
that accepts dev_mode so the test explicitly toggles dev_mode, injects test
doubles for the pool and Engine API, and asserts the correct branch behavior
(pool when dev_mode is true, Engine API attributes when false).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
crates/tests/src/e2e_tests.rs (1)

2034-2092: ⚠️ Potential issue | 🟠 Major

Add the non-dev regression counterpart for this safety-critical behavior.

Line 2034 adds the positive dev-mode fallback test, but there is still no direct negative-path assertion that with_dev_mode(false) + empty attributes + populated txpool yields an empty payload tx list. That companion test is needed to lock in the consensus-safety guarantee.

Based on learnings: Transactions should bypass the mempool and be submitted directly via Engine API payload attributes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/tests/src/e2e_tests.rs` around lines 2034 - 2092, Add a new
negative-path test mirroring test_e2e_dev_mode_txpool_fallback but with dev mode
disabled: create a test (e.g., test_e2e_non_dev_mode_txpool_no_fallback) that
sets Setup::<EvolveEngineTypes>::default().with_dev_mode(false), seeds the
txpool by sending a signed raw transaction using
EthApiClient::send_raw_transaction (same Wallet/TransactionTestContext code),
then call build_block_with_transactions with empty attributes and assert that
payload_envelope.execution_payload.payload_inner.payload_inner.transactions is
empty; reuse the same setup/Environment, parent_block retrieval, and
build_block_with_transactions call patterns to ensure the non-dev path
guarantees no txpool fallback.
🧹 Nitpick comments (1)
crates/tests/src/e2e_tests.rs (1)

2081-2089: Make the assertion specific to the submitted tx, not just non-empty.

assert!(!block_txs.is_empty()) can pass on unrelated pending txs. Prefer asserting the tx submitted at Line 2062 is actually included.

Proposed tightening
-    EthApiClient::<TransactionRequest, Transaction, Block, Receipt, Header, Bytes>::send_raw_transaction(
+    let submitted_hash = EthApiClient::<TransactionRequest, Transaction, Block, Receipt, Header, Bytes>::send_raw_transaction(
         &env.node_clients[0].rpc,
-        raw_tx,
+        raw_tx,
     )
     .await?;
@@
-    assert!(
-        !block_txs.is_empty(),
-        "dev mode should pull transaction from txpool when attributes are empty"
-    );
+    let contains_submitted_tx = block_txs.iter().any(|raw| {
+        let mut slice = raw.as_ref();
+        TxEnvelope::decode_2718(&mut slice)
+            .map(|env| *env.tx_hash() == submitted_hash)
+            .unwrap_or(false)
+    });
+    assert!(
+        contains_submitted_tx,
+        "dev mode fallback block should include the submitted txpool transaction"
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/tests/src/e2e_tests.rs` around lines 2081 - 2089, The test currently
only checks block_txs is non-empty, which can pass for unrelated pending
transactions; instead assert that the specific transaction submitted earlier in
this test is present in
payload_envelope.execution_payload.payload_inner.payload_inner.transactions.
Locate the variable used to submit the tx in this test (the submitted tx value
used when calling the mempool/submit helper) and replace the assert with a
membership/assert-equality check comparing that submitted transaction
(serialized bytes or its hash) against block_txs (e.g.,
assert!(block_txs.contains(&submitted_tx) or
assert_eq!(block_txs.iter().find(...), Some(&submitted_tx))). Ensure you compare
the same representation (raw tx bytes or computed tx hash) as stored in
transactions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/tests/src/e2e_tests.rs`:
- Around line 2034-2092: Add a new negative-path test mirroring
test_e2e_dev_mode_txpool_fallback but with dev mode disabled: create a test
(e.g., test_e2e_non_dev_mode_txpool_no_fallback) that sets
Setup::<EvolveEngineTypes>::default().with_dev_mode(false), seeds the txpool by
sending a signed raw transaction using EthApiClient::send_raw_transaction (same
Wallet/TransactionTestContext code), then call build_block_with_transactions
with empty attributes and assert that
payload_envelope.execution_payload.payload_inner.payload_inner.transactions is
empty; reuse the same setup/Environment, parent_block retrieval, and
build_block_with_transactions call patterns to ensure the non-dev path
guarantees no txpool fallback.

---

Nitpick comments:
In `@crates/tests/src/e2e_tests.rs`:
- Around line 2081-2089: The test currently only checks block_txs is non-empty,
which can pass for unrelated pending transactions; instead assert that the
specific transaction submitted earlier in this test is present in
payload_envelope.execution_payload.payload_inner.payload_inner.transactions.
Locate the variable used to submit the tx in this test (the submitted tx value
used when calling the mempool/submit helper) and replace the assert with a
membership/assert-equality check comparing that submitted transaction
(serialized bytes or its hash) against block_txs (e.g.,
assert!(block_txs.contains(&submitted_tx) or
assert_eq!(block_txs.iter().find(...), Some(&submitted_tx))). Ensure you compare
the same representation (raw tx bytes or computed tx hash) as stored in
transactions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a8d1e90d-fc3a-4c4a-9363-8a433b3b091d

📥 Commits

Reviewing files that changed from the base of the PR and between b89ab07 and 52e3553.

📒 Files selected for processing (3)
  • crates/tests/src/e2e_tests.rs
  • crates/tests/src/test_deploy_allowlist.rs
  • crates/tests/src/test_evolve_engine_api.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/tests/src/test_deploy_allowlist.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants