fix: error computing state for a null epoch#6018
Conversation
WalkthroughAdjusts ForestStateCompute’s range selection: when from_epoch is at or beyond to_ts.epoch(), it clamps from_ts to to_ts; otherwise resolves from_ts via tipset_by_height with TakeOlder. Iteration and computation logic remain unchanged. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant RPC as StateCompute
participant CS as ChainStore
C->>RPC: state_compute(from_epoch, to_ts, n_epochs)
alt from_epoch >= to_ts.epoch()
note over RPC: Clamp start\nfrom_ts = to_ts
else
RPC->>CS: tipset_by_height(from_epoch, to_ts, TakeOlder)
CS-->>RPC: from_ts
end
loop iterate epochs backward
RPC->>CS: load tipset / state for epoch
CS-->>RPC: tipset/state
RPC->>RPC: compute state
end
RPC-->>C: results
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/rpc/methods/state.rs (2)
1441-1451: Simplify by clamping the height and using a single tipset_by_height call.Functionally equivalent but a bit simpler and keeps all paths going through the same API.
- let from_ts = if from_epoch >= to_ts.epoch() { - // When `from_epoch` is a null epoch or `n_epochs` is 1, - // `to_ts.epoch()` could be less than or equal to `from_epoch` - to_ts.clone() - } else { - ctx.chain_index().tipset_by_height( - from_epoch, - to_ts.clone(), - ResolveNullTipset::TakeOlder, - )? - }; + // Clamp start height to the anchor tipset's epoch to avoid requesting above the anchor. + let from_epoch_clamped = std::cmp::min(from_epoch, to_ts.epoch()); + let from_ts = ctx.chain_index().tipset_by_height( + from_epoch_clamped, + to_ts.clone(), + ResolveNullTipset::TakeOlder, + )?;
1435-1435: Nit: use saturating_add to be defensive.Extremely unlikely to matter in practice, but avoids theoretical overflow on pathological inputs.
- let to_epoch = from_epoch + n_epochs - 1; + let to_epoch = from_epoch.saturating_add(n_epochs - 1);
📜 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.
📒 Files selected for processing (1)
src/rpc/methods/state.rs(1 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-25T13:35:24.230Z
Learnt from: hanabi1224
PR: ChainSafe/forest#5969
File: src/tool/subcommands/snapshot_cmd.rs:412-412
Timestamp: 2025-08-25T13:35:24.230Z
Learning: In src/tool/subcommands/snapshot_cmd.rs, the +1 in `last_epoch = ts.epoch() - epochs as i64 + 1` fixes an off-by-1 bug where specifying --check-stateroots=N would validate N+1 epochs instead of N epochs, causing out-of-bounds errors when the snapshot contains only N recent state roots.
Applied to files:
src/rpc/methods/state.rs
📚 Learning: 2025-08-18T03:09:47.932Z
Learnt from: hanabi1224
PR: ChainSafe/forest#5944
File: src/chain/store/index.rs:0-0
Timestamp: 2025-08-18T03:09:47.932Z
Learning: In Forest's tipset_by_height caching implementation, hanabi1224 prefers performance-conscious solutions that leverage finality guarantees rather than expensive chain walking for fork detection. The approach of constraining cache lookups to finalized epochs (using CHECKPOINT_INTERVAL >= CHAIN_FINALITY) provides fork safety without the performance cost of ancestry verification.
Applied to files:
src/rpc/methods/state.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). (20)
- GitHub Check: Calibnet kademlia checks
- GitHub Check: Snapshot unordered export checks
- GitHub Check: Bootstrap checks - Lotus
- GitHub Check: Snapshot export checks v2 with F3 data
- GitHub Check: Bootstrap checks - Forest
- GitHub Check: db-migration-checks
- GitHub Check: Calibnet api test-stateful check
- GitHub Check: Snapshot export checks
- GitHub Check: State migrations
- GitHub Check: Wallet tests
- GitHub Check: Calibnet eth mapping check
- GitHub Check: Devnet checks
- GitHub Check: Calibnet no discovery checks
- GitHub Check: Calibnet stateless RPC check
- GitHub Check: Calibnet stateless mode check
- GitHub Check: Calibnet check
- GitHub Check: Forest CLI checks
- GitHub Check: All lint checks
- GitHub Check: Analyze (rust)
- GitHub Check: Analyze (go)
🔇 Additional comments (1)
src/rpc/methods/state.rs (1)
1441-1451: Null-epoch clamp is correct and fixes the edge case.Clamping
from_tstoto_tswhenfrom_epoch >= to_ts.epoch()prevents requesting a tipset above the anchor and handles null-epoch ranges cleanly. LGTM.
Summary of changes
This PR fixes an error in
forest-cli state compute --epoch {EPOCH}when{EPOCH}is a null epoch. It now calculates the state root of its closest non-null ancestor tipset.Changes introduced in this pull request:
Reference issue to close (if applicable)
Closes
Other information and links
Change checklist
Summary by CodeRabbit