Skip to content

Conversation

kwvg
Copy link
Collaborator

@kwvg kwvg commented Aug 25, 2025

Additional Information

  • To allow removing ::fMasternodeMode, we needed to route all pending global usage and one usage, in wallet/coinjoin.cpp (source) required additional refactoring. When calculating a tally of all outpoints from an address, if the node is in masternode mode, we skip over outputs with collateral values.

    SelectCoinsGroupedByAddresses is used in three places, creating collaterals (source) and denoms (source) in the CoinJoin client, calculating the anonymizable balance (source) in wallet, and tests.

    As the wallet will have no way of querying if masternode mode is enabled, we need to pull this logic outside. It isn't required in the CoinJoin client as masternodes cannot participate in CoinJoin as a client (source) and so fMasternodeMode will not evaluate true, leaving behind GetAnonymizableBalance(), which now accepts fSkipMnCollateral as the caller can query if the node is in masternode mode (source).

    • This requires storing the outpoint's value in order to filter it in GetAnonymizableBalance(), which required a change in CompactTallyItem's structure (source).
  • The signers spun-off in dash#6742 and dash#6761 have been moved to a dedicated masternode mode-only context, ActiveContext, as part of an effort to pull out networking-heavy and interactive consensus elements out of LLMQContext, for bitcoin-chainstate.

    • As the signers plug into existing managers and aren't standalone, they are not public.

    • Alongside the signers, managers that are masternode mode-only have been moved to ActiveContext, namely CCoinJoinServer (source) and llmq::CEHFSignalsHandler (source).

Breaking Changes

None expected.

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 (note: N/A)
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@kwvg kwvg added this to the 23 milestone Aug 25, 2025
Copy link

github-actions bot commented Aug 25, 2025

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

kwvg added 11 commits August 25, 2025 17:02
It is used infrequently enough that the clarity from explicit
specification is preferable, this will make sense in the next commit.
Also, opportunistic const'ing.
The wallet should not concern itself with masternode mode state. Client
side mixing is not available for masternodes so the only place where it
matters is calculating anonymizable balance, where we can filter and
decrement from the existing tally.
We don't initialize mn_activeman in TestingSetup so we don't need to set
`connOptions.m_active_masternode`
ActiveContext is going to host all entities are part of masternode mode
(i.e. used by a node if it is an active masternode) and is required to
separate interactive (i.e. networking-driven) portions of consensus from
portions that can be done non-interactively/offline.
Now that CCoinJoinServer is conditionally initialized, we can remove
checks that assumed its unconditional existence.
@kwvg kwvg force-pushed the active_ctx branch 2 times, most recently from 9a154a8 to e2ff3c6 Compare August 25, 2025 17:47
@kwvg kwvg marked this pull request as ready for review August 26, 2025 12:35
Copy link

coderabbitai bot commented Aug 26, 2025

Walkthrough

The change set introduces ActiveContext and ActiveNotificationInterface, exports their headers, and integrates them into initialization, shutdown, net_processing, and RPC. ChainLocks and InstantSend managers drop CSigSharesManager/is_masternode constructor params, switch signer ownership to external attachment via ConnectSigner/DisconnectSigner, and are wired through ActiveContext. LLMQContext stops constructing CEHFSignalsHandler; EHF signaling is moved behind ActiveContext with a revised UpdatedBlockTip signature. CJContext drops connman/peerman from its constructor; CoinJoin server now uses references. NodeContext gains active_ctx; CConnman adds an m_active_masternode flag and accessor, replacing fMasternodeMode (removed). Wallet APIs change: CompactTallyItem.outpoints→coins and getAnonymizableBalance adds fSkipMnCollateral. All call sites, UI, tests, and lint expectations are updated.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
src/chainlock/chainlock.cpp (1)

454-463: Potential nullptr deref when processing cleanup_txes; guard per prior issue.

Given the known null-check gap noted previously in ChainLockSigner::Cleanup() (per PR #6761 follow-up), this loop should tolerate nullptr entries to avoid crashes.

-    if (m_signer) {
-        const auto cleanup_txes{m_signer->Cleanup()};
-        LOCK(cs);
-        for (const auto& tx : cleanup_txes) {
-            for (const auto& txid : *tx) {
-                txFirstSeenTime.erase(txid);
-            }
-        }
-    }
+    if (m_signer) {
+        const auto cleanup_txes{m_signer->Cleanup()};
+        LOCK(cs);
+        for (const auto& tx : cleanup_txes) {
+            if (!tx) continue; // be defensive against null entries
+            for (const auto& txid : *tx) {
+                txFirstSeenTime.erase(txid);
+            }
+        }
+    }

Note: This aligns with the preference to avoid scope creep while still shoring up a crash vector locally.

src/net_processing.cpp (1)

2876-2886: Null-deref risk: guard cj_server when fetching DSQUEUE in ProcessGetData().

Same issue as above: only checking m_active_ctx is insufficient. Ensure cj_server exists before dereferencing.

-            auto opt_dsq = m_active_ctx ? m_active_ctx->cj_server->GetQueueFromHash(inv.hash) : std::nullopt;
+            auto opt_dsq = (m_active_ctx && m_active_ctx->cj_server)
+                               ? m_active_ctx->cj_server->GetQueueFromHash(inv.hash)
+                               : std::nullopt;
🧹 Nitpick comments (32)
src/net.cpp (2)

1804-1821: Switched eviction protection gating to per-instance masternode state — good; consider accessor for consistency

Using the instance field m_active_masternode removes reliance on the old global and scopes behavior to the CConnman instance. For consistency with other call sites that use IsActiveMasternode(), you might prefer the accessor here as well.

-            if (m_active_masternode) {
+            if (IsActiveMasternode()) {

1974-1977: Inbound gating while unsynced is now per-instance — good; mirror accessor usage

The inbound gate now depends on the CConnman instance being an active masternode, matching the intent of dropping the global. For codebase consistency, consider the accessor here too.

-    if (m_active_masternode && !mn_sync.IsBlockchainSynced()) {
+    if (IsActiveMasternode() && !mn_sync.IsBlockchainSynced()) {
src/net.h (3)

1197-1198: Additive API looks good; clarify latched semantics of m_active_masternode.

Adding Options::m_active_masternode with a default false is fine. Please document that this flag is latched during Init() and is not expected to change at runtime, to avoid future misuse (e.g., someone toggling it after threads start).

Apply:

 struct Options
 {
@@
-        bool m_i2p_accept_incoming;
+        bool m_i2p_accept_incoming;
+        // Set once during Init(); indicates if the node runs in active masternode mode.
+        // Not intended to be mutated after threads are started.
         bool m_active_masternode = false;
 };

1260-1261: Accessor addition is fine; name matches semantics.

IsActiveMasternode() is a clear replacement for the former global. Please add a brief comment to reinforce that this value is stable after Init().

-    bool IsActiveMasternode() const { return m_active_masternode; }
+    // Latched during Init(); not expected to change at runtime.
+    bool IsActiveMasternode() const { return m_active_masternode; }

1235-1236: No unexpected writes; thread-safety future-proofing optional

I verified with ripgrep that the only write to CConnman::m_active_masternode occurs in its Init method (src/net.h:1235), and the only place the corresponding Options::m_active_masternode is set is in src/init.cpp:2441. There are no other assignments to this field, so the current startup-time initialization is thread-safe.

Optional suggestion:

  • If you ever need to toggle m_active_masternode at runtime after worker threads are running, convert the member in CConnman to std::atomic_bool to ensure proper visibility across threads.
test/lint/lint-circular-dependencies.py (1)

25-27: New expected cycles acknowledge ActiveContext wiring; consider breaking them post-merge.

  • active/context -> coinjoin/server -> net_processing -> active/context
  • coinjoin/coinjoin -> instantsend/instantsend -> net_processing -> coinjoin/context -> coinjoin/coinjoin

These likely stem from including concrete types across layers. After this refactor lands, we should aim to break these with forward declarations and dependency inversion (interfaces injected into net_processing). Not blocking.

Potential approaches:

  • Replace net_processing includes in coinjoin/server and instantsend with forward declarations + factory methods.
  • Have ActiveContext expose minimal interfaces (pure virtuals) rather than concrete types to net_processing.
src/llmq/ehf_signals.h (1)

44-45: UpdatedBlockTip signature removal of is_masternode parameter is fine; ensure call site cannot null-deref handler.

The handler no longer needs the is_masternode toggle, which aligns with ActiveContext scoping. However, ActiveNotificationInterface appears to call m_active_ctx.ehf_sighandler->UpdatedBlockTip(pindexNew) unconditionally. If ehf_sighandler can be absent on non-MN nodes, that’s a crash.

Please confirm ehf_sighandler is always non-null in ActiveContext for all nodes. If not, guard the call:

diff --git a/src/active/notificationinterface.cpp b/src/active/notificationinterface.cpp
@@
-    m_active_ctx.ehf_sighandler->UpdatedBlockTip(pindexNew);
+    if (m_active_ctx.ehf_sighandler) {
+        m_active_ctx.ehf_sighandler->UpdatedBlockTip(pindexNew);
+    }

Optionally mark the method noexcept since it should not throw:

-    void UpdatedBlockTip(const CBlockIndex* const pindexNew) EXCLUSIVE_LOCKS_REQUIRED(!cs);
+    void UpdatedBlockTip(const CBlockIndex* const pindexNew) noexcept EXCLUSIVE_LOCKS_REQUIRED(!cs);
src/chainlock/chainlock.h (1)

49-50: Raw pointer for m_signer: define clear lifetime/locking and assert on destruction.

Using a raw pointer is fine for non-ownership, but add defense-in-depth:

  • Annotate usage with a brief comment (non-owning, must be detached before destruction).
  • Add a debug assertion in the destructor that m_signer == nullptr to catch shutdown leaks.
-    chainlock::ChainLockSigner* m_signer{nullptr};
+    // Non-owning; attached via ConnectSigner(), must be detached via DisconnectSigner() before destruction.
+    chainlock::ChainLockSigner* m_signer{nullptr};

And in src/chainlock/chainlock.cpp destructor:

 CChainLocksHandler::~CChainLocksHandler()
 {
+    assert(m_signer == nullptr && "DisconnectSigner() must be called before ~CChainLocksHandler()");
     scheduler->stop();
     scheduler_thread->join();
 }
src/coinjoin/server.cpp (1)

58-72: Reference-based m_mn_activeman usage looks correct; ensure invariant holds (server exists only in MN mode).

Switching from pointer derefs to reference semantics at DSACCEPT time (GetOutPoint/GetProTxHash) removes the need for null checks and simplifies flow. Given ActiveContext constructs CCoinJoinServer only in masternode mode, this is sound. Consider adding a debug assertion at CCoinJoinServer construction (or here) to document the invariant for future maintainers.

Would you like a small follow-up PR adding an assertion in the CCoinJoinServer ctor to confirm that the provided CActiveMasternodeManager corresponds to a valid/registered MN at chain tip?

src/coinjoin/context.h (1)

33-36: CJContext decoupling is solid; constructor and ownership changes are coherent.

  • Dropping server/connman/peerman from CJContext keeps it wallet/client-centric, as intended.
  • Passing mn_activeman only to infer is_masternode for walletman/queueman is reasonable.

Optional: in a future sweep, consider passing a simple bool is_masternode instead of the pointer to reduce coupling further (constructor already treats it as a boolean).

Also applies to: 43-44

src/wallet/interfaces.cpp (1)

434-437: Forwarding of new fSkipMnCollateral flag looks correct

The WalletImpl override now cleanly propagates all three flags to CWallet::GetAnonymizableBalance.

Consider documenting the semantics/order of the three booleans at the declaration site (interfaces/wallet.h) or replacing them with a small flags enum for readability in future work. No action required for this PR.

src/coinjoin/util.cpp (1)

135-138: Avoid unused structured-binding variable (“_”) to prevent -Wunused-variable warnings

Using a named-but-unused binding often triggers warnings with our flags. Prefer accessing .second explicitly, or bind both names and cast-to-void.

Apply this minimal refactor to avoid the unused binding and keep intent clear:

-    for (const auto& [_, outpoint] : tallyItem.coins) {
-        coinControl.Select(outpoint);
-        dummyTx.vin.emplace_back(outpoint, CScript());
-    }
+    for (const auto& coin : tallyItem.coins) {
+        const COutPoint& outpoint = coin.second;
+        coinControl.Select(outpoint);
+        dummyTx.vin.emplace_back(outpoint, CScript());
+    }
src/masternode/node.h (1)

30-31: Decoupling from CValidationInterface — good move

Dropping inheritance simplifies responsibilities. With ActiveNotificationInterface now mediating validation callbacks, this is the right direction.

Since the class no longer inherits from CValidationInterface, you can likely drop the heavy include and forward-declare CBlockIndex instead, reducing compile-time coupling:

-#include <validationinterface.h>
+class CBlockIndex;

Keep threadsafety annotations by retaining <threadsafety.h>. Ensure node.cpp or other .cpp files include validationinterface.h if needed.

src/interfaces/wallet.h (1)

239-239: Verification complete — no outdated calls remain

I’ve confirmed that:

  • The three-argument override exists in src/wallet/interfaces.cpp (line 434).
  • There are no remaining two-argument calls to either getAnonymizableBalance(...) or GetAnonymizableBalance(...) elsewhere in the codebase.

Please add the following parameter documentation above the interface to improve clarity:

-    //! Get anonymizable balance.
-    virtual CAmount getAnonymizableBalance(bool fSkipDenominated, bool fSkipMnCollateral, bool fSkipUnconfirmed) = 0;
+    //! Get anonymizable balance.
+    //! Params:
+    //!  - fSkipDenominated     Exclude denominated outputs that are reserved for mixing.
+    //!  - fSkipMnCollateral    Exclude outputs that match masternode collateral amounts.
+    //!  - fSkipUnconfirmed     Exclude unconfirmed UTXOs.
+    virtual CAmount getAnonymizableBalance(bool fSkipDenominated, bool fSkipMnCollateral, bool fSkipUnconfirmed) = 0;
src/llmq/ehf_signals.cpp (1)

34-47: Confirm ActiveContext gating; optionally skip during IBD

  • CEHFSignalsHandler is only constructed as part of ActiveContext (in src/active/context.cpp), which is only initialized in masternode mode—so non-MN nodes will never invoke UpdatedBlockTip.
  • To reduce unnecessary work during initial block download, you may opt to early-exit in IBD before checking deployments.

Suggested minimal refactor (optional):

 void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew)
 {
+    // Skip signing signals while catching up during IBD.
+    if (m_chainman.ActiveChainstate().IsInitialBlockDownload()) return;
     if (!DeploymentActiveAfter(pindexNew, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) return;
     …
 }
src/wallet/test/wallet_tests.cpp (1)

1429-1436: Switch to tallyItem.coins: add a lightweight value assertion for completeness

The migration from outpoints.size() to coins.size() matches the new CompactTallyItem API. To slightly strengthen the test, you can assert the expected coin value for the single-coin case.

Optional addition:

     BOOST_CHECK_EQUAL(vecTally.size(), 1);
     BOOST_CHECK_EQUAL(vecTally.at(0).nAmount, 500 * COIN);
-    BOOST_CHECK_EQUAL(vecTally.at(0).coins.size(), 1);
+    BOOST_CHECK_EQUAL(vecTally.at(0).coins.size(), 1);
+    // Each entry is (value, COutPoint); verify the value for clarity
+    BOOST_CHECK_EQUAL(vecTally.at(0).coins.at(0).first, 500 * COIN);
src/wallet/test/coinjoin_tests.cpp (1)

207-217: tallyItem.coins now stores (value, outpoint); minor clarity nit on change_pos variable reuse

The emplace_back of (tx->vout[n].nValue, COutPoint{...}) and the updated size assertion are correct and align with the refactor.

Small readability nit: nChangePosRet is reused across iterations as both an input “request” and output “result.” Consider a per-tx local to avoid accidentally carrying a requested index into the next CreateTransaction call.

Suggested tweak:

-        int nChangePosRet{RANDOM_CHANGE_POSITION};
+        int change_pos_for_tx{RANDOM_CHANGE_POSITION};

         ...
-            {
-                auto res = CreateTransaction(*wallet, {{GetScriptForDestination(tallyItem.txdest), nAmount, false}}, nChangePosRet, coinControl);
+            {
+                auto res = CreateTransaction(*wallet, {{GetScriptForDestination(tallyItem.txdest), nAmount, false}}, change_pos_for_tx, coinControl);
                 BOOST_CHECK(res);
                 tx = res->tx;
-                nChangePosRet = res->change_pos;
+                change_pos_for_tx = res->change_pos;
             }
             ...
             for (uint32_t n = 0; n < tx->vout.size(); ++n) {
-                if (nChangePosRet != RANDOM_CHANGE_POSITION && int(n) == nChangePosRet) {
+                if (change_pos_for_tx != RANDOM_CHANGE_POSITION && int(n) == change_pos_for_tx) {
                     // Skip the change output to only return the requested coins
                     continue;
                 }
                 tallyItem.coins.emplace_back(tx->vout[n].nValue, COutPoint{tx->GetHash(), n});
                 tallyItem.nAmount += tx->vout[n].nValue;
             }
-        assert(tallyItem.coins.size() == vecAmounts.size());
+        assert(tallyItem.coins.size() == vecAmounts.size());
src/active/context.cpp (3)

21-29: Assert LLMQContext dependencies are initialized (avoid latent nullptr derefs).

cl_signer, is_signer, cj_server, and ehf_sighandler immediately dereference llmq_ctx subcomponents (clhandler, isman, sigman, shareman, qman). If ActiveContext is ever constructed before LLMQContext finishes wiring, this will crash. Add cheap debug asserts to encode the construction-order contract.

 ActiveContext::ActiveContext(ChainstateManager& chainman, CConnman& connman, CDeterministicMNManager& dmnman,
                              CDSTXManager& dstxman, CMasternodeMetaMan& mn_metaman, CMNHFManager& mnhfman,
                              LLMQContext& llmq_ctx, CSporkManager& sporkman, CTxMemPool& mempool, PeerManager& peerman,
                              const CActiveMasternodeManager& mn_activeman, const CMasternodeSync& mn_sync) :
     m_llmq_ctx{llmq_ctx},
+    // Precondition (debug): LLMQContext must be fully initialized before ActiveContext
+    // (avoids nullptr derefs when constructing signers/handlers below).
+    /* NOLINTNEXTLINE(readability-braces-around-statements) */ assert(m_llmq_ctx.clhandler),
+    /* NOLINTNEXTLINE(readability-braces-around-statements) */ assert(m_llmq_ctx.isman),
+    /* NOLINTNEXTLINE(readability-braces-around-statements) */ assert(m_llmq_ctx.sigman),
+    /* NOLINTNEXTLINE(readability-braces-around-statements) */ assert(m_llmq_ctx.shareman),
+    /* NOLINTNEXTLINE(readability-braces-around-statements) */ assert(m_llmq_ctx.qman),
     cl_signer{std::make_unique<chainlock::ChainLockSigner>(chainman.ActiveChainstate(), *llmq_ctx.clhandler,
                                                            *llmq_ctx.sigman, *llmq_ctx.shareman, sporkman, mn_sync)},
     is_signer{std::make_unique<instantsend::InstantSendSigner>(chainman.ActiveChainstate(), *llmq_ctx.clhandler,
                                                                *llmq_ctx.isman, *llmq_ctx.sigman, *llmq_ctx.shareman,
                                                                *llmq_ctx.qman, sporkman, mempool, mn_sync)},

Follow-up: please confirm init/shutdown order guarantees that LLMQContext outlives ActiveContext. If not, we should guard destructor usage as well (see below).


31-33: Make signer attachment robust to future lifecycle changes.

ConnectSigner() calls are correct. To reduce the chance of disconnect omissions during refactors, consider RAII for the attachment (e.g., a small ScopedSignerAttachment held by ActiveContext that auto-disconnects in its destructor).

If you want, I can sketch a minimal RAII helper and wire it in a follow-up.


35-39: Destructor assumes LLMQContext still alive; add defensive checks or document invariant.

If ActiveContext were ever destroyed after LLMQContext, these calls would UB. Either:

  • Document the invariant that LLMQContext outlives ActiveContext, or
  • Add defensive null checks (cheap) to prevent surprises in edge teardown paths.
 ActiveContext::~ActiveContext()
 {
-    m_llmq_ctx.clhandler->DisconnectSigner();
-    m_llmq_ctx.isman->DisconnectSigner();
+    if (m_llmq_ctx.clhandler) m_llmq_ctx.clhandler->DisconnectSigner();
+    if (m_llmq_ctx.isman) m_llmq_ctx.isman->DisconnectSigner();
 }
src/active/notificationinterface.cpp (1)

17-22: Mirror dsnotification semantics: skip pure disconnects and optionally gate during IBD.

DSNotificationInterface::UpdatedBlockTip returns early when pindexNew == pindexFork and also exits during initial download. ActiveNotificationInterface should likely follow the same pattern to avoid redundant work and reduce churn during IBD.

 void ActiveNotificationInterface::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork,
                                                   bool fInitialDownload)
 {
-    m_mn_activeman.UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);
-    m_active_ctx.ehf_sighandler->UpdatedBlockTip(pindexNew);
+    if (pindexNew == pindexFork) return;
+    m_mn_activeman.UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload);
+    if (!fInitialDownload) {
+        // CEHFSignalsHandler::UpdatedBlockTip internally checks deployments, but skipping during IBD
+        // matches existing notification semantics and avoids unnecessary work.
+        m_active_ctx.ehf_sighandler->UpdatedBlockTip(pindexNew);
+    }
 }

Please confirm there is no intentional behavioral difference here (e.g., EHF wanting earlier signals during IBD).

src/test/denialofservice_tests.cpp (2)

23-24: Header include likely incidental.

Including <active/context.h> in this test compiles fine; however, ideally net_processing.h should forward-declare ActiveContext so tests don't need the concrete header when passing nullptr. If that is already in place, the include can be dropped.


151-155: ✅ All PeerManager::make call sites include the new active_ctx parameter — verification complete

I’ve grep’d every occurrence of PeerManager::make( across the codebase and confirmed that each call now supplies the /*active_ctx=*/ argument in the correct position:

  • src/init.cpp:2148–2150
  • src/net_processing.cpp (factory signature at 1948–1950)
  • src/test/net_peer_connection_tests.cpp:88–90
  • src/test/util/setup_common.cpp:335–337
  • src/test/denialofservice_tests.cpp:151–153, 255–257, 322–324, 427–429

LGTM — all call sites have been updated consistently.

Optional suggestion for future-proofing: you may want to refactor the factory to use a named-arguments builder or a single “dependency bag” struct to guard against parameter-order churn down the road.

src/active/notificationinterface.h (1)

8-8: Include what you use: add for std::unique_ptr

This header declares a std::unique_ptr but doesn’t include . Avoid relying on transitive includes.

 #include <validationinterface.h>
+#include <memory>
src/test/util/setup_common.cpp (1)

48-48: Drop unused include to keep test build lean

active/context.h isn’t referenced in this TU. Consider removing it to reduce dependencies.

-#include <active/context.h>
src/wallet/wallet.h (2)

135-137: Prefer a named struct over std::pair for per-coin entries; keep nAmount and coins in sync

Using std::pair<CAmount, COutPoint> is terse but harms readability and is error-prone (ordering confusion of <amount, outpoint>). Also, having both nAmount and coins introduces an invariant that must be maintained. Consider a small struct and a helper to append coins that also updates nAmount (or assert invariants in debug).

Apply this refactor for clarity and to enforce invariants:

 struct CompactTallyItem
 {
     CTxDestination txdest;
     CAmount nAmount{0};
-    std::vector<std::pair<CAmount, COutPoint>> coins{};
+    struct TallyCoin {
+        CAmount amount;
+        COutPoint outpoint;
+    };
+    std::vector<TallyCoin> coins{};
     CompactTallyItem() = default;
 };

And add a tiny helper (header-only, inline) to keep nAmount and coins consistent:

 struct CompactTallyItem
 {
+    inline void AddCoin(CAmount a, const COutPoint& op) {
+        nAmount += a;
+        coins.push_back(TallyCoin{a, op});
+    }
 };

Then replace ad-hoc increments with AddCoin(...) at call sites (e.g., in SelectCoinsGroupedByAddresses).
If you’d rather not add a helper, at least add a debug-only assertion where you construct vecTallyRet:

#ifndef NDEBUG
CAmount sum = 0;
for (const auto& c : item.second.coins) sum += c.amount;
assert(sum == item.second.nAmount);
#endif

653-656: Add documentation for the new fSkipMnCollateral parameter

The API signature across CWallet, its interface, and all callers has been updated consistently to include fSkipMnCollateral. To prevent misuse, please add a brief doc comment describing this flag in both the wallet header and the interface header.

Files to update:

  • src/wallet/wallet.h (around line 653)
  • src/interfaces/wallet.h (around line 239)

Suggested diff (adjust insertion point as needed):

--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -650,6 +650,13 @@ public:
     void ResendWalletTransactions();
 
+    /**
+     * Get anonymizable balance.
+     *
+     * @param fSkipDenominated   Exclude denominated CoinJoin outputs.
+     * @param fSkipMnCollateral  Exclude deterministic masternode collateral-sized UTXOs.
+     * @param fSkipUnconfirmed   Exclude unconfirmed outputs.
+     */
     CAmount GetAnonymizableBalance(bool fSkipDenominated, bool fSkipMnCollateral, bool fSkipUnconfirmed) const;
     float   GetAverageAnonymizedRounds() const;
     CAmount GetNormalizedAnonymizedBalance() const;
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -236,6 +236,12 @@ public:
     virtual CAmount getBalance() = 0;
 
+    /**
+     * Get anonymizable balance.
+     *
+     * @param fSkipDenominated   Exclude denominated CoinJoin outputs.
+     * @param fSkipMnCollateral  Exclude deterministic masternode collateral-sized UTXOs.
+     * @param fSkipUnconfirmed   Exclude unconfirmed outputs.
+     */
     virtual CAmount getAnonymizableBalance(bool fSkipDenominated, bool fSkipMnCollateral, bool fSkipUnconfirmed) = 0;
 
     virtual float   getAverageAnonymizedRounds() = 0;
src/instantsend/instantsend.h (2)

56-57: Non-owning m_signer pointer: document lifetime and thread-safety expectations

Using a raw non-owning pointer is fine given the ActiveContext wiring, but please explicitly document that:

  • ConnectSigner must be called before Start(), and DisconnectSigner after Stop().
  • The signer’s lifetime must outlive any access from this manager.
  • Access is not synchronized; Connect/Disconnect must not race with reads on worker threads.

Alternatively, consider making m_signer an std::atomic<InstantSendSigner*> for benign races on pointer loads, though comments may suffice if initialization/shutdown order is guaranteed.


91-98: Add missing header for gsl::not_null and guard against double-disconnect

Connect/Disconnect APIs are good. Two small tweaks:

  • Ensure the header explicitly includes whatever defines gsl::not_null (to avoid transitive-include brittleness).
  • Optionally assert in DisconnectSigner that we actually had a signer.

Apply:

 #include <instantsend/signing.h>
+#include <gsl/gsl> // or the project-specific gsl header that provides gsl::not_null

     void ConnectSigner(gsl::not_null<instantsend::InstantSendSigner*> signer)
     {
         // Prohibit double initialization
         assert(m_signer == nullptr);
         m_signer = signer;
     }
-    void DisconnectSigner() { m_signer = nullptr; }
+    void DisconnectSigner() {
+        assert(m_signer != nullptr);
+        m_signer = nullptr;
+    }

If the project uses a different include path for GSL, swap <gsl/gsl> accordingly.

src/instantsend/instantsend.cpp (2)

76-81: Start ordering: start the signer before launching the IS worker to avoid a potential race

WorkThreadMain may consult m_signer and call ProcessPendingRetryLockTxs shortly after the thread is launched. Starting the signer first removes a window where the worker might observe a connected but not-yet-started signer (or worse, a concurrent TransactionAddedToMempool invokes signer methods before Start).

Apply:

-    workThread = std::thread(&util::TraceThread, "isman", [this, &peerman] { WorkThreadMain(peerman); });
-
-    if (m_signer) {
-        m_signer->Start();
-    }
+    if (m_signer) {
+        m_signer->Start();
+    }
+    workThread = std::thread(&util::TraceThread, "isman", [this, &peerman] { WorkThreadMain(peerman); });

If there’s a strong reason to keep the current order, please document thread-safety guarantees for calling signer methods pre-Start.


916-941: Worker loop: benign read of m_signer without synchronization; document ordering guarantees

The worker reads m_signer without locks. Given Connect/Disconnect are intended to run only during init/shutdown, this is acceptable. Please add a short comment near m_signer or in Connect/Disconnect guaranteeing no runtime re-wiring during operation.

src/net_processing.cpp (1)

599-603: Optional: decouple from unique_ptr ownership in member type for ActiveContext.

Storing ActiveContext as const std::unique_ptr<ActiveContext>& tightly couples PeerManagerImpl to the owner’s storage type. A raw pointer (ActiveContext* const) or std::weak_ptr (if shared ownership ever becomes needed) would make the dependency clearer and ease future refactors without changing lifetime management. Not blocking—consistent with other members like m_dmnman.

If you decide to refactor later:

  • Change data member to ActiveContext* const m_active_ctx;
  • Pass active_ctx.get() at construction sites.

Would you like a follow-up patch sketch to perform this refactor across the call sites?

Also applies to: 781-784, 1954-1959, 1967-1981

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 873dee0 and 08db0fd.

📒 Files selected for processing (45)
  • src/Makefile.am (2 hunks)
  • src/active/context.cpp (1 hunks)
  • src/active/context.h (1 hunks)
  • src/active/notificationinterface.cpp (1 hunks)
  • src/active/notificationinterface.h (1 hunks)
  • src/chainlock/chainlock.cpp (1 hunks)
  • src/chainlock/chainlock.h (2 hunks)
  • src/coinjoin/client.cpp (6 hunks)
  • src/coinjoin/context.cpp (1 hunks)
  • src/coinjoin/context.h (1 hunks)
  • src/coinjoin/server.cpp (9 hunks)
  • src/coinjoin/server.h (2 hunks)
  • src/coinjoin/util.cpp (1 hunks)
  • src/dsnotificationinterface.cpp (0 hunks)
  • src/governance/governance.cpp (1 hunks)
  • src/init.cpp (8 hunks)
  • src/instantsend/instantsend.cpp (1 hunks)
  • src/instantsend/instantsend.h (2 hunks)
  • src/interfaces/wallet.h (1 hunks)
  • src/llmq/context.cpp (1 hunks)
  • src/llmq/context.h (0 hunks)
  • src/llmq/ehf_signals.cpp (1 hunks)
  • src/llmq/ehf_signals.h (2 hunks)
  • src/masternode/node.h (2 hunks)
  • src/masternode/sync.cpp (2 hunks)
  • src/net.cpp (2 hunks)
  • src/net.h (4 hunks)
  • src/net_processing.cpp (9 hunks)
  • src/net_processing.h (2 hunks)
  • src/node/context.cpp (1 hunks)
  • src/node/context.h (2 hunks)
  • src/qt/overviewpage.cpp (1 hunks)
  • src/rpc/coinjoin.cpp (2 hunks)
  • src/test/denialofservice_tests.cpp (5 hunks)
  • src/test/net_peer_connection_tests.cpp (2 hunks)
  • src/test/util/setup_common.cpp (2 hunks)
  • src/util/system.cpp (0 hunks)
  • src/util/system.h (0 hunks)
  • src/wallet/coinjoin.cpp (6 hunks)
  • src/wallet/interfaces.cpp (1 hunks)
  • src/wallet/test/coinjoin_tests.cpp (1 hunks)
  • src/wallet/test/wallet_tests.cpp (2 hunks)
  • src/wallet/wallet.h (3 hunks)
  • test/lint/lint-circular-dependencies.py (1 hunks)
  • test/util/data/non-backported.txt (1 hunks)
💤 Files with no reviewable changes (4)
  • src/dsnotificationinterface.cpp
  • src/llmq/context.h
  • src/util/system.h
  • src/util/system.cpp
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/node/context.cpp
  • src/rpc/coinjoin.cpp
  • src/governance/governance.cpp
  • src/net.cpp
  • src/wallet/interfaces.cpp
  • src/active/notificationinterface.h
  • src/interfaces/wallet.h
  • src/coinjoin/util.cpp
  • src/net_processing.h
  • src/active/context.cpp
  • src/masternode/node.h
  • src/chainlock/chainlock.h
  • src/active/context.h
  • src/llmq/ehf_signals.h
  • src/active/notificationinterface.cpp
  • src/test/net_peer_connection_tests.cpp
  • src/chainlock/chainlock.cpp
  • src/wallet/test/coinjoin_tests.cpp
  • src/masternode/sync.cpp
  • src/wallet/test/wallet_tests.cpp
  • src/net.h
  • src/instantsend/instantsend.cpp
  • src/qt/overviewpage.cpp
  • src/coinjoin/server.cpp
  • src/test/util/setup_common.cpp
  • src/node/context.h
  • src/coinjoin/context.cpp
  • src/wallet/wallet.h
  • src/instantsend/instantsend.h
  • src/coinjoin/client.cpp
  • src/net_processing.cpp
  • src/llmq/context.cpp
  • src/coinjoin/server.h
  • src/wallet/coinjoin.cpp
  • src/coinjoin/context.h
  • src/init.cpp
  • src/llmq/ehf_signals.cpp
  • src/test/denialofservice_tests.cpp
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/masternode/node.h
  • src/masternode/sync.cpp
src/{test,wallet/test,qt/test}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Unit tests for C++ code should be placed in src/test/, src/wallet/test/, or src/qt/test/ and use Boost::Test or Qt 5 for GUI tests

Files:

  • src/test/net_peer_connection_tests.cpp
  • src/wallet/test/coinjoin_tests.cpp
  • src/wallet/test/wallet_tests.cpp
  • src/test/util/setup_common.cpp
  • src/test/denialofservice_tests.cpp
🧠 Learnings (7)
📓 Common learnings
Learnt from: kwvg
PR: dashpay/dash#6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
Learnt from: kwvg
PR: dashpay/dash#6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
PR: dashpay/dash#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/bench/**/*.{cpp,h,cc,cxx,hpp} : Performance benchmarks should be placed in src/bench/ and use nanobench

Applied to files:

  • test/util/data/non-backported.txt
📚 Learning: 2025-01-02T21:50:00.967Z
Learnt from: kwvg
PR: dashpay/dash#6504
File: src/llmq/context.cpp:42-43
Timestamp: 2025-01-02T21:50:00.967Z
Learning: LLMQContext manages concurrency for the `CInstantSendManager`. Previously, this was handled globally; now it's handled as a class member in `LLMQContext`, but the concurrency control remains consistent.

Applied to files:

  • src/net_processing.h
  • src/active/context.h
  • src/instantsend/instantsend.h
  • src/llmq/context.cpp
📚 Learning: 2025-07-23T09:30:34.631Z
Learnt from: kwvg
PR: dashpay/dash#6761
File: src/chainlock/signing.h:5-6
Timestamp: 2025-07-23T09:30:34.631Z
Learning: Dash Core uses BITCOIN_ prefix for header guards as the standard convention, inherited from Bitcoin Core. Only a few BLS-specific files in src/bls/ use DASH_ prefix. The vast majority of files (385+) use BITCOIN_ prefix.

Applied to files:

  • src/Makefile.am
📚 Learning: 2025-07-29T14:32:48.369Z
Learnt from: kwvg
PR: dashpay/dash#6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.

Applied to files:

  • src/chainlock/chainlock.h
  • src/chainlock/chainlock.cpp
📚 Learning: 2024-12-29T17:43:41.755Z
Learnt from: kwvg
PR: dashpay/dash#6504
File: src/llmq/quorums.cpp:224-224
Timestamp: 2024-12-29T17:43:41.755Z
Learning: The `CQuorumManager` is fully initialized by `LLMQContext`, addressing any concerns about the manager’s initialization sequence.

Applied to files:

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

Applied to files:

  • src/wallet/test/wallet_tests.cpp
🧬 Code graph analysis (20)
src/active/notificationinterface.h (5)
src/masternode/node.cpp (1)
  • CActiveMasternodeManager (52-61)
src/active/notificationinterface.cpp (3)
  • ActiveNotificationInterface (11-15)
  • UpdatedBlockTip (17-22)
  • UpdatedBlockTip (17-18)
src/llmq/ehf_signals.cpp (2)
  • UpdatedBlockTip (34-47)
  • UpdatedBlockTip (34-34)
src/dsnotificationinterface.cpp (2)
  • UpdatedBlockTip (72-99)
  • UpdatedBlockTip (72-72)
src/active/context.cpp (2)
  • ActiveContext (16-33)
  • ActiveContext (35-39)
src/interfaces/wallet.h (1)
src/wallet/interfaces.cpp (2)
  • fSkipDenominated (434-437)
  • fSkipDenominated (434-434)
src/net_processing.h (1)
src/active/context.cpp (2)
  • ActiveContext (16-33)
  • ActiveContext (35-39)
src/masternode/node.h (2)
src/masternode/node.cpp (3)
  • CActiveMasternodeManager (52-61)
  • UpdatedBlockTip (171-211)
  • UpdatedBlockTip (171-171)
src/active/notificationinterface.cpp (2)
  • UpdatedBlockTip (17-22)
  • UpdatedBlockTip (17-18)
src/chainlock/chainlock.h (4)
src/active/context.h (1)
  • chainlock (23-25)
src/chainlock/chainlock.cpp (2)
  • CChainLocksHandler (46-57)
  • CChainLocksHandler (59-63)
src/instantsend/instantsend.h (2)
  • ConnectSigner (91-96)
  • DisconnectSigner (97-97)
src/chainlock/signing.cpp (2)
  • ChainLockSigner (18-28)
  • ChainLockSigner (30-30)
src/active/context.h (7)
src/coinjoin/coinjoin.h (1)
  • CDSTXManager (378-406)
src/net_processing.h (1)
  • PeerManager (67-67)
src/llmq/ehf_signals.h (2)
  • llmq (16-52)
  • CEHFSignalsHandler (21-51)
src/llmq/context.h (1)
  • llmq (23-33)
src/llmq/ehf_signals.cpp (2)
  • CEHFSignalsHandler (18-27)
  • CEHFSignalsHandler (29-32)
src/active/context.cpp (2)
  • ActiveContext (16-33)
  • ActiveContext (35-39)
src/llmq/context.cpp (2)
  • LLMQContext (19-44)
  • LLMQContext (46-48)
src/llmq/ehf_signals.h (2)
src/llmq/ehf_signals.cpp (2)
  • UpdatedBlockTip (34-47)
  • UpdatedBlockTip (34-34)
src/active/notificationinterface.cpp (2)
  • UpdatedBlockTip (17-22)
  • UpdatedBlockTip (17-18)
src/active/notificationinterface.cpp (2)
src/llmq/ehf_signals.cpp (2)
  • UpdatedBlockTip (34-47)
  • UpdatedBlockTip (34-34)
src/dsnotificationinterface.cpp (2)
  • UpdatedBlockTip (72-99)
  • UpdatedBlockTip (72-72)
src/qt/overviewpage.cpp (1)
src/qt/test/wallettests.cpp (1)
  • walletModel (157-157)
src/coinjoin/server.cpp (1)
src/net_processing.cpp (6)
  • inv (632-632)
  • inv (633-633)
  • inv (634-634)
  • inv (635-635)
  • inv (930-930)
  • inv (2309-2309)
src/node/context.h (1)
src/active/context.cpp (2)
  • ActiveContext (16-33)
  • ActiveContext (35-39)
src/wallet/wallet.h (2)
src/interfaces/wallet.h (1)
  • std (146-346)
src/wallet/coinjoin.cpp (4)
  • SelectCoinsGroupedByAddresses (124-230)
  • SelectCoinsGroupedByAddresses (124-125)
  • GetAnonymizableBalance (433-461)
  • GetAnonymizableBalance (433-433)
src/instantsend/instantsend.h (4)
src/instantsend/instantsend.cpp (2)
  • CInstantSendManager (52-65)
  • CInstantSendManager (67-67)
src/chainlock/chainlock.h (2)
  • ConnectSigner (73-78)
  • DisconnectSigner (79-79)
src/active/context.h (1)
  • instantsend (26-28)
src/instantsend/signing.cpp (2)
  • InstantSendSigner (30-44)
  • InstantSendSigner (46-46)
src/coinjoin/client.cpp (1)
src/wallet/interfaces.cpp (16)
  • m_wallet (158-161)
  • m_wallet (166-166)
  • m_wallet (175-175)
  • m_wallet (176-176)
  • m_wallet (182-182)
  • m_wallet (183-183)
  • m_wallet (438-441)
  • m_wallet (442-445)
  • m_wallet (517-517)
  • m_wallet (518-518)
  • m_wallet (519-519)
  • m_wallet (520-520)
  • m_wallet (521-521)
  • m_wallet (526-526)
  • m_wallet (586-586)
  • bal (405-422)
src/coinjoin/server.h (1)
src/net_processing.h (1)
  • PeerManager (67-67)
src/wallet/coinjoin.cpp (1)
src/wallet/interfaces.cpp (2)
  • fSkipDenominated (434-437)
  • fSkipDenominated (434-434)
src/coinjoin/context.h (1)
src/coinjoin/context.cpp (2)
  • CJContext (12-23)
  • CJContext (25-25)
src/init.cpp (2)
src/coinjoin/server.cpp (2)
  • DoMaintenance (871-879)
  • DoMaintenance (871-871)
src/coinjoin/client.cpp (6)
  • DoMaintenance (1845-1852)
  • DoMaintenance (1845-1845)
  • DoMaintenance (1854-1871)
  • DoMaintenance (1854-1854)
  • DoMaintenance (1916-1922)
  • DoMaintenance (1916-1916)
src/llmq/ehf_signals.cpp (1)
src/active/notificationinterface.cpp (2)
  • UpdatedBlockTip (17-22)
  • UpdatedBlockTip (17-18)
src/test/denialofservice_tests.cpp (1)
src/net_processing.cpp (2)
  • make (1948-1959)
  • make (1948-1956)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: linux64_tsan-test / Test source

Comment on lines +2269 to 2273
return m_cj_ctx->queueman->HasQueue(inv.hash) || (m_active_ctx && m_active_ctx->cj_server->HasQueue(inv.hash));
#else
return m_cj_ctx->server->HasQueue(inv.hash);
return m_active_ctx && m_active_ctx->cj_server->HasQueue(inv.hash);
#endif
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Null-deref risk: guard cj_server before dereferencing in DSQ AlreadyHave() path.

m_active_ctx being non-null does not guarantee m_active_ctx->cj_server is non-null. Dereferencing cj_server without a check can crash on non-masternode nodes or during partial initialization/shutdown.

Apply this diff to add the missing guard:

 #ifdef ENABLE_WALLET
-        return m_cj_ctx->queueman->HasQueue(inv.hash) || (m_active_ctx && m_active_ctx->cj_server->HasQueue(inv.hash));
+        return m_cj_ctx->queueman->HasQueue(inv.hash) ||
+               (m_active_ctx && m_active_ctx->cj_server && m_active_ctx->cj_server->HasQueue(inv.hash));
 #else
-        return m_active_ctx && m_active_ctx->cj_server->HasQueue(inv.hash);
+        return m_active_ctx && m_active_ctx->cj_server && m_active_ctx->cj_server->HasQueue(inv.hash);
 #endif
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return m_cj_ctx->queueman->HasQueue(inv.hash) || (m_active_ctx && m_active_ctx->cj_server->HasQueue(inv.hash));
#else
return m_cj_ctx->server->HasQueue(inv.hash);
return m_active_ctx && m_active_ctx->cj_server->HasQueue(inv.hash);
#endif
}
#ifdef ENABLE_WALLET
return m_cj_ctx->queueman->HasQueue(inv.hash) ||
(m_active_ctx && m_active_ctx->cj_server && m_active_ctx->cj_server->HasQueue(inv.hash));
#else
return m_active_ctx && m_active_ctx->cj_server && m_active_ctx->cj_server->HasQueue(inv.hash);
#endif
}
🤖 Prompt for AI Agents
In src/net_processing.cpp around lines 2269-2273, the code dereferences
m_active_ctx->cj_server without null-checking it; update the return so it
verifies m_active_ctx->cj_server is non-null before calling HasQueue (e.g.,
require m_active_ctx && m_active_ctx->cj_server &&
m_active_ctx->cj_server->HasQueue(inv.hash)), and apply the same null-guard
logic consistently for both build-paths to avoid crashes during partial
init/shutdown.

Comment on lines +5258 to +5260
if (m_active_ctx) {
PostProcessMessage(m_active_ctx->cj_server->ProcessMessage(pfrom, msg_type, vRecv), pfrom.GetId());
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Null-deref risk: guard cj_server in ActiveContext message dispatch.

Wrap the dispatch with an additional cj_server check to avoid dereferencing a null server.

-        if (m_active_ctx) {
-            PostProcessMessage(m_active_ctx->cj_server->ProcessMessage(pfrom, msg_type, vRecv), pfrom.GetId());
-        }
+        if (m_active_ctx && m_active_ctx->cj_server) {
+            PostProcessMessage(m_active_ctx->cj_server->ProcessMessage(pfrom, msg_type, vRecv), pfrom.GetId());
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (m_active_ctx) {
PostProcessMessage(m_active_ctx->cj_server->ProcessMessage(pfrom, msg_type, vRecv), pfrom.GetId());
}
if (m_active_ctx && m_active_ctx->cj_server) {
PostProcessMessage(
m_active_ctx->cj_server->ProcessMessage(pfrom, msg_type, vRecv),
pfrom.GetId()
);
}
🤖 Prompt for AI Agents
In src/net_processing.cpp around lines 5258 to 5260, the code dereferences
m_active_ctx->cj_server without verifying it is non-null; modify the dispatch to
first check that m_active_ctx->cj_server is not null (e.g. if (m_active_ctx &&
m_active_ctx->cj_server) {
PostProcessMessage(m_active_ctx->cj_server->ProcessMessage(pfrom, msg_type,
vRecv), pfrom.GetId()); } ) so ProcessMessage is only called when cj_server is
valid; preserve existing behavior and return path for when the guard fails.

Comment on lines +93 to 96
std::unique_ptr<ActiveContext> active_ctx;
std::unique_ptr<CJContext> cj_ctx;
std::unique_ptr<LLMQContext> llmq_ctx;

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Destruction order bug: ActiveContext dtor uses LLMQContext but llmq_ctx is destroyed first

ActiveContext::~ActiveContext() calls into m_llmq_ctx to DisconnectSigner(). In NodeContext, llmq_ctx is declared after active_ctx, so during destruction llmq_ctx is destroyed before active_ctx. This creates a use-after-destruction hazard.

Reorder members so that llmq_ctx outlives active_ctx (declare llmq_ctx before active_ctx). This also matches construction-time dependencies.

-    //! Dash contexts
-    std::unique_ptr<ActiveContext> active_ctx;
-    std::unique_ptr<CJContext> cj_ctx;
-    std::unique_ptr<LLMQContext> llmq_ctx;
+    //! Dash contexts
+    std::unique_ptr<LLMQContext> llmq_ctx;
+    std::unique_ptr<ActiveContext> active_ctx;
+    std::unique_ptr<CJContext> cj_ctx;

After reordering, please sanity-check that any init code constructs llmq_ctx before active_ctx (expected), and that tests creating NodeContext on their own don’t assume the previous layout.


🏁 Script executed:

#!/bin/bash
set -e

echo "1. Verifying the presence of src/node/context.h"
if [ -f src/node/context.h ]; then
  echo "✅ Found src/node/context.h"
else
  echo "❌ src/node/context.h not found"
  exit 1
fi

echo
echo "2. Extracting NodeContext member declarations (lines 80–120)"
sed -n '80,120p' src/node/context.h

echo
echo "3. Locating ActiveContext::~ActiveContext implementation"
grep -R "ActiveContext::~ActiveContext" -n src | head -n 5

echo
echo "4. Showing code around ActiveContext destructor (±5 lines)"
grep -R -C5 "ActiveContext::~ActiveContext" -n src

Length of output: 2356


Ensure LLMQContext outlives ActiveContext in NodeContext

There is a destruction‐order hazard: C++ destroys members in reverse declaration order. In NodeContext, std::unique_ptr<LLMQContext> llmq_ctx is declared after active_ctx, so upon teardown llmq_ctx is destroyed first—even though ActiveContext::~ActiveContext() calls into the now‐gone LLMQContext. You must swap their declarations to guarantee llmq_ctx remains alive through ActiveContext destruction.

• Location: src/node/context.h, inside struct NodeContext (members around line 93)
• Issue:

  • Current order
    std::unique_ptr<ActiveContext> active_ctx;
    std::unique_ptr<CJContext>    cj_ctx;
    std::unique_ptr<LLMQContext>  llmq_ctx;
  • Destruction sequence: llmq_ctx → cj_ctx → active_ctx, leading to a use‐after‐destruction in ActiveContext::~ActiveContext()

Proposed fix:

@@ –91,3 +91,3
-    std::unique_ptr<ActiveContext> active_ctx;
-    std::unique_ptr<CJContext>    cj_ctx;
-    std::unique_ptr<LLMQContext>  llmq_ctx;
+    std::unique_ptr<LLMQContext>  llmq_ctx;
+    std::unique_ptr<ActiveContext> active_ctx;
+    std::unique_ptr<CJContext>    cj_ctx;

After applying this change, please also:

  • Confirm that NodeContext::NodeContext() initializes (make_unique) llmq_ctx before active_ctx in its constructor body.
  • Run existing tests or add a small teardown check to ensure no regressions rely on the old member order.
🤖 Prompt for AI Agents
In src/node/context.h around lines 93-96, the member declaration order causes
llmq_ctx to be destroyed before active_ctx leading to a use-after-destruction in
ActiveContext::~ActiveContext(); swap the declarations so llmq_ctx is declared
before active_ctx (i.e., std::unique_ptr<LLMQContext> llmq_ctx;
std::unique_ptr<CJContext> cj_ctx; std::unique_ptr<ActiveContext> active_ctx;),
then update NodeContext::NodeContext() to construct/assign llmq_ctx
(make_unique) before creating active_ctx, and finally run the test suite or add
a small teardown test to verify ActiveContext destructor no longer accesses a
destroyed LLMQContext.

Comment on lines 468 to 472
const NodeContext& node = EnsureAnyNodeContext(request.context);
if (node.mn_activeman) {
node.cj_ctx->server->GetJsonInfo(obj);
node.active_ctx->cj_server->GetJsonInfo(obj);
return obj;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Guard ActiveContext access with CHECK_NONFATAL to avoid rare null-deref in tests or partial init.

While node.mn_activeman implies ActiveContext in normal init, unit/functional tests or future refactors could break the invariant. Match existing style (e.g., coinjoin_loader uses CHECK_NONFATAL).

Apply this diff:

-    if (node.mn_activeman) {
-        node.active_ctx->cj_server->GetJsonInfo(obj);
+    if (node.mn_activeman) {
+        CHECK_NONFATAL(CHECK_NONFATAL(node.active_ctx)->cj_server)->GetJsonInfo(obj);
         return obj;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const NodeContext& node = EnsureAnyNodeContext(request.context);
if (node.mn_activeman) {
node.cj_ctx->server->GetJsonInfo(obj);
node.active_ctx->cj_server->GetJsonInfo(obj);
return obj;
}
const NodeContext& node = EnsureAnyNodeContext(request.context);
if (node.mn_activeman) {
CHECK_NONFATAL(CHECK_NONFATAL(node.active_ctx)->cj_server)->GetJsonInfo(obj);
return obj;
}
🤖 Prompt for AI Agents
In src/rpc/coinjoin.cpp around lines 468 to 472, the code assumes
node.active_ctx is non-null when node.mn_activeman is true, which can cause rare
null-derefs in tests; wrap the active_ctx access with a CHECK_NONFATAL that
verifies node.active_ctx is non-null before dereferencing, and if the check
fails simply return obj (or otherwise short-circuit) to match the existing
coinjoin_loader style used elsewhere.

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