Skip to content

Conversation

@LesnyRumcajs
Copy link
Member

@LesnyRumcajs LesnyRumcajs commented Sep 25, 2025

Summary of changes

Changes introduced in this pull request:

  • backports the fix to eth_getCode and eth_getStorageAt from Lotus.
  • removed 100M message limit that failed contract deployments. I couldn't find corresponding limit with @hanabi1224 in Lotus.
  • fixed the API of eth_getTransactionReceipt[Limited] to return an optional receipt, in lines with what Lotus does.

Reference issue to close (if applicable)

Closes #5979
Closes #6214

Other information and links

Change checklist

  • I have performed a self-review of my own code,
  • I have made corresponding changes to the documentation. All new code adheres to the team's documentation standards,
  • I have added tests that prove my fix is effective or that my feature works (if possible),
  • I have made sure the CHANGELOG is up-to-date. All user-facing changes should be reflected in this document.

Summary by CodeRabbit

  • Bug Fixes

    • eth_getCode and eth_getStorageAt now return data for the requested tipset (not the parent).
    • eth_getTransactionReceipt returns null for missing transactions instead of an error.
  • Feature Improvements

    • Removed the legacy 100M gas limit so higher-gas contract deployments are allowed.
    • New API to run a message against a specified state (enables targeted state-based queries).
  • Tests

    • Added regression and snapshot tests and test-only helpers to validate the above behaviors.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Walkthrough

Eth RPC methods now use the requested tipset state for code/storage lookups; transaction receipt RPCs return null for missing receipts; a legacy 100M gas-limit check was removed from the message pool. Several test utilities, snapshots, and a shim re-export were added.

Changes

