Skip to content

feat: check evm tx actually succeeded before determining its tx status#7016

Merged
kamiyaa merged 22 commits intomainfrom
jeff/evm-confirm
Oct 9, 2025
Merged

feat: check evm tx actually succeeded before determining its tx status#7016
kamiyaa merged 22 commits intomainfrom
jeff/evm-confirm

Conversation

@kamiyaa
Copy link
Copy Markdown
Contributor

@kamiyaa kamiyaa commented Sep 9, 2025

Description

Assuming all evm chains nowadays support https://eips.ethereum.org/EIPS/eip-658.
We should check whether the tx actually successfully landed or not.

Related issues

Summary by CodeRabbit

  • Enhancements

    • Improved Ethereum tx-status handling: fetches and logs receipts and better distinguishes included vs finalized outcomes.
  • Bug Fixes

    • Chain reverts (on-chain reverts) are now reported as dropped-by-chain, improving status accuracy and metrics.
  • Tests

    • Added unit tests for reverted, included, and finalized transactions; a dispatcher test was added (duplicated/inlined test present).
  • Chores / Public API

    • New drop-reason types exposed; termination invariant relaxed (allows up to half of expected messages to be dropped).

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Sep 9, 2025

⚠️ No Changeset found

Latest commit: a6b9f44

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 Sep 9, 2025

📝 Walkthrough

Walkthrough

Fetches and logs Ethereum transaction receipts, maps provider errors or missing receipts to TxHashNotFound, treats receipt.status == Some(0) as Dropped(TransactionDropReason::RevertedByChain), otherwise delegates to block-finalization logic. Adds unit tests and relaxes one termination invariant check.

Changes

Cohort / File(s) Summary
Ethereum tx status handling
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs
Fetches and debug-logs the transaction receipt; converts provider errors or missing receipts to LanderError::TxHashNotFound; if receipt.status == Some(0) returns TransactionStatus::Dropped(TransactionDropReason::RevertedByChain); otherwise delegates to block_number_result_to_tx_status. Adds unit tests for reverted, included, and finalized cases.
Dispatcher tests updated/added
rust/main/lander/src/dispatcher/tests.rs
Adds test_entrypoint_send_reverts_onchain that simulates on-chain revert (mocks: simulate/estimate succeed, tx_status -> Dropped(RevertedByChain)); asserts payload becomes InTransaction(Dropped(_)) and metrics updated. Duplicate copy of the same test appears in the diff. Updates test export list to include PayloadDropReason and TransactionDropReason.
Transaction drop reason
rust/main/lander/src/transaction/types.rs
Adds RevertedByChain variant to the public DropReason enum (exposed as TransactionDropReason::RevertedByChain).
Termination invariants relaxed
rust/main/utils/run-locally/src/invariants/termination_invariants.rs
Replaces strict dropped_transactions == 0 check with threshold check: dropped_transactions <= total_messages_expected.div_ceil(2) and updates log message accordingly.
Public exports
(exports affected)
Exposes PayloadDropReason and TransactionDropReason for tests/status handling.

Sequence Diagram(s)

sequenceDiagram
  actor Caller
  participant Checker as TxStatusChecker
  participant Node as Ethereum Node

  Caller->>Checker: get_tx_hash_status(tx_hash)
  Checker->>Node: eth_getTransactionReceipt(tx_hash)
  alt Node returns Ok(Some(receipt))
    Node-->>Checker: receipt (with status)
    alt receipt.status == Some(0)
      Note over Checker #ffecec: status 0 → Dropped (RevertedByChain)
      Checker-->>Caller: Dropped(TransactionDropReason::RevertedByChain)
    else receipt.status == Some(1) or status None
      Note over Checker #eef9ff: delegate to block_number_result_to_tx_status
      Checker->>Node: (maybe) fetch block number / confirmations
      Node-->>Checker: block number / confirmations
      Checker-->>Caller: Included or Finalized
    end
  else Node returns Ok(None)
    Node-->>Checker: no receipt
    Checker->>Node: fetch block number / confirmations
    Node-->>Checker: block number / confirmations
    Checker-->>Caller: Included or Finalized (via block_number_result_to_tx_status)
  end
  opt Node error
    Node-->>Checker: Err(_)
    Checker-->>Caller: LanderError::TxHashNotFound
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • yjamin
  • ameten

