// 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) { 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() || dsq.nTime > GetTime() + PRIVATESEND_QUEUE_TIMEOUT) return; masternode_info_t infoMn = mnodeman.GetMasternodeInfo(dsq.vin); if(!infoMn.fInfoValid) return; if(!dsq.CheckSignature(infoMn.pubKeyMasternode)) { // we probably have outdated info mnodeman.AskForMN(pfrom, dsq.vin); return; } // if the queue is ready, submit if we can if(dsq.fReady) { if(!infoMixingMasternode.fInfoValid) return; if((CNetAddr)infoMixingMasternode.addr != (CNetAddr)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(); } } 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; } mnodeman.nDsqCount++; if(!mnodeman.UpdateLastDsq(dsq.vin)) 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(); } } 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((CNetAddr)infoMixingMasternode.addr != (CNetAddr)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", 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((CNetAddr)infoMixingMasternode.addr != (CNetAddr)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); } 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((CNetAddr)infoMixingMasternode.addr != (CNetAddr)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, GetMessageByID(PoolMessage(nMsgMessageID))); CompletedTransaction(PoolMessage(nMsgMessageID)); } } void CPrivateSendClient::ResetPool() { nCachedLastSuccessBlock = 0; txMyCollateral = CMutableTransaction(); vecMasternodesUsed.clear(); UnlockCoins(); SetNull(); } void CPrivateSendClient::SetNull() { // Client side nEntriesCount = 0; fLastEntryAccepted = false; infoMixingMasternode = masternode_info_t(); CPrivateSend::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((pCurrentBlockIndex && pCurrentBlockIndex->nHeight - nCachedLastSuccessBlock < nMinBlockSpacing) || !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, 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, 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); } } // // 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(); SetNull(); } } // // Check for various timeouts (queue objects, mixing, etc) // void CPrivateSendClient::CheckTimeout() { { TRY_LOCK(cs_darksend, 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", "CPrivateSendClient::CheckTimeout -- Removing expired queue (%s)\n", (*it).ToString()); it = vecDarksendQueue.erase(it); } else ++it; } } 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(); 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& vecTxIn, const std::vector& vecTxOut) { 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); BOOST_FOREACH(CTxIn txin, vecTxIn) vecOutPointLocked.push_back(txin.prevout); // we should already be connected to a Masternode if(!nSessionID) { LogPrintf("CPrivateSendClient::SendDenominate -- No Masternode has been selected yet.\n"); UnlockCoins(); SetNull(); return false; } if(!CheckDiskSpace()) { UnlockCoins(); 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; BOOST_FOREACH(const CTxIn& txin, vecTxIn) { LogPrint("privatesend", "CPrivateSendClient::SendDenominate -- txin=%s\n", txin.ToString()); tx.vin.push_back(txin); } 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, false, true, true)) { LogPrintf("CPrivateSendClient::SendDenominate -- AcceptToMemoryPool() failed! tx=%s", tx.ToString()); UnlockCoins(); SetNull(); return false; } } // store our entry for later use CDarkSendEntry entry(vecTxIn, vecTxOut, txMyCollateral); vecEntries.push_back(entry); RelayIn(entry); 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:") + " " + GetMessageByID(nMessageID); // if rejected at any state if(nStatusUpdate == STATUS_REJECTED) { LogPrintf("CPrivateSendClient::CheckPoolStateUpdate -- entry is rejected by Masternode\n"); UnlockCoins(); SetNull(); SetState(POOL_STATE_ERROR); strLastMessage = 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) { if(fMasterNode || pnode == NULL) return false; finalMutableTransaction = finalTransactionNew; LogPrintf("CPrivateSendClient::SignFinalTransaction -- finalMutableTransaction=%s", finalMutableTransaction.ToString()); 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(unsigned int i = 0; i < finalMutableTransaction.vout.size(); i++) { BOOST_FOREACH(const CTxOut& txout, entry.vecTxDSOut) { if(finalMutableTransaction.vout[i] == txout) { nFoundOutputsCount++; nValue1 += finalMutableTransaction.vout[i].nValue; } } } BOOST_FOREACH(const CTxOut txout, entry.vecTxDSOut) nValue2 += txout.nValue; int nTargetOuputsCount = entry.vecTxDSOut.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(); 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(); SetNull(); return false; } // push all of our signatures to the Masternode LogPrintf("CPrivateSendClient::SignFinalTransaction -- pushing sigs to the masternode, finalMutableTransaction=%s", finalMutableTransaction.ToString()); pnode->PushMessage(NetMsgType::DSSIGNFINALTX, sigs); SetState(POOL_STATE_SIGNING); nTimeLastSuccessfulStep = GetTimeMillis(); return true; } void CPrivateSendClient::NewBlock() { static int64_t nTimeNewBlockReceived = 0; //we we're processing lots of blocks, we'll just leave if(GetTime() - nTimeNewBlockReceived < 10) return; nTimeNewBlockReceived = GetTime(); LogPrint("privatesend", "CPrivateSendClient::NewBlock\n"); CheckTimeout(); } // mixing transaction was completed (failed or successful) void CPrivateSendClient::CompletedTransaction(PoolMessage nMessageID) { if(fMasterNode) return; if(nMessageID == MSG_SUCCESS) { LogPrintf("CompletedTransaction -- success\n"); nCachedLastSuccessBlock = pCurrentBlockIndex->nHeight; } else { LogPrintf("CompletedTransaction -- error\n"); } UnlockCoins(); SetNull(); strLastMessage = GetMessageByID(nMessageID); } 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(bool fDryRun) { if(!fEnablePrivateSend || fMasterNode || !pCurrentBlockIndex) 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(!fPrivateSendMultiSession && pCurrentBlockIndex->nHeight - nCachedLastSuccessBlock < nMinBlockSpacing) { 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 = vecPrivateSendDenominations.back(); // if there are no confirmed DS collateral inputs yet if(!pwalletMain->HasCollateralInputs()) { // should have some additional amount for them nValueMin += PRIVATESEND_COLLATERAL*4; } // 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 + PRIVATESEND_COLLATERAL && nBalanceDenominated < nPrivateSendAmount*COIN) return CreateDenominated(); //check if we have the collateral sized inputs if(!pwalletMain->HasCollateralInputs()) return !pwalletMain->HasCollateralInputs(false) && MakeCollateralAmounts(); if(nSessionID) { strAutoDenomResult = _("Mixing in progress..."); return false; } // Initial phase, find a Masternode // Clean if there is anything left from previous session UnlockCoins(); 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(!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)) 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)) return true; strAutoDenomResult = _("No compatible Masternode found."); return false; } bool CPrivateSendClient::JoinExistingQueue(CAmount nBalanceNeedsAnonymized) { // 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 = mnodeman.GetMasternodeInfo(dsq.vin); if(!infoMn.fInfoValid) { 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(!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 vecTxInTmp; std::vector vCoinsTmp; // Try to match their denominations if possible, select at least 1 denominations if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, vecPrivateSendDenominations[vecBits.front()], nBalanceNeedsAnonymized, vecTxInTmp, vCoinsTmp, nValueInTmp, 0, nPrivateSendRounds)) { LogPrintf("CPrivateSendClient::JoinExistingQueue -- Couldn't match denominations %d %d (%s)\n", vecBits.front(), dsq.nDenom, GetDenominationsToString(dsq.nDenom)); continue; } vecMasternodesUsed.push_back(dsq.vin); CNode* pnodeFound = NULL; { LOCK(cs_vNodes); pnodeFound = FindNode(infoMn.addr); if(pnodeFound) { if(pnodeFound->fDisconnect) { continue; } else { pnodeFound->AddRef(); } } } 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 : ConnectNode((CAddress)infoMn.addr, NULL, true); if(pnode) { infoMixingMasternode = infoMn; nSessionDenom = dsq.nDenom; pnode->PushMessage(NetMsgType::DSACCEPT, nSessionDenom, txMyCollateral); LogPrintf("CPrivateSendClient::JoinExistingQueue -- connected (from queue), sending DSACCEPT: nSessionDenom: %d (%s), addr=%s\n", nSessionDenom, 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) { 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); 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; { LOCK(cs_vNodes); pnodeFound = FindNode(infoMn.addr); if(pnodeFound) { if(pnodeFound->fDisconnect) { nTries++; continue; } else { pnodeFound->AddRef(); } } } LogPrintf("CPrivateSendClient::StartNewQueue -- attempt %d connection to Masternode %s\n", nTries, infoMn.addr.ToString()); CNode* pnode = (pnodeFound && pnodeFound->fMasternode) ? pnodeFound : ConnectNode((CAddress)infoMn.addr, 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 = GetDenominationsByAmounts(vecAmounts); } pnode->PushMessage(NetMsgType::DSACCEPT, nSessionDenom, txMyCollateral); LogPrintf("CPrivateSendClient::StartNewQueue -- connected, sending DSACCEPT, nSessionDenom: %d (%s)\n", nSessionDenom, 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() { std::string strError; std::vector vecTxInRet; 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, vecTxInRet, vecTxOutRet)) { LogPrintf("CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i); return SendDenominate(vecTxInRet, vecTxOutRet); } 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, vecTxInRet, vecTxOutRet)) { LogPrintf("CPrivateSendClient::SubmitDenominate -- Running PrivateSend denominate for all rounds, success\n"); return SendDenominate(vecTxInRet, vecTxOutRet); } // 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& vecTxInRet, 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 vecTxInRet.clear(); vecTxOutRet.clear(); // ** find the coins we'll use std::vector vecTxIn; std::vector vCoins; CAmount nValueIn = 0; CReserveKey reservekey(pwalletMain); /* Select the coins we'll use if nMinRounds >= 0 it means only denominated inputs are going in and coming out */ std::vector vecBits; if (!GetDenominationsBits(nSessionDenom, vecBits)) { strErrorRet = "Incorrect session denom"; return false; } bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecPrivateSendDenominations[vecBits.front()], GetMaxPoolAmount(), vecTxIn, 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); BOOST_FOREACH(CTxIn txin, vecTxIn) { 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 = vecPrivateSendDenominations[nBit]; if (nValueLeft - nValueDenom < 0) continue; // Note: this relies on a fact that both vectors MUST have same size std::vector::iterator it = vecTxIn.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 vecTxInRet.push_back(*it); // remove corresponting items from initial vectors vecTxIn.erase(it); vCoins.erase(it2); CScript scriptDenom; CPubKey vchPubKey; // use unique address assert(reservekey.GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked scriptDenom = GetScriptForDestination(vchPubKey.GetID()); reservekey.KeepKey(); // 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); BOOST_FOREACH(CTxIn txin, vecTxIn) { pwalletMain->UnlockCoin(txin.prevout); } } if (GetDenominations(vecTxOutRet) != nSessionDenom) { // unlock used coins on failure LOCK(pwalletMain->cs_wallet); BOOST_FOREACH(CTxIn txin, vecTxInRet) { pwalletMain->UnlockCoin(txin.prevout); } 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() { std::vector vecTally; if(!pwalletMain->SelectCoinsGrouppedByAddresses(vecTally, false)) { LogPrint("privatesend", "CPrivateSendClient::MakeCollateralAmounts -- SelectCoinsGrouppedByAddresses can't find any inputs!\n"); return false; } BOOST_FOREACH(CompactTallyItem& item, vecTally) { if(!MakeCollateralAmounts(item)) continue; return true; } LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- failed!\n"); return false; } // Split up large inputs or create fee sized inputs bool CPrivateSendClient::MakeCollateralAmounts(const CompactTallyItem& tallyItem) { 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, PRIVATESEND_COLLATERAL*4, 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.address.Get(); 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_NOT1000IFMN); if(!fSuccess) { // if we failed (most likeky not enough funds), try to use all coins instead - // MN-like funds should not be touched in any case and we can't mix denominated without collaterals anyway LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- ONLY_NONDENOMINATED_NOT1000IFMN Error: %s\n", strFail); CCoinControl *coinControlNull = NULL; fSuccess = pwalletMain->CreateTransaction(vecSend, wtx, reservekeyChange, nFeeRet, nChangePosRet, strFail, coinControlNull, true, ONLY_NOT1000IFMN); if(!fSuccess) { LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- ONLY_NOT1000IFMN Error: %s\n", strFail); 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)) { LogPrintf("CPrivateSendClient::MakeCollateralAmounts -- CommitTransaction failed!\n"); return false; } nCachedLastSuccessBlock = pCurrentBlockIndex->nHeight; return true; } // Create denominations by looping through inputs grouped by addresses bool CPrivateSendClient::CreateDenominated() { 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)) continue; return true; } LogPrintf("CPrivateSendClient::CreateDenominated -- failed!\n"); return false; } // Create denominations bool CPrivateSendClient::CreateDenominated(const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals) { std::vector vecSend; CAmount nValueLeft = tallyItem.nAmount; nValueLeft -= PRIVATESEND_COLLATERAL; // leave some room for fees LogPrintf("CreateDenominated0 nValueLeft: %f\n", (float)nValueLeft/COIN); // make our collateral address CReserveKey reservekeyCollateral(pwalletMain); CScript scriptCollateral; CPubKey vchPubKey; assert(reservekeyCollateral.GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked scriptCollateral = GetScriptForDestination(vchPubKey.GetID()); // ****** Add collateral outputs ************ / if(fCreateMixingCollaterals) { vecSend.push_back((CRecipient){scriptCollateral, PRIVATESEND_COLLATERAL*4, false}); nValueLeft -= PRIVATESEND_COLLATERAL*4; } // ****** Add denoms ************ / // make our denom addresses std::vector> reservekeyDenomVec; // try few times - skipping smallest denoms first if there are too much already, if failed - use them int nOutputsTotal = 0; bool fSkip = true; do { BOOST_REVERSE_FOREACH(CAmount nDenomValue, vecPrivateSendDenominations) { 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 != vecPrivateSendDenominations[0] && 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; CPubKey vchPubKey; // use a unique address std::shared_ptr reservekeyDenom = std::make_shared(pwalletMain); reservekeyDenomVec.push_back(reservekeyDenom); assert(reservekeyDenom->GetReservedKey(vchPubKey, false)); // should never fail, as we just unlocked scriptDenom = GetScriptForDestination(vchPubKey.GetID()); 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.address.Get(); 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_NOT1000IFMN); if(!fSuccess) { LogPrintf("CPrivateSendClient::CreateDenominated -- Error: %s\n", strFail); for(auto key : reservekeyDenomVec) key->ReturnKey(); reservekeyCollateral.ReturnKey(); LogPrintf("CPrivateSendClient::CreateDenominated -- %d keys returned\n", reservekeyDenomVec.size() + 1); return false; } for(auto key : reservekeyDenomVec) key->KeepKey(); reservekeyCollateral.KeepKey(); LogPrintf("CPrivateSendClient::CreateDenominated -- %d keys keeped\n", reservekeyDenomVec.size() + 1); if(!pwalletMain->CommitTransaction(wtx, reservekeyChange)) { LogPrintf("CPrivateSendClient::CreateDenominated -- CommitTransaction failed!\n"); return false; } // use the same nCachedLastSuccessBlock as for DS mixing to prevent race nCachedLastSuccessBlock = pCurrentBlockIndex->nHeight; LogPrintf("CPrivateSendClient::CreateDenominated -- txid=%s\n", wtx.GetHash().GetHex()); return true; } void CPrivateSendClient::RelayIn(const CDarkSendEntry& entry) { if(!infoMixingMasternode.fInfoValid) return; LOCK(cs_vNodes); CNode* pnode = FindNode(infoMixingMasternode.addr); if(pnode != NULL) { LogPrintf("CPrivateSendClient::RelayIn -- found master, relaying message to %s\n", pnode->addr.ToString()); pnode->PushMessage(NetMsgType::DSVIN, entry); } } void CPrivateSendClient::SetState(PoolState nStateNew) { LogPrintf("CPrivateSendClient::SetState -- nState: %d, nStateNew: %d\n", nState, nStateNew); nState = nStateNew; } void CPrivateSendClient::UpdatedBlockTip(const CBlockIndex *pindex) { pCurrentBlockIndex = pindex; LogPrint("privatesend", "CPrivateSendClient::UpdatedBlockTip -- pCurrentBlockIndex->nHeight: %d\n", pCurrentBlockIndex->nHeight); if(!fLiteMode && masternodeSync.IsMasternodeListSynced()) { NewBlock(); } } //TODO: Rename/move to core void ThreadCheckPrivateSendClient() { if(fLiteMode) return; // disable all Dash specific functionality 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(); nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN + GetRandInt(PRIVATESEND_AUTO_TIMEOUT_MAX - PRIVATESEND_AUTO_TIMEOUT_MIN); } } } }