Cohort / File(s) Summary
ETH RPC & receipt behavior
src/rpc/methods/eth.rs
EthGetCode and EthGetStorageAt pre-fetch the target tipset state and invoke using call_on_state(...). get_eth_transaction_receipt and RPCs EthGetTransactionReceipt / EthGetTransactionReceiptLimited now return Option<EthTxReceipt> and return Ok(None) for missing receipts instead of errors.
State manager: call on arbitrary state
src/state_manager/mod.rs
Added internal state_cid: Option<Cid> to call_raw and new public call_on_state(state_cid, message, tipset) to execute a message against a given state CID rather than the tipset's parent state.
Message pool gas-limit removal & test
src/message_pool/msgpool/msg_pool.rs
Removed early gas_limit guard that rejected messages >100_000_000; added regression test add_helper_message_gas_limit_test validating acceptance of large gas limits.
Shim re-export and miner import update
src/shim/crypto.rs, src/rpc/methods/miner.rs
Re-exported BLS_SIG_LEN from fvm_shared_latest::crypto::signature in the shim and updated miner.rs to import the constant from the shim.
Test helpers / mocks
src/message/signed_message.rs, src/utils/cache/lru.rs
Added test-only SignedMessage::mock_bls_signed_message(message) and test-only SizeTrackingLruCache::new_mocked() constructors (gated by #[cfg(test)]).
API tests & snapshots
src/tool/subcommands/api_cmd/api_compare_tests.rs, src/tool/subcommands/api_cmd/test_snapshots.txt
Added test asserting EthGetTransactionReceipt returns null for non-existent tx (parity with Lotus). Updated/added RPC snapshot variants for EthGetCode, EthGetStorageAt, and EthGetTransactionReceipt.
Changelog
CHANGELOG.md
Added v0.30.5 entries documenting: EthGetCode/EthGetStorageAt tipset-state fixes (#5979) and EthGetTransactionReceipt behavior & gas-limit removal (#6118).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay special attention to src/rpc/methods/eth.rs for correct propagation of the new Option<EthTxReceipt> return types and callers.
  • Verify src/state_manager/mod.rs call_on_state integrates correctly with existing VM initialization and that state_cid handling doesn't regress other call paths.
  • Check src/message_pool/msgpool/msg_pool.rs test and pool semantics after gas-limit removal to ensure no unintended validation gaps.
  • Review test-only helpers for correct #[cfg(test)] gating and no leakage into production builds.

Possibly related PRs

Suggested reviewers

  • akaladarshi
  • hanabi1224
  • sudo-shashank

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: contract deployment via forge failing' directly addresses the main issue (#6214) resolved by this PR, clearly summarizing the primary change.
Linked Issues check ✅ Passed All code changes directly implement requirements from linked issues: eth_getCode and eth_getStorageAt use correct tipset state (#5979), the 100M gas limit is removed (#6214), and eth_getTransactionReceipt returns optional values.
Out of Scope Changes check ✅ Passed All changes are scoped to the linked issues: test infrastructure helpers (mock_bls_signed_message, new_mocked, regression tests) and snapshot updates are directly supporting the primary fixes.
Docstring Coverage ✅ Passed Docstring coverage is 94.12% which is sufficient. The required threshold is 80.00%.
✨ 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 eth-get-code-fix

📜 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 875df02 and 8762af8.

📒 Files selected for processing (1)
  • src/tool/subcommands/api_cmd/api_compare_tests.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: LesnyRumcajs
Repo: ChainSafe/forest PR: 5907
File: src/rpc/methods/state.rs:523-570
Timestamp: 2025-08-06T15:44:33.467Z
Learning: LesnyRumcajs prefers to rely on BufWriter's Drop implementation for automatic flushing rather than explicit flush() calls in Forest codebase.
🧬 Code graph analysis (1)
src/tool/subcommands/api_cmd/api_compare_tests.rs (4)
src/rpc/client.rs (1)
  • request (258-271)
src/rpc/methods/eth.rs (3)
  • from_str (284-286)
  • from_str (392-403)
  • from_str (452-471)
src/rpc/methods/eth/types.rs (2)
  • from_str (56-60)
  • from_str (225-229)
src/shim/address.rs (2)
  • from_str (178-183)
  • from_str (299-302)
⏰ 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). (9)
  • GitHub Check: Build Ubuntu
  • GitHub Check: Build MacOS
  • GitHub Check: cargo-publish-dry-run
  • GitHub Check: Build forest binaries on Linux AMD64
  • GitHub Check: tests-release
  • GitHub Check: All lint checks
  • GitHub Check: tests
  • GitHub Check: Check
  • GitHub Check: Coverage
🔇 Additional comments (1)
src/tool/subcommands/api_cmd/api_compare_tests.rs (1)

1986-1996: LGTM! Test correctly validates null response for missing transactions.

The test appropriately verifies that both Forest and Lotus return identical null responses when querying a non-existent transaction receipt, aligning with the PR's objective to return Option<EthTxReceipt> instead of errors for missing receipts.


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

@LesnyRumcajs LesnyRumcajs added the RPC requires calibnet RPC checks to run on CI label Sep 25, 2025
@LesnyRumcajs
Copy link
Member Author

@elmattic as discussed, please drive this to completion. Thank you!

@elmattic elmattic removed their assignment Oct 24, 2025
@LesnyRumcajs LesnyRumcajs force-pushed the eth-get-code-fix branch 3 times, most recently from e9f1fa3 to 9968216 Compare December 15, 2025 12:13
@LesnyRumcajs LesnyRumcajs changed the title fix: use correct state for eth_getCode and eth_getStorageAt fix: contract deployment via forge failing Dec 15, 2025
@LesnyRumcajs LesnyRumcajs marked this pull request as ready for review December 15, 2025 15:39
@LesnyRumcajs LesnyRumcajs requested a review from a team as a code owner December 15, 2025 15:39
@LesnyRumcajs LesnyRumcajs requested review from akaladarshi and hanabi1224 and removed request for a team December 15, 2025 15:39
Copy link
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

Caution

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

⚠️ Outside diff range comments (2)
src/rpc/methods/eth.rs (2)

2160-2171: EthGetCode now executes on the tipset state, but actor lookup still uses the parent state

Using tipset_state plus state_manager.call_on_state(state, &message, Some(ts)) ensures the GetBytecode call itself runs against the post‑tipset state, which is what you want.

However, the pre‑check still resolves the target actor against *ts.parent_state() via get_required_actor, while EthGetStorageAt has been updated to look up the actor on the same state obtained from tipset_state. That asymmetry likely means eth_getCode will still fail (or incorrectly return empty code) for actors that only exist in the tipset’s post‑state, which was precisely the bug this PR is trying to fix.

Consider mirroring the EthGetStorageAt pattern here, e.g.:

-        let actor = ctx
-            .state_manager
-            .get_required_actor(&to_address, *ts.parent_state())?;
-        // Not a contract. We could try to distinguish between accounts and "native" contracts here,
-        // but it's not worth it.
-        if !is_evm_actor(&actor.code) {
-            return Ok(Default::default());
-        }
+        let (state, _) = ctx.state_manager.tipset_state(&ts).await?;
+        let Some(actor) = ctx.state_manager.get_actor(&to_address, state)? else {
+            // No actor at this address in this state ⇒ no code.
+            return Ok(Default::default());
+        };
+        // Not a contract. We could try to distinguish between accounts and "native" contracts here,
+        // but it's not worth it.
+        if !is_evm_actor(&actor.code) {
+            return Ok(Default::default());
+        }
@@
-        let (state, _) = ctx.state_manager.tipset_state(&ts).await?;
         let api_invoc_result = 'invoc: {
             for ts in ts.chain(ctx.store()) {

(adjusting the second let (state, _) accordingly to avoid duplication).


2797-2858: get_eth_transaction_receipt still errors on “not indexed” instead of returning None

The signature and callers now expect Result<Option<EthTxReceipt>, ServerError>, and you correctly map lookup errors to Ok(None) with a debug log. But when search_for_message returns Ok(None) (tx not yet on chain / not indexed), the subsequent .context("not indexed")? on the Option turns this into an error, which bubbles out as a server error instead of null in the JSON‑RPC response.

You can preserve the new Optional semantics by explicitly handling the None case before calling .context(...):

 async fn get_eth_transaction_receipt(
@@
-    let option = ctx
+    let option = ctx
         .state_manager
         .search_for_message(None, msg_cid, limit, Some(true))
         .await
         .with_context(|| format!("failed to lookup Eth Txn {tx_hash} as {msg_cid}"));
 
-    let option = match option {
-        Ok(opt) => opt,
-        // Ethereum clients expect an empty response when the message was not found
-        Err(e) => {
-            tracing::debug!("could not find transaction receipt for hash {tx_hash}: {e}");
-            return Ok(None);
-        }
-    };
-
-    let (tipset, receipt) = option.context("not indexed")?;
+    let option = match option {
+        Ok(opt) => opt,
+        // Ethereum clients expect an empty response when the message was not found
+        Err(e) => {
+            tracing::debug!("could not find transaction receipt for hash {tx_hash}: {e}");
+            return Ok(None);
+        }
+    };
+
+    // Also treat “not indexed yet” / no result as a `null` receipt.
+    let Some((tipset, receipt)) = option else {
+        tracing::debug!(
+            "transaction hash {tx_hash} (cid {msg_cid}) was not indexed; returning null receipt"
+        );
+        return Ok(None);
+    };
@@
-    let tx_receipt = new_eth_tx_receipt(&ctx, &parent_ts, &tx, &message_lookup.receipt).await?;
-
-    Ok(Some(tx_receipt))
+    let tx_receipt = new_eth_tx_receipt(&ctx, &parent_ts, &tx, &message_lookup.receipt).await?;
+
+    Ok(Some(tx_receipt))
 }

This aligns the implementation with the new Option return type and the updated snapshots that expect null for not‑found receipts.

🧹 Nitpick comments (4)
src/tool/subcommands/api_cmd/api_compare_tests.rs (1)

1986-1996: New EthGetTransactionReceipt not‑found test correctly asserts null behavior

Using RpcTest::identity(EthGetTransactionReceipt::request((EthHash::from_str("0x…")?,))) ensures both Forest and Lotus now return a successful JSON result with null (Option::None) instead of an error for a non‑existent transaction hash, which is exactly the intended Lotus‑compatible behavior. The constant hash is well‑formed (32‑byte H256), so from_str/serialization will not panic.

If you ever want to guarantee this always exercises the “not found” path (rather than just relying on an astronomically low collision probability), you could derive the hash from a different domain (e.g., hash of a fixed string plus a non‑Ethereum domain tag) or document the assumption that it’s intentionally random‑looking.

src/message_pool/msgpool/msg_pool.rs (1)

680-702: Regression test correctly guards against re‑introducing a hard gas_limit cap

The add_helper_message_gas_limit_test uses TestApi, a mocked BLS sig cache, and SignedMessage::mock_bls_signed_message to exercise add_helper directly with a large gas_limit (666_666_666), and asserts add_helper returns Ok(…). This is a good regression for the removed 100M limit and ensures there is no pool‑specific cap beyond protocol‑level validation. The test is self‑contained and uses only test‑only helpers, so it won’t affect production code.

The comment “There are no limits on a single message” is slightly stronger than what actually happens (there are still protocol/network‑level limits enforced elsewhere); consider rephrasing to “There is no additional message‑pool‑specific gas limit” to avoid confusion.

src/message/signed_message.rs (1)

102-108: Test-only mock_bls_signed_message helper is sound

Using BLS_SIG_LEN via the shim to size the mock BLS signature and new_unchecked to bypass verification is appropriate for test scaffolding and avoids magic numbers. Downstream tests can now prefer this helper over ad‑hoc vec![0; 96] constructions if desired.

src/state_manager/mod.rs (1)

583-678: call_on_state wiring looks correct; consider documenting state expectations

The refactor to thread an optional state_cid through call_raw, keep call behavior unchanged via None, and add call_on_state that forces a specific state root is logically consistent and matches the new eth RPC use‑cases.

For future callers, it would help to document that state_cid should correspond to the “pre‑message” state for tipset (what tipset.parent_state() would otherwise provide); passing a post‑execution state and then re‑applying prior messages could double‑apply effects for non‑SYSTEM actors. Not an issue with current usages, but worth clarifying in docs/comments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e3af9f and 875df02.

📒 Files selected for processing (10)
  • CHANGELOG.md (1 hunks)
  • src/message/signed_message.rs (1 hunks)
  • src/message_pool/msgpool/msg_pool.rs (1 hunks)
  • src/rpc/methods/eth.rs (8 hunks)
  • src/rpc/methods/miner.rs (1 hunks)
  • src/shim/crypto.rs (1 hunks)
  • src/state_manager/mod.rs (3 hunks)
  • src/tool/subcommands/api_cmd/api_compare_tests.rs (1 hunks)
  • src/tool/subcommands/api_cmd/test_snapshots.txt (1 hunks)
  • src/utils/cache/lru.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: LesnyRumcajs
Repo: ChainSafe/forest PR: 5907
File: src/rpc/methods/state.rs:523-570
Timestamp: 2025-08-06T15:44:33.467Z
Learning: LesnyRumcajs prefers to rely on BufWriter's Drop implementation for automatic flushing rather than explicit flush() calls in Forest codebase.
📚 Learning: 2025-09-11T16:03:14.328Z
Learnt from: akaladarshi
Repo: ChainSafe/forest PR: 6000
File: src/tool/subcommands/api_cmd/state_decode_params_tests/miner.rs:4-9
Timestamp: 2025-09-11T16:03:14.328Z
Learning: In the Forest codebase's state_decode_params_tests module, common imports like `to_vec`, `TokenAmount`, `Cid`, etc. are centralized in the mod.rs file and made available to submodules through `use super::*;`, eliminating the need for duplicate imports in individual test files.

Applied to files:

  • src/shim/crypto.rs
📚 Learning: 2025-09-02T14:23:53.808Z
Learnt from: akaladarshi
Repo: ChainSafe/forest PR: 5923
File: src/tool/subcommands/api_cmd/test_snapshots.txt:206-252
Timestamp: 2025-09-02T14:23:53.808Z
Learning: In the Forest project, .rpcsnap.json.zst snapshot files listed in src/tool/subcommands/api_cmd/test_snapshots.txt are not stored in the repository itself but are downloaded from a DigitalOcean (DO) bucket when needed. The manifest file serves as an index/catalog of available snapshots.

Applied to files:

  • src/tool/subcommands/api_cmd/test_snapshots.txt
🧬 Code graph analysis (4)
src/utils/cache/lru.rs (9)
src/message_pool/msgpool/msg_pool.rs (2)
  • new (72-77)
  • new (465-583)
src/chain/store/chain_store.rs (2)
  • new (120-147)
  • new (581-585)
src/beacon/drand.rs (1)
  • new (245-263)
src/state_migration/common/mod.rs (1)
  • new (32-39)
src/chain/store/index.rs (1)
  • new (42-46)
src/blocks/tipset.rs (2)
  • new (246-264)
  • new (474-493)
src/chain_sync/bad_block_cache.rs (1)
  • new (26-30)
src/db/car/mod.rs (1)
  • new (72-78)
src/state_manager/cache.rs (3)
  • new (47-49)
  • new (174-187)
  • new (249-251)
src/message/signed_message.rs (2)
src/message/chain_message.rs (1)
  • message (22-27)
src/shim/crypto.rs (1)
  • new_bls (78-83)
src/tool/subcommands/api_cmd/api_compare_tests.rs (2)
src/rpc/reflect/mod.rs (1)
  • request (288-298)
src/rpc/methods/eth.rs (3)
  • from_str (284-286)
  • from_str (392-403)
  • from_str (452-471)
src/state_manager/mod.rs (1)
src/interpreter/vm.rs (1)
  • new (175-271)
⏰ 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: Coverage
  • GitHub Check: Build Ubuntu
  • GitHub Check: cargo-publish-dry-run
  • GitHub Check: Build MacOS
  • GitHub Check: Build forest binaries on Linux AMD64
  • GitHub Check: All lint checks
  • GitHub Check: tests-release
  • GitHub Check: tests
🔇 Additional comments (9)
src/utils/cache/lru.rs (1)

152-156: Test-only constructor is well-scoped and consistent with existing API

new_mocked is correctly gated with #[cfg(test)], uses a minimal capacity, and leverages new_inner without registering metrics, which is appropriate for unit tests. The use of Cow::Borrowed("mocked_cache") and NonZeroUsize::new(1) matches the internal constructor’s expectations and doesn’t affect non-test behavior.

CHANGELOG.md (1)

58-62: Changelog entries accurately describe the behavioral changes

The three new “Fixed” bullets correctly capture the EthGetCode/EthGetStorageAt tipset fix, the EthGetTransactionReceipt[Limited] null‑on‑missing behavior, and the removal of the legacy 100M gas limit. Wording, links, and placement are consistent with surrounding entries.

src/tool/subcommands/api_cmd/test_snapshots.txt (3)

64-66: Snapshots for eth_getCode look consistent with new behavior

The three eth_getCode snapshot entries (latest/concrete/pending) follow existing naming and tagging conventions and align with the new RPC behavior; no issues from the manifest perspective.


69-71: Snapshots for eth_getStorageAt correctly mirror the new state handling

The added eth_getStorageAt snapshots (latest/concrete/pending) match the established manifest pattern and are appropriate for exercising the updated storage-at semantics.


79-79: Transaction-not-found snapshot matches Optional receipt semantics

The new filecoin_ethgettransactionreceipt_... # transaction not found snapshot is an appropriate fixture for the Option<EthTxReceipt> behavior and looks correct in this manifest.

src/shim/crypto.rs (1)

17-17: Re-exporting BLS_SIG_LEN from the shim is a good consolidation

Exposing BLS_SIG_LEN via the shim is consistent with how other FVM crypto constants are surfaced and simplifies downstream imports without changing behavior.

src/rpc/methods/miner.rs (1)

29-29: Importing BLS_SIG_LEN via the shim keeps FVM details encapsulated

Switching the BLS_SIG_LEN import to crate::shim::crypto cleanly leverages the new re-export and removes a direct dependency on FVM internals from this RPC module.

src/rpc/methods/eth.rs (2)

2212-2233: EthGetStorageAt correctly uses tipset state and call_on_state

For eth_getStorageAt, fetching (state, _) = tipset_state(&ts), resolving the actor via get_actor(&to_address, state)?, and driving the VM through call_on_state(state, &message, Some(ts)) ensures both existence checks and the storage lookup itself see the correct post‑tipset state. This matches the Lotus backport intent and looks good.


2869-2893: RPC types updated to Option<EthTxReceipt> are consistent with helper changes

Switching EthGetTransactionReceipt and EthGetTransactionReceiptLimited to return Option<EthTxReceipt> matches the new get_eth_transaction_receipt helper and the desired eth JSON‑RPC semantics (returning null when a receipt is not found). Once the helper’s None handling is fixed, these method signatures are correct.

You may want to re-run the eth receipt snapshot tests to confirm that null is now returned for missing receipts and that no callers still assume a non‑optional result.

@codecov
Copy link

codecov bot commented Dec 15, 2025

Codecov Report

❌ Patch coverage is 34.48276% with 38 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.42%. Comparing base (2e3af9f) to head (8762af8).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
src/state_manager/mod.rs 0.00% 15 Missing ⚠️
src/rpc/methods/eth.rs 0.00% 13 Missing ⚠️
src/tool/subcommands/api_cmd/api_compare_tests.rs 0.00% 10 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
src/message/signed_message.rs 76.69% <100.00%> (+0.94%) ⬆️
src/message_pool/msgpool/msg_pool.rs 73.68% <100.00%> (+1.00%) ⬆️
src/rpc/methods/miner.rs 0.00% <ø> (ø)
src/shim/crypto.rs 67.65% <ø> (+0.37%) ⬆️
src/utils/cache/lru.rs 59.83% <100.00%> (+1.01%) ⬆️
src/tool/subcommands/api_cmd/api_compare_tests.rs 0.00% <0.00%> (ø)
src/rpc/methods/eth.rs 23.14% <0.00%> (-0.05%) ⬇️
src/state_manager/mod.rs 5.99% <0.00%> (-0.06%) ⬇️

... and 5 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2e3af9f...8762af8. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

hanabi1224
hanabi1224 previously approved these changes Dec 15, 2025
@hanabi1224 hanabi1224 added this pull request to the merge queue Dec 16, 2025
Merged via the queue into main with commit 74a5f0c Dec 16, 2025
54 checks passed
@hanabi1224 hanabi1224 deleted the eth-get-code-fix branch December 16, 2025 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

RPC requires calibnet RPC checks to run on CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failure to deploy a contract with forge Backport Lotus fixes to eth_getCode and eth_getStorageAt

4 participants