Poem

In murky logs the receipt did hide,
We fetched it out, no need to bide.
If chain says "nah", we drop with flair,
Tests nod, metrics fix their stare. 🐸

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description includes the required Description and Related issues sections but omits the Drive-by changes, Backward compatibility, and Testing headings specified in the repository template. Without those sections it is unclear whether there are minor adjustments, compatibility impacts, or what tests have been performed to validate the changes. This makes the description incomplete relative to the template. Please update the PR description to include all template sections by adding a Drive-by changes section if applicable, stating the Backward compatibility status explicitly, and detailing the Testing performed. Ensuring those headings and information are present will make the description fully compliant with the repository standard.
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 (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary change by indicating that an EVM transaction’s actual success is now verified before determining its status, which aligns directly with the code modifications in the PR. It is specific, concise, and immediately communicates the feature being added without extraneous details. A reviewer scanning the history can quickly understand the purpose of the changeset.
✨ 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 jeff/evm-confirm

📜 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 1c4fc1a and 70f9510.

📒 Files selected for processing (1)
  • rust/main/utils/run-locally/src/invariants/termination_invariants.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/utils/run-locally/src/invariants/termination_invariants.rs
⏰ 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: lint-rs
  • GitHub Check: lander-coverage
  • GitHub Check: test-rs
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: e2e-matrix (cosmosnative)
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (cosmwasm)

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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca64e73 and 96ae800.

📒 Files selected for processing (1)
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2 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/tx_status_checker.rs
⏰ 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). (8)
  • GitHub Check: lint-rs
  • GitHub Check: lander-coverage
  • GitHub Check: test-rs
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (cosmwasm)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (cosmosnative)
🔇 Additional comments (2)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2)

7-7: Import looks good.

Bringing TransactionDropReason into scope is fine here.


1-69: Run Clippy on the workspace
I couldn’t verify linting here—please run cargo clippy --workspace --all-targets -- -D warnings and resolve any warnings after the match refactor.

Comment thread rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs Outdated
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: 1

♻️ Duplicate comments (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (1)

53-54: status=0 ≠ Dropped; it means executed-and-reverted. Treat explicitly as a failure, not “DroppedByChain”.

Mapping reverts to “dropped” will mislead upstream logic (retries, user messaging). Also handle unexpected non {0,1} with a warn and non-terminal status.

Apply:

-                _ => TransactionStatus::Dropped(TransactionDropReason::DroppedByChain),
+                Some(0) => {
+                    warn!(?hash, "Tx executed but reverted (receipt.status=0 per EIP-658)");
+                    // Prefer a dedicated failure status if available:
+                    TransactionStatus::Failed
+                    // …or, if you model failures via drop reasons:
+                    // TransactionStatus::Dropped(TransactionDropReason::ExecutionFailed)
+                },
+                Some(other) => {
+                    warn!(?hash, status = other, "Unexpected receipt.status; treating as pending");
+                    TransactionStatus::PendingInclusion
+                },

If TransactionStatus::Failed or ExecutionFailed doesn’t exist yet, I can help wire it through enums, serde, and callers.

🧹 Nitpick comments (3)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (3)

47-48: Nit: reword the EIP-658 comment.

“Assuming all chains…” sounds absolute. Suggest clarifying the fallback behavior when status is absent.

Apply:

-                // Assuming all chains nowadays support this EIP: https://eips.ethereum.org/EIPS/eip-658
-                // 1 = Success, 0 = Failure
+                // EIP-658: 1 = success, 0 = failure (revert). If `status` is absent, fall back to block-number logic.

56-57: Don’t squash provider errors into “TxHashNotFound”.

Differentiate lookup miss vs provider/RPC error to avoid noisy “not found” handling upstream.

Consider:

-        Err(err) => Err(LanderError::TxHashNotFound(err.to_string())),
+        Err(err) => Err(LanderError::Provider(err.to_string())),

(or another variant that represents RPC failure).


60-172: Add coverage for status=None and unexpected status values.

Guard against edge-y chains and future quirks; assert the fallback and warn paths.

I can add:

  • status=None → Included/Finalized per block-number logic
  • status=5 → PendingInclusion + warn!(status=5)
    Want me to push those tests?
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96ae800 and 88a7ac0.

📒 Files selected for processing (1)
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2 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/tx_status_checker.rs
⏰ 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). (1)
  • GitHub Check: build-and-push-to-gcr
🔇 Additional comments (4)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (4)

46-51: Good call: compare status via as_u64() and keep the None fallback.

Much cleaner than peeking into U64 internals. Aye, tidy.


1-173: Run Clippy on the Lander crate
I couldn’t invoke Clippy in this environment—please run locally:

cd rust/main/lander
cargo clippy --all-features -- -D warnings

129-134: Swap MockProvider push order
Looks like get_tx_hash_status fetches the receipt first, then the block number. Push your mocked receipt before U64::from(23327790u64) (in tx_status_checker.rs at lines 129–134) so they line up and avoid any deserialization mix-ups. Run the tests locally to double-check.


156-159: Swap pushes: receipt before block
In rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (lines 156–159), push the tx receipt before the finalized block:

-        let _ = mock_provider.push(U64::from(23328000u64));
-        let tx_receipt = test_tx_receipt(transaction_hash, Some(U64::from(1)));
-        let _ = mock_provider.push(tx_receipt);
+        let tx_receipt = test_tx_receipt(transaction_hash, Some(U64::from(1)));
+        let _ = mock_provider.push(tx_receipt);
+        let _ = mock_provider.push(U64::from(23328000u64));

Due to sandbox limitations, please verify locally with:

cargo test -p lander -- tx_status_checker

Comment thread rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs Outdated
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: 1

Caution

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

⚠️ Outside diff range comments (1)
rust/main/lander/src/dispatcher/tests.rs (1)

349-370: Another test with mismatched expectations.

Same story here - you're pattern-matching for RevertedByChain (lines 349-351) but the metrics assertion expects "FailedSimulation" strings (lines 366-367). These need to line up.

Apply this diff to fix the metrics assertion:

     let metrics_assertion = MetricsAssertion {
         domain: entrypoint.inner.domain.clone(),
         finalized_txs: 0,
         building_stage_queue_length: 0,
         inclusion_stage_pool_length: 0,
         finality_stage_pool_length: 0,
         dropped_payloads: 1,
         dropped_transactions: 1,
-        dropped_payload_reason: "DroppedInTransaction(FailedSimulation)".to_string(),
-        dropped_transaction_reason: "FailedSimulation".to_string(),
+        dropped_payload_reason: "DroppedInTransaction(RevertedByChain)".to_string(),
+        dropped_transaction_reason: "RevertedByChain".to_string(),
         transaction_submissions: 0,
     };
🧹 Nitpick comments (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (1)

46-54: Pattern matching looks solid but could be more explicit.

The use of .as_u64() avoids peeking into the type's internals - that's proper. Delegating Some(1) | None to the block finality check follows EIP-658 semantics.

However, the catch-all _ pattern lumps together Some(0) (explicit failure) with any unexpected values. Consider being more explicit:

-            Ok(match receipt.status.as_ref().map(|s| s.as_u64()) {
-                // Assuming all chains nowadays support this EIP: https://eips.ethereum.org/EIPS/eip-658
-                // 1 = Success, 0 = Failure
-                Some(1) | None => {
-                    block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
-                        .await
-                }
-                _ => TransactionStatus::Dropped(TransactionDropReason::RevertedByChain),
-            })
+            Ok(match receipt.status.as_ref().map(|s| s.as_u64()) {
+                // EIP-658: 1 = Success, 0 = Failure
+                Some(1) | None => {
+                    block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
+                        .await
+                }
+                Some(0) => {
+                    // Explicit EIP-658 failure
+                    TransactionStatus::Dropped(TransactionDropReason::RevertedByChain)
+                }
+                Some(other) => {
+                    warn!(?hash, status = other, "Unexpected receipt.status value; treating as reverted");
+                    TransactionStatus::Dropped(TransactionDropReason::RevertedByChain)
+                }
+            })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88a7ac0 and 08d4be1.

📒 Files selected for processing (3)
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2 hunks)
  • rust/main/lander/src/dispatcher/tests.rs (3 hunks)
  • rust/main/lander/src/transaction/types.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/tests.rs
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs
  • rust/main/lander/src/transaction/types.rs
🧬 Code graph analysis (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (3)
rust/main/lander/src/adapter/chains/ethereum/adapter.rs (2)
  • new (74-127)
  • get_tx_hash_status (607-612)
rust/main/lander/src/adapter/chains/sealevel/adapter.rs (3)
  • new (77-118)
  • get_tx_hash_status (247-310)
  • get_tx_hash_status (419-424)
rust/main/lander/src/adapter/core.rs (2)
  • tx_status (62-79)
  • get_tx_hash_status (58-58)
⏰ 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: test-rs
  • GitHub Check: lander-coverage
  • GitHub Check: lint-rs
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: e2e-matrix (cosmosnative)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (cosmwasm)
🔇 Additional comments (4)
rust/main/lander/src/transaction/types.rs (1)

105-106: Good addition of the RevertedByChain variant.

The new variant clearly distinguishes on-chain execution failures from reorg-induced drops. Nice and tidy.

rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2)

123-146: Test for successful transaction looks good.

The mock setup and assertions for status=1 yielding Included are spot on.


148-171: Finalized transaction test checks out.

Properly tests the status=1 with sufficient finality to return Finalized. All clear.

rust/main/lander/src/dispatcher/tests.rs (1)

269-322: Critical mismatch between mock return value and test assertions.

The test sets up a mock that returns TransactionDropReason::RevertedByChain (line 282), but then asserts that the metrics will show "FailedSimulation" (lines 317-318). The comment at lines 307-308 mentions hardcoding to FailedSimulation, but that doesn't match what your mock is doing here.

Either update the mock to return what the system actually produces, or fix the metrics assertion to match the RevertedByChain drop reason. Right now this test will fail or pass for the wrong reasons.

Apply this diff to align the metrics assertion with the mock:

-    let metrics_assertion = MetricsAssertion {
-        domain: entrypoint.inner.domain.clone(),
-        finalized_txs: 0,
-        building_stage_queue_length: 0,
-        inclusion_stage_pool_length: 0,
-        finality_stage_pool_length: 0,
-        dropped_payloads: 1,
-        dropped_transactions: 1,
-        dropped_payload_reason: "DroppedInTransaction(FailedSimulation)".to_string(),
-        dropped_transaction_reason: "FailedSimulation".to_string(),
-        transaction_submissions: 0,
-    };
+    let metrics_assertion = MetricsAssertion {
+        domain: entrypoint.inner.domain.clone(),
+        finalized_txs: 0,
+        building_stage_queue_length: 0,
+        inclusion_stage_pool_length: 0,
+        finality_stage_pool_length: 0,
+        dropped_payloads: 1,
+        dropped_transactions: 1,
+        dropped_payload_reason: "DroppedInTransaction(RevertedByChain)".to_string(),
+        dropped_transaction_reason: "RevertedByChain".to_string(),
+        transaction_submissions: 0,
+    };

Likely an incorrect or invalid review comment.

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/tx_status_checker.rs (2)

46-54: Nice fix on the U64 handling and EIP-658 semantics.

The logic properly distinguishes successful transactions (status=1 or None) from reverted ones (status=0), returning RevertedByChain for the latter. This addresses all the previous feedback.

One small thing to consider: the wildcard at line 53 catches everything that's not 1 or None. Per EIP-658, you should only see 0 or 1, but if some chain returns, say, 2 or 42, you'd silently treat it as reverted. Might be worth logging unexpected values for debugging.

