-
Notifications
You must be signed in to change notification settings - Fork 1.2k
refactor: drop fMasternodeMode
global, create new context for masternode mode-only logic (ActiveContext
), move spun-off signers and EHF signals handler
#6828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
✅ No Merge Conflicts DetectedThis PR currently has no conflicts with other open PRs. |
Also correct nMaxOupointsPerAddress typo
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.
Needed for next commit
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.
9a154a8
to
e2ff3c6
Compare
WalkthroughThe 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 unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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 consistencyUsing 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 usageThe 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 optionalI verified with ripgrep that the only write to
CConnman::m_active_masternode
occurs in itsInit
method (src/net.h:1235), and the only place the correspondingOptions::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 inCConnman
tostd::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 correctThe 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 warningsUsing 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 moveDropping 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 remainI’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(...)
orGetAnonymizableBalance(...)
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 completenessThe 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 reuseThe 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 completeI’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_ptrThis 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 leanactive/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 syncUsing 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 parameterThe API signature across
CWallet
, its interface, and all callers has been updated consistently to includefSkipMnCollateral
. 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 expectationsUsing 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-disconnectConnect/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 raceWorkThreadMain 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 guaranteesThe 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
) orstd::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.
📒 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
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
if (m_active_ctx) { | ||
PostProcessMessage(m_active_ctx->cj_server->ProcessMessage(pfrom, msg_type, vRecv), pfrom.GetId()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
std::unique_ptr<ActiveContext> active_ctx; | ||
std::unique_ptr<CJContext> cj_ctx; | ||
std::unique_ptr<LLMQContext> llmq_ctx; | ||
|
There was a problem hiding this comment.
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 inActiveContext::~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
beforeactive_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.
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
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.
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.
Additional Information
To allow removing
::fMasternodeMode
, we needed to route all pending global usage and one usage, inwallet/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 evaluatetrue
, leaving behindGetAnonymizableBalance()
, which now acceptsfSkipMnCollateral
as the caller can query if the node is in masternode mode (source).GetAnonymizableBalance()
, which required a change inCompactTallyItem
'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 ofLLMQContext
, forbitcoin-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
, namelyCCoinJoinServer
(source) andllmq::CEHFSignalsHandler
(source).Breaking Changes
None expected.
Checklist