Skip to content

Conversation

@jackzhhuang
Copy link
Contributor

@jackzhhuang jackzhhuang commented Dec 24, 2025

Pull request type

Please check the type of change your PR introduces:

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • Documentation content changes
  • Other (please describe):

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Other information

Summary by CodeRabbit

  • New Features

    • Dual‑VM finalized block metadata and a global per‑block state cache to reuse execution outputs and speed block processing.
    • New execution paths and public APIs to accept precomputed transaction outputs for faster block execution.
    • Added a comprehensive benchmark artifact with full performance summaries.
  • Bug Fixes

    • Ensure blocks are applied on their correct parent during sync.
    • Clear global block‑state cache on shutdown and reset shutdown flag at startup.
  • Tests

    • Unit tests validating cache behavior; extended reconnect timing in a client-server test.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 24, 2025

📝 Walkthrough

Walkthrough

Adds a global per-block state cache, threads dual-VM execution outputs through open-block/finalize (FinalizedBlock), exposes execution-with-outputs APIs, integrates cache lookups into chain execution and miner caching, and updates shutdown and test flows.

Changes

Cohort / File(s) Summary
Benchmark artifact
benchmark_stats_20251208_125237.txt
Adds a text benchmark report with overall metrics, per-block TPS, top‑100 latency transactions, and blocks with high latency.
Cache module & exports
chain/src/txn_output_cache.rs, chain/src/lib.rs
New DashMap-backed BlockStateCache and CachedBlockState, global singleton global_block_state_cache, lifecycle helpers (clear_global_block_state_cache, shutdown flags), eviction policy, tests, and public re-exports.
OpenedBlock / FinalizedBlock
chain/open-block/src/lib.rs, chain/open-block/src/vm2.rs
Introduces public FinalizedBlock; changes OpenedBlock::finalize() to return FinalizedBlock; tracks VM1/VM2 txn outputs, txn_infos, events, table infos, and write_sets during push/finalize; VM2 bookkeeping additions.
Chain execution & cache integration
chain/src/chain.rs
Adds private execute_block_transactions returning VM1/VM2 executed data; integrates global_block_state_cache fast‑path (complete cache hit uses cached statedb/executed_data), preserves public APIs, and uses cache/flush semantics.
Executor: consume outputs
executor/src/block_executor.rs, executor/src/lib.rs
Adds block_execute_with_outputs(...) to run transactions using precomputed TransactionOutput list and re-exports it.
VM2 executor & chain bindings
vm2/executor/src/block_executor.rs, vm2/executor/src/lib.rs, vm2/chain/src/lib.rs
Mirrors executor additions: block_execute_with_outputs and execute_transactions_with_outputs, adds TransactionOutput imports and VM2 txn_info/event bookkeeping.
Miner: cache after finalize
miner/src/create_block_template/block_builder_service.rs
Adds shutdown guards, uses finalized.template, and inserts CachedBlockState (statedb/statedb2/executed_data/executed_data2) into global_block_state_cache keyed by txn_accumulator_root.
Sync pre-apply fork check
sync/src/tasks/block_sync_task.rs
Ensures BlockCollector::apply_block() forks to block.parent_hash() when head mismatch before applying the block.
Tests / txpool
txpool/src/test.rs
Tests updated to use finalized.template and variable renames reflecting FinalizedBlock.
Dependencies / workspace updates
chain/Cargo.toml, node/Cargo.toml
Adds workspace deps: dashmap, starcoin-vm2-executor, and starcoin-chain.
Node shutdown hook
node/src/node.rs
Calls reset_node_shutdown_flag() on launch and clear_global_block_state_cache() during shutdown.
Small test timing change
rpc/client/tests/client_server_test.rs
Increases wait in test_client_reconnect from 3000 ms to 10000 ms.
VM2 error handling
vm2/types/src/error/block_executor.rs
Adds InvalidOutputCount { expected, actual } error variant for mismatched transaction outputs.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Miner
    participant Opened as OpenedBlock
    participant Finalized as FinalizedBlock
    participant Cache as GlobalCache
    participant Chain
    participant Exec as Executor

    Miner->>Opened: push transactions (record VM1 & VM2 outputs/infos/events)
    Opened-->>Miner: finalize() → Finalized (template + statedb(s) + executed_data(s))
    Miner->>Cache: insert(template.txn_accumulator_root, CachedBlockState)

    Chain->>Cache: lookup(txn_accumulator_root)
    alt cache hit (complete)
        Cache-->>Chain: cached statedb & executed_data (VM1 & VM2)
        Chain->>Exec: block_execute_with_outputs(txns, cached outputs)
    else cache miss / partial
        Chain->>Exec: execute_block_transactions(txns) -> fresh VM1 & VM2 execution
    end
    Exec-->>Chain: BlockExecutedData (VM1 & VM2)
    Chain->>Chain: apply state roots, flush state, finalize block
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • welbon
  • simonjiao
  • lushengguo
  • sanlee42

Poem

🐇 I nibbled logs and cached each bite,
Two VMs hummed softly through day and night.
DashMap shelves held states snug and neat,
Blocks hop quicker on the cached beat.
Hop — cached and light, the chain bounds bright!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Cache state db' is vague and does not clearly convey the main objective of this substantial changeset, which involves implementing a dual-VM execution path with per-block state caching across multiple modules. Consider using a more descriptive title that captures the primary change, such as 'Implement block state caching for dual-VM execution' or 'Add global block state cache with dual-VM support'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
chain/src/chain.rs (1)

2443-2471: Cached block execution skips populating txn_table_infos, creating persistent metadata divergence

In execute_dag_block, when the full cache hit condition is met (all CachedBlockState fields Some), executed_data and executed_data2 sourced from FinalizedBlock are used directly. However, FinalizedBlock::finalize constructs both as:

txn_table_infos: BTreeMap::new(), // TODO: track table infos if needed

In contrast, non-cached execution paths (block_execute, execute_transactions) populate txn_table_infos by appending transaction outputs, then call storage.save_table_infos(txn_table_infos) to persist them.

This creates divergence: nodes executing the same block from cache persist empty txn_table_infos, while nodes re-executing without cache persist populated table metadata. Consensus hashes match, but stored metadata differs.

Recommended fix: Populate txn_table_infos during OpenedBlock execution and pass real data to FinalizedBlock construction instead of empty maps.

🧹 Nitpick comments (6)
benchmark_stats_20251208_125237.txt (1)

1-133: Consider removing this benchmark artifact from the repository.

This appears to be a development/testing artifact containing benchmark results. Typically, such files should not be committed to version control unless there's a specific documentation purpose. Consider:

  • Moving to a docs/benchmarks/ directory if needed for reference
  • Adding to .gitignore if it's generated during development
  • Removing from the PR if committed unintentionally
miner/src/create_block_template/block_builder_service.rs (1)

8-11: Finalize path caching looks coherent; be aware of stale entries

The way you cache FinalizedBlock data under template.txn_accumulator_root and later consume it in BlockChain::execute_dag_block is consistent (same key, correct ordering for VM1/VM2 outputs, correct use of Arc for state DBs and executed data). No correctness issues spotted.

One thing to keep in mind: templates that are never turned into committed blocks will leave their entries in GLOBAL_BLOCK_STATE_CACHE / GLOBAL_TXN_OUTPUT_CACHE indefinitely. Over time, in a busy miner this can accumulate a lot of ChainStateDB and TransactionOutput data. You’re currently relying solely on the DAG execute path to remove entries.

Consider adding some form of pruning (size bound / age-based eviction / periodic sweeping) or at least metrics so you can monitor cache growth and act before it becomes a memory problem.

Also applies to: 612-651

executor/src/block_executor.rs (1)

13-13: block_execute_with_outputs mirrors block_execute; consider tightening invariants

The new block_execute_with_outputs helper correctly mirrors block_execute semantics (status handling, gas accounting, write-set application, and commit-on-first-or-last) and should integrate cleanly with the cache path.

Two suggestions:

  1. To guard against mismatched inputs, it’s worth enforcing (at least in debug) that txns.len() == outputs.len() rather than silently truncating to outputs.len():
debug_assert_eq!(txns.len(), outputs.len());
  1. Long-term, you might want to factor the common “consume (txn, output) pairs into BlockExecutedData” logic into a shared internal helper to reduce duplication between block_execute and block_execute_with_outputs.

Also applies to: 149-203

vm2/executor/src/block_executor.rs (1)

12-13: VM2 block_execute_with_outputs is consistent; same note on length invariant

The VM2 block_execute_with_outputs implementation is consistent with the existing block_execute (status handling, write-set application, commit-on-first-or-last, and BlockExecutedData structure).

As with the VM1 variant, I’d recommend a debug assertion that txns.len() == outputs.len() to catch cache / wiring bugs early instead of silently ignoring extra transactions or outputs:

debug_assert_eq!(txns.len(), outputs.len());

Also applies to: 101-154

chain/src/chain.rs (1)

4-9: Cache helper wiring is mostly correct; note that cached state DBs are not yet surfaced

execute_block_transactions_with_cache is wired correctly to:

  • Prefer cached VM1/VM2 outputs when present.
  • Fall back to normal block_execute / execute_transactions otherwise.
  • Respect VM1 gas usage when deciding VM2’s gas budget.

Currently the (Option<Arc<ChainStateDB>>, Option<Arc<ChainStateDB2>>) tuple components are always None; the function never returns a cached state DB. That’s fine for now but makes the signature a bit misleading.

If you don’t plan to return pre-applied state DBs from here, consider either:

  • Dropping the extra return values, or
  • Implementing the “cached state DB” path inside this helper so execute_dag_block doesn’t need to know about it.

Also applies to: 2169-2264

chain/src/txn_output_cache.rs (1)

178-237: Tests cover TransactionOutputCache, consider adding BlockStateCache tests

The existing tests exercise TransactionOutputCache and the global txn output cache well (insert, get, remove, clear). There are no analogous tests for BlockStateCache.

Given BlockStateCache will carry ChainStateDB arcs and executed data, it would be useful to have at least one basic test ensuring:

  • insert / get / remove behave as expected, and
  • clear empties the map.

This will help catch any future refactors that accidentally break the state-cache path.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1f464e and 188800b.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • starcoin.log is excluded by !**/*.log
📒 Files selected for processing (15)
  • benchmark_stats_20251208_125237.txt
  • chain/Cargo.toml
  • chain/open-block/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
  • executor/src/block_executor.rs
  • executor/src/lib.rs
  • miner/src/create_block_template/block_builder_service.rs
  • sync/src/tasks/block_sync_task.rs
  • txpool/src/test.rs
  • vm2/chain/src/lib.rs
  • vm2/executor/src/block_executor.rs
  • vm2/executor/src/lib.rs
🧰 Additional context used
🧠 Learnings (17)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • chain/src/lib.rs
  • sync/src/tasks/block_sync_task.rs
  • miner/src/create_block_template/block_builder_service.rs
  • vm2/chain/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • executor/src/block_executor.rs
  • chain/src/txn_output_cache.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • chain/src/lib.rs
  • sync/src/tasks/block_sync_task.rs
  • miner/src/create_block_template/block_builder_service.rs
  • txpool/src/test.rs
  • vm2/chain/src/lib.rs
  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • chain/src/lib.rs
  • sync/src/tasks/block_sync_task.rs
  • vm2/executor/src/block_executor.rs
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/lib.rs
  • txpool/src/test.rs
  • vm2/chain/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • executor/src/block_executor.rs
  • chain/src/txn_output_cache.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/src/lib.rs
  • vm2/executor/src/block_executor.rs
  • miner/src/create_block_template/block_builder_service.rs
  • txpool/src/test.rs
  • vm2/chain/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • executor/src/block_executor.rs
  • chain/Cargo.toml
  • chain/src/txn_output_cache.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • chain/src/lib.rs
  • vm2/chain/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • executor/src/block_executor.rs
  • chain/Cargo.toml
  • chain/src/txn_output_cache.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • chain/src/lib.rs
  • sync/src/tasks/block_sync_task.rs
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/lib.rs
  • txpool/src/test.rs
  • executor/src/block_executor.rs
📚 Learning: 2025-09-30T01:46:29.197Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: chain/open-block/src/vm2.rs:44-45
Timestamp: 2025-09-30T01:46:29.197Z
Learning: In the Starcoin blockchain (file: chain/open-block/src/vm2.rs), block metadata transaction gas consumption is intentionally NOT counted toward the block gas limit (self.gas_used is not incremented for metadata transactions in initialize() method). Only user transactions in push_txns2() update self.gas_used.

Applied to files:

  • chain/src/lib.rs
  • miner/src/create_block_template/block_builder_service.rs
  • vm2/chain/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • executor/src/block_executor.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-09-16T01:30:06.195Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4658
File: vm2/vm-runtime/src/parallel_executor/mod.rs:0-0
Timestamp: 2025-09-16T01:30:06.195Z
Learning: In the Starcoin codebase, the Display schema for AccessPath is stable, making string-based matching via format!("{}", path) a reliable approach for identifying specific resource paths like the STC transaction fee aggregator.

Applied to files:

  • chain/src/lib.rs
📚 Learning: 2025-04-01T08:16:02.035Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4447
File: sync/src/tasks/block_sync_task.rs:608-620
Timestamp: 2025-04-01T08:16:02.035Z
Learning: In the `fetch_blocks_in_batch` method of `BlockSyncTask`, the filter logic is designed to include block headers where at least one parent exists in the local store but has not yet been integrated into the chain.

Applied to files:

  • sync/src/tasks/block_sync_task.rs
📚 Learning: 2025-09-14T15:08:48.415Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4648
File: miner/tests/miner_test.rs:87-93
Timestamp: 2025-09-14T15:08:48.415Z
Learning: In Starcoin test files (like miner/tests/miner_test.rs), jackzhhuang prefers to use std::thread::sleep for intentional blocking delays in test scenarios, even within actor event handlers, rather than using non-blocking ctx.run_later alternatives.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/lib.rs
📚 Learning: 2025-05-28T10:21:10.718Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4540
File: chain/src/chain.rs:1816-1824
Timestamp: 2025-05-28T10:21:10.718Z
Learning: In chain/src/chain.rs, the total_blocks calculation for epoch statistics always results in at least 1 block because total_selectd_chain_blocks = (current_block_number - epoch_start_block_number) + 1, which is always >= 1, making division by zero impossible in the avg_total_difficulty calculation.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-08-08T10:27:43.881Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: txpool/src/pool/queue.rs:389-392
Timestamp: 2025-08-08T10:27:43.881Z
Learning: In starcoinorg/starcoin#4605, txpool/src/pool/queue.rs: For PendingOrdering::Priority, if pool.try_read() fails, the design is to return an empty Vec and proceed with block building without waiting for the lock (non-blocking behavior is intentional).

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-08-08T10:25:49.039Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: txpool/mock-service/src/lib.rs:114-120
Timestamp: 2025-08-08T10:25:49.039Z
Learning: In PR starcoinorg/starcoin#4605, txpool/mock-service/src/lib.rs: MockTxPoolService::next_sequence_number_with_header is currently unused; keeping todo!() in the mock is acceptable and won’t affect runtime unless invoked.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-09-01T03:56:58.362Z
Learnt from: welbon
Repo: starcoinorg/starcoin PR: 4633
File: vm/vm-runtime/Cargo.toml:48-48
Timestamp: 2025-09-01T03:56:58.362Z
Learning: In the Starcoin codebase, vm1 (starcoin-vm1-vm-runtime) may need to expose the same feature flags as vm2 to satisfy cross-version compatibility requirements when downstream projects like genesis depend on features from both versions. Empty feature declarations like `move-unit-test = []` may be intentionally added for compilation compatibility rather than to activate specific functionality.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/Cargo.toml
📚 Learning: 2025-09-16T01:34:58.198Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4657
File: cmd/starcoin/src/dev/get_coin_cmd.rs:79-90
Timestamp: 2025-09-16T01:34:58.198Z
Learning: In Starcoin's get_coin_cmd.rs batch transaction submission (when repeat > 1), the maintainer lushengguo confirmed the preference for fail-fast behavior using the ? operator - the command should stop on the first transaction failure rather than collecting errors and continuing to submit remaining transactions.

Applied to files:

  • executor/src/block_executor.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/txn_output_cache.rs
🧬 Code graph analysis (11)
chain/src/lib.rs (1)
chain/src/txn_output_cache.rs (2)
  • global_block_state_cache (112-114)
  • global_txn_output_cache (174-176)
sync/src/tasks/block_sync_task.rs (2)
genesis/src/lib.rs (1)
  • block (272-274)
types/src/block/mod.rs (1)
  • block (1128-1130)
miner/src/create_block_template/block_builder_service.rs (1)
chain/src/txn_output_cache.rs (6)
  • global_block_state_cache (112-114)
  • global_txn_output_cache (174-176)
  • new (23-31)
  • new (49-61)
  • new (70-74)
  • new (125-129)
executor/src/lib.rs (2)
executor/src/block_executor.rs (4)
  • block_execute (65-147)
  • block_execute_with_outputs (150-203)
  • get_logger_balance_amount (31-33)
  • set_logger_balance_amount_once (24-28)
vm2/executor/src/block_executor.rs (2)
  • block_execute (36-99)
  • block_execute_with_outputs (102-154)
vm2/chain/src/lib.rs (2)
executor/src/block_executor.rs (1)
  • block_execute_with_outputs (150-203)
vm2/executor/src/block_executor.rs (1)
  • block_execute_with_outputs (102-154)
chain/open-block/src/vm2.rs (2)
vm2/vm-types/src/transaction/mod.rs (2)
  • txn_info (966-968)
  • events (790-792)
chain/src/chain.rs (5)
  • events (762-762)
  • events (778-778)
  • events (1592-1595)
  • events (2623-2623)
  • events (2633-2633)
chain/src/chain.rs (4)
chain/src/txn_output_cache.rs (4)
  • global_block_state_cache (112-114)
  • global_txn_output_cache (174-176)
  • len (91-93)
  • len (152-154)
executor/src/block_executor.rs (2)
  • block_execute_with_outputs (150-203)
  • block_execute (65-147)
chain/open-block/src/lib.rs (1)
  • new (92-167)
vm2/chain/src/lib.rs (3)
  • execute_transactions_with_outputs (27-34)
  • execute_transactions (13-25)
  • get_epoch_from_statedb (62-65)
vm2/executor/src/lib.rs (2)
executor/src/block_executor.rs (2)
  • block_execute (65-147)
  • block_execute_with_outputs (150-203)
vm2/executor/src/block_executor.rs (2)
  • block_execute (36-99)
  • block_execute_with_outputs (102-154)
executor/src/block_executor.rs (2)
vm2/vm-types/src/transaction/mod.rs (7)
  • write_set (786-788)
  • events (790-792)
  • gas_used (794-796)
  • gas_used (902-904)
  • status (641-647)
  • status (798-800)
  • status (906-908)
types/src/transaction/mod.rs (2)
  • gas_used (102-104)
  • status (106-108)
chain/src/txn_output_cache.rs (4)
chain/src/chain.rs (5)
  • new (89-101)
  • executed_data (742-752)
  • executed_data (961-965)
  • executed_data (2606-2616)
  • executed_data2 (2515-2519)
chain/open-block/src/lib.rs (1)
  • new (92-167)
executor/src/block_executor.rs (1)
  • default (45-53)
vm2/executor/src/block_executor.rs (1)
  • default (26-33)
chain/open-block/src/lib.rs (1)
types/src/block/mod.rs (1)
  • block (1128-1130)
🔇 Additional comments (12)
chain/Cargo.toml (1)

4-4: LGTM! Dependency additions support the caching infrastructure.

The additions of dashmap (concurrent hash map for caching) and starcoin-vm2-executor (VM2 execution with outputs) align with the PR's dual-VM caching mechanism.

Also applies to: 38-38

vm2/executor/src/lib.rs (1)

8-8: LGTM! API expansion aligns with the caching architecture.

Re-exporting block_execute_with_outputs provides the public API for executing blocks with pre-computed outputs, enabling the dual-VM caching mechanism described in the PR.

txpool/src/test.rs (1)

282-286: LGTM! Test correctly refactored for FinalizedBlock structure.

The test properly adapts to the new FinalizedBlock type returned by finalize(), accessing state roots and block data through the template field. The functional behavior remains unchanged.

executor/src/lib.rs (1)

8-9: LGTM! API expansion supports caching and mirrors VM2 interface.

Re-exporting block_execute_with_outputs and BlockExecutedData provides the public API for executing blocks with pre-computed outputs and accessing execution results, enabling the caching infrastructure.

chain/src/lib.rs (1)

6-6: LGTM! Public API properly exposes the caching infrastructure.

The module and type exports provide external access to the dual-VM caching mechanism. The global cache accessors follow a singleton pattern for coordinated access across the codebase.

Also applies to: 13-16

vm2/chain/src/lib.rs (1)

9-9: LGTM! VM2 execution path correctly implements cached execution.

The new execute_transactions_with_outputs function provides a parallel execution path that reuses pre-computed outputs, properly delegating to the block executor. The implementation is consistent with the existing execute_transactions pattern.

Also applies to: 27-34

sync/src/tasks/block_sync_task.rs (1)

355-357: The fork operation is already conditional and correctly positioned for state verification.

The check if self.chain.status().head().id() != block.parent_hash() ensures the fork only occurs when necessary—it doesn't fork during normal linear chain progression. The fork before apply_with_verifier is necessary because verification itself requires the chain state to be positioned at the block's parent state roots (consistent with the learning that state must be correctly initialized before block execution). Since verify_with_verifier takes &mut self, it likely reads parent-related state during validation, so the fork cannot be delayed until after verification.

chain/open-block/src/vm2.rs (1)

16-19: VM2 output caching and execution tracking look sound

The added VM2 fields (cached_vm2_outputs, vm2_txn_infos, vm2_txn_events) and their population in:

  • initialize (BlockMetadata),
  • push_txns2 (kept user txns),
  • finalize_block_epilogue (BlockEpilogue),
  • and push_txn_and_state2 (txn_info/events tracking)

produce an output list and execution metadata that align with the transaction order produced by build_block_transactions. That matches what FinalizedBlock expects.

No correctness issues noticed here. The cloned outputs and txn_infos are extra allocations but necessary for the caching path; acceptable given their one-time use per block.

Also applies to: 46-48, 71-87, 126-157, 168-215

chain/open-block/src/lib.rs (3)

44-88: FinalizedBlock structure and OpenedBlock fields are aligned with caching design

The new FinalizedBlock wrapper (template, vm1_outputs, vm2_outputs, statedb/statedb2, executed_data{1,2}) plus the extra fields on OpenedBlock (cached_vm1_outputs, cached_vm2_outputs, vm1/vm2 txn_infos, events, write_sets) provide exactly the data needed by the miner and chain caches.

Construction in OpenedBlock::new initializes all these tracking fields and keeps previous behaviour (state, accumulators, gas tracking) unchanged. No obvious correctness issues.

Also applies to: 137-162


232-276: VM1 output caching order matches VM1 execution order

In push_txns and execute_block_meta_vm1 you push:

  • BlockMetadata output once (in execute_block_meta_vm1), and
  • each kept user transaction’s output (in push_txns),

into cached_vm1_outputs in the same order as they are applied and as they appear in the block (BlockMetadata followed by user txns). This matches the transaction list used later by block_execute_with_outputs, so the VM1 output cache should align cleanly with block execution.


331-376: VM1 execution tracking (infos/events/write_sets) is correct and consistent

push_txn_and_state now:

  • Stores each write_set in vm1_write_sets,
  • Pushes the corresponding TransactionInfo and events into vm1_txn_infos and vm1_txn_events,
  • Maintains the accumulator as before.

This is exactly what FinalizedBlock::finalize needs to reconstruct BlockExecutedData1 for the cache path, and mirrors what the executor would have produced. Looks good.

chain/src/txn_output_cache.rs (1)

15-62: CachedBlockOutputs / CachedBlockState types look appropriate

The cache value types (CachedBlockOutputs wrapping optional Arc<Vec<...>> and CachedBlockState wrapping optional Arc<ChainStateDB{,2}> plus BlockExecutedData{1,2}) are structured sensibly:

  • Arc usage is appropriate to share heavy data across threads.
  • Option allows partial caches (outputs only, or state only) if needed.
  • Constructors wrap plain inputs cleanly.

No correctness issues here.

Copy link

@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 (3)
chain/src/chain.rs (2)

2319-2323: Verify statedb is positioned at selected parent's state before execution.

The code uses &self.statedb.0 and &self.statedb.1 directly without forking from the selected parent's state roots. Per the retrieved learning, the design expects statedb to be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called.

Ensure callers (e.g., execute method at line 1392) guarantee the BlockChain instance is correctly positioned at the parent's state before invoking execute_dag_block. If the incoming block's parent differs from the current head, execution may run on the wrong base state.


2387-2400: Redundant write_set re-application in non-cached execution path.

The executor functions (block_execute, execute_transactions, block_execute_with_outputs) already apply and commit write_sets to the passed-in state DB. Lines 2389-2396 redundantly apply these same write_sets again. Per the vm2/executor/src/block_executor.rs snippet (lines 173-179), block_execute_with_outputs calls chain_state.apply_write_set(write_set.clone()) and chain_state.commit() internally.

This double-application is unnecessary and potentially wasteful (extra commits). The cached path (lines 2382-2386) correctly only flushes.

🔎 Proposed fix: remove the manual loop and only flush
         } else {
-            // Apply write_sets to self.statedb
-            for write_set in executed_data.write_sets.clone() {
-                statedb
-                    .apply_write_set(write_set)
-                    .map_err(BlockExecutorError::BlockChainStateErr)?;
-                statedb
-                    .commit()
-                    .map_err(BlockExecutorError::BlockChainStateErr)?;
-            }
-            // Flush self.statedb
+            // VM1/VM2 executors have already applied and committed write_sets.
+            // Only flush to persist state tree nodes.
             statedb.flush()?;
             statedb2.flush()?;
         }
chain/open-block/src/lib.rs (1)

421-436: Empty txn_table_infos will cause table metadata loss for cached blocks.

Both BlockExecutedData1 and BlockExecutedData2 have txn_table_infos: BTreeMap::new() (lines 426, 435) with TODO comments. When these executed data structs are used via the cache path in execute_dag_block, the empty table_infos get persisted via storage.save_table_infos(), losing table metadata that the non-cached executor would have captured.

Track table_infos during transaction execution (from TransactionOutput::into_inner() which returns table_infos as first element), or treat the cache as incomplete and force re-execution when table_infos are missing.

🔎 Suggested tracking in push_txn_and_state
 fn push_txn_and_state(
     &mut self,
     txn_hash: HashValue,
     output: TransactionOutput,
     root_state_calc: bool,
 ) -> Result<(Option<HashValue>, HashValue)> {
     let (state, _state2) = &mut self.state;
-    let (_table_infos, write_set, events, gas_used, status) = output.into_inner();
+    let (table_infos, write_set, events, gas_used, status) = output.into_inner();
+    
+    // Track table_infos for later use
+    self.vm1_table_infos.extend(table_infos.into_iter().map(|(k, v)| (k, v)));
+    
     // ... rest of method
 }

Add field to OpenedBlock:

vm1_table_infos: BTreeMap<TableHandle, TableInfo>,
🧹 Nitpick comments (2)
chain/src/chain.rs (2)

2172-2264: Return values for cached statedbs are always None; consider removing unused tuple elements.

The function signature returns Option<Arc<ChainStateDB>> and Option<Arc<ChainStateDB2>> (3rd and 4th tuple elements), but line 2263 always returns (None, None). The caller at lines 2381-2386 checks these values, but the condition will never be true. Either:

  1. The statedb caching via this function is incomplete, or
  2. The return signature and caller checks should be simplified

If caching is handled elsewhere (via cached_state from global_block_state_cache), consider simplifying this function to return only (BlockExecutedData, BlockExecutedData2).

🔎 Suggested simplification if statedb caching is handled elsewhere
 fn execute_block_transactions_with_cache(
     statedb: &ChainStateDB,
     statedb2: &ChainStateDB2,
     transactions: &[Transaction],
     transactions2: &[starcoin_vm2_types::transaction::Transaction],
     epoch: &Epoch,
     cached_outputs: &Option<CachedBlockOutputs>,
     block_id: HashValue,
     vm_metrics: Option<VMMetrics>,
-) -> Result<(
-    BlockExecutedData,
-    BlockExecutedData2,
-    Option<Arc<ChainStateDB>>,
-    Option<Arc<ChainStateDB2>>,
-)> {
+) -> Result<(BlockExecutedData, BlockExecutedData2)> {
     // ... execution logic unchanged ...
-    Ok((executed_data, executed_data2, None, None))
+    Ok((executed_data, executed_data2))
 }

2553-2556: Double flush in non-cached execution path.

In the non-cached path, statedb.flush() and statedb2.flush() are called at lines 2398-2399, then again at lines 2555-2556. While not incorrect, this is redundant I/O.

Consider removing the earlier flush (lines 2398-2399) since the final flush here handles persistence.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 188800b and a1422bc.

📒 Files selected for processing (3)
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • miner/src/create_block_template/block_builder_service.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • miner/src/create_block_template/block_builder_service.rs
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-09-30T01:46:29.197Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: chain/open-block/src/vm2.rs:44-45
Timestamp: 2025-09-30T01:46:29.197Z
Learning: In the Starcoin blockchain (file: chain/open-block/src/vm2.rs), block metadata transaction gas consumption is intentionally NOT counted toward the block gas limit (self.gas_used is not incremented for metadata transactions in initialize() method). Only user transactions in push_txns2() update self.gas_used.

Applied to files:

  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-09-16T01:34:58.198Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4657
File: cmd/starcoin/src/dev/get_coin_cmd.rs:79-90
Timestamp: 2025-09-16T01:34:58.198Z
Learning: In Starcoin's get_coin_cmd.rs batch transaction submission (when repeat > 1), the maintainer lushengguo confirmed the preference for fail-fast behavior using the ? operator - the command should stop on the first transaction failure rather than collecting errors and continuing to submit remaining transactions.

Applied to files:

  • chain/open-block/src/lib.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • chain/open-block/src/lib.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2024-09-30T09:31:42.793Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4194
File: flexidag/src/blockdag.rs:444-446
Timestamp: 2024-09-30T09:31:42.793Z
Learning: In the service, `get_dag_state` is used to get the current state of the chain and it passes the main header ID to `BlockDAG`.

Applied to files:

  • chain/src/chain.rs
🧬 Code graph analysis (2)
chain/open-block/src/lib.rs (3)
vm2/vm-types/src/transaction/mod.rs (2)
  • write_set (786-788)
  • events (790-792)
vm/types/src/transaction/mod.rs (2)
  • write_set (683-685)
  • events (691-693)
types/src/multi_state.rs (2)
  • state_root1 (35-37)
  • state_root2 (39-41)
chain/src/chain.rs (4)
chain/src/txn_output_cache.rs (4)
  • global_block_state_cache (112-114)
  • global_txn_output_cache (174-176)
  • len (91-93)
  • len (152-154)
executor/src/block_executor.rs (2)
  • block_execute_with_outputs (150-203)
  • block_execute (65-147)
vm2/executor/src/block_executor.rs (2)
  • block_execute_with_outputs (102-154)
  • block_execute (36-99)
vm2/chain/src/lib.rs (2)
  • execute_transactions_with_outputs (27-34)
  • execute_transactions (13-25)
🔇 Additional comments (10)
chain/src/chain.rs (3)

7-7: LGTM on import additions.

The new imports for global_block_state_cache and global_txn_output_cache properly support the cache integration.


453-455: LGTM on finalize rename.

The rename from template to finalized and access via .template aligns with the new FinalizedBlock return type.


2402-2405: LGTM on cache cleanup.

Proper cleanup of output cache after use prevents unbounded cache growth.

chain/open-block/src/lib.rs (7)

10-41: LGTM on import additions.

The new imports properly support the FinalizedBlock struct and execution data tracking.


44-55: LGTM on FinalizedBlock structure.

The struct properly aggregates all data needed for caching: block template, VM1/VM2 outputs, state databases, and executed data. Arc wrapping for statedbs enables shared ownership across cache and consumers.


78-87: LGTM on new OpenedBlock fields for execution data tracking.

The fields properly track VM1 outputs/infos/events/write_sets and VM2 outputs/infos/events. The asymmetry (no vm2_write_sets) correctly reflects that BlockExecutedData2 doesn't include write_sets.


265-265: LGTM on caching VM1 outputs.

Correctly caches the output before it's consumed by push_txn_and_state. Only kept transactions are cached, which is the correct behavior.


306-308: LGTM on caching BlockMetadata output.

Correctly caches the BlockMetadata transaction output, ensuring the first transaction is included in cached outputs for replay.


346-372: LGTM on execution data tracking.

Properly tracks write_sets (cloned before consumption), txn_infos, and events for later aggregation into BlockExecutedData during finalization.


438-446: LGTM on FinalizedBlock construction.

The struct properly aggregates the template, cached outputs, state databases, and executed data for downstream caching use.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
chain/src/chain.rs (1)

2545-2548: Redundant flush calls detected.

Lines 2547-2548 flush statedb and statedb2 unconditionally, but these were already flushed at lines 2390-2391. This results in double flushing which is wasteful.

If the intent is to ensure flush after verification passes, consider restructuring to avoid the redundant flush.

🧹 Nitpick comments (4)
chain/src/txn_output_cache.rs (2)

91-111: Eviction is non-deterministic but acceptable for this use case.

The eviction strategy removes arbitrary entries since DashMap::iter() has no guaranteed order. While not ideal, this is acceptable because:

  1. The cache is small (16 entries max)
  2. It's for block templates being mined, where temporal locality matters less
  3. The comment at line 93 documents this behavior

For production observability, consider adding a metric counter for eviction events.


233-291: LGTM - Tests cover core cache operations.

The tests validate insert, get, remove, clear, and global cache access. Consider adding a test for the eviction behavior when exceeding max_size.

chain/src/chain.rs (2)

2169-2171: Consider placing the helper function closer to its usage or in a separate module.

The helper function is defined after the ChainWriter impl block, which is unconventional. In Rust, it's more common to place helper functions either:

  1. Before the impl block that uses them, or
  2. In a separate private module

This is a minor organizational suggestion.


2182-2187: Return type includes unused statedb fields that are never populated.

The execute_block_transactions_with_cache function declares Option<Arc<ChainStateDB>> and Option<Arc<ChainStateDB2>> in its return type but always returns (None, None) at line 2263. The callers at lines 2354 and 2367 always receive None for these fields. The non-None values destructured into cached_statedb and cached_statedb2 (line 2335) come exclusively from the full cache hit path (lines 2346-2351), not from the function. Either refactor the function to return cached statedbs when the full cache path is used, or simplify the return type.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1422bc and 5fc0c05.

📒 Files selected for processing (3)
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2024-09-30T09:31:42.793Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4194
File: flexidag/src/blockdag.rs:444-446
Timestamp: 2024-09-30T09:31:42.793Z
Learning: In the service, `get_dag_state` is used to get the current state of the chain and it passes the main header ID to `BlockDAG`.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-09-30T01:46:29.197Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: chain/open-block/src/vm2.rs:44-45
Timestamp: 2025-09-30T01:46:29.197Z
Learning: In the Starcoin blockchain (file: chain/open-block/src/vm2.rs), block metadata transaction gas consumption is intentionally NOT counted toward the block gas limit (self.gas_used is not incremented for metadata transactions in initialize() method). Only user transactions in push_txns2() update self.gas_used.

Applied to files:

  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-09-16T01:34:58.198Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4657
File: cmd/starcoin/src/dev/get_coin_cmd.rs:79-90
Timestamp: 2025-09-16T01:34:58.198Z
Learning: In Starcoin's get_coin_cmd.rs batch transaction submission (when repeat > 1), the maintainer lushengguo confirmed the preference for fail-fast behavior using the ? operator - the command should stop on the first transaction failure rather than collecting errors and continuing to submit remaining transactions.

Applied to files:

  • chain/open-block/src/lib.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • chain/open-block/src/lib.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/txn_output_cache.rs
🧬 Code graph analysis (2)
chain/src/chain.rs (1)
chain/src/txn_output_cache.rs (8)
  • global_block_state_cache (144-146)
  • global_txn_output_cache (229-231)
  • len (123-125)
  • len (207-209)
  • new (30-38)
  • new (56-68)
  • new (78-80)
  • new (158-160)
chain/open-block/src/lib.rs (3)
vm2/vm-types/src/transaction/mod.rs (7)
  • write_set (786-788)
  • events (790-792)
  • gas_used (794-796)
  • gas_used (902-904)
  • status (641-647)
  • status (798-800)
  • status (906-908)
vm/vm-runtime/src/move_vm_ext/session.rs (1)
  • events (148-155)
types/src/transaction/mod.rs (2)
  • gas_used (102-104)
  • status (106-108)
🔇 Additional comments (14)
chain/src/txn_output_cache.rs (4)

1-21: LGTM - Cache module setup with sensible defaults.

The imports and constants are appropriate. The cache sizes of 16 entries are reasonable for a mining-focused cache where only a small number of block templates are typically in flight.


22-39: LGTM - CachedBlockOutputs correctly wraps outputs in Arc.

The new() constructor wraps the vectors in Arc to enable cheap cloning and shared ownership across threads, which is appropriate for a concurrent cache.


41-69: LGTM - CachedBlockState captures all necessary execution artifacts.

The struct correctly captures both VM1 and VM2 statedbs and their respective executed data, enabling full execution skip when cache hits.


113-121: Consider using get with Ref::clone() to avoid holding the read lock.

The current implementation clones inside the closure which is correct, but an alternative pattern using map is slightly more idiomatic:

pub fn get(&self, txn_accumulator_root: HashValue) -> Option<CachedBlockState> {
    self.cache.get(&txn_accumulator_root).map(|v| v.clone())
}

This is already what you have - looks good!

chain/src/chain.rs (3)

453-455: LGTM - Clean adaptation to FinalizedBlock return type.

The change correctly extracts the template from the new FinalizedBlock struct and maintains the existing return signature.


2335-2377: Cache lookup and execution flow is correct.

The logic properly:

  1. Attempts to retrieve cached state first (removes from cache to take ownership)
  2. Falls back to output cache for partial caching
  3. Uses full execution when no cache is available

The info-level logging for cache hits is appropriate for debugging performance.


2394-2397: LGTM - Output cache cleanup after use.

Properly removes the output cache entry after consumption to prevent stale data accumulation.

chain/open-block/src/lib.rs (7)

45-56: LGTM - FinalizedBlock captures all necessary caching artifacts.

The struct properly includes:

  • Block template for mining
  • VM1/VM2 outputs for transaction output caching
  • Arc-wrapped statedbs for state caching
  • Executed data for both VMs to skip re-execution

This enables the full caching workflow introduced in this PR.


79-90: LGTM - Execution tracking fields added to OpenedBlock.

The new fields properly track all execution artifacts needed to construct BlockExecutedData1 and BlockExecutedData2 during finalization:

  • VM1: txn_infos, events, write_sets, table_infos
  • VM2: txn_infos, events (note correctly mentions VM2 doesn't track table_infos)

342-377: LGTM - Properly extracts and tracks execution data during push.

This addresses the previous review concern about empty txn_table_infos. The implementation:

  1. Extracts table_infos from the output via into_inner() (line 343)
  2. Merges them into vm1_table_infos using append() (line 350)
  3. Tracks write_set for later use (line 353)
  4. Accumulates txn_info and events (lines 376-377)

This ensures VM1 table_infos are properly preserved in BlockExecutedData1.


444-452: LGTM - FinalizedBlock construction is complete.

All tracked data is properly transferred to the FinalizedBlock, enabling downstream caching in the miner and chain execution paths.


269-274: LGTM - VM1 outputs cached during transaction processing.

The output is cloned and cached before being consumed by push_txn_and_state, ensuring the data is available for FinalizedBlock.


309-312: LGTM - BlockMetadata output also cached.

Correctly caches the block metadata transaction output alongside user transaction outputs.


435-442: Remove verification note—implementation is correct.

The empty txn_table_infos field is intentionally designed. Downstream code in chain.rs properly accesses and merges executed_data2.txn_table_infos with VM1 data via a chain operation, safely handling the empty map without issues.

Copy link

@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)
chain/src/txn_output_cache.rs (1)

82-99: Eviction strategy uses arbitrary ordering, not LRU or recency-based.

DashMap::iter() provides no ordering guarantees; entries are yielded in arbitrary hash-table order. Line 88's .take(max_size / 2) may evict recently inserted or frequently accessed entries instead of the oldest. In a DAG scenario with multiple competing blocks or delayed block application, this can evict useful cache entries before they're consumed.

Consider:

  • Tracking insertion timestamps and evicting oldest entries (LRU approximation)
  • Using a dedicated LRU crate like lru alongside DashMap for ordering
  • Documenting the arbitrary eviction behavior if acceptable for the use case
chain/src/chain.rs (1)

2172-2212: Document state mutation side effects in helper function.

execute_block_transactions calls block_execute and execute_transactions, both of which apply and commit write_sets directly to the passed statedb and statedb2 references as side effects. This is correct per the architecture (learnings confirm executors handle state commits), but the function signature and comment don't make the mutation explicit.

🔎 Suggested documentation improvement
-/// Helper function to execute block transactions
+/// Helper function to execute block transactions.
+/// Side effect: Executors apply and commit write_sets to statedb and statedb2 in-place.
+/// The returned BlockExecutedData state_root fields reflect the committed states.
 fn execute_block_transactions(
     statedb: &ChainStateDB,
     statedb2: &ChainStateDB2,
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fc0c05 and b29e3b2.

📒 Files selected for processing (6)
  • chain/open-block/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
  • miner/src/create_block_template/block_builder_service.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • chain/src/lib.rs
🧰 Additional context used
🧠 Learnings (15)
📓 Common learnings
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-30T01:46:29.197Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: chain/open-block/src/vm2.rs:44-45
Timestamp: 2025-09-30T01:46:29.197Z
Learning: In the Starcoin blockchain (file: chain/open-block/src/vm2.rs), block metadata transaction gas consumption is intentionally NOT counted toward the block gas limit (self.gas_used is not incremented for metadata transactions in initialize() method). Only user transactions in push_txns2() update self.gas_used.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-01T03:56:58.362Z
Learnt from: welbon
Repo: starcoinorg/starcoin PR: 4633
File: vm/vm-runtime/Cargo.toml:48-48
Timestamp: 2025-09-01T03:56:58.362Z
Learning: In the Starcoin codebase, vm1 (starcoin-vm1-vm-runtime) may need to expose the same feature flags as vm2 to satisfy cross-version compatibility requirements when downstream projects like genesis depend on features from both versions. Empty feature declarations like `move-unit-test = []` may be intentionally added for compilation compatibility rather than to activate specific functionality.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-16T01:34:58.198Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4657
File: cmd/starcoin/src/dev/get_coin_cmd.rs:79-90
Timestamp: 2025-09-16T01:34:58.198Z
Learning: In Starcoin's get_coin_cmd.rs batch transaction submission (when repeat > 1), the maintainer lushengguo confirmed the preference for fail-fast behavior using the ? operator - the command should stop on the first transaction failure rather than collecting errors and continuing to submit remaining transactions.

Applied to files:

  • chain/open-block/src/lib.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • chain/open-block/src/lib.rs
  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2024-09-30T09:31:42.793Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4194
File: flexidag/src/blockdag.rs:444-446
Timestamp: 2024-09-30T09:31:42.793Z
Learning: In the service, `get_dag_state` is used to get the current state of the chain and it passes the main header ID to `BlockDAG`.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-05-28T10:21:10.718Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4540
File: chain/src/chain.rs:1816-1824
Timestamp: 2025-05-28T10:21:10.718Z
Learning: In chain/src/chain.rs, the total_blocks calculation for epoch statistics always results in at least 1 block because total_selectd_chain_blocks = (current_block_number - epoch_start_block_number) + 1, which is always >= 1, making division by zero impossible in the avg_total_difficulty calculation.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-09-14T15:08:48.415Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4648
File: miner/tests/miner_test.rs:87-93
Timestamp: 2025-09-14T15:08:48.415Z
Learning: In Starcoin test files (like miner/tests/miner_test.rs), jackzhhuang prefers to use std::thread::sleep for intentional blocking delays in test scenarios, even within actor event handlers, rather than using non-blocking ctx.run_later alternatives.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-08-08T10:27:43.881Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: txpool/src/pool/queue.rs:389-392
Timestamp: 2025-08-08T10:27:43.881Z
Learning: In starcoinorg/starcoin#4605, txpool/src/pool/queue.rs: For PendingOrdering::Priority, if pool.try_read() fails, the design is to return an empty Vec and proceed with block building without waiting for the lock (non-blocking behavior is intentional).

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/txn_output_cache.rs
🧬 Code graph analysis (3)
chain/open-block/src/vm2.rs (2)
vm2/vm-types/src/transaction/mod.rs (2)
  • txn_info (966-968)
  • events (790-792)
chain/src/chain.rs (5)
  • events (762-762)
  • events (778-778)
  • events (1592-1595)
  • events (2548-2548)
  • events (2558-2558)
chain/src/chain.rs (5)
chain/src/txn_output_cache.rs (3)
  • global_block_state_cache (133-135)
  • new (36-48)
  • new (69-71)
vm2/vm-runtime/src/starcoin_vm.rs (1)
  • execute_block_transactions (1129-1321)
vm2/vm-types/src/on_chain_config/mod.rs (1)
  • epoch (132-134)
executor/src/block_executor.rs (1)
  • block_execute (65-147)
vm2/executor/src/block_executor.rs (1)
  • block_execute (36-99)
miner/src/create_block_template/block_builder_service.rs (1)
chain/src/txn_output_cache.rs (3)
  • global_block_state_cache (133-135)
  • new (36-48)
  • new (69-71)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build and test
🔇 Additional comments (9)
chain/src/txn_output_cache.rs (2)

50-56: LGTM: Defensive completeness check prevents partial cache usage.

is_complete() ensures all four fields are populated before a cache entry is used, preventing partial/incomplete data from being applied during execution. This guards against cache corruption or incomplete finalization.


131-135: LGTM: Global cache accessor is clean and thread-safe.

The Lazy<BlockStateCache> pattern with global_block_state_cache() accessor provides a clean singleton for process-wide caching. DashMap ensures thread-safe concurrent access without explicit locking.

chain/open-block/src/vm2.rs (1)

175-201: LGTM: VM2 execution data tracking aligns with caching flow.

Lines 175-177 document the VM2 output structure (no write_sets field in BlockExecutedData2), and lines 200-201 capture txn_info and events for later use in FinalizedBlock.executed_data2. This mirrors the VM1 bookkeeping pattern and complements the new caching pathway introduced in this PR.

miner/src/create_block_template/block_builder_service.rs (1)

611-633: LGTM: Finalized block caching integrated cleanly into mining flow.

The refactor from template to finalized (line 611) extracts the BlockTemplate at line 619 while preserving the existing callback API. Lines 625-632 cache the full finalized state (both VM1/VM2 statedb and executed_data) keyed by txn_accumulator_root, enabling the executor to skip re-execution when this mined block arrives. Caching before the callback (line 636) ensures the entry is available when needed.

chain/open-block/src/lib.rs (3)

44-55: LGTM: FinalizedBlock struct provides complete execution artifacts for caching.

The struct captures all data required to skip re-execution: the BlockTemplate, both VM statedbs (with write_sets already applied and committed but not flushed), and BlockExecutedData for VM1/VM2. This design enables the mining-to-execution fast path introduced in this PR.


333-344: LGTM: Table_infos and write_sets tracked for VM1 execution.

Lines 333-341 extract and accumulate table_infos (merging updates by TableHandle), and line 344 captures the write_set. This addresses the past review concern about missing table_infos in cached execution, ensuring VM1's BlockExecutedData is complete.


417-433: LGTM: BlockExecutedData construction is complete for both VMs.

VM1's executed_data (lines 418-424) includes txn_infos, txn_events, txn_table_infos, and write_sets. VM2's executed_data2 (lines 428-433) omits write_sets (not part of BlockExecutedData2) and uses empty txn_table_infos per the VM2 design (line 427 comment). This aligns with the dual-VM architecture and provides complete data for caching.

chain/src/chain.rs (2)

2273-2315: LGTM: Cache retrieval and fallback execution logic is sound.

Lines 2275-2276 retrieve and remove the cached entry by txn_accumulator_root. Lines 2280-2291 use the cached statedb, statedb2, and executed_data if is_complete() is true, avoiding re-execution. Lines 2293-2314 fall back to live execution if the cache is partial or missing. The tuple unpacking at line 2279 ensures cached_statedb and cached_statedb2 are Some only when the fast path is taken, enabling correct flush logic downstream (lines 2319-2330).


2317-2330: LGTM: Flush logic correctly distinguishes cached vs non-cached paths.

Lines 2319-2324 flush the cached statedbs when cached_statedb and cached_statedb2 are Some (only set when cache hit and complete, per lines 2289-2290). Lines 2325-2329 flush self.statedb otherwise (cache miss or partial, executors have already applied/committed write_sets per lines 2326-2327 comment). This addresses the past review concern about flush logic and is now correct.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
chain/src/chain.rs (1)

2267-2271: Fix misleading comment and add state positioning validation

The comment at line 2257 incorrectly states "Fork statedb from selected parent's state root" but the code at lines 2267-2268 does not fork—it directly references self.statedb. According to the design intent from PR 4722, the BlockChain instance's state must be pre-positioned at the selected parent's state roots before execute_dag_block is called, with validation to fail if state is incorrectly positioned.

The code currently:

  1. Uses statedb directly without verification it's at the parent's state root
  2. Has a misleading comment claiming a fork that doesn't occur
  3. Relies entirely on callers to ensure correct state positioning with no defensive validation

Either position state at the parent's state root before line 2267 with validation, or remove the misleading comment and document that callers must ensure correct state positioning.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b29e3b2 and 48496ea.

📒 Files selected for processing (1)
  • chain/src/chain.rs
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2024-09-30T09:31:42.793Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4194
File: flexidag/src/blockdag.rs:444-446
Timestamp: 2024-09-30T09:31:42.793Z
Learning: In the service, `get_dag_state` is used to get the current state of the chain and it passes the main header ID to `BlockDAG`.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-09-30T01:46:29.197Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: chain/open-block/src/vm2.rs:44-45
Timestamp: 2025-09-30T01:46:29.197Z
Learning: In the Starcoin blockchain (file: chain/open-block/src/vm2.rs), block metadata transaction gas consumption is intentionally NOT counted toward the block gas limit (self.gas_used is not incremented for metadata transactions in initialize() method). Only user transactions in push_txns2() update self.gas_used.

Applied to files:

  • chain/src/chain.rs
🧬 Code graph analysis (1)
chain/src/chain.rs (5)
chain/src/txn_output_cache.rs (1)
  • global_block_state_cache (133-135)
vm2/vm-runtime/src/starcoin_vm.rs (1)
  • execute_block_transactions (1129-1321)
executor/src/executor.rs (2)
  • execute_block_transactions (24-31)
  • execute_transactions (14-20)
executor/src/block_executor.rs (1)
  • block_execute (65-147)
vm2/executor/src/block_executor.rs (1)
  • block_execute (36-99)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build and test
🔇 Additional comments (2)
chain/src/chain.rs (2)

7-7: LGTM - Cache import added

The import of global_block_state_cache is necessary for the new caching functionality in execute_dag_block.


2169-2212: LGTM - Well-structured helper function

The type alias and helper function are well-implemented:

  • Type alias improves readability for dual-VM execution
  • Helper correctly handles empty VM1 transactions with dummy BlockExecutedData
  • Gas limit calculation ensures VM2 gets the remaining gas budget after VM1 execution
  • Return tuple provides both VM1 and VM2 execution results

Copy link

@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)
chain/src/txn_output_cache.rs (2)

82-98: Consider using an LRU or FIFO eviction policy instead of arbitrary iteration order.

The current eviction logic uses iter().take(max_size / 2) which depends on DashMap's internal iteration order—not insertion time or access time. This can unpredictably evict recently inserted or frequently accessed entries.

For more deterministic behavior, consider one of:

  • LRU: Track access time and evict least-recently-used entries
  • FIFO: Track insertion order and evict oldest entries
  • Time-based TTL: Evict entries older than a threshold
💡 Example: Insertion-order tracking with a VecDeque

Add an insertion order tracker:

 pub struct BlockStateCache {
     cache: DashMap<HashValue, CachedBlockState>,
+    insertion_order: std::sync::Mutex<std::collections::VecDeque<HashValue>>,
     max_size: usize,
 }

Update eviction to remove oldest entries:

     pub fn insert(&self, txn_accumulator_root: HashValue, state: CachedBlockState) {
         // Evict entries if cache is full
         if self.cache.len() >= self.max_size {
-            let to_remove: Vec<_> = self
-                .cache
-                .iter()
-                .take(self.max_size / 2)
-                .map(|entry| *entry.key())
-                .collect();
+            let mut order = self.insertion_order.lock().unwrap();
+            let to_remove: Vec<_> = order.drain(..self.max_size / 2).collect();
             for key in to_remove {
                 self.cache.remove(&key);
             }
             log::debug!(
                 "BlockStateCache evicted entries, new size: {}",
                 self.cache.len()
             );
         }
+        let mut order = self.insertion_order.lock().unwrap();
+        order.push_back(txn_accumulator_root);
         self.cache.insert(txn_accumulator_root, state);
     }

Also update remove() and clear() to maintain the insertion order.


13-15: Consider adding cache observability metrics.

To better understand cache effectiveness and tune the size limit, consider adding metrics such as:

  • Cache hit/miss rate
  • Eviction count and rate
  • Current cache size over time

This would help diagnose if the 16-entry limit is appropriate for the DAG mining workload, or if it needs adjustment.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48496ea and cb1de8c.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
  • node/Cargo.toml
  • node/src/node.rs
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-09-01T03:56:58.362Z
Learnt from: welbon
Repo: starcoinorg/starcoin PR: 4633
File: vm/vm-runtime/Cargo.toml:48-48
Timestamp: 2025-09-01T03:56:58.362Z
Learning: In the Starcoin codebase, vm1 (starcoin-vm1-vm-runtime) may need to expose the same feature flags as vm2 to satisfy cross-version compatibility requirements when downstream projects like genesis depend on features from both versions. Empty feature declarations like `move-unit-test = []` may be intentionally added for compilation compatibility rather than to activate specific functionality.

Applied to files:

  • node/Cargo.toml
📚 Learning: 2025-09-14T01:27:57.535Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: rpc/server/src/module/debug_rpc.rs:87-90
Timestamp: 2025-09-14T01:27:57.535Z
Learning: For debug/dev commands in the Starcoin codebase, the preference is to keep the implementation simple without excessive validation or access control checks, focusing on functionality rather than restrictive safeguards.

Applied to files:

  • node/Cargo.toml
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • node/src/node.rs
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • node/src/node.rs
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-07-03T03:21:32.104Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: vm/types/src/block_metadata/mod.rs:47-48
Timestamp: 2025-07-03T03:21:32.104Z
Learning: In the starcoin repository, the BlockMetadata structure changes are part of a clean slate implementation with no legacy data that needs to be deserialized, so backward compatibility concerns for field type changes are not applicable.

Applied to files:

  • node/src/node.rs
📚 Learning: 2025-09-14T15:08:48.415Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4648
File: miner/tests/miner_test.rs:87-93
Timestamp: 2025-09-14T15:08:48.415Z
Learning: In Starcoin test files (like miner/tests/miner_test.rs), jackzhhuang prefers to use std::thread::sleep for intentional blocking delays in test scenarios, even within actor event handlers, rather than using non-blocking ctx.run_later alternatives.

Applied to files:

  • node/src/node.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • node/src/node.rs
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • node/src/node.rs
  • chain/src/lib.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • chain/src/lib.rs
📚 Learning: 2025-09-30T01:46:29.197Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: chain/open-block/src/vm2.rs:44-45
Timestamp: 2025-09-30T01:46:29.197Z
Learning: In the Starcoin blockchain (file: chain/open-block/src/vm2.rs), block metadata transaction gas consumption is intentionally NOT counted toward the block gas limit (self.gas_used is not incremented for metadata transactions in initialize() method). Only user transactions in push_txns2() update self.gas_used.

Applied to files:

  • chain/src/lib.rs
📚 Learning: 2025-09-16T01:30:06.195Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4658
File: vm2/vm-runtime/src/parallel_executor/mod.rs:0-0
Timestamp: 2025-09-16T01:30:06.195Z
Learning: In the Starcoin codebase, the Display schema for AccessPath is stable, making string-based matching via format!("{}", path) a reliable approach for identifying specific resource paths like the STC transaction fee aggregator.

Applied to files:

  • chain/src/lib.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/txn_output_cache.rs
🧬 Code graph analysis (2)
node/src/node.rs (1)
chain/src/txn_output_cache.rs (1)
  • clear_global_block_state_cache (140-143)
chain/src/lib.rs (1)
chain/src/txn_output_cache.rs (2)
  • clear_global_block_state_cache (140-143)
  • global_block_state_cache (133-135)
🔇 Additional comments (10)
node/Cargo.toml (1)

15-15: LGTM!

The addition of starcoin-chain dependency is necessary to enable the global cache management functionality in the node shutdown flow.

node/src/node.rs (2)

16-16: LGTM!

Import added to support global cache cleanup during node shutdown.


447-449: Correct shutdown ordering: cache cleanup before registry shutdown.

Clearing the global block state cache before registry shutdown ensures that database connections held by cached state objects are released cleanly, preventing lock file issues on restart.

chain/src/lib.rs (2)

6-6: LGTM!

Module declaration exposes the transaction output cache as part of the chain crate's public API.


13-15: LGTM!

Public re-exports enable downstream crates (like the node crate) to access cache management functions and types.

chain/src/txn_output_cache.rs (5)

1-15: LGTM!

Module setup and imports are appropriate. The cache size limit of 16 entries is conservative, which is reasonable for the initial implementation.


17-57: LGTM!

CachedBlockState structure cleanly encapsulates both VM1 and VM2 state along with their execution data. The is_complete() method provides a convenient validation check.


102-129: LGTM!

The get, remove, len, is_empty, clear, and Default implementations are straightforward and correct.


131-143: LGTM!

Global singleton with clear documentation about its purpose during shutdown. The logging in clear_global_block_state_cache() aids debugging.


145-193: LGTM!

Unit tests cover the core operations: insert, get, remove, and global cache access. These provide good coverage of the basic cache semantics.

Copy link

@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

🧹 Nitpick comments (1)
chain/src/txn_output_cache.rs (1)

146-194: Consider adding an eviction test.

The existing tests cover basic insert/get/remove operations, but there's no test that validates the eviction behavior when the cache exceeds max_size. Adding a test that inserts more than MAX_BLOCK_STATE_CACHE_SIZE entries and verifies eviction would increase confidence in the cache's behavior under load.

Example eviction test
#[test]
fn test_block_state_cache_eviction() {
    let cache = BlockStateCache::with_capacity(4);
    let mut roots = vec![];
    
    // Insert 5 entries into a cache with capacity 4
    for _ in 0..5 {
        let txn_root = HashValue::random();
        roots.push(txn_root);
        let state = CachedBlockState::new(None, None, None, None);
        cache.insert(txn_root, state);
    }
    
    // Cache should have evicted some entries to stay near capacity
    assert!(cache.len() <= 4);
    
    // At least one entry should still be present
    assert!(!cache.is_empty());
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43be63c and 5e7151c.

📒 Files selected for processing (2)
  • chain/src/txn_output_cache.rs
  • node/src/node.rs
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • node/src/node.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • node/src/node.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-14T15:08:48.415Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4648
File: miner/tests/miner_test.rs:87-93
Timestamp: 2025-09-14T15:08:48.415Z
Learning: In Starcoin test files (like miner/tests/miner_test.rs), jackzhhuang prefers to use std::thread::sleep for intentional blocking delays in test scenarios, even within actor event handlers, rather than using non-blocking ctx.run_later alternatives.

Applied to files:

  • node/src/node.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • node/src/node.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • node/src/node.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • chain/src/txn_output_cache.rs
🧬 Code graph analysis (2)
node/src/node.rs (1)
chain/src/txn_output_cache.rs (1)
  • clear_global_block_state_cache (137-144)
chain/src/txn_output_cache.rs (2)
executor/src/block_executor.rs (1)
  • default (45-53)
vm2/executor/src/block_executor.rs (1)
  • default (26-33)
🔇 Additional comments (5)
node/src/node.rs (2)

16-16: LGTM: Cache cleanup import added.

The import of clear_global_block_state_cache integrates the cache cleanup mechanism into the node shutdown flow.


447-448: Cache clearing order is safe—services do not access the cache during shutdown.

The global block state cache is cleared before registry.shutdown_system_sync(), which is correct. Services only access the cache during normal block processing in the miner and chain modules. The stopped() lifecycle handlers (called during shutdown) only unsubscribe from events and do not attempt cache lookups, so cache misses are not a practical concern here.

chain/src/txn_output_cache.rs (3)

1-15: LGTM: Imports and cache size constant.

The imports are appropriate for the caching implementation. A limit of 16 entries is reasonable for a mining block template cache.


131-144: LGTM: Global cache singleton and clear function.

The global singleton uses Lazy for thread-safe initialization, and clear_global_block_state_cache() logs the entry count before clearing, which aids observability during shutdown.


50-56: The concern about is_complete() is not supported by the codebase. The system design requires both VMs to be executed and cached together. The only production construction of CachedBlockState (in block_builder_service.rs) always populates all four fields with Some() values. The strict check in is_complete() is appropriate and correctly prevents use of partial cache entries.

Likely an incorrect or invalid review comment.

Copy link

@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)
chain/src/txn_output_cache.rs (1)

80-100: Doc comment is misleading; eviction is arbitrary, not age-based.

The comment on line 81 says "older entries are evicted" but DashMap::iter() provides no ordering guarantees. The iteration order is arbitrary, not based on insertion time. Consider updating the comment to accurately reflect the behavior:

-    /// Insert cached state using txn_accumulator_root as key.
-    /// If cache exceeds max_size, older entries are evicted.
+    /// Insert cached state using txn_accumulator_root as key.
+    /// If cache exceeds max_size, arbitrary entries are evicted (not LRU/FIFO).

Note: The race condition and non-deterministic eviction issues were flagged in previous reviews. Given the small cache size (16 entries) and the specific use case (miner block templates), the impact is limited, but the doc comment should at least be accurate.

🧹 Nitpick comments (1)
chain/src/txn_output_cache.rs (1)

162-209: Tests cover basic operations; consider adding eviction test.

The existing tests validate core CRUD functionality and correctly handle potential test interference via delta checks. Consider adding a test that validates eviction behavior when the cache exceeds max_size:

🔎 Suggested test for eviction
#[test]
fn test_block_state_cache_eviction() {
    let cache = BlockStateCache::with_capacity(4);
    
    // Insert more than max_size entries
    for _ in 0..6 {
        let txn_root = HashValue::random();
        let state = CachedBlockState::new(None, None, None, None);
        cache.insert(txn_root, state);
    }
    
    // Cache should have evicted entries and not exceed max_size + 1
    assert!(cache.len() <= 4);
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e7151c and 9ba729b.

📒 Files selected for processing (4)
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
  • miner/src/create_block_template/block_builder_service.rs
  • node/src/node.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • node/src/node.rs
  • chain/src/lib.rs
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-07-03T03:21:32.104Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: vm/types/src/block_metadata/mod.rs:47-48
Timestamp: 2025-07-03T03:21:32.104Z
Learning: In the starcoin repository, the BlockMetadata structure changes are part of a clean slate implementation with no legacy data that needs to be deserialized, so backward compatibility concerns for field type changes are not applicable.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-14T15:08:48.415Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4648
File: miner/tests/miner_test.rs:87-93
Timestamp: 2025-09-14T15:08:48.415Z
Learning: In Starcoin test files (like miner/tests/miner_test.rs), jackzhhuang prefers to use std::thread::sleep for intentional blocking delays in test scenarios, even within actor event handlers, rather than using non-blocking ctx.run_later alternatives.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-05-28T10:21:10.718Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4540
File: chain/src/chain.rs:1816-1824
Timestamp: 2025-05-28T10:21:10.718Z
Learning: In chain/src/chain.rs, the total_blocks calculation for epoch statistics always results in at least 1 block because total_selectd_chain_blocks = (current_block_number - epoch_start_block_number) + 1, which is always >= 1, making division by zero impossible in the avg_total_difficulty calculation.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-08-08T10:27:43.881Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: txpool/src/pool/queue.rs:389-392
Timestamp: 2025-08-08T10:27:43.881Z
Learning: In starcoinorg/starcoin#4605, txpool/src/pool/queue.rs: For PendingOrdering::Priority, if pool.try_read() fails, the design is to return an empty Vec and proceed with block building without waiting for the lock (non-blocking behavior is intentional).

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/txn_output_cache.rs
🧬 Code graph analysis (1)
miner/src/create_block_template/block_builder_service.rs (1)
chain/src/txn_output_cache.rs (4)
  • global_block_state_cache (136-138)
  • is_node_shutting_down (140-142)
  • new (36-48)
  • new (69-71)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build and test
🔇 Additional comments (5)
miner/src/create_block_template/block_builder_service.rs (3)

8-11: LGTM!

Imports correctly bring in the global cache, shutdown flag, and cached state type from the chain module.


541-543: Shutdown guards are well-placed.

The shutdown checks at each major processing stage (block creation, VM1/VM2 transaction processing, epilogue, finalization, template notification) ensure graceful termination without leaving work half-done. This pattern prevents wasted computation during node shutdown.

Also applies to: 569-571, 592-594, 614-616, 628-630, 640-642


632-658: Finalization and caching logic is correct.

The flow properly:

  1. Obtains the finalized result containing all execution outputs
  2. Extracts the template for the callback
  3. Caches the complete state (both StateDbs and executed data) before notifying

Using txn_accumulator_root as the cache key is appropriate since the block_id isn't known until mining completes (it includes the nonce).

chain/src/txn_output_cache.rs (2)

17-57: Well-designed cache entry structure.

The documentation clearly explains the caching purpose. Using Option for all fields allows partial states while is_complete() provides a convenient validation check.


131-160: Global utilities are correctly implemented.

The Lazy initialization ensures the cache is created once on first access. The shutdown flag uses SeqCst ordering which is conservative but ensures visibility across all threads. The clear_global_block_state_cache() correctly couples shutdown signaling with cache clearing.

Copy link

@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)
chain/src/txn_output_cache.rs (2)

82-100: Race condition in eviction logic (known issue).

As noted in past reviews, the size check (line 84) and insert (line 99) are not atomic. Concurrent inserts can all observe len >= max_size, all evict entries, and all insert. This can cause:

  1. Cache temporarily exceeding max_size
  2. More entries evicted than necessary

Given the small cache size (16) and low contention (only miner inserts), this is low risk but worth documenting or addressing in future.


85-90: Eviction order is non-deterministic (known issue).

DashMap::iter() provides arbitrary iteration order. The eviction doesn't target oldest or least-useful entries. For this use case (small cache, mined blocks), arbitrary eviction is acceptable but not ideal.

Consider tracking insertion order if LRU-like behavior becomes important.

🧹 Nitpick comments (5)
rpc/client/tests/client_server_test.rs (1)

79-80: Consider verifying the increased wait time is necessary.

The 10-second wait (increased from 3 seconds) likely accommodates the new global state cache clearing, dual-VM execution shutdown, and Rayon thread pool coordination introduced in this PR. While this change is reasonable given the added cleanup complexity, it increases test runtime by 7 seconds.

Consider verifying that 10 seconds is the minimum necessary duration, or explore a more deterministic approach such as polling for shutdown completion with a timeout:

💡 Optional: More robust shutdown wait pattern
// Example pattern (if shutdown status API is available):
let mut attempts = 0;
while attempts < 100 && !is_fully_shutdown() {
    std::thread::sleep(Duration::from_millis(100));
    attempts += 1;
}
if attempts >= 100 {
    warn!("Shutdown wait timed out after 10s");
}

Note: This is only beneficial if there's a way to query shutdown completion status. If not available, the current fixed sleep is acceptable given the new shutdown complexity.

Based on learnings, using std::thread::sleep for intentional blocking delays in tests aligns with the preferred approach in this codebase.

vm2/executor/src/block_executor.rs (1)

100-154: Function correctly implements cached execution path.

The block_execute_with_outputs function properly mirrors the block_execute logic but uses pre-computed outputs, enabling state cache reuse. The control flow, error handling, and boundary commit logic (lines 130-138) are consistent with the existing function.

Optional: Consider extracting shared logic to reduce duplication

Lines 100-154 largely duplicate the core processing loop from block_execute (lines 55-95). You could extract the common logic into a helper function that both paths call:

fn process_outputs<S: ChainStateReader + ChainStateWriter + Sync>(
    chain_state: &S,
    txns: &[Transaction],
    outputs: Vec<TransactionOutput>,
) -> ExecutorResult<BlockExecutedData> {
    let mut executed_data = BlockExecutedData::default();
    let last_index = outputs
        .len()
        .checked_sub(1)
        .ok_or_else(|| BlockExecutorError::BlockTransactionZero)?;
    
    for (index, (txn, output)) in txns
        .iter()
        .take(outputs.len())
        .zip(outputs.into_iter())
        .enumerate()
    {
        // ... shared processing logic ...
    }
    
    executed_data.state_root = chain_state.state_root();
    Ok(executed_data)
}

pub fn block_execute<S: ChainStateReader + ChainStateWriter + Sync>(
    chain_state: &S,
    txns: Vec<Transaction>,
    block_gas_limit: u64,
    vm_metrics: Option<VMMetrics>,
) -> ExecutorResult<BlockExecutedData> {
    let txn_outputs = do_execute_block_transactions(
        chain_state,
        txns.clone(),
        Some(block_gas_limit),
        vm_metrics,
    )?;
    process_outputs(chain_state, &txns, txn_outputs)
}

pub fn block_execute_with_outputs<S: ChainStateReader + ChainStateWriter + Sync>(
    chain_state: &S,
    txns: Vec<Transaction>,
    outputs: Vec<TransactionOutput>,
) -> ExecutorResult<BlockExecutedData> {
    process_outputs(chain_state, &txns, outputs)
}

This reduces maintenance burden and ensures both paths stay synchronized.

executor/src/block_executor.rs (2)

148-203: Function correctly implements cached execution path for VM1.

The block_execute_with_outputs function properly mirrors the VM1 block_execute logic but uses pre-computed outputs. The VM1-specific handling of write_sets (line 196) is correctly preserved.

Optional: Extract shared logic to reduce duplication

Lines 148-203 largely duplicate the core processing loop from block_execute (lines 84-143). Consider extracting the common processing logic into a helper function that both execution paths can call, similar to the suggestion for the VM2 version. This would reduce maintenance burden and ensure consistency.


167-167: Consider adding balance event logging for consistency.

In block_execute (lines 92-105), large STC balance events are logged for monitoring. The block_execute_with_outputs function skips this logging entirely.

If balance event monitoring is important for observability, consider adding the same logging logic here:

     let txn_hash = txn.id();
     let (mut table_infos, write_set, events, gas_used, status) = output.into_inner();
+    let balance_amount = get_logger_balance_amount();
+    let _ = events.iter().any(|e| {
+        if let Ok(balance_event) = BalanceEvent::try_from(e) {
+            let res = balance_event.token_code() == &G_STC_TOKEN_CODE.clone()
+                && balance_event.amount()
+                    > STCUnit::STC.value_of(balance_amount as u128).scaling();
+            if res {
+                warn!("Logging Event: txn_hash {}, {}", txn_hash, balance_event);
+            }
+            res
+        } else {
+            false
+        }
+    });
     match status {

Alternatively, if cached execution shouldn't trigger monitoring alerts (to avoid duplicate logs), document this design decision with a comment.

chain/src/chain.rs (1)

2172-2212: Potential underflow in VM2 gas limit calculation.

Line 2207 subtracts vm1_gas_used from epoch.block_gas_limit(). If VM1 somehow used more gas than the limit (edge case), this could underflow. Consider using saturating_sub.

🔎 Proposed fix
-        epoch.block_gas_limit() - vm1_gas_used,
+        epoch.block_gas_limit().saturating_sub(vm1_gas_used),
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ba729b and e5e82dc.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • starcoin.log is excluded by !**/*.log
📒 Files selected for processing (18)
  • benchmark_stats_20251208_125237.txt
  • chain/Cargo.toml
  • chain/open-block/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
  • executor/src/block_executor.rs
  • executor/src/lib.rs
  • miner/src/create_block_template/block_builder_service.rs
  • node/Cargo.toml
  • node/src/node.rs
  • rpc/client/tests/client_server_test.rs
  • sync/src/tasks/block_sync_task.rs
  • txpool/src/test.rs
  • vm2/chain/src/lib.rs
  • vm2/executor/src/block_executor.rs
  • vm2/executor/src/lib.rs