If you want to add that safety net, something like this:

-            Ok(match receipt.status.as_ref().map(|s| s.as_u64()) {
+            let status_value = receipt.status.as_ref().map(|s| s.as_u64());
+            Ok(match status_value {
                 // Assuming all chains nowadays support this EIP: https://eips.ethereum.org/EIPS/eip-658
                 // 1 = Success, 0 = Failure
                 Some(1) | None => {
                     block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
                         .await
                 }
-                _ => TransactionStatus::Dropped(TransactionDropReason::RevertedByChain),
+                Some(0) => TransactionStatus::Dropped(TransactionDropReason::RevertedByChain),
+                Some(other) => {
+                    warn!(?hash, status = other, "Unexpected receipt.status value per EIP-658");
+                    TransactionStatus::Dropped(TransactionDropReason::RevertedByChain)
+                }
             })

98-173: These tests look solid.

All three test cases properly validate the updated logic:

  • Failed tx (status=0) correctly asserts RevertedByChain
  • Successful txs (status=1) correctly assert Included or Finalized based on block finality

The test assertions now match the implementation, which resolves the previous mismatch.

If you're feeling thorough, you could add one more test for the status=None case (pre-EIP-658 chains that don't report status). Your comment assumes all chains support EIP-658 these days, so this is more of a belt-and-suspenders thing.

Something like:

#[tokio::test]
async fn test_get_tx_hash_status_no_status_field() {
    let transaction_hash =
        H256::from_str("575841942e0de82d3129cccf53e4e9c75b6d8a163f8a83d330a2e8d574820a4d")
            .unwrap();

    let mock_provider = MockProvider::new();
    let _ = mock_provider.push(U64::from(23327790u64));
    let tx_receipt = test_tx_receipt(transaction_hash, None);
    let _ = mock_provider.push(tx_receipt);

    let ethers_provider = Provider::new(mock_provider);
    let evm_provider: Arc<dyn EvmProviderForLander> = Arc::new(EthereumProvider::new(
        Arc::new(ethers_provider),
        HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum),
    ));
    let reorg_period = EthereumReorgPeriod::Blocks(15);

    let tx_status = get_tx_hash_status(&evm_provider, transaction_hash.into(), &reorg_period)
        .await
        .unwrap();
    assert_eq!(tx_status, TransactionStatus::Included);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08d4be1 and 3d7ea70.

📒 Files selected for processing (1)
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2 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/tx_status_checker.rs
🧬 Code graph analysis (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2)
rust/main/lander/src/adapter/chains/ethereum/adapter.rs (2)
  • new (74-127)
  • get_tx_hash_status (607-612)
rust/main/lander/src/adapter/chains/sealevel/adapter.rs (3)
  • new (77-118)
  • get_tx_hash_status (247-310)
  • get_tx_hash_status (419-424)
⏰ 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). (12)
  • GitHub Check: infra-test
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (cosmwasm)
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (cosmosnative)
  • GitHub Check: agent-configs (mainnet3)
  • GitHub Check: test-rs
  • GitHub Check: lint-rs
  • GitHub Check: lander-coverage
  • GitHub Check: build-and-push-to-gcr
🔇 Additional comments (2)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2)

7-7: LGTM!

The TransactionDropReason import is spot on for the new RevertedByChain handling.


73-94: LGTM!

Clean helper function for building test receipts. Makes the test setup much easier to follow.

@ameten
Copy link
Copy Markdown
Contributor

ameten commented Oct 7, 2025

It looks like e2e tests for EVM do not pass.

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/tx_status_checker.rs (2)

45-56: Much better now.

The previous concerns about destructuring U64 internals and misclassifying status=0 have been properly addressed. The code now correctly uses .as_u64() and maps a failed transaction to RevertedByChain.

One thing though - the wildcard pattern treats Some(1), None, and any unexpected values (like Some(2)) all the same. Per EIP-658, only 0 and 1 are defined, so unexpected values shouldn't happen... but if they do, you won't know about it.

