Skip to content

Conversation

@UdjinM6
Copy link

@UdjinM6 UdjinM6 commented Nov 13, 2025

Issue being fixed or feature implemented

#6961 should fix the issue for new nodes migrating to v23. This PR aims to provide tools for nodes that updated before the fix implemented in #6961.

What was done?

Preparatory Refactoring (2 commits)

  1. BuildNewListFromBlock overload - Added method overload that accepts explicit prevList parameter instead of loading from database.
    Needed for repair to rebuild from trusted snapshots.
  2. CDeterministicMNList::IsEqual - Added equality comparison method to verify recalculated diffs produce correct results.

Main Feature (1 commit)

  1. evodb verify and repair RPCs - Core implementation:
  • Two new hidden RPC commands for diagnosing/repairing corrupted evodb diffs
  • evodb verify - Read-only verification between snapshots (every 576 blocks)
  • evodb repair - Recalculates corrupted diffs from blockchain data
  • Fail-fast on critical errors, efficient 16MB batched writes, cache clearing

Documentation (2 commits)

  1. Comprehensive docs - Full technical documentation in doc/evodb-verify-repair.md
  2. Release notes - Short user-facing notes in doc/release-notes-6969.md

Key Points

  • Purpose: Repair corrupted evodb diffs without full reindex (when possible)
  • Use cases: After crashes, disk corruption, or suspected database issues
  • Limitations: Can't repair missing snapshots (needs reindex), requires unpruned blocks
  • Performance: I/O intensive, logs progress every 100 snapshot pairs
  • Safety: Verifies repairs before committing, clears caches, clear error messages

How Has This Been Tested?

  1. Sync on testnet/mainet with v22.1.3 (save evodb from datadir for repeated experiments) and stop the node
  2. Start v23 (with --noconnect to avoid altering blocks and chainstate), wait for migration to finish and stop the node
  3. Start a node with this patch and run:
    1. evodb verify, should see a bunch of errors in verificationErrors
    2. evodb repair, should see same errors in verificationErrors and none in repairErrors (can specify start/stop params from errors in verificationErrors to speed things up a bit, verificationErrors should look accordingly)
    3. evodb verify or evodb repair again, should see no errors now

Breaking Changes

n/a

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

UdjinM6 and others added 5 commits November 13, 2025 03:24
Add overload that takes an explicit starting CDeterministicMNList instead of
loading from GetListForBlock. This allows rebuilding masternode lists from
trusted snapshots without depending on potentially corrupted diffs in the
database.

Key changes:
- Add new overload taking prevList parameter (specialtxman.h)
- Refactor original method to delegate to new overload (specialtxman.cpp)
- Add assert to verify prevList is either empty or matches previous block
- Skip collateral validation when using dummy coins view (historical blocks)

Co-authored-by: Claude (Anthropic AI) <[email protected]>
Add method to compare two masternode lists for equality while ignoring
non-deterministic members (nTotalRegisteredCount, internalId).

Non-deterministic members can differ between nodes due to different sync
histories but don't affect consensus validity. This method is useful for
verifying that masternode list states match after applying diffs.

Key features:
- Compares blockHash, nHeight, and mnUniquePropertyMap
- Compares map sizes (but not nTotalRegisteredCount)
- Iterates through all masternodes comparing deterministic fields
- Uses SerializeHash for pdmnState to avoid enumerating all fields

Co-authored-by: Claude (Anthropic AI) <[email protected]>
Add two new RPC commands for verifying and repairing corrupted evodb diff
records between snapshots stored every 576 blocks.

evodb verify (read-only):
- Verifies that applying stored diffs between snapshots produces correct results
- Reports verification errors without modifying database
- Defaults to full range from DIP0003 activation to chain tip

evodb repair (writes to database):
- First runs verification on all snapshot pairs
- For failed pairs, recalculates diffs from actual blockchain data
- Writes repaired diffs to database using efficient batching (16MB chunks)
- Clears both diff and list caches to prevent serving stale data
- Only commits repairs if recalculation verification passes

Key implementation details:
- Uses BuildNewListFromBlock overload with dummy coins view to avoid UTXO lookups
- Breaks circular dependency by rebuilding from trusted snapshots, not corrupted diffs
- Handles missing initial snapshot at DIP0003 (creates empty list)
- Treats any other missing snapshot as critical error requiring reindex
- Logs progress every 100 snapshot pairs to avoid spam
- Separate error reporting for verification vs repair phases

New public types:
- CDeterministicMNManager::RecalcDiffsResult - result struct with detailed stats
- Forward declarations for ChainstateManager and CSpecialTxProcessor

Co-authored-by: Claude (Anthropic AI) <[email protected]>
Add detailed documentation for the new evodb verify and repair RPC
commands, covering:
- Command syntax and parameters
- Return value specifications
- Verification and repair process details
- Usage guidelines and troubleshooting
- Performance and implementation notes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Add release notes for new hidden RPC commands:
- evodb verify: read-only verification of evodb diffs
- evodb repair: repair corrupted diffs from blockchain data

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@UdjinM6 UdjinM6 added this to the 23.0.1 milestone Nov 13, 2025
@UdjinM6 UdjinM6 added the RPC Some notable changes to RPC params/behaviour/descriptions label Nov 13, 2025
@github-actions
Copy link

github-actions bot commented Nov 13, 2025

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@coderabbitai
Copy link

coderabbitai bot commented Nov 13, 2025

Walkthrough

This PR adds hidden RPCs evodb verify and evodb repair to verify and optionally repair EvoDB deterministic masternode list diffs across 576-block snapshots. It implements CDeterministicMNManager::RecalculateAndRepairDiffs and helpers: CollectSnapshotBlocks, VerifySnapshotPair, RepairSnapshotPair, and WriteRepairedDiffs. CSpecialTxProcessor gains a RebuildListFromBlock/BuildNewListFromBlock overload that accepts a previous MN list. RPC handlers validate start/stop, call the manager workflow with a build-list callback, and return JSON containing start/stop heights, snapshots verified, diffs recalculated, and verification/repair error arrays. Repair commits diffs in batched writes and clears related caches.

Sequence Diagram(s)

sequenceDiagram
    participant RPC as RPC command
    participant Impl as evodb_verify_or_repair_impl
    participant DMNMgr as CDeterministicMNManager
    participant Collector as CollectSnapshotBlocks
    participant Verifier as VerifySnapshotPair
    participant Repairer as RepairSnapshotPair
    participant Writer as WriteRepairedDiffs
    participant DB as EvoDB

    RPC->>Impl: call evodb verify/repair(start?, stop?)
    Impl->>DMNMgr: RecalculateAndRepairDiffs(start, stop, build_callback, repair)
    DMNMgr->>Collector: CollectSnapshotBlocks(start, stop)
    Collector-->>DMNMgr: snapshot block list

    loop each snapshot pair
        DMNMgr->>Verifier: VerifySnapshotPair(from, to, fromSnapshot, toSnapshot)
        rect rgb(230,240,255)
            Note over Verifier: apply stored diffs sequentially and compare to target
        end
        Verifier-->>DMNMgr: ok / errors

        alt failure AND repair mode
            DMNMgr->>Repairer: RepairSnapshotPair(..., build_callback)
            rect rgb(230,255,230)
                Note over Repairer: replay blocks, rebuild lists, recalc diffs, validate
            end
            Repairer-->>DMNMgr: recalculated diffs
        end
    end

    alt repair mode and diffs exist
        DMNMgr->>Writer: WriteRepairedDiffs(recalculated_diffs)
        Writer->>DB: batch write diffs (flush on size limit)
        Writer->>DB: clear mnListDiffsCache & mnListsCache
        Writer-->>DMNMgr: write status
    end

    DMNMgr-->>Impl: RecalcDiffsResult (counts, errors)
    Impl-->>RPC: JSON response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing close inspection:
    • src/evo/deterministicmns.cpp / .h — new multi-step workflow, locking annotations, error composition, batch write and cache-clear logic, DIP0003 boundary handling.
    • src/evo/specialtxman.cpp / .h — new overloads and use of provided prevList, assertions, and collateral validation gating.
    • src/rpc/evo.cpp — parameter validation, defaults, result JSON structure, and RPC registration.
    • Documentation files (doc/evodb-verify-repair.md, doc/release-notes-6969.md) — examples, return schemas, and error semantics.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add evodb verify and repair RPC commands' accurately and concisely summarizes the main change: adding two new RPC commands for verifying and repairing evodb diffs.
Description check ✅ Passed The description comprehensively covers the pull request scope, including the issue being fixed, the changes made across multiple commits, testing methodology, and feature limitations.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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 (4)
src/rpc/evo.cpp (1)

1763-1839: Don't hold cs_main for the entire verify/repair run

The outer LOCK(::cs_main) keeps the global validation lock held while RecalculateAndRepairDiffs traverses every snapshot pair, reads blocks, and potentially writes batches back to LevelDB. The docs you added spell out that these commands are intentionally long-running and I/O heavy, so on mainnet this will freeze block validation, mempool admission, and RPCs for minutes or hours. Please confine cs_main to short critical sections (fetching the start/stop indices, etc.) and reacquire it only within the callback right before calling BuildNewListFromBlock. That keeps the chain view stable without stalling the whole node.

-    LOCK(::cs_main);
-
-    const CBlockIndex* start_index;
-    const CBlockIndex* stop_index;
-
-    // Default to DIP0003 activation height if startBlock not specified
-    if (request.params[0].isNull()) {
-        const auto& consensus_params = Params().GetConsensus();
-        start_index = chainman.ActiveChain()[consensus_params.DIP0003Height];
-        if (!start_index) {
-            throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot find DIP0003 activation block");
-        }
-    } else {
-        uint256 start_block_hash = ParseBlock(request.params[0], chainman, "startBlock");
-        start_index = chainman.m_blockman.LookupBlockIndex(start_block_hash);
-        if (!start_index) {
-            throw JSONRPCError(RPC_INVALID_PARAMETER, "Start block not found");
-        }
-    }
-    ...
-    if (request.params[1].isNull()) {
-        stop_index = chainman.ActiveChain().Tip();
-        if (!stop_index) {
-            throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot find chain tip");
-        }
-    } else {
-        uint256 stop_block_hash = ParseBlock(request.params[1], chainman, "stopBlock");
-        stop_index = chainman.m_blockman.LookupBlockIndex(stop_block_hash);
-        if (!stop_index) {
-            throw JSONRPCError(RPC_INVALID_PARAMETER, "Stop block not found");
-        }
-    }
+    const CBlockIndex* start_index;
+    const CBlockIndex* stop_index;
+    {
+        LOCK(::cs_main);
+        const auto& consensus_params = Params().GetConsensus();
+        start_index = request.params[0].isNull()
+            ? chainman.ActiveChain()[consensus_params.DIP0003Height]
+            : chainman.m_blockman.LookupBlockIndex(ParseBlock(request.params[0], chainman, "startBlock"));
+        if (!start_index) {
+            throw JSONRPCError(RPC_INVALID_PARAMETER, "Start block not found");
+        }
+
+        stop_index = request.params[1].isNull()
+            ? chainman.ActiveChain().Tip()
+            : chainman.m_blockman.LookupBlockIndex(ParseBlock(request.params[1], chainman, "stopBlock"));
+        if (!stop_index) {
+            throw JSONRPCError(RPC_INVALID_PARAMETER, "Stop block not found");
+        }
+    }
     ...
-    auto build_list_func = [&chain_helper](const CBlock& block, gsl::not_null<const CBlockIndex*> pindexPrev,
-                                           const CDeterministicMNList& prevList, const CCoinsViewCache& view,
-                                           bool debugLogs, BlockValidationState& state,
-                                           CDeterministicMNList& mnListRet) NO_THREAD_SAFETY_ANALYSIS -> bool {
-        return chain_helper.special_tx->BuildNewListFromBlock(block, pindexPrev, prevList, view, debugLogs, state, mnListRet);
-    };
+    auto build_list_func = [&chain_helper](const CBlock& block, gsl::not_null<const CBlockIndex*> pindexPrev,
+                                           const CDeterministicMNList& prevList, const CCoinsViewCache& view,
+                                           bool debugLogs, BlockValidationState& state,
+                                           CDeterministicMNList& mnListRet) -> bool {
+        LOCK(::cs_main);
+        return chain_helper.special_tx->BuildNewListFromBlock(block, pindexPrev, prevList, view, debugLogs, state, mnListRet);
+    };
src/evo/deterministicmns.h (1)

441-479: Consider verifying mnInternalIdMap consistency.

The method compares mnInternalIdMap.size() (Line 456) but doesn't verify the actual internal ID mappings are consistent with the MN entries being compared. While internalId is non-deterministic across nodes, within a single list the mapping should be consistent with mnMap.

If the goal is to detect corruption (rather than just cross-node comparison), consider verifying that for each MN in mnMap, the corresponding entry in mnInternalIdMap points back to the correct proTxHash.

Example verification:

// After line 458, add:
// Verify internal ID mappings are consistent with mnMap
for (const auto& [proTxHash, dmn] : mnMap) {
    auto mapped_hash = mnInternalIdMap.find(dmn->GetInternalId());
    if (!mapped_hash || *mapped_hash != proTxHash) {
        return false;
    }
}

If this is only for cross-node comparison where internal IDs may legitimately differ, the current implementation is acceptable.

src/evo/deterministicmns.cpp (2)

1595-1607: Clarify the double assignment of result.start_height.

Lines 1590 and 1606 both assign to result.start_height. While this appears intentional (the original value is used in the log message at line 1603-1604), the logic is confusing.

Consider restructuring:

// Clamp start height to DIP0003 activation (no snapshots/diffs exist before this)
if (start_index->nHeight < consensus_params.DIP0003Height) {
    const int original_start_height = start_index->nHeight;
    start_index = stop_index->GetAncestor(consensus_params.DIP0003Height);
    if (!start_index) {
        result.verification_errors.push_back(strprintf("Stop height %d is below DIP0003 activation height %d",
                                                       stop_index->nHeight, consensus_params.DIP0003Height));
        return result;
    }
    LogPrintf("CDeterministicMNManager::%s -- Clamped start height from %d to DIP0003 activation height %d\n",
              __func__, original_start_height, consensus_params.DIP0003Height);
    // Set result to reflect the clamped start height
    result.start_height = start_index->nHeight;
} else {
    result.start_height = start_index->nHeight;
}

This makes the intent clearer and avoids the double assignment.


1689-1693: Consider lock holding duration during I/O operations.

Line 1692 calls WriteRepairedDiffs while holding the cs lock. This method performs batched database writes (16MB batches, potentially multiple flush operations at lines 1903 and 1911 in WriteRepairedDiffs). Holding cs during I/O could block other operations that need access to masternode data.

Consider whether the cache clearing (lines 1917-1920 in WriteRepairedDiffs) is the only operation that truly requires cs, and if the database writes could be performed without it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a39960b and 6df3a7c.

📒 Files selected for processing (7)
  • doc/evodb-verify-repair.md (1 hunks)
  • doc/release-notes-6969.md (1 hunks)
  • src/evo/deterministicmns.cpp (2 hunks)
  • src/evo/deterministicmns.h (3 hunks)
  • src/evo/specialtxman.cpp (2 hunks)
  • src/evo/specialtxman.h (1 hunks)
  • src/rpc/evo.cpp (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
doc/**

📄 CodeRabbit inference engine (CLAUDE.md)

Unless specifically prompted, avoid making changes to the doc directory (documentation)

Files:

  • doc/release-notes-6969.md
  • doc/evodb-verify-repair.md
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
  • src/rpc/evo.cpp
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
🧠 Learnings (7)
📓 Common learnings
Learnt from: UdjinM6
Repo: dashpay/dash PR: 6933
File: src/llmq/utils.cpp:284-298
Timestamp: 2025-11-04T18:24:27.241Z
Learning: In consensus-critical code (such as quorum formation, block validation, or deployment activation logic), do not suggest changes to the logic itself even if the implementation appears theoretically incorrect or off-by-one. Consensus rules, once deployed on the Dash network, must be preserved exactly to avoid network forks. Refactoring PRs should maintain perfect behavioral equivalence. Only suggest logic changes if explicitly accompanied by a DIP (Dash Improvement Proposal) or if the maintainer indicates the consensus rule needs to be changed with appropriate activation logic.
📚 Learning: 2025-01-07T18:50:44.838Z
Learnt from: knst
Repo: dashpay/dash PR: 6511
File: src/evo/deterministicmns.cpp:1369-1373
Timestamp: 2025-01-07T18:50:44.838Z
Learning: The functions `MigrateDBIfNeeded` and `MigrateDBIfNeeded2` in `src/evo/deterministicmns.cpp` are temporary and will be removed in a future version. Refactoring suggestions for these functions should be avoided.

Applied to files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp} : Masternode lists must use immutable data structures (Immer library) for thread safety

Applied to files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/evo/specialtx.h : Special transactions use payload extensions defined in src/evo/specialtx.h

Applied to files:

  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
📚 Learning: 2025-07-09T15:02:26.899Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6729
File: src/evo/deterministicmns.cpp:1313-1316
Timestamp: 2025-07-09T15:02:26.899Z
Learning: In Dash's masternode transaction validation, `IsVersionChangeValid()` is only called by transaction types that update existing masternode entries (like `ProUpServTx`, `ProUpRegTx`, `ProUpRevTx`), not by `ProRegTx` which creates new entries. This means validation logic in `IsVersionChangeValid()` only applies to the subset of transaction types that actually call it, not all masternode transaction types.

Applied to files:

  • src/evo/specialtxman.cpp
📚 Learning: 2025-08-08T07:01:47.332Z
Learnt from: knst
Repo: dashpay/dash PR: 6805
File: src/wallet/rpc/wallet.cpp:357-357
Timestamp: 2025-08-08T07:01:47.332Z
Learning: In src/wallet/rpc/wallet.cpp, the upgradetohd RPC now returns a UniValue string message (RPCResult::Type::STR) instead of a boolean, including guidance about mnemonic backup and null-character passphrase handling; functional tests have been updated to assert returned strings in several cases.

Applied to files:

  • src/rpc/evo.cpp
🧬 Code graph analysis (5)
src/evo/deterministicmns.h (3)
src/validation.cpp (1)
  • ChainstateManager (6051-6061)
src/evo/specialtxman.h (2)
  • CSpecialTxProcessor (38-92)
  • Consensus (28-28)
src/evo/deterministicmns.cpp (12)
  • RecalculateAndRepairDiffs (1583-1696)
  • RecalculateAndRepairDiffs (1583-1585)
  • GetListForBlockInternal (773-865)
  • GetListForBlockInternal (773-773)
  • CollectSnapshotBlocks (1698-1748)
  • CollectSnapshotBlocks (1698-1699)
  • VerifySnapshotPair (1750-1801)
  • VerifySnapshotPair (1750-1752)
  • RepairSnapshotPair (1803-1877)
  • RepairSnapshotPair (1803-1805)
  • WriteRepairedDiffs (1879-1924)
  • WriteRepairedDiffs (1879-1880)
src/evo/deterministicmns.cpp (3)
src/chainparams.cpp (2)
  • Params (1356-1359)
  • Params (1356-1356)
src/evo/evodb.h (1)
  • cs (65-69)
src/evo/deterministicmns.h (2)
  • CDeterministicMNList (133-194)
  • nHeight (148-181)
src/evo/specialtxman.cpp (1)
src/evo/deterministicmns.h (2)
  • CDeterministicMNList (133-194)
  • nHeight (148-181)
src/evo/specialtxman.h (1)
src/evo/specialtxman.cpp (4)
  • BuildNewListFromBlock (174-181)
  • BuildNewListFromBlock (174-176)
  • BuildNewListFromBlock (183-511)
  • BuildNewListFromBlock (183-186)
src/rpc/evo.cpp (1)
src/rpc/server_util.cpp (4)
  • EnsureAnyNodeContext (28-45)
  • EnsureAnyNodeContext (28-28)
  • EnsureChainman (87-93)
  • EnsureChainman (87-87)
🪛 LanguageTool
doc/evodb-verify-repair.md

[style] ~84-~84: Consider placing the discourse marker ‘first’ at the beginning of the sentence for more clarity.
Context: ...ed ... ] } ``` Description: This command first runs verification on all snapshot pairs...

(SENT_START_FIRST_PREMIUM)

🪛 markdownlint-cli2 (0.18.1)
doc/evodb-verify-repair.md

3-3: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


8-8: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


57-57: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: Lint / Run linters
🔇 Additional comments (10)
src/evo/deterministicmns.h (5)

37-38: LGTM: Forward declarations reduce header coupling.

The forward declarations for ChainstateManager and CSpecialTxProcessor appropriately reduce compilation dependencies.


723-731: LGTM: Result structure is well-designed.

The RecalcDiffsResult struct appropriately captures all relevant outcomes including error details, making the API easy to use and debug.


733-741: LGTM: Function type alias is clear and well-defined.

The BuildListFromBlockFunc alias provides good abstraction for the callback pattern, making the RecalculateAndRepairDiffs method flexible and testable.


743-746: LGTM: Method declaration is correct.

The [[nodiscard]] attribute is appropriate since ignoring the result could miss critical errors. Lock annotations correctly specify that both cs and cs_main must not be held by caller.


756-767: LGTM: Helper method declarations are well-structured.

The private helper methods appropriately decompose the complex repair workflow. Lock requirements are clearly documented and consistent with the implementation pattern.

src/evo/deterministicmns.cpp (5)

28-28: LGTM: Required include for std::function.

The <functional> include is necessary for the BuildListFromBlockFunc type alias.


1698-1748: LGTM: Snapshot collection logic is correct.

The implementation correctly handles:

  • Walking backwards to find the starting snapshot
  • Special case for irregular spacing at DIP0003 activation height
  • Collecting all subsequent snapshots at regular intervals

The calculation at line 1728 correctly rounds up to the first regular snapshot period after DIP0003 activation.


1750-1801: LGTM: Verification logic is sound.

The implementation correctly:

  • Applies all stored diffs sequentially to build up from the source snapshot
  • Uses IsEqual (Line 1789) for comparison, appropriately ignoring non-deterministic fields
  • Includes exception handling for corrupt diffs
  • Logs progress at reasonable intervals

1879-1924: LGTM: Batched write and cache clearing logic is correct.

The implementation appropriately:

  • Uses 16MB batches to manage memory usage (Line 1889)
  • Flushes batches when threshold is reached
  • Clears both mnListDiffsCache and mnListsCache (Lines 1918-1919), which is essential since cached lists were built from the old diffs

The cache clearing ensures subsequent reads fetch the repaired diffs from disk rather than stale cached data.


1829-1840: No issues found - the dummy coins view approach is properly designed and safe.

The GetCoin() calls in BuildNewListFromBlock are guarded by if (!view.GetBestBlock().IsNull()) (line 255). A freshly created dummy CCoinsView has no best block, so the condition is false and collateral verification is safely skipped. This is intentional: historical blocks (processed during repair) were already validated and don't need UTXO re-verification. The code comments explicitly document this design.

{"stopBlock", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The ending block height (defaults to current chain tip)."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: consider to create a common helper for both functions not for implementations only, but also to return RPCHelpMan; because they have big pieces of common code; and multiple duplicated strings

Comment on lines +1654 to +1667
} else {
// Any other missing snapshot is critical corruption beyond our repair capability
result.verification_errors.push_back(strprintf("CRITICAL: Snapshot missing at height %d. "
"This cannot be repaired by this tool - full reindex required.", from_index->nHeight));
return result;
}
}

if (!has_to_snapshot) {
// Missing target snapshot is always critical - we cannot repair snapshots, only diffs
result.verification_errors.push_back(strprintf("CRITICAL: Snapshot missing at height %d. "
"This cannot be repaired by this tool - full reindex required.", to_index->nHeight));
return result;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: duplicated code and error message between different branches from/to;

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

Caution

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

⚠️ Outside diff range comments (1)
src/evo/deterministicmns.h (1)

1-1: Fix formatting issues.

The CI pipeline detected clang-format differences.

Run the following command to fix formatting:

./contrib/devtools/clang-format-diff.py -p1
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6df3a7c and 5a1b1e2.

📒 Files selected for processing (2)
  • src/evo/deterministicmns.cpp (2 hunks)
  • src/evo/deterministicmns.h (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/evo/deterministicmns.cpp
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/evo/deterministicmns.h
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/evo/deterministicmns.h
🧠 Learnings (3)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
📚 Learning: 2025-01-07T18:50:44.838Z
Learnt from: knst
Repo: dashpay/dash PR: 6511
File: src/evo/deterministicmns.cpp:1369-1373
Timestamp: 2025-01-07T18:50:44.838Z
Learning: The functions `MigrateDBIfNeeded` and `MigrateDBIfNeeded2` in `src/evo/deterministicmns.cpp` are temporary and will be removed in a future version. Refactoring suggestions for these functions should be avoided.

Applied to files:

  • src/evo/deterministicmns.h
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp} : Masternode lists must use immutable data structures (Immer library) for thread safety

Applied to files:

  • src/evo/deterministicmns.h
🧬 Code graph analysis (1)
src/evo/deterministicmns.h (2)
src/evo/specialtxman.h (2)
  • CSpecialTxProcessor (38-92)
  • Consensus (28-28)
src/evo/deterministicmns.cpp (12)
  • RecalculateAndRepairDiffs (1583-1692)
  • RecalculateAndRepairDiffs (1583-1585)
  • GetListForBlockInternal (773-865)
  • GetListForBlockInternal (773-773)
  • CollectSnapshotBlocks (1694-1742)
  • CollectSnapshotBlocks (1694-1695)
  • VerifySnapshotPair (1744-1786)
  • VerifySnapshotPair (1744-1746)
  • RepairSnapshotPair (1788-1862)
  • RepairSnapshotPair (1788-1790)
  • WriteRepairedDiffs (1864-1910)
  • WriteRepairedDiffs (1864-1865)
🪛 GitHub Actions: Clang Diff Format Check
src/evo/deterministicmns.h

[error] 1-1: Clang format differences found. Run './contrib/devtools/clang-format-diff.py -p1' to fix formatting in this file.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: Lint / Run linters
🔇 Additional comments (4)
src/evo/deterministicmns.h (4)

37-38: LGTM!

Forward declarations are appropriate for the new diff recalculation functionality.


441-479: LGTM!

The IsEqual method correctly implements deterministic comparison by excluding non-deterministic fields (nTotalRegisteredCount, internalId). Using SerializeHash for pdmnState comparison (line 474) is a safe, deterministic approach that avoids enumerating all state fields, which is appropriate for this verification use case.


723-731: LGTM!

Well-structured result type for aggregating verification and repair outcomes.


733-746: LGTM!

The BuildListFromBlockFunc callback type and RecalculateAndRepairDiffs method signature are well-designed. Using std::function is appropriate for this infrequent operation, and the [[nodiscard]] attribute correctly enforces result checking.

@UdjinM6 UdjinM6 force-pushed the feat_evodb_verify_repair_rpc branch from 5a1b1e2 to f3ee1d7 Compare November 13, 2025 15:02
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a1b1e2 and f3ee1d7.

📒 Files selected for processing (2)
  • src/evo/deterministicmns.cpp (2 hunks)
  • src/evo/deterministicmns.h (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
🧠 Learnings (6)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
📚 Learning: 2025-01-07T18:50:44.838Z
Learnt from: knst
Repo: dashpay/dash PR: 6511
File: src/evo/deterministicmns.cpp:1369-1373
Timestamp: 2025-01-07T18:50:44.838Z
Learning: The functions `MigrateDBIfNeeded` and `MigrateDBIfNeeded2` in `src/evo/deterministicmns.cpp` are temporary and will be removed in a future version. Refactoring suggestions for these functions should be avoided.

Applied to files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp} : Masternode lists must use immutable data structures (Immer library) for thread safety

Applied to files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
📚 Learning: 2025-07-22T14:38:45.606Z
Learnt from: PastaPastaPasta
Repo: dashpay/dash PR: 6738
File: src/evo/smldiff.h:91-94
Timestamp: 2025-07-22T14:38:45.606Z
Learning: In the Dash codebase, EXCLUSIVE_LOCKS_REQUIRED annotations are Clang thread safety annotations that are enforced at compile time on supported platforms like macOS. If callers don't properly hold the required locks, the build will fail with compile-time errors, not runtime issues.

Applied to files:

  • src/evo/deterministicmns.h
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.

Applied to files:

  • src/evo/deterministicmns.cpp
🧬 Code graph analysis (2)
src/evo/deterministicmns.h (3)
src/validation.cpp (1)
  • ChainstateManager (6051-6061)
src/evo/specialtxman.h (2)
  • CSpecialTxProcessor (38-92)
  • Consensus (28-28)
src/node/chainstate.h (1)
  • Consensus (28-30)
src/evo/deterministicmns.cpp (3)
src/chainparams.cpp (2)
  • Params (1356-1359)
  • Params (1356-1356)
src/evo/deterministicmns.h (2)
  • CDeterministicMNList (133-194)
  • nHeight (148-181)
src/evo/evodb.h (1)
  • cs (65-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: linux64_multiprocess-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: arm-linux-build / Build source

…romBlock

Rationale:
  - Short and clear
  - The "rebuild" prefix makes it obvious you're starting from an existing list
  - Distinguishes it well from BuildNewListFromBlock (which builds from database)
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 (4)
src/rpc/evo.cpp (3)

1755-1816: Helper wiring into diff recalc/repair looks correct; be mindful of cs_main duration

evodb_verify_or_repair_impl correctly:

  • Resolves start_index/stop_index with sane defaults (DIP0003 activation to tip) and enforces stopHeight >= startHeight.
  • Holds cs_main while resolving chain indices and calling RecalculateAndRepairDiffs, which matches existing DMN/RPC patterns.
  • Wraps the new CSpecialTxProcessor::RebuildListFromBlock via build_list_func with a matching signature and NO_THREAD_SAFETY_ANALYSIS, relying on the outer LOCK(::cs_main).

Only operational note: RecalculateAndRepairDiffs can span large ranges, so holding cs_main for the whole call may block other chain operations on busy nodes. If that method internally breaks work into chunks where cs_main is not strictly required, consider relaxing the outer lock in the future (or acquiring it only around chain access inside the loop).

If you’d like, I can sketch a follow-up that documents or narrows the locking expectations around RecalculateAndRepairDiffs to avoid surprises if that code evolves.


1817-1841: Result marshalling matches the recalc/repair struct; consider future-proofing error payloads

The conversion of recalc_result into UniValue is straightforward and consistent with the described API:

  • All scalar counters (startHeight, stopHeight, diffsRecalculated, snapshotsVerified) are propagated.
  • verification_errors and repair_errors are emitted as simple string arrays, with repairErrors only present in repair mode as documented.

If you later decide to enrich error reporting (e.g., include block heights or snapshot indexes), this is a good place to switch from plain strings to small JSON objects without changing the top-level schema.


1843-1922: evodb verify/repair RPC contracts look coherent and aligned with the helper

Both evodb_verify and evodb_repair:

  • Share consistent argument semantics (startBlock, stopBlock, optional, clamped defaults) and call the common helper with the appropriate repair flag.
  • Document result fields that match what evodb_verify_or_repair_impl returns, including the absence of repairErrors in verify-only mode and the meaning of diffsRecalculated.

Nit-level only: the RPCArg::Type::NUM for startBlock / stopBlock mirrors existing uses of ParseBlock in this file, but the implementation does accept both heights and hashes. If you ever expose these commands (un-hide them), you might consider updating the arg type/help text to mention hashes explicitly for consistency with ParseBlock’s error message.

Please double-check that RecalculateAndRepairDiffs indeed leaves diffsRecalculated == 0 when repair=false, matching the help text for evodb verify.

src/evo/specialtxman.cpp (1)

183-196: RebuildListFromBlock precondition assertion is reasonable; small copy nit

RebuildListFromBlock’s assertion:

assert(prevList == CDeterministicMNList() || prevList.GetBlockHash() == pindexPrev->GetBlockHash());

is a good internal invariant: callers must either pass a default/empty list (initial state) or a list whose block hash matches the previous block. Given that prevList is only constructed from snapshots or GetListForBlock, this should never be hit in normal operation and helps catch misuse early. This also aligns with the retrieved learning that deterministic MN list equality focuses on consensus-relevant fields, not non-deterministic internalId mappings. Based on learnings

Minor nit: you can avoid one copy by skipping the oldList temporary and using prevList directly as the source:

-    CDeterministicMNList oldList = prevList;
-    CDeterministicMNList newList = oldList;
+    CDeterministicMNList newList = prevList;

since oldList is only used to derive payee and to iterate MNs, both of which can be done from prevList.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3ee1d7 and f8be956.

📒 Files selected for processing (3)
  • src/evo/specialtxman.cpp (2 hunks)
  • src/evo/specialtxman.h (1 hunks)
  • src/rpc/evo.cpp (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/rpc/evo.cpp
  • src/evo/specialtxman.h
  • src/evo/specialtxman.cpp
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/evo/specialtxman.h
  • src/evo/specialtxman.cpp
🧠 Learnings (7)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
Learnt from: UdjinM6
Repo: dashpay/dash PR: 6969
File: src/evo/deterministicmns.h:441-479
Timestamp: 2025-11-13T20:02:55.434Z
Learning: In `src/evo/deterministicmns.h`, the `internalId` field in `CDeterministicMN` and the `mnInternalIdMap` in `CDeterministicMNList` are non-deterministic and used only for internal bookkeeping and efficient lookups. Different nodes can assign different internalIds to the same masternode depending on their sync history. Methods like `IsEqual()` intentionally ignore internalId mappings and only compare consensus-critical deterministic fields (proTxHash, collateral, state, etc.).
📚 Learning: 2025-08-08T07:01:47.332Z
Learnt from: knst
Repo: dashpay/dash PR: 6805
File: src/wallet/rpc/wallet.cpp:357-357
Timestamp: 2025-08-08T07:01:47.332Z
Learning: In src/wallet/rpc/wallet.cpp, the upgradetohd RPC now returns a UniValue string message (RPCResult::Type::STR) instead of a boolean, including guidance about mnemonic backup and null-character passphrase handling; functional tests have been updated to assert returned strings in several cases.

Applied to files:

  • src/rpc/evo.cpp
📚 Learning: 2025-11-13T20:02:55.434Z
Learnt from: UdjinM6
Repo: dashpay/dash PR: 6969
File: src/evo/deterministicmns.h:441-479
Timestamp: 2025-11-13T20:02:55.434Z
Learning: In `src/evo/deterministicmns.h`, the `internalId` field in `CDeterministicMN` and the `mnInternalIdMap` in `CDeterministicMNList` are non-deterministic and used only for internal bookkeeping and efficient lookups. Different nodes can assign different internalIds to the same masternode depending on their sync history. Methods like `IsEqual()` intentionally ignore internalId mappings and only compare consensus-critical deterministic fields (proTxHash, collateral, state, etc.).

Applied to files:

  • src/evo/specialtxman.h
  • src/evo/specialtxman.cpp
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/evo/specialtx.h : Special transactions use payload extensions defined in src/evo/specialtx.h

Applied to files:

  • src/evo/specialtxman.h
  • src/evo/specialtxman.cpp
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • src/evo/specialtxman.cpp
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp} : Masternode lists must use immutable data structures (Immer library) for thread safety

Applied to files:

  • src/evo/specialtxman.cpp
📚 Learning: 2025-07-09T15:02:26.899Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6729
File: src/evo/deterministicmns.cpp:1313-1316
Timestamp: 2025-07-09T15:02:26.899Z
Learning: In Dash's masternode transaction validation, `IsVersionChangeValid()` is only called by transaction types that update existing masternode entries (like `ProUpServTx`, `ProUpRegTx`, `ProUpRevTx`), not by `ProRegTx` which creates new entries. This means validation logic in `IsVersionChangeValid()` only applies to the subset of transaction types that actually call it, not all masternode transaction types.

Applied to files:

  • src/evo/specialtxman.cpp
🧬 Code graph analysis (3)
src/rpc/evo.cpp (1)
src/rpc/server_util.cpp (4)
  • EnsureAnyNodeContext (28-45)
  • EnsureAnyNodeContext (28-28)
  • EnsureChainman (87-93)
  • EnsureChainman (87-87)
src/evo/specialtxman.h (1)
src/evo/specialtxman.cpp (2)
  • RebuildListFromBlock (183-511)
  • RebuildListFromBlock (183-186)
src/evo/specialtxman.cpp (1)
src/evo/deterministicmns.h (2)
  • CDeterministicMNList (133-194)
  • nHeight (148-181)
⏰ 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 container / Build container
🔇 Additional comments (4)
src/evo/specialtxman.h (1)

82-87: Header overload looks consistent and well-scoped

The new RebuildListFromBlock overload cleanly separates the “rebuild from explicit prev list” path from the DB-backed BuildNewListFromBlock and keeps the same locking contract on cs_main. Signature and parameter order match the cpp implementation and the build_list_func lambda in evodb_verify_or_repair_impl, so the public API surface here looks solid.

src/rpc/evo.cpp (1)

2083-2093: Hidden RPC registration is correctly wired

Registering evodb_verify and evodb_repair under the "hidden" category matches the intent to keep these as expert-only maintenance commands while still making them callable via evodb verify/repair. The placement alongside other evo RPCs in RegisterEvoRPCCommands is consistent.

src/evo/specialtxman.cpp (2)

174-181: BuildNewListFromBlock delegation keeps existing behavior while enabling reuse

The refactor to:

  • fetch oldList via m_dmnman.GetListForBlock(pindexPrev) and
  • delegate to RebuildListFromBlock

preserves existing behavior for normal block processing while letting other callers (like evodb repair) inject a trusted prevList. This avoids duplicating the list-update logic and keeps BuildNewListFromBlock as the canonical “from-chainstate” entry point.


252-263: Collateral check gating via view.GetBestBlock() – confirm view setup in evodb repair path

Guarding the external-collateral check with:

if (!view.GetBestBlock().IsNull()) {
    ...
}

matches the comment “complain about spent collaterals only when we process the tip” and avoids re-flagging already-validated ProRegTx collateral when working with historical or synthetic views (e.g., in evodb verify/repair). This is important to keep the rebuild/verification path from failing on legitimate collaterals that were later spent.

The correctness of this approach hinges on the callers:

  • Using a non-null best block when validating at the tip in ProcessSpecialTxsInBlock, and
  • Using a view with GetBestBlock() == uint256() (or otherwise null-equivalent) when doing offline reconstruction in RecalculateAndRepairDiffs, so that collateral checks are skipped there.

It would be good to explicitly ensure in RecalculateAndRepairDiffs that the CCoinsViewCache passed to build_list_func has its best block cleared before calling RebuildListFromBlock, to make this behavior robust against future changes in how the view is constructed. If you want, I can outline a small helper to create such a “validation-light” view.

Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

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

LGTM f8be956 see new comment

Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

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

I just met real-case scenario to use build from this PR [testnet]:

> protx listdiff 1176911  1176913
....
pubKeyOperator": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
...
>  evodb repair 1170000  1179999
{
  "startHeight": 1170000,
  "stopHeight": 1179999,
  "diffsRecalculated": 576,
  "snapshotsVerified": 16,
  "verificationErrors": [
    "Verification failed between snapshots at heights 1176768 and 1177344: Applied diffs do not match target snapshot"
  ],
  "repairErrors": [
  ]
}
> protx listdiff 1176911  1176913
...
        "pubKeyOperator": "8dfb2a7e5fc39cf220f1d5020e73a243ec9ba301a92af9b5a50d390471efc266341a168dd97b2312c5e92782b4432598"
...

Though, output of evodb repair is a bit messy; it doesn't show how much diffs have been actually fixed, instead it says "Verification failed between snapshots at heights 1176768 and 1177344: Applied diffs do not match target snapshot" and I assumed repair is failed. But actually it helped and fixed public key.
@UdjinM6 I think RPC output should be updated to make it more clear, especially if there any diffs have been fixed.

Also I suggest instead taking "the first snapshot after block X" take the closest snapshot "before"; and insted "the last snapshot before block Y" take the closest snapshot "after".
Because at first I tried to call "evodb repair 1176911 1176913" and nothing happened:

> evodb repair 1176911  1176913
{
  "startHeight": 1176911,
  "stopHeight": 1176913,
  "diffsRecalculated": 0,
  "snapshotsVerified": 0,
  "verificationErrors": [
    "Need at least 2 snapshots, found 1"
  ],
  "repairErrors": [
  ]
}

Copy link
Member

@PastaPastaPasta PastaPastaPasta left a comment

Choose a reason for hiding this comment

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

utACK f8be956

@PastaPastaPasta PastaPastaPasta merged commit 002f1b2 into dashpay:develop Nov 18, 2025
31 of 39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-candidate-23.0.x RPC Some notable changes to RPC params/behaviour/descriptions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants