Skip to content

backport: assumeutxo M2 — EvoDB multi-chainstate isolation and subsystem gating - #51

Merged
PastaPastaPasta merged 85 commits into
developfrom
assumeutxo/m2-evodb-roles
Aug 8, 2026
Merged

backport: assumeutxo M2 — EvoDB multi-chainstate isolation and subsystem gating#51
PastaPastaPasta merged 85 commits into
developfrom
assumeutxo/m2-evodb-roles

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Issue being fixed or feature implemented

M1 (#50) landed snapshot persistence but kept Dash's single-EvoDB
assumption. To support two live chainstates (background/normal +
snapshot) each with independent masternode/quorum/credit-pool state, EvoDB
needs stable per-chainstate identities and transaction overlays, and every
side-effect subsystem (MN list notifications, LLMQ signing, RPC, UI) needs
to bind validation to the calling chainstate. This is milestone 2/7 (design
docs D2/D3). The execution
plan and per-unit ledger (M2 section) are tracked outside the repo in a
private gist, not checked in: https://gist.github.com/PastaPastaPasta/aa52b1f89fb74a0566ba3b5e15afab6a

PR 2/7 in the stacked series — base is develop after M1 merged.

What was done?

  • B2 — EvoDB multi-chainstate: shared single CEvoDB with per-chainstate
    best-block markers and flush transactions (Option A from the design doc:
    union converges, no merge needed at completion). First implementation was
    rejected on round 1 adversarial review (cross-context pending-read
    visibility broke crash atomicity, unstable role-keyed markers, undo-erase
    cross-chainstate deletion, tombstones defeated by fallback reads); reworked
    to own-overlay+disk-only reads, storage-identity-keyed markers/contexts, an
    undo-erase guard, and a downgrade flag. Two items were explicitly deferred
    to M3: marker promotion to the legacy b_b4 key at completion, and holistic
    base-state comparison (both left as TODOs in evodb.h).
  • B3 — subsystem gating: masternode/quorum/credit-pool side effects gated
    by the calling chainstate and snapshot-validation state.
    Two review rounds found and fixed a critical seeded-commitment accounting
    bug and, more seriously, that MNHF/asset-unlock quorum resolution used
    active-chain semantics during background validation — fixed by threading
    the validating chainstate through CQuorumManager::HasQuorum/GetQuorum/ScanQuorums.
    Masternodes refuse DKG/signing on an unvalidated snapshot chainstate.
  • Orchestrator-found fix: CEvoDB::WriteDerived byte-compares
    re-serialized values, so every block-derived payload type must serialize
    canonically. Two latent violations (CDeterministicMNListDiff::updatedMNs
    as an unordered_map, MNHF Signals as an unordered_map) caused
    BLOCK_CONSENSUS failures on reconsiderblock after restart — fixed by
    sorting on serialize / switching to std::map. As a result,
    feature_mnehf.py and feature_asset_locks.py are now permanent members
    of the milestone functional gate from M2 onward.

How Has This Been Tested?

  • make check: exit 0, 0 failures.
  • Functional subset on the final tree: feature_mnehf, feature_asset_locks,
    feature_dip3_deterministicmns ×2, feature_llmq_signing ×2,
    feature_llmq_rotation, rpc_quorum — all passed.
  • Lint: circular-dependencies and whitespace PASS; python lint deferred to CI.
  • 3 review rounds each for B2 and B3 (adversarial rework → crash-matrix
    verification → final adjudication); milestone gate PASS on 2026-07-11.

Breaking Changes

None externally. Internally, ActivateExistingSnapshot becomes fallible
(returns/propagates failure instead of the upstream reference-return shape) —
noted for future backports that touch this path.

Checklist:

  • I have performed a self-review of my own code
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation (docs rewrite lands in M7/B8)
  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features

    • Added independent management of normal and snapshot chain data.
    • Added status reporting for masternode and quorum participation during snapshot validation.
    • Improved chainlock cache retention and cleanup.
  • Bug Fixes

    • Prevented quorum and masternode operations before snapshot validation completes.
    • Improved handling of unavailable block data and persistence inconsistencies.
  • Tests

    • Expanded coverage for snapshots, database overlays, quorum processing, serialization, and cache pruning.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Core validation and chainstate changes

Layer / File(s) Summary
Identity-scoped EvoDB and chainstate persistence
src/evo/evodb.*, src/validation.*, src/node/chainstate.cpp, src/dbwrapper.h
EvoDB transactions, derived writes, best-block markers, snapshot activation, and chainstate notifications now use chainstate identities.
Chain-aware quorum and special-transaction processing
src/llmq/*, src/evo/assetlocktx.*, src/evo/mnhftx.*, src/evo/specialtxman.*, src/evo/creditpool.cpp, src/evo/deterministicmns.cpp
Quorum lookup, commitment handling, special-transaction validation, and EvoDB persistence now receive explicit chain context and check write results.
Snapshot validation gating and unavailable block data
src/active/*, src/llmq/signing_shares.*, src/llmq/snapshot.cpp, src/evo/smldiff.*, src/net_processing.cpp, src/rpc/masternode.cpp, src/rpc/quorums.cpp
Unvalidated snapshots block DKG and signing. Snapshot builders report unavailable block data. Network handlers avoid peer penalties for that error.
Cache thresholds and deterministic serialization
src/chainlock/*, src/limitedmap.h, src/versionbits.h, src/evo/chainhelper.*, src/evo/deterministicmns.h, src/node/miner.cpp, src/rpc/blockchain.cpp
Caches now separate retained and prune-after sizes. Signal and masternode-list serialization use ordered data.
Validation, persistence, and cache tests
src/test/*, src/Makefile.test.include
Tests cover EvoDB overlays, snapshot markers, chain-aware commitments, callback suppression, canonical serialization, cache pruning, and on-disk test databases.

Pull request description validation

Layer / File(s) Summary
Description mention checker
.github/workflows/check_pr_description_mentions.py, .github/workflows/semantic-pull-request.yml
CI checks pull request descriptions for username mentions while allowing email addresses.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: knst, udjinm6

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.44% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the PR's main changes: AssumeUTXO M2 backport, EvoDB multi-chainstate isolation, and subsystem gating.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch assumeutxo/m2-evodb-roles

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.

@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m1-persistence branch 2 times, most recently from 01795d3 to dc94574 Compare July 12, 2026 14:09
@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m2-evodb-roles branch from 552e404 to 2a37be9 Compare July 12, 2026 14:17
@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m1-persistence branch 4 times, most recently from 35d0d60 to 183f168 Compare July 12, 2026 15:42
@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m2-evodb-roles branch 3 times, most recently from 56cd37a to f4b01da Compare July 13, 2026 22:11
@PastaPastaPasta
PastaPastaPasta changed the base branch from assumeutxo/m1-persistence to develop July 13, 2026 22:12
@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m2-evodb-roles branch from f4b01da to 6a803ac Compare July 13, 2026 22:17

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

Caution

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

⚠️ Outside diff range comments (1)
src/test/validation_chainstatemanager_tests.cpp (1)

463-500: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reopen EvoDB to simulate an actual node restart.

SimulateNodeRestart replaces only the chainstate manager; the same CEvoDB object and its transaction contexts survive. Consequently, the persistence test at Lines 767-793 can pass using retained state rather than data reopened from disk. Close and recreate m_node.evodb with .wipe=false after tearing down its consumers.

🤖 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/test/validation_chainstatemanager_tests.cpp` around lines 463 - 500,
Update SimulateNodeRestart to close and recreate m_node.evodb with wipe=false
after tearing down all consumers that depend on it and before constructing the
replacement ChainstateManager. Ensure the recreated CEvoDB uses the existing
database contents while clearing the old transaction contexts, so persistence
tests exercise reopened disk state.
🤖 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/smldiff.cpp`:
- Around line 185-189: Remove the BLOCK_HAVE_DATA requirement from the
baseBlockIndex ancestry/list-anchor validation in src/evo/smldiff.cpp (lines
185-189), while retaining data checks for the blockIndex that is actually read.
In src/llmq/snapshot.cpp, update lines 43, 89-90, and 123 so metadata-only
CBlockIndex and snapshot lookup paths do not require raw block data; keep such
checks only at actual disk-read sites.

In `@src/test/util/setup_common.cpp`:
- Line 247: Reset or otherwise destroy m_node.evodb in
BasicTestingSetup::~BasicTestingSetup before calling
fs::remove_all(m_path_root), ensuring the disk-backed EvoDB is closed while
preserving the existing in-memory behavior.

In `@src/test/validation_chainstatemanager_tests.cpp`:
- Line 818: Update the restart call in the validation-chainstate test to pass
flush_chainstates=false after erasing the snapshot marker, so the erased marker
remains absent when startup failure is checked. Ensure SimulateNodeRestart
supports this no-flush path with a real EvoDB reopen, while preserving the
existing restart behavior elsewhere.

In `@src/validation.cpp`:
- Around line 3564-3567: Extend the active-chainstate guards in
src/validation.cpp at lines 3564-3567 and 3669-3672 to also enclose the adjacent
uiInterface.NotifyBlockTip calls at lines 3570-3573 and 3674-3677. Keep both
SynchronousUpdatedBlockTip and UpdatedBlockTip notifications restricted to
ActiveChainstate, preventing all related UI tip notifications from background
chainstates.

In `@test/lint/lint-circular-dependencies.py`:
- Line 27: Remove the allowlisted cycle entry involving blockfilter and
interfaces/chain.h from the circular-dependency test. Refactor the dependency
path through validation, kernel/chain, and interfaces/chain.h so shared chain
metadata comes from a lower-level header or an interface rather than the
concrete interface-layer dependency, preserving the intended isolation boundary.

---

Outside diff comments:
In `@src/test/validation_chainstatemanager_tests.cpp`:
- Around line 463-500: Update SimulateNodeRestart to close and recreate
m_node.evodb with wipe=false after tearing down all consumers that depend on it
and before constructing the replacement ChainstateManager. Ensure the recreated
CEvoDB uses the existing database contents while clearing the old transaction
contexts, so persistence tests exercise reopened disk state.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9edf0b40-f213-4658-b878-17634348f43a

📥 Commits

Reviewing files that changed from the base of the PR and between f153015 and 6a803ac6acb2ac818b8d8cd8c6495b7f30b081db.

📒 Files selected for processing (51)
  • src/Makefile.am
  • src/Makefile.test.include
  • src/active/context.cpp
  • src/active/context.h
  • src/active/dkgsessionhandler.cpp
  • src/dbwrapper.h
  • src/evo/assetlocktx.cpp
  • src/evo/assetlocktx.h
  • src/evo/chainhelper.cpp
  • src/evo/chainhelper.h
  • src/evo/creditpool.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/deterministicmns.h
  • src/evo/evodb.cpp
  • src/evo/evodb.h
  • src/evo/mnhftx.cpp
  • src/evo/mnhftx.h
  • src/evo/smldiff.cpp
  • src/evo/smldiff.h
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
  • src/interfaces/chain.h
  • src/kernel/chain.cpp
  • src/kernel/chain.h
  • src/llmq/blockprocessor.cpp
  • src/llmq/blockprocessor.h
  • src/llmq/context.cpp
  • src/llmq/quorumsman.cpp
  • src/llmq/quorumsman.h
  • src/llmq/signing_shares.cpp
  • src/llmq/signing_shares.h
  • src/llmq/snapshot.cpp
  • src/net_processing.cpp
  • src/node/chainstate.cpp
  • src/node/interfaces.cpp
  • src/node/miner.cpp
  • src/rpc/blockchain.cpp
  • src/rpc/masternode.cpp
  • src/rpc/quorums.cpp
  • src/test/evo_db_tests.cpp
  • src/test/evo_deterministicmns_tests.cpp
  • src/test/util/setup_common.cpp
  • src/test/util/setup_common.h
  • src/test/validation_chainstatemanager_tests.cpp
  • src/validation.cpp
  • src/validation.h
  • src/versionbits.h
  • src/wallet/test/fuzz/notifications.cpp
  • src/wallet/wallet.cpp
  • src/wallet/wallet.h
  • test/lint/lint-circular-dependencies.py

Comment thread src/evo/smldiff.cpp Outdated
Comment thread src/test/util/setup_common.cpp
Comment thread src/test/validation_chainstatemanager_tests.cpp
Comment thread src/validation.cpp
Comment thread test/lint/lint-circular-dependencies.py Outdated
@@ -24,6 +24,7 @@
"kernel/mempool_persist -> validation -> kernel/mempool_persist",
# Dash
"banman -> common/bloom -> evo/assetlocktx -> llmq/quorumsman -> llmq/blockprocessor -> net -> banman",
"blockfilter -> evo/specialtx_filter -> evo/providertx -> validation -> kernel/chain -> interfaces/chain.h -> blockfilter",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Break the new interface-layer cycle instead of allowlisting it.

The cycle routes implementation dependencies back through interfaces/chain.h, weakening the isolation boundary. Move the shared chain metadata contract to a lower-level header or replace the concrete dependency, then remove this exception.

As per coding guidelines, “Use interfaces in src/interfaces/ for codebase isolation and inter-process communication.” <coding_guidelines>

🤖 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 `@test/lint/lint-circular-dependencies.py` at line 27, Remove the allowlisted
cycle entry involving blockfilter and interfaces/chain.h from the
circular-dependency test. Refactor the dependency path through validation,
kernel/chain, and interfaces/chain.h so shared chain metadata comes from a
lower-level header or an interface rather than the concrete interface-layer
dependency, preserving the intended isolation boundary.

Source: Coding guidelines

@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m2-evodb-roles branch from 6a803ac to 014afb4 Compare July 13, 2026 22:45
@PastaPastaPasta PastaPastaPasta changed the title backport: assumeutxo M2 — EvoDB roles and multi-chainstate subsystem gating backport: assumeutxo M2 — EvoDB multi-chainstate isolation and subsystem gating Jul 13, 2026
@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m2-evodb-roles branch 2 times, most recently from 06b5c6f to 1a95697 Compare July 14, 2026 17:01

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

♻️ Duplicate comments (2)
src/test/validation_chainstatemanager_tests.cpp (1)

785-807: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Re-verify: does SimulateNodeRestart()'s default flush restore the marker this test just erased?

This test erases and fully commits the SNAPSHOT EvoDB marker (789-795), then calls this->SimulateNodeRestart(); with the default flush_chainstates=true, which flushes every chainstate — including the snapshot chainstate whose marker was just erased — before DetectSnapshotChainstate is checked. A sibling test (chainstatemanager_evodb_snapshot_only_flush_restart, 748-759) deliberately uses SimulateNodeRestart(/*flush_chainstates=*/false) to avoid exactly this kind of marker rewrite. This is the same scenario a past review flagged as defeating the missing-marker test via an unwanted flush; the resolution is marked "Addressed" but the visible code here still uses the default true, so it's worth confirming Chainstate::ForceFlushStateToDisk() doesn't unconditionally re-derive/re-write the EvoDB best-block marker (if it does, this test would pass for the wrong reason, or could become flaky).

#!/bin/bash
# Inspect ForceFlushStateToDisk to see whether it unconditionally writes the EvoDB best-block marker.
rg -n -B3 -A25 'void Chainstate::ForceFlushStateToDisk' src/validation.cpp
rg -n -B3 -A5 'WriteBestBlock' src/validation.cpp
🤖 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/test/validation_chainstatemanager_tests.cpp` around lines 785 - 807, Use
SimulateNodeRestart(/*flush_chainstates=*/false) in
chainstatemanager_snapshot_init_missing_evodb_marker so the erased and committed
SNAPSHOT EvoDB marker remains absent when DetectSnapshotChainstate runs; keep
the rest of the test unchanged.
src/validation.cpp (1)

3564-3573: 🎯 Functional Correctness | 🟠 Major

UI tip notification still not gated to the active chainstate.

GetMainSignals().SynchronousUpdatedBlockTip/UpdatedBlockTip are now correctly restricted to the active chainstate (Lines 3564-3567), but the adjacent uiInterface.NotifyBlockTip(...) a few lines below (3571-3573) is gated only by pindex_was_in_chain, not by this == &m_chainman.ActiveChainstate(). The same gap exists in MarkConflictingBlock at Lines 3669-3677. Every other refactored call site in this file (ConnectBlock, DisconnectBlock, ActivateBestChain) gates the ValidationInterface signals and the UI notification together in a single active-chainstate check — these two sites are the outliers. A background/snapshot-validation chainstate invalidating/marking-conflicting a block on its own chain would still surface a UI tip update for a chain that isn't the active one.

This was flagged in a prior review round and marked addressed, but the current code still shows the unfixed pattern.

🐛 Suggested fix (apply analogously at both sites)
         InvalidChainFound(to_mark_failed);
         if (this == &m_chainman.ActiveChainstate()) {
             GetMainSignals().SynchronousUpdatedBlockTip(m_chain.Tip(), nullptr, IsInitialBlockDownload());
             GetMainSignals().UpdatedBlockTip(m_chain.Tip(), nullptr, IsInitialBlockDownload());
+
+            // Only notify about a new block tip if the active chain was modified.
+            if (pindex_was_in_chain) {
+                uiInterface.NotifyBlockTip(GetSynchronizationState(IsInitialBlockDownload()), to_mark_failed->pprev);
+            }
         }
     }
-
-    // Only notify about a new block tip if the active chain was modified.
-    if (pindex_was_in_chain) {
-        uiInterface.NotifyBlockTip(GetSynchronizationState(IsInitialBlockDownload()), to_mark_failed->pprev);
-    }
     return true;
🤖 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` around lines 3564 - 3573, Gate the
uiInterface.NotifyBlockTip call in both the shown block-tip handling path and
MarkConflictingBlock with the same this == &m_chainman.ActiveChainstate()
condition used for the corresponding ValidationInterface signals, while
preserving the existing pindex_was_in_chain check.
🧹 Nitpick comments (6)
src/evo/evodb.cpp (1)

96-123: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Non-NORMAL best-block key hardcodes uint8_t{1} regardless of which identity it is.

ReadBestBlock/WriteBestBlock route every identity other than NORMAL to the same literal key (EVODB_BEST_BLOCK, uint8_t{1}). Since every identity's root_transaction is layered over the same underlying CDBWrapper (db), this only avoids collisions today because exactly one non-NORMAL identity (SNAPSHOT) exists. If a third identity is introduced (the PR notes marker work continues into M3), two non-NORMAL identities would silently overwrite each other's best-block marker on the shared disk namespace.

Deriving the suffix from the identity itself removes the landmine cheaply:

♻️ Suggested fix
-    return transaction.Read(std::make_pair(EVODB_BEST_BLOCK, uint8_t{1}), hash);
+    return transaction.Read(std::make_pair(EVODB_BEST_BLOCK, static_cast<uint8_t>(identity)), hash);

and symmetrically in WriteBestBlock.

🤖 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/evodb.cpp` around lines 96 - 123, Update ReadBestBlock and
WriteBestBlock so the non-NORMAL best-block key derives its suffix from the
provided EvoDbIdentity instead of hardcoding uint8_t{1}. Preserve the existing
EVODB_BEST_BLOCK key for NORMAL and ensure distinct non-NORMAL identities use
distinct keys in the shared database namespace.
src/validation.h (1)

542-542: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider marking the method as const.

If EvoDbInconsistencyMessage does not modify the state of the object, it is best practice to mark it as const.

♻️ Proposed refactor
-    std::string EvoDbInconsistencyMessage();
+    std::string EvoDbInconsistencyMessage() const;
🤖 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.h` at line 542, Update EvoDbInconsistencyMessage to be
const-qualified if its implementation does not modify object state, and ensure
the corresponding definition matches the declaration.
src/test/block_reward_reallocation_tests.cpp (2)

54-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the typo in the parameter name.

The parameter name is misspelled as utoxs. Consider renaming it to utxos (and updating its usages within the methods) for clarity and consistency.

  • src/test/block_reward_reallocation_tests.cpp#L54-L54: rename utoxs to utxos.
  • src/test/block_reward_reallocation_tests.cpp#L84-L84: rename utoxs to utxos.
🤖 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/test/block_reward_reallocation_tests.cpp` at line 54, Rename the
SelectUTXOs parameter from utoxs to utxos and update all references within the
function. Apply the same spelling correction at
src/test/block_reward_reallocation_tests.cpp lines 54-54 and 84-84.

86-86: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Initialize the change variable.

Although SelectUTXOs writes a value to this reference internally, explicitly initializing the variable at declaration improves safety and readability.

♻️ Proposed refactor
-    CAmount change;
+    CAmount change = 0;
🤖 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/test/block_reward_reallocation_tests.cpp` at line 86, Initialize the
CAmount variable change at its declaration before passing it to SelectUTXOs,
while preserving the existing reference usage and value assignment flow.
src/llmq/net_signing.cpp (1)

67-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated log/ban/rethrow pattern.

The try/catch block (log rejection, BanNode, throw;) is duplicated across QSIGSHARE, QSIGSESANN, QSIGSHARESINV/QGETSIGSHARES, and QBSIGSHARES. A small templated helper would remove the duplication and keep future limit/message-type changes in one place.

♻️ Suggested helper
template <typename T>
static bool TryUnserializeCapped(CDataStream& vRecv, std::vector<T>& msgs, size_t max_size,
                                  std::string_view msg_type, NodeId peer_id,
                                  PeerManager& peer_manager, std::function<void(NodeId)> ban_node)
{
    try {
        if (!UnserializeVectorWithMaxSize(vRecv, msgs, max_size)) {
            throw std::ios_base::failure(strprintf("%s vector size too large", msg_type));
        }
        return true;
    } catch (const std::ios_base::failure& e) {
        LogPrint(BCLog::LLMQ_SIGS, "NetSigning::%s -- rejected %s from peer=%d: %s\n",
                 __func__, msg_type, peer_id, e.what());
        ban_node(peer_id);
        throw;
    }
}
🤖 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/llmq/net_signing.cpp` around lines 67 - 130, Extract the duplicated
deserialization error handling from NetSigning’s message branches into a
reusable templated helper near the relevant implementation, covering capped
vector deserialization, rejection logging, peer banning, and exception
rethrowing. Update the QSIGSHARE, QSIGSESANN, QSIGSHARESINV/QGETSIGSHARES, and
QBSIGSHARES paths to use the helper while preserving each branch’s existing size
limit and deserialization routine.
src/test/evo_deterministicmns_tests.cpp (1)

149-173: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

CreateProRegTxExternalCollateral funds a redundant collateral-sized output.

Since the collateral is supplied externally via collateralOutpoint, this helper doesn't need to fund another dmn_types::Regular.collat_amount output — the other update-tx helpers in this file fund with a nominal 1 * COIN for exactly this reason. Creating a second collateral-value output here is at best confusing and at worst a latent trap for any future caller that runs GetCollateralOutpoint on this function's output expecting it to identify a real collateral.

♻️ Suggested funding-amount fix
-    const auto spent = FundTransaction(chainman, tx, utxos, scriptPayout, dmn_types::Regular.collat_amount);
+    const auto spent = FundTransaction(chainman, tx, utxos, scriptPayout, 1 * COIN);
🤖 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/test/evo_deterministicmns_tests.cpp` around lines 149 - 173, Update
CreateProRegTxExternalCollateral so FundTransaction uses the nominal 1 * COIN
amount instead of dmn_types::Regular.collat_amount, while preserving the
externally supplied collateralOutpoint as the actual collateral reference.
🤖 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/active/context.cpp`:
- Around line 98-109: Move the nodeman->UpdatedBlockTip call in
CActiveMasternodeManager::UpdatedBlockTip below the
IsSnapshotActiveAndUnvalidated guard, so it is skipped while snapshot validation
is active. Preserve the existing m_snapshot_duty_blocked logging and state
transitions, and invoke UpdatedBlockTip only after validation has completed.

In `@src/test/evo_deterministicmns_tests.cpp`:
- Around line 1454-1471: Update
deterministic_mn_list_diff_serialization_is_canonical to test a serialization
behavior that specifically requires canonical key ordering, rather than only
comparing different insertion orders of unordered_map entries. Add a scenario
whose serialized output differs when key sorting is removed, and assert the
canonical result against an explicitly constructed expected ordering or byte
sequence.

---

Duplicate comments:
In `@src/test/validation_chainstatemanager_tests.cpp`:
- Around line 785-807: Use SimulateNodeRestart(/*flush_chainstates=*/false) in
chainstatemanager_snapshot_init_missing_evodb_marker so the erased and committed
SNAPSHOT EvoDB marker remains absent when DetectSnapshotChainstate runs; keep
the rest of the test unchanged.

In `@src/validation.cpp`:
- Around line 3564-3573: Gate the uiInterface.NotifyBlockTip call in both the
shown block-tip handling path and MarkConflictingBlock with the same this ==
&m_chainman.ActiveChainstate() condition used for the corresponding
ValidationInterface signals, while preserving the existing pindex_was_in_chain
check.

---

Nitpick comments:
In `@src/evo/evodb.cpp`:
- Around line 96-123: Update ReadBestBlock and WriteBestBlock so the non-NORMAL
best-block key derives its suffix from the provided EvoDbIdentity instead of
hardcoding uint8_t{1}. Preserve the existing EVODB_BEST_BLOCK key for NORMAL and
ensure distinct non-NORMAL identities use distinct keys in the shared database
namespace.

In `@src/llmq/net_signing.cpp`:
- Around line 67-130: Extract the duplicated deserialization error handling from
NetSigning’s message branches into a reusable templated helper near the relevant
implementation, covering capped vector deserialization, rejection logging, peer
banning, and exception rethrowing. Update the QSIGSHARE, QSIGSESANN,
QSIGSHARESINV/QGETSIGSHARES, and QBSIGSHARES paths to use the helper while
preserving each branch’s existing size limit and deserialization routine.

In `@src/test/block_reward_reallocation_tests.cpp`:
- Line 54: Rename the SelectUTXOs parameter from utoxs to utxos and update all
references within the function. Apply the same spelling correction at
src/test/block_reward_reallocation_tests.cpp lines 54-54 and 84-84.
- Line 86: Initialize the CAmount variable change at its declaration before
passing it to SelectUTXOs, while preserving the existing reference usage and
value assignment flow.

In `@src/test/evo_deterministicmns_tests.cpp`:
- Around line 149-173: Update CreateProRegTxExternalCollateral so
FundTransaction uses the nominal 1 * COIN amount instead of
dmn_types::Regular.collat_amount, while preserving the externally supplied
collateralOutpoint as the actual collateral reference.

In `@src/validation.h`:
- Line 542: Update EvoDbInconsistencyMessage to be const-qualified if its
implementation does not modify object state, and ensure the corresponding
definition matches the declaration.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 286d0ead-f9b0-42f1-8d60-585ac42c9b99

📥 Commits

Reviewing files that changed from the base of the PR and between 6a803ac6acb2ac818b8d8cd8c6495b7f30b081db and 1a95697.

📒 Files selected for processing (52)
  • .github/workflows/handle_potential_conflicts.py
  • .github/workflows/test_handle_potential_conflicts.py
  • src/Makefile.test.include
  • src/active/context.cpp
  • src/active/context.h
  • src/active/dkgsessionhandler.cpp
  • src/dbwrapper.h
  • src/evo/assetlocktx.cpp
  • src/evo/assetlocktx.h
  • src/evo/chainhelper.cpp
  • src/evo/chainhelper.h
  • src/evo/creditpool.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/deterministicmns.h
  • src/evo/evodb.cpp
  • src/evo/evodb.h
  • src/evo/mnhftx.cpp
  • src/evo/mnhftx.h
  • src/evo/smldiff.cpp
  • src/evo/smldiff.h
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
  • src/index/coinstatsindex.cpp
  • src/index/coinstatsindex.h
  • src/llmq/blockprocessor.cpp
  • src/llmq/blockprocessor.h
  • src/llmq/context.cpp
  • src/llmq/net_signing.cpp
  • src/llmq/net_signing.h
  • src/llmq/quorumsman.cpp
  • src/llmq/quorumsman.h
  • src/llmq/signing_shares.cpp
  • src/llmq/signing_shares.h
  • src/llmq/snapshot.cpp
  • src/net_processing.cpp
  • src/node/chainstate.cpp
  • src/node/miner.cpp
  • src/rpc/blockchain.cpp
  • src/rpc/masternode.cpp
  • src/rpc/quorums.cpp
  • src/test/block_reward_reallocation_tests.cpp
  • src/test/evo_db_tests.cpp
  • src/test/evo_deterministicmns_tests.cpp
  • src/test/governance_inv_tests.cpp
  • src/test/llmq_utils_tests.cpp
  • src/test/util/setup_common.cpp
  • src/test/util/setup_common.h
  • src/test/validation_chainstatemanager_tests.cpp
  • src/validation.cpp
  • src/validation.h
  • src/versionbits.h
  • test/functional/feature_coinstatsindex.py
💤 Files with no reviewable changes (2)
  • src/index/coinstatsindex.cpp
  • test/functional/feature_coinstatsindex.py
🚧 Files skipped from review as they are similar to previous changes (27)
  • src/llmq/context.cpp
  • src/evo/chainhelper.cpp
  • src/evo/deterministicmns.h
  • src/evo/assetlocktx.h
  • src/versionbits.h
  • src/node/miner.cpp
  • src/evo/creditpool.cpp
  • src/rpc/quorums.cpp
  • src/active/dkgsessionhandler.cpp
  • src/node/chainstate.cpp
  • src/rpc/masternode.cpp
  • src/active/context.h
  • src/evo/smldiff.cpp
  • src/evo/mnhftx.h
  • src/evo/smldiff.h
  • src/llmq/signing_shares.cpp
  • src/net_processing.cpp
  • src/evo/specialtxman.h
  • src/evo/assetlocktx.cpp
  • src/llmq/quorumsman.h
  • src/evo/mnhftx.cpp
  • src/Makefile.test.include
  • src/test/util/setup_common.h
  • src/llmq/quorumsman.cpp
  • src/llmq/blockprocessor.h
  • src/evo/specialtxman.cpp
  • src/evo/evodb.h

Comment thread src/active/context.cpp
Comment thread src/test/evo_deterministicmns_tests.cpp
@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m2-evodb-roles branch from 1a95697 to a3c4476 Compare July 20, 2026 16:59

@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/evo/evodb.h`:
- Around line 190-197: Update GetMemoryUsage() to acquire LOCK(cs) before
iterating transaction_contexts, and annotate transaction_contexts with
GUARDED_BY(cs) so accesses are statically protected. Preserve the existing
memory-usage calculation.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ba18c9b-3d24-48ca-bcd5-424590fe282c

📥 Commits

Reviewing files that changed from the base of the PR and between 1a95697 and a3c4476.

📒 Files selected for processing (54)
  • .github/workflows/build-depends.yml
  • .github/workflows/build-src.yml
  • .github/workflows/build.yml
  • src/Makefile.test.include
  • src/active/context.cpp
  • src/active/context.h
  • src/active/dkgsessionhandler.cpp
  • src/coinjoin/coinjoin.cpp
  • src/coinjoin/coinjoin.h
  • src/coinjoin/server.cpp
  • src/coinjoin/server.h
  • src/dbwrapper.h
  • src/evo/assetlocktx.cpp
  • src/evo/assetlocktx.h
  • src/evo/chainhelper.cpp
  • src/evo/chainhelper.h
  • src/evo/creditpool.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/deterministicmns.h
  • src/evo/evodb.cpp
  • src/evo/evodb.h
  • src/evo/mnhftx.cpp
  • src/evo/mnhftx.h
  • src/evo/smldiff.cpp
  • src/evo/smldiff.h
  • src/evo/specialtxman.cpp
  • src/evo/specialtxman.h
  • src/governance/governance.cpp
  • src/governance/governance.h
  • src/llmq/blockprocessor.cpp
  • src/llmq/blockprocessor.h
  • src/llmq/context.cpp
  • src/llmq/quorumsman.cpp
  • src/llmq/quorumsman.h
  • src/llmq/signing_shares.cpp
  • src/llmq/signing_shares.h
  • src/llmq/snapshot.cpp
  • src/net_processing.cpp
  • src/node/chainstate.cpp
  • src/node/miner.cpp
  • src/rpc/blockchain.cpp
  • src/rpc/masternode.cpp
  • src/rpc/quorums.cpp
  • src/test/bls_tests.cpp
  • src/test/coinjoin_inouts_tests.cpp
  • src/test/evo_db_tests.cpp
  • src/test/evo_deterministicmns_tests.cpp
  • src/test/governance_inv_tests.cpp
  • src/test/util/setup_common.cpp
  • src/test/util/setup_common.h
  • src/test/validation_chainstatemanager_tests.cpp
  • src/validation.cpp
  • src/validation.h
  • src/versionbits.h
💤 Files with no reviewable changes (2)
  • src/governance/governance.h
  • src/test/bls_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (31)
  • src/evo/smldiff.h
  • src/versionbits.h
  • src/evo/assetlocktx.h
  • src/llmq/context.cpp
  • src/dbwrapper.h
  • src/rpc/masternode.cpp
  • src/Makefile.test.include
  • src/evo/specialtxman.h
  • src/evo/mnhftx.h
  • src/evo/chainhelper.cpp
  • src/evo/creditpool.cpp
  • src/evo/smldiff.cpp
  • src/net_processing.cpp
  • src/llmq/signing_shares.h
  • src/evo/deterministicmns.cpp
  • src/node/miner.cpp
  • src/evo/chainhelper.h
  • src/active/context.cpp
  • src/validation.h
  • src/test/util/setup_common.h
  • src/llmq/blockprocessor.h
  • src/evo/assetlocktx.cpp
  • src/node/chainstate.cpp
  • src/evo/specialtxman.cpp
  • src/llmq/signing_shares.cpp
  • src/llmq/quorumsman.h
  • src/rpc/blockchain.cpp
  • src/llmq/blockprocessor.cpp
  • src/llmq/quorumsman.cpp
  • src/validation.cpp
  • src/test/evo_deterministicmns_tests.cpp

Comment thread src/evo/evodb.h Outdated
@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m2-evodb-roles branch from a3c4476 to 26c8718 Compare July 25, 2026 00:07
…ed masternodes

536f388 fix(qt): keep PoSe score visible when hiding banned masternodes (PastaClaw)

Pull request description:

  # PR description

  ## Summary

  - Keep the Masternodes tab PoSe Score column visible when "Hide banned" is
    checked.
  - Continue filtering banned masternodes via the existing proxy filter.

  Closes dashpay#7286.

  ## Validation

  - `git diff --check`
  - Pre-PR review gate: ship
  - Not run: full build / GUI smoke test; no build directory was available in
    this worktree.

Top commit has no ACKs.

Tree-SHA512: ca69dd31322d78ced7d48621ea4dbef8ec65305411abcd245a9b8c7b3059870dfc174caec2ec6d9a05f7940263379e4d5991ad5d1eeb69f5b4894d5aa4751bde
(cherry picked from commit 71453a8)
…otx listdiff results

Backport of dashpay#7360 (upstream merge fb31170, cherry-picked with -m1).

v23.1.x adaptation: upstream wraps the deprecated platformP2PPort/platformHTTPPort fields in IsServiceDeprecatedRPCEnabled(); that gate does not exist on this branch (removed by bbcd9d5 - the fields deliberately remain unenforced through gating) and v23.1.7 always returned them. Replaced the condition with 'if (true)' to keep the block structurally aligned with develop, per review feedback from UdjinM6 on the previous attempt. The genuine fix (reading the live Platform ports from netInfo instead of the always-zero scalar fields) is unchanged from upstream.

(cherry picked from commit fb31170e24c1793d442ede1a3aeb00f89f52fdcb)
…tibility error for freetype

4a8a5a3 Merge bitcoin#32693: depends: fix cmake compatibility error for freetype (Konstantin Akimov)

Pull request description:

  ## Issue being fixed or feature implemented
  It fixes freetype dependency build on Kubuntu 26.04 which provide cmake-4 by default.

  ## What was done?
  Backport bitcoin#32693

  ## How Has This Been Tested?
  Build succeed

  ## Breaking Changes
  N/A

  ## Checklist:
  - [x] 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
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  PastaPastaPasta:
    utACK 4a8a5a3

Tree-SHA512: b9b796ef3a6e39a1acd58bee1f990d7a5c1b9bcc1b75bc5cb8f9e00ad9cc57cd85c5fd62f585ba7cb57493e55236d8d95069a739acad271cfd01dc6da23065ab
(cherry picked from commit b281041)
4e5cc4b fix: stabilize par help text in manpages (PastaClaw)

Pull request description:

  # fix: stabilize par help text in manpages

  ## Issue being fixed or feature implemented

  Regenerating manpages currently records the local machine's CPU count in the
  `-par` and `-parbls` help text. That makes otherwise unrelated release
  manpage regeneration change those entries depending on which machine generated
  the pages.

  ## What was done?

  Updated the `-par` and `-parbls` help text to avoid printing the dynamic lower
  bound derived from `GetNumCores()`.

  Runtime behavior is unchanged: negative values still mean "leave that many
  cores free", `0` still auto-detects, and the existing max/default values remain
  documented. The checked-in `dashd` and `dash-qt` manpages were updated to match
  the new stable generated text.

  ## How Has This Been Tested?

  Tested on macOS arm64:

  ```bash
  git diff --check
  python3 -m py_compile contrib/devtools/gen-manpages.py
  rg -n -e '\\fB\\-[0-9]+\\fR to' doc/man/dashd.1 doc/man/dash-qt.1
  ```

  The grep command returned no matches, confirming the affected generated
  manpages no longer contain a CPU-count-specific lower bound.

  Also ran a pre-PR code review gate against the exact worktree diff:

  ```text
  Recommendation: ship
  ```

  Note: this worktree did not have configured Dash Core build artifacts and
  `help2man` is not installed locally, so I did not rerun full manpage generation
  from rebuilt binaries here. The manpage edits mirror the changed `src/init.cpp`
  help text.

  ## Breaking Changes

  None.

  ## Checklist

  - [x] 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
  - [x] I have made corresponding changes to the documentation
  - [ ] I have assigned this pull request to a milestone _(for repository
    code-owners and collaborators only)_

ACKs for top commit:
  PastaPastaPasta:
    utACK 4e5cc4b
  UdjinM6:
    utACK 4e5cc4b

Tree-SHA512: a547caf7f7dce3c2d4dc8295cba75d23da3870b90fd274ee9a3ebbcb2320d8415ef682afe5ce8a9cd56f6b1c979a9c5b7f680032aa2774fd2d61282fbb4007e5
(cherry picked from commit dfe1e5d)
Backport of dashpay#7395 (upstream merge 547bf5e, cherry-picked with -m1).

v23.1.x adaptation: in release_docker_hub.yml the branch pins actions/github-script at v6 (develop was at v7); applied the same bump to v8 that the PR makes. No other pins in that file were changed - develop-only bumps from unrelated PRs were not pulled in.

(cherry picked from commit 547bf5eee2405ec53d213bad9b31c3d7b6284546)
7cb0c29 fix: fall back to serial map when fork is unavailable, drop multiprocess dep (UdjinM6)
de1cc2c fix: run of circular-dependencies with python3.15 (Konstantin Akimov)

Pull request description:

  ## Issue being fixed or feature implemented
  Multiprocess uses dill to pickle the nested handle_module2 closure and dill's Python 3.15 support is broken (co_lnotab was removed from code objects).

  ## What was done?
  Moved some functions and variables to global namespace.

  ## How Has This Been Tested?
  Run `test/lint/lint-circular-dependencies.py` with python3.15
  Review hint: use `git show -w --color-moved=dimmed-zebra`

  ## Breaking Changes
    _Please describe any breaking changes your code introduces_

  ## Checklist:
  - [x] 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
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  UdjinM6:
    utACK 7cb0c29

Tree-SHA512: cc1aef094f8ed0ffd17e4ef7fdb3bb20af048b8de5c2120b0283fcf7ebb3957f8a8736d97dcd543ef5ac1d3210ed39934fde898d5c47a0ad482d4bf0e263bb91
(cherry picked from commit 87ac140)
PastaPastaPasta and others added 28 commits August 3, 2026 22:24
…etHandler::ProcessGetData

c01370e refactor: drop redundant CConnman argument from NetHandler::ProcessGetData (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  `NetHandler::ProcessGetData` takes a `CConnman&` argument that no implementation actually needs. Every handler that overrides it already holds its own `CConnman&`:

  - `CCoinJoinServer::connman`
  - `NetGovernance::m_connman`
  - `NetDKG::ActiveDKG::connman`

  In a running node there is exactly one `CConnman` — `node.connman` in `init.cpp` — and that is the same object `PeerManagerImpl` passes as `m_connman` at the call site, so the argument was always identical to the member.

  The parameter is a mechanical artifact of a56c106 ("refactor: drop dependency of PeerManager on CoinJoinServer"), which moved these branches out of `PeerManagerImpl::ProcessGetData`. The moved lines read `m_connman.PushMessage(...)`, so `m_connman` was promoted to a parameter to keep the hunk a 1:1 move. Nobody reconciled it with the members the handlers already had.

  In `CCoinJoinServer::ProcessGetData` the parameter additionally **shadowed** the class member of the same name, so the body silently used the argument while every other method in the file uses the member.

  ## What was done?

  Dropped `CConnman& connman` from the `NetHandler::ProcessGetData` virtual and from all three overrides; each handler now replies through the `CConnman` it already holds.

  `msgMaker` stays a parameter — it is `CNetMsgMaker(pfrom.GetCommonVersion())`, genuinely per-peer state the handler cannot reconstruct on its own.

  `NetDKG` keeps its `CConnman` where it already lived, in the active-mode-only `ActiveDKG` bundle, and replies via `m_active->connman`. One consequence worth noting for review: this makes the existing `m_active == nullptr` early return *guard* the replies rather than merely short-circuit them, so the comment above it is updated to say so. That is not a behavior change — the class doc already documents `ProcessGetData` as active-mode only, because the underlying `Get*` calls return false by construction in observer mode.

  No behavior change: same object, same call, in every case.

  ## How Has This Been Tested?

  Not built or run locally — this branch was reviewed by inspection. Verified by grep that no `ProcessGetData` declaration, definition, or call site still carries a `CConnman` argument. Relying on CI for the compile and test run.

  ## Breaking Changes

  None. `NetHandler` is an internal interface with no external implementors.

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [x] 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)_

Top commit has no ACKs.

Tree-SHA512: 0239c12d6b43b68c856944f1352b7aef36a84a9536d621892e7ca77b58ea57bfa37577cc9af9de22ad742f817efc276661c3fd04d358e29b7006fd2ac03b03c6
… from AsyncSignIfMember

7c05e56 refactor: drop redundant CSigningManager argument from AsyncSignIfMember (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  `CSigSharesManager::AsyncSignIfMember` takes a `CSigningManager&` argument it does not need. The class has held a `CSigningManager& sigman` member since a352456 (dashpay#4988), declared 34 lines above the method in the same header, and the parameter **shadows** it.

  There is exactly one `CSigningManager` in a node — `node.llmq_ctx->sigman`. `init.cpp` threads it into `ActiveContext`, which hands the same reference to `CSigSharesManager`'s member and to each caller's own member, so every call site already passes the object the member points at:

  | Call site | Passes |
  |---|---|
  | `chainlock/signing.cpp:177` | `m_sigman` |
  | `instantsend/signing.cpp:356`, `:415` | `m_sigman` |
  | `llmq/ehf_signals.cpp:82` | `sigman` |
  | `rpc/quorums.cpp:533` | `*llmq_ctx.sigman` |

  The parameter comes from 0052fca ("refactor: move `AsyncSignIfMember()` to `CSigSharesManager`"). Before that move the method lived on `CSigningManager` and took the *opposite* manager, `CSigSharesManager& shareman`. After the move that argument became `this`, and a `sigman` argument was added to reach back for the recovered-sigs db — a mirror-image swap that is correct in isolation, but that nobody reconciled with the member the destination class already had.

  Consequence today: the single `sigman.GetDb()` in the body resolves to the argument, while the surrounding code in the same function reaches for `qman` and `m_chainman` as members.

  ## What was done?

  Dropped the `CSigningManager& sigman` parameter from the declaration, the definition, and all five call sites.

  The body is untouched — `sigman` now resolves to the member, which is the same object it already received. Parameter-list continuation lines keep their alignment because the opening paren does not move.

  ## How Has This Been Tested?

  Not built or run locally — reviewed by inspection. Verified by grep that no declaration, definition, or call site still passes a `CSigningManager` to `AsyncSignIfMember`, and that removing the argument leaves no caller-side variable unused (`llmq_ctx` in `rpc/quorums.cpp` is still used a few lines below). Relying on CI for the compile and test run.

  Worth noting for reviewers: this class of shadowing is invisible to our warning set. Dash configures `-Wshadow-field`, which only fires when a field shadows a *base class* field; a parameter shadowing a member of the same class needs `-Wshadow-all` (or GCC's `-Wshadow`).

  ## Breaking Changes

  None.

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [x] 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)_

Top commit has no ACKs.

Tree-SHA512: 64cb21e35730b4008804d719664810c851c4a8c84f7f2474d3c9104b75ccb00985fb8607a3cda83797e8b3d0bebfb94d278f54d2ae73faa3b2002bb790cfbcc6
…ective, drop -gdwarf-4 pin

74fcc11 guix: keep full debug info in darwin release dSYMs (pasta)
11b1815 ci: drop the tsan job's explicit CXXFLAGS (pasta)
30b9f14 build: drop the -gdwarf-4 pin from stacktrace flags (pasta)
00daed4 build: emit BACKTRACE_FLAGS before debug-level flags (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  CI ccache entries are enormous — ~490 MB compressed per target, ~3.3 GB per develop push — which permanently fills the repository's 10 GB Actions cache quota and causes entries to be evicted within hours of being saved (run 30848679596 found no usable ccache for 4 of 9 targets and spent 27–37 min recompiling each). Since June 2026 `pull_request_target` runs have read-only cache tokens, so PR CI inherits every one of those misses.

  The root cause is a flag-interaction bug, not a missing setting. `configure.ac` already declares the right debug level — *"We always enable at least -g1 debug info to support proper stacktraces in crash infos"* — but it never takes effect:

  - `BACKTRACE_FLAGS` carries `-gdwarf-4`, added when libbacktrace could not parse DWARF 5. Being a `-g`-family flag, it also **implies debug level 2**.
  - `src/Makefile.am` placed `$(BACKTRACE_FLAGS)` *after* `$(DEBUG_CXXFLAGS)`, so the `-gdwarf-4` silently escalated the declared `-g1` floor to full `-g2` in every build — and clamped `--enable-debug`'s `-g3` down to `-g2` as well.

  Full `-g2` DWARF is ~90% of every object in this template-heavy codebase. Measured on `src/validation.cpp` (clang, `-O2`): **9.7 MB** with today's effective flags, **2.5 MB** at `-g1`, 0.65 MB with no debug info. Nothing in CI consumes more than function names and line tables — crash-hook backtraces and sanitizer reports are fully served by `-g1` (verified: reports keep full `function file:line` frames).

  ## What was done?

  Three commits:

  1. **`build: emit BACKTRACE_FLAGS before debug-level flags`** — reorder `AM_CFLAGS`/`AM_CXXFLAGS` so the DWARF-version flag comes first and the debug *level* is decided by what follows: the `-g1` floor, `--enable-debug`'s `-g3`, or the user's own flags (which automake always places last). Kept as defense-in-depth even after commit 2: any future `-g`-family flag in `BACKTRACE_FLAGS` would otherwise reintroduce the bug.
  2. **`build: drop the -gdwarf-4 pin from stacktrace flags`** — the pin's reason is gone: the libbacktrace pinned in depends (`b9e40069`, 2025-11-06) has had DWARF 5 support since 2021 and is what `dashd` statically links, and every CI/release compiler (gcc 11+, clang 14+) defaults to DWARF 5. Using the compiler default shrinks debug info a further ~10–25% at `-g1` (g++ 67→62 KB, clang++ 39→30 KB on a representative object). The valgrind CI jobs keep their own `-gdwarf-4` env pins — a separate valgrind-compatibility concern.
  3. **`ci: drop the tsan job's explicit CXXFLAGS`** — the depends `config.site` prepends the host release flags (`-O2`) to user `CXXFLAGS`, so this job has always built at `-O2`; its trailing `'-g'` only escalated debug info to level 2 (at ~540 MB compressed, the largest ccache entry of any target). Removing the override lets tsan pick up the same `-O2` + `-g1` floor as every other job instead of remaining a special case. A fork run of the full tsan suite at this debug level passed ([30867089833](https://github.com/PastaPastaPasta/dash/actions/runs/30867089833)); its timings also confirmed the debug level has no wall-clock effect on the tsan tests (unit 355s vs 349s, functional 39 min vs 37.5 min).

  4. **`guix: keep full debug info in darwin release dSYMs`** — the guix darwin case unsets `HOST_CFLAGS`, so the mac release build's debug level was never requested explicitly: it came from the same `-gdwarf-4` escalation this PR removes, and the shipped dSYM debug artifacts (`make osx_debug`) would have silently dropped from `-g2` to `-g1` while linux/mingw releases keep `-g2` via their explicit `-O2 -g`. Request `-g` explicitly for darwin instead (optimization and target flags still come from depends' `config.site`). Credit: flagged by Codex review.

  No other env-file changes are needed: with the floor effective, every job whose flags lack a `-g` (linux64, sqlite, nowallet, ubsan, fuzz, aarch64, mac) lands on `-g1` automatically, and dev builds get the same documented behavior instead of an accidental `-g2`.

  Expected cache effect: ~490 MB → roughly 130–170 MB compressed per affected target, taking a develop push's ccache footprint from ~3.3 GB to about 1 GB, which stops the quota-eviction cycle.

  ## How Has This Been Tested?

  - **Full fork CI run of the `-g1` flag shape** ([30867089833](https://github.com/PastaPastaPasta/dash/actions/runs/30867089833)): every build and test job green, including the tsan, ubsan, and multiprocess functional suites. Cold `-g1` builds were consistently faster than cold `-g2` baselines on the same runner class (linux64 1318s vs 1629s, sqlite 1152s vs 1366s, ubsan 2038s vs 2223s).
  - **Fork CI run of the configure-level fix** (commits 1–2 exactly, no env changes): [30872992596](https://github.com/PastaPastaPasta/dash/actions/runs/30872992596) — in progress at PR time; demonstrates the floor applying to every target via the per-job ccache stats.
  - **Flag-order semantics** verified empirically on gcc and clang (`-g1 … -gdwarf-4` ⇒ 9.7 MB object; `-gdwarf-4 … -g1` ⇒ 2.5 MB).
  - **Sanitizer symbolization at `-g1`**: UBSAN (`print_stacktrace=1`) and TSAN reports keep full function + file:line frames.
  - **libbacktrace DWARF 4 vs 5 equivalence**, using the exact depends-pinned commit (`b9e40069`) linked into a multi-TU harness calling `backtrace_full()` at `-g1 -O2`: symbolized output (function + file:line, including cross-TU frames) is byte-identical between `-gdwarf-4` and `-gdwarf-5` builds on Linux (g++ 13 and clang++ 18, binary versions confirmed via readelf) and on native macOS (Apple clang 21 with dSYM, versions confirmed via dwarfdump: 0x0004 vs 0x0005).

  ## Breaking Changes

  None for releases: linux and mingw pass an explicit `-O2 -g` which still lands last, and darwin now requests `-g` explicitly (commit 4), so all release binaries and dSYM artifacts keep full `-g2` (now DWARF 5, which all release tooling supports).

  Developer-facing note: builds against a depends prefix without explicit flags previously got an accidental `-g2` and will now get the documented `-g1`. Developers who want full debug info pass `CXXFLAGS='-g -O2'` (user flags always win) or use `--enable-debug`, which now correctly yields `-g3` instead of being clamped to `-g2`.

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [x] 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
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

ACKs for top commit:
  knst:
    utACK 74fcc11

Tree-SHA512: ed85a4f4ac3d99aa807dd30aa3146d7830c88737ccb0d277ba4990bf1a2b1f85110bc5cf806b7954b10a4db0e60b1fd4922ddf6c12132d7846d14152c47080dc
… in env -u when include paths are set

1dc8e9b build: keep TODO comment on darwin C_INCLUDE_PATH workaround (pasta)
477e5a7 build: only wrap darwin CC/CXX in env -u when include paths are set (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  ccache is silently disabled for the entire `mac-build` CI job, and has been since d201e43 (2026-01-19) re-added the `env -u` prefix to `darwin_CC`/`darwin_CXX` (it was previously broken from 2020-08 until the bitcoin#30451 backport in 1d8868b removed the prefix in 2025-02).

  Every mac CI run shows:

  ```
  Cacheable calls:      0 / 1021 ( 0.00%)
  Uncacheable calls: 1021 / 1021 (100.0%)
  ```

  and recompiles all objects from scratch (~22 min compile in run 30848679596), while saving a 17 KB ccache cache entry.

  **Root cause:** configure prepends ccache to `CC`, producing `ccache env -u C_INCLUDE_PATH -u CPLUS_INCLUDE_PATH /usr/bin/clang ...`. ccache treats `env` as the compiler (type `other`), consumes `-u VAR` as an option taking an argument, then classifies the absolute clang path — an existing file not starting with `-` — as a second source file. `CCACHE_DEBUG` shows the verdict:

  ```
  Compiler: /usr/bin/env
  Compiler type: other
  Result: multiple_source_files
  ```

  Every call is rejected and silently falls back to the real compiler, so the build succeeds and nothing ever flagged it. Current ccache master has the same parsing behavior; this is not fixed by upgrading ccache.

  ## What was done?

  The `env -u` prefix exists to stop Guix (`contrib/guix/libexec/build.sh` exports `C_INCLUDE_PATH="${NATIVE_GCC}/include"` etc.) from leaking native-toolchain headers into the darwin cross-build — the conflict d201e43 fixed. CI never sets those variables, so the prefix does nothing there except break ccache.

  The prefix is now emitted only when `C_INCLUDE_PATH` or `CPLUS_INCLUDE_PATH` is actually defined (checked via `$(origin ...)`, so set-but-empty still counts as set):

  - **Guix:** variables are exported → prefix present → behavior unchanged (Guix doesn't use ccache, so the ccache issue never applied there).
  - **CI / containers:** variables unset → plain `$(clang_prog)` → ccache wraps clang directly and caching works.

  Upstream removed the prefix entirely (bitcoin#30451) because their Guix build.sh no longer exports these variables; ours still does, so the conditional keeps the Guix protection while re-applying the effect of bitcoin#30451 everywhere else. The stale TODO comments pointing at bitcoin#30451/dashpay#7184 are replaced with an explanation of both constraints.

  Cached depends are not invalidated by this change: `depends/gen_id` hashes compiler `-v` output, which is byte-identical with and without the prefix when the variables are unset (verified below).

  ## How Has This Been Tested?

  All in `ubuntu:24.04` (same ccache 4.9.1 + clang 18 as the CI image), plus a make-level check:

  1. **Repro of the bug:** `ccache env -u C_INCLUDE_PATH -u CPLUS_INCLUDE_PATH /usr/bin/clang --target=x86_64-apple-darwin -isysroot... -nostdlibinc ... -c t.c` → `Uncacheable calls: 1/1`, `CCACHE_DEBUG` log shows `Result: multiple_source_files`. Bare `clang` instead of an absolute path does not trigger it, confirming the source-file misparse.
  2. **New shape:** same flags without the prefix → `Cacheable calls: 2/2`, second compile is a cache hit.
  3. **Conditional:** scratch makefile including `depends/hosts/darwin.mk`: with both variables unset `darwin_CC` has no prefix; with `C_INCLUDE_PATH` set (or set-but-empty) the prefix is emitted.
  4. **depends build id stability:** `{clang -v; clang -v -E -xc -o /dev/null -}` output (the `gen_id` preimage components) is byte-identical with and without the `env -u` prefix when the variables are unset, so CI's cached depends remain valid.

  Guix builds are unaffected by construction (the conditional evaluates true in that environment), but a `guix-build` run on this PR will confirm.

  ## Breaking Changes

  None.

  ## Checklist:
  - [x] 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
  - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

Top commit has no ACKs.

Tree-SHA512: 0a5381345712017376d0e12111002bd282ab09180006e3a0e3e7e5815eedb075de343cc8e0f4037852861bc610eb79daa8e055a2b4c9a2076f3f5ddfabc65c74
5950828 fix(ci): hash all depends recipe inputs (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  The depends cache key did not cover every file used to derive depends package build IDs. In [run 30864594362](https://github.com/dashpay/dash/actions/runs/30864594362/job/91859430920?pr=7540), PR dashpay#7540 changed `depends/hosts/darwin.mk`, but cache lookup still reported a hit and skipped the dedicated depends build. The macOS source job then rebuilt packages for about 17 minutes.

  ## What was done?

  - Include the complete static depends recipe inputs in the cache key: the Makefiles, ID generator, config files, builders, hosts, packages, and patches.
  - Check out the full tracked `depends/` directory in the cache-check job so those inputs are available to `hashFiles`.

  This causes a true cache miss when a relevant depends input changes, allowing the existing depends job/artifact handoff to provide matching packages to source jobs.

  ## How Has This Been Tested?

  - Ran `git diff --check`.
  - Ran `actionlint .github/workflows/build-depends.yml`. It reports existing diagnostics for the repository's custom checkout input and pre-existing shell-style issues; no diagnostics are caused by this change.

  ## Breaking Changes

  None.

  ## Checklist:

  - [x] 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

  This pull request was created by Codex.

Top commit has no ACKs.

Tree-SHA512: 893f4ddf6d0d2419384c70bfd95e1585baae3401cdba30def8f0433f952fe9c1e59bcad351b7a20c6d28b69f525c94c13a8e8fdb2e93aa12af4ec28e217171cc
…the quorum on QSIGREC

5619e24 refactor: trim comment density in VerifyAndProcessRecoveredSig (pasta)
dcdc0fa perf(llmq): check quorum activity before materializing the quorum on QSIGREC (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  `CSigningManager::VerifyAndProcessRecoveredSig` called `qman.GetQuorum(...)` before `IsQuorumActive(...)`.

  Those two have very different costs. `IsQuorumActive` is bounded to the `keepOldConnections` most recent quorums at the tip, and that set is shared and cached across callers. `GetQuorum` takes the peer-supplied hash and can rebuild an arbitrary historical mined commitment on a cache miss — a deterministic masternode list replay plus member selection. An unsolicited `QSIGREC` naming an inactive quorum hash therefore forced the expensive path before the cheap gate had a chance to reject it.

  This was split out of [dashpay#7519](dashpay#7519), where it had been bundled with unrelated QGETDATA work.

  ## What was done?

  Swap the order so the cheap gate runs first. Once `IsQuorumActive` passes, the hash is one of the recent quorums `ScanQuorums` covers, so the subsequent `GetQuorum` is usually served from cache. `ScanQuorums` and `GetQuorum` keep separate LRUs, so a rebuild there is still possible, but only of a recent quorum — never of the arbitrary historical one a peer could otherwise name.

  A null quorum after that point is no longer peer-controlled — it means the quorum was reported active but is no longer materializable, e.g. after a reorg — so it is logged without a misbehaviour score.

  The caller (`NetSigning`) has already rejected unknown LLMQ types before this point, so the reordering does not widen what reaches `IsQuorumActive`.

  ## How Has This Been Tested?

  Compiles cleanly. This is a reordering of two existing checks with no behavioural change for valid input, so it is covered by the existing QSIGREC paths in the functional suite. Full validation is delegated to CI.

  ## Breaking Changes

  None.

  ## Checklist:

  - [x] I have performed a self-review of my own code
  - [x] 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

Top commit has no ACKs.

Tree-SHA512: dd799dae655f6513ea2ba809e4b832c3f12b538d1121e5081b48f155d3601705b324f82f2725c8581155ae889be3ac20b2720111dbb747818c6eac630331539f
…itle linter

fcaf59d docs: refine PR guidance and prefer existing test files (pasta)
46775db docs: instruct agents to follow PR template and title linter (pasta)

Pull request description:

  ## Issue being fixed or feature implemented

  Guidance in `AGENTS.md` and `CLAUDE.md` should clearly instruct coding agents on PR hygiene (template, title linter) and test file creation strategy.

  ## What was done?

  - Updated `AGENTS.md` and `CLAUDE.md` under `## PR Hygiene` to instruct agents to follow `.github/PULL_REQUEST_TEMPLATE.md` for descriptions and satisfy the active PR title linter in `.github/workflows/semantic-pull-request.yml`.
  - Added test file selection guidance under `## Test Commands` instructing agents to prefer adding test cases to existing test files unless creating a new file is justified by complexity, performance/parallelism, or logical distinction.

  ## How Has This Been Tested?

  Verified that `AGENTS.md` and `CLAUDE.md` contain identical guidance (`diff AGENTS.md CLAUDE.md`).

  ## Breaking Changes

  None.

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [ ] I have commented my code, particularly in hard-to-understand areas
  - [x] I have made corresponding changes to the documentation
  - [ ] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_

  This pull request was created by Codex.

Top commit has no ACKs.

Tree-SHA512: 412617558a5ddd10b7f766ebe6465155d34e871fd0c2575da4fd18f1639b9f1b0179439c49791bca5680a21a0836a910250f8dba1e535c9915eb559d95592db4
Pass the validating chainstate through special transaction and quorum commitment processing instead of borrowing the active chainstate.

Interpret mined-commitment records and quorum resolution relative to the caller's chain. The cached values remain reusable, but chain membership is reevaluated across reorgs and chainstates while public non-validation callers retain active-chain semantics. This prevents snapshot-seeded records from suppressing commitments or satisfying MNHF and asset-unlock quorum lookups during background validation.

Add dual-chainstate coverage for a commitment seeded at a block not yet contained by the background chain, including HasQuorum and GetQuorum cache-order checks.
Emit block, tip, deterministic masternode-list, UI, and flush notifications only for the active chainstate. In particular, suppressing background ChainStateFlushed prevents a background locator from regressing wallet best-block state.

Keep BlockChecked ungated because its subscribers are mining/block-submit and peer validation/relay accounting; it does not reach CMNAuth. Document all 21 B3 call-site dispositions and extend the dual-chainstate test with validation-interface and UI counters.
Check local block-data availability before building masternode-list diffs and quorum rotation info. Treat failures caused by pruning or an unvalidated snapshot base like pruned getdata: log and silently drop the plausible request without increasing the peer's misbehavior score.

Malformed and implausible requests retain the pre-existing penalties.
Disable DKG participation and quorum signing until snapshot background validation completes. Enforce the refusal at CreateSigShare, the actual share-production boundary, so direct RPC, async, and queued signing paths cannot bypass it.

The quorum sign RPC now returns a clear JSON-RPC error for both submit modes, and masternode status exposes the disabled participation state. Add unit coverage for the shared production-gate predicate across snapshot activation.
A WriteDerived failure means independently derived block data disagrees with the copy already recorded in EvoDB. That is local state corruption (or a cross-chainstate divergence bug), never evidence about the block being processed. Previously the mismatch surfaced as BLOCK_CONSENSUS: the block was persistently marked BLOCK_FAILED_VALID (surviving restart and forking the node off the network) and the relaying peer was handed a 100-point misbehavior score via BlockChecked, which background validation also triggers.

Instead, follow the existing EvoDbInconsistencyMessage convention: request node shutdown via AbortNode and fail validation with M_ERROR, which neither marks the block invalid nor punishes peers. The credit-pool and MNHF sites abort at the throw site because miner and RPC callers never pass through a validation-state catch; a typed EvoDbInconsistencyError lets the four block-path catch blocks that would otherwise swallow it into BLOCK_CONSENSUS reclassify it as M_ERROR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ethrow

CMNHFManager::GetForBlock re-throws internal ProcessBlock failures as a plain runtime_error, which would let a downstream generic catch misreport an EvoDB mismatch (M_ERROR) as a consensus failure. Re-throw typed when the validation state carries M_ERROR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reads and writes outside a BeginTransaction scope previously always bound to the NORMAL identity. While a snapshot chainstate is active, transaction-less consumers (RPC, mempool, miner, P2P serving) could not see snapshot-chain records still pending in the SNAPSHOT root overlay, and transaction-less writes dirtied the wrong overlay. CEvoDB now tracks a default identity which snapshot activation (ActivateSnapshot, ActivateExistingSnapshot) sets to SNAPSHOT and ResetChainstates resets; snapshot completion must reset it to NORMAL when marker promotion lands.

Also make the GetListForBlockInternal fallback loud: a missing list diff for a DIP3-active block is pending-elsewhere or corrupt data, never the pre-DIP3 genesis of the masternode list, so throw instead of silently caching an empty list and clobbering m_initial_snapshot_index. The thrown message carries the IsBlockDataUnavailableError sentinel and BuildSimplifiedMNListDiff converts it into a serve failure, so peers requesting such history are not penalized.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GetListForBlock can now throw when list data is unavailable (missing diff for a DIP3-active block). CActiveMasternodeManager::UpdatedBlockTip and GovernanceSigner's trigger creation run on the scheduler thread, where an uncaught exception terminates the node; catch it there and skip the update instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The scheduler-thread guards added for snapshot nodes caught every std::exception, but the only recoverable condition is a masternode list diff that is not on this node yet. CDeterministicMNList::ApplyDiff raises a plain std::runtime_error for missing removals or updates, duplicate masternodes, and duplicate unique properties -- local EvoDB corruption, which was then logged as benign unavailable history while the active masternode stayed READY.

Give the unavailable-history condition its own BlockDataUnavailableError type and catch only that. Everything else propagates exactly as it did before these guards existed. BuildSimplifiedMNListDiff is narrowed the same way, so local corruption no longer turns into a peer-facing error string.
bls::bls_legacy_scheme is process-wide, but the correct value is a property of the block being processed. ConnectBlock only saved the current value and committed a successful transition; it never established the scheme for the chainstate it was validating. ProcessSpecialTxsInBlock switches legacy->basic when crossing V19 forward and never the other way, so with an active post-V19 snapshot the background chainstate validated pre-V19 blocks under the basic scheme, decoding historical BLS-serialized quorum and special-transaction data with the wrong one. A background disconnect across V19 could conversely commit legacy and leave active-chain consumers there.

Enter ConnectBlock under the scheme the block's parent left behind, and commit it out of ConnectTip/DisconnectTip only from the active chainstate.
GetCurrentIdentity() resolved to the process-wide active_transaction, so while the background chainstate held a NORMAL transaction open every concurrent transaction-less read was redirected to NORMAL instead of the active snapshot's default identity. CQuorumManager::GetQuorum releases cs_main before BuildQuorumFromCommitment calls GetMinedCommitment: if background validation started in between, snapshot-only records still pending in the SNAPSHOT overlay dropped out of that lookup and the active node intermittently reported a missing quorum.

An open transaction is the identity of one validation execution context, not of the process, so resolve it only for the thread that began it.
…away

Two ways a persisted snapshot chainstate could outlive itself:

PopulateAndValidateSnapshot commits the SNAPSHOT best-block and dual-chainstate markers as its last step. If activation was then abandoned -- WriteSnapshotBaseBlockhash on an unwritable datadir, say -- only the coins directory was removed. The node went back to a single chainstate carrying a permanent dual-chainstate marker, which turns supported legacy missing-diff bootstrapping into unavailable history, and a stale SNAPSHOT marker that a future snapshot directory could satisfy ActivateExistingSnapshot with. Erase both when activation is rolled back.

LoadChainstate constructs CEvoDB with wipe=true for -reindex and -reindex-chainstate, erasing the SNAPSHOT marker while chainstate_snapshot and its base-blockhash file stay on disk. DetectSnapshotChainstate then found the directory and ActivateExistingSnapshot rejected it on the marker just erased, so startup failed with advice ('reindex is required') the user had already followed -- on every retry. Discard the persisted snapshot chainstate's on-disk artifacts alongside the EvoDB wipe. This is the Dash-shaped equivalent of the reindex-time cleanup in bitcoin#27596/bitcoin#29726; it runs before any chainstate has coins views, so no mempool transfer or leveldb-lock dance is needed.
@PastaPastaPasta
PastaPastaPasta force-pushed the assumeutxo/m2-evodb-roles branch from 993d53b to 066fca8 Compare August 4, 2026 21:32
@PastaPastaPasta
PastaPastaPasta merged commit ab65592 into develop Aug 8, 2026
70 of 71 checks passed
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.

3 participants