You could make the handling more explicit if you want:

-        Ok(Some(receipt)) => match receipt.status.as_ref().map(|s| s.as_u64()) {
-            // https://eips.ethereum.org/EIPS/eip-658
-            Some(0) => Ok(TransactionStatus::Dropped(
-                TransactionDropReason::RevertedByChain,
-            )),
-            _ => {
-                let res =
-                    block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
-                        .await;
-                Ok(res)
-            }
-        },
+        Ok(Some(receipt)) => match receipt.status.as_ref().map(|s| s.as_u64()) {
+            // https://eips.ethereum.org/EIPS/eip-658
+            Some(0) => {
+                warn!(?hash, "Transaction reverted on-chain (status=0)");
+                Ok(TransactionStatus::Dropped(
+                    TransactionDropReason::RevertedByChain,
+                ))
+            }
+            Some(1) | None => {
+                let res =
+                    block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
+                        .await;
+                Ok(res)
+            }
+            Some(other) => {
+                warn!(?hash, status = other, "Unexpected receipt status; treating as success");
+                let res =
+                    block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
+                        .await;
+                Ok(res)
+            }
+        },

61-175: Nice coverage.

The tests cover the main paths: failed tx (status=0), successful included tx, and successful finalized tx. The mock setup looks solid.

You could add one more test for backward compatibility with pre-EIP-658 chains (where status is None). Just to make sure that path doesn't break:

#[tokio::test]
async fn test_get_tx_hash_status_pre_eip658() {
    let transaction_hash =
        H256::from_str("575841942e0de82d3129cccf53e4e9c75b6d8a163f8a83d330a2e8d574820a4d")
            .unwrap();

    let mock_provider = MockProvider::new();

    let _ = mock_provider.push(U64::from(23327790u64));
    let tx_receipt = test_tx_receipt(transaction_hash, None);
    let _ = mock_provider.push(tx_receipt);

    let ethers_provider = Provider::new(mock_provider);
    let evm_provider: Arc<dyn EvmProviderForLander> = Arc::new(EthereumProvider::new(
        Arc::new(ethers_provider),
        HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum),
    ));
    let reorg_period = EthereumReorgPeriod::Blocks(15);

    let tx_status = get_tx_hash_status(&evm_provider, transaction_hash.into(), &reorg_period)
        .await
        .unwrap();
    assert_eq!(tx_status, TransactionStatus::Included);
}

Also heads up - the PR objectives mention that e2e tests for EVM don't pass. Might want to check what's going on there, though these unit tests look fine to me.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d7ea70 and ebc48bc.

📒 Files selected for processing (1)
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2 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/tx_status_checker.rs
🧬 Code graph analysis (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (3)
rust/main/lander/src/adapter/chains/ethereum/adapter.rs (2)
  • new (74-127)
  • get_tx_hash_status (607-612)
rust/main/lander/src/adapter/chains/sealevel/adapter.rs (3)
  • new (77-118)
  • get_tx_hash_status (247-310)
  • get_tx_hash_status (419-424)
rust/main/lander/src/adapter/core.rs (2)
  • tx_status (62-79)
  • get_tx_hash_status (58-58)
⏰ 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: lint-rs
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: test-rs
  • GitHub Check: lander-coverage
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (cosmwasm)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (cosmosnative)
🔇 Additional comments (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (1)

7-7: Good add.

The TransactionDropReason import is needed for the new RevertedByChain variant. Clean.

@kamiyaa
Copy link
Copy Markdown
Contributor Author

kamiyaa commented Oct 7, 2025

It looks like e2e tests for EVM do not pass.

Fixed

Copy link
Copy Markdown
Contributor

@ameten ameten left a comment

Choose a reason for hiding this comment

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

Need unit test fixed

@kamiyaa kamiyaa added this pull request to the merge queue Oct 8, 2025
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 8, 2025
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/tx_status_checker.rs (2)

48-59: Consider more explicit pattern matching for status values.

Right now the catch-all _ lumps together Some(1), unexpected values like Some(2), and None. While it works, you're missing a chance to catch weird status values that shouldn't exist per EIP-658.

Here's a more defensive approach:

-    match receipt.status.as_ref().map(|s| s.as_u64()) {
-        // https://eips.ethereum.org/EIPS/eip-658
-        Some(0) => Ok(TransactionStatus::Dropped(
-            TransactionDropReason::RevertedByChain,
-        )),
-        _ => {
-            let res =
-                block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
-                    .await;
-            Ok(res)
-        }
-    }
+    match receipt.status.as_ref().map(|s| s.as_u64()) {
+        // https://eips.ethereum.org/EIPS/eip-658
+        Some(0) => Ok(TransactionStatus::Dropped(
+            TransactionDropReason::RevertedByChain,
+        )),
+        Some(1) => {
+            let res =
+                block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
+                    .await;
+            Ok(res)
+        }
+        Some(other) => {
+            warn!(?hash, status = other, "Unexpected receipt status value; treating as successful");
+            let res =
+                block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
+                    .await;
+            Ok(res)
+        }
+        None => {
+            // Pre-EIP-658 chain or status not available
+            let res =
+                block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
+                    .await;
+            Ok(res)
+        }
+    }

This way you'll spot any chains doing something funky with status codes.


62-176: Consider adding test coverage for None status.

You've got tests for status=0 and status=1, but nothing for when receipt.status is None. That's a valid scenario for pre-EIP-658 chains or when status isn't available, and it'd be good to verify it falls back to the block-number logic correctly.

Add a test like:

#[tokio::test]
async fn test_get_tx_hash_status_no_status_field() {
    let transaction_hash =
        H256::from_str("575841942e0de82d3129cccf53e4e9c75b6d8a163f8a83d330a2e8d574820a4d")
            .unwrap();

    let mock_provider = MockProvider::new();

    let _ = mock_provider.push(U64::from(23327774u64));
    let tx_receipt = test_tx_receipt(transaction_hash, None);
    let _ = mock_provider.push(tx_receipt);

    let ethers_provider = Provider::new(mock_provider);
    let evm_provider: Arc<dyn EvmProviderForLander> = Arc::new(EthereumProvider::new(
        Arc::new(ethers_provider),
        HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum),
    ));
    let reorg_period = EthereumReorgPeriod::Blocks(15);

    let tx_status = get_tx_hash_status(&evm_provider, transaction_hash.into(), &reorg_period)
        .await
        .unwrap();
    assert_eq!(tx_status, TransactionStatus::Included);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8544daf and dfea50c.

📒 Files selected for processing (1)
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2 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/tx_status_checker.rs
🧬 Code graph analysis (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (3)
rust/main/lander/src/adapter/chains/sealevel/adapter.rs (3)
  • new (78-119)
  • get_tx_hash_status (248-311)
  • get_tx_hash_status (420-425)
rust/main/lander/src/adapter/chains/ethereum/adapter.rs (2)
  • new (74-127)
  • get_tx_hash_status (607-612)
rust/main/lander/src/adapter/core.rs (2)
  • tx_status (62-79)
  • get_tx_hash_status (58-58)
⏰ 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 (cosmwasm)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: e2e-matrix (cosmosnative)
  • GitHub Check: lint-rs
  • GitHub Check: test-rs
  • GitHub Check: lander-coverage
🔇 Additional comments (4)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (4)

7-7: Good addition of proper error handling.

The import and receipt fetching logic look solid. Mapping provider errors and None receipts to TxHashNotFound is the right move here.

Also applies to: 41-47


75-96: Clean test helper.

The test_tx_receipt function is well-structured and makes the tests readable. Good stuff.


152-175: Third test looks good.

The finalized test is correct—transaction in block 23327789 with finalized block at 23328000 properly returns Finalized.


127-150: Test assertion doesn't match the logic.

Look, in this test you're setting the finalized block to 23327790 and the transaction block to 23327789. The code checks if finalized_block >= block_number, which is 23327790 >= 23327789 = true. That means it returns Finalized, not Included.

Fix it like this:

-        let _ = mock_provider.push(U64::from(23327790u64));
+        let _ = mock_provider.push(U64::from(23327774u64));
         let tx_receipt = test_tx_receipt(transaction_hash, Some(U64::from(1)));

This sets finalized to 23327774, which is 15 blocks before the tx block (23327789), so the tx will be Included but not yet Finalized.

Likely an incorrect or invalid review comment.

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 (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (1)

48-59: EIP-658 handling is correct, but consider logging unexpected values.

You've addressed the past feedback properly—using .as_u64() instead of destructuring, and mapping status=0 to RevertedByChain. That's the right call.

The catch-all _ at line 53 treats all non-zero statuses (including status=1 and unexpected values like status=2) the same way. Per EIP-658, only 0 and 1 should appear, but if something weird shows up, you might want to know about it.

Consider adding a warning for unexpected status values:

     match receipt.status.as_ref().map(|s| s.as_u64()) {
         // https://eips.ethereum.org/EIPS/eip-658
         Some(0) => Ok(TransactionStatus::Dropped(
             TransactionDropReason::RevertedByChain,
         )),
+        Some(1) => {
+            let res =
+                block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
+                    .await;
+            Ok(res)
+        }
+        Some(other) => {
+            warn!(?hash, status = other, "Unexpected receipt.status per EIP-658; treating as success");
+            let res =
+                block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
+                    .await;
+            Ok(res)
+        }
-        _ => {
+        None => {
             let res =
                 block_number_result_to_tx_status(provider, receipt.block_number, reorg_period)
                     .await;
             Ok(res)
         }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfea50c and 1912750.

📒 Files selected for processing (1)
  • rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2 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/tx_status_checker.rs
🧬 Code graph analysis (1)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (2)
rust/main/lander/src/adapter/chains/sealevel/adapter.rs (3)
  • new (78-119)
  • get_tx_hash_status (248-311)
  • get_tx_hash_status (420-425)
rust/main/lander/src/adapter/chains/ethereum/adapter.rs (2)
  • new (74-127)
  • get_tx_hash_status (607-612)
⏰ 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: e2e-matrix (cosmosnative)
  • GitHub Check: e2e-matrix (sealevel)
  • GitHub Check: e2e-matrix (cosmwasm)
  • GitHub Check: e2e-matrix (radix)
  • GitHub Check: e2e-matrix (starknet)
  • GitHub Check: e2e-matrix (evm)
  • GitHub Check: build-and-push-to-gcr
  • GitHub Check: lint-rs
  • GitHub Check: test-rs
  • GitHub Check: lander-coverage
🔇 Additional comments (3)
rust/main/lander/src/adapter/chains/ethereum/adapter/tx_status_checker.rs (3)

7-7: Import looks good.

The addition of TransactionDropReason to the imports is necessary for the new RevertedByChain handling. Well done addressing the previous feedback.


41-47: Receipt retrieval and error handling look solid.

The error mapping to TxHashNotFound for both provider errors and missing receipts is appropriate. The debug logging gives you visibility into what's going on.


62-176: Tests cover the key scenarios.

You've got good coverage here:

  • Failed tx (status=0) correctly maps to Dropped(RevertedByChain)
  • Successful tx (status=1) maps to Included when not finalized
  • Successful tx (status=1) maps to Finalized when past the reorg period

The test setup with MockProvider is clean and the assertions match the implementation. Nice work.

@kamiyaa kamiyaa added this pull request to the merge queue Oct 9, 2025
Merged via the queue into main with commit 1a5a1d0 Oct 9, 2025
39 checks passed
@kamiyaa kamiyaa deleted the jeff/evm-confirm branch October 9, 2025 17:56
@github-project-automation github-project-automation Bot moved this from In Review to Done in Hyperlane Tasks Oct 9, 2025
@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 9, 2025

Codecov Report

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

Additional details and impacted files
@@          Coverage Diff          @@
##            main   #7016   +/-   ##
=====================================
  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