From b164bcc7a5ce5110f2a5b30931a3fe41ddc9de3a Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 4 Sep 2018 13:54:59 +0300 Subject: [PATCH] Split PS into Manager and Session and allow running multiple mixing sessions in parallel (client side) (#2203) * Split PS into Manager and Session * Adjust log messages accordingly * add -privatesendsessions cmd-line option * address review comments * bump MAX_OUTBOUND_MASTERNODE_CONNECTIONS to 30 +10 for parallel mixing * protect vecSessions * constructors * Rewrite CMasternodeMan::ProcessMasternodeConnections() to use CPrivateSendClientManager::GetMixingMasternodesInfo(). This should solve potential deadlock cs_vecqueue vs cs_vNodes. * Drop no longer used IsMixingMasternode() * lock cs_wallet when mixing uses balance related functions --- src/init.cpp | 6 +- src/masternodeman.cpp | 19 +- src/net.h | 2 +- src/privatesend-client.cpp | 565 ++++++++++++++++++++++++------------- src/privatesend-client.h | 134 ++++++--- src/privatesend-server.cpp | 5 +- src/privatesend-server.h | 5 +- src/privatesend.cpp | 35 ++- src/privatesend.h | 39 ++- src/qt/overviewpage.cpp | 12 +- src/rpc/masternode.cpp | 28 +- 11 files changed, 561 insertions(+), 289 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index e148b6e72..c66e0039d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -579,6 +579,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageGroup(_("PrivateSend options:")); strUsage += HelpMessageOpt("-enableprivatesend=", strprintf(_("Enable use of automated PrivateSend for funds stored in this wallet (0-1, default: %u)"), 0)); strUsage += HelpMessageOpt("-privatesendmultisession=", strprintf(_("Enable multiple PrivateSend mixing sessions per block, experimental (0-1, default: %u)"), DEFAULT_PRIVATESEND_MULTISESSION)); + strUsage += HelpMessageOpt("-privatesendsessions=", strprintf(_("Use N separate masternodes in parallel to mix funds (%u-%u, default: %u)"), MIN_PRIVATESEND_SESSIONS, MAX_PRIVATESEND_SESSIONS, DEFAULT_PRIVATESEND_SESSIONS)); strUsage += HelpMessageOpt("-privatesendrounds=", strprintf(_("Use N separate masternodes for each denominated input to mix funds (%u-%u, default: %u)"), MIN_PRIVATESEND_ROUNDS, MAX_PRIVATESEND_ROUNDS, DEFAULT_PRIVATESEND_ROUNDS)); strUsage += HelpMessageOpt("-privatesendamount=", strprintf(_("Keep N DASH anonymized (%u-%u, default: %u)"), MIN_PRIVATESEND_AMOUNT, MAX_PRIVATESEND_AMOUNT, DEFAULT_PRIVATESEND_AMOUNT)); strUsage += HelpMessageOpt("-liquidityprovider=", strprintf(_("Provide liquidity to PrivateSend by infrequently mixing coins on a continual basis (%u-%u, default: %u, 1=very frequent, high fees, %u=very infrequent, low fees)"), @@ -918,6 +919,8 @@ void InitParameterInteraction() if (nLiqProvTmp > 0) { ForceSetArg("-enableprivatesend", "1"); LogPrintf("%s: parameter interaction: -liquidityprovider=%d -> setting -enableprivatesend=1\n", __func__, nLiqProvTmp); + ForceSetArg("-privatesendsessions", itostr(MIN_PRIVATESEND_SESSIONS)); + LogPrintf("%s: parameter interaction: -liquidityprovider=%d -> setting -privatesendsessions=%d\n", __func__, nLiqProvTmp, itostr(std::numeric_limits::max())); ForceSetArg("-privatesendrounds", itostr(std::numeric_limits::max())); LogPrintf("%s: parameter interaction: -liquidityprovider=%d -> setting -privatesendrounds=%d\n", __func__, nLiqProvTmp, itostr(std::numeric_limits::max())); ForceSetArg("-privatesendamount", itostr(MAX_PRIVATESEND_AMOUNT)); @@ -1921,6 +1924,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) privateSendClient.fEnablePrivateSend = GetBoolArg("-enableprivatesend", false); privateSendClient.fPrivateSendMultiSession = GetBoolArg("-privatesendmultisession", DEFAULT_PRIVATESEND_MULTISESSION); + privateSendClient.nPrivateSendSessions = std::min(std::max((int)GetArg("-privatesendsessions", DEFAULT_PRIVATESEND_SESSIONS), MIN_PRIVATESEND_SESSIONS), MAX_PRIVATESEND_SESSIONS); privateSendClient.nPrivateSendRounds = std::min(std::max((int)GetArg("-privatesendrounds", DEFAULT_PRIVATESEND_ROUNDS), MIN_PRIVATESEND_ROUNDS), nMaxRounds); privateSendClient.nPrivateSendAmount = std::min(std::max((int)GetArg("-privatesendamount", DEFAULT_PRIVATESEND_AMOUNT), MIN_PRIVATESEND_AMOUNT), MAX_PRIVATESEND_AMOUNT); #endif // ENABLE_WALLET @@ -2023,7 +2027,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) scheduler.scheduleEvery(boost::bind(&CPrivateSendServer::DoMaintenance, boost::ref(privateSendServer), boost::ref(*g_connman)), 1); #ifdef ENABLE_WALLET else - scheduler.scheduleEvery(boost::bind(&CPrivateSendClient::DoMaintenance, boost::ref(privateSendClient), boost::ref(*g_connman)), 1); + scheduler.scheduleEvery(boost::bind(&CPrivateSendClientManager::DoMaintenance, boost::ref(privateSendClient), boost::ref(*g_connman)), 1); #endif // ENABLE_WALLET } diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index a8e54e18e..c7a53e55c 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -882,11 +882,22 @@ void CMasternodeMan::ProcessMasternodeConnections(CConnman& connman) //we don't care about this for regtest if(Params().NetworkIDString() == CBaseChainParams::REGTEST) return; - connman.ForEachNode(CConnman::AllNodes, [](CNode* pnode) { + std::vector vecMnInfo; // will be empty when no wallet #ifdef ENABLE_WALLET - if(pnode->fMasternode && !privateSendClient.IsMixingMasternode(pnode)) { -#else - if(pnode->fMasternode) { + privateSendClient.GetMixingMasternodesInfo(vecMnInfo); +#endif // ENABLE_WALLET + + connman.ForEachNode(CConnman::AllNodes, [&vecMnInfo](CNode* pnode) { + if (pnode->fMasternode) { +#ifdef ENABLE_WALLET + bool fFound = false; + for (const auto& mnInfo : vecMnInfo) { + if (pnode->addr == mnInfo.addr) { + fFound = true; + break; + } + } + if (fFound) return; // do NOT disconnect mixing masternodes #endif // ENABLE_WALLET LogPrintf("Closing Masternode connection: peer=%d, addr=%s\n", pnode->id, pnode->addr.ToString()); pnode->fDisconnect = true; diff --git a/src/net.h b/src/net.h index bc58b4eba..afbbc0287 100644 --- a/src/net.h +++ b/src/net.h @@ -65,7 +65,7 @@ static const int MAX_OUTBOUND_CONNECTIONS = 8; /** Maximum number of addnode outgoing nodes */ static const int MAX_ADDNODE_CONNECTIONS = 8; /** Maximum number if outgoing masternodes */ -static const int MAX_OUTBOUND_MASTERNODE_CONNECTIONS = 20; +static const int MAX_OUTBOUND_MASTERNODE_CONNECTIONS = 30; /** -listen default */ static const bool DEFAULT_LISTEN = true; /** -upnp default */ diff --git a/src/privatesend-client.cpp b/src/privatesend-client.cpp index 2cbbf4c66..b6e3b44d5 100644 --- a/src/privatesend-client.cpp +++ b/src/privatesend-client.cpp @@ -18,18 +18,22 @@ #include -CPrivateSendClient privateSendClient; +CPrivateSendClientManager privateSendClient; -void CPrivateSendClient::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) +void CPrivateSendClientManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) { if(fMasternodeMode) return; if(fLiteMode) return; // ignore all Dash related functionality if(!masternodeSync.IsBlockchainSynced()) return; - if(strCommand == NetMsgType::DSQUEUE) { - TRY_LOCK(cs_darksend, lockRecv); - if(!lockRecv) return; + if(!CheckDiskSpace()) { + ResetPool(); + fEnablePrivateSend = false; + LogPrintf("CPrivateSendClientManager::ProcessMessage -- Not enough disk space, disabling PrivateSend.\n"); + return; + } + if(strCommand == NetMsgType::DSQUEUE) { if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint("privatesend", "DSQUEUE -- peer=%d using obsolete version %i\n", pfrom->id, pfrom->nVersion); connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, @@ -40,13 +44,18 @@ void CPrivateSendClient::ProcessMessage(CNode* pfrom, const std::string& strComm CDarksendQueue dsq; vRecv >> dsq; - // process every dsq only once - for (const auto& q : vecDarksendQueue) { - if(q == dsq) { - // LogPrint("privatesend", "DSQUEUE -- %s seen\n", dsq.ToString()); - return; + { + TRY_LOCK(cs_vecqueue, lockRecv); + if(!lockRecv) return; + + // process every dsq only once + for (const auto& q : vecDarksendQueue) { + if(q == dsq) { + // LogPrint("privatesend", "DSQUEUE -- %s seen\n", dsq.ToString()); + return; + } } - } + } // cs_vecqueue LogPrint("privatesend", "DSQUEUE -- %s new\n", dsq.ToString()); @@ -64,17 +73,20 @@ void CPrivateSendClient::ProcessMessage(CNode* pfrom, const std::string& strComm // if the queue is ready, submit if we can if(dsq.fReady) { - if(!infoMixingMasternode.fInfoValid) return; - if(infoMixingMasternode.addr != infoMn.addr) { - LogPrintf("DSQUEUE -- message doesn't match current Masternode: infoMixingMasternode=%s, addr=%s\n", infoMixingMasternode.addr.ToString(), infoMn.addr.ToString()); - return; - } - - if(nState == POOL_STATE_QUEUE) { - LogPrint("privatesend", "DSQUEUE -- PrivateSend queue (%s) is ready on masternode %s\n", dsq.ToString(), infoMn.addr.ToString()); - SubmitDenominate(connman); + LOCK(cs_vecsessions); + for (auto& session : vecSessions) { + masternode_info_t mnMixing; + if (session.GetMixingMasternodeInfo(mnMixing) && mnMixing.addr == infoMn.addr && session.GetState() == POOL_STATE_QUEUE) { + LogPrint("privatesend", "DSQUEUE -- PrivateSend queue (%s) is ready on masternode %s\n", dsq.ToString(), infoMn.addr.ToString()); + session.SubmitDenominate(connman); + return; + } } } else { + LOCK(cs_vecsessions); // have to lock this first to avoid deadlocks with cs_vecqueue + TRY_LOCK(cs_vecqueue, lockRecv); + if(!lockRecv) return; + for (const auto& q : vecDarksendQueue) { if(q.masternodeOutpoint == dsq.masternodeOutpoint) { // no way same mn can send another "not yet ready" dsq this soon @@ -94,14 +106,35 @@ void CPrivateSendClient::ProcessMessage(CNode* pfrom, const std::string& strComm if(!mnodeman.AllowMixing(dsq.masternodeOutpoint)) return; LogPrint("privatesend", "DSQUEUE -- new PrivateSend queue (%s) from masternode %s\n", dsq.ToString(), infoMn.addr.ToString()); - if(infoMixingMasternode.fInfoValid && infoMixingMasternode.outpoint == dsq.masternodeOutpoint) { - dsq.fTried = true; + for (auto& session : vecSessions) { + masternode_info_t mnMixing; + if (session.GetMixingMasternodeInfo(mnMixing) && mnMixing.outpoint == dsq.masternodeOutpoint) { + dsq.fTried = true; + } } vecDarksendQueue.push_back(dsq); dsq.Relay(connman); } - } else if(strCommand == NetMsgType::DSSTATUSUPDATE) { + } else if ( + strCommand == NetMsgType::DSSTATUSUPDATE || + strCommand == NetMsgType::DSFINALTX || + strCommand == NetMsgType::DSCOMPLETE + ) { + LOCK(cs_vecsessions); + for (auto& session : vecSessions) { + session.ProcessMessage(pfrom, strCommand, vRecv, connman); + } + } +} + +void CPrivateSendClientSession::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) +{ + if(fMasternodeMode) return; + if(fLiteMode) return; // ignore all Dash related functionality + if(!masternodeSync.IsBlockchainSynced()) return; + + if(strCommand == NetMsgType::DSSTATUSUPDATE) { if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint("privatesend", "DSSTATUSUPDATE -- peer=%d using obsolete version %i\n", pfrom->id, pfrom->nVersion); @@ -209,17 +242,26 @@ void CPrivateSendClient::ProcessMessage(CNode* pfrom, const std::string& strComm } } -void CPrivateSendClient::ResetPool() +void CPrivateSendClientSession::ResetPool() { - nCachedLastSuccessBlock = 0; txMyCollateral = CMutableTransaction(); - vecMasternodesUsed.clear(); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); } -void CPrivateSendClient::SetNull() +void CPrivateSendClientManager::ResetPool() +{ + LOCK(cs_vecsessions); + nCachedLastSuccessBlock = 0; + vecMasternodesUsed.clear(); + for (auto& session : vecSessions) { + session.ResetPool(); + } + vecSessions.clear(); +} + +void CPrivateSendClientSession::SetNull() { // Client side nEntriesCount = 0; @@ -227,13 +269,13 @@ void CPrivateSendClient::SetNull() infoMixingMasternode = masternode_info_t(); pendingDsaRequest = CPendingDsaRequest(); - CPrivateSendBase::SetNull(); + CPrivateSendBaseSession::SetNull(); } // // Unlock coins after mixing fails or succeeds // -void CPrivateSendClient::UnlockCoins() +void CPrivateSendClientSession::UnlockCoins() { if (!pwalletMain) return; @@ -248,13 +290,13 @@ void CPrivateSendClient::UnlockCoins() vecOutPointLocked.clear(); } -std::string CPrivateSendClient::GetStatus() +std::string CPrivateSendClientSession::GetStatus(bool fWaitForBlock) { static int nStatusMessageProgress = 0; nStatusMessageProgress += 10; std::string strSuffix = ""; - if(WaitForAnotherBlock() || !masternodeSync.IsBlockchainSynced()) + if(fWaitForBlock || !masternodeSync.IsBlockchainSynced()) return strAutoDenomResult; switch(nState) { @@ -297,25 +339,55 @@ std::string CPrivateSendClient::GetStatus() } } -bool CPrivateSendClient::GetMixingMasternodeInfo(masternode_info_t& mnInfoRet) +std::string CPrivateSendClientManager::GetStatuses() +{ + LOCK(cs_vecsessions); + std::string strStatus; + bool fWaitForBlock = WaitForAnotherBlock(); + + for (auto& session : vecSessions) { + strStatus += session.GetStatus(fWaitForBlock) + "; "; + } + return strStatus; +} + +std::string CPrivateSendClientManager::GetSessionDenoms() +{ + LOCK(cs_vecsessions); + std::string strSessionDenoms; + + for (auto& session : vecSessions) { + strSessionDenoms += (session.nSessionDenom ? CPrivateSend::GetDenominationsToString(session.nSessionDenom) : "N/A") + "; "; + } + return strSessionDenoms.empty() ? "N/A" : strSessionDenoms; +} + +bool CPrivateSendClientSession::GetMixingMasternodeInfo(masternode_info_t& mnInfoRet) const { mnInfoRet = infoMixingMasternode.fInfoValid ? infoMixingMasternode : masternode_info_t(); return infoMixingMasternode.fInfoValid; } -bool CPrivateSendClient::IsMixingMasternode(const CNode* pnode) +bool CPrivateSendClientManager::GetMixingMasternodesInfo(std::vector& vecMnInfoRet) const { - return infoMixingMasternode.fInfoValid && pnode->addr == infoMixingMasternode.addr; + LOCK(cs_vecsessions); + for (const auto& session : vecSessions) { + masternode_info_t mnInfo; + if (session.GetMixingMasternodeInfo(mnInfo)) { + vecMnInfoRet.push_back(mnInfo); + } + } + return !vecMnInfoRet.empty(); } // // Check the mixing progress and send client updates if a Masternode // -void CPrivateSendClient::CheckPool() +void CPrivateSendClientSession::CheckPool() { // reset if we're here for 10 seconds if((nState == POOL_STATE_ERROR || nState == POOL_STATE_SUCCESS) && GetTime() - nTimeLastSuccessfulStep >= 10) { - LogPrint("privatesend", "CPrivateSendClient::CheckPool -- timeout, RESETTING\n"); + LogPrint("privatesend", "CPrivateSendClientSession::CheckPool -- timeout, RESETTING\n"); UnlockCoins(); if (nState == POOL_STATE_ERROR) { keyHolderStorage.ReturnAll(); @@ -327,24 +399,20 @@ void CPrivateSendClient::CheckPool() } // -// Check for various timeouts (queue objects, mixing, etc) +// Check session timeouts // -void CPrivateSendClient::CheckTimeout() +bool CPrivateSendClientSession::CheckTimeout() { - if(fMasternodeMode) return; - - CheckQueue(); - - if(!fEnablePrivateSend) return; + if(fMasternodeMode) return false; // catching hanging sessions switch(nState) { case POOL_STATE_ERROR: - LogPrint("privatesend", "CPrivateSendClient::CheckTimeout -- Pool error -- Running CheckPool\n"); + LogPrint("privatesend", "CPrivateSendClientSession::CheckTimeout -- Pool error -- Running CheckPool\n"); CheckPool(); break; case POOL_STATE_SUCCESS: - LogPrint("privatesend", "CPrivateSendClient::CheckTimeout -- Pool success -- Running CheckPool\n"); + LogPrint("privatesend", "CPrivateSendClientSession::CheckTimeout -- Pool success -- Running CheckPool\n"); CheckPool(); break; default: @@ -355,14 +423,34 @@ void CPrivateSendClient::CheckTimeout() int nTimeout = (nState == POOL_STATE_SIGNING) ? PRIVATESEND_SIGNING_TIMEOUT : PRIVATESEND_QUEUE_TIMEOUT; bool fTimeout = GetTime() - nTimeLastSuccessfulStep >= nTimeout + nLagTime; - if(nState != POOL_STATE_IDLE && fTimeout) { - LogPrint("privatesend", "CPrivateSendClient::CheckTimeout -- %s timed out (%ds) -- resetting\n", - (nState == POOL_STATE_SIGNING) ? "Signing" : "Session", nTimeout); - UnlockCoins(); - keyHolderStorage.ReturnAll(); - SetNull(); - SetState(POOL_STATE_ERROR); - strLastMessage = _("Session timed out."); + if(nState == POOL_STATE_IDLE || !fTimeout) + return false; + + LogPrint("privatesend", "CPrivateSendClientSession::CheckTimeout -- %s timed out (%ds) -- resetting\n", + (nState == POOL_STATE_SIGNING) ? "Signing" : "Session", nTimeout); + UnlockCoins(); + keyHolderStorage.ReturnAll(); + SetNull(); + SetState(POOL_STATE_ERROR); + + return true; +} + +// +// Check all queues and sessions for timeouts +// +void CPrivateSendClientManager::CheckTimeout() +{ + if(fMasternodeMode) return; + if(!fEnablePrivateSend) return; + + CheckQueue(); + + LOCK(cs_vecsessions); + for (auto& session : vecSessions) { + if (session.CheckTimeout()) { + strAutoDenomResult = _("Session timed out."); + } } } @@ -370,10 +458,10 @@ void CPrivateSendClient::CheckTimeout() // Execute a mixing denomination via a Masternode. // This is only ran from clients // -bool CPrivateSendClient::SendDenominate(const std::vector& vecTxDSIn, const std::vector& vecTxOut, CConnman& connman) +bool CPrivateSendClientSession::SendDenominate(const std::vector& vecTxDSIn, const std::vector& vecTxOut, CConnman& connman) { if(fMasternodeMode) { - LogPrintf("CPrivateSendClient::SendDenominate -- PrivateSend from a Masternode is not supported currently.\n"); + LogPrintf("CPrivateSendClientSession::SendDenominate -- PrivateSend from a Masternode is not supported currently.\n"); return false; } @@ -391,7 +479,7 @@ bool CPrivateSendClient::SendDenominate(const std::vector& vecTxDSIn, c // we should already be connected to a Masternode if(!nSessionID) { - LogPrintf("CPrivateSendClient::SendDenominate -- No Masternode has been selected yet.\n"); + LogPrintf("CPrivateSendClientSession::SendDenominate -- No Masternode has been selected yet.\n"); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); @@ -402,15 +490,14 @@ bool CPrivateSendClient::SendDenominate(const std::vector& vecTxDSIn, c UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); - fEnablePrivateSend = false; - LogPrintf("CPrivateSendClient::SendDenominate -- Not enough disk space, disabling PrivateSend.\n"); + LogPrintf("CPrivateSendClientSession::SendDenominate -- Not enough disk space.\n"); return false; } SetState(POOL_STATE_ACCEPTING_ENTRIES); strLastMessage = ""; - LogPrintf("CPrivateSendClient::SendDenominate -- Added transaction to pool.\n"); + LogPrintf("CPrivateSendClientSession::SendDenominate -- Added transaction to pool.\n"); { // construct a pseudo tx, for debugging purpuses only @@ -418,16 +505,16 @@ bool CPrivateSendClient::SendDenominate(const std::vector& vecTxDSIn, c CMutableTransaction tx; for (const auto& txdsin : vecTxDSIn) { - LogPrint("privatesend", "CPrivateSendClient::SendDenominate -- txdsin=%s\n", txdsin.ToString()); + LogPrint("privatesend", "CPrivateSendClientSession::SendDenominate -- txdsin=%s\n", txdsin.ToString()); tx.vin.push_back(txdsin); } for (const CTxOut& txout : vecTxOut) { - LogPrint("privatesend", "CPrivateSendClient::SendDenominate -- txout=%s\n", txout.ToString()); + LogPrint("privatesend", "CPrivateSendClientSession::SendDenominate -- txout=%s\n", txout.ToString()); tx.vout.push_back(txout); } - LogPrintf("CPrivateSendClient::SendDenominate -- Submitting partial tx %s", tx.ToString()); + LogPrintf("CPrivateSendClientSession::SendDenominate -- Submitting partial tx %s", tx.ToString()); } // store our entry for later use @@ -440,7 +527,7 @@ bool CPrivateSendClient::SendDenominate(const std::vector& vecTxDSIn, c } // Incoming message from Masternode updating the progress of mixing -bool CPrivateSendClient::CheckPoolStateUpdate(PoolState nStateNew, int nEntriesCountNew, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID, int nSessionIDNew) +bool CPrivateSendClientSession::CheckPoolStateUpdate(PoolState nStateNew, int nEntriesCountNew, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID, int nSessionIDNew) { if(fMasternodeMode) return false; @@ -451,7 +538,7 @@ bool CPrivateSendClient::CheckPoolStateUpdate(PoolState nStateNew, int nEntriesC // if rejected at any state if(nStatusUpdate == STATUS_REJECTED) { - LogPrintf("CPrivateSendClient::CheckPoolStateUpdate -- entry is rejected by Masternode\n"); + LogPrintf("CPrivateSendClientSession::CheckPoolStateUpdate -- entry is rejected by Masternode\n"); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); @@ -465,14 +552,14 @@ bool CPrivateSendClient::CheckPoolStateUpdate(PoolState nStateNew, int nEntriesC // new session id should be set only in POOL_STATE_QUEUE state nSessionID = nSessionIDNew; nTimeLastSuccessfulStep = GetTime(); - LogPrintf("CPrivateSendClient::CheckPoolStateUpdate -- set nSessionID to %d\n", nSessionID); + LogPrintf("CPrivateSendClientSession::CheckPoolStateUpdate -- set nSessionID to %d\n", nSessionID); return true; } else if(nStateNew == POOL_STATE_ACCEPTING_ENTRIES && nEntriesCount != nEntriesCountNew) { nEntriesCount = nEntriesCountNew; nTimeLastSuccessfulStep = GetTime(); fLastEntryAccepted = true; - LogPrintf("CPrivateSendClient::CheckPoolStateUpdate -- new entry accepted!\n"); + LogPrintf("CPrivateSendClientSession::CheckPoolStateUpdate -- new entry accepted!\n"); return true; } } @@ -486,21 +573,22 @@ bool CPrivateSendClient::CheckPoolStateUpdate(PoolState nStateNew, int nEntriesC // check it to make sure it's what we want, then sign it if we agree. // If we refuse to sign, it's possible we'll be charged collateral // -bool CPrivateSendClient::SignFinalTransaction(const CTransaction& finalTransactionNew, CNode* pnode, CConnman& connman) +bool CPrivateSendClientSession::SignFinalTransaction(const CTransaction& finalTransactionNew, CNode* pnode, CConnman& connman) { if (!pwalletMain) return false; if(fMasternodeMode || pnode == nullptr) return false; + if(!infoMixingMasternode.fInfoValid) return false; finalMutableTransaction = finalTransactionNew; - LogPrintf("CPrivateSendClient::SignFinalTransaction -- finalMutableTransaction=%s", finalMutableTransaction.ToString()); + LogPrintf("CPrivateSendClientSession::SignFinalTransaction -- finalMutableTransaction=%s", finalMutableTransaction.ToString()); // Make sure it's BIP69 compliant sort(finalMutableTransaction.vin.begin(), finalMutableTransaction.vin.end(), CompareInputBIP69()); sort(finalMutableTransaction.vout.begin(), finalMutableTransaction.vout.end(), CompareOutputBIP69()); if(finalMutableTransaction.GetHash() != finalTransactionNew.GetHash()) { - LogPrintf("CPrivateSendClient::SignFinalTransaction -- WARNING! Masternode %s is not BIP69 compliant!\n", infoMixingMasternode.outpoint.ToStringShort()); + LogPrintf("CPrivateSendClientSession::SignFinalTransaction -- WARNING! Masternode %s is not BIP69 compliant!\n", infoMixingMasternode.outpoint.ToStringShort()); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); @@ -546,7 +634,7 @@ bool CPrivateSendClient::SignFinalTransaction(const CTransaction& finalTransacti if(nFoundOutputsCount < nTargetOuputsCount || nValue1 != nValue2) { // in this case, something went wrong and we'll refuse to sign. It's possible we'll be charged collateral. But that's // better then signing if the transaction doesn't look like what we wanted. - LogPrintf("CPrivateSendClient::SignFinalTransaction -- My entries are not correct! Refusing to sign: nFoundOutputsCount: %d, nTargetOuputsCount: %d\n", nFoundOutputsCount, nTargetOuputsCount); + LogPrintf("CPrivateSendClientSession::SignFinalTransaction -- My entries are not correct! Refusing to sign: nFoundOutputsCount: %d, nTargetOuputsCount: %d\n", nFoundOutputsCount, nTargetOuputsCount); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); @@ -556,20 +644,20 @@ bool CPrivateSendClient::SignFinalTransaction(const CTransaction& finalTransacti const CKeyStore& keystore = *pwalletMain; - LogPrint("privatesend", "CPrivateSendClient::SignFinalTransaction -- Signing my input %i\n", nMyInputIndex); + LogPrint("privatesend", "CPrivateSendClientSession::SignFinalTransaction -- Signing my input %i\n", nMyInputIndex); if(!SignSignature(keystore, prevPubKey, finalMutableTransaction, nMyInputIndex, int(SIGHASH_ALL|SIGHASH_ANYONECANPAY))) { // changes scriptSig - LogPrint("privatesend", "CPrivateSendClient::SignFinalTransaction -- Unable to sign my own transaction!\n"); + LogPrint("privatesend", "CPrivateSendClientSession::SignFinalTransaction -- Unable to sign my own transaction!\n"); // not sure what to do here, it will timeout...? } sigs.push_back(finalMutableTransaction.vin[nMyInputIndex]); - LogPrint("privatesend", "CPrivateSendClient::SignFinalTransaction -- nMyInputIndex: %d, sigs.size(): %d, scriptSig=%s\n", nMyInputIndex, (int)sigs.size(), ScriptToAsmStr(finalMutableTransaction.vin[nMyInputIndex].scriptSig)); + LogPrint("privatesend", "CPrivateSendClientSession::SignFinalTransaction -- nMyInputIndex: %d, sigs.size(): %d, scriptSig=%s\n", nMyInputIndex, (int)sigs.size(), ScriptToAsmStr(finalMutableTransaction.vin[nMyInputIndex].scriptSig)); } } } if(sigs.empty()) { - LogPrintf("CPrivateSendClient::SignFinalTransaction -- can't sign anything!\n"); + LogPrintf("CPrivateSendClientSession::SignFinalTransaction -- can't sign anything!\n"); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); @@ -578,7 +666,7 @@ bool CPrivateSendClient::SignFinalTransaction(const CTransaction& finalTransacti } // push all of our signatures to the Masternode - LogPrintf("CPrivateSendClient::SignFinalTransaction -- pushing sigs to the masternode, finalMutableTransaction=%s", finalMutableTransaction.ToString()); + LogPrintf("CPrivateSendClientSession::SignFinalTransaction -- pushing sigs to the masternode, finalMutableTransaction=%s", finalMutableTransaction.ToString()); CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSSIGNFINALTX, sigs)); SetState(POOL_STATE_SIGNING); @@ -588,13 +676,13 @@ bool CPrivateSendClient::SignFinalTransaction(const CTransaction& finalTransacti } // mixing transaction was completed (failed or successful) -void CPrivateSendClient::CompletedTransaction(PoolMessage nMessageID) +void CPrivateSendClientSession::CompletedTransaction(PoolMessage nMessageID) { if(fMasternodeMode) return; if(nMessageID == MSG_SUCCESS) { LogPrintf("CompletedTransaction -- success\n"); - nCachedLastSuccessBlock = nCachedBlockHeight; + privateSendClient.UpdatedSuccessBlock(); keyHolderStorage.KeepAll(); } else { LogPrintf("CompletedTransaction -- error\n"); @@ -605,12 +693,23 @@ void CPrivateSendClient::CompletedTransaction(PoolMessage nMessageID) strLastMessage = CPrivateSend::GetMessageByID(nMessageID); } -bool CPrivateSendClient::IsDenomSkipped(CAmount nDenomValue) +void CPrivateSendClientManager::UpdatedSuccessBlock() +{ + if(fMasternodeMode) return; + nCachedLastSuccessBlock = nCachedBlockHeight; +} + +bool CPrivateSendClientManager::IsDenomSkipped(const CAmount& nDenomValue) { return std::find(vecDenominationsSkipped.begin(), vecDenominationsSkipped.end(), nDenomValue) != vecDenominationsSkipped.end(); } -bool CPrivateSendClient::WaitForAnotherBlock() +void CPrivateSendClientManager::AddSkippedDenom(const CAmount& nDenomValue) +{ + vecDenominationsSkipped.push_back(nDenomValue); +} + +bool CPrivateSendClientManager::WaitForAnotherBlock() { if(!masternodeSync.IsMasternodeListSynced()) return true; @@ -621,10 +720,10 @@ bool CPrivateSendClient::WaitForAnotherBlock() return nCachedBlockHeight - nCachedLastSuccessBlock < nMinBlocksToWait; } -bool CPrivateSendClient::CheckAutomaticBackup() +bool CPrivateSendClientManager::CheckAutomaticBackup() { if (!pwalletMain) { - LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- Wallet is not initialized, no mixing available.\n"); + LogPrint("privatesend", "CPrivateSendClientManager::CheckAutomaticBackup -- Wallet is not initialized, no mixing available.\n"); strAutoDenomResult = _("Wallet is not initialized") + ", " + _("no mixing available."); fEnablePrivateSend = false; // no mixing return false; @@ -632,7 +731,7 @@ bool CPrivateSendClient::CheckAutomaticBackup() switch(nWalletBackups) { case 0: - LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- Automatic backups disabled, no mixing available.\n"); + LogPrint("privatesend", "CPrivateSendClientManager::CheckAutomaticBackup -- Automatic backups disabled, no mixing available.\n"); strAutoDenomResult = _("Automatic backups disabled") + ", " + _("no mixing available."); fEnablePrivateSend = false; // stop mixing pwalletMain->nKeysLeftSinceAutoBackup = 0; // no backup, no "keys since last backup" @@ -641,43 +740,43 @@ bool CPrivateSendClient::CheckAutomaticBackup() // Automatic backup failed, nothing else we can do until user fixes the issue manually. // There is no way to bring user attention in daemon mode so we just update status and // keep spamming if debug is on. - LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- ERROR! Failed to create automatic backup.\n"); + LogPrint("privatesend", "CPrivateSendClientManager::CheckAutomaticBackup -- ERROR! Failed to create automatic backup.\n"); strAutoDenomResult = _("ERROR! Failed to create automatic backup") + ", " + _("see debug.log for details."); return false; case -2: // We were able to create automatic backup but keypool was not replenished because wallet is locked. // There is no way to bring user attention in daemon mode so we just update status and // keep spamming if debug is on. - LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- WARNING! Failed to create replenish keypool, please unlock your wallet to do so.\n"); + LogPrint("privatesend", "CPrivateSendClientManager::CheckAutomaticBackup -- WARNING! Failed to create replenish keypool, please unlock your wallet to do so.\n"); strAutoDenomResult = _("WARNING! Failed to replenish keypool, please unlock your wallet to do so.") + ", " + _("see debug.log for details."); return false; } if(pwalletMain->nKeysLeftSinceAutoBackup < PRIVATESEND_KEYS_THRESHOLD_STOP) { // We should never get here via mixing itself but probably smth else is still actively using keypool - LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- Very low number of keys left: %d, no mixing available.\n", pwalletMain->nKeysLeftSinceAutoBackup); + LogPrint("privatesend", "CPrivateSendClientManager::CheckAutomaticBackup -- Very low number of keys left: %d, no mixing available.\n", pwalletMain->nKeysLeftSinceAutoBackup); strAutoDenomResult = strprintf(_("Very low number of keys left: %d") + ", " + _("no mixing available."), pwalletMain->nKeysLeftSinceAutoBackup); // It's getting really dangerous, stop mixing fEnablePrivateSend = false; return false; } else if(pwalletMain->nKeysLeftSinceAutoBackup < PRIVATESEND_KEYS_THRESHOLD_WARNING) { // Low number of keys left but it's still more or less safe to continue - LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- Very low number of keys left: %d\n", pwalletMain->nKeysLeftSinceAutoBackup); + LogPrint("privatesend", "CPrivateSendClientManager::CheckAutomaticBackup -- Very low number of keys left: %d\n", pwalletMain->nKeysLeftSinceAutoBackup); strAutoDenomResult = strprintf(_("Very low number of keys left: %d"), pwalletMain->nKeysLeftSinceAutoBackup); if(fCreateAutoBackups) { - LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- Trying to create new backup.\n"); + LogPrint("privatesend", "CPrivateSendClientManager::CheckAutomaticBackup -- Trying to create new backup.\n"); std::string warningString; std::string errorString; if(!AutoBackupWallet(pwalletMain, "", warningString, errorString)) { if(!warningString.empty()) { // There were some issues saving backup but yet more or less safe to continue - LogPrintf("CPrivateSendClient::CheckAutomaticBackup -- WARNING! Something went wrong on automatic backup: %s\n", warningString); + LogPrintf("CPrivateSendClientManager::CheckAutomaticBackup -- WARNING! Something went wrong on automatic backup: %s\n", warningString); } if(!errorString.empty()) { // Things are really broken - LogPrintf("CPrivateSendClient::CheckAutomaticBackup -- ERROR! Failed to create automatic backup: %s\n", errorString); + LogPrintf("CPrivateSendClientManager::CheckAutomaticBackup -- ERROR! Failed to create automatic backup: %s\n", errorString); strAutoDenomResult = strprintf(_("ERROR! Failed to create automatic backup") + ": %s", errorString); return false; } @@ -688,7 +787,7 @@ bool CPrivateSendClient::CheckAutomaticBackup() } } - LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- Keys left since latest backup: %d\n", pwalletMain->nKeysLeftSinceAutoBackup); + LogPrint("privatesend", "CPrivateSendClientManager::CheckAutomaticBackup -- Keys left since latest backup: %d\n", pwalletMain->nKeysLeftSinceAutoBackup); return true; } @@ -696,10 +795,9 @@ bool CPrivateSendClient::CheckAutomaticBackup() // // Passively run mixing in the background to anonymize funds based on the given configuration. // -bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun) +bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool fDryRun) { if(fMasternodeMode) return false; // no client-side mixing on masternodes - if(!fEnablePrivateSend) return false; if(nState != POOL_STATE_IDLE) return false; if(!masternodeSync.IsMasternodeListSynced()) { @@ -712,14 +810,17 @@ bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun return false; } + CAmount nBalanceNeedsAnonymized; + CAmount nValueMin = CPrivateSend::GetSmallestDenomination(); + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + if(!fDryRun && pwalletMain->IsLocked(true)) { strAutoDenomResult = _("Wallet is locked."); return false; } - if(!CheckAutomaticBackup()) - return false; - if(GetEntriesCount() > 0) { strAutoDenomResult = _("Mixing in progress..."); return false; @@ -731,20 +832,12 @@ bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun return false; } - if(WaitForAnotherBlock()) { - LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- Last successful PrivateSend action was too recent\n"); - strAutoDenomResult = _("Last successful PrivateSend action was too recent."); - return false; - } - if(mnodeman.size() == 0) { - LogPrint("privatesend", "CPrivateSendClient::DoAutomaticDenominating -- No Masternodes detected\n"); + LogPrint("privatesend", "CPrivateSendClientSession::DoAutomaticDenominating -- No Masternodes detected\n"); strAutoDenomResult = _("No Masternodes detected."); return false; } - CAmount nValueMin = CPrivateSend::GetSmallestDenomination(); - // if there are no confirmed DS collateral inputs yet if(!pwalletMain->HasCollateralInputs()) { // should have some additional amount for them @@ -752,11 +845,11 @@ bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun } // including denoms but applying some restrictions - CAmount nBalanceNeedsAnonymized = pwalletMain->GetNeedsToBeAnonymizedBalance(nValueMin); + nBalanceNeedsAnonymized = pwalletMain->GetNeedsToBeAnonymizedBalance(nValueMin); // anonymizable balance is way too small if(nBalanceNeedsAnonymized < nValueMin) { - LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- Not enough funds to anonymize\n"); + LogPrintf("CPrivateSendClientSession::DoAutomaticDenominating -- Not enough funds to anonymize\n"); strAutoDenomResult = _("Not enough funds to anonymize."); return false; } @@ -768,7 +861,7 @@ bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun CAmount nBalanceDenominatedUnconf = pwalletMain->GetDenominatedBalance(true); CAmount nBalanceDenominated = nBalanceDenominatedConf + nBalanceDenominatedUnconf; - LogPrint("privatesend", "CPrivateSendClient::DoAutomaticDenominating -- nValueMin: %f, nBalanceNeedsAnonymized: %f, nBalanceAnonimizableNonDenom: %f, nBalanceDenominatedConf: %f, nBalanceDenominatedUnconf: %f, nBalanceDenominated: %f\n", + LogPrint("privatesend", "CPrivateSendClientSession::DoAutomaticDenominating -- nValueMin: %f, nBalanceNeedsAnonymized: %f, nBalanceAnonimizableNonDenom: %f, nBalanceDenominatedConf: %f, nBalanceDenominatedUnconf: %f, nBalanceDenominated: %f\n", (float)nValueMin/COIN, (float)nBalanceNeedsAnonymized/COIN, (float)nBalanceAnonimizableNonDenom/COIN, @@ -781,7 +874,7 @@ bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun // Check if we have should create more denominated inputs i.e. // there are funds to denominate and denominated balance does not exceed // max amount to mix yet. - if(nBalanceAnonimizableNonDenom >= nValueMin + CPrivateSend::GetCollateralAmount() && nBalanceDenominated < nPrivateSendAmount*COIN) + if(nBalanceAnonimizableNonDenom >= nValueMin + CPrivateSend::GetCollateralAmount() && nBalanceDenominated < privateSendClient.nPrivateSendAmount*COIN) return CreateDenominated(connman); //check if we have the collateral sized inputs @@ -800,8 +893,8 @@ bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun SetNull(); // should be no unconfirmed denoms in non-multi-session mode - if(!fPrivateSendMultiSession && nBalanceDenominatedUnconf > 0) { - LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- Found unconfirmed denominated outputs, will wait till they confirm to continue.\n"); + if(!privateSendClient.fPrivateSendMultiSession && nBalanceDenominatedUnconf > 0) { + LogPrintf("CPrivateSendClientSession::DoAutomaticDenominating -- Found unconfirmed denominated outputs, will wait till they confirm to continue.\n"); strAutoDenomResult = _("Found unconfirmed denominated outputs, will wait till they confirm to continue."); return false; } @@ -810,18 +903,54 @@ bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun std::string strReason; if(txMyCollateral == CMutableTransaction()) { if(!pwalletMain->CreateCollateralTransaction(txMyCollateral, strReason)) { - LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- create collateral error:%s\n", strReason); + LogPrintf("CPrivateSendClientSession::DoAutomaticDenominating -- create collateral error:%s\n", strReason); return false; } } else { if(!CPrivateSend::IsCollateralValid(txMyCollateral)) { - LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- invalid collateral, recreating...\n"); + LogPrintf("CPrivateSendClientSession::DoAutomaticDenominating -- invalid collateral, recreating...\n"); if(!pwalletMain->CreateCollateralTransaction(txMyCollateral, strReason)) { - LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- create collateral error: %s\n", strReason); + LogPrintf("CPrivateSendClientSession::DoAutomaticDenominating -- create collateral error: %s\n", strReason); return false; } } } + } // LOCK2(cs_main, pwalletMain->cs_wallet); + + bool fUseQueue = GetRandInt(100) > 33; + // don't use the queues all of the time for mixing unless we are a liquidity provider + if((privateSendClient.nLiquidityProvider || fUseQueue) && JoinExistingQueue(nBalanceNeedsAnonymized, connman)) + return true; + + // do not initiate queue if we are a liquidity provider to avoid useless inter-mixing + if(privateSendClient.nLiquidityProvider) return false; + + if(StartNewQueue(nValueMin, nBalanceNeedsAnonymized, connman)) + return true; + + strAutoDenomResult = _("No compatible Masternode found."); + return false; +} + +bool CPrivateSendClientManager::DoAutomaticDenominating(CConnman& connman, bool fDryRun) +{ + if (fMasternodeMode) return false; // no client-side mixing on masternodes + if (!fEnablePrivateSend) return false; + + if (!masternodeSync.IsMasternodeListSynced()) { + strAutoDenomResult = _("Can't mix while sync in progress."); + return false; + } + + if (!pwalletMain) { + strAutoDenomResult = _("Wallet is not initialized"); + return false; + } + + if (!fDryRun && pwalletMain->IsLocked(true)) { + strAutoDenomResult = _("Wallet is locked."); + return false; + } int nMnCountEnabled = mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION); @@ -835,38 +964,49 @@ bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun LogPrint("privatesend", " vecMasternodesUsed: new size: %d, threshold: %d\n", (int)vecMasternodesUsed.size(), nThreshold_high); } - bool fUseQueue = GetRandInt(100) > 33; - // don't use the queues all of the time for mixing unless we are a liquidity provider - if((nLiquidityProvider || fUseQueue) && JoinExistingQueue(nBalanceNeedsAnonymized, connman)) - return true; + LOCK(cs_vecsessions); + bool fResult = true; + if ((int)vecSessions.size() < nPrivateSendSessions) { + vecSessions.emplace_back(); + } + for (auto& session : vecSessions) { + if (!CheckAutomaticBackup()) + return false; - // do not initiate queue if we are a liquidity provider to avoid useless inter-mixing - if(nLiquidityProvider) return false; + if (WaitForAnotherBlock()) { + LogPrintf("CPrivateSendClientManager::DoAutomaticDenominating -- Last successful PrivateSend action was too recent\n"); + strAutoDenomResult = _("Last successful PrivateSend action was too recent."); + return false; + } - if(StartNewQueue(nValueMin, nBalanceNeedsAnonymized, connman)) - return true; + fResult &= session.DoAutomaticDenominating(connman, fDryRun); + } - strAutoDenomResult = _("No compatible Masternode found."); - return false; + return fResult; } -bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman) +void CPrivateSendClientManager::AddUsedMasternode(const COutPoint& outpointMn) +{ + vecMasternodesUsed.push_back(outpointMn); +} + +masternode_info_t CPrivateSendClientManager::GetNotUsedMasternode() +{ + return mnodeman.FindRandomNotInVec(vecMasternodesUsed, MIN_PRIVATESEND_PEER_PROTO_VERSION); +} + +bool CPrivateSendClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman) { if (!pwalletMain) return false; std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); // Look through the queues and see if anything matches - for (auto& dsq : vecDarksendQueue) { - // only try each queue once - if(dsq.fTried) continue; - dsq.fTried = true; - - if(dsq.IsExpired()) continue; - + CDarksendQueue dsq; + while (privateSendClient.GetQueueItemAndTry(dsq)) { masternode_info_t infoMn; if(!mnodeman.GetMasternodeInfo(dsq.masternodeOutpoint, infoMn)) { - LogPrintf("CPrivateSendClient::JoinExistingQueue -- dsq masternode is not in masternode list, masternode=%s\n", dsq.masternodeOutpoint.ToStringShort()); + LogPrintf("CPrivateSendClientSession::JoinExistingQueue -- dsq masternode is not in masternode list, masternode=%s\n", dsq.masternodeOutpoint.ToStringShort()); continue; } @@ -874,7 +1014,7 @@ bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CCon // skip next mn payments winners if (mnpayments.IsScheduled(infoMn, 0)) { - LogPrintf("CPrivateSendClient::JoinExistingQueue -- skipping winner, masternode=%s\n", infoMn.outpoint.ToStringShort()); + LogPrintf("CPrivateSendClientSession::JoinExistingQueue -- skipping winner, masternode=%s\n", infoMn.outpoint.ToStringShort()); continue; } @@ -888,7 +1028,7 @@ bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CCon // in order for dsq to get into vecDarksendQueue, so we should be safe to mix already, // no need for additional verification here - LogPrint("privatesend", "CPrivateSendClient::JoinExistingQueue -- found valid queue: %s\n", dsq.ToString()); + LogPrint("privatesend", "CPrivateSendClientSession::JoinExistingQueue -- found valid queue: %s\n", dsq.ToString()); CAmount nValueInTmp = 0; std::vector vecTxDSInTmp; @@ -903,15 +1043,15 @@ bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CCon nMinAmount = nMaxAmount = dsq.nInputCount * vecStandardDenoms[vecBits.front()]; } // Try to match their denominations if possible, select exact number of denominations - if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, nMinAmount, nMaxAmount, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, nPrivateSendRounds)) { - LogPrintf("CPrivateSendClient::JoinExistingQueue -- Couldn't match %d denominations %d %d (%s)\n", dsq.nInputCount, vecBits.front(), dsq.nDenom, CPrivateSend::GetDenominationsToString(dsq.nDenom)); + if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, nMinAmount, nMaxAmount, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, privateSendClient.nPrivateSendRounds)) { + LogPrintf("CPrivateSendClientSession::JoinExistingQueue -- Couldn't match %d denominations %d %d (%s)\n", dsq.nInputCount, vecBits.front(), dsq.nDenom, CPrivateSend::GetDenominationsToString(dsq.nDenom)); continue; } - vecMasternodesUsed.push_back(dsq.masternodeOutpoint); + privateSendClient.AddUsedMasternode(dsq.masternodeOutpoint); if (connman.IsMasternodeOrDisconnectRequested(infoMn.addr)) { - LogPrintf("CPrivateSendClient::JoinExistingQueue -- skipping masternode connection, addr=%s\n", infoMn.addr.ToString()); + LogPrintf("CPrivateSendClientSession::JoinExistingQueue -- skipping masternode connection, addr=%s\n", infoMn.addr.ToString()); continue; } @@ -923,7 +1063,7 @@ bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CCon // TODO: add new state POOL_STATE_CONNECTING and bump MIN_PRIVATESEND_PEER_PROTO_VERSION SetState(POOL_STATE_QUEUE); nTimeLastSuccessfulStep = GetTime(); - LogPrintf("CPrivateSendClient::JoinExistingQueue -- pending connection (from queue): nSessionDenom: %d (%s), nSessionInputCount: %d, addr=%s\n", + LogPrintf("CPrivateSendClientSession::JoinExistingQueue -- pending connection (from queue): nSessionDenom: %d (%s), nSessionInputCount: %d, addr=%s\n", nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), nSessionInputCount, infoMn.addr.ToString()); strAutoDenomResult = _("Trying to connect..."); return true; @@ -932,7 +1072,7 @@ bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CCon return false; } -bool CPrivateSendClient::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsAnonymized, CConnman& connman) +bool CPrivateSendClientSession::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsAnonymized, CConnman& connman) { if (!pwalletMain) return false; @@ -942,34 +1082,34 @@ bool CPrivateSendClient::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsA // ** find the coins we'll use std::vector vecTxIn; CAmount nValueInTmp = 0; - if(!pwalletMain->SelectCoinsDark(nValueMin, nBalanceNeedsAnonymized, vecTxIn, nValueInTmp, 0, nPrivateSendRounds)) { + if(!pwalletMain->SelectCoinsDark(nValueMin, nBalanceNeedsAnonymized, vecTxIn, nValueInTmp, 0, privateSendClient.nPrivateSendRounds)) { // this should never happen - LogPrintf("CPrivateSendClient::StartNewQueue -- Can't mix: no compatible inputs found!\n"); + LogPrintf("CPrivateSendClientSession::StartNewQueue -- Can't mix: no compatible inputs found!\n"); strAutoDenomResult = _("Can't mix: no compatible inputs found!"); return false; } // otherwise, try one randomly while(nTries < 10) { - masternode_info_t infoMn = mnodeman.FindRandomNotInVec(vecMasternodesUsed, MIN_PRIVATESEND_PEER_PROTO_VERSION); + masternode_info_t infoMn = privateSendClient.GetNotUsedMasternode(); if(!infoMn.fInfoValid) { - LogPrintf("CPrivateSendClient::StartNewQueue -- Can't find random masternode!\n"); + LogPrintf("CPrivateSendClientSession::StartNewQueue -- Can't find random masternode!\n"); strAutoDenomResult = _("Can't find random Masternode."); return false; } // skip next mn payments winners if (mnpayments.IsScheduled(infoMn, 0)) { - LogPrintf("CPrivateSendClient::StartNewQueue -- skipping winner, masternode=%s\n", infoMn.outpoint.ToStringShort()); + LogPrintf("CPrivateSendClientSession::StartNewQueue -- skipping winner, masternode=%s\n", infoMn.outpoint.ToStringShort()); nTries++; continue; } - vecMasternodesUsed.push_back(infoMn.outpoint); + privateSendClient.AddUsedMasternode(infoMn.outpoint); if(infoMn.nLastDsq != 0 && infoMn.nLastDsq + nMnCountEnabled/5 > mnodeman.nDsqCount) { - LogPrintf("CPrivateSendClient::StartNewQueue -- Too early to mix on this masternode!" + LogPrintf("CPrivateSendClientSession::StartNewQueue -- Too early to mix on this masternode!" " masternode=%s addr=%s nLastDsq=%d CountEnabled/5=%d nDsqCount=%d\n", infoMn.outpoint.ToStringShort(), infoMn.addr.ToString(), infoMn.nLastDsq, nMnCountEnabled/5, mnodeman.nDsqCount); @@ -978,12 +1118,12 @@ bool CPrivateSendClient::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsA } if (connman.IsMasternodeOrDisconnectRequested(infoMn.addr)) { - LogPrintf("CPrivateSendClient::StartNewQueue -- skipping masternode connection, addr=%s\n", infoMn.addr.ToString()); + LogPrintf("CPrivateSendClientSession::StartNewQueue -- skipping masternode connection, addr=%s\n", infoMn.addr.ToString()); nTries++; continue; } - LogPrintf("CPrivateSendClient::StartNewQueue -- attempt %d connection to Masternode %s\n", nTries, infoMn.addr.ToString()); + LogPrintf("CPrivateSendClientSession::StartNewQueue -- attempt %d connection to Masternode %s\n", nTries, infoMn.addr.ToString()); std::vector vecAmounts; pwalletMain->ConvertList(vecTxIn, vecAmounts); @@ -1004,7 +1144,7 @@ bool CPrivateSendClient::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsA std::vector vCoinsTmp; std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); - bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecStandardDenoms[vecBits.front()], vecStandardDenoms[vecBits.front()] * PRIVATESEND_ENTRY_MAX_SIZE, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, nPrivateSendRounds); + bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecStandardDenoms[vecBits.front()], vecStandardDenoms[vecBits.front()] * PRIVATESEND_ENTRY_MAX_SIZE, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, privateSendClient.nPrivateSendRounds); if (!fSelected) { return false; } @@ -1021,7 +1161,7 @@ bool CPrivateSendClient::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsA // TODO: add new state POOL_STATE_CONNECTING and bump MIN_PRIVATESEND_PEER_PROTO_VERSION SetState(POOL_STATE_QUEUE); nTimeLastSuccessfulStep = GetTime(); - LogPrintf("CPrivateSendClient::StartNewQueue -- pending connection, nSessionDenom: %d (%s), nSessionInputCount: %d, addr=%s\n", + LogPrintf("CPrivateSendClientSession::StartNewQueue -- pending connection, nSessionDenom: %d (%s), nSessionInputCount: %d, addr=%s\n", nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), nSessionInputCount, infoMn.addr.ToString()); strAutoDenomResult = _("Trying to connect..."); return true; @@ -1030,16 +1170,15 @@ bool CPrivateSendClient::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsA return false; } -void CPrivateSendClient::ProcessPendingDsaRequest(CConnman& connman) +bool CPrivateSendClientSession::ProcessPendingDsaRequest(CConnman& connman) { - if (!pendingDsaRequest) return; + if (!pendingDsaRequest) return false; bool fDone = connman.ForNode(pendingDsaRequest.GetAddr(), [&](CNode* pnode) { LogPrint("privatesend", "-- processing dsa queue for addr=%s\n", pnode->addr.ToString()); nTimeLastSuccessfulStep = GetTime(); // TODO: this vvvv should be here after new state POOL_STATE_CONNECTING is added and MIN_PRIVATESEND_PEER_PROTO_VERSION is bumped // SetState(POOL_STATE_QUEUE); - strAutoDenomResult = _("Mixing in progress..."); CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSACCEPT, pendingDsaRequest.GetDSA())); return true; @@ -1048,51 +1187,65 @@ void CPrivateSendClient::ProcessPendingDsaRequest(CConnman& connman) if (fDone) { pendingDsaRequest = CPendingDsaRequest(); } else if (pendingDsaRequest.IsExpired()) { - LogPrint("privatesend", "CPrivateSendClient::%s -- failed to connect to %s\n", __func__, pendingDsaRequest.GetAddr().ToString()); + LogPrint("privatesend", "CPrivateSendClientSession::%s -- failed to connect to %s\n", __func__, pendingDsaRequest.GetAddr().ToString()); SetNull(); } + + return fDone; +} + +void CPrivateSendClientManager::ProcessPendingDsaRequest(CConnman& connman) +{ + LOCK(cs_vecsessions); + for (auto& session : vecSessions) { + if (session.ProcessPendingDsaRequest(connman)) { + strAutoDenomResult = _("Mixing in progress..."); + } + } } -bool CPrivateSendClient::SubmitDenominate(CConnman& connman) +bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman) { + LOCK2(cs_main, pwalletMain->cs_wallet); + std::string strError; std::vector vecTxDSInRet; std::vector vecTxOutRet; // Submit transaction to the pool if we get here - if (nLiquidityProvider) { + if (privateSendClient.nLiquidityProvider) { // Try to use only inputs with the same number of rounds starting from the lowest number of rounds possible - for(int i = 0; i< nPrivateSendRounds; i++) { + for(int i = 0; i< privateSendClient.nPrivateSendRounds; i++) { if(PrepareDenominate(i, i + 1, strError, vecTxDSInRet, vecTxOutRet)) { - LogPrintf("CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i); + LogPrintf("CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i); return SendDenominate(vecTxDSInRet, vecTxOutRet, connman); } - LogPrint("privatesend", "CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for %d rounds, error: %s\n", i, strError); + LogPrint("privatesend", "CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for %d rounds, error: %s\n", i, strError); } } else { // Try to use only inputs with the same number of rounds starting from the highest number of rounds possible - for(int i = nPrivateSendRounds; i > 0; i--) { + for(int i = privateSendClient.nPrivateSendRounds; i > 0; i--) { if(PrepareDenominate(i - 1, i, strError, vecTxDSInRet, vecTxOutRet)) { - LogPrintf("CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i); + LogPrintf("CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i); return SendDenominate(vecTxDSInRet, vecTxOutRet, connman); } - LogPrint("privatesend", "CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for %d rounds, error: %s\n", i, strError); + LogPrint("privatesend", "CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for %d rounds, error: %s\n", i, strError); } } // We failed? That's strange but let's just make final attempt and try to mix everything - if(PrepareDenominate(0, nPrivateSendRounds, strError, vecTxDSInRet, vecTxOutRet)) { - LogPrintf("CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for all rounds, success\n"); + if(PrepareDenominate(0, privateSendClient.nPrivateSendRounds, strError, vecTxDSInRet, vecTxOutRet)) { + LogPrintf("CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for all rounds, success\n"); return SendDenominate(vecTxDSInRet, vecTxOutRet, connman); } // Should never actually get here but just in case - LogPrintf("CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for all rounds, error: %s\n", strError); + LogPrintf("CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for all rounds, error: %s\n", strError); strAutoDenomResult = strError; return false; } -bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, std::vector& vecTxDSInRet, std::vector& vecTxOutRet) +bool CPrivateSendClientSession::PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, std::vector& vecTxDSInRet, std::vector& vecTxOutRet) { if(!pwalletMain) { strErrorRet = "Wallet is not initialized"; @@ -1135,7 +1288,7 @@ bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std:: return false; } - LogPrintf("CPrivateSendClient::PrepareDenominate -- max value: %f\n", (double)nValueIn/COIN); + LogPrintf("CPrivateSendClientSession::PrepareDenominate -- max value: %f\n", (double)nValueIn/COIN); { LOCK(pwalletMain->cs_wallet); @@ -1197,7 +1350,7 @@ bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std:: } } - if (CPrivateSend::GetDenominations(vecTxOutRet) != nSessionDenom || (nSessionInputCount != 0 && vecTxOutRet.size() != nSessionInputCount)) { + if (CPrivateSend::GetDenominations(vecTxOutRet) != nSessionDenom || (nSessionInputCount != 0 && vecTxOutRet.size() != (size_t)nSessionInputCount)) { { // unlock used coins on failure LOCK(pwalletMain->cs_wallet); @@ -1206,7 +1359,7 @@ bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std:: } } keyHolderStorage.ReturnAll(); - strErrorRet = "Can't make current denominated outputs"; + strErrorRet = "Can't prepare current denominated outputs"; return false; } @@ -1215,13 +1368,13 @@ bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std:: } // Create collaterals by looping through inputs grouped by addresses -bool CPrivateSendClient::MakeCollateralAmounts(CConnman& connman) +bool CPrivateSendClientSession::MakeCollateralAmounts(CConnman& connman) { if (!pwalletMain) return false; std::vector vecTally; if(!pwalletMain->SelectCoinsGrouppedByAddresses(vecTally, false)) { - LogPrint("privatesend", "CPrivateSendClient::MakeCollateralAmounts -- SelectCoinsGrouppedByAddresses can't find any inputs!\n"); + LogPrint("privatesend", "CPrivateSendClientSession::MakeCollateralAmounts -- SelectCoinsGrouppedByAddresses can't find any inputs!\n"); return false; } @@ -1238,12 +1391,12 @@ bool CPrivateSendClient::MakeCollateralAmounts(CConnman& connman) } // If we got here then smth is terribly broken actually - LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- ERROR: Can't make collaterals!\n"); + LogPrintf("CPrivateSendClientSession::MakeCollateralAmounts -- ERROR: Can't make collaterals!\n"); return false; } // Split up large inputs or create fee sized inputs -bool CPrivateSendClient::MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated, CConnman& connman) +bool CPrivateSendClientSession::MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated, CConnman& connman) { if (!pwalletMain) return false; @@ -1283,13 +1436,13 @@ bool CPrivateSendClient::MakeCollateralAmounts(const CompactTallyItem& tallyItem bool fSuccess = pwalletMain->CreateTransaction(vecSend, wtx, reservekeyChange, nFeeRet, nChangePosRet, strFail, &coinControl, true, ONLY_NONDENOMINATED); if(!fSuccess) { - LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- ONLY_NONDENOMINATED: %s\n", strFail); + LogPrintf("CPrivateSendClientSession::MakeCollateralAmounts -- ONLY_NONDENOMINATED: %s\n", strFail); // If we failed then most likely there are not enough funds on this address. if(fTryDenominated) { // Try to also use denominated coins (we can't mix denominated without collaterals anyway). if(!pwalletMain->CreateTransaction(vecSend, wtx, reservekeyChange, nFeeRet, nChangePosRet, strFail, &coinControl, true, ALL_COINS)) { - LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- ALL_COINS Error: %s\n", strFail); + LogPrintf("CPrivateSendClientSession::MakeCollateralAmounts -- ALL_COINS Error: %s\n", strFail); reservekeyCollateral.ReturnKey(); return false; } @@ -1302,22 +1455,22 @@ bool CPrivateSendClient::MakeCollateralAmounts(const CompactTallyItem& tallyItem reservekeyCollateral.KeepKey(); - LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- txid=%s\n", wtx.GetHash().GetHex()); + LogPrintf("CPrivateSendClientSession::MakeCollateralAmounts -- txid=%s\n", wtx.GetHash().GetHex()); // use the same nCachedLastSuccessBlock as for DS mixing to prevent race CValidationState state; if(!pwalletMain->CommitTransaction(wtx, reservekeyChange, &connman, state)) { - LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- CommitTransaction failed! Reason given: %s\n", state.GetRejectReason()); + LogPrintf("CPrivateSendClientSession::MakeCollateralAmounts -- CommitTransaction failed! Reason given: %s\n", state.GetRejectReason()); return false; } - nCachedLastSuccessBlock = nCachedBlockHeight; + privateSendClient.UpdatedSuccessBlock(); return true; } // Create denominations by looping through inputs grouped by addresses -bool CPrivateSendClient::CreateDenominated(CConnman& connman) +bool CPrivateSendClientSession::CreateDenominated(CConnman& connman) { if (!pwalletMain) return false; @@ -1325,7 +1478,7 @@ bool CPrivateSendClient::CreateDenominated(CConnman& connman) std::vector vecTally; if(!pwalletMain->SelectCoinsGrouppedByAddresses(vecTally)) { - LogPrint("privatesend", "CPrivateSendClient::CreateDenominated -- SelectCoinsGrouppedByAddresses can't find any inputs!\n"); + LogPrint("privatesend", "CPrivateSendClientSession::CreateDenominated -- SelectCoinsGrouppedByAddresses can't find any inputs!\n"); return false; } @@ -1336,12 +1489,12 @@ bool CPrivateSendClient::CreateDenominated(CConnman& connman) return true; } - LogPrintf("CPrivateSendClient::CreateDenominated -- failed!\n"); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- failed!\n"); return false; } // Create denominations -bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman) +bool CPrivateSendClientSession::CreateDenominated(const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman) { if (!pwalletMain) return false; @@ -1351,7 +1504,7 @@ bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bo CAmount nValueLeft = tallyItem.nAmount; nValueLeft -= CPrivateSend::GetCollateralAmount(); // leave some room for fees - LogPrintf("CreateDenominated0: %s nValueLeft: %f\n", CBitcoinAddress(tallyItem.txdest).ToString(), (float)nValueLeft/COIN); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- 0 - %s nValueLeft: %f\n", CBitcoinAddress(tallyItem.txdest).ToString(), (float)nValueLeft/COIN); // ****** Add an output for mixing collaterals ************ / @@ -1377,13 +1530,17 @@ bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bo // and there are still larger denoms which can be used for mixing // check skipped denoms - if(IsDenomSkipped(nDenomValue)) continue; + if(privateSendClient.IsDenomSkipped(nDenomValue)) { + strAutoDenomResult = strprintf(_("Too many %f denominations, skipping."), (float)nDenomValue/COIN); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- %s\n", strAutoDenomResult); + continue; + } // find new denoms to skip if any (ignore the largest one) if(nDenomValue != vecStandardDenoms.front() && pwalletMain->CountInputsWithAmount(nDenomValue) > DENOMS_COUNT_MAX) { strAutoDenomResult = strprintf(_("Too many %f denominations, removing."), (float)nDenomValue/COIN); - LogPrintf("CPrivateSendClient::CreateDenominated -- %s\n", strAutoDenomResult); - vecDenominationsSkipped.push_back(nDenomValue); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- %s\n", strAutoDenomResult); + privateSendClient.AddSkippedDenom(nDenomValue); continue; } } @@ -1399,17 +1556,17 @@ bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bo //increment outputs and subtract denomination amount nOutputs++; nValueLeft -= nDenomValue; - LogPrintf("CreateDenominated1: totalOutputs: %d, nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f\n", nOutputsTotal + nOutputs, nOutputsTotal, nOutputs, (float)nValueLeft/COIN); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- 1 - totalOutputs: %d, nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f\n", nOutputsTotal + nOutputs, nOutputsTotal, nOutputs, (float)nValueLeft/COIN); } nOutputsTotal += nOutputs; if(nValueLeft == 0) break; } - LogPrintf("CreateDenominated2: nOutputsTotal: %d, nValueLeft: %f\n", nOutputsTotal, (float)nValueLeft/COIN); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- 2 - nOutputsTotal: %d, nValueLeft: %f\n", nOutputsTotal, (float)nValueLeft/COIN); // if there were no outputs added, start over without skipping fSkip = !fSkip; } while (nOutputsTotal == 0 && !fSkip); - LogPrintf("CreateDenominated3: nOutputsTotal: %d, nValueLeft: %f\n", nOutputsTotal, (float)nValueLeft/COIN); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- 3 - nOutputsTotal: %d, nValueLeft: %f\n", nOutputsTotal, (float)nValueLeft/COIN); // if we have anything left over, it will be automatically send back as change - there is no need to send it manually @@ -1431,7 +1588,7 @@ bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bo bool fSuccess = pwalletMain->CreateTransaction(vecSend, wtx, reservekeyChange, nFeeRet, nChangePosRet, strFail, &coinControl, true, ONLY_NONDENOMINATED); if(!fSuccess) { - LogPrintf("CPrivateSendClient::CreateDenominated -- Error: %s\n", strFail); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- Error: %s\n", strFail); keyHolderStorageDenom.ReturnAll(); return false; } @@ -1440,43 +1597,43 @@ bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bo CValidationState state; if(!pwalletMain->CommitTransaction(wtx, reservekeyChange, &connman, state)) { - LogPrintf("CPrivateSendClient::CreateDenominated -- CommitTransaction failed! Reason given: %s\n", state.GetRejectReason()); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- CommitTransaction failed! Reason given: %s\n", state.GetRejectReason()); return false; } // use the same nCachedLastSuccessBlock as for DS mixing to prevent race - nCachedLastSuccessBlock = nCachedBlockHeight; - LogPrintf("CPrivateSendClient::CreateDenominated -- txid=%s\n", wtx.GetHash().GetHex()); + privateSendClient.UpdatedSuccessBlock(); + LogPrintf("CPrivateSendClientSession::CreateDenominated -- txid=%s\n", wtx.GetHash().GetHex()); return true; } -void CPrivateSendClient::RelayIn(const CDarkSendEntry& entry, CConnman& connman) +void CPrivateSendClientSession::RelayIn(const CDarkSendEntry& entry, CConnman& connman) { if(!infoMixingMasternode.fInfoValid) return; connman.ForNode(infoMixingMasternode.addr, [&entry, &connman](CNode* pnode) { - LogPrintf("CPrivateSendClient::RelayIn -- found master, relaying message to %s\n", pnode->addr.ToString()); + LogPrintf("CPrivateSendClientSession::RelayIn -- found master, relaying message to %s\n", pnode->addr.ToString()); CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSVIN, entry)); return true; }); } -void CPrivateSendClient::SetState(PoolState nStateNew) +void CPrivateSendClientSession::SetState(PoolState nStateNew) { - LogPrintf("CPrivateSendClient::SetState -- nState: %d, nStateNew: %d\n", nState, nStateNew); + LogPrintf("CPrivateSendClientSession::SetState -- nState: %d, nStateNew: %d\n", nState, nStateNew); nState = nStateNew; } -void CPrivateSendClient::UpdatedBlockTip(const CBlockIndex *pindex) +void CPrivateSendClientManager::UpdatedBlockTip(const CBlockIndex *pindex) { nCachedBlockHeight = pindex->nHeight; - LogPrint("privatesend", "CPrivateSendClient::UpdatedBlockTip -- nCachedBlockHeight: %d\n", nCachedBlockHeight); + LogPrint("privatesend", "CPrivateSendClientManager::UpdatedBlockTip -- nCachedBlockHeight: %d\n", nCachedBlockHeight); } -void CPrivateSendClient::DoMaintenance(CConnman& connman) +void CPrivateSendClientManager::DoMaintenance(CConnman& connman) { if(fLiteMode) return; // disable all Dash specific functionality if(fMasternodeMode) return; // no client-side mixing on masternodes @@ -1488,10 +1645,10 @@ void CPrivateSendClient::DoMaintenance(CConnman& connman) static unsigned int nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN; nTick++; - privateSendClient.CheckTimeout(); - privateSendClient.ProcessPendingDsaRequest(connman); + CheckTimeout(); + ProcessPendingDsaRequest(connman); if(nDoAutoNextRun == nTick) { - privateSendClient.DoAutomaticDenominating(connman); + DoAutomaticDenominating(connman); nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN + GetRandInt(PRIVATESEND_AUTO_TIMEOUT_MAX - PRIVATESEND_AUTO_TIMEOUT_MIN); } } diff --git a/src/privatesend-client.h b/src/privatesend-client.h index 7b9c9f2ce..0d730c07f 100644 --- a/src/privatesend-client.h +++ b/src/privatesend-client.h @@ -10,17 +10,20 @@ #include "wallet/wallet.h" #include "privatesend-util.h" -class CPrivateSendClient; +class CPrivateSendClientManager; class CConnman; static const int DENOMS_COUNT_MAX = 100; +static const int MIN_PRIVATESEND_SESSIONS = 1; static const int MIN_PRIVATESEND_ROUNDS = 2; static const int MIN_PRIVATESEND_AMOUNT = 2; static const int MIN_PRIVATESEND_LIQUIDITY = 0; +static const int MAX_PRIVATESEND_SESSIONS = 10; static const int MAX_PRIVATESEND_ROUNDS = 16; static const int MAX_PRIVATESEND_AMOUNT = MAX_MONEY / COIN; static const int MAX_PRIVATESEND_LIQUIDITY = 100; +static const int DEFAULT_PRIVATESEND_SESSIONS = 4; static const int DEFAULT_PRIVATESEND_ROUNDS = 4; static const int DEFAULT_PRIVATESEND_AMOUNT = 1000; static const int DEFAULT_PRIVATESEND_LIQUIDITY = 0; @@ -34,7 +37,7 @@ static const int PRIVATESEND_KEYS_THRESHOLD_WARNING = 100; static const int PRIVATESEND_KEYS_THRESHOLD_STOP = 50; // The main object for accessing mixing -extern CPrivateSendClient privateSendClient; +extern CPrivateSendClientManager privateSendClient; class CPendingDsaRequest { @@ -75,23 +78,11 @@ public: } }; -/** Used to keep track of current status of mixing pool - */ -class CPrivateSendClient : public CPrivateSendBase +class CPrivateSendClientSession : public CPrivateSendBaseSession { private: - // Keep track of the used Masternodes - std::vector vecMasternodesUsed; - - std::vector vecDenominationsSkipped; std::vector vecOutPointLocked; - int nCachedLastSuccessBlock; - int nMinBlocksToWait; // how many blocks to wait after one successful mixing tx in non-multisession mode - - // Keep track of current block height - int nCachedBlockHeight; - int nEntriesCount; bool fLastEntryAccepted; @@ -104,19 +95,6 @@ private: CKeyHolderStorage keyHolderStorage; // storage for keys used in PrepareDenominate - /// Check for process - void CheckPool(); - void CompletedTransaction(PoolMessage nMessageID); - - bool IsDenomSkipped(CAmount nDenomValue); - - bool WaitForAnotherBlock(); - - // Make sure we have enough keys since last backup - bool CheckAutomaticBackup(); - bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman); - bool StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsAnonymized, CConnman& connman); - /// Create denominations bool CreateDenominated(CConnman& connman); bool CreateDenominated(const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman); @@ -125,8 +103,9 @@ private: bool MakeCollateralAmounts(CConnman& connman); bool MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated, CConnman& connman); - /// As a client, submit part of a future mixing transaction to a Masternode to start the process - bool SubmitDenominate(CConnman& connman); + bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman); + bool StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsAnonymized, CConnman& connman); + /// step 1: prepare denominated inputs and outputs bool PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, std::vector& vecTxDSInRet, std::vector& vecTxOutRet); /// step 2: send denominated inputs and outputs prepared in step 1 @@ -137,6 +116,10 @@ private: // Set the 'state' value, with some logging and capturing when the state changed void SetState(PoolState nStateNew); + /// Check for process + void CheckPool(); + void CompletedTransaction(PoolMessage nMessageID); + /// As a client, check and sign the final transaction bool SignFinalTransaction(const CTransaction& finalTransactionNew, CNode* pnode, CConnman& connman); @@ -145,6 +128,68 @@ private: void SetNull(); public: + CPrivateSendClientSession() : + vecOutPointLocked(), + nEntriesCount(0), + fLastEntryAccepted(false), + strLastMessage(), + strAutoDenomResult(), + infoMixingMasternode(), + txMyCollateral(), + pendingDsaRequest(), + keyHolderStorage() + {} + CPrivateSendClientSession(const CPrivateSendClientSession& other) { /* dummy copy constructor*/ SetNull(); } + + void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); + + void UnlockCoins(); + + void ResetPool(); + + std::string GetStatus(bool fWaitForBlock); + + bool GetMixingMasternodeInfo(masternode_info_t& mnInfoRet) const; + + /// Passively run mixing in the background according to the configuration in settings + bool DoAutomaticDenominating(CConnman& connman, bool fDryRun=false); + + /// As a client, submit part of a future mixing transaction to a Masternode to start the process + bool SubmitDenominate(CConnman& connman); + + bool ProcessPendingDsaRequest(CConnman& connman); + + bool CheckTimeout(); +}; + +/** Used to keep track of current status of mixing pool + */ +class CPrivateSendClientManager : public CPrivateSendBaseManager +{ +private: + // Keep track of the used Masternodes + std::vector vecMasternodesUsed; + + std::vector vecDenominationsSkipped; + + // TODO: or map ?? + std::vector vecSessions; + mutable CCriticalSection cs_vecsessions; + + int nCachedLastSuccessBlock; + int nMinBlocksToWait; // how many blocks to wait after one successful mixing tx in non-multisession mode + std::string strAutoDenomResult; + + // Keep track of current block height + int nCachedBlockHeight; + + bool WaitForAnotherBlock(); + + // Make sure we have enough keys since last backup + bool CheckAutomaticBackup(); + +public: + int nPrivateSendSessions; int nPrivateSendRounds; int nPrivateSendAmount; int nLiquidityProvider; @@ -154,40 +199,49 @@ public: int nCachedNumBlocks; //used for the overview screen bool fCreateAutoBackups; //builtin support for automatic backups - CPrivateSendClient() : + CPrivateSendClientManager() : + vecMasternodesUsed(), + vecDenominationsSkipped(), + vecSessions(), nCachedLastSuccessBlock(0), nMinBlocksToWait(1), - txMyCollateral(CMutableTransaction()), + strAutoDenomResult(), + nCachedBlockHeight(0), nPrivateSendRounds(DEFAULT_PRIVATESEND_ROUNDS), nPrivateSendAmount(DEFAULT_PRIVATESEND_AMOUNT), nLiquidityProvider(DEFAULT_PRIVATESEND_LIQUIDITY), fEnablePrivateSend(false), fPrivateSendMultiSession(DEFAULT_PRIVATESEND_MULTISESSION), nCachedNumBlocks(std::numeric_limits::max()), - fCreateAutoBackups(true) { SetNull(); } + fCreateAutoBackups(true) + {} void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); + bool IsDenomSkipped(const CAmount& nDenomValue); + void AddSkippedDenom(const CAmount& nDenomValue); void ClearSkippedDenominations() { vecDenominationsSkipped.clear(); } void SetMinBlocksToWait(int nMinBlocksToWaitIn) { nMinBlocksToWait = nMinBlocksToWaitIn; } - void ResetPool(); - void UnlockCoins(); + std::string GetStatuses(); + std::string GetSessionDenoms(); - std::string GetStatus(); - - bool GetMixingMasternodeInfo(masternode_info_t& mnInfoRet); - bool IsMixingMasternode(const CNode* pnode); + bool GetMixingMasternodesInfo(std::vector& vecMnInfoRet) const; /// Passively run mixing in the background according to the configuration in settings bool DoAutomaticDenominating(CConnman& connman, bool fDryRun=false); + void CheckTimeout(); + void ProcessPendingDsaRequest(CConnman& connman); - void CheckTimeout(); + void AddUsedMasternode(const COutPoint& outpointMn); + masternode_info_t GetNotUsedMasternode(); + + void UpdatedSuccessBlock(); void UpdatedBlockTip(const CBlockIndex *pindex); diff --git a/src/privatesend-server.cpp b/src/privatesend-server.cpp index e6f115a03..7ad012b57 100644 --- a/src/privatesend-server.cpp +++ b/src/privatesend-server.cpp @@ -76,7 +76,7 @@ void CPrivateSendServer::ProcessMessage(CNode* pfrom, const std::string& strComm } } else if(strCommand == NetMsgType::DSQUEUE) { - TRY_LOCK(cs_darksend, lockRecv); + TRY_LOCK(cs_vecqueue, lockRecv); if(!lockRecv) return; if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { @@ -282,7 +282,8 @@ void CPrivateSendServer::SetNull() // MN side vecSessionCollaterals.clear(); - CPrivateSendBase::SetNull(); + CPrivateSendBaseSession::SetNull(); + CPrivateSendBaseManager::SetNull(); } // diff --git a/src/privatesend-server.h b/src/privatesend-server.h index eb6207692..f9219627c 100644 --- a/src/privatesend-server.h +++ b/src/privatesend-server.h @@ -15,7 +15,7 @@ extern CPrivateSendServer privateSendServer; /** Used to keep track of current status of mixing pool */ -class CPrivateSendServer : public CPrivateSendBase +class CPrivateSendServer : public CPrivateSendBaseSession, public CPrivateSendBaseManager { private: // Mixing uses collateral transactions to trust parties entering the pool @@ -66,8 +66,7 @@ private: void SetNull(); public: - CPrivateSendServer() : - fUnitTest(false) { SetNull(); } + CPrivateSendServer() : vecSessionCollaterals(), fUnitTest(false) {} void ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman); diff --git a/src/privatesend.cpp b/src/privatesend.cpp index 25198a7ec..5cac0dc67 100644 --- a/src/privatesend.cpp +++ b/src/privatesend.cpp @@ -183,9 +183,10 @@ bool CDarksendBroadcastTx::IsExpired(int nHeight) return (nConfirmedHeight != -1) && (nHeight - nConfirmedHeight > 24); } -void CPrivateSendBase::SetNull() +void CPrivateSendBaseSession::SetNull() { // Both sides + LOCK(cs_darksend); nState = POOL_STATE_IDLE; nSessionID = 0; nSessionDenom = 0; @@ -196,22 +197,44 @@ void CPrivateSendBase::SetNull() nTimeLastSuccessfulStep = GetTime(); } -void CPrivateSendBase::CheckQueue() +void CPrivateSendBaseManager::SetNull() { - TRY_LOCK(cs_darksend, lockDS); + LOCK(cs_vecqueue); + vecDarksendQueue.clear(); +} + +void CPrivateSendBaseManager::CheckQueue() +{ + TRY_LOCK(cs_vecqueue, lockDS); if(!lockDS) return; // it's ok to fail here, we run this quite frequently // check mixing queue objects for timeouts std::vector::iterator it = vecDarksendQueue.begin(); while(it != vecDarksendQueue.end()) { if((*it).IsExpired()) { - LogPrint("privatesend", "CPrivateSendBase::%s -- Removing expired queue (%s)\n", __func__, (*it).ToString()); + LogPrint("privatesend", "CPrivateSendBaseManager::%s -- Removing expired queue (%s)\n", __func__, (*it).ToString()); it = vecDarksendQueue.erase(it); } else ++it; } } -std::string CPrivateSendBase::GetStateString() const +bool CPrivateSendBaseManager::GetQueueItemAndTry(CDarksendQueue& dsqRet) +{ + TRY_LOCK(cs_vecqueue, lockDS); + if(!lockDS) return false; // it's ok to fail here, we run this quite frequently + + for (auto& dsq : vecDarksendQueue) { + // only try each queue once + if(dsq.fTried || dsq.IsExpired()) continue; + dsq.fTried = true; + dsqRet = dsq; + return true; + } + + return false; +} + +std::string CPrivateSendBaseSession::GetStateString() const { switch(nState) { case POOL_STATE_IDLE: return "IDLE"; @@ -504,5 +527,5 @@ void CPrivateSend::SyncTransaction(const CTransaction& tx, const CBlockIndex *pi // When tx is 0-confirmed or conflicted, posInBlock is SYNC_TRANSACTION_NOT_IN_BLOCK and nConfirmedHeight should be set to -1 mapDSTX[txHash].SetConfirmedHeight(posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK ? -1 : pindex->nHeight); - LogPrint("privatesend", "CPrivateSendClient::SyncTransaction -- txid=%s\n", txHash.ToString()); + LogPrint("privatesend", "CPrivateSend::SyncTransaction -- txid=%s\n", txHash.ToString()); } diff --git a/src/privatesend.h b/src/privatesend.h index c8b221e10..47b0158ae 100644 --- a/src/privatesend.h +++ b/src/privatesend.h @@ -348,14 +348,11 @@ public: }; // base class -class CPrivateSendBase +class CPrivateSendBaseSession { protected: mutable CCriticalSection cs_darksend; - // The current mixing sessions in progress on the network - std::vector vecDarksendQueue; - std::vector vecEntries; // Masternode/clients entries PoolState nState; // should be one of the POOL_STATE_XXX values @@ -366,21 +363,47 @@ protected: CMutableTransaction finalMutableTransaction; // the finalized transaction ready for signing void SetNull(); - void CheckQueue(); public: - int nSessionDenom; //Users must submit an denom matching this + int nSessionDenom; //Users must submit a denom matching this int nSessionInputCount; //Users must submit a count matching this - CPrivateSendBase() { SetNull(); } + CPrivateSendBaseSession() : + vecEntries(), + nState(POOL_STATE_IDLE), + nTimeLastSuccessfulStep(0), + nSessionID(0), + finalMutableTransaction(), + nSessionDenom(0), + nSessionInputCount(0) + {} + CPrivateSendBaseSession(const CPrivateSendBaseSession& other) { /* dummy copy constructor*/ SetNull(); } - int GetQueueSize() const { return vecDarksendQueue.size(); } int GetState() const { return nState; } std::string GetStateString() const; int GetEntriesCount() const { return vecEntries.size(); } }; +// base class +class CPrivateSendBaseManager +{ +protected: + mutable CCriticalSection cs_vecqueue; + + // The current mixing sessions in progress on the network + std::vector vecDarksendQueue; + + void SetNull(); + void CheckQueue(); + +public: + CPrivateSendBaseManager() : vecDarksendQueue() {} + + int GetQueueSize() const { return vecDarksendQueue.size(); } + bool GetQueueItemAndTry(CDarksendQueue& dsqRet); +}; + // helper class class CPrivateSend { diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 1ea7c5d24..b285506dd 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -564,7 +564,7 @@ void OverviewPage::privateSendStatus() updatePrivateSendProgress(); } - QString strStatus = QString(privateSendClient.GetStatus().c_str()); + QString strStatus = QString(privateSendClient.GetStatuses().c_str()); QString s = tr("Last PrivateSend message:\n") + strStatus; @@ -573,13 +573,7 @@ void OverviewPage::privateSendStatus() ui->labelPrivateSendLastMessage->setText(s); - if(privateSendClient.nSessionDenom == 0){ - ui->labelSubmittedDenom->setText(tr("N/A")); - } else { - QString strDenom(CPrivateSend::GetDenominationsToString(privateSendClient.nSessionDenom).c_str()); - ui->labelSubmittedDenom->setText(strDenom); - } - + ui->labelSubmittedDenom->setText(QString(privateSendClient.GetSessionDenoms().c_str())); } void OverviewPage::privateSendAuto(){ @@ -642,7 +636,7 @@ void OverviewPage::togglePrivateSend(){ if(!privateSendClient.fEnablePrivateSend){ ui->togglePrivateSend->setText(tr("Start Mixing")); - privateSendClient.UnlockCoins(); + privateSendClient.ResetPool(); } else { ui->togglePrivateSend->setText(tr("Stop Mixing")); } diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 34495b4be..5e42fe20d 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -63,7 +63,7 @@ UniValue privatesend(const JSONRPCRequest& request) privateSendClient.fEnablePrivateSend = true; bool result = privateSendClient.DoAutomaticDenominating(*g_connman); - return "Mixing " + (result ? "started successfully" : ("start failed: " + privateSendClient.GetStatus() + ", will retry")); + return "Mixing " + (result ? "started successfully" : ("start failed: " + privateSendClient.GetStatuses() + ", will retry")); } if(request.params[0].get_str() == "stop") { @@ -88,19 +88,25 @@ UniValue getpoolinfo(const JSONRPCRequest& request) "Returns an object containing mixing pool related information.\n"); #ifdef ENABLE_WALLET - CPrivateSendBase* pprivateSendBase = fMasternodeMode ? (CPrivateSendBase*)&privateSendServer : (CPrivateSendBase*)&privateSendClient; + CPrivateSendBaseManager* pprivateSendBaseManager = fMasternodeMode ? (CPrivateSendBaseManager*)&privateSendServer : (CPrivateSendBaseManager*)&privateSendClient; UniValue obj(UniValue::VOBJ); - obj.push_back(Pair("state", pprivateSendBase->GetStateString())); - obj.push_back(Pair("mixing_mode", (!fMasternodeMode && privateSendClient.fPrivateSendMultiSession) ? "multi-session" : "normal")); - obj.push_back(Pair("queue", pprivateSendBase->GetQueueSize())); - obj.push_back(Pair("entries", pprivateSendBase->GetEntriesCount())); - obj.push_back(Pair("status", privateSendClient.GetStatus())); + // TODO: + // obj.push_back(Pair("state", pprivateSendBase->GetStateString())); + obj.push_back(Pair("queue", pprivateSendBaseManager->GetQueueSize())); + // obj.push_back(Pair("entries", pprivateSendBase->GetEntriesCount())); + obj.push_back(Pair("status", privateSendClient.GetStatuses())); - masternode_info_t mnInfo; - if (privateSendClient.GetMixingMasternodeInfo(mnInfo)) { - obj.push_back(Pair("outpoint", mnInfo.outpoint.ToStringShort())); - obj.push_back(Pair("addr", mnInfo.addr.ToString())); + std::vector vecMnInfo; + if (privateSendClient.GetMixingMasternodesInfo(vecMnInfo)) { + UniValue pools(UniValue::VARR); + for (const auto& mnInfo : vecMnInfo) { + UniValue pool(UniValue::VOBJ); + pool.push_back(Pair("outpoint", mnInfo.outpoint.ToStringShort())); + pool.push_back(Pair("addr", mnInfo.addr.ToString())); + pools.push_back(pool); + } + obj.push_back(Pair("pools", pools)); } if (pwalletMain) {