Skip to content

Evo: speed up early reindex merkle checks#1864

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

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced coinbase transaction (CBTx) merkle-root validation with support for externally provided deterministic masternode lists and multi-level caching.
  • Improvements

    • More accurate detection of when masternode list changes affect CBTx merkle roots.
    • Safer DKG session message handling: invalid BLS signatures are rejected early, and batch verification falls back when needed.
    • Improved validation logging context for easier troubleshooting.
  • Tests

    • Expanded deterministic masternode diff, invalidation, rollback, and parallel-consistency coverage; updated DIP3 RPC test assertions to check immediately.

Walkthrough

The PR extends deterministic MN outputs into CbTx merkle-root validation, adds change-aware caching for MN-list root calculation, expands invalidation regression coverage, and switches several validation logs to the validation log category.

Changes

CbTx Deterministic MN Root Pipeline

Layer / File(s) Summary
Deterministic MN and CbTx contract updates
src/evo/deterministicmns.h, src/evo/cbtx.h
Adds HasSameMNMap and HasCbTxMerkleRootChanges, extends ProcessBlock, and updates CbTx declarations to accept deterministic MN list inputs and change flags.
ProcessBlock and tip-notification state propagation
src/evo/deterministicmns.cpp, src/dsnotificationinterface.cpp
ProcessBlock now returns the processed list and cbTx-change status, and UpdatedBlockTip notifies the deterministic MN manager before the fork early return.
CbTx merkle-root caching and special-tx wiring
src/evo/cbtx.cpp, src/evo/specialtx.cpp
Adds deterministic-list root calculation with cache reuse checks, then passes the returned MN list and change flag from special-tx processing into CheckCbTxMerkleRoots.
Unit tests for cbTx-relevant diff detection
src/test/evo_simplifiedmns_tests.cpp
Adds helpers and a test covering HasCbTxMerkleRootChanges for empty, updated, added, and removed MN diffs.
DIP3 invalidation/disconnect regression suite
src/test/evo_deterministicmns_tests.cpp, qa/rpc-tests/dip3-deterministicmns.py
Adds invalidation/disconnect regression helpers and tests, plus an immediate masternode-list assertion helper for the RPC test.

Validation Categorized Logging

Layer / File(s) Summary
Log category updates across validation entry points
src/validation.cpp
Replaces seven top-level validation log calls with LogPrint("validation", ...) while keeping the same contextual fields.

Estimated code review effort: 4 (Complex) | ~50 minutes

Suggested reviewers: justanwar, firstcryptoman, levonpetrosyan93

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed but does not use the required template headings or separate PR intention and code changes brief sections. Rewrite the description using the template headings "PR intention" and "Code changes brief", and briefly state the issue solved and the main implementation changes.
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 (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: speeding up early reindex merkle checks.
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.
✨ 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.

@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

@navidR navidR force-pushed the dev/navidr/reindex-early-speedup branch from 155b6d9 to c0db1be Compare July 6, 2026 11:54
@codeant-ai

codeant-ai Bot commented Jul 6, 2026

Copy link
Copy Markdown

User rahimi.nv@gmail.com does not have a PR Review subscription.

Go to Team management and add this email to the PR Review subscription.

@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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/evo/cbtx.h (1)

49-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new parameter contract with Doxygen comments.

The deterministicMNList pointer and cbTxMerkleRootMNListChanged flag carry non-obvious preconditions (per the implementation in cbtx.cpp, the pointer is only used if its height/block hash match pindex/block, otherwise callers silently fall back to a full rebuild; the flag controls whether cached merkle-root values are reused). None of this is documented at the declaration site, making misuse easy for future callers.

📝 Suggested Doxygen documentation
+/**
+ * `@param` deterministicMNList Optional pre-computed MN list for `pindex`. Only used if its height and
+ *        block hash match `pindex`/`block`; otherwise the MN list is rebuilt from scratch.
+ * `@param` cbTxMerkleRootMNListChanged Set to false only when the caller has verified the cbTx-relevant
+ *        MN-list fields are unchanged since the previous block, allowing the merkle root to be reused
+ *        from cache. Defaults to true (always recompute) for safety.
+ */
 bool CheckCbTxMerkleRoots(const CBlock& block, const CBlockIndex* pindex, CValidationState& state, const CDeterministicMNList* deterministicMNList = nullptr, bool cbTxMerkleRootMNListChanged = true);
 bool CalcCbTxMerkleRootMNList(const CBlock& block, const CBlockIndex* pindexPrev, uint256& merkleRootRet, CValidationState& state);
+/**
+ * `@param` cbTxMerkleRootMNListChanged See CheckCbTxMerkleRoots. Must be false only when the caller can
+ *        guarantee no cbTx-relevant change occurred relative to `pindexPrev`.
+ */
 bool CalcCbTxMerkleRootMNList(const CDeterministicMNList& deterministicMNList, uint256& merkleRootRet, const CBlockIndex* pindexPrev = nullptr, bool cbTxMerkleRootMNListChanged = true);

As per coding guidelines, "Prefer Doxygen-compatible comments for non-obvious public interfaces" for **/*.{h,hpp} files.

🤖 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.h` around lines 49 - 51, Add Doxygen-compatible comments to the
cbtx.h declarations for CheckCbTxMerkleRoots and CalcCbTxMerkleRootMNList to
document the new parameter contract. Explain the intended use and preconditions
for deterministicMNList and cbTxMerkleRootMNListChanged, including when the
pointer is honored versus ignored and when cached merkle-root values may be
reused. Keep the comments at the declaration site so callers can understand the
behavior without reading cbtx.cpp.

Source: Coding guidelines

🤖 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.

Inline comments:
In `@src/evo/cbtx.cpp`:
- Around line 177-184: The smlCached comparison in the cache-hit path needs to
be guarded by hasCached like the earlier cache checks, because the current
SimplifiedMNListsEqual(sml, smlCached) branch can succeed on the first call with
default-constructed cache state. Update the existing cache logic around
smlCached, deterministicMNListCached, merkleRootCached, and mutatedCached so the
equality check only runs when hasCached is true, keeping the invariant explicit
and avoiding accidental reliance on default values.

---

Nitpick comments:
In `@src/evo/cbtx.h`:
- Around line 49-51: Add Doxygen-compatible comments to the cbtx.h declarations
for CheckCbTxMerkleRoots and CalcCbTxMerkleRootMNList to document the new
parameter contract. Explain the intended use and preconditions for
deterministicMNList and cbTxMerkleRootMNListChanged, including when the pointer
is honored versus ignored and when cached merkle-root values may be reused. Keep
the comments at the declaration site so callers can understand the behavior
without reading cbtx.cpp.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 55711e21-8912-4391-9422-cc4a80a74f5a

📥 Commits

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

📒 Files selected for processing (12)
  • 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/llmq/quorums_dkgsession.cpp
  • src/llmq/quorums_dkgsessionhandler.cpp
  • src/test/evo_deterministicmns_tests.cpp
  • src/test/evo_simplifiedmns_tests.cpp
  • src/validation.cpp
✅ Files skipped from review due to trivial changes (1)
  • src/validation.cpp
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/dsnotificationinterface.cpp
  • src/evo/specialtx.cpp
  • qa/rpc-tests/dip3-deterministicmns.py
  • src/evo/deterministicmns.h
  • src/evo/deterministicmns.cpp
  • src/test/evo_deterministicmns_tests.cpp

Comment thread src/evo/cbtx.cpp
Comment on lines +177 to 184
static CSimplifiedMNList smlCached;

if (sml.mnList == smlCached.mnList) {
if (SimplifiedMNListsEqual(sml, smlCached)) {
deterministicMNListCached = deterministicMNList;
hasCached = true;
merkleRootRet = merkleRootCached;
return !mutatedCached;
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard the smlCached comparison with hasCached.

Unlike the two earlier cache checks, this branch does not verify hasCached. On the first invocation smlCached is default-constructed (empty); if sml is also empty the comparison succeeds and returns the default merkleRootCached/mutatedCached. It is correct only by coincidence (empty-list root is null). Add the guard to keep the invariant explicit and robust to future default changes.

🛡️ Proposed guard
-    if (SimplifiedMNListsEqual(sml, smlCached)) {
+    if (hasCached && SimplifiedMNListsEqual(sml, smlCached)) {
📝 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
static CSimplifiedMNList smlCached;
if (sml.mnList == smlCached.mnList) {
if (SimplifiedMNListsEqual(sml, smlCached)) {
deterministicMNListCached = deterministicMNList;
hasCached = true;
merkleRootRet = merkleRootCached;
return !mutatedCached;
}
static CSimplifiedMNList smlCached;
if (hasCached && SimplifiedMNListsEqual(sml, smlCached)) {
deterministicMNListCached = deterministicMNList;
hasCached = true;
merkleRootRet = merkleRootCached;
return !mutatedCached;
}
🤖 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 177 - 184, The smlCached comparison in the
cache-hit path needs to be guarded by hasCached like the earlier cache checks,
because the current SimplifiedMNListsEqual(sml, smlCached) branch can succeed on
the first call with default-constructed cache state. Update the existing cache
logic around smlCached, deterministicMNListCached, merkleRootCached, and
mutatedCached so the equality check only runs when hasCached is true, keeping
the invariant explicit and avoiding accidental reliance on default values.

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