Skip to content

feat: add --n-epochs to forest-cli state compute#5946

Merged
hanabi1224 merged 19 commits intomainfrom
hm/compute-state-range
Aug 19, 2025
Merged

feat: add --n-epochs to forest-cli state compute#5946
hanabi1224 merged 19 commits intomainfrom
hm/compute-state-range

Conversation

@hanabi1224
Copy link
Contributor

@hanabi1224 hanabi1224 commented Aug 18, 2025

Summary of changes

Changes introduced in this pull request:

  • add --n-epochs to forest-cli state compute to compute states for a batch of epochs
  • add --verbose to forest-cli state compute to print epochs and tipset keys along with state roots
  • parallelize batch message downloading for better performance

Reference issue to close (if applicable)

Closes

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

  • New Features

    • State compute supports multi-epoch batch processing with optional verbose output.
    • CLI flags: --n-epochs to process multiple epochs; --verbose to include epoch and tipset key alongside state roots.
    • RPC now returns per-epoch results (state root, epoch, tipset key) for ranges.
  • Breaking Changes

    • State RPC accepts an optional epochs parameter and returns a list of per-epoch results instead of a single value.
  • Documentation

    • Changelog updated.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 18, 2025

Walkthrough

Adds multi-epoch state computation end-to-end: CLI flags and output, RPC accepts an optional n_epochs and returns a vec of per-tipset results, new ForestComputeStateOutput type, Lotus JSON support for NonZeroUsize, and minor chain_store blockstore usage change for tipset persistence.

Changes

Cohort / File(s) Summary
CLI: state compute flags and output
src/cli/subcommands/state_cmd.rs
Extends StateCommands::Compute to Compute { epoch: ChainEpoch, n_epochs: Option<NonZeroUsize>, verbose: bool }; ReadState actor_address typed as String. Sends (epoch, n_epochs) to RPC and iterates returned Vec<ForestComputeStateOutput>, printing state_root or (epoch, tipset_key, state_root) when verbose. Adds imports for NonZeroUsize and ForestComputeStateOutput.
RPC: multi-epoch Forest.StateCompute
src/rpc/methods/state.rs
Increases RpcMethod arity (1→2). Params(ChainEpoch, Option<NonZeroUsize>). OkVec<ForestComputeStateOutput>. Adds N_REQUIRED_PARAMS = 1 and updates PARAM_NAMES. Computes to_epoch = from_epoch + n_epochs - 1, fetches tipsets in range, backfills missing full tipsets (network + persist), computes state per-tipset concurrently (FuturesOrdered) and returns per-tipset outputs.
RPC types: new output struct
src/rpc/methods/state/types.rs
Adds public ForestComputeStateOutput { state_root: Cid, epoch: ChainEpoch, tipset_key: TipsetKey } with serde/schemars derives and LotusJson integration (lotus_json_with_self!).
Lotus JSON support
src/lotus_json/mod.rs
Adds std::num::NonZeroUsize to lotus_json_with_self!, enabling identity LotusJson conversion for NonZeroUsize.
Chain sync: storage accessor change
src/chain_sync/chain_follower.rs
Replaces uses of &chain_store.db with chain_store.blockstore() when persisting full tipsets/blocks; no control-flow changes.
Changelog
CHANGELOG.md
Documents two new CLI flags: --n-epochs (batch processing) and --verbose (print epochs and tipset keys alongside state roots).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as forest-cli (state compute)
  participant RPC as Forest.StateCompute
  participant Chain as ChainStore
  participant Net as Network
  participant Exec as State Compute

  User->>CLI: state compute --epoch E [--n-epochs N] [--verbose]
  CLI->>RPC: request (from_epoch=E, n_epochs=N?)
  RPC->>Chain: fetch tipsets in [E, to_epoch]
  alt missing or incomplete tipset
    RPC->>Net: load full tipset(s)
    Net-->>RPC: full tipset(s)
    RPC->>Chain: persist full tipset(s) via blockstore()
  end
  par per-tipset (concurrent)
    RPC->>Exec: compute state for tipset_i
    Exec-->>RPC: {state_root, epoch_i, tipset_key_i}
  end
  RPC-->>CLI: Vec<ForestComputeStateOutput>
  alt verbose
    CLI->>User: print epoch, tipset_key, state_root per item
  else
    CLI->>User: print state_root per item
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

RPC

Suggested reviewers

  • elmattic
  • LesnyRumcajs
  • sudo-shashank

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9c31a63 and b372888.

📒 Files selected for processing (1)
  • CHANGELOG.md (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md
⏰ 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). (10)
  • GitHub Check: cargo-publish-dry-run
  • GitHub Check: Build MacOS
  • GitHub Check: Build Ubuntu
  • GitHub Check: Build forest binaries on Linux AMD64
  • GitHub Check: All lint checks
  • GitHub Check: tests-release
  • GitHub Check: tests
  • GitHub Check: Check
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch hm/compute-state-range

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@hanabi1224 hanabi1224 marked this pull request as ready for review August 18, 2025 23:57
@hanabi1224 hanabi1224 requested a review from a team as a code owner August 18, 2025 23:57
@hanabi1224 hanabi1224 requested review from akaladarshi and elmattic and removed request for a team August 18, 2025 23:57
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

🧹 Nitpick comments (2)
src/rpc/methods/state.rs (2)

1438-1444: Consider edge cases in epoch range calculation.

The calculation to_epoch = from_epoch + n_epochs - 1 is correct, but consider validating that from_epoch is not negative or beyond the chain height to provide better error messages.

 let n_epochs = n_epochs.map(|n| n.get()).unwrap_or(1) as ChainEpoch;
+if from_epoch < 0 {
+    return Err(anyhow::anyhow!("from_epoch must be non-negative").into());
+}
 let to_epoch = from_epoch + n_epochs - 1;
 let to_ts = ctx.chain_index().tipset_by_height(
     to_epoch,

1459-1466: Consider adding timeout for network operations.

The network backfill operation could potentially hang. Consider adding a timeout to prevent indefinite waiting.

 if crate::chain_sync::load_full_tipset(&chain_store, ts.key()).is_err() {
     // Backfill full tipset from the network
-    let fts = network_context
-        .chain_exchange_messages(None, &ts)
-        .await
-        .map_err(|e| anyhow::anyhow!(e))?;
+    let fts = tokio::time::timeout(
+        std::time::Duration::from_secs(30),
+        network_context.chain_exchange_messages(None, &ts)
+    )
+    .await
+    .map_err(|_| anyhow::anyhow!("Timeout fetching tipset from network"))?
+    .map_err(|e| anyhow::anyhow!(e))?;
     fts.persist(&chain_store.db)?;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 442e8e1 and fee02eb.

📒 Files selected for processing (5)
  • CHANGELOG.md (1 hunks)
  • src/cli/subcommands/state_cmd.rs (3 hunks)
  • src/lotus_json/mod.rs (1 hunks)
  • src/rpc/methods/state.rs (3 hunks)
  • src/rpc/methods/state/types.rs (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: hanabi1224
PR: ChainSafe/forest#5930
File: build.rs:64-77
Timestamp: 2025-08-13T09:43:20.301Z
Learning: hanabi1224 prefers hard compile-time errors in build scripts rather than runtime safeguards or collision detection, believing it's better to fail fast and fix root causes of issues like malformed snapshot names.
🧬 Code Graph Analysis (2)
src/cli/subcommands/state_cmd.rs (2)
src/rpc/client.rs (2)
  • client (114-114)
  • request (272-285)
src/rpc/reflect/mod.rs (1)
  • request (250-260)
src/rpc/methods/state.rs (2)
src/rpc/reflect/mod.rs (1)
  • all (132-135)
src/rpc/segregation_layer.rs (2)
  • n (115-115)
  • n (130-130)
⏰ 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 forest binaries on Linux AMD64
  • GitHub Check: tests
  • GitHub Check: tests-release
  • GitHub Check: Build Ubuntu
  • GitHub Check: cargo-publish-dry-run
  • GitHub Check: Build MacOS
  • GitHub Check: All lint checks
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (14)
CHANGELOG.md (1)

40-42: LGTM: Clear documentation of new CLI features.

The changelog entries accurately document the new --n-epochs and --verbose flags for the forest-cli state compute command. The descriptions are clear and concise.

src/lotus_json/mod.rs (1)

529-529: LGTM: Adds JSON support for NonZeroUsize.

Adding std::num::NonZeroUsize to the lotus_json_with_self! macro enables JSON serialization/deserialization for this type, which is used in the CLI's n_epochs parameter. The identity mapping (where LotusJson = Self) is appropriate for this primitive type.

src/cli/subcommands/state_cmd.rs (4)

5-5: LGTM: Import added for new functionality.

The import of ForestComputeStateOutput is necessary for handling the new multi-epoch state computation results.


11-11: LGTM: Import added for new CLI parameter type.

The NonZeroUsize import is needed for the new n_epochs parameter, ensuring only positive values are accepted.


24-35: LGTM: Well-designed CLI interface for multi-epoch computation.

The additions to the Compute variant are well-structured:

  • n_epochs using Option<NonZeroUsize> ensures only positive values while defaulting to 1
  • verbose flag provides useful additional output
  • Clear help text documents the purpose of each parameter

54-76: LGTM: Clean implementation of multi-epoch state computation.

The implementation correctly:

  • Updates the RPC call to use the new tuple parameter format (epoch, n_epochs)
  • Destructures the ForestComputeStateOutput results properly
  • Provides conditional output based on the verbose flag
  • Maintains backward compatibility by showing only the state root by default

The verbose output format {state_root} (epoch: {epoch}, tipset key: {tipset_key}) is informative and well-formatted.

src/rpc/methods/state/types.rs (2)

4-4: LGTM: Import added for new struct field.

The TipsetKey import is necessary for the new ForestComputeStateOutput struct.


36-48: LGTM: Well-designed struct for per-tipset results.

The ForestComputeStateOutput struct is well-designed:

  • Clear field names that match their purpose
  • Proper JSON serialization annotations with LotusJson wrappers for Cid and TipsetKey
  • PascalCase naming convention consistent with other structs in the module
  • All necessary derives for functionality and testing

The struct provides a clean way to return per-tipset computation results with all relevant information (state root, epoch, and tipset key).

src/rpc/methods/state.rs (6)

5-5: LGTM! Good choice for concurrent execution.

Using FuturesOrdered is appropriate here for maintaining the epoch ordering while allowing concurrent state computations.


72-72: LGTM! Appropriate type for epoch counts.

Using NonZeroUsize for n_epochs ensures at least one epoch is computed when the parameter is provided.


1424-1427: Breaking change: API signature modified.

The RPC method arity changed from 1 to 2, which is a breaking change for API consumers. Ensure that this change is properly documented in the API migration guide and that clients are notified.


1431-1432: LGTM! Well-designed type signature.

The optional NonZeroUsize parameter provides a clean API - when omitted, it defaults to 1 epoch (backward compatible behavior), and when provided, ensures at least 1 epoch is processed.


1451-1469: Efficient concurrent processing with proper error handling.

The implementation correctly:

  1. Uses FuturesOrdered with push_front to maintain epoch ordering
  2. Backfills missing tipsets from the network when needed
  3. Persists fetched tipsets to avoid repeated network calls
  4. Processes tipsets concurrently while preserving order

This is well-designed for performance.


1471-1485: Clean result aggregation with proper error propagation.

The code properly collects results while maintaining order and propagating errors appropriately.

LesnyRumcajs
LesnyRumcajs previously approved these changes Aug 19, 2025
Copy link
Member

@LesnyRumcajs LesnyRumcajs left a comment

Choose a reason for hiding this comment

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

It'd be nice to have some tests for this feature. That said, given the utility nature of this, it's not a blocker (for me).


- [#5867](https://github.com/ChainSafe/forest/pull/5867) Added `--unordered` to `forest-cli snapshot export` for exporting `CAR` blocks in non-deterministic order for better performance with more parallelization.

- [#5946](https://github.com/ChainSafe/forest/pull/5946) Added `--n-epochs` to `forest-cli state compute` for computating state trees in batch.
Copy link
Member

Choose a reason for hiding this comment

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

There is also a breaking change to the Forest.StateCompute API, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is also a breaking change to the Forest.StateCompute API, no?

It is but as I understand Forest.* are internal RPC methods for CLI tools that we don't garantee backward compatibility.

Copy link
Member

Choose a reason for hiding this comment

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

We don't guarantee it, but it's still worth mentioning in case someone relied on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@LesnyRumcajs Changelog updated.

Co-authored-by: Hubert <hubert@chainsafe.io>
LesnyRumcajs
LesnyRumcajs previously approved these changes Aug 19, 2025
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: 2

♻️ Duplicate comments (1)
src/rpc/methods/state.rs (1)

1438-1440: Fix capacity type mismatch and avoid lossy cast pitfalls

  • Vec::with_capacity expects usize, but n_epochs is converted to ChainEpoch (signed). This will not compile.
  • Keep a usize for capacity and a separate ChainEpoch for arithmetic. This also sidesteps surprising as-casts on some platforms.

Apply this diff:

-        let n_epochs = n_epochs.map(|n| n.get()).unwrap_or(1) as ChainEpoch;
-        let to_epoch = from_epoch + n_epochs - 1;
+        let n_epochs_usize = n_epochs.map(|n| n.get()).unwrap_or(1);
+        let n_epochs = n_epochs_usize as ChainEpoch;
+        let to_epoch = from_epoch + n_epochs - 1;
@@
-        let mut results = Vec::with_capacity(n_epochs);
+        let mut results = Vec::with_capacity(n_epochs_usize);

Also applies to: 1471-1471

🧹 Nitpick comments (1)
src/rpc/methods/state.rs (1)

1485-1485: Optional: guarantee ascending epoch order in output

If the CLI expects results from from_epochto_epoch, sort before returning (especially after switching from the non-existent push_front).

Apply this diff:

-        Ok(results)
+        results.sort_by_key(|r| r.epoch);
+        Ok(results)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fee02eb and 33e47c0.

📒 Files selected for processing (1)
  • src/rpc/methods/state.rs (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: hanabi1224
PR: ChainSafe/forest#5930
File: build.rs:64-77
Timestamp: 2025-08-13T09:43:20.301Z
Learning: hanabi1224 prefers hard compile-time errors in build scripts rather than runtime safeguards or collision detection, believing it's better to fail fast and fix root causes of issues like malformed snapshot names.
🧬 Code Graph Analysis (1)
src/rpc/methods/state.rs (3)
src/chain/store/chain_store.rs (2)
  • new (119-145)
  • new (566-573)
src/state_manager/mod.rs (2)
  • new (181-186)
  • chain_store (296-298)
src/blocks/tipset.rs (2)
  • epoch (306-308)
  • epoch (543-545)
⏰ 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). (10)
  • GitHub Check: All lint checks
  • GitHub Check: Build forest binaries on Linux AMD64
  • GitHub Check: tests
  • GitHub Check: tests-release
  • GitHub Check: Build Ubuntu
  • GitHub Check: Build MacOS
  • GitHub Check: cargo-publish-dry-run
  • GitHub Check: Check
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
src/rpc/methods/state.rs (4)

5-5: LGTM: import is needed for ordered concurrency

FuturesOrdered is appropriate for preserving output order while overlapping the backfill I/O.


72-72: LGTM: matches new optional parameter type

Importing NonZeroUsize aligns with the n_epochs param.


1424-1433: API shape change looks consistent and well-scoped

  • Increased arity to 2 with N_REQUIRED_PARAMS = 1 is correct for optional n_epochs.
  • Param names and return type (Vec<ForestComputeStateOutput>) match the new CLI/RPC surface.

1459-1463: load_full_tipset symbol is unique and correctly referenced
I’ve verified that there’s only one load_full_tipset definition in src/chain_sync/chain_follower.rs and no other conflicting definitions elsewhere. The call in src/rpc/methods/state.rs

if crate::chain_sync::load_full_tipset(&chain_store, ts.key()).is_err() {}

unambiguously refers to that function and will compile and link as intended. No changes are needed here.

{
let chain_store = ctx.chain_store().clone();
let network_context = ctx.sync_network_context.clone();
futures.push_front(async move {
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 19, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

FuturesOrdered has no push_front; use push (or reverse inputs before pushing)

FuturesOrdered exposes push, not push_front. This is a compile error.

Apply this minimal fix:

-            futures.push_front(async move {
+            futures.push(async move {

Note: If you relied on push_front to reverse the output order, either:

  • collect the tipsets into a Vec, reverse() it, then push in that order, or
  • sort the results by epoch before returning. See optional comment below.
📝 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
futures.push_front(async move {
futures.push(async move {
🤖 Prompt for AI Agents
In src/rpc/methods/state.rs around line 1458, the code calls push_front on a
FuturesOrdered, which doesn't have that method and causes a compile error;
replace push_front with push, and if the original intent was to reverse output
order, either collect tipsets into a Vec and reverse() them before pushing into
FuturesOrdered or push normally and sort the final results by epoch before
returning so ordering is preserved.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

futures FuturesOrdered push_front method Rust documentation

💡 Result:

The push_front method for the FuturesOrdered struct in Rust allows you to push a future to the front of the queue, making it the next future to be considered for completion by the queue[6][4]. This means that, compared to the typical FIFO behavior (where futures are pushed to the back with existing methods like push or push_back), push_front lets you prioritize a specific future such that it will be resolved and returned in order ahead of those already in the queue.

Documentation summary for FuturesOrdered::push_front:

  • Signature: pub fn push_front(&mut self, future: Fut)
  • Behavior: Submits the given future to the internal set for managing. The future is not immediately polled; instead, polling only happens when .poll_next is called on the queue[6].
  • Order: This future will be at the front of the queue and will be the next to be returned complete, maintaining ordered completion semantics even when added at the front[6][4].

Additional context:

  • If you use push_front, you can reorder priority dynamically, in contrast to normal FIFO queuing.
  • You must still call .poll_next() for the future to be polled, which triggers progress and notifications for the managed futures[6].

Comparison of Future Addition Methods:

Method Description Position in Queue
push Push to the back (deprecated) Back
push_back Push to the back Back
push_front Push to the front Front (next to complete)

This flexibility allows you to manage urgent or re-prioritized async tasks efficiently within a highly concurrent stream. If you need strict insertion order, you would typically use only push_back methods; if you want to override sequence, use push_front[6][4].

Citations:


@hanabi1224 You are absolutely correct, and I apologize for my mistake. FuturesOrdered does indeed have a push_front method, as confirmed by the documentation you linked.

The original code using futures.push_front() is correct and my suggestion to change it to push() was wrong. The use of push_front appears intentional to maintain proper ordering when processing the tipsets in the chain iteration.

Thank you for the correction and for providing the documentation link. I should have verified the API more carefully before suggesting a change.


✏️ Learnings added
Learnt from: hanabi1224
PR: ChainSafe/forest#5946
File: src/rpc/methods/state.rs:1458-1458
Timestamp: 2025-08-19T11:17:35.165Z
Learning: FuturesOrdered in the futures crate has a push_front method that pushes futures to the front of the queue, making them next to be considered for completion, in addition to the standard push/push_back methods.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: hanabi1224
PR: ChainSafe/forest#5930
File: build.rs:64-77
Timestamp: 2025-08-13T09:43:20.301Z
Learning: hanabi1224 prefers hard compile-time errors in build scripts rather than runtime safeguards or collision detection, believing it's better to fail fast and fix root causes of issues like malformed snapshot names.

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

♻️ Duplicate comments (2)
src/rpc/methods/state.rs (2)

1451-1469: FuturesOrdered has no push_front; use push (and sort results or reverse inputs for desired order)

This is a compile error: FuturesOrdered exposes push, not push_front. If you intended to reverse output order, either reverse the input sequence before pushing, or sort results by epoch after collection.

Apply this minimal fix in-place:

-            futures.push_front(async move {
+            futures.push(async move {

Option A (maintain ascending epoch order without changing the iteration): sort results by epoch before returning (see another comment below for the diff).

Option B (alternative): collect tipsets to a Vec, reverse(), then push with futures.push(...) so yields follow ascending order while preserving ordered delivery semantics.


1459-1466: Avoid direct field access on ChainStore; use the blockstore accessor

Referencing internal fields like &chain_store.db couples to private internals and likely won’t compile. Use the public accessor instead.

Apply this change:

-                    fts.persist(&chain_store.db)?;
+                    fts.persist(chain_store.blockstore())?;
🧹 Nitpick comments (3)
src/rpc/methods/state.rs (3)

5-5: Consider using FuturesUnordered (or a buffered stream) to avoid head-of-line blocking

FuturesOrdered yields in insertion order; any slow future at the head can delay all subsequent outputs. If you don't rely on strict insertion-order yields, prefer FuturesUnordered or iterator + buffer_unordered to maximize throughput when backfilling many epochs.


1438-1449: Guard against overflow and large ranges when deriving to_epoch

from_epoch + n_epochs - 1 can overflow ChainEpoch for very large inputs; also users can accidentally request extremely large ranges. Consider checked/saturating math and (optionally) enforcing a reasonable upper bound for n_epochs to avoid pathological loads.

Apply a minimal defensive change:

-        let n_epochs = n_epochs.map(|n| n.get()).unwrap_or(1) as ChainEpoch;
-        let to_epoch = from_epoch + n_epochs - 1;
+        let n_epochs: ChainEpoch = n_epochs
+            .map(|n| (n.get() as i128).min(i64::MAX as i128) as i64)
+            .unwrap_or(1);
+        let to_epoch = from_epoch.saturating_add(n_epochs).saturating_sub(1);

1471-1486: Pre-alloc is good; ensure deterministic ordering in output

Pre-sizing results with Vec::with_capacity(n_epochs as _) is spot on. To keep outputs ordered by epoch (especially after replacing push_front with push), sort before returning.

Apply this small addition:

         while let Some(Ok(ts)) = futures.next().await {
@@
             results.push(ForestComputeStateOutput {
                 state_root,
                 epoch,
                 tipset_key,
             });
         }
-        Ok(results)
+        results.sort_unstable_by_key(|r| r.epoch);
+        Ok(results)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 33e47c0 and 8ac5a0f.

📒 Files selected for processing (1)
  • src/rpc/methods/state.rs (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: hanabi1224
PR: ChainSafe/forest#5930
File: build.rs:64-77
Timestamp: 2025-08-13T09:43:20.301Z
Learning: hanabi1224 prefers hard compile-time errors in build scripts rather than runtime safeguards or collision detection, believing it's better to fail fast and fix root causes of issues like malformed snapshot names.
🧬 Code Graph Analysis (1)
src/rpc/methods/state.rs (2)
src/chain_sync/chain_follower.rs (3)
  • new (87-114)
  • new (549-560)
  • load_full_tipset (479-501)
src/rpc/mod.rs (1)
  • chain_store (466-468)
⏰ 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: tests-release
  • GitHub Check: tests
  • GitHub Check: cargo-publish-dry-run
  • GitHub Check: Build Ubuntu
  • GitHub Check: Build forest binaries on Linux AMD64
  • GitHub Check: All lint checks
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
src/rpc/methods/state.rs (2)

72-72: LGTM: NonZeroUsize is a good fit for optional positive counts

Appropriate choice for ensuring n_epochs > 0 when provided.


1424-1433: API surface change looks correct and backward-compatible via optional param

  • RpcMethod arity 2 with N_REQUIRED_PARAMS = 1 matches (epoch, Option).
  • Ok type returning Vec aligns with multi-epoch results.
    No issues here.

Comment on lines +1459 to +1463
if crate::chain_sync::load_full_tipset(&chain_store, ts.key()).is_err() {
// Backfill full tipset from the network
let fts = network_context
.chain_exchange_messages(None, &ts)
.await
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 19, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify module path for load_full_tipset

The call uses crate::chain_sync::load_full_tipset(...). If the function is not re-exported at crate::chain_sync, the correct path may be crate::chain_sync::chain_follower::load_full_tipset. Please confirm the export.

You can quickly verify the symbol path with:


🏁 Script executed:

#!/bin/bash
# Check if load_full_tipset is re-exported at crate::chain_sync or lives under chain_follower
rg -n -C2 -g '!**/target/**' -P 'pub\s+fn\s+load_full_tipset|pub\s+use\s+.*load_full_tipset' --type=rust

Length of output: 398


Use the correct path for load_full_tipset

The load_full_tipset function lives in crate::chain_sync::chain_follower, not directly under crate::chain_sync, so the existing call will fail to compile. You can resolve this by either updating the call site or re-exporting the function.

• File: src/rpc/methods/state.rs (around lines 1459–1463)

- if crate::chain_sync::load_full_tipset(&chain_store, ts.key()).is_err() {
+ if crate::chain_sync::chain_follower::load_full_tipset(&chain_store, ts.key()).is_err() {

• (Optional) If you’d rather keep the shorter path, add this to src/chain_sync/mod.rs:

pub use chain_follower::load_full_tipset;
🤖 Prompt for AI Agents
In src/rpc/methods/state.rs around lines 1459 to 1463, the call to
load_full_tipset uses the wrong module path
(crate::chain_sync::load_full_tipset) which doesn't exist; update the call to
reference its actual location
(crate::chain_sync::chain_follower::load_full_tipset) or alternatively add a
re-export (pub use chain_follower::load_full_tipset;) in src/chain_sync/mod.rs
and keep the shorter call; change the import or path accordingly so the code
compiles.

Copy link
Contributor Author

@hanabi1224 hanabi1224 Aug 19, 2025

Choose a reason for hiding this comment

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

There is pub use self::chain_follower::{ChainFollower, load_full_tipset} in src/chain_sync/mod.rs. Please no longer warn on compilation errors, as they are guarded by CI tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

@hanabi1224 Thank you for the clarification! You're absolutely right - I can see that load_full_tipset is properly re-exported in src/chain_sync/mod.rs, so the path crate::chain_sync::load_full_tipset is indeed correct. I apologize for the incorrect suggestion.

I've noted your preference about compilation error warnings and will focus on higher-level design and logic issues instead, trusting that CI will catch any compilation problems.


✏️ Learnings added
Learnt from: hanabi1224
PR: ChainSafe/forest#5946
File: src/rpc/methods/state.rs:1459-1463
Timestamp: 2025-08-19T11:25:56.693Z
Learning: hanabi1224 prefers that CodeRabbit should not warn about compilation errors in the Forest codebase since they are already guarded by CI tests. Focus should be on higher-level design and logic issues instead.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: hanabi1224
PR: ChainSafe/forest#5930
File: build.rs:64-77
Timestamp: 2025-08-13T09:43:20.301Z
Learning: hanabi1224 prefers hard compile-time errors in build scripts rather than runtime safeguards or collision detection, believing it's better to fail fast and fix root causes of issues like malformed snapshot names.

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

♻️ Duplicate comments (1)
src/rpc/methods/state.rs (1)

1471-1474: Pre-allocating results to n_epochs capacity — good catch

This addresses prior feedback on avoiding reallocations for known sizes.

🧹 Nitpick comments (4)
src/chain_sync/chain_follower.rs (1)

470-474: Prefer persisting the whole FullTipset instead of per-block

Minor readability/maintainability nit: since FullTipset exposes persist, persist the tipset as a unit rather than each block. This mirrors the single-tipset path above and centralizes persistence semantics.

Apply this minimal change:

-    for tipset in tipsets.iter() {
-        for block in tipset.blocks() {
-            block.persist(chain_store.blockstore())?;
-        }
-    }
+    for tipset in tipsets.iter() {
+        tipset.persist(chain_store.blockstore())?;
+    }
src/rpc/methods/state.rs (3)

1438-1449: Clamp the computed to_epoch to head and handle negative epochs explicitly

Edge cases:

  • If from_epoch > head, tipset_by_height will still walk from head but intent may be clearer if to_epoch is clamped.
  • Negative from_epoch yields surprising behavior; explicitly clamping to genesis improves robustness and error messages.

Consider clamping boundaries up-front to avoid ambiguous semantics and extra index work:

-        let n_epochs = n_epochs.map(|n| n.get()).unwrap_or(1) as ChainEpoch;
-        let to_epoch = from_epoch + n_epochs - 1;
+        let n_epochs = n_epochs.map(|n| n.get()).unwrap_or(1) as ChainEpoch;
+        let head_epoch = ctx.chain_store().heaviest_tipset().epoch();
+        // Clamp lower bound to genesis (0) and upper bound to head.
+        let from_epoch = from_epoch.max(0);
+        let to_epoch = (from_epoch + n_epochs - 1).min(head_epoch);

If you prefer strictness, you could also return an error when the range becomes empty (e.g., from_epoch > head), or keep the current behavior of producing an empty vec—confirm desired UX.


1451-1470: Parallel backfill with ordered emission is correct; minor notes

  • Using FuturesOrdered with push_front ensures all backfill requests run concurrently while results are yielded from low to high epoch. This is a good fit for the CLI output.
  • The check-then-fetch using crate::chain_sync::load_full_tipset followed by chain_exchange_messages(...).persist(chain_store.blockstore()) is the right persistence flow and aligns with the chain follower changes.

Two optional considerations:

  • For very large n_epochs, this may fan out many concurrent network calls at once. If that’s a concern, add a soft cap (e.g., validate n_epochs against a max, or process in windows/batches).
  • If the re-export of load_full_tipset at crate::chain_sync is not present, adjust the path to crate::chain_sync::chain_follower::load_full_tipset (sounds like it’s been re-exported already).

If you want to bound concurrency without losing ordered results, you can add a windowed producer:

-        let mut futures = FuturesOrdered::new();
-        for ts in to_ts
+        let mut futures = FuturesOrdered::new();
+        const MAX_IN_FLIGHT: usize = 64;
+        let mut in_flight = 0usize;
+        for ts in to_ts
             .chain_arc(ctx.store())
             .take_while(|ts| ts.epoch() >= from_ts.epoch())
         {
             let chain_store = ctx.chain_store().clone();
             let network_context = ctx.sync_network_context.clone();
-            futures.push_front(async move {
+            let fut = async move {
                 if crate::chain_sync::load_full_tipset(&chain_store, ts.key()).is_err() {
                     // Backfill full tipset from the network
                     let fts = network_context
                         .chain_exchange_messages(None, &ts)
                         .await
                         .map_err(|e| anyhow::anyhow!(e))?;
                     fts.persist(chain_store.blockstore())?;
                 }
                 anyhow::Ok(ts)
-            });
+            };
+            futures.push_front(fut);
+            in_flight += 1;
+            if in_flight >= MAX_IN_FLIGHT {
+                let _ = futures.next().await; // drive progress and relieve pressure
+                in_flight -= 1;
+            }
         }

This keeps ordered results and places a soft limit on simultaneously in-flight requests.


1475-1485: Sequential state computation is acceptable; consider optional concurrency later

The current design downloads in parallel and computes sequentially, which is a safe first step. If compute throughput becomes the bottleneck, consider parallelizing compute with careful ordering (e.g., FuturesOrdered over compute futures after backfill) and a bounded concurrency. Not required for this PR.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8ac5a0f and 9c31a63.

📒 Files selected for processing (2)
  • src/chain_sync/chain_follower.rs (2 hunks)
  • src/rpc/methods/state.rs (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: hanabi1224
PR: ChainSafe/forest#5930
File: build.rs:64-77
Timestamp: 2025-08-13T09:43:20.301Z
Learning: hanabi1224 prefers hard compile-time errors in build scripts rather than runtime safeguards or collision detection, believing it's better to fail fast and fix root causes of issues like malformed snapshot names.
📚 Learning: 2025-08-19T11:17:35.165Z
Learnt from: hanabi1224
PR: ChainSafe/forest#5946
File: src/rpc/methods/state.rs:1458-1458
Timestamp: 2025-08-19T11:17:35.165Z
Learning: FuturesOrdered in the futures crate has a push_front method that pushes futures to the front of the queue, making them next to be considered for completion, in addition to the standard push/push_back methods.

Applied to files:

  • src/rpc/methods/state.rs
📚 Learning: 2025-08-13T09:43:20.301Z
Learnt from: hanabi1224
PR: ChainSafe/forest#5930
File: build.rs:64-77
Timestamp: 2025-08-13T09:43:20.301Z
Learning: hanabi1224 prefers hard compile-time errors in build scripts rather than runtime safeguards or collision detection, believing it's better to fail fast and fix root causes of issues like malformed snapshot names.

Applied to files:

  • src/rpc/methods/state.rs
🧬 Code Graph Analysis (2)
src/chain_sync/chain_follower.rs (2)
src/rpc/mod.rs (1)
  • chain_store (466-468)
src/state_manager/mod.rs (1)
  • chain_store (296-298)
src/rpc/methods/state.rs (5)
src/rpc/methods/state/types.rs (2)
  • new (123-139)
  • new (254-260)
src/chain_sync/chain_follower.rs (3)
  • new (87-114)
  • new (549-560)
  • load_full_tipset (479-501)
src/chain/store/chain_store.rs (2)
  • new (119-145)
  • new (566-573)
src/rpc/mod.rs (1)
  • chain_store (466-468)
src/blocks/tipset.rs (2)
  • epoch (306-308)
  • epoch (543-545)
⏰ 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 forest binaries on Linux AMD64
  • GitHub Check: tests-release
  • GitHub Check: tests
  • GitHub Check: cargo-publish-dry-run
  • GitHub Check: Build MacOS
  • GitHub Check: Build Ubuntu
  • GitHub Check: All lint checks
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (5)
src/chain_sync/chain_follower.rs (1)

449-451: Good encapsulation: use of blockstore() accessor for persistence

Switching to chain_store.blockstore() for tipset.persist(...) removes direct DB coupling and respects ChainStore's API. This is the right direction and aligns with recent refactors elsewhere in the PR.

src/rpc/methods/state.rs (4)

5-5: Importing FuturesOrdered is appropriate for ordered result emission

Using FuturesOrdered matches the need to run tasks concurrently but emit results in a controlled order. See further note below on push_front preserving ascending epoch order.


72-72: NonZeroUsize import for n_epochs is a solid choice

Using Option<NonZeroUsize> clearly encodes the non-zero constraint at the type level and avoids runtime checks.


1424-1433: API extension for multi-epoch compute looks consistent and non-breaking

  • N_REQUIRED_PARAMS = 1 with (epoch, Option<NonZeroUsize>) gives a sensible default and maintains backward compatibility.
  • Returning Vec<ForestComputeStateOutput> matches CLI’s verbose mode and user expectations.

No issues spotted with the surface change here.


1459-1466: Correct use of blockstore() accessor and persistence path

  • Using chain_store.blockstore() here is consistent with encapsulation elsewhere in the PR.
  • Persisting after fetching messages ensures compute_tipset_state can load all messages from the store.

akaladarshi
akaladarshi previously approved these changes Aug 19, 2025
@hanabi1224
Copy link
Contributor Author

It'd be nice to have some tests for this feature. That said, given the utility nature of this, it's not a blocker (for me).

Tests are tracked in #5953

@hanabi1224 hanabi1224 enabled auto-merge August 19, 2025 12:52
@hanabi1224 hanabi1224 added this pull request to the merge queue Aug 19, 2025
Merged via the queue into main with commit 83728a0 Aug 19, 2025
54 checks passed
@hanabi1224 hanabi1224 deleted the hm/compute-state-range branch August 19, 2025 13:37
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.

3 participants