// Copyright (c) 2014-2017 The Dash Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "privatesend-client.h" #include "coincontrol.h" #include "consensus/validation.h" #include "core_io.h" #include "init.h" #include "masternode-sync.h" #include "masternodeman.h" #include "script/sign.h" #include "txmempool.h" #include "util.h" #include "utilmoneystr.h" #include CPrivateSendClient privateSendClient; void CPrivateSendClient::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv, CConnman& connman) { if(fMasterNode) 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(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrint("privatesend", "DSQUEUE -- incompatible version! nVersion: %d\n", pfrom->nVersion); return; } CDarksendQueue dsq; vRecv >> dsq; // process every dsq only once BOOST_FOREACH(CDarksendQueue q, vecDarksendQueue) { if(q == dsq) { // LogPrint("privatesend", "DSQUEUE -- %s seen\n", dsq.ToString()); return; } } LogPrint("privatesend", "DSQUEUE -- %s new\n", dsq.ToString()); if(dsq.IsExpired()) return; masternode_info_t infoMn; if(!mnodeman.GetMasternodeInfo(dsq.vin.prevout, infoMn)) return; if(!dsq.CheckSignature(infoMn.pubKeyMasternode)) { // we probably have outdated info mnodeman.AskForMN(pfrom, dsq.vin.prevout, connman); return; } // 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); } } else { BOOST_FOREACH(CDarksendQueue q, vecDarksendQueue) { if(q.vin == dsq.vin) { // no way same mn can send another "not yet ready" dsq this soon LogPrint("privatesend", "DSQUEUE -- Masternode %s is sending WAY too many dsq messages\n", infoMn.addr.ToString()); return; } } int nThreshold = infoMn.nLastDsq + mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION)/5; LogPrint("privatesend", "DSQUEUE -- nLastDsq: %d threshold: %d nDsqCount: %d\n", infoMn.nLastDsq, nThreshold, mnodeman.nDsqCount); //don't allow a few nodes to dominate the queuing process if(infoMn.nLastDsq != 0 && nThreshold > mnodeman.nDsqCount) { LogPrint("privatesend", "DSQUEUE -- Masternode %s is sending too many dsq messages\n", infoMn.addr.ToString()); return; } if(!mnodeman.AllowMixing(dsq.vin.prevout)) return; LogPrint("privatesend", "DSQUEUE -- new PrivateSend queue (%s) from masternode %s\n", dsq.ToString(), infoMn.addr.ToString()); if(infoMixingMasternode.fInfoValid && infoMixingMasternode.vin.prevout == dsq.vin.prevout) { dsq.fTried = true; } vecDarksendQueue.push_back(dsq); dsq.Relay(connman); } } else if(strCommand == NetMsgType::DSSTATUSUPDATE) { if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrintf("DSSTATUSUPDATE -- incompatible version! nVersion: %d\n", pfrom->nVersion); return; } if(!infoMixingMasternode.fInfoValid) return; if(infoMixingMasternode.addr != pfrom->addr) { //LogPrintf("DSSTATUSUPDATE -- message doesn't match current Masternode: infoMixingMasternode %s addr %s\n", infoMixingMasternode.addr.ToString(), pfrom->addr.ToString()); return; } int nMsgSessionID; int nMsgState; int nMsgEntriesCount; int nMsgStatusUpdate; int nMsgMessageID; vRecv >> nMsgSessionID >> nMsgState >> nMsgEntriesCount >> nMsgStatusUpdate >> nMsgMessageID; LogPrint("privatesend", "DSSTATUSUPDATE -- nMsgSessionID %d nMsgState: %d nEntriesCount: %d nMsgStatusUpdate: %d nMsgMessageID %d\n", nMsgSessionID, nMsgState, nEntriesCount, nMsgStatusUpdate, nMsgMessageID); if(nMsgState < POOL_STATE_MIN || nMsgState > POOL_STATE_MAX) { LogPrint("privatesend", "DSSTATUSUPDATE -- nMsgState is out of bounds: %d\n", nMsgState); return; } if(nMsgStatusUpdate < STATUS_REJECTED || nMsgStatusUpdate > STATUS_ACCEPTED) { LogPrint("privatesend", "DSSTATUSUPDATE -- nMsgStatusUpdate is out of bounds: %d\n", nMsgStatusUpdate); return; } if(nMsgMessageID < MSG_POOL_MIN || nMsgMessageID > MSG_POOL_MAX) { LogPrint("privatesend", "DSSTATUSUPDATE -- nMsgMessageID is out of bounds: %d\n", nMsgMessageID); return; } LogPrint("privatesend", "DSSTATUSUPDATE -- GetMessageByID: %s\n", CPrivateSend::GetMessageByID(PoolMessage(nMsgMessageID))); if(!CheckPoolStateUpdate(PoolState(nMsgState), nMsgEntriesCount, PoolStatusUpdate(nMsgStatusUpdate), PoolMessage(nMsgMessageID), nMsgSessionID)) { LogPrint("privatesend", "DSSTATUSUPDATE -- CheckPoolStateUpdate failed\n"); } } else if(strCommand == NetMsgType::DSFINALTX) { if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrintf("DSFINALTX -- incompatible version! nVersion: %d\n", pfrom->nVersion); return; } if(!infoMixingMasternode.fInfoValid) return; if(infoMixingMasternode.addr != pfrom->addr) { //LogPrintf("DSFINALTX -- message doesn't match current Masternode: infoMixingMasternode %s addr %s\n", infoMixingMasternode.addr.ToString(), pfrom->addr.ToString()); return; } int nMsgSessionID; CTransaction txNew; vRecv >> nMsgSessionID >> txNew; if(nSessionID != nMsgSessionID) { LogPrint("privatesend", "DSFINALTX -- message doesn't match current PrivateSend session: nSessionID: %d nMsgSessionID: %d\n", nSessionID, nMsgSessionID); return; } LogPrint("privatesend", "DSFINALTX -- txNew %s", txNew.ToString()); //check to see if input is spent already? (and probably not confirmed) SignFinalTransaction(txNew, pfrom, connman); } else if(strCommand == NetMsgType::DSCOMPLETE) { if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { LogPrintf("DSCOMPLETE -- incompatible version! nVersion: %d\n", pfrom->nVersion); return; } if(!infoMixingMasternode.fInfoValid) return; if(infoMixingMasternode.addr != pfrom->addr) { LogPrint("privatesend", "DSCOMPLETE -- message doesn't match current Masternode: infoMixingMasternode=%s addr=%s\n", infoMixingMasternode.addr.ToString(), pfrom->addr.ToString()); return; } int nMsgSessionID; int nMsgMessageID; vRecv >> nMsgSessionID >> nMsgMessageID; if(nMsgMessageID < MSG_POOL_MIN || nMsgMessageID > MSG_POOL_MAX) { LogPrint("privatesend", "DSCOMPLETE -- nMsgMessageID is out of bounds: %d\n", nMsgMessageID); return; } if(nSessionID != nMsgSessionID) { LogPrint("privatesend", "DSCOMPLETE -- message doesn't match current PrivateSend session: nSessionID: %d nMsgSessionID: %d\n", nSessionID, nMsgSessionID); return; } LogPrint("privatesend", "DSCOMPLETE -- nMsgSessionID %d nMsgMessageID %d (%s)\n", nMsgSessionID, nMsgMessageID, CPrivateSend::GetMessageByID(PoolMessage(nMsgMessageID))); CompletedTransaction(PoolMessage(nMsgMessageID)); } } void CPrivateSendClient::ResetPool() { nCachedLastSuccessBlock = 0; txMyCollateral = CMutableTransaction(); vecMasternodesUsed.clear(); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); } void CPrivateSendClient::SetNull() { // Client side nEntriesCount = 0; fLastEntryAccepted = false; infoMixingMasternode = masternode_info_t(); CPrivateSendBase::SetNull(); } // // Unlock coins after mixing fails or succeeds // void CPrivateSendClient::UnlockCoins() { while(true) { TRY_LOCK(pwalletMain->cs_wallet, lockWallet); if(!lockWallet) {MilliSleep(50); continue;} BOOST_FOREACH(COutPoint outpoint, vecOutPointLocked) pwalletMain->UnlockCoin(outpoint); break; } vecOutPointLocked.clear(); } std::string CPrivateSendClient::GetStatus() { static int nStatusMessageProgress = 0; nStatusMessageProgress += 10; std::string strSuffix = ""; if(WaitForAnotherBlock() || !masternodeSync.IsBlockchainSynced()) return strAutoDenomResult; switch(nState) { case POOL_STATE_IDLE: return _("PrivateSend is idle."); case POOL_STATE_QUEUE: if( nStatusMessageProgress % 70 <= 30) strSuffix = "."; else if(nStatusMessageProgress % 70 <= 50) strSuffix = ".."; else if(nStatusMessageProgress % 70 <= 70) strSuffix = "..."; return strprintf(_("Submitted to masternode, waiting in queue %s"), strSuffix);; case POOL_STATE_ACCEPTING_ENTRIES: if(nEntriesCount == 0) { nStatusMessageProgress = 0; return strAutoDenomResult; } else if(fLastEntryAccepted) { if(nStatusMessageProgress % 10 > 8) { fLastEntryAccepted = false; nStatusMessageProgress = 0; } return _("PrivateSend request complete:") + " " + _("Your transaction was accepted into the pool!"); } else { if( nStatusMessageProgress % 70 <= 40) return strprintf(_("Submitted following entries to masternode: %u / %d"), nEntriesCount, CPrivateSend::GetMaxPoolTransactions()); else if(nStatusMessageProgress % 70 <= 50) strSuffix = "."; else if(nStatusMessageProgress % 70 <= 60) strSuffix = ".."; else if(nStatusMessageProgress % 70 <= 70) strSuffix = "..."; return strprintf(_("Submitted to masternode, waiting for more entries ( %u / %d ) %s"), nEntriesCount, CPrivateSend::GetMaxPoolTransactions(), strSuffix); } case POOL_STATE_SIGNING: if( nStatusMessageProgress % 70 <= 40) return _("Found enough users, signing ..."); else if(nStatusMessageProgress % 70 <= 50) strSuffix = "."; else if(nStatusMessageProgress % 70 <= 60) strSuffix = ".."; else if(nStatusMessageProgress % 70 <= 70) strSuffix = "..."; return strprintf(_("Found enough users, signing ( waiting %s )"), strSuffix); case POOL_STATE_ERROR: return _("PrivateSend request incomplete:") + " " + strLastMessage + " " + _("Will retry..."); case POOL_STATE_SUCCESS: return _("PrivateSend request complete:") + " " + strLastMessage; default: return strprintf(_("Unknown state: id = %u"), nState); } } bool CPrivateSendClient::GetMixingMasternodeInfo(masternode_info_t& mnInfoRet) { mnInfoRet = infoMixingMasternode.fInfoValid ? infoMixingMasternode : masternode_info_t(); return infoMixingMasternode.fInfoValid; } bool CPrivateSendClient::IsMixingMasternode(const CNode* pnode) { return infoMixingMasternode.fInfoValid && pnode->addr == infoMixingMasternode.addr; } // // Check the mixing progress and send client updates if a Masternode // void CPrivateSendClient::CheckPool() { // reset if we're here for 10 seconds if((nState == POOL_STATE_ERROR || nState == POOL_STATE_SUCCESS) && GetTimeMillis() - nTimeLastSuccessfulStep >= 10000) { LogPrint("privatesend", "CPrivateSendClient::CheckPool -- timeout, RESETTING\n"); UnlockCoins(); if (nState == POOL_STATE_ERROR) { keyHolderStorage.ReturnAll(); } else { keyHolderStorage.KeepAll(); } SetNull(); } } // // Check for various timeouts (queue objects, mixing, etc) // void CPrivateSendClient::CheckTimeout() { CheckQueue(); if(!fEnablePrivateSend && !fMasterNode) return; // catching hanging sessions if(!fMasterNode) { switch(nState) { case POOL_STATE_ERROR: LogPrint("privatesend", "CPrivateSendClient::CheckTimeout -- Pool error -- Running CheckPool\n"); CheckPool(); break; case POOL_STATE_SUCCESS: LogPrint("privatesend", "CPrivateSendClient::CheckTimeout -- Pool success -- Running CheckPool\n"); CheckPool(); break; default: break; } } int nLagTime = fMasterNode ? 0 : 10000; // if we're the client, give the server a few extra seconds before resetting. int nTimeout = (nState == POOL_STATE_SIGNING) ? PRIVATESEND_SIGNING_TIMEOUT : PRIVATESEND_QUEUE_TIMEOUT; bool fTimeout = GetTimeMillis() - nTimeLastSuccessfulStep >= nTimeout*1000 + nLagTime; if(nState != POOL_STATE_IDLE && fTimeout) { LogPrint("privatesend", "CPrivateSendClient::CheckTimeout -- %s timed out (%ds) -- restting\n", (nState == POOL_STATE_SIGNING) ? "Signing" : "Session", nTimeout); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); SetState(POOL_STATE_ERROR); strLastMessage = _("Session timed out."); } } // // 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) { if(fMasterNode) { LogPrintf("CPrivateSendClient::SendDenominate -- PrivateSend from a Masternode is not supported currently.\n"); return false; } if(txMyCollateral == CMutableTransaction()) { LogPrintf("CPrivateSendClient:SendDenominate -- PrivateSend collateral not set\n"); return false; } // lock the funds we're going to use BOOST_FOREACH(CTxIn txin, txMyCollateral.vin) vecOutPointLocked.push_back(txin.prevout); for (const auto& txdsin : vecTxDSIn) vecOutPointLocked.push_back(txdsin.prevout); // we should already be connected to a Masternode if(!nSessionID) { LogPrintf("CPrivateSendClient::SendDenominate -- No Masternode has been selected yet.\n"); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); return false; } if(!CheckDiskSpace()) { UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); fEnablePrivateSend = false; LogPrintf("CPrivateSendClient::SendDenominate -- Not enough disk space, disabling PrivateSend.\n"); return false; } SetState(POOL_STATE_ACCEPTING_ENTRIES); strLastMessage = ""; LogPrintf("CPrivateSendClient::SendDenominate -- Added transaction to pool.\n"); //check it against the memory pool to make sure it's valid { CValidationState validationState; CMutableTransaction tx; for (const auto& txdsin : vecTxDSIn) { LogPrint("privatesend", "CPrivateSendClient::SendDenominate -- txdsin=%s\n", txdsin.ToString()); tx.vin.push_back(txdsin); } BOOST_FOREACH(const CTxOut& txout, vecTxOut) { LogPrint("privatesend", "CPrivateSendClient::SendDenominate -- txout=%s\n", txout.ToString()); tx.vout.push_back(txout); } LogPrintf("CPrivateSendClient::SendDenominate -- Submitting partial tx %s", tx.ToString()); mempool.PrioritiseTransaction(tx.GetHash(), tx.GetHash().ToString(), 1000, 0.1*COIN); TRY_LOCK(cs_main, lockMain); if(!lockMain || !AcceptToMemoryPool(mempool, validationState, CTransaction(tx), false, NULL, NULL, false, true, true)) { LogPrintf("CPrivateSendClient::SendDenominate -- AcceptToMemoryPool() failed! tx=%s", tx.ToString()); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); return false; } } // store our entry for later use CDarkSendEntry entry(vecTxDSIn, vecTxOut, txMyCollateral); vecEntries.push_back(entry); RelayIn(entry, connman); nTimeLastSuccessfulStep = GetTimeMillis(); return true; } // Incoming message from Masternode updating the progress of mixing bool CPrivateSendClient::CheckPoolStateUpdate(PoolState nStateNew, int nEntriesCountNew, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID, int nSessionIDNew) { if(fMasterNode) return false; // do not update state when mixing client state is one of these if(nState == POOL_STATE_IDLE || nState == POOL_STATE_ERROR || nState == POOL_STATE_SUCCESS) return false; strAutoDenomResult = _("Masternode:") + " " + CPrivateSend::GetMessageByID(nMessageID); // if rejected at any state if(nStatusUpdate == STATUS_REJECTED) { LogPrintf("CPrivateSendClient::CheckPoolStateUpdate -- entry is rejected by Masternode\n"); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); SetState(POOL_STATE_ERROR); strLastMessage = CPrivateSend::GetMessageByID(nMessageID); return true; } if(nStatusUpdate == STATUS_ACCEPTED && nState == nStateNew) { if(nStateNew == POOL_STATE_QUEUE && nSessionID == 0 && nSessionIDNew != 0) { // new session id should be set only in POOL_STATE_QUEUE state nSessionID = nSessionIDNew; nTimeLastSuccessfulStep = GetTimeMillis(); LogPrintf("CPrivateSendClient::CheckPoolStateUpdate -- set nSessionID to %d\n", nSessionID); return true; } else if(nStateNew == POOL_STATE_ACCEPTING_ENTRIES && nEntriesCount != nEntriesCountNew) { nEntriesCount = nEntriesCountNew; nTimeLastSuccessfulStep = GetTimeMillis(); fLastEntryAccepted = true; LogPrintf("CPrivateSendClient::CheckPoolStateUpdate -- new entry accepted!\n"); return true; } } // only situations above are allowed, fail in any other case return false; } // // After we receive the finalized transaction from the Masternode, we must // 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) { if(fMasterNode || pnode == NULL) return false; finalMutableTransaction = finalTransactionNew; LogPrintf("CPrivateSendClient::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.vin.prevout.ToStringShort()); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); return false; } std::vector sigs; //make sure my inputs/outputs are present, otherwise refuse to sign BOOST_FOREACH(const CDarkSendEntry entry, vecEntries) { BOOST_FOREACH(const CTxDSIn txdsin, entry.vecTxDSIn) { /* Sign my transaction and all outputs */ int nMyInputIndex = -1; CScript prevPubKey = CScript(); CTxIn txin = CTxIn(); for(unsigned int i = 0; i < finalMutableTransaction.vin.size(); i++) { if(finalMutableTransaction.vin[i] == txdsin) { nMyInputIndex = i; prevPubKey = txdsin.prevPubKey; txin = txdsin; } } if(nMyInputIndex >= 0) { //might have to do this one input at a time? int nFoundOutputsCount = 0; CAmount nValue1 = 0; CAmount nValue2 = 0; for (const auto& txoutFinal : finalMutableTransaction.vout) { for (const auto& txout: entry.vecTxOut) { if(txoutFinal == txout) { nFoundOutputsCount++; nValue1 += txoutFinal.nValue; } } } for (const auto& txout : entry.vecTxOut) nValue2 += txout.nValue; int nTargetOuputsCount = entry.vecTxOut.size(); 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); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); return false; } const CKeyStore& keystore = *pwalletMain; LogPrint("privatesend", "CPrivateSendClient::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"); // 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)); } } } if(sigs.empty()) { LogPrintf("CPrivateSendClient::SignFinalTransaction -- can't sign anything!\n"); UnlockCoins(); keyHolderStorage.ReturnAll(); SetNull(); return false; } // push all of our signatures to the Masternode LogPrintf("CPrivateSendClient::SignFinalTransaction -- pushing sigs to the masternode, finalMutableTransaction=%s", finalMutableTransaction.ToString()); connman.PushMessage(pnode, NetMsgType::DSSIGNFINALTX, sigs); SetState(POOL_STATE_SIGNING); nTimeLastSuccessfulStep = GetTimeMillis(); return true; } // mixing transaction was completed (failed or successful) void CPrivateSendClient::CompletedTransaction(PoolMessage nMessageID) { if(fMasterNode) return; if(nMessageID == MSG_SUCCESS) { LogPrintf("CompletedTransaction -- success\n"); nCachedLastSuccessBlock = nCachedBlockHeight; keyHolderStorage.KeepAll(); } else { LogPrintf("CompletedTransaction -- error\n"); keyHolderStorage.ReturnAll(); } UnlockCoins(); SetNull(); strLastMessage = CPrivateSend::GetMessageByID(nMessageID); } bool CPrivateSendClient::WaitForAnotherBlock() { if(!masternodeSync.IsMasternodeListSynced()) return true; if(fPrivateSendMultiSession) return false; return nCachedBlockHeight - nCachedLastSuccessBlock < nMinBlocksToWait; } bool CPrivateSendClient::CheckAutomaticBackup() { switch(nWalletBackups) { case 0: LogPrint("privatesend", "CPrivateSendClient::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" return false; case -1: // 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 spaming if debug is on. LogPrint("privatesend", "CPrivateSendClient::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 spaming if debug is on. LogPrint("privatesend", "CPrivateSendClient::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); 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); strAutoDenomResult = strprintf(_("Very low number of keys left: %d"), pwalletMain->nKeysLeftSinceAutoBackup); if(fCreateAutoBackups) { LogPrint("privatesend", "CPrivateSendClient::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); } if(!errorString.empty()) { // Things are really broken LogPrintf("CPrivateSendClient::CheckAutomaticBackup -- ERROR! Failed to create automatic backup: %s\n", errorString); strAutoDenomResult = strprintf(_("ERROR! Failed to create automatic backup") + ": %s", errorString); return false; } } } else { // Wait for smth else (e.g. GUI action) to create automatic backup for us return false; } } LogPrint("privatesend", "CPrivateSendClient::CheckAutomaticBackup -- Keys left since latest backup: %d\n", pwalletMain->nKeysLeftSinceAutoBackup); return true; } // // Passively run mixing in the background to anonymize funds based on the given configuration. // bool CPrivateSendClient::DoAutomaticDenominating(CConnman& connman, bool fDryRun) { if(fMasterNode) return false; // no client-side mixing on masternodes if(!fEnablePrivateSend) return false; if(!pwalletMain || pwalletMain->IsLocked(true)) return false; if(nState != POOL_STATE_IDLE) return false; if(!masternodeSync.IsMasternodeListSynced()) { strAutoDenomResult = _("Can't mix while sync in progress."); return false; } if(!CheckAutomaticBackup()) return false; if(GetEntriesCount() > 0) { strAutoDenomResult = _("Mixing in progress..."); return false; } TRY_LOCK(cs_darksend, lockDS); if(!lockDS) { strAutoDenomResult = _("Lock is already in place."); return false; } if(!fDryRun && pwalletMain->IsLocked(true)) { strAutoDenomResult = _("Wallet is locked."); 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"); 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 nValueMin += CPrivateSend::GetMaxCollateralAmount(); } // including denoms but applying some restrictions CAmount nBalanceNeedsAnonymized = pwalletMain->GetNeedsToBeAnonymizedBalance(nValueMin); // anonymizable balance is way too small if(nBalanceNeedsAnonymized < nValueMin) { LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- Not enough funds to anonymize\n"); strAutoDenomResult = _("Not enough funds to anonymize."); return false; } // excluding denoms CAmount nBalanceAnonimizableNonDenom = pwalletMain->GetAnonymizableBalance(true); // denoms CAmount nBalanceDenominatedConf = pwalletMain->GetDenominatedBalance(); 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", (float)nValueMin/COIN, (float)nBalanceNeedsAnonymized/COIN, (float)nBalanceAnonimizableNonDenom/COIN, (float)nBalanceDenominatedConf/COIN, (float)nBalanceDenominatedUnconf/COIN, (float)nBalanceDenominated/COIN); if(fDryRun) return true; // 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) return CreateDenominated(connman); //check if we have the collateral sized inputs if(!pwalletMain->HasCollateralInputs()) return !pwalletMain->HasCollateralInputs(false) && MakeCollateralAmounts(connman); if(nSessionID) { strAutoDenomResult = _("Mixing in progress..."); return false; } // Initial phase, find a Masternode // Clean if there is anything left from previous session UnlockCoins(); keyHolderStorage.ReturnAll(); 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"); strAutoDenomResult = _("Found unconfirmed denominated outputs, will wait till they confirm to continue."); return false; } //check our collateral and create new if needed std::string strReason; if(txMyCollateral == CMutableTransaction()) { if(!pwalletMain->CreateCollateralTransaction(txMyCollateral, strReason)) { LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- create collateral error:%s\n", strReason); return false; } } else { if(!CPrivateSend::IsCollateralValid(txMyCollateral)) { LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- invalid collateral, recreating...\n"); if(!pwalletMain->CreateCollateralTransaction(txMyCollateral, strReason)) { LogPrintf("CPrivateSendClient::DoAutomaticDenominating -- create collateral error: %s\n", strReason); return false; } } } int nMnCountEnabled = mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION); // If we've used 90% of the Masternode list then drop the oldest first ~30% int nThreshold_high = nMnCountEnabled * 0.9; int nThreshold_low = nThreshold_high * 0.7; LogPrint("privatesend", "Checking vecMasternodesUsed: size: %d, threshold: %d\n", (int)vecMasternodesUsed.size(), nThreshold_high); if((int)vecMasternodesUsed.size() > nThreshold_high) { vecMasternodesUsed.erase(vecMasternodesUsed.begin(), vecMasternodesUsed.begin() + vecMasternodesUsed.size() - nThreshold_low); 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; // do not initiate queue if we are a liquidity provider to avoid useless inter-mixing if(nLiquidityProvider) return false; if(StartNewQueue(nValueMin, nBalanceNeedsAnonymized, connman)) return true; strAutoDenomResult = _("No compatible Masternode found."); return false; } bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman) { std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); // Look through the queues and see if anything matches BOOST_FOREACH(CDarksendQueue& dsq, vecDarksendQueue) { // only try each queue once if(dsq.fTried) continue; dsq.fTried = true; if(dsq.IsExpired()) continue; masternode_info_t infoMn; if(!mnodeman.GetMasternodeInfo(dsq.vin.prevout, infoMn)) { LogPrintf("CPrivateSendClient::JoinExistingQueue -- dsq masternode is not in masternode list, masternode=%s\n", dsq.vin.prevout.ToStringShort()); continue; } if(infoMn.nProtocolVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) continue; std::vector vecBits; if(!CPrivateSend::GetDenominationsBits(dsq.nDenom, vecBits)) { // incompatible denom continue; } // mixing rate limit i.e. nLastDsq check should already pass in DSQUEUE ProcessMessage // 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()); CAmount nValueInTmp = 0; std::vector vecTxDSInTmp; std::vector vCoinsTmp; // Try to match their denominations if possible, select at least 1 denominations if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, vecStandardDenoms[vecBits.front()], nBalanceNeedsAnonymized, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, nPrivateSendRounds)) { LogPrintf("CPrivateSendClient::JoinExistingQueue -- Couldn't match denominations %d %d (%s)\n", vecBits.front(), dsq.nDenom, CPrivateSend::GetDenominationsToString(dsq.nDenom)); continue; } vecMasternodesUsed.push_back(dsq.vin.prevout); CNode* pnodeFound = NULL; bool fDisconnect = false; connman.ForNode(infoMn.addr, CConnman::AllNodes, [&pnodeFound, &fDisconnect](CNode* pnode) { pnodeFound = pnode; if(pnodeFound->fDisconnect) { fDisconnect = true; } else { pnodeFound->AddRef(); } return true; }); if (fDisconnect) continue; LogPrintf("CPrivateSendClient::JoinExistingQueue -- attempt to connect to masternode from queue, addr=%s\n", infoMn.addr.ToString()); // connect to Masternode and submit the queue request CNode* pnode = (pnodeFound && pnodeFound->fMasternode) ? pnodeFound : connman.ConnectNode(CAddress(infoMn.addr, NODE_NETWORK), NULL, true); if(pnode) { infoMixingMasternode = infoMn; nSessionDenom = dsq.nDenom; connman.PushMessage(pnode, NetMsgType::DSACCEPT, nSessionDenom, txMyCollateral); LogPrintf("CPrivateSendClient::JoinExistingQueue -- connected (from queue), sending DSACCEPT: nSessionDenom: %d (%s), addr=%s\n", nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom), pnode->addr.ToString()); strAutoDenomResult = _("Mixing in progress..."); SetState(POOL_STATE_QUEUE); nTimeLastSuccessfulStep = GetTimeMillis(); if(pnodeFound) { pnodeFound->Release(); } return true; } else { LogPrintf("CPrivateSendClient::JoinExistingQueue -- can't connect, addr=%s\n", infoMn.addr.ToString()); strAutoDenomResult = _("Error connecting to Masternode."); continue; } } return false; } bool CPrivateSendClient::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsAnonymized, CConnman& connman) { int nTries = 0; int nMnCountEnabled = mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION); // ** find the coins we'll use std::vector vecTxIn; CAmount nValueInTmp = 0; if(!pwalletMain->SelectCoinsDark(nValueMin, nBalanceNeedsAnonymized, vecTxIn, nValueInTmp, 0, nPrivateSendRounds)) { // this should never happen LogPrintf("CPrivateSendClient::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); if(!infoMn.fInfoValid) { LogPrintf("CPrivateSendClient::StartNewQueue -- Can't find random masternode!\n"); strAutoDenomResult = _("Can't find random Masternode."); return false; } vecMasternodesUsed.push_back(infoMn.vin.prevout); if(infoMn.nLastDsq != 0 && infoMn.nLastDsq + nMnCountEnabled/5 > mnodeman.nDsqCount) { LogPrintf("CPrivateSendClient::StartNewQueue -- Too early to mix on this masternode!" " masternode=%s addr=%s nLastDsq=%d CountEnabled/5=%d nDsqCount=%d\n", infoMn.vin.prevout.ToStringShort(), infoMn.addr.ToString(), infoMn.nLastDsq, nMnCountEnabled/5, mnodeman.nDsqCount); nTries++; continue; } CNode* pnodeFound = NULL; bool fDisconnect = false; connman.ForNode(infoMn.addr, CConnman::AllNodes, [&pnodeFound, &fDisconnect](CNode* pnode) { pnodeFound = pnode; if(pnodeFound->fDisconnect) { fDisconnect = true; } else { pnodeFound->AddRef(); } return true; }); if (fDisconnect) { nTries++; continue; } LogPrintf("CPrivateSendClient::StartNewQueue -- attempt %d connection to Masternode %s\n", nTries, infoMn.addr.ToString()); CNode* pnode = (pnodeFound && pnodeFound->fMasternode) ? pnodeFound : connman.ConnectNode(CAddress(infoMn.addr, NODE_NETWORK), NULL, true); if(pnode) { LogPrintf("CPrivateSendClient::StartNewQueue -- connected, addr=%s\n", infoMn.addr.ToString()); infoMixingMasternode = infoMn; std::vector vecAmounts; pwalletMain->ConvertList(vecTxIn, vecAmounts); // try to get a single random denom out of vecAmounts while(nSessionDenom == 0) { nSessionDenom = CPrivateSend::GetDenominationsByAmounts(vecAmounts); } connman.PushMessage(pnode, NetMsgType::DSACCEPT, nSessionDenom, txMyCollateral); LogPrintf("CPrivateSendClient::StartNewQueue -- connected, sending DSACCEPT, nSessionDenom: %d (%s)\n", nSessionDenom, CPrivateSend::GetDenominationsToString(nSessionDenom)); strAutoDenomResult = _("Mixing in progress..."); SetState(POOL_STATE_QUEUE); nTimeLastSuccessfulStep = GetTimeMillis(); if(pnodeFound) { pnodeFound->Release(); } return true; } else { LogPrintf("CPrivateSendClient::StartNewQueue -- can't connect, addr=%s\n", infoMn.addr.ToString()); nTries++; continue; } } return false; } bool CPrivateSendClient::SubmitDenominate(CConnman& connman) { std::string strError; std::vector vecTxDSInRet; std::vector vecTxOutRet; // Submit transaction to the pool if we get here // 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--) { if(PrepareDenominate(i - 1, i, strError, vecTxDSInRet, vecTxOutRet)) { LogPrintf("CPrivateSendClient::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); } // 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"); 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); strAutoDenomResult = strError; return false; } bool CPrivateSendClient::PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, std::vector& vecTxDSInRet, std::vector& vecTxOutRet) { if(!pwalletMain) { strErrorRet = "Wallet is not initialized"; return false; } if (pwalletMain->IsLocked(true)) { strErrorRet = "Wallet locked, unable to create transaction!"; return false; } if (GetEntriesCount() > 0) { strErrorRet = "Already have pending entries in the PrivateSend pool"; return false; } // make sure returning vectors are empty before filling them up vecTxDSInRet.clear(); vecTxOutRet.clear(); // ** find the coins we'll use std::vector vecTxDSIn; std::vector vCoins; CAmount nValueIn = 0; /* Select the coins we'll use if nMinRounds >= 0 it means only denominated inputs are going in and coming out */ std::vector vecBits; if (!CPrivateSend::GetDenominationsBits(nSessionDenom, vecBits)) { strErrorRet = "Incorrect session denom"; return false; } std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecStandardDenoms[vecBits.front()], CPrivateSend::GetMaxPoolAmount(), vecTxDSIn, vCoins, nValueIn, nMinRounds, nMaxRounds); if (nMinRounds >= 0 && !fSelected) { strErrorRet = "Can't select current denominated inputs"; return false; } LogPrintf("CPrivateSendClient::PrepareDenominate -- max value: %f\n", (double)nValueIn/COIN); { LOCK(pwalletMain->cs_wallet); for (auto& txin : vecTxDSIn) { pwalletMain->LockCoin(txin.prevout); } } CAmount nValueLeft = nValueIn; // Try to add every needed denomination, repeat up to 5-PRIVATESEND_ENTRY_MAX_SIZE times. // NOTE: No need to randomize order of inputs because they were // initially shuffled in CWallet::SelectCoinsByDenominations already. int nStep = 0; int nStepsMax = 5 + GetRandInt(PRIVATESEND_ENTRY_MAX_SIZE-5+1); while (nStep < nStepsMax) { BOOST_FOREACH(int nBit, vecBits) { CAmount nValueDenom = vecStandardDenoms[nBit]; if (nValueLeft - nValueDenom < 0) continue; // Note: this relies on a fact that both vectors MUST have same size std::vector::iterator it = vecTxDSIn.begin(); std::vector::iterator it2 = vCoins.begin(); while (it2 != vCoins.end()) { // we have matching inputs if ((*it2).tx->vout[(*it2).i].nValue == nValueDenom) { // add new input in resulting vector vecTxDSInRet.push_back(*it); // remove corresponting items from initial vectors vecTxDSIn.erase(it); vCoins.erase(it2); CScript scriptDenom = keyHolderStorage.AddKey(pwalletMain).GetScriptForDestination(); // add new output CTxOut txout(nValueDenom, scriptDenom); vecTxOutRet.push_back(txout); // subtract denomination amount nValueLeft -= nValueDenom; // step is complete break; } ++it; ++it2; } } if(nValueLeft == 0) break; nStep++; } { // unlock unused coins LOCK(pwalletMain->cs_wallet); for (auto& txin : vecTxDSIn) { pwalletMain->UnlockCoin(txin.prevout); } } if (CPrivateSend::GetDenominations(vecTxOutRet) != nSessionDenom) { // unlock used coins on failure LOCK(pwalletMain->cs_wallet); for (auto& txin : vecTxDSInRet) { pwalletMain->UnlockCoin(txin.prevout); } keyHolderStorage.ReturnAll(); strErrorRet = "Can't make current denominated outputs"; return false; } // We also do not care about full amount as long as we have right denominations return true; } // Create collaterals by looping through inputs grouped by addresses bool CPrivateSendClient::MakeCollateralAmounts(CConnman& connman) { std::vector vecTally; if(!pwalletMain->SelectCoinsGrouppedByAddresses(vecTally, false)) { LogPrint("privatesend", "CPrivateSendClient::MakeCollateralAmounts -- SelectCoinsGrouppedByAddresses can't find any inputs!\n"); return false; } // First try to use only non-denominated funds BOOST_FOREACH(CompactTallyItem& item, vecTally) { if(!MakeCollateralAmounts(item, false, connman)) continue; return true; } // There should be at least some denominated funds we should be able to break in pieces to continue mixing BOOST_FOREACH(CompactTallyItem& item, vecTally) { if(!MakeCollateralAmounts(item, true, connman)) continue; return true; } // If we got here then smth is terribly broken actually LogPrintf("CPrivateSendClient::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) { LOCK2(cs_main, pwalletMain->cs_wallet); // denominated input is always a single one, so we can check its amount directly and return early if(!fTryDenominated && tallyItem.vecTxIn.size() == 1 && CPrivateSend::IsDenominatedAmount(tallyItem.nAmount)) return false; CWalletTx wtx; CAmount nFeeRet = 0; int nChangePosRet = -1; std::string strFail = ""; std::vector vecSend; // make our collateral address CReserveKey reservekeyCollateral(pwalletMain); // make our change address CReserveKey reservekeyChange(pwalletMain); CScript scriptCollateral; CPubKey vchPubKey; assert(reservekeyCollateral.GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked scriptCollateral = GetScriptForDestination(vchPubKey.GetID()); vecSend.push_back((CRecipient){scriptCollateral, CPrivateSend::GetMaxCollateralAmount(), false}); // try to use non-denominated and not mn-like funds first, select them explicitly CCoinControl coinControl; coinControl.fAllowOtherInputs = false; coinControl.fAllowWatchOnly = false; // send change to the same address so that we were able create more denoms out of it later coinControl.destChange = tallyItem.txdest; BOOST_FOREACH(const CTxIn& txin, tallyItem.vecTxIn) coinControl.Select(txin.prevout); bool fSuccess = pwalletMain->CreateTransaction(vecSend, wtx, reservekeyChange, nFeeRet, nChangePosRet, strFail, &coinControl, true, ONLY_NONDENOMINATED); if(!fSuccess) { LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- ONLY_NONDENOMINATED: %s\n", strFail); // If we failed then most likeky 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); reservekeyCollateral.ReturnKey(); return false; } } else { // Nothing else we can do. reservekeyCollateral.ReturnKey(); return false; } } reservekeyCollateral.KeepKey(); LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- txid=%s\n", wtx.GetHash().GetHex()); // use the same nCachedLastSuccessBlock as for DS mixinx to prevent race if(!pwalletMain->CommitTransaction(wtx, reservekeyChange, &connman)) { LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- CommitTransaction failed!\n"); return false; } nCachedLastSuccessBlock = nCachedBlockHeight; return true; } // Create denominations by looping through inputs grouped by addresses bool CPrivateSendClient::CreateDenominated(CConnman& connman) { LOCK2(cs_main, pwalletMain->cs_wallet); std::vector vecTally; if(!pwalletMain->SelectCoinsGrouppedByAddresses(vecTally)) { LogPrint("privatesend", "CPrivateSendClient::CreateDenominated -- SelectCoinsGrouppedByAddresses can't find any inputs!\n"); return false; } bool fCreateMixingCollaterals = !pwalletMain->HasCollateralInputs(); BOOST_FOREACH(CompactTallyItem& item, vecTally) { if(!CreateDenominated(item, fCreateMixingCollaterals, connman)) continue; return true; } LogPrintf("CPrivateSendClient::CreateDenominated -- failed!\n"); return false; } // Create denominations bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman) { std::vector vecSend; CKeyHolderStorage keyHolderStorageDenom; CAmount nValueLeft = tallyItem.nAmount; nValueLeft -= CPrivateSend::GetCollateralAmount(); // leave some room for fees LogPrintf("CreateDenominated0 nValueLeft: %f\n", (float)nValueLeft/COIN); // ****** Add an output for mixing collaterals ************ / if(fCreateMixingCollaterals) { CScript scriptCollateral = keyHolderStorageDenom.AddKey(pwalletMain).GetScriptForDestination(); vecSend.push_back((CRecipient){ scriptCollateral, CPrivateSend::GetMaxCollateralAmount(), false }); nValueLeft -= CPrivateSend::GetMaxCollateralAmount(); } // ****** Add outputs for denoms ************ / // try few times - skipping smallest denoms first if there are too many of them already, if failed - use them too int nOutputsTotal = 0; bool fSkip = true; do { std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); BOOST_REVERSE_FOREACH(CAmount nDenomValue, vecStandardDenoms) { if(fSkip) { // Note: denoms are skipped if there are already DENOMS_COUNT_MAX of them // and there are still larger denoms which can be used for mixing // check skipped denoms if(IsDenomSkipped(nDenomValue)) 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); continue; } } int nOutputs = 0; // add each output up to 11 times until it can't be added again while(nValueLeft - nDenomValue >= 0 && nOutputs <= 10) { CScript scriptDenom = keyHolderStorageDenom.AddKey(pwalletMain).GetScriptForDestination(); vecSend.push_back((CRecipient){ scriptDenom, nDenomValue, false }); //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); } nOutputsTotal += nOutputs; if(nValueLeft == 0) break; } LogPrintf("CreateDenominated2: 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); // if we have anything left over, it will be automatically send back as change - there is no need to send it manually CCoinControl coinControl; coinControl.fAllowOtherInputs = false; coinControl.fAllowWatchOnly = false; // send change to the same address so that we were able create more denoms out of it later coinControl.destChange = tallyItem.txdest; BOOST_FOREACH(const CTxIn& txin, tallyItem.vecTxIn) coinControl.Select(txin.prevout); CWalletTx wtx; CAmount nFeeRet = 0; int nChangePosRet = -1; std::string strFail = ""; // make our change address CReserveKey reservekeyChange(pwalletMain); bool fSuccess = pwalletMain->CreateTransaction(vecSend, wtx, reservekeyChange, nFeeRet, nChangePosRet, strFail, &coinControl, true, ONLY_NONDENOMINATED); if(!fSuccess) { LogPrintf("CPrivateSendClient::CreateDenominated -- Error: %s\n", strFail); keyHolderStorageDenom.ReturnAll(); return false; } keyHolderStorageDenom.KeepAll(); if(!pwalletMain->CommitTransaction(wtx, reservekeyChange, &connman)) { LogPrintf("CPrivateSendClient::CreateDenominated -- CommitTransaction failed!\n"); return false; } // use the same nCachedLastSuccessBlock as for DS mixing to prevent race nCachedLastSuccessBlock = nCachedBlockHeight; LogPrintf("CPrivateSendClient::CreateDenominated -- txid=%s\n", wtx.GetHash().GetHex()); return true; } void CPrivateSendClient::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()); connman.PushMessage(pnode, NetMsgType::DSVIN, entry); return true; }); } void CPrivateSendClient::SetState(PoolState nStateNew) { LogPrintf("CPrivateSendClient::SetState -- nState: %d, nStateNew: %d\n", nState, nStateNew); nState = nStateNew; } void CPrivateSendClient::UpdatedBlockTip(const CBlockIndex *pindex) { nCachedBlockHeight = pindex->nHeight; LogPrint("privatesend", "CPrivateSendClient::UpdatedBlockTip -- nCachedBlockHeight: %d\n", nCachedBlockHeight); } //TODO: Rename/move to core void ThreadCheckPrivateSendClient(CConnman& connman) { if(fLiteMode) return; // disable all Dash specific functionality if(fMasterNode) return; // no client-side mixing on masternodes static bool fOneThread; if(fOneThread) return; fOneThread = true; // Make this thread recognisable as the PrivateSend thread RenameThread("dash-ps-client"); unsigned int nTick = 0; unsigned int nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN; while (true) { MilliSleep(1000); if(masternodeSync.IsBlockchainSynced() && !ShutdownRequested()) { nTick++; privateSendClient.CheckTimeout(); if(nDoAutoNextRun == nTick) { privateSendClient.DoAutomaticDenominating(connman); nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN + GetRandInt(PRIVATESEND_AUTO_TIMEOUT_MAX - PRIVATESEND_AUTO_TIMEOUT_MIN); } } } }