Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ BITCOIN_CORE_H = \
logging.h \
logging/timer.h \
mapport.h \
masternode/active/context.h \
masternode/active/notificationinterface.h \
masternode/node.h \
masternode/meta.h \
masternode/payments.h \
Expand Down Expand Up @@ -520,6 +522,8 @@ libbitcoin_node_a_SOURCES = \
llmq/snapshot.cpp \
llmq/utils.cpp \
mapport.cpp \
masternode/active/context.cpp \
masternode/active/notificationinterface.cpp \
masternode/node.cpp \
masternode/meta.cpp \
masternode/payments.cpp \
Expand Down
8 changes: 2 additions & 6 deletions src/chainlock/chainlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,15 @@ bool AreChainLocksEnabled(const CSporkManager& sporkman)
}

CChainLocksHandler::CChainLocksHandler(CChainState& chainstate, CQuorumManager& _qman, CSigningManager& _sigman,
CSigSharesManager& _shareman, CSporkManager& sporkman, CTxMemPool& _mempool,
const CMasternodeSync& mn_sync, bool is_masternode) :
CSporkManager& sporkman, CTxMemPool& _mempool, const CMasternodeSync& mn_sync) :
m_chainstate{chainstate},
qman{_qman},
spork_manager{sporkman},
mempool{_mempool},
m_mn_sync{mn_sync},
scheduler{std::make_unique<CScheduler>()},
scheduler_thread{
std::make_unique<std::thread>(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))},
m_signer{is_masternode
? std::make_unique<chainlock::ChainLockSigner>(chainstate, *this, _sigman, _shareman, sporkman, mn_sync)
: nullptr}
std::make_unique<std::thread>(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))}
{
}

Expand Down
14 changes: 10 additions & 4 deletions src/chainlock/chainlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ namespace llmq {
class CInstantSendManager;
class CQuorumManager;
class CSigningManager;
class CSigSharesManager;
enum class VerifyRecSigStatus;

class CChainLocksHandler final : public chainlock::ChainLockSignerParent
Expand All @@ -47,7 +46,7 @@ class CChainLocksHandler final : public chainlock::ChainLockSignerParent
std::unique_ptr<CScheduler> scheduler;
std::unique_ptr<std::thread> scheduler_thread;

std::unique_ptr<chainlock::ChainLockSigner> m_signer{nullptr};
chainlock::ChainLockSigner* m_signer{nullptr};
Copy link

@coderabbitai coderabbitai bot Sep 9, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make signer pointer updates thread-safe (guard with cs).

m_signer is mutated at runtime via Connect/Disconnect while CChainLocksHandler runs a scheduler thread. Without synchronization, this is a data race. Guard the pointer with cs and lock in ConnectSigner/DisconnectSigner.

Apply this minimal diff:

@@
-    chainlock::ChainLockSigner* m_signer{nullptr};
+    chainlock::ChainLockSigner* m_signer GUARDED_BY(cs){nullptr};
@@
-    void ConnectSigner(gsl::not_null<chainlock::ChainLockSigner*> signer)
+    void ConnectSigner(gsl::not_null<chainlock::ChainLockSigner*> signer)
     {
         // Prohibit double initialization
         assert(m_signer == nullptr);
-        m_signer = signer;
+        LOCK(cs);
+        m_signer = signer;
     }
-    void DisconnectSigner() { m_signer = nullptr; }
+    void DisconnectSigner()
+    {
+        LOCK(cs);
+        m_signer = nullptr;
+    }
📝 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
chainlock::ChainLockSigner* m_signer{nullptr};
// File: src/chainlock/chainlock.h
// — inside CChainLocksHandler (around line 49), assuming `CCriticalSection cs;` is already declared above
private:
// Mark m_signer as guarded by cs for static analysis
chainlock::ChainLockSigner* m_signer GUARDED_BY(cs){nullptr};
public:
void ConnectSigner(gsl::not_null<chainlock::ChainLockSigner*> signer)
{
// Prohibit double initialization
assert(m_signer == nullptr);
// Synchronize assignment
LOCK(cs);
m_signer = signer;
}
void DisconnectSigner()
{
// Synchronize reset
LOCK(cs);
m_signer = nullptr;
}

Copy link
Member

Choose a reason for hiding this comment

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

bot, please confirm we could just use an atomic here right? as we are just guarding for the writing to the pointer

Copy link

Choose a reason for hiding this comment

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

@PastaPastaPasta Yes, you're absolutely correct! Using std::atomic<chainlock::ChainLockSigner*> would be the better approach here since we're only guarding pointer read/write operations.

The access pattern is:

  • Writes: ConnectSigner/DisconnectSigner (infrequent)
  • Reads: if (m_signer) checks followed by method calls (frequent)

std::atomic<T*> provides sequential consistency for these operations without the overhead of mutex locking. The usage would be:

std::atomic<chainlock::ChainLockSigner*> m_signer{nullptr};

void ConnectSigner(gsl::not_null<chainlock::ChainLockSigner*> signer) {
    assert(m_signer.load() == nullptr);
    m_signer.store(signer);
}

void DisconnectSigner() {
    m_signer.store(nullptr);
}

And existing if (m_signer) checks would become if (m_signer.load()) or just if (m_signer) since atomic pointers have implicit conversion.

This is much more efficient than mutex synchronization for this simple pointer swap pattern, especially given that reads are likely far more frequent than writes.


🧠 Learnings used
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.

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.


mutable Mutex cs;
std::atomic<bool> tryLockChainTipScheduled{false};
Expand All @@ -68,10 +67,17 @@ class CChainLocksHandler final : public chainlock::ChainLockSignerParent

public:
explicit CChainLocksHandler(CChainState& chainstate, CQuorumManager& _qman, CSigningManager& _sigman,
CSigSharesManager& _shareman, CSporkManager& sporkman, CTxMemPool& _mempool,
const CMasternodeSync& mn_sync, bool is_masternode);
CSporkManager& sporkman, CTxMemPool& _mempool, const CMasternodeSync& mn_sync);
~CChainLocksHandler();

void ConnectSigner(gsl::not_null<chainlock::ChainLockSigner*> signer)
{
// Prohibit double initialization
assert(m_signer == nullptr);
m_signer = signer;
}
void DisconnectSigner() { m_signer = nullptr; }

void Start(const llmq::CInstantSendManager& isman);
void Stop();

Expand Down
13 changes: 5 additions & 8 deletions src/coinjoin/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,19 @@
#ifdef ENABLE_WALLET
#include <coinjoin/client.h>
#endif // ENABLE_WALLET
#include <coinjoin/server.h>
#include <coinjoin/coinjoin.h>

CJContext::CJContext(ChainstateManager& chainman, CConnman& connman, CDeterministicMNManager& dmnman,
CMasternodeMetaMan& mn_metaman, CTxMemPool& mempool,
const CActiveMasternodeManager* const mn_activeman, const CMasternodeSync& mn_sync,
const llmq::CInstantSendManager& isman, std::unique_ptr<PeerManager>& peerman, bool relay_txes) :
dstxman{std::make_unique<CDSTXManager>()},
CJContext::CJContext(ChainstateManager& chainman, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman,
CTxMemPool& mempool, const CActiveMasternodeManager* const mn_activeman,
const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, bool relay_txes) :
#ifdef ENABLE_WALLET
walletman{std::make_unique<CoinJoinWalletManager>(chainman, dmnman, mn_metaman, mempool, mn_sync, isman, queueman,
/*is_masternode=*/mn_activeman != nullptr)},
queueman{relay_txes ? std::make_unique<CCoinJoinClientQueueManager>(*walletman, dmnman, mn_metaman, mn_sync,
/*is_masternode=*/mn_activeman != nullptr)
: nullptr},
#endif // ENABLE_WALLET
server{std::make_unique<CCoinJoinServer>(chainman, connman, dmnman, *dstxman, mn_metaman, mempool, mn_activeman,
mn_sync, isman, peerman)}
dstxman{std::make_unique<CDSTXManager>()}
{}

CJContext::~CJContext() {}
14 changes: 4 additions & 10 deletions src/coinjoin/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@
#include <memory>

class CActiveMasternodeManager;
class CBlockPolicyEstimator;
class CCoinJoinServer;
class CConnman;
class CDeterministicMNManager;
class CDSTXManager;
class ChainstateManager;
class CMasternodeMetaMan;
class CMasternodeSync;
class CTxMemPool;
class PeerManager;
namespace llmq {
class CInstantSendManager;
};
Expand All @@ -34,19 +30,17 @@ class CoinJoinWalletManager;
struct CJContext {
CJContext() = delete;
CJContext(const CJContext&) = delete;
CJContext(ChainstateManager& chainman, CConnman& connman, CDeterministicMNManager& dmnman,
CMasternodeMetaMan& mn_metaman, CTxMemPool& mempool, const CActiveMasternodeManager* const mn_activeman,
const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman,
std::unique_ptr<PeerManager>& peerman, bool relay_txes);
CJContext(ChainstateManager& chainman, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman,
CTxMemPool& mempool, const CActiveMasternodeManager* const mn_activeman, const CMasternodeSync& mn_sync,
const llmq::CInstantSendManager& isman, bool relay_txes);
~CJContext();

const std::unique_ptr<CDSTXManager> dstxman;
#ifdef ENABLE_WALLET
// The main object for accessing mixing
const std::unique_ptr<CoinJoinWalletManager> walletman;
const std::unique_ptr<CCoinJoinClientQueueManager> queueman;
#endif // ENABLE_WALLET
const std::unique_ptr<CCoinJoinServer> server;
const std::unique_ptr<CDSTXManager> dstxman;
};

#endif // BITCOIN_COINJOIN_CONTEXT_H
62 changes: 18 additions & 44 deletions src/coinjoin/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

MessageProcessingResult CCoinJoinServer::ProcessMessage(CNode& peer, std::string_view msg_type, CDataStream& vRecv)
{
if (!m_mn_activeman) return {};
if (!m_mn_sync.IsBlockchainSynced()) return {};

if (msg_type == NetMsgType::DSACCEPT) {
Expand All @@ -42,7 +41,6 @@ MessageProcessingResult CCoinJoinServer::ProcessMessage(CNode& peer, std::string

void CCoinJoinServer::ProcessDSACCEPT(CNode& peer, CDataStream& vRecv)
{
assert(m_mn_activeman);
assert(m_mn_metaman.IsValid());

if (IsSessionReady()) {
Expand All @@ -58,7 +56,7 @@ void CCoinJoinServer::ProcessDSACCEPT(CNode& peer, CDataStream& vRecv)
LogPrint(BCLog::COINJOIN, "DSACCEPT -- nDenom %d (%s) txCollateral %s", dsa.nDenom, CoinJoin::DenominationToString(dsa.nDenom), dsa.txCollateral.ToString()); /* Continued */

auto mnList = m_dmnman.GetListAtChainTip();
auto dmn = mnList.GetValidMNByCollateral(m_mn_activeman->GetOutPoint());
auto dmn = mnList.GetValidMNByCollateral(m_mn_activeman.GetOutPoint());
if (!dmn) {
PushStatus(peer, STATUS_REJECTED, ERR_MN_LIST);
return;
Expand All @@ -69,7 +67,7 @@ void CCoinJoinServer::ProcessDSACCEPT(CNode& peer, CDataStream& vRecv)
TRY_LOCK(cs_vecqueue, lockRecv);
if (!lockRecv) return;

auto mnOutpoint = m_mn_activeman->GetOutPoint();
auto mnOutpoint = m_mn_activeman.GetOutPoint();

if (ranges::any_of(vecCoinJoinQueue,
[&mnOutpoint](const auto& q){return q.masternodeOutpoint == mnOutpoint;})) {
Expand Down Expand Up @@ -183,7 +181,7 @@ MessageProcessingResult CCoinJoinServer::ProcessDSQUEUE(NodeId from, CDataStream
TRY_LOCK(cs_vecqueue, lockRecv);
if (!lockRecv) return ret;
vecCoinJoinQueue.push_back(dsq);
m_peerman->RelayDSQ(dsq);
m_peerman.RelayDSQ(dsq);
}
return ret;
}
Expand Down Expand Up @@ -254,8 +252,6 @@ void CCoinJoinServer::SetNull()
//
void CCoinJoinServer::CheckPool()
{
if (!m_mn_activeman) return;

if (int entries = GetEntriesCount(); entries != 0) LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CheckPool -- entries count %lu\n", entries);

// If we have an entry for each collateral, then create final tx
Expand Down Expand Up @@ -317,7 +313,6 @@ void CCoinJoinServer::CreateFinalTransaction()
void CCoinJoinServer::CommitFinalTransaction()
{
AssertLockNotHeld(cs_coinjoin);
if (!m_mn_activeman) return; // check and relay final tx only on masternode

CTransactionRef finalTransaction = WITH_LOCK(cs_coinjoin, return MakeTransactionRef(finalMutableTransaction));
uint256 hashTx = finalTransaction->GetHash();
Expand All @@ -341,18 +336,16 @@ void CCoinJoinServer::CommitFinalTransaction()

// create and sign masternode dstx transaction
if (!m_dstxman.GetDSTX(hashTx)) {
CCoinJoinBroadcastTx dstxNew(finalTransaction,
m_mn_activeman->GetOutPoint(),
m_mn_activeman->GetProTxHash(),
GetAdjustedTime());
dstxNew.Sign(*m_mn_activeman);
CCoinJoinBroadcastTx dstxNew(finalTransaction, m_mn_activeman.GetOutPoint(), m_mn_activeman.GetProTxHash(),
GetAdjustedTime());
dstxNew.Sign(m_mn_activeman);
m_dstxman.AddDSTX(dstxNew);
}

LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- TRANSMITTING DSTX\n");

CInv inv(MSG_DSTX, hashTx);
Assert(m_peerman)->RelayInv(inv);
m_peerman.RelayInv(inv);

// Tell the clients it was successful
RelayCompletedTransaction(MSG_SUCCESS);
Expand Down Expand Up @@ -380,7 +373,6 @@ void CCoinJoinServer::CommitFinalTransaction()
void CCoinJoinServer::ChargeFees() const
{
AssertLockNotHeld(cs_coinjoin);
if (!m_mn_activeman) return;

//we don't need to charge collateral for every offence.
if (GetRand<int>(/*nMax=*/100) > 33) return;
Expand Down Expand Up @@ -448,8 +440,6 @@ void CCoinJoinServer::ChargeFees() const
*/
void CCoinJoinServer::ChargeRandomFees() const
{
if (!m_mn_activeman) return;

for (const auto& txCollateral : vecSessionCollaterals) {
if (GetRand<int>(/*nMax=*/100) > 10) return;
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::ChargeRandomFees -- charging random fees, txCollateral=%s", txCollateral->ToString()); /* Continued */
Expand All @@ -463,15 +453,13 @@ void CCoinJoinServer::ConsumeCollateral(const CTransactionRef& txref) const
if (!ATMPIfSaneFee(m_chainman, txref)) {
LogPrint(BCLog::COINJOIN, "%s -- ATMPIfSaneFee failed\n", __func__);
} else {
Assert(m_peerman)->RelayTransaction(txref->GetHash());
m_peerman.RelayTransaction(txref->GetHash());
LogPrint(BCLog::COINJOIN, "%s -- Collateral was consumed\n", __func__);
}
}

bool CCoinJoinServer::HasTimedOut() const
{
if (!m_mn_activeman) return false;

if (nState == POOL_STATE_IDLE) return false;

int nTimeout = (nState == POOL_STATE_SIGNING) ? COINJOIN_SIGNING_TIMEOUT : COINJOIN_QUEUE_TIMEOUT;
Expand All @@ -484,8 +472,6 @@ bool CCoinJoinServer::HasTimedOut() const
//
void CCoinJoinServer::CheckTimeout()
{
if (!m_mn_activeman) return;

CheckQueue();

// Too early to do anything
Expand All @@ -504,19 +490,15 @@ void CCoinJoinServer::CheckTimeout()
*/
void CCoinJoinServer::CheckForCompleteQueue()
{
if (!m_mn_activeman) return;

if (nState == POOL_STATE_QUEUE && IsSessionReady()) {
SetState(POOL_STATE_ACCEPTING_ENTRIES);

CCoinJoinQueue dsq(nSessionDenom,
m_mn_activeman->GetOutPoint(),
m_mn_activeman->GetProTxHash(),
GetAdjustedTime(), true);
CCoinJoinQueue dsq(nSessionDenom, m_mn_activeman.GetOutPoint(), m_mn_activeman.GetProTxHash(),
GetAdjustedTime(), true);
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CheckForCompleteQueue -- queue is ready, signing and relaying (%s) " /* Continued */
"with %d participants\n", dsq.ToString(), vecSessionCollaterals.size());
dsq.Sign(*m_mn_activeman);
m_peerman->RelayDSQ(dsq);
dsq.Sign(m_mn_activeman);
m_peerman.RelayDSQ(dsq);
WITH_LOCK(cs_vecqueue, vecCoinJoinQueue.push_back(dsq));
}
}
Expand Down Expand Up @@ -572,7 +554,6 @@ bool CCoinJoinServer::IsInputScriptSigValid(const CTxIn& txin) const
bool CCoinJoinServer::AddEntry(const CCoinJoinEntry& entry, PoolMessage& nMessageIDRet)
{
AssertLockNotHeld(cs_coinjoin);
if (!m_mn_activeman) return false;

if (size_t(GetEntriesCount()) >= vecSessionCollaterals.size()) {
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: entries is full!\n", __func__);
Expand Down Expand Up @@ -682,8 +663,6 @@ bool CCoinJoinServer::IsSignaturesComplete() const

bool CCoinJoinServer::IsAcceptableDSA(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet) const
{
if (!m_mn_activeman) return false;

// is denom even something legit?
if (!CoinJoin::IsValidDenomination(dsa.nDenom)) {
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- denom not valid!\n", __func__);
Expand All @@ -703,7 +682,7 @@ bool CCoinJoinServer::IsAcceptableDSA(const CCoinJoinAccept& dsa, PoolMessage& n

bool CCoinJoinServer::CreateNewSession(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet)
{
if (!m_mn_activeman || nSessionID != 0) return false;
if (nSessionID != 0) return false;

// new session can only be started in idle mode
if (nState != POOL_STATE_IDLE) {
Expand All @@ -725,13 +704,11 @@ bool CCoinJoinServer::CreateNewSession(const CCoinJoinAccept& dsa, PoolMessage&

if (!fUnitTest) {
//broadcast that I'm accepting entries, only if it's the first entry through
CCoinJoinQueue dsq(nSessionDenom,
m_mn_activeman->GetOutPoint(),
m_mn_activeman->GetProTxHash(),
GetAdjustedTime(), false);
CCoinJoinQueue dsq(nSessionDenom, m_mn_activeman.GetOutPoint(), m_mn_activeman.GetProTxHash(),
GetAdjustedTime(), false);
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CreateNewSession -- signing and relaying new queue: %s\n", dsq.ToString());
dsq.Sign(*m_mn_activeman);
m_peerman->RelayDSQ(dsq);
dsq.Sign(m_mn_activeman);
m_peerman.RelayDSQ(dsq);
LOCK(cs_vecqueue);
vecCoinJoinQueue.push_back(dsq);
}
Expand All @@ -745,7 +722,7 @@ bool CCoinJoinServer::CreateNewSession(const CCoinJoinAccept& dsa, PoolMessage&

bool CCoinJoinServer::AddUserToExistingSession(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet)
{
if (!m_mn_activeman || nSessionID == 0 || IsSessionReady()) return false;
if (nSessionID == 0 || IsSessionReady()) return false;

if (!IsAcceptableDSA(dsa, nMessageIDRet)) {
return false;
Expand Down Expand Up @@ -881,8 +858,6 @@ void CCoinJoinServer::RelayCompletedTransaction(PoolMessage nMessageID)

void CCoinJoinServer::SetState(PoolState nStateNew)
{
if (!m_mn_activeman) return;

if (nStateNew == POOL_STATE_ERROR) {
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::SetState -- Can't set state to ERROR as a Masternode. \n");
return;
Expand All @@ -895,7 +870,6 @@ void CCoinJoinServer::SetState(PoolState nStateNew)

void CCoinJoinServer::DoMaintenance()
{
if (!m_mn_activeman) return; // only run on masternodes
if (!m_mn_sync.IsBlockchainSynced()) return;
if (ShutdownRequested()) return;

Expand Down
Loading
Loading