-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix: revalidate CoinJoin DSA session before admission #7596
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,7 +67,7 @@ void CCoinJoinServer::ProcessDSACCEPT(CNode& peer, CDataStream& vRecv) | |
| { | ||
| assert(m_mn_metaman.IsValid()); | ||
|
|
||
| if (IsSessionReady()) { | ||
| if (WITH_LOCK(cs_coinjoin, return IsSessionReady())) { | ||
| // too many users in this session already, reject new ones | ||
| LogPrint(BCLog::COINJOIN, "DSACCEPT -- queue is already full!\n"); | ||
| PushStatus(peer, STATUS_REJECTED, ERR_QUEUE_FULL); | ||
|
|
@@ -86,7 +86,7 @@ void CCoinJoinServer::ProcessDSACCEPT(CNode& peer, CDataStream& vRecv) | |
| return; | ||
| } | ||
|
|
||
| if (vecSessionCollaterals.empty()) { | ||
| if (WITH_LOCK(cs_coinjoin, return vecSessionCollaterals.empty())) { | ||
| { | ||
| const auto hasQueue = m_queueman.TryHasQueueFromMasternode(m_mn_activeman.GetOutPoint()); | ||
| if (!hasQueue.has_value()) return; | ||
|
|
@@ -541,17 +541,23 @@ void CCoinJoinServer::CheckTimeout() | |
| */ | ||
| void CCoinJoinServer::CheckForCompleteQueue() | ||
| { | ||
| if (nState == POOL_STATE_QUEUE && IsSessionReady()) { | ||
| SetState(POOL_STATE_ACCEPTING_ENTRIES); | ||
| int session_denom; | ||
| size_t participants; | ||
| { | ||
| LOCK(cs_coinjoin); | ||
| if (nState != POOL_STATE_QUEUE || !IsSessionReady()) return; | ||
|
|
||
| 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.vchSig = m_mn_activeman.SignBasic(dsq.GetSignatureHash()); | ||
| m_peer_manager->PeerRelayDSQ(dsq); | ||
| m_queueman.AddQueue(std::move(dsq)); | ||
| SetState(POOL_STATE_ACCEPTING_ENTRIES); | ||
| session_denom = nSessionDenom; | ||
| participants = vecSessionCollaterals.size(); | ||
| } | ||
|
|
||
| CCoinJoinQueue dsq(session_denom, m_mn_activeman.GetOutPoint(), m_mn_activeman.GetProTxHash(), GetAdjustedTime(), true); | ||
| LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CheckForCompleteQueue -- ready queue %s with %d participants\n", | ||
| dsq.ToString(), participants); | ||
| dsq.vchSig = m_mn_activeman.SignBasic(dsq.GetSignatureHash()); | ||
| m_peer_manager->PeerRelayDSQ(dsq); | ||
| m_queueman.AddQueue(std::move(dsq)); | ||
| } | ||
|
|
||
| // Check to make sure a given input matches an input in the pool and its scriptSig is valid | ||
|
|
@@ -807,35 +813,44 @@ bool CCoinJoinServer::CreateNewSession(const CCoinJoinAccept& dsa, PoolMessage& | |
|
|
||
| bool CCoinJoinServer::AddUserToExistingSession(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet) | ||
| { | ||
| if (nSessionID == 0 || IsSessionReady()) return false; | ||
|
|
||
| if (!IsAcceptableDSA(dsa, nMessageIDRet)) { | ||
| return false; | ||
| int session_id; | ||
| int session_denom; | ||
| { | ||
| LOCK(cs_coinjoin); | ||
| if (nSessionID == 0 || nState != POOL_STATE_QUEUE) { | ||
| nMessageIDRet = ERR_MODE; | ||
| return false; | ||
| } | ||
| if (IsSessionReady()) { | ||
| nMessageIDRet = ERR_QUEUE_FULL; | ||
| return false; | ||
| } | ||
| session_id = nSessionID; | ||
| session_denom = nSessionDenom; | ||
| } | ||
|
|
||
| // we only add new users to an existing session when we are in queue mode | ||
| if (nState != POOL_STATE_QUEUE) { | ||
| nMessageIDRet = ERR_MODE; | ||
| LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddUserToExistingSession -- incompatible mode: nState=%d\n", nState); | ||
| if (!IsAcceptableDSA(dsa, nMessageIDRet)) { | ||
| return false; | ||
| } | ||
|
|
||
| if (dsa.nDenom != nSessionDenom) { | ||
| LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddUserToExistingSession -- incompatible denom %d (%s) != nSessionDenom %d (%s)\n", | ||
| dsa.nDenom, CoinJoin::DenominationToString(dsa.nDenom), nSessionDenom, CoinJoin::DenominationToString(nSessionDenom)); | ||
| if (dsa.nDenom != session_denom) { | ||
| LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddUserToExistingSession -- incompatible denom %d (%s) != %d (%s)\n", | ||
| dsa.nDenom, CoinJoin::DenominationToString(dsa.nDenom), session_denom, | ||
| CoinJoin::DenominationToString(session_denom)); | ||
| nMessageIDRet = ERR_DENOM; | ||
| return false; | ||
| } | ||
|
|
||
| LOCK(cs_coinjoin); | ||
|
|
||
| // A scheduler-thread timeout can reset the session via SetNull() between the checks above | ||
| // and taking cs_coinjoin, so revalidate: a collateral must never be committed to a session | ||
| // that no longer exists. | ||
| if (nSessionID == 0 || nState != POOL_STATE_QUEUE) { | ||
| if (nSessionID != session_id || nSessionDenom != session_denom || nState != POOL_STATE_QUEUE) { | ||
| nMessageIDRet = ERR_MODE; | ||
| return false; | ||
| } | ||
| if (IsSessionReady()) { | ||
| nMessageIDRet = ERR_QUEUE_FULL; | ||
| return false; | ||
| } | ||
|
Comment on lines
+846
to
+853
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: Add regression coverage for DSA session revalidation This synchronization fix adds session identity, denomination, state, readiness, and capacity invariants, but the PR does not add tests that exercise the relevant interleavings. Existing CoinJoin tests do not call source: ['codex', 'coderabbit'] |
||
|
|
||
| // Session collaterals are only ever test-accepted, never added to the mempool, so nothing | ||
| // pins their identity: the same UTXO can be re-signed into arbitrarily many distinct txids. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Add deterministic tests for the new session synchronization invariants.
This diff changes queue completion and admission interleavings, but it includes no targeted C++ test changes.
src/coinjoin/server.cpp#L544-L560: Add a test that concurrent completion checks produce one queue transition and reject further queue-mode admissions after the state changes.src/coinjoin/server.cpp#L816-L853: Add a controlled interleaving that changes or resets the session after the snapshot, then assert that the stale admission does not commit collateral.As per coding guidelines, “Choose and add targeted C++ unit tests for changed behavior.”
📍 Affects 1 file
src/coinjoin/server.cpp#L544-L560(this comment)src/coinjoin/server.cpp#L816-L853🤖 Prompt for AI Agents
Source: Coding guidelines
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.
This is a fair testing concern, but the proposed deterministic interleavings would require adding production-visible test seams or blocking hooks inside private admission methods. That scaffolding would be substantially larger than this synchronization fix and would make the PR harder to review.
I verified the affected paths with the existing CoinJoin unit suites, a debug/werror build, and the repository's TSAN CI configuration. I’m keeping this PR focused on the lock/snapshot invariant; a reusable concurrency test harness would be better introduced separately if maintainers want one.
🤖 Posted autonomously by Codex on behalf of pasta.
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.
@PastaPastaPasta, thanks for the detailed scope assessment.The current validation gives useful coverage, but it does not prove the specific admission interleavings. I agree that production-visible hooks would be disproportionate for this focused synchronization change.
Would you like me to open a follow-up GitHub issue for a reusable CoinJoin concurrency test harness? The issue can define deterministic coverage for queue completion and stale admission snapshots without expanding this PR.
You are interacting with an AI system.