✅ Files skipped from review due to trivial changes (1)
  • benchmark_stats_20251208_125237.txt
🚧 Files skipped from review as they are similar to previous changes (7)
  • chain/src/lib.rs
  • vm2/chain/src/lib.rs
  • sync/src/tasks/block_sync_task.rs
  • vm2/executor/src/lib.rs
  • txpool/src/test.rs
  • executor/src/lib.rs
  • node/Cargo.toml
🧰 Additional context used
🧠 Learnings (17)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/open-block/src/vm2.rs
  • vm2/executor/src/block_executor.rs
  • chain/Cargo.toml
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/block_executor.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • chain/open-block/src/vm2.rs
  • vm2/executor/src/block_executor.rs
  • node/src/node.rs
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/block_executor.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • chain/open-block/src/vm2.rs
  • vm2/executor/src/block_executor.rs
  • node/src/node.rs
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/block_executor.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-09-30T01:46:29.197Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: chain/open-block/src/vm2.rs:44-45
Timestamp: 2025-09-30T01:46:29.197Z
Learning: In the Starcoin blockchain (file: chain/open-block/src/vm2.rs), block metadata transaction gas consumption is intentionally NOT counted toward the block gas limit (self.gas_used is not incremented for metadata transactions in initialize() method). Only user transactions in push_txns2() update self.gas_used.

Applied to files:

  • chain/open-block/src/vm2.rs
  • executor/src/block_executor.rs
  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-09-01T03:56:58.362Z
Learnt from: welbon
Repo: starcoinorg/starcoin PR: 4633
File: vm/vm-runtime/Cargo.toml:48-48
Timestamp: 2025-09-01T03:56:58.362Z
Learning: In the Starcoin codebase, vm1 (starcoin-vm1-vm-runtime) may need to expose the same feature flags as vm2 to satisfy cross-version compatibility requirements when downstream projects like genesis depend on features from both versions. Empty feature declarations like `move-unit-test = []` may be intentionally added for compilation compatibility rather than to activate specific functionality.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/Cargo.toml
  • node/src/node.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • chain/open-block/src/vm2.rs
  • chain/Cargo.toml
  • executor/src/block_executor.rs
  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • node/src/node.rs
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/block_executor.rs
  • chain/src/chain.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-07-03T03:21:32.104Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: vm/types/src/block_metadata/mod.rs:47-48
Timestamp: 2025-07-03T03:21:32.104Z
Learning: In the starcoin repository, the BlockMetadata structure changes are part of a clean slate implementation with no legacy data that needs to be deserialized, so backward compatibility concerns for field type changes are not applicable.

Applied to files:

  • node/src/node.rs
  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-09-14T15:08:48.415Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4648
File: miner/tests/miner_test.rs:87-93
Timestamp: 2025-09-14T15:08:48.415Z
Learning: In Starcoin test files (like miner/tests/miner_test.rs), jackzhhuang prefers to use std::thread::sleep for intentional blocking delays in test scenarios, even within actor event handlers, rather than using non-blocking ctx.run_later alternatives.

Applied to files:

  • node/src/node.rs
  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/chain.rs
  • rpc/client/tests/client_server_test.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • node/src/node.rs
  • miner/src/create_block_template/block_builder_service.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-05-28T10:21:10.718Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4540
File: chain/src/chain.rs:1816-1824
Timestamp: 2025-05-28T10:21:10.718Z
Learning: In chain/src/chain.rs, the total_blocks calculation for epoch statistics always results in at least 1 block because total_selectd_chain_blocks = (current_block_number - epoch_start_block_number) + 1, which is always >= 1, making division by zero impossible in the avg_total_difficulty calculation.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-08-08T10:27:43.881Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: txpool/src/pool/queue.rs:389-392
Timestamp: 2025-08-08T10:27:43.881Z
Learning: In starcoinorg/starcoin#4605, txpool/src/pool/queue.rs: For PendingOrdering::Priority, if pool.try_read() fails, the design is to return an empty Vec and proceed with block building without waiting for the lock (non-blocking behavior is intentional).

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-09-16T01:34:58.198Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4657
File: cmd/starcoin/src/dev/get_coin_cmd.rs:79-90
Timestamp: 2025-09-16T01:34:58.198Z
Learning: In Starcoin's get_coin_cmd.rs batch transaction submission (when repeat > 1), the maintainer lushengguo confirmed the preference for fail-fast behavior using the ? operator - the command should stop on the first transaction failure rather than collecting errors and continuing to submit remaining transactions.

Applied to files:

  • executor/src/block_executor.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2024-09-30T09:31:42.793Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4194
File: flexidag/src/blockdag.rs:444-446
Timestamp: 2024-09-30T09:31:42.793Z
Learning: In the service, `get_dag_state` is used to get the current state of the chain and it passes the main header ID to `BlockDAG`.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-28T08:35:37.355Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.

Applied to files:

  • chain/src/chain.rs
🧬 Code graph analysis (8)
chain/open-block/src/vm2.rs (2)
vm2/vm-types/src/transaction/mod.rs (2)
  • txn_info (966-968)
  • events (790-792)
chain/src/chain.rs (5)
  • events (762-762)
  • events (778-778)
  • events (1592-1595)
  • events (2541-2541)
  • events (2551-2551)
vm2/executor/src/block_executor.rs (3)
vm2/vm-types/src/transaction/mod.rs (8)
  • default (705-707)
  • write_set (786-788)
  • events (790-792)
  • gas_used (794-796)
  • gas_used (902-904)
  • status (641-647)
  • status (798-800)
  • status (906-908)
vm/types/src/transaction/mod.rs (5)
  • write_set (683-685)
  • events (691-693)
  • gas_used (695-697)
  • status (623-629)
  • status (699-701)
types/src/transaction/mod.rs (2)
  • gas_used (102-104)
  • status (106-108)
node/src/node.rs (1)
chain/src/txn_output_cache.rs (2)
  • clear_global_block_state_cache (152-160)
  • reset_node_shutdown_flag (148-150)
miner/src/create_block_template/block_builder_service.rs (1)
chain/src/txn_output_cache.rs (4)
  • global_block_state_cache (136-138)
  • is_node_shutting_down (140-142)
  • new (36-48)
  • new (69-71)
executor/src/block_executor.rs (2)
vm2/vm-types/src/transaction/mod.rs (7)
  • write_set (786-788)
  • events (790-792)
  • gas_used (794-796)
  • gas_used (902-904)
  • status (641-647)
  • status (798-800)
  • status (906-908)
types/src/transaction/mod.rs (2)
  • gas_used (102-104)
  • status (106-108)
chain/src/txn_output_cache.rs (2)
executor/src/block_executor.rs (1)
  • default (45-53)
vm2/executor/src/block_executor.rs (1)
  • default (26-33)
chain/open-block/src/lib.rs (2)
chain/src/chain.rs (13)
  • execute_block_transactions (2172-2212)
  • std (806-806)
  • std (2578-2578)
  • new (89-101)
  • events (762-762)
  • events (778-778)
  • events (1592-1595)
  • events (2541-2541)
  • events (2551-2551)
  • executed_data (742-752)
  • executed_data (961-965)
  • executed_data (2524-2534)
  • executed_data2 (2440-2444)
chain/src/txn_output_cache.rs (2)
  • new (36-48)
  • new (69-71)
rpc/client/tests/client_server_test.rs (1)
rpc/client/src/lib.rs (1)
  • sleep (776-779)
🔇 Additional comments (22)
chain/Cargo.toml (1)

4-4: LGTM! Dependency additions align with caching infrastructure.

The addition of dashmap (concurrent HashMap for state caching) and starcoin-vm2-executor (VM2 execution data structures) are appropriate for the per-block state cache and dual-VM execution paths introduced in this PR.

Also applies to: 38-38

node/src/node.rs (2)

16-16: LGTM! Proper initialization of shutdown flag on node launch.

Calling reset_node_shutdown_flag() at the start of launch ensures the global shutdown flag is cleared to false when the node starts, which is essential for correct behavior after node restarts.

Also applies to: 192-192


448-448: LGTM! Proper cache cleanup during shutdown.

The clear_global_block_state_cache() call at the start of shutdown_system ensures:

  1. The shutdown flag is set (preventing new cache operations)
  2. The global block state cache is properly cleared before registry shutdown

This maintains correct cache lifecycle and prevents stale data on restart.

chain/open-block/src/vm2.rs (2)

175-177: Clear documentation of VM2 design difference.

The comment appropriately clarifies that VM2's BlockExecutedData2 does not include a write_sets field, distinguishing it from VM1's design.


198-201: LGTM! Proper VM2 execution data tracking.

The code correctly records VM2 transaction info and events by pushing them into vm2_txn_infos and vm2_txn_events, enabling downstream finalization to aggregate both VM1 and VM2 execution data.

executor/src/block_executor.rs (1)

160-164: This function is currently unused in the VM1 executor codebase.

The code at lines 160-164 does use .take(outputs.len()).zip() as described, which silently handles length mismatches by truncating to the smaller collection. However, the function block_execute_with_outputs in executor/src/block_executor.rs has no callers in the VM1 codebase—it is only exported from executor/src/lib.rs. The only actual call site is in vm2/chain/src/lib.rs, which calls the VM2 version of this function.

If this function is intended for future use or should be removed, clarify in the implementation. If it will be used, add explicit validation to ensure length equality rather than relying on silent truncation:

if txns.len() != outputs.len() {
    return Err(BlockExecutorError::InvalidOutputCount { 
        expected: txns.len(), 
        actual: outputs.len() 
    });
}
chain/open-block/src/lib.rs (5)

44-55: LGTM! Well-structured FinalizedBlock for caching.

The struct cleanly encapsulates all data needed for cache reuse: the block template, both VM statedbs (with write_sets already applied), and execution data for both VMs. This enables completely skipping re-execution when the mined block is received.


333-344: Good: Correct table_infos merging.

Using BTreeMap::append correctly moves entries from the output's table_infos into the accumulated vm1_table_infos. Later entries with the same TableHandle will overwrite earlier ones, which is the desired behavior.


435-441: LGTM! FinalizedBlock correctly constructed.

The finalize method properly packages all necessary data for caching: template, Arc-wrapped statedbs, and both VM's executed data. The statedbs have write_sets applied but not flushed, ready for cache consumers to flush.


78-86: The VM2 execution data is already being populated correctly. Both vm2_txn_infos and vm2_txn_events are tracked in push_txn_and_state2, which is called from the push_txns2 transaction processing path in vm2.rs. The VM1 and VM2 paths are symmetric in their data tracking approach.


417-433: VM2 intentionally does not track table_infos in its TransactionOutput.

The code correctly sets txn_table_infos: BTreeMap::new() for VM2 on line 432. Unlike VM1, VM2's TransactionOutput structure does not include a table_infos field—this is a design difference between the two VMs. No additional action needed.

miner/src/create_block_template/block_builder_service.rs (2)

540-543: Good: Shutdown guards enable graceful termination.

The early return on shutdown prevents unnecessary work when the node is stopping. This pattern is correctly applied before each major processing step.


632-658: LGTM! Correct cache population order.

The flow is correct: finalize → extract template → cache state → notify callback. The cache is populated with complete data (all fields Some) which matches what is_complete() checks in the consumer.

chain/src/chain.rs (4)

453-455: LGTM! Clean integration with FinalizedBlock.

The template extraction from FinalizedBlock is straightforward and the excluded transactions are correctly merged.


2267-2271: Verify statedb is positioned at correct parent state.

Per learnings, the BlockChain's statedb must be initialized at the selected parent's state roots before execute_dag_block is called. The code references self.statedb.0 and self.statedb.1 directly. Confirm the caller ensures proper state positioning before invocation.

Based on learnings, this design is intentional to avoid performance overhead of repeated state forking.


2279-2315: Cache hit/miss logic is correct.

The code properly distinguishes:

  1. Complete cache hit → use cached executed_data and statedbs
  2. Partial cache → fall back to execution
  3. No cache → execute normally

The is_complete() check ensures all four fields are present before using cached data.


2317-2330: Flush logic correctly handles both paths.

Per previous discussion: when using cached statedbs from the miner, those need to be flushed. When not using cache, the executors (block_execute/execute_transactions) have already applied and committed write_sets to self.statedb, so only flush is needed.

This addresses the past review concern about the flush condition.

chain/src/txn_output_cache.rs (5)

1-15: LGTM! Clear module documentation and appropriate cache size.

The cache size of 16 is reasonable for mined blocks. The comments clearly explain the purpose and design rationale.


23-57: LGTM! CachedBlockState properly encapsulates cached data.

The is_complete() method provides a clean check for full cache availability, enabling the consumer to distinguish complete hits from partial cache states.


131-138: LGTM! Global singleton properly initialized.

Using once_cell::Lazy ensures thread-safe lazy initialization of the global cache.


140-160: LGTM! Shutdown helpers are well-designed.

The SeqCst ordering ensures proper visibility across threads. Setting the shutdown flag before clearing the cache in clear_global_block_state_cache ensures consistent state during shutdown.


162-209: LGTM! Good test coverage for core cache operations.

Tests cover insert/get, remove, and global cache interactions. Consider adding a test for eviction behavior when cache exceeds max_size.

Copy link

@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)
chain/src/txn_output_cache.rs (1)

84-92: Non-deterministic eviction order remains unresolved.

As flagged in previous reviews, DashMap::iter() provides no ordering guarantees (not LRU, FIFO, or insertion order). Using take(max_size / 2) evicts arbitrary entries, which may include recently inserted or frequently accessed cache entries. This can lead to suboptimal cache performance, especially during mining template generation bursts.

Consider one of:

  • Track insertion timestamps and evict oldest entries
  • Use an LRU-aware structure (e.g., lru crate with a DashMap wrapper)
  • Add access-order tracking for frequently used entries
  • Document that eviction is best-effort/arbitrary if acceptable for this workload
🧹 Nitpick comments (5)
chain/src/txn_output_cache.rs (5)

47-53: Consider validating consistency between VM1 and VM2 data in is_complete().

The method only checks field presence but doesn't verify that the VM1 and VM2 data are related or consistent (e.g., same transaction count, matching state roots). While the current implementation may be sufficient, consider adding assertions or validation to catch potential mismatches during cache insertion.


82-82: Handle potential mutex poisoning more gracefully.

The unwrap() on eviction_lock.lock() will panic if the mutex is poisoned (e.g., if a previous thread panicked while holding the lock). While unlikely, this could cause cascading failures during cache operations.

Consider logging and either propagating the error or using expect() with a descriptive message.

🔎 Potential fix using expect with context
-            let _guard = self.eviction_lock.lock().unwrap();
+            let _guard = self.eviction_lock.lock()
+                .expect("BlockStateCache eviction_lock poisoned");

152-160: Mixing concerns: cache clearing and shutdown coordination.

The function clear_global_block_state_cache() both clears the cache and sets the global shutdown flag (line 153). This couples two separate concerns—cache lifecycle and node shutdown state—which may confuse callers who only want to clear the cache without signaling shutdown.

Consider one of:

  • Rename to shutdown_and_clear_cache() to clarify dual purpose
  • Split into two functions: clear_global_block_state_cache() (only clears) and a higher-level shutdown coordinator that calls both
  • Document the shutdown side effect prominently if the coupling is intentional

56-123: Consider adding observability metrics for cache performance.

The cache lacks instrumentation for hit/miss rates, eviction frequency, and size trends. These metrics would be valuable for:

  • Tuning MAX_BLOCK_STATE_CACHE_SIZE
  • Detecting anomalies (e.g., unexpectedly high miss rates)
  • Understanding mining template caching effectiveness

Consider adding:

  • Cache hit/miss counters in get() and remove()
  • Eviction counter in insert()
  • Periodic logging or metrics export of cache statistics

162-210: Consider expanding test coverage for edge cases.

Current tests cover basic insert/get/remove operations but miss:

  • Eviction behavior when cache exceeds MAX_BLOCK_STATE_CACHE_SIZE
  • is_complete() method validation
  • Concurrent access patterns (multiple threads inserting/evicting)
  • Cache behavior with complete vs incomplete CachedBlockState

While not critical, these tests would increase confidence in the caching logic under stress.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5e82dc and a911641.

📒 Files selected for processing (1)
  • chain/src/txn_output_cache.rs
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • chain/src/txn_output_cache.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • chain/src/txn_output_cache.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • chain/src/txn_output_cache.rs
🧬 Code graph analysis (1)
chain/src/txn_output_cache.rs (2)
executor/src/block_executor.rs (1)
  • default (45-53)
vm2/executor/src/block_executor.rs (1)
  • default (26-33)
🔇 Additional comments (1)
chain/src/txn_output_cache.rs (1)

80-100: Race condition resolved with eviction lock.

The double-checked locking pattern using eviction_lock correctly addresses the race condition flagged in previous reviews. Multiple threads now synchronize on eviction, preventing the cache from exceeding max_size during concurrent inserts.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
vm2/types/src/error/block_executor.rs (1)

36-37: LGTM! Clear error variant for output count validation.

The new InvalidOutputCount variant is well-structured with appropriate field types and a descriptive error message that clearly communicates the mismatch.

Optional: Consider enhancing message specificity

For even greater clarity, you could make explicit what expected and actual represent:

-    #[error("the number of transaction outputs is not equal to the number of transactions, expected length: {expected}, actual length: {actual}")]
+    #[error("the number of transaction outputs is not equal to the number of transactions, expected: {expected} transactions, actual: {actual} outputs")]

However, the current message is already quite clear in context.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a911641 and 6bb22e7.

📒 Files selected for processing (2)
  • vm2/executor/src/block_executor.rs
  • vm2/types/src/error/block_executor.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • vm2/executor/src/block_executor.rs
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.

Copy link

@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

🤖 Fix all issues with AI agents
In @chain/src/chain.rs:
- Around line 2267-2330: The cached-path flushes cached_db/cached_db2 but never
updates self.statedb, so the Chain's active state remains stale; after
confirming cached_statedb and cached_statedb2 are Some, assign the cached Arcs
back into self.statedb (e.g., self.statedb = (cached_db.clone(),
cached_db2.clone()) or set the tuple fields) so the chain uses the cached state
for subsequent operations; also avoid holding long-lived &self.statedb borrows
(the local statedb/statedb2 should be clones or obtained after possible
assignment) to satisfy Rust borrow rules.
🧹 Nitpick comments (5)
rpc/client/tests/client_server_test.rs (1)

80-80: Extended wait time for node shutdown and cache cleanup.

The 7-second increase (3s → 10s) accommodates the new per-block state cache clearing and dual-VM execution lifecycle introduced in this PR. The test has a 120-second timeout, so this is acceptable.

Optional: Consider verifying the minimum required wait time

If 10 seconds proves consistently sufficient, consider whether a shorter duration (e.g., 5-7 seconds) might work with the new cache management, or document why 10 seconds is necessary. This could be validated with repeated test runs.

vm2/chain/src/lib.rs (1)

27-34: LGTM! Clean delegation pattern.

The new execute_transactions_with_outputs function properly delegates to the executor layer and maintains separation of concerns. Error handling is appropriately deferred to block_execute_with_outputs.

📝 Optional: Consider adding a doc comment

Adding a brief doc comment would clarify when to use this function versus execute_transactions:

+/// Execute transactions with pre-computed outputs from cache.
+/// Use this when outputs are already available to skip re-execution.
 pub fn execute_transactions_with_outputs(
     statedb: &ChainStateDB,
     transactions: Vec<Transaction>,
     outputs: Vec<TransactionOutput>,
 ) -> ExecutorResult<BlockExecutedData> {
vm2/executor/src/block_executor.rs (1)

118-160: Implementation correctly handles pre-computed outputs.

The function properly:

  • Applies write sets to chain state
  • Commits at first and last transaction indices (per established pattern)
  • Creates TransactionInfo entries with appropriate state roots
  • Handles all transaction status cases (Keep/Discard/Retry)
  • Updates final state root
♻️ Consider extracting shared logic

Lines 118-157 duplicate significant logic from block_execute (lines 55-95). Consider extracting the shared transaction processing loop into a helper function to reduce duplication and improve maintainability:

fn process_transaction_output<S: ChainStateReader + ChainStateWriter>(
    chain_state: &S,
    index: usize,
    last_index: usize,
    txn_hash: HashValue,
    output: TransactionOutput,
    executed_data: &mut BlockExecutedData,
) -> ExecutorResult<()> {
    let (write_set, events, gas_used, status, _) = output.into_inner();
    match status {
        TransactionStatus::Discard(status) => {
            Err(BlockExecutorError::BlockTransactionDiscard(status, txn_hash))
        }
        TransactionStatus::Keep(status) => {
            chain_state
                .apply_write_set(write_set)
                .map_err(BlockExecutorError::BlockChainStateErr)?;
            let txn_state_root = if index == last_index || index == 0 {
                Some(
                    chain_state
                        .commit()
                        .map_err(BlockExecutorError::BlockChainStateErr)?,
                )
            } else {
                None
            };
            let t = TransactionInfo::new(txn_hash, txn_state_root, events.as_slice(), gas_used, status);
            executed_data.txn_infos.push(t);
            executed_data.txn_events.push(events);
            Ok(())
        }
        TransactionStatus::Retry => Err(BlockExecutorError::BlockExecuteRetryErr),
    }
}

Both block_execute and block_execute_with_outputs could then call this helper in their respective loops.

chain/src/txn_output_cache.rs (2)

79-100: Consider eviction policy for production use.

The eviction logic uses double-checked locking correctly to prevent races. However, the eviction policy (removing arbitrary first half of entries via take()) is non-deterministic and doesn't prioritize recently/frequently used entries.

For the current mining use case with a small cache (16 entries), this simple approach is acceptable. However, if the cache grows or usage patterns change, consider implementing LRU or LFU eviction.


162-210: Tests cover basic functionality; consider adding concurrency tests.

The tests validate core cache operations. For a more robust test suite, consider adding:

  • Concurrent access tests (multiple threads inserting/reading)
  • Eviction behavior tests (filling cache beyond max_size)
  • Cache hit/miss scenarios

For the initial implementation, the current tests are sufficient.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6bb22e7 and 2e6df3c.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • starcoin.log is excluded by !**/*.log
📒 Files selected for processing (19)
  • benchmark_stats_20251208_125237.txt
  • chain/Cargo.toml
  • chain/open-block/src/lib.rs
  • chain/open-block/src/vm2.rs
  • chain/src/chain.rs
  • chain/src/lib.rs
  • chain/src/txn_output_cache.rs
  • executor/src/block_executor.rs
  • executor/src/lib.rs
  • miner/src/create_block_template/block_builder_service.rs
  • node/Cargo.toml
  • node/src/node.rs
  • rpc/client/tests/client_server_test.rs
  • sync/src/tasks/block_sync_task.rs
  • txpool/src/test.rs
  • vm2/chain/src/lib.rs
  • vm2/executor/src/block_executor.rs
  • vm2/executor/src/lib.rs
  • vm2/types/src/error/block_executor.rs
✅ Files skipped from review due to trivial changes (1)
  • benchmark_stats_20251208_125237.txt
🚧 Files skipped from review as they are similar to previous changes (4)
  • chain/src/lib.rs
  • node/src/node.rs
  • vm2/types/src/error/block_executor.rs
  • node/Cargo.toml
🧰 Additional context used
🧠 Learnings (18)
📓 Common learnings
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.
📚 Learning: 2025-08-08T10:16:46.394Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:185-187
Timestamp: 2025-08-08T10:16:46.394Z
Learning: In starcoin/chain/src/chain.rs, BlockChain::statedb currently consumes self to move out ChainStateDB during block building to avoid cloning or recreating the state DB for transaction filtering. This is intentional for performance; a rename to into_statedb is acceptable but behavior should remain a consuming getter.

Applied to files:

  • vm2/chain/src/lib.rs
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/block_executor.rs
  • chain/open-block/src/vm2.rs
  • txpool/src/test.rs
  • sync/src/tasks/block_sync_task.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-20T09:58:31.897Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4722
File: chain/src/chain.rs:2151-2155
Timestamp: 2025-10-20T09:58:31.897Z
Learning: In starcoin/chain/src/chain.rs, for DAG block execution: the BlockChain instance's state (self.statedb.0 and self.statedb.1) must be initialized/positioned at the selected parent's state roots BEFORE execute_dag_block is called. Execution should validate and fail if state is not correctly positioned, rather than forking state mid-execution. This design avoids the performance overhead of repeated state forking during execution.

Applied to files:

  • vm2/chain/src/lib.rs
  • vm2/executor/src/block_executor.rs
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/lib.rs
  • executor/src/block_executor.rs
  • chain/open-block/src/vm2.rs
  • txpool/src/test.rs
  • sync/src/tasks/block_sync_task.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-10-10T01:54:58.184Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: vm2/executor/src/block_executor.rs:73-81
Timestamp: 2025-10-10T01:54:58.184Z
Learning: In the Starcoin blockchain executor (both VM1 and VM2), committing state at intermediate transactions (e.g., first transaction with `index == 0`) is acceptable because if any subsequent transaction in the block is discarded, the entire block execution aborts and the block won't connect to the chain. Block-level atomicity is maintained at a higher level, ensuring partial blocks are never persisted.

Applied to files:

  • vm2/chain/src/lib.rs
  • vm2/executor/src/block_executor.rs
  • chain/Cargo.toml
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/block_executor.rs
  • chain/open-block/src/vm2.rs
  • txpool/src/test.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-30T01:46:29.197Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4703
File: chain/open-block/src/vm2.rs:44-45
Timestamp: 2025-09-30T01:46:29.197Z
Learning: In the Starcoin blockchain (file: chain/open-block/src/vm2.rs), block metadata transaction gas consumption is intentionally NOT counted toward the block gas limit (self.gas_used is not incremented for metadata transactions in initialize() method). Only user transactions in push_txns2() update self.gas_used.

Applied to files:

  • vm2/chain/src/lib.rs
  • executor/src/block_executor.rs
  • chain/open-block/src/vm2.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-09-16T01:44:35.596Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4656
File: vm2/vm-runtime/src/starcoin_vm.rs:1622-1624
Timestamp: 2025-09-16T01:44:35.596Z
Learning: In the Starcoin codebase parallel execution optimization, passing `&data_cache.as_move_resolver()` instead of `data_cache` directly to `execute_user_transaction` is an intentional efficiency optimization to reduce multiple VM-Config loading calls, not a bug.

Applied to files:

  • vm2/chain/src/lib.rs
  • chain/Cargo.toml
  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/block_executor.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-08-08T10:20:45.797Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: chain/src/chain.rs:1104-1106
Timestamp: 2025-08-08T10:20:45.797Z
Learning: In starcoin/chain/src/chain.rs, ChainReader::get_block(hash) is intentionally implemented to return any block from storage without verifying membership in the current main chain/DAG view (the previous exist_block_filter was removed). Callers that require main-chain-only results should perform their own existence/membership checks (e.g., exist_block/check_exist_block) as needed.

Applied to files:

  • vm2/chain/src/lib.rs
  • miner/src/create_block_template/block_builder_service.rs
  • txpool/src/test.rs
  • sync/src/tasks/block_sync_task.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-16T01:34:58.198Z
Learnt from: lushengguo
Repo: starcoinorg/starcoin PR: 4657
File: cmd/starcoin/src/dev/get_coin_cmd.rs:79-90
Timestamp: 2025-09-16T01:34:58.198Z
Learning: In Starcoin's get_coin_cmd.rs batch transaction submission (when repeat > 1), the maintainer lushengguo confirmed the preference for fail-fast behavior using the ? operator - the command should stop on the first transaction failure rather than collecting errors and continuing to submit remaining transactions.

Applied to files:

  • vm2/executor/src/block_executor.rs
  • executor/src/block_executor.rs
  • chain/open-block/src/lib.rs
📚 Learning: 2025-09-01T03:56:58.362Z
Learnt from: welbon
Repo: starcoinorg/starcoin PR: 4633
File: vm/vm-runtime/Cargo.toml:48-48
Timestamp: 2025-09-01T03:56:58.362Z
Learning: In the Starcoin codebase, vm1 (starcoin-vm1-vm-runtime) may need to expose the same feature flags as vm2 to satisfy cross-version compatibility requirements when downstream projects like genesis depend on features from both versions. Empty feature declarations like `move-unit-test = []` may be intentionally added for compilation compatibility rather than to activate specific functionality.

Applied to files:

  • chain/Cargo.toml
  • miner/src/create_block_template/block_builder_service.rs
  • chain/open-block/src/vm2.rs
📚 Learning: 2025-07-03T03:25:16.732Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: miner/src/create_block_template/new_header_service.rs:0-0
Timestamp: 2025-07-03T03:25:16.732Z
Learning: In Starcoin's miner/src/create_block_template/new_header_service.rs, panic! is intentionally used in impossible code branches (like equal block ID comparison after early equality check) to detect logical errors early and ensure immediate restart rather than allowing potentially corrupted state to continue.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/lib.rs
  • executor/src/block_executor.rs
  • txpool/src/test.rs
  • sync/src/tasks/block_sync_task.rs
  • chain/open-block/src/lib.rs
  • chain/src/chain.rs
📚 Learning: 2025-07-03T03:21:32.104Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4572
File: vm/types/src/block_metadata/mod.rs:47-48
Timestamp: 2025-07-03T03:21:32.104Z
Learning: In the starcoin repository, the BlockMetadata structure changes are part of a clean slate implementation with no legacy data that needs to be deserialized, so backward compatibility concerns for field type changes are not applicable.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-09-14T15:08:48.415Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4648
File: miner/tests/miner_test.rs:87-93
Timestamp: 2025-09-14T15:08:48.415Z
Learning: In Starcoin test files (like miner/tests/miner_test.rs), jackzhhuang prefers to use std::thread::sleep for intentional blocking delays in test scenarios, even within actor event handlers, rather than using non-blocking ctx.run_later alternatives.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
  • executor/src/lib.rs
  • rpc/client/tests/client_server_test.rs
  • chain/src/chain.rs
📚 Learning: 2025-05-28T10:21:10.718Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4540
File: chain/src/chain.rs:1816-1824
Timestamp: 2025-05-28T10:21:10.718Z
Learning: In chain/src/chain.rs, the total_blocks calculation for epoch statistics always results in at least 1 block because total_selectd_chain_blocks = (current_block_number - epoch_start_block_number) + 1, which is always >= 1, making division by zero impossible in the avg_total_difficulty calculation.

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-08-08T10:27:43.881Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4605
File: txpool/src/pool/queue.rs:389-392
Timestamp: 2025-08-08T10:27:43.881Z
Learning: In starcoinorg/starcoin#4605, txpool/src/pool/queue.rs: For PendingOrdering::Priority, if pool.try_read() fails, the design is to return an empty Vec and proceed with block building without waiting for the lock (non-blocking behavior is intentional).

Applied to files:

  • miner/src/create_block_template/block_builder_service.rs
📚 Learning: 2025-04-01T08:16:02.035Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4447
File: sync/src/tasks/block_sync_task.rs:608-620
Timestamp: 2025-04-01T08:16:02.035Z
Learning: In the `fetch_blocks_in_batch` method of `BlockSyncTask`, the filter logic is designed to include block headers where at least one parent exists in the local store but has not yet been integrated into the chain.

Applied to files:

  • sync/src/tasks/block_sync_task.rs
📚 Learning: 2024-09-30T09:31:42.793Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4194
File: flexidag/src/blockdag.rs:444-446
Timestamp: 2024-09-30T09:31:42.793Z
Learning: In the service, `get_dag_state` is used to get the current state of the chain and it passes the main header ID to `BlockDAG`.

Applied to files:

  • chain/src/chain.rs
📚 Learning: 2025-11-26T05:13:52.832Z
Learnt from: simonjiao
Repo: starcoinorg/starcoin PR: 4762
File: storage/src/db_storage/mod.rs:138-168
Timestamp: 2025-11-26T05:13:52.832Z
Learning: In rust-rocksdb, the Cache type is a thin FFI wrapper around RocksDB's C++ cache. The C++ side manages lifetime through reference counting. When Cache is passed to BlockBasedOptions::set_block_cache, the C++ RocksDB increments its internal reference count, so the cache remains alive even after the Rust Cache wrapper is dropped. Therefore, creating a Cache locally in a function and passing it to set_block_cache is safe—the local Rust wrapper can be dropped without causing use-after-free.

Applied to files:

  • chain/src/chain.rs
  • chain/src/txn_output_cache.rs
📚 Learning: 2025-09-28T08:35:37.355Z
Learnt from: jackzhhuang
Repo: starcoinorg/starcoin PR: 4698
File: cmd/tx-factory/src/main.rs:165-165
Timestamp: 2025-09-28T08:35:37.355Z
Learning: In the tx-factory cmd tool, jackzhhuang prefers long unlock durations (multiple hours) for account unlocking rather than the typical short durations like 1 minute.

Applied to files:

  • chain/src/chain.rs
🧬 Code graph analysis (10)
vm2/chain/src/lib.rs (4)
vm/e2e-tests/src/account.rs (1)
  • transaction (161-163)
chain/src/chain.rs (4)
  • transactions (991-994)
  • executed_data (742-752)
  • executed_data (961-965)
  • executed_data (2524-2534)
vm2/executor/src/block_executor.rs (1)
  • block_execute_with_outputs (102-160)
executor/src/block_executor.rs (1)
  • block_execute_with_outputs (150-203)
vm2/executor/src/lib.rs (2)
vm2/executor/src/block_executor.rs (2)
  • block_execute (36-99)
  • block_execute_with_outputs (102-160)
executor/src/block_executor.rs (2)
  • block_execute (65-147)
  • block_execute_with_outputs (150-203)
miner/src/create_block_template/block_builder_service.rs (1)
chain/src/txn_output_cache.rs (4)
  • global_block_state_cache (136-138)
  • is_node_shutting_down (140-142)
  • new (33-45)
  • new (67-69)
executor/src/lib.rs (1)
executor/src/block_executor.rs (2)
  • get_logger_balance_amount (31-33)
  • set_logger_balance_amount_once (24-28)
executor/src/block_executor.rs (3)
vm2/executor/src/block_executor.rs (1)
  • block_execute_with_outputs (102-160)
vm2/vm-types/src/transaction/mod.rs (7)
  • write_set (786-788)
  • events (790-792)
  • gas_used (794-796)
  • gas_used (902-904)
  • status (641-647)
  • status (798-800)
  • status (906-908)
types/src/transaction/mod.rs (2)
  • gas_used (102-104)
  • status (106-108)
chain/open-block/src/vm2.rs (2)
vm2/vm-types/src/transaction/mod.rs (2)
  • txn_info (966-968)
  • events (790-792)
chain/src/chain.rs (5)
  • events (762-762)
  • events (778-778)
  • events (1592-1595)
  • events (2541-2541)
  • events (2551-2551)
chain/open-block/src/lib.rs (3)
chain/src/chain.rs (11)
  • execute_block_transactions (2172-2212)
  • new (89-101)
  • events (762-762)
  • events (778-778)
  • events (1592-1595)
  • events (2541-2541)
  • events (2551-2551)
  • executed_data (742-752)
  • executed_data (961-965)
  • executed_data (2524-2534)
  • executed_data2 (2440-2444)
vm2/chain/src/lib.rs (1)
  • execute_transactions (13-25)
chain/src/txn_output_cache.rs (2)
  • new (33-45)
  • new (67-69)
rpc/client/tests/client_server_test.rs (1)
rpc/client/src/lib.rs (1)
  • sleep (776-779)
chain/src/chain.rs (8)
vm2/vm-runtime/src/starcoin_vm.rs (1)
  • execute_block_transactions (1129-1321)
executor/src/executor.rs (2)
  • execute_block_transactions (24-31)
  • execute_transactions (14-20)
rpc/api/src/types.rs (1)
  • transactions (964-972)
vm2/vm-types/src/on_chain_config/mod.rs (1)
  • epoch (132-134)
vm2/vm-types/src/on_chain_resource/epoch.rs (1)
  • epoch (163-165)
vm2/executor/src/block_executor.rs (1)
  • block_execute (36-99)
executor/src/block_executor.rs (1)
  • block_execute (65-147)
vm2/vm-types/src/transaction/mod.rs (1)
  • block_id (962-964)
chain/src/txn_output_cache.rs (3)
chain/open-block/src/lib.rs (1)
  • new (91-165)
vm2/executor/src/block_executor.rs (1)
  • default (26-33)
executor/src/block_executor.rs (1)
  • default (45-53)
🔇 Additional comments (28)
txpool/src/test.rs (1)

282-286: LGTM! Correctly adapts to the new FinalizedBlock API.

The test correctly updates to the new finalization flow where finalize() returns a FinalizedBlock containing a template field. The changes properly access state roots and block construction through finalized.template, aligning with the dual-VM execution architecture introduced in this PR.

chain/Cargo.toml (2)

4-4: LGTM! DashMap enables concurrent cache access.

Adding dashmap as a workspace dependency is appropriate for implementing the global per-block state cache, as it provides thread-safe concurrent HashMap operations needed for cache lookups across multiple execution contexts.


38-38: LGTM! VM2 executor integration for dual-VM execution.

Adding starcoin-vm2-executor enables the dual-VM execution flow with pre-computed outputs, which is central to this PR's caching mechanism.

executor/src/block_executor.rs (1)

13-13: LGTM! TransactionOutput import enables cached execution.

The import of TransactionOutput is necessary for the new block_execute_with_outputs function to consume pre-computed outputs.

sync/src/tasks/block_sync_task.rs (1)

355-357: No issues found with fork error handling or design pattern.

The pre-apply fork in apply_block is correctly implemented. The ? operator ensures that if fork() fails, the assignment to self.chain never executes, so no inconsistent state is possible—the original chain reference remains unchanged on error. Additionally, fork() creates a new BlockChain instance rather than mutating the caller, so the pattern is sound.

The pre-apply fork correctly positions the state at the parent before execution, which aligns with the cache-aware execution model. Forking occurs only on parent mismatch, not on every block—this is expected behavior for DAG sync with non-linear block ordering, not a performance problem.

Likely an incorrect or invalid review comment.

vm2/executor/src/lib.rs (1)

8-8: LGTM! Clean API expansion.

The re-export of block_execute_with_outputs correctly extends the public API to support execution with pre-computed outputs, aligning with the new caching strategy.

executor/src/lib.rs (1)

7-9: LGTM! Consistent API extension.

The addition of block_execute_with_outputs to the public API maintains consistency with the VM2 executor path and supports the new outputs-aware execution flow.

vm2/chain/src/lib.rs (1)

9-9: Import addition is appropriate.

Adding TransactionOutput to support the new outputs-aware execution path.

chain/open-block/src/vm2.rs (2)

175-177: Clarifying comment is helpful.

The comment appropriately documents the intentional difference between VM2 and VM1 execution paths regarding write_set tracking.


198-201: LGTM! Proper VM2 execution bookkeeping.

Recording VM2 transaction info and events enables dual-VM execution tracking. The placement after TransactionInfo2 creation is logical and consistent with the execution flow.

vm2/executor/src/block_executor.rs (2)

12-12: Import addition is correct.

Adding TransactionOutput is necessary to support the new outputs-aware execution path.


101-117: LGTM! Proper validation of inputs.

The function signature is clear, and the length validation at lines 112-116 correctly prevents index mismatches between transactions and outputs. The InvalidOutputCount error provides actionable information.

chain/open-block/src/lib.rs (5)

44-55: LGTM: Well-designed FinalizedBlock structure.

The struct correctly bundles all necessary data for caching: template, state databases, and VM execution results. The documentation clearly explains the optimization goal.


78-86: LGTM: Appropriate tracking fields for dual-VM execution.

The tracking fields correctly capture VM1 and VM2 execution data. The comment on line 86 helpfully documents that VM2 uses TransactionAuxiliaryData instead of table_infos, explaining the asymmetry.


154-159: LGTM: Proper initialization of tracking fields.

All tracking fields are correctly initialized to empty collections.


333-368: LGTM: Correct accumulation of VM1 execution data.

The code properly extracts and tracks table_infos, write_sets, txn_infos, and events from transaction outputs. The use of BTreeMap::append on line 341 correctly merges table_infos, keeping the latest value for duplicate keys.


380-441: All callers of finalize() have been properly updated to handle FinalizedBlock.

The return type change from BlockTemplate to FinalizedBlock is correctly handled across all callers. The implementation bundles the template alongside VM execution data (statedb, statedb2, executed_data, executed_data2), and all three call sites properly extract the needed fields from the wrapper struct.

miner/src/create_block_template/block_builder_service.rs (4)

8-11: LGTM: Appropriate imports for caching functionality.

The imports correctly bring in the cache infrastructure and shutdown control from the chain module.


541-543: LGTM: Appropriate shutdown checks for graceful termination.

The shutdown checks at key stages of block building ensure the system can terminate cleanly without wasting resources on work that will be discarded. The placement balances responsiveness with avoiding excessive checking overhead.

Also applies to: 569-571, 592-594, 614-616, 628-630, 640-642


632-644: LGTM: Correct handling of FinalizedBlock.

The code properly adapts to the new finalize() return type, extracting the template for use while retaining the full finalized data for caching.


646-658: The caching logic is correct; no uniqueness concerns apply.

Using txn_accumulator_root as the cache key is intentional and properly designed. As documented in chain/src/txn_output_cache.rs, this value uniquely identifies a block's transaction list (not the block itself). Since transaction execution is deterministic, blocks with identical transaction lists will always produce identical execution results, making this a suitable and efficient cache key. No collision risk exists.

chain/src/chain.rs (3)

7-7: LGTM: Clean imports and type aliasing.

The import and type alias improve code clarity for dual-VM execution.

Also applies to: 2169-2169


2171-2212: LGTM: Well-structured dual-VM execution helper.

The helper function correctly sequences VM1 and VM2 execution, properly managing the gas budget between them. The default case for empty VM1 transactions is handled appropriately.


2273-2315: LGTM: Correct cache lookup with appropriate fallbacks.

The cache integration properly handles all cases:

  • Complete cache hit: reuses cached state and execution data
  • Partial/missing cache: falls back to normal execution

The use of remove() ensures each cached entry is consumed only once, preventing stale data reuse.

chain/src/txn_output_cache.rs (4)

14-54: LGTM: Well-designed CachedBlockState structure.

The struct appropriately uses Option to represent potentially partial cache entries, and is_complete() provides a clear validation method. The documentation explains the optimization goal clearly.


56-77: LGTM: Appropriate concurrent data structure.

Using DashMap for the cache enables lock-free concurrent reads while maintaining thread safety. The eviction_lock correctly coordinates eviction to prevent races.


102-110: LGTM: Correct get and remove semantics.

The methods properly handle DashMap's guard-based access. Cloning in get() is necessary and cheap due to Arc-wrapped data.


131-160: LGTM: Proper global state management.

The global cache and shutdown flag are correctly implemented with appropriate synchronization primitives. The clear_global_block_state_cache function correctly sets the shutdown flag before clearing, ensuring no new entries are added during cleanup.

Comment on lines +2267 to 2330
let statedb = &self.statedb.0;
let statedb2 = &self.statedb.1;

// Get epoch from forked statedb (read from VM2's statedb)
let epoch = get_epoch_from_statedb(&statedb2)?;

// Execute VM1 transactions
let executed_data = if !transactions.is_empty() {
starcoin_executor::block_execute(
&statedb,
transactions.clone(),
epoch.block_gas_limit(),
self.vm_metrics.clone(),
)?
} else {
BlockExecutedData {
state_root: statedb.state_root(),
txn_infos: vec![],
txn_events: vec![],
txn_table_infos: BTreeMap::new(),
write_sets: vec![],
}
};
let epoch = get_epoch_from_statedb(statedb2)?;

// Check for cached state (StateDB + BlockExecutedData)
// If cache hit, we can skip execution and apply_write_set entirely
let state_cache = global_block_state_cache();
let cached_state = state_cache.remove(header.txn_accumulator_root());

// Execute or use cached data
let (executed_data, executed_data2, cached_statedb, cached_statedb2) =
if let Some(cached) = cached_state {
if cached.is_complete() {
info!(
"Using cached StateDB and executed data for block {:?}",
block_id
);
(
cached.executed_data.unwrap(),
cached.executed_data2.unwrap(),
cached.statedb,
cached.statedb2,
)
} else {
// Partial cache - fall back to execution
let (data, data2) = execute_block_transactions(
statedb,
statedb2,
&transactions,
&transactions2,
&epoch,
self.vm_metrics.clone(),
)?;
(data, data2, None, None)
}
} else {
// No cache - execute normally
let (data, data2) = execute_block_transactions(
statedb,
statedb2,
&transactions,
&transactions2,
&epoch,
self.vm_metrics.clone(),
)?;
(data, data2, None, None)
};

// Apply write sets for VM1
for write_set in executed_data.write_sets {
statedb
.apply_write_set(write_set)
.map_err(BlockExecutorError::BlockChainStateErr)?;
statedb
.commit()
.map_err(BlockExecutorError::BlockChainStateErr)?;
// If we used cached statedb, we need to flush the cached ones
// Otherwise, we only need to flush self.statedb (executor already applied write_sets)
if cached_statedb.is_some() && cached_statedb2.is_some() {
// Flush cached statedbs
let cached_db = cached_statedb.as_ref().unwrap();
let cached_db2 = cached_statedb2.as_ref().unwrap();
cached_db.flush()?;
cached_db2.flush()?;
} else {
// Note: write_sets are already applied and committed by executor functions
// (block_execute / execute_transactions), we only need to flush here
statedb.flush()?;
statedb2.flush()?;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Self.statedb not updated when using cached state.

There's a state management issue in the cache path:

  1. Lines 2267-2268 borrow references to self.statedb.0 and self.statedb.1
  2. When cache is used (lines 2319-2324), the code flushes cached_db and cached_db2
  3. However, self.statedb.0 and self.statedb.1 are never updated with the cached state

This means after executing a cached block, the BlockChain instance's state databases remain at the parent's state, not advancing to the new block's state. This will cause incorrect state for subsequent operations.

Suggested fix: After using cached statedbs, update self.statedb:

         if cached_statedb.is_some() && cached_statedb2.is_some() {
             // Flush cached statedbs
             let cached_db = cached_statedb.as_ref().unwrap();
             let cached_db2 = cached_statedb2.as_ref().unwrap();
             cached_db.flush()?;
             cached_db2.flush()?;
+            // Update self.statedb to the cached state
+            self.statedb = (cached_db.clone(), cached_db2.clone());
         } else {

Note: Cloning Arc<ChainStateDB> is cheap since it's just incrementing a reference count.

📝 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
let statedb = &self.statedb.0;
let statedb2 = &self.statedb.1;
// Get epoch from forked statedb (read from VM2's statedb)
let epoch = get_epoch_from_statedb(&statedb2)?;
// Execute VM1 transactions
let executed_data = if !transactions.is_empty() {
starcoin_executor::block_execute(
&statedb,
transactions.clone(),
epoch.block_gas_limit(),
self.vm_metrics.clone(),
)?
} else {
BlockExecutedData {
state_root: statedb.state_root(),
txn_infos: vec![],
txn_events: vec![],
txn_table_infos: BTreeMap::new(),
write_sets: vec![],
}
};
let epoch = get_epoch_from_statedb(statedb2)?;
// Check for cached state (StateDB + BlockExecutedData)
// If cache hit, we can skip execution and apply_write_set entirely
let state_cache = global_block_state_cache();
let cached_state = state_cache.remove(header.txn_accumulator_root());
// Execute or use cached data
let (executed_data, executed_data2, cached_statedb, cached_statedb2) =
if let Some(cached) = cached_state {
if cached.is_complete() {
info!(
"Using cached StateDB and executed data for block {:?}",
block_id
);
(
cached.executed_data.unwrap(),
cached.executed_data2.unwrap(),
cached.statedb,
cached.statedb2,
)
} else {
// Partial cache - fall back to execution
let (data, data2) = execute_block_transactions(
statedb,
statedb2,
&transactions,
&transactions2,
&epoch,
self.vm_metrics.clone(),
)?;
(data, data2, None, None)
}
} else {
// No cache - execute normally
let (data, data2) = execute_block_transactions(
statedb,
statedb2,
&transactions,
&transactions2,
&epoch,
self.vm_metrics.clone(),
)?;
(data, data2, None, None)
};
// Apply write sets for VM1
for write_set in executed_data.write_sets {
statedb
.apply_write_set(write_set)
.map_err(BlockExecutorError::BlockChainStateErr)?;
statedb
.commit()
.map_err(BlockExecutorError::BlockChainStateErr)?;
// If we used cached statedb, we need to flush the cached ones
// Otherwise, we only need to flush self.statedb (executor already applied write_sets)
if cached_statedb.is_some() && cached_statedb2.is_some() {
// Flush cached statedbs
let cached_db = cached_statedb.as_ref().unwrap();
let cached_db2 = cached_statedb2.as_ref().unwrap();
cached_db.flush()?;
cached_db2.flush()?;
} else {
// Note: write_sets are already applied and committed by executor functions
// (block_execute / execute_transactions), we only need to flush here
statedb.flush()?;
statedb2.flush()?;
}
let statedb = &self.statedb.0;
let statedb2 = &self.statedb.1;
// Get epoch from forked statedb (read from VM2's statedb)
let epoch = get_epoch_from_statedb(statedb2)?;
// Check for cached state (StateDB + BlockExecutedData)
// If cache hit, we can skip execution and apply_write_set entirely
let state_cache = global_block_state_cache();
let cached_state = state_cache.remove(header.txn_accumulator_root());
// Execute or use cached data
let (executed_data, executed_data2, cached_statedb, cached_statedb2) =
if let Some(cached) = cached_state {
if cached.is_complete() {
info!(
"Using cached StateDB and executed data for block {:?}",
block_id
);
(
cached.executed_data.unwrap(),
cached.executed_data2.unwrap(),
cached.statedb,
cached.statedb2,
)
} else {
// Partial cache - fall back to execution
let (data, data2) = execute_block_transactions(
statedb,
statedb2,
&transactions,
&transactions2,
&epoch,
self.vm_metrics.clone(),
)?;
(data, data2, None, None)
}
} else {
// No cache - execute normally
let (data, data2) = execute_block_transactions(
statedb,
statedb2,
&transactions,
&transactions2,
&epoch,
self.vm_metrics.clone(),
)?;
(data, data2, None, None)
};
// If we used cached statedb, we need to flush the cached ones
// Otherwise, we only need to flush self.statedb (executor already applied write_sets)
if cached_statedb.is_some() && cached_statedb2.is_some() {
// Flush cached statedbs
let cached_db = cached_statedb.as_ref().unwrap();
let cached_db2 = cached_statedb2.as_ref().unwrap();
cached_db.flush()?;
cached_db2.flush()?;
// Update self.statedb to the cached state
self.statedb = (cached_db.clone(), cached_db2.clone());
} else {
// Note: write_sets are already applied and committed by executor functions
// (block_execute / execute_transactions), we only need to flush here
statedb.flush()?;
statedb2.flush()?;
}
🤖 Prompt for AI Agents
In @chain/src/chain.rs around lines 2267 - 2330, The cached-path flushes
cached_db/cached_db2 but never updates self.statedb, so the Chain's active state
remains stale; after confirming cached_statedb and cached_statedb2 are Some,
assign the cached Arcs back into self.statedb (e.g., self.statedb =
(cached_db.clone(), cached_db2.clone()) or set the tuple fields) so the chain
uses the cached state for subsequent operations; also avoid holding long-lived
&self.statedb borrows (the local statedb/statedb2 should be clones or obtained
after possible assignment) to satisfy Rust borrow rules.

Comment on lines +149 to +203
/// Execute block with pre-computed outputs (from cache)
pub fn block_execute_with_outputs<S: ChainStateReader + ChainStateWriter>(
chain_state: &S,
txns: Vec<Transaction>,
outputs: Vec<TransactionOutput>,
) -> ExecutorResult<BlockExecutedData> {
let mut executed_data = BlockExecutedData::default();
let last_index = outputs
.len()
.checked_sub(1)
.ok_or_else(|| BlockExecutorError::BlockTransactionZero)?;
for (index, (txn, output)) in txns
.iter()
.take(outputs.len())
.zip(outputs.into_iter())
.enumerate()
{
let txn_hash = txn.id();
let (mut table_infos, write_set, events, gas_used, status) = output.into_inner();
match status {
TransactionStatus::Discard(status) => {
return Err(BlockExecutorError::BlockTransactionDiscard(
status, txn_hash,
));
}
TransactionStatus::Keep(status) => {
chain_state
.apply_write_set(write_set.clone())
.map_err(BlockExecutorError::BlockChainStateErr)?;
let txn_state_root = if index == last_index || index == 0 {
Some(
chain_state
.commit()
.map_err(BlockExecutorError::BlockChainStateErr)?,
)
} else {
None
};
executed_data.txn_infos.push(TransactionInfo::new(
txn_hash,
txn_state_root,
events.as_slice(),
gas_used,
status,
));
executed_data.txn_events.push(events);
executed_data.txn_table_infos.append(&mut table_infos);
executed_data.write_sets.push(write_set);
}
TransactionStatus::Retry => return Err(BlockExecutorError::BlockExecuteRetryErr),
};
}
executed_data.state_root = chain_state.state_root();
Ok(executed_data)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the executor files in the repository
find . -name "block_executor.rs" -type f | head -20

Repository: starcoinorg/starcoin

Length of output: 173


🏁 Script executed:

# Search for the BlockExecutorError enum definition
rg -n "enum BlockExecutorError" --type rust -A 30

Repository: starcoinorg/starcoin

Length of output: 4141


🏁 Script executed:

# Check for balance event logging patterns
rg -n "LOGGER_BALANCE_AMOUNT|balance.*log" --type rust

Repository: starcoinorg/starcoin

Length of output: 1234


🏁 Script executed:

# Check the actual file at lines 149-203 to see the implementation
if [ -f "executor/src/block_executor.rs" ]; then
  echo "=== executor/src/block_executor.rs (lines 149-203) ===" && sed -n '149,203p' executor/src/block_executor.rs
elif [ -f "vm2/executor/src/block_executor.rs" ]; then
  echo "=== vm2/executor/src/block_executor.rs (lines 149-203) ===" && sed -n '149,203p' vm2/executor/src/block_executor.rs
fi

Repository: starcoinorg/starcoin

Length of output: 2248


🏁 Script executed:

# Search for block_execute function to find balance logging
rg -n "fn block_execute" --type rust -A 60 | grep -A 60 "block_execute[^_]"

Repository: starcoinorg/starcoin

Length of output: 8856


🏁 Script executed:

# Check imports and error enum in VM1 executor
head -50 executor/src/block_executor.rs | grep -E "use|import|BlockExecutorError"

Repository: starcoinorg/starcoin

Length of output: 851


🏁 Script executed:

# Check full BlockExecutorError enum in types/src/error/mod.rs
cat -n types/src/error/mod.rs | head -50

Repository: starcoinorg/starcoin

Length of output: 1683


🏁 Script executed:

# Verify the complete error definition in VM1 types
rg -n "^pub enum BlockExecutorError" types/src/error/mod.rs -A 30

Repository: starcoinorg/starcoin

Length of output: 1194


🏁 Script executed:

# Check if InvalidOutputCount needs to be added to VM1 error types
rg -n "InvalidOutputCount" types/src/error/mod.rs

Repository: starcoinorg/starcoin

Length of output: 46


🏁 Script executed:

# Check how block_execute_with_outputs is called in the codebase
rg -n "block_execute_with_outputs" --type rust -B 3 -A 3

Repository: starcoinorg/starcoin

Length of output: 2189


🏁 Script executed:

# Look for any callers that might pass mismatched lengths
rg -n "block_execute_with_outputs" executor/src/block_executor.rs -A 1 -B 1

Repository: starcoinorg/starcoin

Length of output: 228


🏁 Script executed:

# Find where block_execute_with_outputs is called in VM1 (executor-based chain)
rg -n "block_execute_with_outputs" chain/src/ --type rust -B 5 -A 5

Repository: starcoinorg/starcoin

Length of output: 46


🏁 Script executed:

# Check if there's similar validation in VM2 block_execute_with_outputs
sed -n '102,140p' vm2/executor/src/block_executor.rs

Repository: starcoinorg/starcoin

Length of output: 1573


🏁 Script executed:

# Verify if there are any docs or comments explaining why balance logging is omitted in block_execute_with_outputs
rg -n "block_execute_with_outputs|pre-computed" executor/src/block_executor.rs -B 5 -A 2

Repository: starcoinorg/starcoin

Length of output: 363


🏁 Script executed:

# Check what callers pass to block_execute_with_outputs (if any) and how they ensure matching lengths
rg -rn "block_execute_with_outputs" . --type rust | grep -v "pub fn block_execute_with_outputs" | grep -v "\.rs:8:" | head -20

Repository: starcoinorg/starcoin

Length of output: 480


Add input validation with appropriate error handling.

The block_execute_with_outputs function lacks input validation that should ensure txns.len() == outputs.len(). Currently, take(outputs.len()) silently ignores extra transactions, which could mask bugs. VM2's implementation includes this validation.

However, the suggested fix references BlockExecutorError::InvalidOutputCount, which does not exist in the VM1 error types (types/src/error/mod.rs). Add validation using OtherError or another appropriate existing variant:

 pub fn block_execute_with_outputs<S: ChainStateReader + ChainStateWriter>(
     chain_state: &S,
     txns: Vec<Transaction>,
     outputs: Vec<TransactionOutput>,
 ) -> ExecutorResult<BlockExecutedData> {
+    if txns.len() != outputs.len() {
+        return Err(BlockExecutorError::OtherError(Box::new(anyhow::anyhow!(
+            "transaction count {} does not match output count {}",
+            txns.len(),
+            outputs.len()
+        ))));
+    }
     let mut executed_data = BlockExecutedData::default();

The balance event logging omission (lines 92–105 in block_execute) is intentional for pre-computed outputs but should be documented in a comment explaining why it's skipped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants