Skip to content

Evo: speed up early reindex merkle checks#1864

Closed
navidR wants to merge 1 commit into
firoorg:masterfrom
navidR:dev/navidr/reindex-early-speedup
Closed

Evo: speed up early reindex merkle checks#1864
navidR wants to merge 1 commit into
firoorg:masterfrom
navidR:dev/navidr/reindex-early-speedup

Conversation

@navidR

@navidR navidR commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

This patch speeds up early -reindex around the DIP3 deterministic masternode activation window by avoiding redundant cbTx masternode-list merkle-root rebuilds. During ConnectBlock, Firo already builds the deterministic masternode list and diff for the block; the patch reuses that freshly computed list for cbTx merkle validation, and if the diff only changes fields that are not serialized into CSimplifiedMNListEntry, it safely reuses the previous merkle root instead of rebuilding and rehashing the same effective simplified MN list. Consensus behavior is preserved because additions, removals, and every state field that affects the cbTx MN merkle root still force recomputation, while unrelated fields such as payout/last-paid metadata do not. It also gates several high-volume validation trace logs behind -debug=validation, reducing reindex log I/O without removing opt-in diagnostics.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d946a9b1-bff7-470c-83ab-7dbe858a6d96

📥 Commits

Reviewing files that changed from the base of the PR and between 58e77a6 and 155b6d9.

📒 Files selected for processing (10)
  • qa/rpc-tests/dip3-deterministicmns.py
  • src/dsnotificationinterface.cpp
  • src/evo/cbtx.cpp
  • src/evo/cbtx.h
  • src/evo/deterministicmns.cpp
  • src/evo/deterministicmns.h
  • src/evo/specialtx.cpp
  • src/test/evo_deterministicmns_tests.cpp
  • src/test/evo_simplifiedmns_tests.cpp
  • src/validation.cpp
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/evo/specialtx.cpp
  • src/validation.cpp
  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
  • src/evo/cbtx.h

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced coinbase transaction validation with support for externally-provided masternode lists and multi-level caching for improved efficiency.
  • Improvements

    • Improved masternode list change detection for transaction validation accuracy.
    • Comprehensive test coverage for masternode list invalidation and concurrent access scenarios.
    • Optimized block tip update sequence and validation logging.

Walkthrough

The PR extends EVO coinbase transaction (CbTx) Merkle root validation with multi-level caching and deterministic MN list awareness. New change-detection methods (HasSameMNMap, HasCbTxMerkleRootChanges) are added to the MN data model; ProcessBlock gains output parameters for the computed list and change flag; CalcCbTxMerkleRootMNList gains a CDeterministicMNList overload with three-tier caching; ProcessSpecialTxsInBlock wires these together with unit test validation. A comprehensive DIP3 invalidation regression test suite validates behavior under block rollback and disconnect scenarios. Seven validation functions switch to categorized logging, UpdatedBlockTip call ordering is adjusted, and RPC test assertions are improved.

Changes

CbTx Merkle Root Caching with Deterministic MN List

Layer / File(s) Summary
CDeterministicMNList/Diff change-detection methods
src/evo/deterministicmns.h
HasSameMNMap compares persistent-map identity between two MN lists; HasCbTxMerkleRootChanges short-circuits on added/removed sets and scans updated MN states for a cbTx-relevant field bitmask.
ProcessBlock extended output parameters
src/evo/deterministicmns.h, src/evo/deterministicmns.cpp
ProcessBlock declaration and implementation gain optional newListRet and cbTxMerkleRootMNListChangedRet output parameters; newList height/hash are set before fJustCheck early return; the changed flag is populated from diff.HasCbTxMerkleRootChanges().
CbTx MerkleRoot refactor with CDeterministicMNList overload and multi-level caching
src/evo/cbtx.h, src/evo/cbtx.cpp
Adds CDeterministicMNList forward declaration and updated/new function declarations; introduces SimplifiedMNListsEqual helper; refactors the CBlock overload to delegate to a new CDeterministicMNList overload implementing three-tier caching (change flag + block hash, HasSameMNMap, SimplifiedMNListsEqual); extends CheckCbTxMerkleRoots to accept an optional CDeterministicMNList* and route to the appropriate computation path.
ProcessSpecialTxsInBlock wiring
src/evo/specialtx.cpp
Conditionally allocates CDeterministicMNList and a changed-flag boolean when fCheckCbTxMerleRoots is true, passes them into ProcessBlock, and forwards the results to CheckCbTxMerkleRoots.
Unit tests for HasCbTxMerkleRootChanges
src/test/evo_simplifiedmns_tests.cpp
Adds static test helpers and a new BOOST_AUTO_TEST_CASE that validates HasCbTxMerkleRootChanges returns false for empty diffs, true for each cbTx-relevant state field mutation, and true for MN additions/removals.

DIP3 Invalidation Regression Test Suite

Layer / File(s) Summary
Test helpers and validation bridging infrastructure
src/test/evo_deterministicmns_tests.cpp
Adds validation interface and DS notification includes; defines test-only bridging classes for driving MN manager updates via validation signals; implements utility functions for coinbase/collateral transaction handling, block processing with manager updates, invalidation via manager and validation signals, MN registration, and tip set assertions.
DIP3 invalidation and rollback test cases
src/test/evo_deterministicmns_tests.cpp
Adds six new test cases validating deterministic MN manager correctness under single/multiple collateral spend rollbacks, cache behavior across disconnect boundaries, pure disconnect notification updates, repeated invalidate signal restoration, and multithreaded parallel-reader consistency.

Supporting Infrastructure Changes

Layer / File(s) Summary
Validation function categorized logging
src/validation.cpp
Seven LogPrintf/other logging calls in CheckTransaction, ConnectBlock, UpdateTip, ConnectTip, ActivateBestChainStep, CheckBlock, and AcceptBlock are replaced with LogPrint("validation", ...).
UpdatedBlockTip deterministic manager call ordering
src/dsnotificationinterface.cpp
Moves deterministicMNManager->UpdatedBlockTip(pindexNew) before the pindexNew == pindexFork early-return check to ensure the manager is notified regardless of block detection.
DIP3 RPC test assertion improvements
qa/rpc-tests/dip3-deterministicmns.py
Adds assert_mnlist_now helper for immediate masternode-list validation and updates a test case to use immediate assertion instead of polling after invalidateblock.

Sequence Diagram

sequenceDiagram
    participant ProcessSpecialTxsInBlock
    participant CDeterministicMNManager
    participant CheckCbTxMerkleRoots
    participant CalcCbTxMerkleRootMNList

    ProcessSpecialTxsInBlock->>CDeterministicMNManager: ProcessBlock(block, pindex, state, fJustCheck, &newList, &changed)
    CDeterministicMNManager-->>ProcessSpecialTxsInBlock: newList (CDeterministicMNList), changed (bool)
    ProcessSpecialTxsInBlock->>CheckCbTxMerkleRoots: (block, pindex, state, &newList, changed)
    CheckCbTxMerkleRoots->>CalcCbTxMerkleRootMNList: (newList, merkleRootRet, pindexPrev, changed)
    Note over CalcCbTxMerkleRootMNList: Check cache: changed flag + block hash<br/>→ HasSameMNMap<br/>→ SimplifiedMNListsEqual<br/>→ compute & cache
    CalcCbTxMerkleRootMNList-->>CheckCbTxMerkleRoots: merkle root
    CheckCbTxMerkleRoots-->>ProcessSpecialTxsInBlock: validation result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • firoorg/firo#1818: Both PRs update qa/rpc-tests/dip3-deterministicmns.py's DIP3 MN-list assertions around invalidateblock handling—main PR adds an immediate assert_mnlist_now path, while the retrieved PR hardens assert_mnlist itself with a polling/timeout loop.

Suggested labels

size:S

Suggested reviewers

  • psolstice
  • levonpetrosyan93
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Evo: speed up early reindex merkle checks' accurately describes the primary optimization in the changeset - speeding up merkle root validation during reindexing by reusing computed deterministic masternode lists.
Description check ✅ Passed The description comprehensively explains the PR's intent, the specific optimization mechanism, consensus implications, and performance benchmarks. It fully addresses the mandatory 'PR intention' section with clear details about the change and its problem-solving approach.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@navidR

navidR commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot requested a review from psolstice June 14, 2026 13:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/validation.cpp (1)

578-578: ⚡ Quick win

Reuse hashTx in the trace log.

CheckTransaction already receives the txid as hashTx; recomputing tx.GetHash() here adds avoidable work on a hot path when validation tracing is enabled.

♻️ Proposed fix
-    LogPrint("validation", "CheckTransaction nHeight=%d, isVerifyDB=%d, isCheckWallet=%d, txHash=%s\n", nHeight, (int)isVerifyDB, (int)isCheckWallet, tx.GetHash().ToString());
+    LogPrint("validation", "CheckTransaction nHeight=%d, isVerifyDB=%d, isCheckWallet=%d, txHash=%s\n", nHeight, (int)isVerifyDB, (int)isCheckWallet, hashTx.ToString());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validation.cpp` at line 578, The LogPrint call in the CheckTransaction
function is unnecessarily recomputing the transaction hash by calling
tx.GetHash().ToString() when the function already receives this value as the
hashTx parameter. Replace the tx.GetHash().ToString() call with the hashTx
parameter in the LogPrint statement to eliminate redundant work on this hot
validation path.
src/evo/cbtx.cpp (1)

148-199: 💤 Low value

Well-designed three-level caching strategy.

The multi-level caching (change flag + block hash, mnMap identity, simplified list equality) is sound and thread-safe under the existing lock. Consider adding a brief comment block before line 159 summarizing the three cache levels for future maintainers, as this logic is subtle and critical to the optimization.

📝 Suggested documentation
     static bool hasCached{false};
 
+    // Three-level cache to avoid recomputing merkle root:
+    // 1. If cbTxMerkleRootMNListChanged==false and building on cached block, reuse merkle root
+    // 2. If mnMap identity matches (structural sharing), reuse merkle root
+    // 3. If simplified MN lists are equal, reuse merkle root
     if (!cbTxMerkleRootMNListChanged && pindexPrev && hasCached && deterministicMNListCached.GetBlockHash() == pindexPrev->GetBlockHash()) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/evo/cbtx.cpp` around lines 148 - 199, Add a comment block before the
first cache check in the CalcCbTxMerkleRootMNList function (before the if
statement checking cbTxMerkleRootMNListChanged) that explains the three-level
caching strategy. The comment should briefly describe: the first cache level
that uses the change flag and block hash comparison, the second cache level that
uses MN map identity checking via HasSameMNMap, and the third cache level that
uses simplified list equality comparison via SimplifiedMNListsEqual. This will
help future maintainers understand the subtle optimization logic without needing
to reverse-engineer the intent from the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/evo/cbtx.cpp`:
- Around line 148-199: Add a comment block before the first cache check in the
CalcCbTxMerkleRootMNList function (before the if statement checking
cbTxMerkleRootMNListChanged) that explains the three-level caching strategy. The
comment should briefly describe: the first cache level that uses the change flag
and block hash comparison, the second cache level that uses MN map identity
checking via HasSameMNMap, and the third cache level that uses simplified list
equality comparison via SimplifiedMNListsEqual. This will help future
maintainers understand the subtle optimization logic without needing to
reverse-engineer the intent from the code.

In `@src/validation.cpp`:
- Line 578: The LogPrint call in the CheckTransaction function is unnecessarily
recomputing the transaction hash by calling tx.GetHash().ToString() when the
function already receives this value as the hashTx parameter. Replace the
tx.GetHash().ToString() call with the hashTx parameter in the LogPrint statement
to eliminate redundant work on this hot validation path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 108ff02a-c859-4f6b-a785-8ef0f0fff979

📥 Commits

Reviewing files that changed from the base of the PR and between 2fade3d and 58e77a6.

📒 Files selected for processing (7)
  • src/evo/cbtx.cpp
  • src/evo/cbtx.h
  • src/evo/deterministicmns.cpp
  • src/evo/deterministicmns.h
  • src/evo/specialtx.cpp
  • src/test/evo_simplifiedmns_tests.cpp
  • src/validation.cpp

@navidR

navidR commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author
   Window              Master    Patched    Speedup    Time reduction
  ━━━━━━━━━━━━━━━━━━  ━━━━━━━━  ━━━━━━━━━  ━━━━━━━━━  ━━━━━━━━━━━━━━━━
   280000 -> 285000      135s        56s      2.41x             58.5%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   285000 -> 290000      238s       109s      2.18x             54.2%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   290000 -> 295000      200s        87s      2.30x             56.5%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   295000 -> 300000      210s       102s      2.06x             51.4%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   300000 -> 305000      253s        70s      3.61x             72.3%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   305000 -> 310000      110s        55s      2.00x             50.0%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   310000 -> 315000      168s        60s      2.80x             64.3%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   315000 -> 320000      155s        70s      2.21x             54.8%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   280000 -> 315000     1314s       539s      2.44x             59.0%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   280000 -> 320000     1469s       609s      2.41x             58.5%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   300000 -> 315000      531s       185s      2.87x             65.2%
  ──────────────────  ────────  ─────────  ─────────  ────────────────
   300000 -> 320000      686s       255s      2.69x             62.8%

@navidR navidR force-pushed the dev/navidr/reindex-early-speedup branch from 58e77a6 to 155b6d9 Compare June 14, 2026 15:51
@navidR navidR marked this pull request as ready for review June 14, 2026 16:35
@coderabbitai coderabbitai Bot requested a review from levonpetrosyan93 June 14, 2026 16:36
@coderabbitai coderabbitai Bot added the size:S This PR changes 10-29 lines, ignoring generated files label Jun 14, 2026

@levonpetrosyan93 levonpetrosyan93 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

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

Labels

size:S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants