Skip to content

fix: Drop transaction if we cannot resubmit it#7240

Merged
ameten merged 7 commits intomainfrom
ameten/lander-optimism
Oct 22, 2025
Merged

fix: Drop transaction if we cannot resubmit it#7240
ameten merged 7 commits intomainfrom
ameten/lander-optimism

Conversation

@ameten
Copy link
Copy Markdown
Contributor

@ameten ameten commented Oct 22, 2025

Description

Drop transaction if we cannot resubmit it when we reached our limit in gas fee escalation.

Backward compatibility

Yes

Testing

Manual

Summary by CodeRabbit

  • Bug Fixes
    • More accurate handling of transaction resubmission when the new gas price equals the existing one: returns a clear non-retryable response or an "already exists" response depending on current status.
  • Behavior
    • Transaction status is persisted before resubmission checks so decisions reflect stored state.
  • Tests
    • Expanded coverage across statuses and fee types, including partial fee-change scenarios.
  • Logging
    • Additional debug logs for transaction loading and stage transitions.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Oct 22, 2025

⚠️ No Changeset found

Latest commit: e078352

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 22, 2025

📝 Walkthrough

Walkthrough

Resubmission handling for Ethereum transactions changed: when the new gas price equals the existing one, the adapter now returns TxWontBeResubmitted for PendingInclusion/Dropped(_) and TxAlreadyExists for Mempool/Included/Finalized. Added TxWontBeResubmitted, persisted some status transitions, expanded tests, and added debug logs.

Changes

Cohort / File(s) Summary
Error variant
rust/main/lander/src/error.rs
Added LanderError::TxWontBeResubmitted; updated to_metrics_label; treat it as non-retryable in IsRetryable.
Adapter logic
rust/main/lander/src/adapter/chains/ethereum/adapter.rs
On same gas price, return TxWontBeResubmitted for PendingInclusion/Dropped(_) and TxAlreadyExists for Mempool/Included/Finalized; added local TransactionStatus imports.
Inclusion stage
rust/main/lander/src/dispatcher/stages/inclusion_stage.rs
Persist status via update_tx_status before checking tx_ready_for_resubmission in try_process_tx_with_next_status.
Transaction loader / logging
rust/main/lander/src/dispatcher/db/transaction/loader.rs
Added debug logs for highest index, retrieved transactions, and status transitions; load now logs and Skipped-returns for Finalized and Dropped.
Tests
rust/main/lander/src/adapter/chains/ethereum/adapter/tests.rs
Added/renamed tests covering resubmission outcomes across statuses and tx types (EIP‑1559 and Legacy), same-gas-price branches, and partial EIP‑1559 fee changes.
Manifest
Cargo.toml
Manifest touched (no public API changes).

Sequence Diagram(s)

sequenceDiagram
  participant Caller as SubmitAttempt
  participant Adapter as EthereumAdapter
  participant Store as TxStore

  Caller->>Adapter: submit(tx, new_gas)
  Adapter->>Store: fetch_existing_tx(tx.hash)
  Store-->>Adapter: existing_tx(status, old_gas)

  alt new_gas == old_gas
    note right of Adapter #f9f0c1: decision depends on status
    Adapter->>Adapter: match status
    alt PendingInclusion or Dropped(_)
      Adapter-->>Caller: Err(TxWontBeResubmitted)
    else Mempool or Included or Finalized
      Adapter-->>Caller: Err(TxAlreadyExists)
    end
  else new_gas != old_gas
    Adapter-->>Caller: proceed with resubmission
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • yjamin
  • kamiyaa
  • daniel-savu

Poem

In the fen where txs get stuck and sigh,
Same gas, different fate — now we know why.
If it’s dropped or pending, we won’t give chase,
If it’s in mempool, it keeps its place.
Quiet patch, swamp-smarts — onwards we fly.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: Drop transaction if we cannot resubmit it" directly captures the main behavioral change across all modified files: adding logic to handle scenarios where transaction resubmission isn't possible due to gas escalation limits. It's concise, specific, and gives reviewers a clear understanding of the primary purpose without unnecessary noise or verbosity.
Description Check ✅ Passed The description covers the essential sections from the template: it explains what's included (dropping transactions when gas escalation limit is reached), confirms backward compatibility, and specifies manual testing. However, the "Drive-by changes" and "Related issues" sections are omitted, though these appear to be optional or non-critical for this particular change. The core information needed to understand the PR's scope and implications is present and adequately detailed.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ameten/lander-optimism

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01d497a and f5feeeb.

📒 Files selected for processing (1)
  • rust/main/lander/src/dispatcher/db/transaction/loader.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
rust/main/**/src/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Run cargo clippy for Rust code linting

Files:

  • rust/main/lander/src/dispatcher/db/transaction/loader.rs
🧬 Code graph analysis (1)
rust/main/lander/src/dispatcher/db/transaction/loader.rs (2)
rust/main/lander/src/dispatcher/db/loader.rs (4)
  • retrieve_by_index (26-26)
  • retrieve_by_index (249-252)
  • load (27-27)
  • load (254-257)
rust/main/lander/src/dispatcher/db/payload/loader.rs (2)
  • retrieve_by_index (46-48)
  • load (50-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (cosmwasm)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: yarn-install
  • GitHub Check: e2e-matrix (cosmosnative)
  • GitHub Check: agent-configs (testnet4)
  • GitHub Check: agent-configs (mainnet3)
  • GitHub Check: build-and-push-to-gcr
  • GitHub Check: lint-rs
  • GitHub Check: test-rs
  • GitHub Check: lander-coverage
🔇 Additional comments (5)
rust/main/lander/src/dispatcher/db/transaction/loader.rs (5)

44-46: Nice observability enhancement here!

The debug logging for the highest transaction index helps track the loader's progress. Good pattern of logging after retrieval.


50-52: Good diagnostic addition.

Logging the retrieved transaction alongside its index gives you solid context when troubleshooting. Matches the pattern you've got going in other loaders too.


56-61: Solid logging for stage transitions.

Tracking when transactions get dispatched to the inclusion stage will make debugging the pipeline much easier.


62-66: Consistent stage logging, looks good.

The finality stage logging mirrors the inclusion stage nicely. You've got good visibility into the transaction lifecycle now.


67-70: Match expression is exhaustive—all variants accounted for.

The code properly covers all five TransactionStatus variants:

  • PendingInclusion and Mempool → sent to inclusion stage
  • Included → sent to finality stage
  • Finalized and Dropped(DropReason) → skipped as already terminal states

The Dropped(_) pattern correctly ignores the associated DropReason data, which makes sense since there's no need to process it further.

Please confirm that cargo clippy has been run on the code in rust/main/lander/ as per the coding guidelines.


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.

Copy link
Copy Markdown
Contributor

@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: 0

🧹 Nitpick comments (2)
rust/main/lander/src/adapter/chains/ethereum/adapter/tests.rs (2)

381-410: Legacy parity: add mempool/finalized cases

You covered Included and PendingInclusion for legacy. For symmetry with EIP‑1559, add:

  • Legacy + Mempool → expect TxAlreadyExists
  • Legacy + Finalized → expect TxAlreadyExists

This keeps the garden evenly trimmed across tx types.

Also applies to: 412-442


176-208: Reduce setup duplication with a small builder helper

A tiny test helper will de‑mud the repeated “set existing gas price” blocks.

Here’s a sketch you can drop into the tests module:

+fn with_eip1559_price(
+    mut tx: crate::Transaction,
+    max_fee: u64,
+    max_prio: u64,
+) -> crate::Transaction {
+    if let VmSpecificTxData::Evm(p) = &mut tx.vm_specific_data {
+        p.tx = TypedTransaction::Eip1559(Eip1559TransactionRequest {
+            from: Some(H160::random()),
+            to: Some(H160::random().into()),
+            nonce: Some(0u64.into()),
+            gas: Some(21_000u64.into()),
+            max_fee_per_gas: Some(max_fee.into()),
+            max_priority_fee_per_gas: Some(max_prio.into()),
+            value: Some(1u64.into()),
+            ..Default::default()
+        });
+    }
+    tx
+}
+
+fn with_legacy_price(mut tx: crate::Transaction, gas_price: u64) -> crate::Transaction {
+    if let VmSpecificTxData::Evm(p) = &mut tx.vm_specific_data {
+        p.tx = TypedTransaction::Legacy(TransactionRequest {
+            from: Some(H160::random()),
+            to: Some(H160::random().into()),
+            nonce: Some(0u64.into()),
+            gas: Some(21_000u64.into()),
+            gas_price: Some(gas_price.into()),
+            value: Some(1u64.into()),
+            ..Default::default()
+        });
+    }
+    tx
+}

Then tests become one‑liners for setup, keeping the path clear.

Also applies to: 210-242, 244-276, 278-310, 312-346, 381-410, 412-442

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 131cca2 and 88d5ba1.

📒 Files selected for processing (2)
  • rust/main/lander/src/adapter/chains/ethereum/adapter.rs (2 hunks)
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tests.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
rust/main/**/src/**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Run cargo clippy for Rust code linting

Files:

  • rust/main/lander/src/adapter/chains/ethereum/adapter/tests.rs
  • rust/main/lander/src/adapter/chains/ethereum/adapter.rs
🧬 Code graph analysis (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tests.rs (2)
rust/main/lander/src/adapter/chains/ethereum/adapter.rs (3)
  • tx (264-273)
  • tx (651-657)
  • check_if_resubmission_makes_sense (163-194)
rust/main/lander/src/adapter/chains/ethereum/tests.rs (1)
  • dummy_evm_tx (116-138)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: infra-test
  • GitHub Check: build-and-push-to-gcr
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: e2e-matrix (cosmosnative)
  • GitHub Check: e2e-matrix (cosmwasm)
  • GitHub Check: lander-coverage
  • GitHub Check: lint-rs
  • GitHub Check: test-rs
🔇 Additional comments (3)
rust/main/lander/src/adapter/chains/ethereum/adapter.rs (2)

167-168: Scoped import keeps things tidy

Pulling TransactionStatus variants into local scope is clean and keeps the outer namespace uncluttered. Nice and lean, just how I like my trails.


187-190: Status-based split is correct; optional logging enhancement suggested

The enum mapping is exhaustive and sound:

  • PendingInclusion | Dropped(_) → TxWontBeResubmitted
  • Mempool | Included | Finalized → TxAlreadyExists

All five TransactionStatus variants are covered. Context logging already exists at line 182 with the transaction object. The suggested patch to log the specific error variant chosen is a reasonable optional refinement, particularly useful for metrics/triage if you're tracking error types downstream.

Since the sandbox environment prevents running cargo clippy and cargo test, you'll want to execute these linting commands locally to catch any missed enum uses or other Rust idioms across the crate.

rust/main/lander/src/adapter/chains/ethereum/adapter/tests.rs (1)

177-208: Good coverage for EIP‑1559 same‑price outcomes across statuses

Assertions line up with the new split (TxAlreadyExists vs TxWontBeResubmitted). Solid guardrails, no fuss.

Consider adding one negative case where the estimator yields a lower gas price to ensure the submission path never decreases price (if that’s an invariant). If the escalate logic already clamps, a unit test here will keep it honest.

Also applies to: 210-242, 244-276, 278-310, 312-346

Comment thread rust/main/lander/src/dispatcher/stages/inclusion_stage.rs
@ameten ameten added this pull request to the merge queue Oct 22, 2025
Merged via the queue into main with commit 0a6647c Oct 22, 2025
39 checks passed
@ameten ameten deleted the ameten/lander-optimism branch October 22, 2025 17:49
@github-project-automation github-project-automation Bot moved this from In Review to Done in Hyperlane Tasks Oct 22, 2025
@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 22, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 0.00%. Comparing base (8fd3bf7) to head (e078352).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@          Coverage Diff          @@
##            main   #7240   +/-   ##
=====================================
  Coverage   0.00%       0           
=====================================
  Files          1       0    -1     
  Lines         14       0   -14     
=====================================
+ Misses        14       0   -14     
Components Coverage Δ
core ∅ <ø> (∅)
hooks ∅ <ø> (∅)
isms ∅ <ø> (∅)
token ∅ <ø> (∅)
middlewares ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants