Skip to content

fix: invalidate quorum commitment cache on disconnect#7476

Open
thepastaclaw wants to merge 1 commit into
dashpay:developfrom
thepastaclaw:fix/cbtx-quorum-cache-reorg
Open

fix: invalidate quorum commitment cache on disconnect#7476
thepastaclaw wants to merge 1 commit into
dashpay:developfrom
thepastaclaw:fix/cbtx-quorum-cache-reorg

Conversation

@thepastaclaw

Copy link
Copy Markdown

Issue being fixed or feature implemented

CachedGetQcHashesQcIndexedHashes kept process-lifetime caches for CbTx quorum merkle-root calculation:

  1. an outer whole-result cache keyed by the active quorum base-block list, and
  2. an inner LRU keyed only by those base-block hashes.

Neither key includes the serialized CFinalCommitment that was actually mined for a base. Different valid branches can mine different valid commitments for the same base list. On disconnect/reorg, evoDb is rolled back correctly, but these caches were not invalidated. That can make CalcCbTxMerkleRootQuorums reject an otherwise-valid replacement branch with bad-cbtx-quorummerkleroot.

Dash triggerability is lower than a pure PoS setting because of PoW and ChainLocks, but the cache keying is still branch-dependent and therefore a correctness bug.

Out of scope: automatic reconsideration of a chainlocked branch already marked failed.

What was done?

  • Hoisted the caches to file-scope guarded state and added InvalidateCachedQcHashes() to clear both the outer whole-result cache and the per-quorum LRU.
  • Call that invalidation from CQuorumBlockProcessor::UndoBlock when any non-null mined commitment is undone.
  • Added unit tests that:
    • prove stale commitment hashes can survive an evoDb branch swap without invalidation, and
    • prove UndoBlock invalidation makes the replacement commitment visible.

How Has This Been Tested?

  • Configured a worktree-local debug build against the shared depends prefix.
  • Built src/test/test_dash.
  • Ran: ./src/test/test_dash --run_test=evo_cbtx_tests (pass: 3 cases, including the two new regression cases).

Breaking Changes

None.

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 (N/A for this correctness fix)
  • I have assigned this pull request to a milestone (if appropriate)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@thepastaclaw

thepastaclaw commented Jul 21, 2026

Copy link
Copy Markdown
Author

✅ Final review complete — no blockers (commit acb611a)

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

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: 804a1faf-7032-4110-aacf-b7b5314172fd

📥 Commits

Reviewing files that changed from the base of the PR and between 8a897de and acb611a.

📒 Files selected for processing (5)
  • src/Makefile.am
  • src/evo/cbtx.cpp
  • src/evo/cbtx_cache.h
  • src/llmq/blockprocessor.cpp
  • src/test/evo_cbtx_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/llmq/blockprocessor.cpp
  • src/evo/cbtx.cpp

Walkthrough

The CbTx quorum commitment hash cache now uses mutex-protected process-lifetime state with whole-result caches and per-LMQ-type LRU entries. A public invalidation helper clears cached results and LRU contents. UndoBlock invokes invalidation after undoing non-null quorum commitments. Tests cover explicit invalidation after commitment replacement and automatic invalidation during block undo.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CQuorumBlockProcessor
  participant UndoBlock
  participant InvalidateCachedQcHashes
  participant CachedGetQcHashesQcIndexedHashes
  CQuorumBlockProcessor->>UndoBlock: undo block commitments
  UndoBlock->>InvalidateCachedQcHashes: invalidate after non-null commitment undo
  InvalidateCachedQcHashes-->>CachedGetQcHashesQcIndexedHashes: clear cached QC hash state
  CachedGetQcHashesQcIndexedHashes->>GetMinedCommitment: reload commitment data on cache miss
Loading

Suggested reviewers: pastapastapasta

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 accurately summarizes the main fix: invalidating quorum commitment cache state on disconnect.
Description check ✅ Passed The description is directly about the same cache invalidation bug, the fix, and the added tests.
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.

@thepastaclaw

Copy link
Copy Markdown
Author

The five container/cache failures in run 29848453760 are the known pull_request_target bootstrap failure, not a #7476 source failure.

Each failing job stops at checkout before compiling this branch because the reusable workflow is loaded from base develop, where checkout v6.1 reports allow-unsafe-pr-checkout: false and refuses the fork head. PR #7474 contains the base-workflow compatibility fix. I am leaving #7476 unchanged and not rerunning these checks.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Final validation — Codex + Sonnet

This PR fixes a real correctness bug where process-lifetime CbTx quorum-commitment hash caches were keyed only by base-block identity, not by the mined CFinalCommitment, allowing a disconnect/reorg to leave stale commitment hashes and incorrectly reject a valid replacement branch with bad-cbtx-quorummerkleroot. I verified the fix directly: InvalidateCachedQcHashes() correctly clears both the outer whole-result cache and the per-quorum LRU under g_qc_hashes_cache_mutex, and is called from CQuorumBlockProcessor::UndoBlock whenever a non-null commitment is undone; the new unit tests demonstrate both the bug (stale hash surviving an evoDb swap) and the fix. The only issue is commit hygiene: the second and third commits are pure iteration on the first commit's own comments/tests within the same PR (one deletes an UndoBlock comment, the next re-adds a shorter version of the same comment one commit later), which is confirmed noise for permanent git blame/bisect history and should be squashed.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — dash-core-commit-history (completed)
  • Verifier: claude-sonnet-5 — verifier
  • Sonnet reviewers: claude-sonnet-5 — general (failed), claude-sonnet-5 — dash-core-commit-history (failed), claude-sonnet-5 — general (completed), claude-sonnet-5 — dash-core-commit-history (completed)

🟡 1 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/llmq/blockprocessor.cpp`:
- [SUGGESTION] src/llmq/blockprocessor.cpp:417-424: Squash the two iterative follow-up commits into the primary fix commit
  I diffed all three commits directly. 5a142a8 introduces the cache-invalidation fix, an explanatory comment at the UndoBlock call site, and the regression tests. c39393a then trims that same comment down and simplifies the tests it just added (including removing an EraseMinedCommitment helper and inlining CBlockIndex/genesis setup). 52abbf3 immediately re-adds a shortened version of the identical comment c39393a had just deleted, word-for-word restating the same rationale ("branch-dependent even when the active quorum base-block list is unchanged"). None of these three commits is independently meaningful in permanent history: c39393a's comment removal is undone one commit later, and 52abbf3 exists solely to patch what c39393a just deleted. A future `git blame`/`git log -p` on this line will show a comment added, deleted, and re-added within the same PR — pure noise for bisect/blame with no reviewable content difference. Squash all three into one commit via interactive rebase before merge.

Comment on lines 417 to +424
AddMineableCommitment(qc);
}

// Drop both CbTx qc-hash cache layers: mined commitments are branch-dependent
// even when the active quorum base-block list is unchanged.
if (undone_commitment) {
InvalidateCachedQcHashes();
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟡 Suggestion: Squash the two iterative follow-up commits into the primary fix commit

I diffed all three commits directly. 5a142a8 introduces the cache-invalidation fix, an explanatory comment at the UndoBlock call site, and the regression tests. c39393a then trims that same comment down and simplifies the tests it just added (including removing an EraseMinedCommitment helper and inlining CBlockIndex/genesis setup). 52abbf3 immediately re-adds a shortened version of the identical comment c39393a had just deleted, word-for-word restating the same rationale ("branch-dependent even when the active quorum base-block list is unchanged"). None of these three commits is independently meaningful in permanent history: c39393a's comment removal is undone one commit later, and 52abbf3 exists solely to patch what c39393a just deleted. A future git blame/git log -p on this line will show a comment added, deleted, and re-added within the same PR — pure noise for bisect/blame with no reviewable content difference. Squash all three into one commit via interactive rebase before merge.

source: ['claude', 'codex']

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 8a897dee04f: squashed the two iterative follow-ups into the primary fix, leaving one signed commit. The resulting tree is byte-identical to the reviewed 52abbf390d3d head; evo_cbtx_tests and git diff --check pass.

@thepastaclaw
thepastaclaw force-pushed the fix/cbtx-quorum-cache-reorg branch from 52abbf3 to 8a897de Compare July 22, 2026 21:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/test/evo_cbtx_tests.cpp`:
- Around line 165-218: Add a local RAII cleanup guard to both
qc_hash_cache_invalidated_on_commitment_branch_change
(src/test/evo_cbtx_tests.cpp lines 165-218) and the UndoBlock test
(src/test/evo_cbtx_tests.cpp lines 232-279) so InvalidateCachedQcHashes() runs
when each test exits, while retaining the existing entry invalidation; ensure
the guard restores the process-global cache even on early failure or exceptions.
🪄 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: 020a5135-daf9-4f95-8d64-2c6c715c31d3

📥 Commits

Reviewing files that changed from the base of the PR and between 52abbf3 and 8a897de.

📒 Files selected for processing (4)
  • src/evo/cbtx.cpp
  • src/evo/cbtx.h
  • src/llmq/blockprocessor.cpp
  • src/test/evo_cbtx_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/evo/cbtx.cpp

Comment thread src/test/evo_cbtx_tests.cpp
CachedGetQcHashesQcIndexedHashes keyed both process-lifetime layers only by
quorum base blocks/hashes, not by the mined CFinalCommitment. Different valid
branches can therefore produce different commitment hashes for the same base
list. evoDb rolled back on disconnect, but the caches did not, which can make
CalcCbTxMerkleRootQuorums reject a valid replacement branch with
bad-cbtx-quorummerkleroot.

Invalidate both the outer whole-result cache and the per-quorum LRU from
CQuorumBlockProcessor::UndoBlock when a non-null commitment is undone, and add
unit coverage for the branch-change and UndoBlock invalidation seams.

Co-Authored-By: Claude <noreply@anthropic.com>
@thepastaclaw
thepastaclaw force-pushed the fix/cbtx-quorum-cache-reorg branch from 8a897de to acb611a Compare July 22, 2026 21:40
@thepastaclaw

Copy link
Copy Markdown
Author

Addressed the latest review and current-head CI findings in amended commit acb611aacfc (kept as a single clean-history commit):

  • added RAII cache cleanup to both new tests for failure/exception-safe isolation;
  • switched the file-scope cache lock to the established GlobalMutex pattern for Clang thread-safety analysis;
  • split the invalidation declaration into evo/cbtx_cache.h to remove the new evo/cbtx -> llmq/blockprocessor -> evo/cbtx include cycle.

Local validation: changed translation units syntax-check cleanly, circular-dependency/include-guard/whitespace lints pass, and independent Codex review found no actionable regression. CI has been re-triggered by the push.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant