fix: refine Arc params to reduce pointer cloning#6255
Conversation
WalkthroughA comprehensive refactoring systematically replacing owned Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~40 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/state_manager/circulating_supply.rs (1)
151-159: Simplifying market::State::load argument removes an unnecessary referencePassing
db(a&Arc<DB>) instead of&dbavoids a&&Arc<DB>and better matches how similar loaders are typically called, with no behavioral change.You might also consider updating the adjacent
miner::State::load(&db, ..)andmultisig::State::load(&db, ..)calls to usedbdirectly for consistency, though this is purely cosmetic.src/chain_sync/tipset_syncer.rs (1)
225-331: Async/blocking validation split is sensible; consider grouping state-dependent tasks if contention shows upMoving base-fee and parent-weight checks into
spawn_blockingwith ownedArcblockstore, and keeping state-root/receipt-root and consensus checks in async tasks, is a reasonable separation: CPU/blockstore-heavy, synchronous code runs off the core runtime, and async pieces reuse the existingtipset_statepipeline. Everything captured into the tasks (Arc<StateManager<_>>,Arc<Block>,Arc<Tipset>,work_addr) isSend + 'static, soJoinSetusage remains sound.If you ever see excessive contention in these per-block validations, you could explore reusing a single cloned blockstore for both blocking closures or batching the "parent-dependent" checks (base fee, weight, state/receipt roots) into one blocking job to reduce task orchestration overhead, but that’s an optional micro-optimization.
src/state_manager/mod.rs (2)
470-508: Nice early-return viatry_lookup_state_from_next_tipsetIntegrating
self.try_lookup_state_from_next_tipset(tipset)intotipset_state_outputgives a cheap, blockstore-backed fast path before kicking off a full VM state computation. The method call via&Arc<Self>auto-deref is fine, and the logic still populates caches and indexes only when the state actually has to be computed.If this optimization proves valuable, you might consider instrumenting a metric (hit/miss rate) here to validate its impact in production, but that’s optional and can be added later.
1658-1681: Borrowed-tipsetresolve_to_deterministic_addresswiring is soundChanging
resolve_to_deterministic_addressto takets: &Arc<Tipset>and updatingminer_get_base_infoto call it with&tipsetkeeps all uses onArc<Tipset>while avoiding extra cloning. The fast path (parent-stateStateTree) and slow path (tipset_state(ts)then building a newStateTree) still implement the same resolution semantics.You could consider sharing the deterministic-address resolution logic with
resolve_to_key_addrto reduce duplication, but that’s a style refactor, not required.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
src/chain/store/chain_store.rs(3 hunks)src/chain_sync/chain_follower.rs(1 hunks)src/chain_sync/sync_status.rs(1 hunks)src/chain_sync/tipset_syncer.rs(4 hunks)src/fil_cns/validation.rs(2 hunks)src/interpreter/fvm2.rs(1 hunks)src/interpreter/fvm3.rs(1 hunks)src/interpreter/fvm4.rs(1 hunks)src/libp2p/service.rs(2 hunks)src/rpc/methods/eth/filter/mod.rs(1 hunks)src/rpc/methods/f3.rs(1 hunks)src/rpc/methods/gas.rs(1 hunks)src/rpc/methods/miner.rs(1 hunks)src/rpc/methods/state.rs(2 hunks)src/rpc/methods/wallet.rs(1 hunks)src/rpc/mod.rs(1 hunks)src/state_manager/circulating_supply.rs(1 hunks)src/state_manager/mod.rs(13 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: hanabi1224
Repo: ChainSafe/forest PR: 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.
Learnt from: hanabi1224
Repo: ChainSafe/forest PR: 6057
File: src/cli/subcommands/f3_cmd.rs:0-0
Timestamp: 2025-09-09T10:37:17.947Z
Learning: hanabi1224 prefers having default timeouts (like 10m for --no-progress-timeout) to prevent commands from hanging indefinitely, even when the timeout flag isn't explicitly provided by users. This fail-fast approach is preferred over requiring explicit flag usage.
📚 Learning: 2025-10-17T14:24:47.046Z
Learnt from: hanabi1224
Repo: ChainSafe/forest PR: 6167
File: src/tool/subcommands/state_compute_cmd.rs:89-91
Timestamp: 2025-10-17T14:24:47.046Z
Learning: In `src/tool/subcommands/state_compute_cmd.rs`, when using `ReadOpsTrackingStore` to generate minimal snapshots, `HEAD_KEY` should be written to `db.tracker` (not `db` itself) before calling `export_forest_car()`, because the export reads from the tracker MemoryDB which accumulates only the accessed data during computation.
Applied to files:
src/rpc/mod.rssrc/state_manager/circulating_supply.rssrc/interpreter/fvm4.rssrc/chain/store/chain_store.rs
📚 Learning: 2025-09-02T10:05:34.350Z
Learnt from: akaladarshi
Repo: ChainSafe/forest PR: 5923
File: src/rpc/registry/actors/miner.rs:221-223
Timestamp: 2025-09-02T10:05:34.350Z
Learning: For miner actor ChangeOwnerAddress and ChangeOwnerAddressExported methods: versions 8-10 use bare Address as parameter type, while versions 11+ use ChangeOwnerAddressParams. This reflects the evolution of the Filecoin miner actor parameter structures across versions.
Applied to files:
src/rpc/methods/eth/filter/mod.rssrc/rpc/methods/miner.rs
📚 Learning: 2025-08-18T03:09:47.932Z
Learnt from: hanabi1224
Repo: ChainSafe/forest PR: 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/interpreter/fvm4.rssrc/interpreter/fvm3.rssrc/interpreter/fvm2.rs
📚 Learning: 2025-08-08T12:11:55.266Z
Learnt from: hanabi1224
Repo: ChainSafe/forest PR: 5867
File: src/ipld/util.rs:461-487
Timestamp: 2025-08-08T12:11:55.266Z
Learning: Forest (src/ipld/util.rs, Rust): In UnorderedChainStream::poll_next, dropping `extract_sender` (when no more tipsets and the extract queue is empty) is the intended shutdown signal for workers. Any subsequent attempt to enqueue work after this drop is a logic error and should be treated as an error; do not change `send()` to ignore a missing sender.
Applied to files:
src/interpreter/fvm3.rssrc/interpreter/fvm2.rssrc/chain_sync/chain_follower.rs
🧬 Code graph analysis (8)
src/rpc/methods/eth/filter/mod.rs (1)
src/blocks/chain4u.rs (1)
tipset(183-185)
src/rpc/methods/gas.rs (1)
src/chain/store/chain_store.rs (1)
messages_for_tipset_with_cache(618-632)
src/chain_sync/tipset_syncer.rs (4)
src/chain/store/base_fee.rs (1)
compute_base_fee(59-98)src/fil_cns/mod.rs (1)
weight(91-96)src/fil_cns/weight.rs (1)
fil_cns(23-57)src/state_manager/mod.rs (1)
get_bls_public_key(1265-1282)
src/libp2p/service.rs (1)
src/chain_sync/network_context.rs (1)
peer_manager(134-136)
src/fil_cns/validation.rs (4)
src/state_manager/mod.rs (3)
chain_store(348-350)beacon_schedule(291-293)chain_config(358-360)src/utils/misc/env.rs (1)
is_env_truthy(17-19)src/blocks/block.rs (1)
header(30-32)src/chain/store/chain_store.rs (1)
chain_config(252-254)
src/rpc/methods/f3.rs (1)
src/shim/actors/builtin/miner/mod.rs (1)
worker(896-898)
src/chain/store/chain_store.rs (3)
src/state_manager/mod.rs (4)
chain_index(353-355)chain_config(358-360)heaviest_tipset(208-210)new(180-182)src/interpreter/fvm2.rs (1)
new(44-61)src/shim/state_tree.rs (1)
new_from_tipset(190-192)
src/state_manager/mod.rs (1)
src/shim/actors/builtin/miner/mod.rs (1)
info(84-97)
⏰ 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). (7)
- GitHub Check: Build forest binaries on Linux AMD64
- GitHub Check: tests
- GitHub Check: tests-release
- GitHub Check: All lint checks
- GitHub Check: cargo-publish-dry-run
- GitHub Check: Build MacOS
- GitHub Check: Build Ubuntu
🔇 Additional comments (28)
src/rpc/methods/f3.rs (1)
449-452: Using borrowed tipset here correctly matches the new resolve_to_deterministic_address API
tsis already a&Arc<Tipset>, so passing it directly avoids an unnecessaryArcclone while staying compatible with the updatedresolve_to_deterministic_addresssignature.src/libp2p/service.rs (2)
439-446: Switching handle_network_message to &PeerManager is type‑safe and avoids Arc noiseCallers now pass
&self.peer_manager(anArc<PeerManager>field), which deref‑coerces to&PeerManager, so this removes an extra Arc layer in the signature without changing behavior.
865-870: Borrowing PeerManager in handle_forest_behaviour_event is consistent with new call sitesAccepting
peer_manager: &PeerManageraligns with the updated run loop usage (&self.peer_manager) and keeps the handler free of unnecessary Arc cloning.src/rpc/methods/wallet.rs (1)
250-255: WalletSignMessage now correctly borrows the heaviest tipsetPassing
&tsintoresolve_to_deterministic_addressmatches the updated&Arc<Tipset>signature and removes an unnecessaryArcclone without changing semantics.src/rpc/methods/eth/filter/mod.rs (1)
293-307: Reusing the borrowed tipset in collect_events avoids redundant Arc cloning
tipsetis already a&Arc<Tipset>, andresolve_to_deterministic_addressnow takes&Arc<Tipset>, so passing it directly is correct and more efficient than cloning theArc.src/chain_sync/chain_follower.rs (1)
850-857: validate_tipset now borrows the StateManager Arc instead of taking ownershipPassing
&state_managerintovalidate_tipsetmatches the updated signature and removes an extraArcclone per task, without changing validation behavior.src/interpreter/fvm3.rs (1)
67-75: Borrowing chain_index/chain_config/heaviest_tipset into get_lookback_tipset_for_round is correctUsing
&self.chain_index,&self.chain_config, and&self.heaviest_tipsetmatches the refactoredget_lookback_tipset_for_roundsignature and avoids cloning theseArcs while preserving behavior.src/rpc/methods/gas.rs (1)
115-123: estimate_gas_premium now calls messages_for_tipset_with_cache using borrowed store and cacheSwitching to
data.store()and&data.msgs_in_tipsetaligns with the updatedmessages_for_tipset_with_cachesignature, eliminating extraArcclones without changing gas premium calculation logic.src/chain_sync/tipset_syncer.rs (4)
100-121: Arc borrowing change invalidate_tipsetlooks correctSwitching
state_managerto&Arc<StateManager<DB>>and cloning it only when spawningvalidate_blockkeeps the async tasks'staticwhile avoiding an extra Arc clone at the call site. The ownership and lifetimes still line up, andchainstorecontinues to be used only on the sync path foradd_to_tipset_tracker.
207-215:get_lookback_tipset_for_roundargument wiring is consistentPassing
state_manager.chain_store().chain_index()andstate_manager.chain_config()by reference, plus&base_tipset, matches how the helper is used elsewhere (e.g.,miner_get_base_info). The mapping to a state root handle and its later use inget_miner_work_addrpreserves the previous behavior.
352-370: BLS pubkey lookup now cleanly reuses the shared blockstore ArcUsing
let db = state_manager.blockstore();and then callingStateManager::get_bls_public_key(db, ...)avoids cloning the blockstoreArcat the call site while keeping the helper responsible for owning its ownArcclone. This keeps the interface tidy and aligns with other state-manager helpers.
483-488: Message-root computation correctly switches to borrowed blockstoreCalling
TipsetValidator::compute_msg_root(state_manager.blockstore(), ...)instead of passing an owned clone reduces unnecessaryArcchurn and is consistent with how other helpers now accept&Arc<DB>. No behavior change to the header vs. computed root comparison logic.src/state_manager/mod.rs (6)
440-451:get_all_sectorsreceiver change is consistentSwitching
get_all_sectorsto take&self(instead ofself: &Arc<Self>) matches the rest of the synchronous state-accessors in this impl and doesn’t affect behavior; it still walks throughget_actorandminer::State::loadas before.
582-668:call_raw/callborrowingselfandtipsetis an improvementUsing
&selfand&Arc<Tipset>incall_raw, and havingcallconstruct a localArc<Tipset>and pass&ts, removes unnecessaryArcindirection on the API without changing semantics. The VMExecutionContextstill receives an ownedArc::clone(tipset), so the lifetime and ownership over the heaviest tipset are preserved.
710-748: Circulating-supply call now reuses the shared blockstore handlePassing
self.blockstore()intoget_vm_circulating_supplyincall_with_gasaligns with the broader refactor of using borrowedArc<DB>everywhere. That avoids a redundantArcclone while leaving the VM construction path unchanged.
1550-1585: Range-validation helpers now use&selfcleanly
validate_rangeandvalidate_tipsetsno longer requireself: &Arc<Self>, which makes them simpler to call from both Arc-managed and direct contexts. They still pass clonedArchandles (chain index, chain config, beacon, engine) into the freevalidate_tipsetsfunction, so the actual validation behavior is untouched.
1588-1622: Search helpers now consistently borrowself
search_for_messageusing&selfand keeping the search logic incheck_search/search_back_for_messageunchanged is a straightforward API cleanup. The interplay between “from” tipset, heaviest tipset, and look-back semantics remains the same.
1265-1282:get_bls_public_keysignature matches new call sitesTaking
db: &Arc<DB>lets callers passstate_manager.blockstore()directly (as incheck_block_messages), while the helper remains responsible for cloning theArcforStateTree::new_from_root. Internally it still resolves to a key address and enforces the “must be BLS” invariant, so behavior is preserved with slightly cheaper call plumbing.src/interpreter/fvm4.rs (1)
67-75: BorrowingArcparameters here is correct and avoids extra clonesPassing
&self.chain_index,&self.chain_config, and&self.heaviest_tipsetmatches the updatedChainStore::get_lookback_tipset_for_roundsignature without changing behavior.src/interpreter/fvm2.rs (1)
63-71: Consistent Arc borrowing in lookback callUsing
&self.chain_index,&self.chain_config, and&self.heaviest_tipsetaligns with the newget_lookback_tipset_for_roundAPI and removes unnecessaryArccloning.src/rpc/mod.rs (1)
486-488: Expose the underlyingArc<DB>fromRPCState::storeReturning
&Arc<DB>directly fromchain_store().blockstore()is consistent withChainStore’s API and avoids extra indirection; existing callers can still treat it as aBlockstorevia deref.src/chain_sync/sync_status.rs (1)
141-185: Accepting&StateManager<DB>instead of&Arc<StateManager<DB>>is a safe simplificationThe method body only needs a shared reference, so dropping the
Arcfrom the signature reduces coupling and avoids needlessArccloning at call sites while preserving behavior.src/rpc/methods/miner.rs (1)
115-132: Miner lookback state now uses borrowedArcinputs correctlyPassing
ctx.chain_index(),ctx.chain_config(), and&parent_tipsetintoChainStore::get_lookback_tipset_for_round, then wrapping the returned state root inArckeeps the original semantics while avoiding redundantArcclones.src/fil_cns/validation.rs (1)
71-176: Async validation refactor correctly switches to borrowed/ArcinputsThe updated
get_lookback_tipset_for_roundcall and thespawn_blocking/async closures now:
- Move/cloned
Arcstate (state_manager,base_tipset,block,prev_beacon,lookback_state) into the tasks, and- Pass shared references (
&StateManager<_>,&Tipset,&Cid,&BeaconEntry,&Address) into the validation helpers.This removes unnecessary
Arccloning while keeping lifetimes and concurrency semantics sound.src/rpc/methods/state.rs (2)
84-95:StateCall::runnow correctly abstracts overStateManagerwithout exposingArcTaking
state_manager: &StateManager<DB>and calling it via&ctx.state_managerkeeps the call behavior identical while hidingArcfrom the API surface and avoiding redundant clones.
196-205: Pass tipset by reference intoresolve_to_deterministic_addressSwitching to
resolve_to_deterministic_address(address, &ts)avoids moving/cloning theArc<Tipset>and matches an interface that works on a borrowed tipset, without changing the call’s semantics.src/chain/store/chain_store.rs (2)
321-381: Refactored lookback helper cleanly borrowsArcinputs
get_lookback_tipset_for_roundnow:
- Accepts
&Arc<ChainIndex<_>>,&Arc<ChainConfig>, and&Arc<Tipset>,- Clones those
Arcs only where needed (apply_block_messages, return value).This preserves the prior logic while avoiding unnecessary
Arcmoves.
617-632:messages_for_tipsetnever populatesapplied/balances, so it always yields no messagesThe Arc-borrowing changes here look correct, but there is a pre‑existing logic bug in
messages_for_tipset:
appliedandbalancesstart empty.- They are only initialized when
applied.contains_key(from_address)istrue:if applied.contains_key(from_address) { let actor_state = state .get_actor(from_address)? .ok_or_else(|| Error::Other("Actor state not found".to_string()))?; applied.insert(*from_address, actor_state.sequence); balances.insert(*from_address, actor_state.balance.clone().into()); }- Because nothing is ever inserted before this check, it never runs for a new sender;
applied.get_mut(from_address)then returnsNone, and the message is skipped.- As a result,
messages_for_tipset(and anything usingmessages_for_tipset_with_cache) will always return an emptyVec<ChainMessage>for a fresh tipset.This contradicts the intended Lotus‑style behavior of initializing sender state on first encounter and then walking sequential nonces.
Flip the condition so initialization happens for first‑seen senders:
- if applied.contains_key(from_address) { + if !applied.contains_key(from_address) { let actor_state = state .get_actor(from_address)? .ok_or_else(|| Error::Other("Actor state not found".to_string()))?; applied.insert(*from_address, actor_state.sequence); balances.insert(*from_address, actor_state.balance.clone().into()); }With this change, the rest of the nonce/balance filtering logic becomes effective and
messages_for_tipsetwill actually surface valid messages as intended.Also applies to: 637-692
⛔ Skipped due to learnings
Learnt from: hanabi1224 Repo: ChainSafe/forest PR: 5867 File: src/ipld/util.rs:461-487 Timestamp: 2025-08-08T12:11:55.266Z Learning: Forest (src/ipld/util.rs, Rust): In UnorderedChainStream::poll_next, dropping `extract_sender` (when no more tipsets and the extract queue is empty) is the intended shutdown signal for workers. Any subsequent attempt to enqueue work after this drop is a logic error and should be treated as an error; do not change `send()` to ignore a missing sender.Learnt from: hanabi1224 Repo: ChainSafe/forest PR: 6167 File: src/tool/subcommands/state_compute_cmd.rs:89-91 Timestamp: 2025-10-17T14:24:47.046Z Learning: In `src/tool/subcommands/state_compute_cmd.rs`, when using `ReadOpsTrackingStore` to generate minimal snapshots, `HEAD_KEY` should be written to `db.tracker` (not `db` itself) before calling `export_forest_car()`, because the export reads from the tracker MemoryDB which accumulates only the accessed data during computation.
Summary of changes
Changes introduced in this pull request:
Reference issue to close (if applicable)
Closes
Other information and links
Change checklist
Summary by CodeRabbit